Evaluate position based on remaining moves

This commit is contained in:
2022-01-31 12:35:41 -05:00
parent 3336d47732
commit bcc86e18f9

View File

@@ -38,8 +38,16 @@ class WordleState:
)
class DictionaryProvider:
def provide(self) -> List[Word]:
return self._get_dictionary(self._word_filter(WORD_LENGTH))
def __init__(self, limit: Optional[int]=None):
self.limit = limit
def provide(self, ) -> List[Word]:
words = self._get_dictionary(self._word_filter(WORD_LENGTH))
if self.limit:
return words[:self.limit]
else:
return words
def _get_dictionary(self, filter_fun: Callable[str, bool] = None) -> List[Word]:
l = []
@@ -63,13 +71,36 @@ class DictionaryProvider:
return f
class WordleStateEvaluator:
class WordleWordPlayable:
def is_word_playable(self, state: WordleState, word: Word):
for idx, ch in enumerate(word):
# Position is not already solved
if state.board[idx] is not None and state.board[idx] != ch:
return False
# Letter is not already excluded
constraint = state.letter_constraints[ch]
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
class WordleRemainingWordsStateEvaluator:
"""
A class which takes a WordleState and returns a score for the state
"""
def __init__(self, dictionary_provider: DictionaryProvider):
self.dictionary_provider = dictionary_provider
def __init__(self, dictionary_provider: DictionaryProvider, word_playable: WordleWordPlayable):
self.dictionary = dictionary_provider.provide()
self.word_playable = word_playable
def evaluate(self, state: WordleState) -> int:
"""
@@ -77,29 +108,7 @@ class WordleStateEvaluator:
"""
# Score values are totally arbitrary. This will be improved in the future based on the remaining selections
# Game is solved. Return a big number.
if all(state.board):
return 999999999
score = 0
for constraint in state.letter_constraints.values():
if constraint == WordleLetterConstraint.UNKNOWN:
score += 0
elif constraint == WordleLetterConstraint.EXCLUDED:
score += 10
elif constraint == WordleLetterConstraint.INCLUDED:
score += 100
for letter in state.board:
if letter:
score += 100
for letters in state.letters_eliminated:
score += len(letters)
return score
return -sum([1 for word in self.dictionary if self.word_playable.is_word_playable(state, word)])
@dataclass
class WordleMoveProcessor:
@@ -133,19 +142,21 @@ class WordleMoveCalculator:
def __init__(
self,
dictionary_provider: DictionaryProvider,
state_evaluator: WordleStateEvaluator,
state_evaluator, # TODO: parent class WordleStateEvaluator,
move_processor: WordleMoveProcessor,
word_playable: WordleWordPlayable,
):
self.dictionary = dictionary_provider.provide()
self.state_evaluator = state_evaluator
self.move_processor = move_processor
self.word_playable = word_playable
def calculate(
self, state: WordleState, evaluation_depth: int, limit: Optional[int]=None,
) -> List[Tuple[Word, int]]:
guesses = []
words = [
word for word in self.dictionary if self._is_word_playable(state, word)
word for word in self.dictionary if self.word_playable.is_word_playable(state, word)
]
move_rankings = [] # type: List[Tuple[Word, int]]
@@ -169,36 +180,17 @@ class WordleMoveCalculator:
move_rankings = move_rankings[0:limit]
return move_rankings
def _is_word_playable(self, state: WordleState, word: Word):
for idx, ch in enumerate(word):
# Position is not already solved
if state.board[idx] is not None and state.board[idx] != ch:
return False
constraint = state.letter_constraints[ch]
# 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(format='%(asctime)s %(message)s', level=logging.INFO)
dictionary_provider = DictionaryProvider()
state_evaluator = WordleStateEvaluator(dictionary_provider)
dictionary_provider = DictionaryProvider(limit=100)
word_playable = WordleWordPlayable()
state_evaluator = WordleRemainingWordsStateEvaluator(dictionary_provider, word_playable)
move_processor = WordleMoveProcessor()
move_calculator = WordleMoveCalculator(
dictionary_provider, state_evaluator, move_processor
dictionary_provider, state_evaluator, move_processor, word_playable
)
answer = random.choice(dictionary_provider.provide())
@@ -208,11 +200,9 @@ def main():
while not all(state.board):
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)
moves = move_calculator.calculate(state, 1)
log.info("Best Moves: %s", moves[:5])
log.info("Num words remaining: %d", len(moves))
guess = moves[0][0]
log.info("Guess: %s", guess)
state = move_processor.process(state, guess)