Mastermind
Codes of 4 colours from 6, scored black and white by counting colours: play against a secret code with the codes still possible counted after each answer, watch the computer's rule solve any code, or see how one guess is scored.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
Codes of 4 colours, each from 1 to 6, repeats allowed: 1,296 of them. You can guess the computer's code, watch the computer guess yours, or see how one guess is scored.
main.eml- the menu, playing, watching and scoringpegs.eml- every code, scoring, the codes that fit an answer, the computer's rule and the secret code
How each part works:
- A guess is answered with black (the right colour in the right place) and white (the right colour in the wrong place). Colours are counted with a dict, as in the corpus case
char-frequency-table. Each colour is in both codes as often as the smaller of its two counts. The total of those, minus black, is white. - After each answer, the codes still possible are the ones that would have given the same answer to the same guess. The program counts them and can give one as a hint. It also says when a guess could not have been the code.
- The computer's rule: guess 1122, then the first code, in order, that fits every answer so far. Over all 1,296 codes it never needs more than 8 guesses: 6,508 in all, 5.02 a code.
- The secret code comes from a seed, through a linear congruential generator.
What is checked: menu choices 1 to 4; a seed from 0 to 999999; codes of 4 colours, each from 1 to 6. An empty answer cancels. While playing, it stops the game and shows the code.
Sessions: sessions/basic.in gives: - a game (seed 7): two guesses that could not have been the code, two hints, and the code found in 4 guesses; - a game given up after two guesses (seed 42); - the computer solving 3456 and 1511 in 5 guesses, and 6543 in 7; - three scores: 1234 against 1325, 1122 against 2211, and 6666 against 6616.
sessions/bad-input.in gives: - menu choices 0 and x; the seed x, then a cancel; - the guesses 123, 12345, 1237 and abcd; then 1111 ten times, until no guesses are left; - a cancel, 777, and a random code whose seed is cancelled; - scores with 12, and two cancels.
Built on the verified corpus case char-frequency-table (counting with a dict, one character at a time).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Mastermind ==
Codes of 4 colours, each 1 to 6: black for right colour, right place; white for right colour, wrong place.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 0
Pick a number from 1 to 4.
1) play 2) watch the computer 3) score a guess 4) quit
choice> x
Pick a number from 1 to 4.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 1
seed (0 to 999999)> x
Type a whole number from 0 to 999999.
seed (0 to 999999)>
Cancelled.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 1
seed (0 to 999999)> 0
I have picked a code: 4 colours, each 1 to 6, repeats allowed. You have 10 guesses.
guess 1 (4 colours 1 to 6, ? for a hint)> 123
Type 4 colours, each 1 to 6, like 1234.
guess 1 (4 colours 1 to 6, ? for a hint)> 12345
Type 4 colours, each 1 to 6, like 1234.
guess 1 (4 colours 1 to 6, ? for a hint)> 1237
Type 4 colours, each 1 to 6, like 1234.
guess 1 (4 colours 1 to 6, ? for a hint)> abcd
Type 4 colours, each 1 to 6, like 1234.
guess 1 (4 colours 1 to 6, ? for a hint)> 1111
0 black, 0 white. 625 codes still fit every answer.
guess 2 (4 colours 1 to 6, ? for a hint)> 1111
0 black, 0 white. 625 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
guess 3 (4 colours 1 to 6, ? for a hint)> 1111
0 black, 0 white. 625 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
guess 4 (4 colours 1 to 6, ? for a hint)> 1111
0 black, 0 white. 625 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
guess 5 (4 colours 1 to 6, ? for a hint)> 1111
0 black, 0 white. 625 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
guess 6 (4 colours 1 to 6, ? for a hint)> 1111
0 black, 0 white. 625 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
guess 7 (4 colours 1 to 6, ? for a hint)> 1111
0 black, 0 white. 625 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
guess 8 (4 colours 1 to 6, ? for a hint)> 1111
0 black, 0 white. 625 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
guess 9 (4 colours 1 to 6, ? for a hint)> 1111
0 black, 0 white. 625 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
guess 10 (4 colours 1 to 6, ? for a hint)> 1111
0 black, 0 white. 625 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
No guesses left. The code was 3536.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 2
your code (4 colours 1 to 6), or random>
Cancelled.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 2
your code (4 colours 1 to 6), or random> 777
Type 4 colours, each 1 to 6, like 1234, or random.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 2
your code (4 colours 1 to 6), or random> random
seed (0 to 999999)>
Cancelled.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 3
guess (4 colours 1 to 6)> 12
code (4 colours 1 to 6)> 1234
Type 4 colours, each 1 to 6, like 1234.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 3
guess (4 colours 1 to 6)>
Cancelled.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 3
guess (4 colours 1 to 6)> 1234
code (4 colours 1 to 6)>
Cancelled.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 4
Bye.
What was typed (37 lines)
0
x
1
x
1
0
123
12345
1237
abcd
1111
1111
1111
1111
1111
1111
1111
1111
1111
1111
2
2
777
2
random
3
12
1234
3
3
1234
4
basic
interpreter: byte-equal== Mastermind ==
Codes of 4 colours, each 1 to 6: black for right colour, right place; white for right colour, wrong place.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 1
seed (0 to 999999)> 7
I have picked a code: 4 colours, each 1 to 6, repeats allowed. You have 10 guesses.
guess 1 (4 colours 1 to 6, ? for a hint)> 1122
1 black, 1 white. 208 codes still fit every answer.
guess 2 (4 colours 1 to 6, ? for a hint)> 3344
0 black, 0 white. 56 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
guess 3 (4 colours 1 to 6, ? for a hint)> ?
A code that fits every answer so far: 1255.
guess 3 (4 colours 1 to 6, ? for a hint)> 1311
3 black, 0 white. 2 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
guess 4 (4 colours 1 to 6, ? for a hint)> ?
A code that fits every answer so far: 1511.
guess 4 (4 colours 1 to 6, ? for a hint)> 1511
4 black: that is the code, in 4 guesses.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 1
seed (0 to 999999)> 42
I have picked a code: 4 colours, each 1 to 6, repeats allowed. You have 10 guesses.
guess 1 (4 colours 1 to 6, ? for a hint)> 1234
1 black, 2 white. 132 codes still fit every answer.
guess 2 (4 colours 1 to 6, ? for a hint)> 5566
1 black, 0 white. 36 codes still fit every answer. (That guess could not have been the code: it did not fit an earlier answer.)
guess 3 (4 colours 1 to 6, ? for a hint)>
You stopped. The code was 2436.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 2
your code (4 colours 1 to 6), or random> 3456
The rule: guess 1122, then the first code, in order, that fits every answer so far.
guess code answer codes left
1 1122 0 black, 0 white 256
2 3333 1 black, 0 white 108
3 3444 2 black, 0 white 12
4 3455 3 black, 0 white 2
5 3456 4 black, 0 white 1
Solved in 5 guesses. Over all 1,296 codes this rule never needs more than 8: 6,508 guesses in all, 5.02 a code.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 2
your code (4 colours 1 to 6), or random> random
seed (0 to 999999)> 7
The code is 1511.
The rule: guess 1122, then the first code, in order, that fits every answer so far.
guess code answer codes left
1 1122 1 black, 1 white 208
2 1233 1 black, 0 white 45
3 1411 3 black, 0 white 8
4 1414 2 black, 0 white 2
5 1511 4 black, 0 white 1
Solved in 5 guesses. Over all 1,296 codes this rule never needs more than 8: 6,508 guesses in all, 5.02 a code.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 2
your code (4 colours 1 to 6), or random> 6543
The rule: guess 1122, then the first code, in order, that fits every answer so far.
guess code answer codes left
1 1122 0 black, 0 white 256
2 3333 1 black, 0 white 108
3 3444 1 black, 1 white 24
4 5345 1 black, 2 white 6
5 5436 0 black, 4 white 2
6 6354 1 black, 3 white 1
7 6543 4 black, 0 white 1
Solved in 7 guesses. Over all 1,296 codes this rule never needs more than 8: 6,508 guesses in all, 5.02 a code.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 3
guess (4 colours 1 to 6)> 1234
code (4 colours 1 to 6)> 1325
colour in guess in code in both
1 1 1 1
2 1 1 1
3 1 1 1
4 1 0 0
5 0 1 0
Colours in both: 3. Right colour in the right place: 1 black. The rest, 3 - 1: 2 white.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 3
guess (4 colours 1 to 6)> 1122
code (4 colours 1 to 6)> 2211
colour in guess in code in both
1 2 2 2
2 2 2 2
Colours in both: 4. Right colour in the right place: 0 black. The rest, 4 - 0: 4 white.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 3
guess (4 colours 1 to 6)> 6666
code (4 colours 1 to 6)> 6616
colour in guess in code in both
1 0 1 0
6 4 3 3
Colours in both: 3. Right colour in the right place: 3 black. The rest, 3 - 3: 0 white.
1) play 2) watch the computer 3) score a guess 4) quit
choice> 4
Bye.
What was typed (30 lines)
1
7
1122
3344
?
1311
?
1511
1
42
1234
5566
2
3456
2
random
7
2
6543
3
1234
1325
3
1122
2211
3
6666
6616
4
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P100 Mastermind: guess the computer's code, watch the computer guess
# yours, or see how one guess is scored.
import pegs
def plain(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def number(s, most):
if len(s) == 0 or len(s) > most:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
return n
def lpad(s, n):
while len(s) < n:
" " + s => s
return s
def answer(r):
return str(r[0]) + " black, " + str(r[1]) + " white"
def still(n):
if n == 1:
return "1 code still fits every answer."
return str(n) + " codes still fit every answer."
def ask_seed():
while True:
plain(input("seed (0 to 999999)> ")) => a
if a == "":
return -1
number(a, 6) => n
if n >= 0:
return n
"Type a whole number from 0 to 999999." ^0
def play():
ask_seed() => seed
if seed == -1:
"Cancelled." ^0
return
pegs.secret(seed) => code
"I have picked a code: 4 colours, each 1 to 6, repeats allowed. You have 10 guesses." ^0
pegs.all_codes() => left
1 => turn
while turn <= 10:
plain(input("guess " + str(turn) + " (4 colours 1 to 6, ? for a hint)> ")) => g
if g == "":
("You stopped. The code was " + code + ".") ^0
return
if g == "?":
("A code that fits every answer so far: " + left[0] + ".") ^0
elif not pegs.valid(g):
"Type 4 colours, each 1 to 6, like 1234." ^0
else:
pegs.score(g, code) => r
False => fitted
for c in left:
if c == g:
True => fitted
pegs.fits(left, g, r) => left
if r[0] == 4:
("4 black: that is the code, in " + str(turn) + " guesses.") ^0
return
if fitted:
(answer(r) + ". " + still(len(left))) ^0
else:
(answer(r) + ". " + still(len(left)) + " (That guess could not have been the code: it did not fit an earlier answer.)") ^0
turn + 1 => turn
("No guesses left. The code was " + code + ".") ^0
def watch():
plain(input("your code (4 colours 1 to 6), or random> ")) => s
if s == "":
"Cancelled." ^0
return
if s == "random":
ask_seed() => seed
if seed == -1:
"Cancelled." ^0
return
pegs.secret(seed) => s
("The code is " + s + ".") ^0
elif not pegs.valid(s):
"Type 4 colours, each 1 to 6, like 1234, or random." ^0
return
"The rule: guess 1122, then the first code, in order, that fits every answer so far." ^0
" guess code answer codes left" ^0
pegs.solve(s) => rows
for k in [0:len(rows) - 1]:
rows[k] => row
(" " + lpad(str(k + 1), 5) + " " + row[0] + " " + answer(row[1]) + lpad(str(row[2]), 13)) ^0
("Solved in " + str(len(rows)) + " guesses. Over all 1,296 codes this rule never needs more than 8: 6,508 guesses in all, 5.02 a code.") ^0
def score_two():
plain(input("guess (4 colours 1 to 6)> ")) => g
if g == "":
"Cancelled." ^0
return
plain(input("code (4 colours 1 to 6)> ")) => c
if c == "":
"Cancelled." ^0
return
if not pegs.valid(g) or not pegs.valid(c):
"Type 4 colours, each 1 to 6, like 1234." ^0
return
pegs.counts(g) => cg
pegs.counts(c) => cc
" colour in guess in code in both" ^0
0 => both
for k in ["1", "2", "3", "4", "5", "6"]:
0 => a
0 => b
if k in cg:
cg[k] => a
if k in cc:
cc[k] => b
if a > 0 or b > 0:
a => m
if b < a:
b => m
both + m => both
(" " + lpad(k, 6) + lpad(str(a), 10) + lpad(str(b), 9) + lpad(str(m), 9)) ^0
pegs.score(g, c) => r
("Colours in both: " + str(both) + ". Right colour in the right place: " + str(r[0]) + " black. The rest, " + str(both) + " - " + str(r[0]) + ": " + str(r[1]) + " white.") ^0
"== Mastermind ==" ^0
"Codes of 4 colours, each 1 to 6: black for right colour, right place; white for right colour, wrong place." ^0
True => running
while running:
"" ^0
"1) play 2) watch the computer 3) score a guess 4) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
play()
elif choice == "2":
watch()
elif choice == "3":
score_two()
elif choice == "4":
False => running
else:
"Pick a number from 1 to 4." ^0
"Bye." ^0
Python projection (main.py)
import pegs
def plain(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def number(s, most):
if len(s) == 0 or len(s) > most:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
return n
def lpad(s, n):
while len(s) < n:
s = " " + s
return s
def answer(r):
return str(r[0]) + " black, " + str(r[1]) + " white"
def still(n):
if n == 1:
return "1 code still fits every answer."
return str(n) + " codes still fit every answer."
def ask_seed():
while True:
a = plain(input("seed (0 to 999999)> "))
if a == "":
return -1
n = number(a, 6)
if n >= 0:
return n
print("Type a whole number from 0 to 999999.")
def play():
seed = ask_seed()
if seed == -1:
print("Cancelled.")
return
code = pegs.secret(seed)
print("I have picked a code: 4 colours, each 1 to 6, repeats allowed. You have 10 guesses.")
left = pegs.all_codes()
turn = 1
while turn <= 10:
g = plain(input("guess " + str(turn) + " (4 colours 1 to 6, ? for a hint)> "))
if g == "":
print("You stopped. The code was " + code + ".")
return
if g == "?":
print("A code that fits every answer so far: " + left[0] + ".")
elif not pegs.valid(g):
print("Type 4 colours, each 1 to 6, like 1234.")
else:
r = pegs.score(g, code)
fitted = False
for c in left:
if c == g:
fitted = True
left = pegs.fits(left, g, r)
if r[0] == 4:
print("4 black: that is the code, in " + str(turn) + " guesses.")
return
if fitted:
print(answer(r) + ". " + still(len(left)))
else:
print(answer(r) + ". " + still(len(left)) + " (That guess could not have been the code: it did not fit an earlier answer.)")
turn = turn + 1
print("No guesses left. The code was " + code + ".")
def watch():
s = plain(input("your code (4 colours 1 to 6), or random> "))
if s == "":
print("Cancelled.")
return
if s == "random":
seed = ask_seed()
if seed == -1:
print("Cancelled.")
return
s = pegs.secret(seed)
print("The code is " + s + ".")
elif not pegs.valid(s):
print("Type 4 colours, each 1 to 6, like 1234, or random.")
return
print("The rule: guess 1122, then the first code, in order, that fits every answer so far.")
print(" guess code answer codes left")
rows = pegs.solve(s)
for k in range(0, len(rows)):
row = rows[k]
print(" " + lpad(str(k + 1), 5) + " " + row[0] + " " + answer(row[1]) + lpad(str(row[2]), 13))
print("Solved in " + str(len(rows)) + " guesses. Over all 1,296 codes this rule never needs more than 8: 6,508 guesses in all, 5.02 a code.")
def score_two():
g = plain(input("guess (4 colours 1 to 6)> "))
if g == "":
print("Cancelled.")
return
c = plain(input("code (4 colours 1 to 6)> "))
if c == "":
print("Cancelled.")
return
if not pegs.valid(g) or not pegs.valid(c):
print("Type 4 colours, each 1 to 6, like 1234.")
return
cg = pegs.counts(g)
cc = pegs.counts(c)
print(" colour in guess in code in both")
both = 0
for k in ["1", "2", "3", "4", "5", "6"]:
a = 0
b = 0
if k in cg:
a = cg[k]
if k in cc:
b = cc[k]
if a > 0 or b > 0:
m = a
if b < a:
m = b
both = both + m
print(" " + lpad(k, 6) + lpad(str(a), 10) + lpad(str(b), 9) + lpad(str(m), 9))
r = pegs.score(g, c)
print("Colours in both: " + str(both) + ". Right colour in the right place: " + str(r[0]) + " black. The rest, " + str(both) + " - " + str(r[0]) + ": " + str(r[1]) + " white.")
print("== Mastermind ==")
print("Codes of 4 colours, each 1 to 6: black for right colour, right place; white for right colour, wrong place.")
running = True
while running:
print("")
print("1) play 2) watch the computer 3) score a guess 4) quit")
choice = plain(input("choice> "))
if choice == "1":
play()
elif choice == "2":
watch()
elif choice == "3":
score_two()
elif choice == "4":
running = False
else:
print("Pick a number from 1 to 4.")
print("Bye.")
pegs.eml
eml# P100 Mastermind - codes of 4 colours, 1 to 6, repeats allowed: 1,296 of
# them. A guess is answered with black (right colour, right place) and
# white (right colour, wrong place), counted colour by colour with a dict
# as in the corpus case char-frequency-table. The computer's rule: guess
# 1122, then the first code, in order, that fits every answer so far.
def all_codes():
[] => out
for a in [1:6]:
for b in [1:6]:
for c in [1:6]:
for d in [1:6]:
out + [str(a) + str(b) + str(c) + str(d)] => out
return out
def valid(s):
if len(s) != 4:
return False
for c in s:
if not (c >= "1" and c <= "6"):
return False
return True
def counts(s):
{} => out
for c in s:
if c in out:
out[c] + 1 => out[c]
else:
1 => out[c]
return out
def score(guess, code):
# [black, white]: black counts the places that match; every colour
# matches as often as it is in both, the fewer of the two counts, and
# white is what that leaves beyond black.
0 => black
for i in [0:3]:
if guess[i] == code[i]:
black + 1 => black
counts(guess) => g
counts(code) => k
0 => both
for c in g:
if c in k:
if g[c] < k[c]:
both + g[c] => both
else:
both + k[c] => both
return [black, both - black]
def fits(codes, guess, answer):
# the codes that would have given this answer to this guess
[] => out
for c in codes:
score(guess, c) => r
if r[0] == answer[0] and r[1] == answer[1]:
out + [c] => out
return out
def solve(code):
# [[guess, answer, codes still fitting], ...] until 4 black
all_codes() => left
"1122" => guess
[] => out
True => going
while going:
score(guess, code) => r
fits(left, guess, r) => left
out + [[guess, r, len(left)]] => out
if r[0] == 4:
False => going
else:
left[0] => guess
return out
def secret(seed):
# 4 colours from a linear congruential generator, top 16 bits
seed => x
"" => out
for i in [1:4]:
(1664525 * x + 1013904223) % 4294967296 => x
# x is below 2 ** 32, so the division is exact
out + str(int((x - x % 65536) / 65536) % 6 + 1) => out
return out
Python projection (pegs.py)
def all_codes():
out = []
for a in range(1, 7):
for b in range(1, 7):
for c in range(1, 7):
for d in range(1, 7):
out = out + [str(a) + str(b) + str(c) + str(d)]
return out
def valid(s):
if len(s) != 4:
return False
for c in s:
if not (c >= "1" and c <= "6"):
return False
return True
def counts(s):
out = {}
for c in s:
if c in out:
out[c] = out[c] + 1
else:
out[c] = 1
return out
def score(guess, code):
black = 0
for i in range(0, 4):
if guess[i] == code[i]:
black = black + 1
g = counts(guess)
k = counts(code)
both = 0
for c in g:
if c in k:
if g[c] < k[c]:
both = both + g[c]
else:
both = both + k[c]
return [black, both - black]
def fits(codes, guess, answer):
out = []
for c in codes:
r = score(guess, c)
if r[0] == answer[0] and r[1] == answer[1]:
out = out + [c]
return out
def solve(code):
left = all_codes()
guess = "1122"
out = []
going = True
while going:
r = score(guess, code)
left = fits(left, guess, r)
out = out + [[guess, r, len(left)]]
if r[0] == 4:
going = False
else:
guess = left[0]
return out
def secret(seed):
x = seed
out = ""
for i in range(1, 5):
x = (1664525 * x + 1013904223) % 4294967296
out = out + str(int((x - x % 65536) / 65536) % 6 + 1)
return out