Initial commit.

This commit is contained in:
dongar committed 2023-03-25 19:42:27 -05:00
commit 7b3ac2c964
6 files changed
+370429

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>
+9
View File
@@ -0,0 +1,9 @@
A python library with support for playing or solving Wordle.
=============
The internal dictionary is derived from the kind folks at:
https://github.com/dwyl/english-words
This disctionary does NOT match the official worldle dictionary, so it certainly contains
words not accepted by the official wordle app, and is probably missing words available
to the offical game.
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/python
import wordle
def display_instructions():
"""Print out instuctions before starting play."""
print(f"""Welcome to Wordle!
Wordle has a hidden word of five letters, and you get to
make up to six guesses to find out what it is.
After each guess, you get feedback on each letter of your guess.
* {wordle.display_result(wordle.LetterResult.CORRECT, "Correct!")}
* {wordle.display_result(wordle.LetterResult.MOVE, "Correct, in a different location.")}
* {wordle.display_result(wordle.LetterResult.INCORRECT, "Not in the word.")}
""")
def read_valid_guess(guess_number):
while True:
print(f"Guess {guess_number}: ", end='')
guess = input()
if guess in wordle.dictionary():
return guess
print(f"{guess} is not a valid Wordle word. Please try again.")
def display_results(guess, results):
print(" ", end='')
for i in range(wordle.WORDLE_LENGTH):
print(wordle.display_result(results[i], guess[i]), end='')
print("")
def play_game():
display_instructions()
target = wordle.select_word()
# To help with debugging
# print(f"The target is: {target}")
for guess_number in range(1, 6):
guess = read_valid_guess(guess_number)
results = wordle.grade_word(target, guess)
if wordle.success_result(results):
print(f"You won! It took {guess_number} guesses.")
return
display_results(guess, results)
print(f"Sorry, no guesses remaining. The word was: {target}")
if __name__ == '__main__':
play_game()
+8
View File
@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}
+223
View File
@@ -0,0 +1,223 @@
#!/usr/bin/python
import random
import string
from enum import Enum
from collections import defaultdict
WORDLE_LENGTH = 5
class LetterResult(Enum):
"""Is an individual letter in a word in the result?"""
CORRECT = 1
MOVE = 2
INCORRECT = 3
UNKNOWN = 4
class InvalidWord(Exception):
"""Thrown when an input word is not a valid Wordle."""
pass
_wordle_dictionary = set()
_invalid_words = set(['staia', 'erept', 'boily', 'bolty', 'pindy', 'hyrse', 'hirse', 'retan', 'sisel', 'skeel', 'telae', 'genae', 'beata', 'beala', 'macco', 'beele', 'divet', 'vedet', 'stail', 'roter'])
def dictionary():
if not _wordle_dictionary:
with open('words_alpha.txt') as word_file:
all_words = word_file.read().split()
_wordle_dictionary.update(
filter(lambda w: len(w) == WORDLE_LENGTH and w not in _invalid_words, all_words))
return _wordle_dictionary
def select_word():
return random.choice(list(dictionary()))
def grade_word(target, guess):
# We don't check word length, since all valid words are 5 letters.
if guess not in dictionary():
raise InvalidWord(f"{guess} is not a valid Wordle.")
result = [LetterResult.UNKNOWN] * WORDLE_LENGTH
# We copy target, and remove letters if we find them. This handles duplicate letters correctly.
# For example, if the target is "pulls" and we guess "apple", only the first "p" should be a
# MOVE. The second should be INCORRECT.
remaining = list(target)
# Find all correct results first.
for i in range(WORDLE_LENGTH):
if target[i] == guess[i]:
result[i] = LetterResult.CORRECT
remaining.remove(guess[i])
for i in range(WORDLE_LENGTH):
if result[i] == LetterResult.UNKNOWN:
if guess[i] in remaining:
# Letter is in the wrong place.
result[i] = LetterResult.MOVE
remaining.remove(guess[i])
else:
# Letter is not in target.
result[i] = LetterResult.INCORRECT
return result
def display_result(result, text):
opening = {
LetterResult.CORRECT: '\033[92m',
LetterResult.MOVE: '\033[33m',
LetterResult.INCORRECT: '\033[36m',
LetterResult.UNKNOWN: '',
}
closing = '\033[0m'
return f"{opening[result]}{text}{closing}"
def success_result(result):
for r in result:
if r != LetterResult.CORRECT:
return False
return True
def analyze_dictionary(wordle_dictionary):
# Word Position, Letter: Count
position_matches = [defaultdict(int),
defaultdict(int),
defaultdict(int),
defaultdict(int),
defaultdict(int)]
# Letter: Count
word_matches = defaultdict(int)
for word in wordle_dictionary:
unique_letters = set(word)
for letter in unique_letters:
word_matches[letter] += 1
for index in range(len(word)):
letter = word[index]
assert (letter in string.ascii_lowercase)
position_matches[index][letter] += 1
return position_matches, word_matches
def score_word(position_matches, word_matches, word):
result = 0
# Green Score
for index in range(len(word)):
letter = word[index]
green_score = position_matches[index][letter]
result += 10 * green_score
# Yellow Score
for letter in set(word):
yellow_score = word_matches[letter]
result += yellow_score
return result
def scored_words(wordle_dictionary=dictionary()):
# word: score
scored_words = dict()
position_matches, word_matches = analyze_dictionary(wordle_dictionary)
for word in wordle_dictionary:
scored_words[word] = score_word(position_matches, word_matches, word)
result = list(wordle_dictionary)
result.sort(key=lambda word: scored_words[word], reverse=True)
return [(word, scored_words[word]) for word in result]
class Constraints():
correct = [None] * WORDLE_LENGTH # Letter is None, or known.
# Sets of letters that must be elsewhere
move = defaultdict(set)
min = defaultdict(int)
exact = set()
# List of LetterResult values.
def update_graded(self, guess, grade):
guess = guess.lower()
new_min = defaultdict(int)
new_exact = set()
for i, letter in enumerate(guess):
match grade[i]:
case LetterResult.CORRECT:
self.correct[i] = letter
new_min[letter] += 1
case LetterResult.MOVE:
self.move[letter].add(i)
new_min[letter] += 1
case LetterResult.INCORRECT:
new_exact.add(letter)
new_min[letter] += 0 # Ensure letter is in min.
for letter, m in new_min.items():
self.min[letter] = max(m, self.min[letter])
self.exact |= new_exact
def matches(self, guess):
counts = defaultdict(int)
# All known correct letters in place.
for i, letter in enumerate(guess):
if self.correct[i] and self.correct[i] != letter:
return False # If it's incorrect, the word is incorrect.
# Any MOVE letters in known wrong locations.
if letter in self.move:
if i in self.move[letter]:
return False
counts[letter] += 1
for letter, min in self.min.items():
if letter in self.exact:
if counts[letter] != min:
return False
else:
if counts[letter] < min:
return False
return True
def filter_dictionary(constraints, wordle_dictionary=dictionary()):
return set(filter(lambda w: constraints.matches(w), wordle_dictionary))
if __name__ == '__main__':
constraints = Constraints()
constraints.update_graded('cares', [LetterResult.INCORRECT, LetterResult.INCORRECT, LetterResult.MOVE, LetterResult.CORRECT, LetterResult.INCORRECT])
constraints.update_graded('rover', [LetterResult.INCORRECT, LetterResult.CORRECT, LetterResult.MOVE, LetterResult.CORRECT, LetterResult.CORRECT])
constraints.update_graded('vomer', [LetterResult.CORRECT, LetterResult.CORRECT, LetterResult.INCORRECT, LetterResult.CORRECT, LetterResult.CORRECT])
valid_words = filter_dictionary(constraints)
print("Top: ", scored_words(valid_words)[:1])
+370105
View File
File diff suppressed because it is too large. Load diff