A Homemade Wordle

I’ve been hooked to Wordle recently - hats off to the website owner, one Josh Wardle (also congrats to him cuz I think NYT bought the site for allegedly low seven figures. :D). Since I’ve spent much time on cracking its daily challenges, I figured why not write one myself!

As for my little knock-off program, the rules are very simple and are consistent with the official Wordle. For each guess, you get hints on:

  1. right character in the right place
  2. right character but wrong place
  3. no such character

That’s it. And you only have a maximum of 6 attempts too.

Since UI isn’t really my forte, I sorta skipped skimmed on that. Here’s a sample output of my little program from command line:

Welcome to Xiaofeng's own version of command line Wordle. You have a maximum of 6 attempts. Good luck!
Enter your 5-letter guess of word: sunday
Please input a 5-letter word
Enter your 5-letter guess of word: solid
Results:
uppercase = right character in the right place; 
lowercase = right character but wrong place; 
hyphen    = no such character
attempt 1: solid  --> - o L - - 

Enter your 5-letter guess of word: bully
Results:
uppercase = right character in the right place; 
lowercase = right character but wrong place; 
hyphen    = no such character
attempt 1: solid  --> - o L - - 
attempt 2: bully  --> - - L L - 

Enter your 5-letter guess of word: jello
Results:
uppercase = right character in the right place; 
lowercase = right character but wrong place; 
hyphen    = no such character
attempt 1: solid  --> - o L - - 
attempt 2: bully  --> - - L L - 
attempt 3: jello  --> - E L L O 

Enter your 5-letter guess of word: hello
Congrats, you have cracked this!

And the code in Python:

#!/usr/bin/env python3


from collections import Counter, OrderedDict
from enum import Enum
from random import choice
from sys import exit


class Result(Enum):
    MATCHED = 10
    MISPLACED = 20
    NONE = 30


class Wordle:

    # All in lowercase. should really hide this and/or get a much bigger dictionary
    corpus = [
        'hello',
        'those',
        'skill',
        'shard',
        'elder',
        'pleat',
        'tesla'
    ]

    def __init__(self, try_limit):
        self.try_limit = try_limit
        self.tries = 0
        self.secret_word = choice(self.corpus)
        self.snaps = []

    def check(self, word):
        assert len(word) == len(self.secret_word)
    
        if self.tries >= self.try_limit:
            exit("Sorry, you have run out number of attempts. The answer is {self.secret_word}. Better luck on the next Wordle!")

        self.tries += 1
        
        word = word.lower()
        if word == self.secret_word:
            exit('Congrats, you have cracked this!')

        # Technically we can use list instead, since the key is just the index. But I just love OrderedDict too much
        res = OrderedDict()
        expected = Counter(self.secret_word)
        for i in range(len(word)):
            if word[i] == self.secret_word[i]:
                res[i] = Result.MATCHED
                expected[word[i]] -= 1
                continue

            if expected[word[i]] > 0:
                res[i] = Result.MISPLACED
                expected[word[i]] -= 1
            else:
                res[i] = Result.NONE
        
        self.snaps.append((word,res))
        self.print_snaps_()

    def print_snaps_(self):
        print('Results:\nuppercase = right character in the right place; \nlowercase = right character but wrong place; \nhyphen    = no such character')
        for i, snap in enumerate(self.snaps):
            print(f'attempt {i+1}:', end=' ')
            self.print_snap_(snap)
        print()
        
    def print_snap_(self, snap):
        """
        :type snap: (str, OrderedDict)
        """
        word, res = snap
        print(word, ' --> ', end='')
        for k, v in res.items():
            if v == Result.MATCHED:
                print(word[k].upper(), end=' ')
            if v == Result.MISPLACED:
                print(word[k], end=' ')
            if v == Result.NONE:
                print('-', end=' ')
        print()


if __name__ == '__main__':

    MAX_GUESS = 6
    wordle = Wordle(MAX_GUESS)

    print(f"Welcome to Xiaofeng's own version of command line Wordle. You have a maximum of {MAX_GUESS} attempts. Good luck!")

    while True:
        try:
            guess = input("Enter your 5-letter guess of word: ")
            if len(guess) != 5:
                print("Please input a 5-letter word")
                continue

            wordle.check(guess)
        except Exception as e:
            print(e)

A couple things to improve:

  1. The corpus is definitely too small and shouldn’t be in plaintext for sure. Maybe hook up to some API (such as a word list from Stanford) and encrypt it. I suppose dumping the list to file would work too.
  2. The input guesses should be valid words but right now 5-letter gibberish would pass too. Need to validate input.
  3. A bit more thinking needed for edge cases on repeated characters.

Otherwise, this two-hour side hustle was a bit fun!