diff --git a/60 Mastermind/python/mastermind.py b/60 Mastermind/python/mastermind.py new file mode 100644 index 00000000..ccc2ae9c --- /dev/null +++ b/60 Mastermind/python/mastermind.py @@ -0,0 +1,230 @@ +import random, sys + + + +def main(): + + global colors, color_letters, num_positions, num_colors, human_score, computer_score + colors = ["BLACK", "WHITE", "RED", "GREEN", "ORANGE", "YELLOW", "PURPLE", "TAN"] + color_letters = "BWRGOYPT" + + num_colors = 100 + human_score = 0 + computer_score = 0 + + # get user inputs for game conditions + print("Mastermind") + print('Creative Computing Morristown, New Jersey') + while num_colors > 8: + 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 + all_possibilities = [1] * possibilities + + print("Number of possibilities {}".format(possibilities)) + print('Color\tLetter') + print('=====\t======') + for element in range(0, num_colors): + print("{}\t{}".format(colors[element], colors[element][0])) + + current_round = 1 + + while current_round <= num_rounds: + print('Round number {}'.format(current_round)) + num_moves = 1 + guesses = [] + turn_over = False + print('Guess my combination ...') + answer = int(possibilities * random.random()) + numeric_answer = [-1] * num_positions + for i in range(0, answer): + numeric_answer = get_possibility(numeric_answer) + #human_readable_answer = make_human_readable(numeric_answer) + while (num_moves < 10 and not turn_over ): + print('Move # {} Guess : '.format(num_moves)) + user_command = input('Guess ') + if user_command == "BOARD": + print_board(guesses) #2000 + elif user_command == "QUIT": #2500 + human_readable_answer = make_human_readable(numeric_answer) + print('QUITTER! MY COMBINATION WAS: {}'.format(human_readable_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("INVALID GUESS: {}".format(invalid_letters)) + else: + guess_results = compare_two_positions(user_command, make_human_readable(numeric_answer)) + print("Results: {}".format(guess_results)) + if guess_results[1] == num_positions: # correct guess + turn_over = True + print("You guessed it in {} moves!".format(num_moves)) + human_score = human_score + num_moves + print_score() + else: + 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 + print ("YOU RAN OUT OF MOVES! THAT'S ALL YOU GET!") + print("THE ACTUAL COMBINATION WAS: {}".format(make_human_readable(numeric_answer))) + human_score = human_score + num_moves + print_score() + + # COMPUTER TURN + guesses = [] + 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.") + player_ready = input("HIT RETURN WHEN READY: ") + while (num_moves < 10 and not turn_over and not inconsistent_information): + found_guess = False + computer_guess = int(possibilities * random.random()) + if all_possibilities[computer_guess] == 1: # random guess is possible, use it + found_guess = True + guess = computer_guess + else: + for i in range (computer_guess, possibilities): + if all_possibilities[i] == 1: + found_guess = True + guess = i + break + if not found_guess: + for i in range (0, computer_guess): + if all_possibilities[i] == 1: + found_guess = True + 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: + numeric_guess = [-1] * num_positions + for i in range(0, guess): + numeric_guess = get_possibility(numeric_guess) + human_readable_guess = make_human_readable(numeric_guess) + print('My guess is: {}'.format(human_readable_guess)) + blacks, whites = input("ENTER BLACKS, WHITES (e.g. 1,2): ").split(",") + blacks = int(blacks) + whites = int(whites) + if blacks == num_positions: #Correct guess + print('I GOT IT IN {} MOVES'.format(num_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 + numeric_possibility = [-1] * num_positions + for j in range (0, i): + numeric_possibility = get_possibility(numeric_possibility) + human_readable_possibility = make_human_readable(numeric_possibility) #4000 + comparison = compare_two_positions(human_readable_possibility, human_readable_guess) + print(comparison) + if ((blacks != comparison[1]) or (whites != comparison[2])): + all_possibilities[i] = 0 + if not turn_over: # COMPUTER DID NOT GUESS + 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() + +#470 +def get_invalid_letters(user_command): + """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): + """Prints previous guesses within the round.""" + print("Board") + print("Move\tGuess\tBlack White") + for idx, guess in enumerate(guesses): + print('{}\t{}\t{} {}'.format(idx+1, guess[0], guess[1], guess[2])) + +#3500 +# Easily the place for most optimization, since they generate every possibility +# every time when checking for potential solutions +# From the original article: +# "We did try a version that kept an actual list of all possible combinations +# (as a string array), which was significantly faster than this versionn but +# which ate tremendous amounts of memory." +def get_possibility(possibility): + #print(possibility) + if possibility[0] > -1: #3530 + current_position = 0 # Python arrays are zero-indexed + while True: + if possibility[current_position] < num_colors-1: # zero-index again + possibility[current_position] += 1 + return possibility + else: + possibility[current_position] = 0 + current_position += 1 + else: #3524 + possibility = [0] * num_positions + return possibility + +#4500 +def compare_two_positions(guess, answer): + """Returns blacks (correct color and position) and whites (correct color only) for candidate position (guess) versus reference position (answer).""" + increment = 0 + blacks = 0 + whites = 0 + initial_guess = guess + for pos in range(0, num_positions): + if guess[pos] != answer[pos]: + for pos2 in range(0, num_positions): + if not(guess[pos] != answer[pos2] or 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=False): + """Prints 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(" COMPUTER {}".format(computer_score)) + print(" HUMAN {}".format(human_score)) + +#4000, 5500, 6000 subroutines are all identical +def make_human_readable(num): + """Make the numeric representation of a position human readable.""" + retval = '' + for i in range(0, len(num)): + retval = retval + color_letters[int(num[i])] + return retval + +if __name__ == "__main__": + main()