diff --git a/src/cli.py b/src/cli.py index 0ff9814..37d146b 100644 --- a/src/cli.py +++ b/src/cli.py @@ -15,6 +15,7 @@ from shiv.bootstrap import current_zipfile import src from src.helpers import flip_char, load_config, write_config from src.joplin import process_joplin_posts, get_folders, BASE_URL +from src.wordle import wordle from src.art import BANNERS @@ -27,12 +28,12 @@ from src.art import BANNERS @click.option( "--update", is_flag=True, - help="Check Gitea for a new version and auto-update.", + help="Check Forgejo for a new version and auto-update.", ) def main(ctx, update): """Launch a utility or drop into a command line REPL if no command is given.""" if update: - update_from_gitea() + update_from_forgejo() sys.exit() elif ctx.invoked_subcommand is None: banner = random.choice(BANNERS) @@ -208,8 +209,8 @@ def get_saved_from_reddit() -> None: _process(item) -def update_from_gitea(): - """Get the newest release from Gitea and install it.""" +def update_from_forgejo(): + """Get the newest release from Forgejo and install it.""" status = Status("Checking for new release...") status.start() @@ -219,7 +220,7 @@ def update_from_gitea(): if response.status_code != 200: status.stop() click.echo( - f"Something went wrong when talking to Gitea; got a" + f"Something went wrong when talking to Forgejo; got a" f" {response.status_code} with the following content:\n" f"{response.content}" ) @@ -243,6 +244,7 @@ def update_from_gitea(): status.stop() click.echo(f"Updated to {release_data['tag_name']}! 🎉") +main.add_command(wordle) if __name__ == "__main__": main() diff --git a/src/wordle.py b/src/wordle.py new file mode 100644 index 0000000..b80f81f --- /dev/null +++ b/src/wordle.py @@ -0,0 +1,179 @@ +import random + +import click +import httpx +from rich.status import Status + +from src.helpers import base_folder + + +all_valid_guesses = "https://gist.githubusercontent.com/itsthejoker/7d4c0d10a4e97cc7493e765d9e848a53/raw/b3970cc79bf88c872dd67c3896d8a8673377b0fd/wordle-nyt-allowed-guesses-update-12546.txt" +all_answers = "https://gist.githubusercontent.com/itsthejoker/1c7b8b97c454ce464aa5ea7f187d81e9/raw/1a2e59929b833925af800298d1003e48d90b5898/wordle-nyt-answers-alphabetical.txt" +answers_path = base_folder / "opsbox_wordle_answers.txt" +guesses_path = base_folder / "opsbox_wordle_guesses.txt" + + +def download_wordle_data(): + with httpx.Client() as client: + with open(answers_path, "w") as f: + f.write(client.get(all_answers).text) + with open(guesses_path, "w") as f: + f.write(client.get(all_valid_guesses).text) + + +def check_if_need_to_download_data(): + if not answers_path.exists() or not guesses_path.exists(): + download_wordle_data() + + +def load_data(): + status = Status("Setting up game...") + status.start() + + check_if_need_to_download_data() + with open(answers_path, "r") as f: + answers = f.read().splitlines() + with open(guesses_path, "r") as f: + guesses = f.read().splitlines() + guesses += answers # answers are valid guesses + + status.stop() + return answers, guesses + + +def print_keyboard(guessed_word_list: list[str], keyboard_display: dict[str, int]): + first_row = "qwertyuiop" + second_row = "asdfghjkl" + third_row = "zxcvbnm" + click.echo() + all_letters_guessed = set("".join(guessed_word_list)) + for count, item in enumerate([first_row, second_row, third_row]): + click.echo(" " * count if count != 2 else " " * 3, nl=False) + for letter in item: + click.echo(" ", nl=False) + if letter in all_letters_guessed and keyboard_display.get(letter) == 2: + click.echo( + click.style(letter.capitalize(), fg="green", bold=True), nl=False + ) + elif letter in all_letters_guessed and keyboard_display.get(letter) == 1: + click.echo( + click.style(letter.capitalize(), fg="yellow", bold=True), nl=False + ) + elif letter in all_letters_guessed: + click.echo( + click.style(letter.capitalize(), fg="white", bold=True), nl=False + ) + else: + click.echo(click.style(letter.capitalize(), fg="white"), nl=False) + click.echo("\n") + + +def print_word_matrix( + answer: str, + letter_counts: dict[str, int], + guessed_word_list: list[str], + keyboard_display: dict[str, int], +) -> None: + for guess in guessed_word_list: + guess_letter_counts = {} + click.echo(" ", nl=False) + for count, letter in enumerate(guess): + add_newline = count == 4 + guess_letter_counts[letter] = guess_letter_counts.get(letter, 0) + 1 + if letter == answer[count]: + click.echo( + click.style(letter.capitalize(), fg="green", bold=True), + nl=add_newline, + ) + keyboard_display[letter] = 2 + elif ( + letter in answer + and guess_letter_counts[letter] <= letter_counts[letter] + ): + click.echo( + click.style(letter.capitalize(), fg="yellow", bold=True), + nl=add_newline, + ) + + if keyboard_display.get(letter) != 2: + keyboard_display[letter] = 1 + elif ( + letter in answer and guess_letter_counts[letter] > letter_counts[letter] + ): + click.echo( + click.style(letter.capitalize(), fg="white", bold=True), + nl=add_newline, + ) + else: + click.echo( + click.style(letter.capitalize(), fg="white", bold=True), + nl=add_newline, + ) + if len(guessed_word_list) < 6 and answer not in guessed_word_list: + click.echo() + click.echo("You have ", nl=False) + click.echo( + click.style(str(6 - len(guessed_word_list)), fg="white", bold=True), + nl=False, + ) + click.echo(f" guess{'es' if len(guessed_word_list) != 4 else ''} left.") + + +def get_unique(sequence): + seen = set() + return [x for x in sequence if not (x in seen or seen.add(x))] + + +@click.command(help="Start a new game of Wordle!") +@click.option("--debug", is_flag=True, help="Debug mode") +def wordle(debug): + click.echo() + click.echo("Starting a new game of Wordle!") + click.echo("You have six tries to find a 5 letter word.") + click.echo( + "Gray letters are not in the word, yellow letters are in the word but in the" + " wrong place, and green letters are in the right place." + ) + click.echo("Good luck!") + click.echo() + answers, guesses = load_data() + + todays_word = random.choice(answers) + # every key is a letter, the value is 1 if it's yellow and 2 if it's green + keyboard_display = {} + if debug: + click.echo(f"Today's word is {todays_word}") + + guessed_words = [] + letter_set = get_unique(todays_word) + letter_counts = {letter: todays_word.count(letter) for letter in letter_set} + + while True: + if len(guessed_words) == 6: + click.echo("You lose!") + click.echo("The word was: ", nl=False) + click.echo(click.style(str(6 - len(todays_word)), fg="white", bold=True)) + click.echo("Better luck next time!") + break + + guess = click.prompt("Enter a guess") + guess = guess.lower() + click.echo() + + if len(guess) < 5 or len(guess) > 5: + click.echo("Guesses must be 5 letters.") + continue + + if guess not in guesses: + click.echo("Sorry, that's not a valid guess. Try again!") + continue + + guessed_words += [guess] + + print_word_matrix(todays_word, letter_counts, guessed_words, keyboard_display) + print_keyboard(guessed_words, keyboard_display) + + click.echo() + if guess == todays_word: + click.echo("You win!") + break