-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathmastermind.py
More file actions
226 lines (194 loc) · 8.5 KB
/
Copy pathmastermind.py
File metadata and controls
226 lines (194 loc) · 8.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
import random
import sys
from typing import List, Union, Tuple
# define some parameters for the game which should not be modified.
def setup_game() -> Tuple[int, int, int, int]:
print("""
MASTERMIND
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
""")
# get user inputs for game conditions
num_colors: int = len(COLOR_LETTERS) + 1
while num_colors > len(COLOR_LETTERS):
num_colors = int(input("Number of colors (max 8): ")) # C9 in BASIC
num_positions = int(input("Number of positions: ")) # P9 in BASIC
num_rounds = int(input("Number of rounds: ")) # R9 in BASIC
possibilities = num_colors**num_positions
print(f"Number of possibilities {possibilities}")
print("Color\tLetter")
print("=====\t======")
for element in range(0, num_colors):
print(f"{COLORS[element]}\t{COLORS[element][0]}")
return num_colors, num_positions, num_rounds, possibilities
# Global variables
COLORS = ["BLACK", "WHITE", "RED", "GREEN", "ORANGE", "YELLOW", "PURPLE", "TAN"]
COLOR_LETTERS = "BWRGOYPT"
NUM_COLORS, NUM_POSITIONS, NUM_ROUNDS, POSSIBILITIES = setup_game()
human_score = 0
computer_score = 0
def main() -> None:
current_round = 1
while current_round <= NUM_ROUNDS:
print(f"Round number {current_round}")
human_turn()
computer_turn()
current_round += 1
print_score(is_final_score=True)
sys.exit()
def human_turn() -> None:
global human_score
num_moves = 1
guesses: List[List[Union[str, int]]] = []
print("Guess my combination ...")
secret_combination = int(POSSIBILITIES * random.random())
answer = possibility_to_color_code(secret_combination)
while True:
print(f"Move # {num_moves} Guess : ")
user_command = input("Guess ")
if user_command == "BOARD":
print_board(guesses) # 2000
elif user_command == "QUIT": # 2500
print(f"QUITTER! MY COMBINATION WAS: {answer}")
print("GOOD BYE")
quit()
elif len(user_command) != NUM_POSITIONS: # 410
print("BAD NUMBER OF POSITIONS")
else:
invalid_letters = get_invalid_letters(user_command)
if invalid_letters > "":
print(f"INVALID GUESS: {invalid_letters}")
else:
guess_results = compare_two_positions(user_command, answer)
if guess_results[1] == NUM_POSITIONS: # correct guess
print(f"You guessed it in {num_moves} moves!")
human_score = human_score + num_moves
print_score()
return # from human turn, triumphant
else:
print(f"You have {guess_results[1]} blacks and {guess_results[2]} whites")
guesses.append(guess_results)
num_moves += 1
if num_moves > 10: # RAN OUT OF MOVES
print("YOU RAN OUT OF MOVES! THAT'S ALL YOU GET!")
print(f"THE ACTUAL COMBINATION WAS: {answer}")
human_score = human_score + num_moves
print_score()
return # from human turn, defeated
def computer_turn() -> None:
global computer_score
while True:
all_possibilities = [1] * POSSIBILITIES
num_moves = 1
print("NOW I GUESS. THINK OF A COMBINATION.")
input("HIT RETURN WHEN READY: ")
while True:
possible_guess = find_first_solution_of(all_possibilities)
if possible_guess < 0: # no solutions left :(
print("YOU HAVE GIVEN ME INCONSISTENT INFORMATION.")
print("TRY AGAIN, AND THIS TIME PLEASE BE MORE CAREFUL.")
break # out of inner while loop, restart computer turn
computer_guess = possibility_to_color_code(possible_guess)
print(f"My guess is: {computer_guess}")
blacks_str, whites_str = input(
"ENTER BLACKS, WHITES (e.g. 1,2): "
).split(",")
blacks = int(blacks_str)
whites = int(whites_str)
if blacks == NUM_POSITIONS: # Correct guess
print(f"I GOT IT IN {num_moves} MOVES")
computer_score = computer_score + num_moves
print_score()
return # from computer turn
# computer guessed wrong, deduce which solutions to eliminate.
for i in range(0, POSSIBILITIES):
if all_possibilities[i] == 0: # already ruled out
continue
possible_answer = possibility_to_color_code(i)
comparison = compare_two_positions(
possible_answer, computer_guess
)
if (blacks != comparison[1]) or (whites != comparison[2]):
all_possibilities[i] = 0
if num_moves == 10:
print("I USED UP ALL MY MOVES!")
print("I GUESS MY CPU IS JUST HAVING AN OFF DAY.")
computer_score = computer_score + num_moves
print_score()
return # from computer turn, defeated.
num_moves += 1
def find_first_solution_of(all_possibilities: List[int]) -> int:
"""Scan through all_possibilities for first remaining non-zero marker,
starting from some random position and wrapping around if needed.
If not found return -1."""
start = int(POSSIBILITIES * random.random())
for i in range(0, POSSIBILITIES):
solution = (i + start) % POSSIBILITIES
if all_possibilities[solution]:
return solution
return -1
# 470
def get_invalid_letters(user_command) -> str:
"""Makes sure player input consists of valid colors for selected game configuration."""
valid_colors = COLOR_LETTERS[:NUM_COLORS]
invalid_letters = ""
for letter in user_command:
if letter not in valid_colors:
invalid_letters = invalid_letters + letter
return invalid_letters
# 2000
def print_board(guesses) -> None:
"""Print previous guesses within the round."""
print("Board")
print("Move\tGuess\tBlack White")
for idx, guess in enumerate(guesses):
print(f"{idx + 1}\t{guess[0]}\t{guess[1]} {guess[2]}")
def possibility_to_color_code(possibility: int) -> str:
"""Accepts a (decimal) number representing one permutation in the realm of
possible secret codes and returns the color code mapped to that permutation.
This algorithm is essentially converting a decimal number to a number with
a base of #num_colors, where each color code letter represents a digit in
that #num_colors base."""
color_code: str = ""
pos: int = NUM_COLORS ** NUM_POSITIONS # start with total possibilities
remainder = possibility
for _ in range(NUM_POSITIONS - 1, 0, -1): # process all but the last digit
pos = pos // NUM_COLORS
color_code += COLOR_LETTERS[remainder // pos]
remainder = remainder % pos
color_code += COLOR_LETTERS[remainder] # last digit is what remains
return color_code
# 4500
def compare_two_positions(guess: str, answer: str) -> List[Union[str, int]]:
"""Returns blacks (correct color and position) and whites (correct color
only) for candidate position (guess) versus reference position (answer)."""
blacks = 0
whites = 0
initial_guess = guess
increment = 0
for pos in range(0, NUM_POSITIONS):
if guess[pos] != answer[pos]:
for pos2 in range(0, NUM_POSITIONS):
if guess[pos] == answer[pos2] and guess[pos2] != answer[pos2]: # correct color but not correct place
whites = whites + 1
answer = answer[:pos2] + chr(increment) + answer[pos2 + 1:]
guess = guess[:pos] + chr(increment + 1) + guess[pos + 1:]
increment = increment + 2
else: # correct color and placement
blacks = blacks + 1
# THIS IS DEVIOUSLY CLEVER
guess = guess[:pos] + chr(increment + 1) + guess[pos + 1:]
answer = answer[:pos] + chr(increment) + answer[pos + 1:]
increment = increment + 2
return [initial_guess, blacks, whites]
# 5000 + logic from 1160
def print_score(is_final_score: bool = False) -> None:
"""Print score after each turn ends, including final score at end of game."""
if is_final_score:
print("GAME OVER")
print("FINAL SCORE:")
else:
print("SCORE:")
print(f" COMPUTER {computer_score}")
print(f" HUMAN {human_score}")
if __name__ == "__main__":
main()