python-data-structures-evil-hangman / py_evil_hangman / word_maker.py
word_maker.py
Raw
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)