From 3336d477325e48180aafaffe6cd3a4c1f35dc78e Mon Sep 17 00:00:00 2001 From: Max Regan Date: Mon, 31 Jan 2022 12:01:40 -0500 Subject: [PATCH] Don't reguess letters in same positions --- src/wordle.py | 63 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/src/wordle.py b/src/wordle.py index 4c2b252..e70230f 100644 --- a/src/wordle.py +++ b/src/wordle.py @@ -9,7 +9,7 @@ from pathlib import Path import string WORD_LENGTH = 5 # TODO: Make this configurable at runtime -logger = logging.getLogger(__name__) +log = logging.getLogger(__name__) Letter = chr Position = int @@ -33,7 +33,9 @@ class WordleState: c: WordleLetterConstraint.UNKNOWN for c in string.ascii_lowercase } ) - + letters_eliminated: List[Dict[Letter, None]] = field( + default_factory=lambda: [dict() for i in range(WORD_LENGTH)] + ) class DictionaryProvider: def provide(self) -> List[Word]: @@ -43,7 +45,7 @@ class DictionaryProvider: l = [] if not filter_fun: filter_fun = lambda s: True - with open("/usr/share/dict/words") as d: + with open("/home/max/Desktop/clean-words-5.txt") as d: return [ word for word in filter(filter_fun, map(lambda w: w.strip(), d.readlines())) @@ -55,7 +57,7 @@ class DictionaryProvider: len(word) == length and all(map(lambda x: x in string.ascii_lowercase, word)) and not all(map(lambda c: c in "xiv", word)) - and (word.startswith("s") or word.startswith("z")) + #and (word.startswith("s") or word.startswith("z")) ) # hack to remove some roman numerals return f @@ -92,6 +94,10 @@ class WordleStateEvaluator: for letter in state.board: if letter: score += 100 + + for letters in state.letters_eliminated: + score += len(letters) + return score @@ -102,15 +108,20 @@ class WordleMoveProcessor: board=state.board, letter_constraints=state.letter_constraints.copy(), answer=state.answer, + letters_eliminated=[ d.copy() for d in state.letters_eliminated ], ) for idx, ch in enumerate(guess): if ch in new_state.answer: new_state.letter_constraints[ch] = WordleLetterConstraint.INCLUDED - if state.answer[idx] == ch: - new_state.board[idx] = ch else: new_state.letter_constraints[ch] = WordleLetterConstraint.EXCLUDED + + if new_state.answer[idx] == ch: + new_state.board[idx] = ch + else: + new_state.letters_eliminated[idx][ch] = None + return new_state @@ -130,7 +141,7 @@ class WordleMoveCalculator: self.move_processor = move_processor def calculate( - self, state: WordleState, num_guesses: int, evaluation_depth: int + self, state: WordleState, evaluation_depth: int, limit: Optional[int]=None, ) -> List[Tuple[Word, int]]: guesses = [] words = [ @@ -145,6 +156,7 @@ class WordleMoveCalculator: board=state.board.copy(), letter_constraints=state.letter_constraints.copy(), answer=answer, + letters_eliminated=[ d.copy() for d in state.letters_eliminated ], ) new_state = self.move_processor.process(temp_state, guess) word_total += self.state_evaluator.evaluate(temp_state) @@ -152,8 +164,10 @@ class WordleMoveCalculator: word_score = word_total / len(words) move_rankings.append((guess, word_score)) - move_rankings = sorted(move_rankings, key=lambda x: x[1]) - return move_rankings[0:num_guesses] + move_rankings = sorted(move_rankings, key=lambda x: x[1], reverse=True) + if limit: + move_rankings = move_rankings[0:limit] + return move_rankings def _is_word_playable(self, state: WordleState, word: Word): for idx, ch in enumerate(word): @@ -165,12 +179,20 @@ class WordleMoveCalculator: # Letter is not already excluded if constraint == WordleLetterConstraint.EXCLUDED: return False + + if ch in state.letters_eliminated[idx]: + return False + + for letter, constraint in state.letter_constraints.items(): + if constraint == WordleLetterConstraint.INCLUDED and letter not in word: + return False + return True def main(): - logging.basicConfig() + logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) dictionary_provider = DictionaryProvider() state_evaluator = WordleStateEvaluator(dictionary_provider) @@ -182,15 +204,24 @@ def main(): answer = random.choice(dictionary_provider.provide()) state = WordleState(board=[None] * WORD_LENGTH, answer=answer) iteration = 1 + guesses = [] # type: List[str] while not all(state.board): - print("Round:", iteration) - print("Board:", str(state.board)) - move = move_calculator.calculate(state, 5, 1)[0] - guess = move[0] - print("Guess:", guess) + log.info("Round: %d", iteration) + log.debug("State: %s", str(state)) + if iteration == 1: + guess = "sales" + else: + moves = move_calculator.calculate(state, 5, limit=5) + log.info("Best Moves: %s", moves) + guess = moves[0][0] + log.info("Guess: %s", guess) state = move_processor.process(state, guess) + log.info("Board: %s", str(state.board)) + guesses.append(guess) iteration += 1 - print("Board:", str(state.board)) + print(f"Answer: {answer}") + print(f"Iterations: {iteration - 1}") + print(f"Guesses: {str(', '.join(guesses))}") if __name__ == "__main__":