From 84fce13f359685b3104f9a8179d73e3371a63299 Mon Sep 17 00:00:00 2001 From: Joe Nellis Date: Sat, 30 Apr 2022 00:42:24 -0700 Subject: [PATCH] Refactoring player turn and computer turn to separate methods. Computer turn logic previously ignored counting turns. Computer turn previously gave up the round if the user enters "inconsistent information" about the computers guess when it should have restarted the computers turn. Refactoring to remove usage of 'flag' variables to control program flow. --- 60_Mastermind/python/mastermind.py | 209 ++++++++++++++--------------- 1 file changed, 104 insertions(+), 105 deletions(-) diff --git a/60_Mastermind/python/mastermind.py b/60_Mastermind/python/mastermind.py index cb89a97b..cc77153c 100644 --- a/60_Mastermind/python/mastermind.py +++ b/60_Mastermind/python/mastermind.py @@ -5,15 +5,15 @@ 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(""" + print(""" MASTERMIND CREATIVE COMPUTING MORRISTOWN, NEW JERSEY - - - + + + """) # get user inputs for game conditions - num_colors: int = len(COLOR_LETTERS)+1 + 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 @@ -37,119 +37,119 @@ computer_score = 0 def main() -> None: - global human_score, computer_score - current_round = 1 - while current_round <= NUM_ROUNDS: print(f"Round number {current_round}") - num_moves = 1 - guesses: List[List[Union[str, int]]] = [] - turn_over = False - print("Guess my combination ...") - secret_combination = int(POSSIBILITIES * random.random()) - answer = possibility_to_color_code(secret_combination) - while num_moves < 10 and not turn_over: - 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") + 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: - invalid_letters = get_invalid_letters(user_command) - if invalid_letters > "": - print(f"INVALID GUESS: {invalid_letters}") + 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: - guess_results = compare_two_positions(user_command, answer) - if guess_results[1] == NUM_POSITIONS: # correct guess - turn_over = True - print(f"You guessed it in {num_moves} moves!") - human_score = human_score + num_moves - print_score() - else: - print( - "You have {} blacks and {} whites".format( - guess_results[1], guess_results[2] - ) + print( + "You have {} blacks and {} whites".format( + guess_results[1], guess_results[2] ) - num_moves = num_moves + 1 - guesses.append(guess_results) - if not turn_over: # RAN OUT OF MOVES + ) + 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 - # COMPUTER TURN - turn_over = False - inconsistent_information = False - while not turn_over and not inconsistent_information: - all_possibilities = [1] * POSSIBILITIES - num_moves = 1 - inconsistent_information = False - print("NOW I GUESS. THINK OF A COMBINATION.") - input("HIT RETURN WHEN READY: ") - while num_moves < 10 and not turn_over and not inconsistent_information: - found_guess = False - possible_guess = int(POSSIBILITIES * random.random()) - if ( - all_possibilities[possible_guess] == 1 - ): # random guess is possible, use it - found_guess = True - else: - for i in range(possible_guess + 1, POSSIBILITIES): - if all_possibilities[i] == 1: - found_guess = True - possible_guess = i - break - if not found_guess: - for i in range(0, possible_guess): - if all_possibilities[i] == 1: - found_guess = True - possible_guess = i - break - if not found_guess: # inconsistent info from user - print("YOU HAVE GIVEN ME INCONSISTENT INFORMATION.") - print("TRY AGAIN, AND THIS TIME PLEASE BE MORE CAREFUL.") - turn_over = True - inconsistent_information = True - else: - 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") - turn_over = True - computer_score = computer_score + num_moves - print_score() - else: - num_moves += 1 - 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 not turn_over: # COMPUTER DID NOT GUESS + +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() - current_round += 1 - print_score(is_final_score=True) - sys.exit() + 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 @@ -172,7 +172,6 @@ def print_board(guesses) -> None: 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. @@ -182,7 +181,7 @@ def possibility_to_color_code(possibility: int) -> str: color_code: str = "" pos: int = NUM_COLORS ** NUM_POSITIONS # start with total possibilities remainder = possibility - for i in range(NUM_POSITIONS - 1, 0, -1): # process all but the last digit + 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