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.
Mastermind
- Mastermind first creates a hidden
code
of lengthcode_length
consisting code pegs with possibly duplicate colors chosen from a sequence ofcolors
. - Coderbreaker provides a
guess
of thecode
. - 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'
andguess
is'BGGG'
, then - the feedback should be
'bbw'
withblack_key_pegs_count == 2
because of__GG
in the guess, andwhite_key_pegs_count == 1
because of_B__
in the guess._G__
in theguess
should not be awarded an additional white peg because__GG
in thecode
has been counted.
- If the
- The number of black pegs (
- 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()