diff --git a/.gitignore b/.gitignore index bee8a64..b6e4761 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,129 @@ -__pycache__ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..306f58e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file diff --git a/bot.py b/bot.py index 95f8dc3..52c83de 100644 --- a/bot.py +++ b/bot.py @@ -1,37 +1,50 @@ import random + import numpy as np import pandas as pd + class Agent: - def __init__(self, game, f_name='data/words.csv'): - self.vowels = ['A','E','I','O','U','Y'] + def __init__(self, game, f_name=r"D:\np\wordle_solver\data\words.csv"): + self.vowels = ["A", "E", "I", "O", "U", "Y"] w_bank = pd.read_csv(f_name) - w_bank = w_bank[w_bank['words'].str.len()==game.letters] - w_bank['words'] = w_bank['words'].str.upper() #Convert all words to uppercase - w_bank['v-count'] = w_bank['words'].apply(lambda x: ''.join(set(x))).str.count('|'.join(self.vowels)) #Count amount of vowels in words + w_bank = w_bank[w_bank["words"].str.len() == game.letters] + w_bank["words"] = w_bank[ + "words" + ].str.upper() # Convert all words to uppercase + w_bank["v-count"] = ( + w_bank["words"] + .apply(lambda x: "".join(set(x))) + .str.count("|".join(self.vowels)) + ) # Count amount of vowels in words self.w_bank = w_bank self.game = game - self.prediction = ['' for _ in range(game.letters)] + self.prediction = ["" for _ in range(game.letters)] self.y_letters = {} self.g_letters = [] def calc_letter_probs(self): for x in range(self.game.letters): - counts = self.w_bank['words'].str[x].value_counts(normalize=True).to_dict() - self.w_bank[f'p-{x}'] = self.w_bank['words'].str[x].map(counts) + counts = ( + self.w_bank["words"] + .str[x] + .value_counts(normalize=True) + .to_dict() + ) + self.w_bank[f"p-{x}"] = self.w_bank["words"].str[x].map(counts) def parse_board(self): if self.game.g_count > 0: g_hold = [] for x, c in enumerate(self.game.colours[self.game.g_count - 1]): letter = self.game.board[self.game.g_count - 1][x] - if c == 'Y': + if c == "Y": if letter not in self.y_letters: self.y_letters[letter] = [x] else: if x not in self.y_letters[letter]: self.y_letters[letter].append(x) - elif c == 'G': + elif c == "G": self.prediction[x] = letter else: if letter in self.prediction: @@ -41,31 +54,39 @@ def parse_board(self): self.y_letters[letter].append(x) elif letter not in self.g_letters: self.g_letters.append(letter) - self.g_letters = [l for l in self.g_letters if l not in self.y_letters and l not in self.prediction] + self.g_letters = [ + l + for l in self.g_letters + if l not in self.y_letters and l not in self.prediction + ] def choose_action(self): self.parse_board() if len(self.g_letters) > 0: - self.w_bank = self.w_bank[~self.w_bank['words'].str.contains('|'.join(self.g_letters))] + self.w_bank = self.w_bank[ + ~self.w_bank["words"].str.contains("|".join(self.g_letters)) + ] self.g_letters = [] if len(self.y_letters) > 0: - y_str = '^' + ''.join(fr'(?=.*{l})' for l in self.y_letters) - self.w_bank = self.w_bank[self.w_bank['words'].str.contains(y_str)] + y_str = "^" + "".join(rf"(?=.*{l})" for l in self.y_letters) + self.w_bank = self.w_bank[self.w_bank["words"].str.contains(y_str)] for s, p in self.y_letters.items(): for i in p: - self.w_bank = self.w_bank[self.w_bank['words'].str[i]!=s] + self.w_bank = self.w_bank[self.w_bank["words"].str[i] != s] self.y_letters = {} for i, s in enumerate(self.prediction): - if s != '': - self.w_bank = self.w_bank[self.w_bank['words'].str[i]==s] - self.w_bank['w-score'] = [0] * len(self.w_bank) + if s != "": + self.w_bank = self.w_bank[self.w_bank["words"].str[i] == s] + self.w_bank["w-score"] = [0] * len(self.w_bank) if len(self.w_bank) > 5: - self.calc_letter_probs() #Recalculate letter position probability + self.calc_letter_probs() # Recalculate letter position probability for x in range(self.game.letters): - if self.prediction[x] == '': - self.w_bank['w-score'] += self.w_bank[f'p-{x}'] + if self.prediction[x] == "": + self.w_bank["w-score"] += self.w_bank[f"p-{x}"] if True not in [True for s in self.prediction if s in self.vowels]: - self.w_bank['w-score'] += self.w_bank['v-count'] / self.game.letters - mv_bank = self.w_bank[self.w_bank['w-score']==self.w_bank['w-score'].max()] - result = random.choice(mv_bank['words'].tolist()) + self.w_bank["w-score"] += self.w_bank["v-count"] / self.game.letters + mv_bank = self.w_bank[ + self.w_bank["w-score"] == self.w_bank["w-score"].max() + ] + result = random.choice(mv_bank["words"].tolist()) return result diff --git a/main.py b/main.py index a36dbad..2206f6b 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import random + import numpy as np import pandas as pd from tqdm import tqdm @@ -6,89 +7,90 @@ from bot import Agent from wordle import Wordle -ROWS = 6 -LETTERS = 5 -GAMES = 10 +ROWS = 6 # the number of rows in the wordle, or the number of times you can guess the word +LETTERS = 5 # the number of characters in the word +GAMES = 10 # the number of games to play -w_bank = pd.read_csv('data/words.csv') -w_bank = w_bank[w_bank['words'].str.len()==LETTERS] -w_bank['words'] = w_bank['words'].str.upper() #Convert all words to uppercase +w_bank = pd.read_csv(r"D:\np\wordle_solver\data\words.csv") +w_bank = w_bank[w_bank["words"].str.len() == LETTERS] +w_bank["words"] = w_bank["words"].str.upper() # Convert all words to uppercase -control = input('What would you like to do?\n\n-Test Solver [T]\n-Game Assist [A]\n-Play Game [P]\n\n') -if 'T' in str(control).upper() or 'P' in str(control).upper(): - if 'P' in str(control).upper(): - print('PLAY GAME SELECTED\n---------------------') +control = input( + "What would you like to do?\n\n-Test Solver [T]\n-Game Assist [A]\n-Play Game [P]\n\n" +) +if "T" in str(control).upper() or "P" in str(control).upper(): + if "P" in str(control).upper(): + print("PLAY GAME SELECTED\n---------------------") else: - print('TEST SOLVER SELECTED\n---------------------\n') + print("TEST SOLVER SELECTED\n---------------------\n") results = [] - if 'P' in str(control).upper(): + if "P" in str(control).upper(): silent = True GAMES = 1 else: silent = False - for _ in tqdm(range(GAMES), desc='GAMES', disable=silent): - word = random.choice(w_bank['words'].tolist()) - game = Wordle( - word, - rows=ROWS, - letters=LETTERS - ) + for _ in tqdm(range(GAMES), desc="GAMES", disable=silent): + word = random.choice(w_bank["words"].tolist()) + game = Wordle(word, rows=ROWS, letters=LETTERS) bot = Agent(game) while game.is_end() == False: - if 'P' in str(control).upper(): - u_inp = input('\n* PLEASE GUESS A 5 LETTER WORD\n') + if "P" in str(control).upper(): + u_inp = input("\n* PLEASE GUESS A 5 LETTER WORD\n") else: u_inp = bot.choose_action() if game.valid_guess(u_inp) == True: game.update_board(u_inp) - if 'P' in str(control).upper(): + if "P" in str(control).upper(): # print(game.colours[game.g_count-1]) print("* COLORS & GUESSES:") - for c,b in zip(game.colours,game.board): - colors_string="".join(c) - guess_string="".join(b) - if guess_string != colors_string: # simple hack to not print blank lines: color string is never a legit word. so if both are equal then its an empty line (we haven't played it yet). - print(colors_string, guess_string) + for c, b in zip(game.colours, game.board): + colors_string = "".join(c) + guess_string = "".join(b) + if ( + guess_string != colors_string + ): # simple hack to not print blank lines: color string is never a legit word. so if both are equal then its an empty line (we haven't played it yet). + print(colors_string, guess_string) else: - print('ERROR - WORDS MUST BE 5 LETTERS') + print("ERROR - WORDS MUST BE 5 LETTERS") r = game.game_result() - if 'P' in str(control).upper(): + if "P" in str(control).upper(): if r[0] == True: if r[1] > 0: - print(f'\nCONGRATS YOU WON IN {r[1] + 1} GUESSES!\n') + print(f"\nCONGRATS YOU WON IN {r[1] + 1} GUESSES!\n") else: - print(f'\nCONGRATS YOU WON IN {r[1] + 1} GUESS!\n') + print(f"\nCONGRATS YOU WON IN {r[1] + 1} GUESS!\n") else: - print(f'\nSORRY YOU DID NOT WIN.\n') - print(np.array(game.board),'\n') - results.append({'word':word,'result':r[0],'moves':r[1]+1}) + print(f"\nSORRY YOU DID NOT WIN.\n") + print(np.array(game.board), "\n") + results.append({"word": word, "result": r[0], "moves": r[1] + 1}) results = pd.DataFrame(results) print(results) - print(f'Win Percent = {(len(results[results["result"]==True]) / len(results)) * 100}%\nAverage Moves = {results[results["result"]==True]["moves"].mean()}') -elif 'A' in str(control).upper(): - print('GAME ASSIST ACTIVATED\n---------------------') - game = Wordle( - None, - rows=ROWS, - letters=LETTERS + print( + f'Win Percent = {(len(results[results["result"]==True]) / len(results)) * 100}%\nAverage Moves = {results[results["result"]==True]["moves"].mean()}' ) +elif "A" in str(control).upper(): + print("GAME ASSIST ACTIVATED\n---------------------") + game = Wordle(None, rows=ROWS, letters=LETTERS) bot = Agent(game) for i in range(ROWS): guess = bot.choose_action() - print(f'\nSuggested Word = {guess}\n') - u_inp = input('What were the colours returned [ex. ybggy]?\n') + print(f"\nSuggested Word = {guess}\n") + u_inp = input("What were the colours returned [ex. ybggy]?\n") + if u_inp.lower() == "ggggg": + print("\nCONGRATS YOU WON The game!\n") + break game.colours[i] = [s for s in str(u_inp).upper()] game.board[i] = [s for s in str(guess).upper()] game.g_count += 1 for x, s in enumerate(game.colours[i]): - if s == 'Y': + if s == "Y": if guess[x] in bot.y_letters: bot.y_letters[guess[x]].append(x) else: bot.y_letters[guess[x]] = [x] - elif s == 'B': + elif s == "B": if guess[x] in bot.g_letters: bot.g_letters.append(guess[x]) - elif s == 'G': + elif s == "G": bot.prediction[x] = guess[x]