from collections import defaultdict # You might find this useful import os from itertools import groupby from random import randrange class WordMakerHuman(): def __init__(self, words_file, debug): # we need to prompt the player for a word, then clear the screen so that player 2 doesn't see the word. self.debug = debug self.words = {} # Make sure to read up the documentation on dictionaries. They will be extremely useful for this project. with open(words_file) as wordfile: for line in wordfile: word = line.strip() if len(word) > 0: self.words[word] = True def reset(self, word_length): question = "" while True: question = input(f"Please type in your word of length {word_length}: ") if question in self.words and len(question) == word_length: break print("Invalid word.") if not self.debug: os.system("clear||cls") # clear the terminal self.word = question def getValidWord(self): return self.word def getAmountOfValidWords(self): return 1 # the only possible word is self.word def guess(self, guess_letter): idx = self.word.find(guess_letter) ret = [] while idx != -1: ret.append(idx) idx = self.word.find(guess_letter, idx + 1) return ret class WordMakerAI(): def __init__(self, words_file, debug): # Read dictionary.txt file as a list 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.filtered_list = None self.debug = debug # Step 1: import file to list with open(words_file) as file_obj: words = file_obj.read().split() # Step 2: generate a dictionary where each key represents 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): # start a new game with a word length of `word_length`. This will always be called before guess() or # getValidWord() are called. this function should be O(1). # Clear the console for every game restart os.system("clear||cls") # 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] def getValidWord(self): # Return a random word from the remaining filtered list when the player is out of guesses return self.filtered_list[randrange(0, len(self.filtered_list), 1)] def getAmountOfValidWords(self): # Returns the length of the remaining words list stored inside self.filtered_list which is being updated # with every guess (within the guess function) and reset with every game (within the reset function) return len(self.filtered_list) def guess(self, guess_letter): # Steps to process the guess (given a single letter guess_letter & the self-updating list # self.filtered_list already available in the class as generated by the reset function): # 1. Use the function letter_combinations (which uses dashed_word function) # to generate a dictionary of all possible combinations of guess_letter in the # current self.filtered_list # 2. Use the function max_key to return the dictionary combinations with the most words assigned. # This function removes duplicate options by taking the max '-' in the tied combinations (which means # it will prioritize the least letters in a combination (including the empty combination with 0 letters) # 3. Use the function letter_indices to convert the returned string combination to a list containing the # letter index/indices in the chosen combination 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 within 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] def letter_indices(letter_combination): # This function loops over a single chosen combination marking the indices for letter position in a string # (i.e. appends the index to an empty list if letter != '-') letter_indices = [] for idx, val in enumerate(letter_combination): if val != '-': letter_indices.append(idx) return letter_indices options_dictionary = letter_combinations(guess_letter, self.filtered_list) combination_choice = max_key(options_dictionary) self.filtered_list = options_dictionary[combination_choice] # update self.filtered_list as per chosen guess return letter_indices(combination_choice)