from itertools import groupby from random import randrange class GameState(): def __init__(self, guesses, word_length, debug): self.word_length = word_length self.guessed = [] self.guesses = guesses # starting number of guesses self.done_letters = 0 self.word = ["-"] * word_length self.debug = debug def print_state(self, wordsRemaining): wrStr = "" if not self.debug else f" ({wordsRemaining} words remain)" print(f"\nYou have {self.guesses} incorrect guesses left{wrStr}.") print("Used letters: {}".format(" ".join(self.guessed))) print("Word: {}".format("".join(self.word))) # an abstract class defining the word guesser interface class WordGuesser(): def __init__(self, guesses, words_file, debug): self.debug = debug self.guesses = guesses def reset(self, word_length): self.state = GameState(self.guesses, word_length, self.debug) def getGuess(self): # return the next best guess # use self.gamestate to inquire about the current status of the game raise NotImplementedError # Subclasses should override this method class WordGuesserHuman(WordGuesser): def getGuess(self): while True: inp = input("Enter guess: ").lower() if len(inp) != 1 or inp in self.state.guessed or not inp.isalpha(): print("Invalid guess.") else: break return inp # CHECK GAMEMANAGER.PY FOR EXTRA INSTRUCTIONS TO DEPLOY THE AI GUESSER class WordGuesserAI(): def __init__(self, guesses, words_file, debug): # Read dictionary.txt file as a list (comma-separated not \n separated) then generate # a dictionary with each unique word length as a key, and the corresponding word list # in the values # Step 0: Initialize variables to be used within the class self.alphabet = [] self.filtered_list = None self.debug = debug self.guesses = guesses # Step 1: import file to list with open(words_file) as file_obj: words = file_obj.read().split() # Step 2: generate a dictionary where keys represent a unique word length group self.dictionary_by_length = {k: list(g) for k, g in groupby(sorted(words, key=len), len)} def reset(self, word_length): self.state = GameState(self.guesses, word_length, self.debug) # Capture word group of length word_length in self.filtered_list from the dictionary of all possible # lengths self.dictionary_by_length defined in the function __init__ self.filtered_list = self.dictionary_by_length[word_length] # Define the alphabet to use to keep track of remaining letter options to guess self.alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] def getGuess(self): # Steps to generate a letter guess to minimize adversary response each time: # 1. Loop over the alphabet variable (which contains remaining letter options) and simulate the adversary response # for each letter (which should be the longest list possible) using guess() and store all simulation lengths in a list. # Documentation for the guess() is available in the word_maker.py file in the current directory # 2. Store the minimum and maximum length of adversary responses and, if both are equal (meaning all possible # guesses return the same adversary response), the program chooses a random letter from the alphabet. Otherwise, # the program returns the letter with minimum adversary response length def guess(guess_letter, words, guesser): def dashed_word(word, letter): # This function takes a word and a letter then returns its dashed version # (e.g. dashed_word('act','a') returns 'a--') dashed_word = '' for single_letter in word: # loop over each character in word and either append letter or - to dashed_word if single_letter == str(letter): dashed_word += single_letter else: dashed_word += '-' return dashed_word def letter_combinations(letter, words): # This function takes the guessed letter and the list of remaining words, loops over every word in the list # and places it under a relevant combination dictionary_by_combination = {} for word in words: dash = dashed_word(word, letter) # get the dashed theme of the word given the guessed letter if dash in dictionary_by_combination: # prevent duplication of keys inside the dictionary dictionary_by_combination[dash].append(word) else: dictionary_by_combination[dash] = [word] return dictionary_by_combination def max_key(dictionary): # This function returns the combination with the most remaining words while choosing the least number of # letters possible (including no letters). It returns the combination in the format e.g. '--a-' max_count = max( len(v) for v in dictionary.values()) # stores the maximum length of a list under a dict key max_key = [k for k, v in dictionary.items() if len(v) == max_count] # returns a list of keys with max items dash_count = [] for word in max_key: # loops over every max key choosing the least amount of letters possible dash_count.append(word.count('-')) max_dash_count_index = dash_count.index( max(dash_count)) # get index of min letters (always a single value) return max_key[max_dash_count_index] options_dictionary = letter_combinations(guess_letter, words) combination_choice = max_key(options_dictionary) resulting_list = options_dictionary[combination_choice] if guesser: return len(resulting_list) else: return resulting_list def find_optimum_guess(alphabet, words): optimum_lengths = [] for letter in alphabet: optimum_lengths.append(guess(letter, words, True)) min_value = min(optimum_lengths) max_value = max(optimum_lengths) if min_value == max_value: random_index = randrange(0, len(alphabet), 1) return alphabet[random_index] else: index_of_least_error = optimum_lengths.index(min_value) return alphabet[index_of_least_error] guessed_letter = find_optimum_guess(self.alphabet, self.filtered_list) self.alphabet.remove(guessed_letter) self.filtered_list = guess(guessed_letter, self.filtered_list, False) return guessed_letter