Skip to article frontmatterSkip to article content
from __init__ import install_dependencies

await install_dependencies()
import random
from ipywidgets import interact
%reload_ext divewidgets

In this notebook, you will write a game called Mastermind. Play the video below to learn about the rule of the game.

  1. Mastermind first creates a hidden code of length code_length consisting code pegs with possibly duplicate colors chosen from a sequence of colors.
  2. Coderbreaker provides a guess of the code.
  3. Mastermind generates a feedback consisting of key pegs of black and white colors:
    • The number of black pegs (black_key_pegs_count) is the number of code pegs that are the correct colors in the correct positions.
    • The number of white pegs (white_key_pegs_count) is the number of code pegs that are the correct colors but in incorrect positions.
    • Each code peg should be counted only once, i.e., a code peg cannot be awarded more than one key peg. E.g.,
      • If the code is 'RBGG' and guess is 'BGGG', then
      • the feedback should be 'bbw' with
        • black_key_pegs_count == 2 because of __GG in the guess, and
        • white_key_pegs_count == 1 because of _B__ in the guess.
        • _G__ in the guess should not be awarded an additional white peg because __GG in the code has been counted.
  4. Codebreaker wins if the code is correctly guessed within a certain number (max_num_guesses) of guesses.

Random Code Generation

The first exercise is to generate a random hidden code so even one person can play the game as Codebreaker. Watch the following video to understand how computers generate random numbers.

Pseudo-random numbers

To generate random content in Python, we can use the random module imported at the beginning of the notebook. The module provides some useful functions to generate random objects as follows.

for i in range(10):
    print(random.random())  # random floating point numbers in [0,1)
for i in range(10):
    print(random.randint(3, 10), end=" ")  # random integer in range [3,10]
for i in range(10):
    print(random.choice("RBG"), end="")  # random element in the sequence 'RBG'

We can generate a reproducible pseudo-random sequence by specifying the seed.

# repeatedly run the cell to see new sequences.
random.seed(123456)
for i in range(10):
    print(random.randint(3, 10), end=" ")

By default random uses the system time as the seed. We can call seed without any argument to revert to the default behavior.

# repeatedly run the cell to see new sequences.
random.seed()
for i in range(10):
    print(random.randint(3, 10), end=" ")
def get_code(colors, code_length):
    code = ''
    # YOUR CODE HERE
    raise NotImplementedError
    return code

Guess Validation

# YOUR CODE HERE
raise NotImplementedError
Source
# tests
assert valid_code("RBG", 1, "R") == True
assert valid_code("RBG", 2, "B") == False
assert valid_code("RBG", 2, "RP") == False
assert valid_code("RBG", 0, "") == True

Feedback Generation

According to the rules of Mastermind, double-counting of a single peg (as black and white) is not allowed. To facilitate this check, we have written a new module markposition that allows you to mark any non-negative integer position as counted.

# YOUR CODE HERE
raise NotImplementedError
Source
# tests
reset_all_to_not_counted()
mark_as_counted(3)
assert check_if_counted(3) and not check_if_counted(0)
def markmypositions():
   # YOUR CODE HERE
   raise NotImplementedError
Source
# tests
markmypositions()
for i in range(11):
    assert not check_if_counted(i) if i % 2 else check_if_counted(i)
# YOUR CODE HERE
raise NotImplementedError
Source
# tests
def test_get_feedback(feedback, code, guess):
    feedback_ = get_feedback(code, guess)
    correct = feedback == feedback_
    if not correct:
        print(
            f'With code="{code}" and guess="{guess}", feedback should be "{feedback}", not "{feedback_}".'
        )
    assert correct


test_get_feedback(10 * "b" + "w" * 0, "RGBRGBRGBY", "RGBRGBRGBY")
test_get_feedback(0 * "b" + "w" * 10, "RGBRGBRGBY", "YRGBRGBRGB")
test_get_feedback(8 * "b" + "w" * 0, "RGRGRGRG", "RGRGRGRG")
test_get_feedback(0 * "b" + "w" * 8, "RGRGRGRG", "GRGRGRGR")
test_get_feedback(0 * "b" + "w" * 6, "RRRRGGG", "GGGGRRR")
test_get_feedback(1 * "b" + "w" * 6, "RRRRGGG", "GGGRRRR")
test_get_feedback(5 * "b" + "w" * 2, "RRRRGGG", "RRRGGGR")
test_get_feedback(1 * "b" + "w" * 0, "RRRRGGG", "RYYPPBB")
test_get_feedback(0 * "b" + "w" * 1, "RRRRG", "GBBBB")
test_get_feedback(0 * "b" + "w" * 0, "RRRRG", "YBBBB")

Play the Game

After finishing the previous exercises, you can play the game as a code breaker against a random mastermind.

# mastermind
import ipywidgets as widgets
from IPython.display import HTML, display


def main():
    """The main function that runs the mastermind game."""
    max_num_guesses = code_length = code = num_guesses_left = None
    is_game_ended = True
    colors = "ROYGBP"
    color_code = {
        "R": "#F88,#F00,#800",
        "O": "#FD8,#F80,#840",
        "Y": "#FF8,#FF0,#AA0",
        "G": "#8F8,#0F0,#080",
        "B": "#88F,#00F,#008",
        "P": "#F8F,#F0F,#808",
        "b": "#888,#000,#000",
        "w": "#FFF,#EEE,#888",
    }

    # returns the HTML code for a colored peg.
    def getPeg(color, size=30):
        return """<div style='display:inline-block;
                              background-image: radial-gradient(circle, {0}); 
                              width:{1}px; height:{1}px; border-radius:50%;'>
                  </div>""".format(
            color_code[color], size
        )

    colors_display = widgets.HBox(
        [widgets.Label(value="Color codes:")]
        + [
            widgets.HBox([widgets.Label(value=color), widgets.HTML(getPeg(color))])
            for color in colors
        ]
    )

    max_num_guesses_input = widgets.IntSlider(
        min=5, max=15, value=10, description="# guesses:"
    )
    code_length_input = widgets.IntSlider(
        min=2, max=10, value=4, description="Code length:"
    )
    code_input = widgets.Password(description="Code:")
    start_new_game_button = widgets.Button(description="Start a new game")

    guess_input = widgets.Text(description="Guess:")
    submit_guess_button = widgets.Button(description="Submit guess")
    board = widgets.Output()
    message = widgets.Output()

    display(
        widgets.VBox(
            [
                max_num_guesses_input,
                code_length_input,
                colors_display,
                widgets.HBox([code_input, start_new_game_button]),
                widgets.HBox([guess_input, submit_guess_button]),
                board,
                message,
            ]
        )
    )

    # A listener that starts a new game
    def start_new_game(button):
        nonlocal code, num_guesses_left, is_game_ended, max_num_guesses, code_length
        max_num_guesses = max_num_guesses_input.value
        code_length = code_length_input.value
        board.clear_output()
        message.clear_output()
        code = code_input.value or get_code(colors, code_length)
        with message:
            if not valid_code(colors, code_length, code):
                display(
                    HTML(
                        """<p>The code {} is invalid.<br>
                        Leave the code box empty to randomly generated a code.
                        </p>""".format(
                            code
                        )
                    )
                )
                is_game_ended = True
            else:
                num_guesses_left = max_num_guesses
                is_game_ended = num_guesses_left <= 0
                display(
                    HTML(
                        "<p>Game started! {} Guesses left.</p>".format(num_guesses_left)
                    )
                )

    # A listener that submits a guess
    def submit_guess(button):
        nonlocal num_guesses_left, is_game_ended
        guess = guess_input.value
        with message:
            message.clear_output()
            if is_game_ended:
                display(
                    HTML(
                        """<p>Game has not started.<br> 
                        Please start a new game.</p>"""
                    )
                )
                return
            if not valid_code(colors, code_length, guess):
                display(HTML("<p>Invalid guess.</p>"))
                return
        feedback = get_feedback(code, guess)
        num_guesses_left -= 1
        with board:
            content = ""
            for k in guess:
                content += getPeg(k)
            content += """<div style='display:inline-block; 
                             margin: 0px 5px 0px 30px; 
                             position:relative; top:5px;'>Feeback:</div>
                          <div style='display:inline-block; 
                             border: 1px solid; width:120px; height:30px;'>"""
            for k in feedback:
                content += getPeg(k, 28)
            content += "</div>"
            display(HTML(content))

        with message:
            message.clear_output()
            if feedback == "b" * code_length:
                is_game_ended = True
                display(
                    HTML(
                        "<p>You won with {} guesses left!</p>".format(num_guesses_left)
                    )
                )
                return
            is_game_ended = num_guesses_left <= 0
            if is_game_ended:
                display(HTML("<p>Game over...</p>"))
                return
            display(HTML("<p>{} Guesses left.</p>".format(num_guesses_left)))

    start_new_game_button.on_click(start_new_game)
    submit_guess_button.on_click(submit_guess)


main()