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: 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]: def _get_dictionary(self, filter_fun: Callable[str, bool] = None) -> List[Word]:
l = [] l = []
@@ -57,19 +65,42 @@ class DictionaryProvider:
len(word) == length len(word) == length
and all(map(lambda x: x in string.ascii_lowercase, word)) and all(map(lambda x: x in string.ascii_lowercase, word))
and not all(map(lambda c: c in "xiv", 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 ) # hack to remove some roman numerals
return f 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 A class which takes a WordleState and returns a score for the state
""" """
def __init__(self, dictionary_provider: DictionaryProvider): def __init__(self, dictionary_provider: DictionaryProvider, word_playable: WordleWordPlayable):
self.dictionary_provider = dictionary_provider self.dictionary = dictionary_provider.provide()
self.word_playable = word_playable
def evaluate(self, state: WordleState) -> int: 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 # Score values are totally arbitrary. This will be improved in the future based on the remaining selections
return -sum([1 for word in self.dictionary if self.word_playable.is_word_playable(state, word)])
# 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
@dataclass @dataclass
class WordleMoveProcessor: class WordleMoveProcessor:
@@ -133,19 +142,21 @@ class WordleMoveCalculator:
def __init__( def __init__(
self, self,
dictionary_provider: DictionaryProvider, dictionary_provider: DictionaryProvider,
state_evaluator: WordleStateEvaluator, state_evaluator, # TODO: parent class WordleStateEvaluator,
move_processor: WordleMoveProcessor, move_processor: WordleMoveProcessor,
word_playable: WordleWordPlayable,
): ):
self.dictionary = dictionary_provider.provide() self.dictionary = dictionary_provider.provide()
self.state_evaluator = state_evaluator self.state_evaluator = state_evaluator
self.move_processor = move_processor self.move_processor = move_processor
self.word_playable = word_playable
def calculate( def calculate(
self, state: WordleState, evaluation_depth: int, limit: Optional[int]=None, self, state: WordleState, evaluation_depth: int, limit: Optional[int]=None,
) -> List[Tuple[Word, int]]: ) -> List[Tuple[Word, int]]:
guesses = [] guesses = []
words = [ 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]] move_rankings = [] # type: List[Tuple[Word, int]]
@@ -169,36 +180,17 @@ class WordleMoveCalculator:
move_rankings = move_rankings[0:limit] move_rankings = move_rankings[0:limit]
return move_rankings 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(): def main():
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
dictionary_provider = DictionaryProvider() dictionary_provider = DictionaryProvider(limit=100)
state_evaluator = WordleStateEvaluator(dictionary_provider) word_playable = WordleWordPlayable()
state_evaluator = WordleRemainingWordsStateEvaluator(dictionary_provider, word_playable)
move_processor = WordleMoveProcessor() move_processor = WordleMoveProcessor()
move_calculator = WordleMoveCalculator( move_calculator = WordleMoveCalculator(
dictionary_provider, state_evaluator, move_processor dictionary_provider, state_evaluator, move_processor, word_playable
) )
answer = random.choice(dictionary_provider.provide()) answer = random.choice(dictionary_provider.provide())
@@ -208,11 +200,9 @@ def main():
while not all(state.board): while not all(state.board):
log.info("Round: %d", iteration) log.info("Round: %d", iteration)
log.debug("State: %s", str(state)) log.debug("State: %s", str(state))
if iteration == 1: moves = move_calculator.calculate(state, 1)
guess = "sales" log.info("Best Moves: %s", moves[:5])
else: log.info("Num words remaining: %d", len(moves))
moves = move_calculator.calculate(state, 5, limit=5)
log.info("Best Moves: %s", moves)
guess = moves[0][0] guess = moves[0][0]
log.info("Guess: %s", guess) log.info("Guess: %s", guess)
state = move_processor.process(state, guess) state = move_processor.process(state, guess)