Source Code Game GUI

What is The Humble-Nishiyama Randomness Game?

The Humble-Nishiyama Randomness Game is a game invented by two mathematicians: Steve Humble and Yutaka Nishiyama. It is a game that seems fair at the first glance, yet has a way to increase a chance of winning significantly.

The game is based on the idea of Penney Ante game in which 2 players select their own sequential outcomes (n = 3) of a coin toss (either HEADS or TAILS). The coin will be flipped repeatedly until one of the selected sequences comes first, and whose the sequence appears is the winner of the game. In the case of The Humble-Nishiyama Randomness Game, instead of using a coin, a deck of cards consisting 26 black cards and 26 red cards is used (either RED or BLACK).

The rule seems straight forward and fair. However, there is one great strategy that allows Player(B) has a bigger chance of winning than Player(A) up to 70%+

NOTE: Player(A) represents the first player who calls a sequence.

The Winning Strategy

Assume that Player(A) has selected a sequence RED RED BLACK. All Player(B) needs to do is that use his/her opponent sequence as a reference, take the second value, convert it into the opposite value, then put it in front of the sequence, finally ditch the last value.

Example Selecting RED RED BLACK

  • Take the second value and convert it to the opposite RED -> BLACK
  • Put the converted value in front of the sequence -> BLACK RED RED BLACK
  • Finally ditch the last value -> BLACK RED RED

According to the table below, with this strategy, it is guaranteed that Player(B) will always have a higher chance against Player(A) whatsoever.

probability table

Experimental Procedures

The rules and the strategy are easy to understand, yet clearer evidence is still needed. Therefore, I decided to create a program that simulates the nature of the game. First, I created a GameEngine class that contains everything we need (e.g., deck, rules, algorithm, play, experiment).

  • Create a GameEngine
# Import libraries
import random
from typing import List, Dict, Any


# Core Game Engine
class GameEngine:
    """ Two players play the game using an ordinary deck of cards.
        The cards will be dealt out in a row, one after another.
        Before the dealing begins, each player claims an ordered sequence of colors that might turn up,
        for example “red black red” (RBR) or “black red red” (BRR).
        As the cards are dealt, if three successive cards turn up in one of these sequences,
        the player who claimed it gets to collect those three cards as a trick, and the dealing continues.
        When all 52 cards have been dealt, the player who has collected the most tricks wins
        (In a typical game, 7 to 9 tricks are won.)
    """
    def __init__(self) -> None:
        """ Generate a deck of cards containing blacks (26) and reds (26) """
        self.all_cards = ['black' for _ in range(26)] + ['red' for _ in range(26)]
  • Create a card selection: with and without winning strategy
    def _card_selection(self) -> List[str]:
        """ Select patterns of cards (n=3)
            return ['black', 'black', 'red']
        """
        return [random.choice(self.all_cards) for _ in range(3)]

    def _card_selection_winning(self, reference_sequence: List[str]) -> List[str]:
        """ Apply a winning method
            Take a middle value, conjugate, put it in fromt of the sequence, and ditch the last value
            Ex.1) BBR -> RBBR -> RBB
            Ex.2) RRR -> BRRR -> BRR
        """
        if not isinstance(reference_sequence, list) or len(reference_sequence) not in [3]:
            return []
        converted_value = 'red' if reference_sequence[1] in ['black'] else 'black'
        winning_sequence = reference_sequence[:2]
        winning_sequence.insert(0, converted_value)
        return winning_sequence
  • Create a core gameplay
    def _play(self, is_winning_method_used: bool = False) -> Dict[str: bool]:
        """ Play the game """
        deck = self.all_cards.copy() # Get a dek of cards
        random.shuffle(deck) # Shuffle a deck (in place)

        # Select patterns
        computer_sequence = self._card_selection()
        player_sequence =  self._card_selection_winning(computer_sequence) \
                                if is_winning_method_used else self._card_selection()

        # Start playing game
        current_game_sequence = []
        final_result = {}
        while deck:
            drawn_card = deck.pop()
            current_game_sequence.append(drawn_card)
            # Adjust current_game_sequence to contain only three elements
            if len(current_game_sequence) > 3:
                current_game_sequence = current_game_sequence[-3:]
            # Check the result
            if len(current_game_sequence) == 3:
                game_result = self._check_result(computer_sequence, player_sequence, current_game_sequence)
                if True in game_result.values():
                    final_result = game_result.copy()
                    break

        return final_result
  • Create a result checking method
    def _check_result(self, computer_sequence: List[str],
                                player_sequence: List[str],
                                current_game_sequence: List[str]) -> Dict[str, bool]:
        """ Check the result of a game
            All sequences must contain the same length of elements
            return the result of a player winnig a game or not
        """
        result = {
            'computer_has_won': False,
            'player_has_won': False
        }

        if computer_sequence == current_game_sequence:
            result['computer_has_won'] = True

        if player_sequence == current_game_sequence:
            result['player_has_won'] = True

        return result
  • In order to make it easier for the experiment, I also wrote a method to run the game n times.
    def start_experiment(self, is_winning_method_used: bool = False, n: int = 1000) -> Dict[str, int]:
        """ Start an experiment and Display the result """
        experimental_result = {'player_won': 0, 'computer_won': 0}
        if not isinstance(n, int):
            return None

        for _ in range(n):
            game_result = self._play(is_winning_method_used)
            experimental_result['player_won'] += game_result['player_has_won']
            experimental_result['computer_won'] += game_result['computer_has_won']

        return experimental_result
  • Lastly, putting them all together, we got a complete core game engine to be used for the experiment.
# Import libraries
import random
from typing import List, Dict, Any


# Core Game Engine
class GameEngine:
    """ Two players play the game using an ordinary deck of cards.
        The cards will be dealt out in a row, one after another.
        Before the dealing begins, each player claims an ordered sequence of colors that might turn up,
        for example “red black red” (RBR) or “black red red” (BRR).
        As the cards are dealt, if three successive cards turn up in one of these sequences,
        the player who claimed it gets to collect those three cards as a trick, and the dealing continues.
        When all 52 cards have been dealt, the player who has collected the most tricks wins
        (In a typical game, 7 to 9 tricks are won.)
    """

    def __init__(self) -> None:
        """ Generate a deck of cards containing blacks (26) and reds (26) """
        self.all_cards = ['black' for _ in range(26)] + ['red' for _ in range(26)]

    def _card_selection(self) -> List[str]:
        """ Select patterns of cards (n=3)
            return ['black', 'black', 'red']
        """
        return [random.choice(self.all_cards) for _ in range(3)]

    def _card_selection_winning(self, reference_sequence: List[str]) -> List[str]:
        """ Apply a winning method
            Take a middle value, conjugate, put it in fromt of the sequence, and ditch the last value
            Ex.1) BBR -> RBBR -> RBB
            Ex.2) RRR -> BRRR -> BRR
        """
        if not isinstance(reference_sequence, list) or len(reference_sequence) not in [3]:
            return []
        converted_value = 'red' if reference_sequence[1] in ['black'] else 'black'
        winning_sequence = reference_sequence[:2]
        winning_sequence.insert(0, converted_value)
        return winning_sequence

    def _check_result(self, computer_sequence: List[str],
                                player_sequence: List[str],
                                current_game_sequence: List[str]) -> Dict[str, bool]:
        """ Check the result of a game
            All sequences must contain the same length of elements
            return the result of a player winnig a game or not
        """
        result = {
            'computer_has_won': False,
            'player_has_won': False
        }

        if computer_sequence == current_game_sequence:
            result['computer_has_won'] = True

        if player_sequence == current_game_sequence:
            result['player_has_won'] = True

        return result

    def _play(self, is_winning_method_used: bool = False) -> Dict[str: bool]:
        """ Play the game """
        deck = self.all_cards.copy() # Get a dek of cards
        random.shuffle(deck) # Shuffle a deck (in place)

        # Select patterns
        computer_sequence = self._card_selection()
        player_sequence =  self._card_selection_winning(computer_sequence) \
                                if is_winning_method_used else self._card_selection()

        # Start playing game
        current_game_sequence = []
        final_result = {}
        while deck:
            drawn_card = deck.pop()
            current_game_sequence.append(drawn_card)
            # Adjust current_game_sequence to contain only three elements
            if len(current_game_sequence) > 3:
                current_game_sequence = current_game_sequence[-3:]
            # Check the result
            if len(current_game_sequence) == 3:
                game_result = self._check_result(computer_sequence, player_sequence, current_game_sequence)
                if True in game_result.values():
                    final_result = game_result.copy()
                    break

        return final_result

    def start_experiment(self, is_winning_method_used: bool = False, n: int = 1000) -> Dict[str, int]:
        """ Start an experiment and Display the result """
        experimental_result = {'player_won': 0, 'computer_won': 0}
        if not isinstance(n, int):
            return None

        for _ in range(n):
            game_result = self._play(is_winning_method_used)
            experimental_result['player_won'] += game_result['player_has_won']
            experimental_result['computer_won'] += game_result['computer_has_won']

        return experimental_result

As can be seen from the method start_experiment, the default value of n is 1000 which means the game will be played 1000 times. To start the experiment, I wrote the main function as follows:

# Import libraries
import pandas as pd
import matplotlib.pyplot as plt
from game_engine import GameEngine


# Load Core GameEngine
game_engine = GameEngine()


# Main function
def main() -> None:
    # Without a winning method
    without_winning_method_res = game_engine.start_experiment(is_winning_method_used=False)

    # With a winning method
    with_winning_method_res = game_engine.start_experiment(is_winning_method_used=True)

    # Prepare DataFrame
    df_without_winning_method = pd.DataFrame([without_winning_method_res])
    df_without_winning_method['cat'] = 'without'
    df_with_winning_method_res = pd.DataFrame([with_winning_method_res])
    df_with_winning_method_res['cat'] = 'with'
    df_final = pd.concat([df_without_winning_method, df_with_winning_method_res])
    df_final = df_final.set_index('cat')
    print(df_final)

    # Visualisation
    df_final.plot.bar()
    plt.show()

    return None


# Execute the script
if __name__ == '__main__':
    main()

Results

The results showed that playing with the winning strategy, Player(B) had a greater chance of winning (72.9%) against Player(A) from 1000 games played. On the other hand, playing without the winning strategy both players had a similar chance of winning 50-50.

NOTE: In this case, Player(A) is represented by computer

         player_won  computer_won
cat
without         555           575
with            729           271
figure 1

Summary

The game that seems like a perfectly-balanced one may not be as it presents. A significant advantage can always be gained with the right strategies. As can be seen earlier that with a good mathematic strategy, one player could win more than 70% of the time.

Furthermore, with a combination of probability and computer programming, everything can be proved. Therefore, this stresses that technical skills are highly needed and can lead to new exploratory opportunities.

References