Don't reguess letters in same positions
This commit is contained in:
@@ -9,7 +9,7 @@ from pathlib import Path
|
|||||||
import string
|
import string
|
||||||
|
|
||||||
WORD_LENGTH = 5 # TODO: Make this configurable at runtime
|
WORD_LENGTH = 5 # TODO: Make this configurable at runtime
|
||||||
logger = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
Letter = chr
|
Letter = chr
|
||||||
Position = int
|
Position = int
|
||||||
@@ -33,7 +33,9 @@ class WordleState:
|
|||||||
c: WordleLetterConstraint.UNKNOWN for c in string.ascii_lowercase
|
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:
|
class DictionaryProvider:
|
||||||
def provide(self) -> List[Word]:
|
def provide(self) -> List[Word]:
|
||||||
@@ -43,7 +45,7 @@ class DictionaryProvider:
|
|||||||
l = []
|
l = []
|
||||||
if not filter_fun:
|
if not filter_fun:
|
||||||
filter_fun = lambda s: True
|
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 [
|
return [
|
||||||
word
|
word
|
||||||
for word in filter(filter_fun, map(lambda w: w.strip(), d.readlines()))
|
for word in filter(filter_fun, map(lambda w: w.strip(), d.readlines()))
|
||||||
@@ -55,7 +57,7 @@ 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
|
||||||
@@ -92,6 +94,10 @@ class WordleStateEvaluator:
|
|||||||
for letter in state.board:
|
for letter in state.board:
|
||||||
if letter:
|
if letter:
|
||||||
score += 100
|
score += 100
|
||||||
|
|
||||||
|
for letters in state.letters_eliminated:
|
||||||
|
score += len(letters)
|
||||||
|
|
||||||
return score
|
return score
|
||||||
|
|
||||||
|
|
||||||
@@ -102,15 +108,20 @@ class WordleMoveProcessor:
|
|||||||
board=state.board,
|
board=state.board,
|
||||||
letter_constraints=state.letter_constraints.copy(),
|
letter_constraints=state.letter_constraints.copy(),
|
||||||
answer=state.answer,
|
answer=state.answer,
|
||||||
|
letters_eliminated=[ d.copy() for d in state.letters_eliminated ],
|
||||||
)
|
)
|
||||||
|
|
||||||
for idx, ch in enumerate(guess):
|
for idx, ch in enumerate(guess):
|
||||||
if ch in new_state.answer:
|
if ch in new_state.answer:
|
||||||
new_state.letter_constraints[ch] = WordleLetterConstraint.INCLUDED
|
new_state.letter_constraints[ch] = WordleLetterConstraint.INCLUDED
|
||||||
if state.answer[idx] == ch:
|
|
||||||
new_state.board[idx] = ch
|
|
||||||
else:
|
else:
|
||||||
new_state.letter_constraints[ch] = WordleLetterConstraint.EXCLUDED
|
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
|
return new_state
|
||||||
|
|
||||||
|
|
||||||
@@ -130,7 +141,7 @@ class WordleMoveCalculator:
|
|||||||
self.move_processor = move_processor
|
self.move_processor = move_processor
|
||||||
|
|
||||||
def calculate(
|
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]]:
|
) -> List[Tuple[Word, int]]:
|
||||||
guesses = []
|
guesses = []
|
||||||
words = [
|
words = [
|
||||||
@@ -145,6 +156,7 @@ class WordleMoveCalculator:
|
|||||||
board=state.board.copy(),
|
board=state.board.copy(),
|
||||||
letter_constraints=state.letter_constraints.copy(),
|
letter_constraints=state.letter_constraints.copy(),
|
||||||
answer=answer,
|
answer=answer,
|
||||||
|
letters_eliminated=[ d.copy() for d in state.letters_eliminated ],
|
||||||
)
|
)
|
||||||
new_state = self.move_processor.process(temp_state, guess)
|
new_state = self.move_processor.process(temp_state, guess)
|
||||||
word_total += self.state_evaluator.evaluate(temp_state)
|
word_total += self.state_evaluator.evaluate(temp_state)
|
||||||
@@ -152,8 +164,10 @@ class WordleMoveCalculator:
|
|||||||
word_score = word_total / len(words)
|
word_score = word_total / len(words)
|
||||||
move_rankings.append((guess, word_score))
|
move_rankings.append((guess, word_score))
|
||||||
|
|
||||||
move_rankings = sorted(move_rankings, key=lambda x: x[1])
|
move_rankings = sorted(move_rankings, key=lambda x: x[1], reverse=True)
|
||||||
return move_rankings[0:num_guesses]
|
if limit:
|
||||||
|
move_rankings = move_rankings[0:limit]
|
||||||
|
return move_rankings
|
||||||
|
|
||||||
def _is_word_playable(self, state: WordleState, word: Word):
|
def _is_word_playable(self, state: WordleState, word: Word):
|
||||||
for idx, ch in enumerate(word):
|
for idx, ch in enumerate(word):
|
||||||
@@ -165,12 +179,20 @@ class WordleMoveCalculator:
|
|||||||
# Letter is not already excluded
|
# Letter is not already excluded
|
||||||
if constraint == WordleLetterConstraint.EXCLUDED:
|
if constraint == WordleLetterConstraint.EXCLUDED:
|
||||||
return False
|
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
|
return True
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
logging.basicConfig()
|
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
|
||||||
|
|
||||||
dictionary_provider = DictionaryProvider()
|
dictionary_provider = DictionaryProvider()
|
||||||
state_evaluator = WordleStateEvaluator(dictionary_provider)
|
state_evaluator = WordleStateEvaluator(dictionary_provider)
|
||||||
@@ -182,15 +204,24 @@ def main():
|
|||||||
answer = random.choice(dictionary_provider.provide())
|
answer = random.choice(dictionary_provider.provide())
|
||||||
state = WordleState(board=[None] * WORD_LENGTH, answer=answer)
|
state = WordleState(board=[None] * WORD_LENGTH, answer=answer)
|
||||||
iteration = 1
|
iteration = 1
|
||||||
|
guesses = [] # type: List[str]
|
||||||
while not all(state.board):
|
while not all(state.board):
|
||||||
print("Round:", iteration)
|
log.info("Round: %d", iteration)
|
||||||
print("Board:", str(state.board))
|
log.debug("State: %s", str(state))
|
||||||
move = move_calculator.calculate(state, 5, 1)[0]
|
if iteration == 1:
|
||||||
guess = move[0]
|
guess = "sales"
|
||||||
print("Guess:", guess)
|
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)
|
state = move_processor.process(state, guess)
|
||||||
|
log.info("Board: %s", str(state.board))
|
||||||
|
guesses.append(guess)
|
||||||
iteration += 1
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user