User Tools

Site Tools


cs20pf22as14

cs20pf22as14: A Wordle Guesser!

Goals

  • Develop some logic to successfully solve Worldle-like puzzles.
  • Learn a bit about interacting with a simple stateful web service from Python.

Prerequisites

This assignment requires familiarity with the lecture materials presented in class through week 14.


Background

Wordle

Image depicting a Wordle game that was won in four guesses

You may be familiar with Wordle, which was just someone's little pet project that became very popular very quickly in late 2021. Quoth the arbiter of all human information:

Players have six attempts to guess a five-letter word, with feedback given for each guess in the form of colored tiles indicating when letters match or occupy the correct position. The mechanics are nearly identical to the 1955 pen-and-paper game Jotto and the television game show franchise Lingo. Wordle has a single daily solution, with all players attempting to guess the same word.

To the right, you can see a visual depiction of what it looks like to play Wordle. In this example, the player guessed the correct word in four tries. Each guess is given feedback as described on Wikipedia:

After every guess, each letter is marked as either green, yellow or gray: green indicates that letter is correct and in the correct position, yellow means it is in the answer but not in the right position, while gray indicates it is not in the answer at all. Multiple instances of the same letter in a guess, such as the “o”s in “robot”, will be colored green or yellow only if the letter also appears multiple times in the answer; otherwise, excess repeating letters will be colored gray.

Wordle Web Service on jeff.cis.cabrllo.edu

There is a stateful web service at URL https://jeff.cis.cabrillo.edu/util/wordle that provides an API for playing a Wordle-like game. Visiting that URL in a browser will show you a short message. The service is stateful in the sense that it keeps track of the state of a user's game (and history of games) on the server side. When a client “logs in” by sending a user ID, the server will respond with a cookie that allows the client to stay “logged in” as it plays the game, in the same way that you remain “logged into” this website you're using right now. This is the same mechanism that keeps you logged into most other services like Twitter, Gmail, etc., when you're using them through a web browser.

Your user ID for this service is: <html>

hello

</html>

Words are chosen randomly from file /srv/datasets/wordle_words.txt, which contains 18455 five-letter words, one per line.

<html> <script> function updateWordleUserId() { window.fetch(`/util/sha1?q=${document.getElementById('userid').innerText}`, {

method: 'get'

}).then(response ⇒

response.text()

).then(text ⇒ {

document.getElementById('wordle_user_id').innerText = text;

}).catch(err ⇒ {

console.log('Something bad happened: ' + err);

}); } updateWordleUserId(); </script> </html>

Communicating with this service is made easy by the requests module, where a simple Python dict is the means for sending request data (e.g. logging in and making a guess) to the server and receiving responses from the server (indicating the progress of the game)

>>> import requests
>>> URL = 'https://jeff.cis.cabrillo.edu/util/wordle'
>>> USER_ID = '0123456789abcdef0123456789abcdef01234567'  # this dummy user id works for demo purposes
>>> session = requests.Session()  # will automatically deal with cookies
>>> # logs in and tells the server to reset all records of wins/losses for this user:
>>> response = session.post(URL, {'user_id': USER_ID, 'reset_statistics': True})
>>> print(response.json())  # method json() converts the response to a Python dict
{'message': 'Welcome, user 0123456789abcdef0123456789abcdef01234567! Never seen you before... 😉'}
>>> response = session.post(URL, {'guess': 'APPLE'})
>>> print(response.json())
{'progress': [False, None, None, None, None], 'guesses': 1}

The response to a guess request consists of a dict in either of the following forms:

  1. If a game is in progress (i.e. fewer than 6 guesses, and the word has not been guessed):
    1. Key progress has a list of five values describing the feedback for each character in the guess: True is equivalent to a green tile in Wordle, False is equivalent to a yellow tile, and None is equivalent to a gray tile.
    2. Key guesses indicates how many guesses have been made in the game (15).
  2. If a game has ended (i.e. the word has been guessed, or there have been six incorrect guesses):
    1. Key progress is the same as previously.
    2. Key guesses will have the value 6.
    3. Key word indicates what the correct word was.
    4. Key total_games indicates the total number of games the user has played.
    5. Key wins indicates the number of games the user has won.

Here is a screenshot of me manually replicating the Wordle game illustrated in the image above through this web service:

Screenshot of a replication of the four-guess World game above using the requests module to communicate with our Wordle web service


Assignment

You shall define a class named WordlePlayer in a module named wordle with the following attributes:

  1. A constructor that sends a login request to the Wordle service on jeff.cis.cabrillo.edu.
  2. A method named play() that plays one game of Wordle, attempting to guess the correct word.

If you'd like to print any status messages etc., that's fine. They will be ignored.

Starter Code

Feel free to start with this code. For now it is an interface for a human to play a Wordle game. At minimum you need to change the play() method to play automatically instead of use user input for the guesses.

wordle.py
""" Interacting with the Wordle web service on jeff.cis.cabrillo.edu. """
 
import requests
from colorama import Fore, Back, Style
 
URL = 'https://jeff.cis.cabrillo.edu/util/wordle'  # Might as well keep this around
USER_ID = '0123456789abcdef0123456789abcdef01234567'  # Change this
 
 
def _load_words():
  """Returns the Wordle dictionary file as a set of strings."""
  with open('/srv/datasets/wordle_words.txt') as word_file:
    all_words = set(map(str.rstrip, word_file))
  return all_words
 
 
DICTIONARY = _load_words()
 
 
class WordlePlayer:
  """I try to win Wordle games!"""
 
  def __init__(self):
    """Logs into the Wordle service on jeff.cis.cabrillo.edu."""
    self._session = requests.Session()
    self._session.post(URL, {'user_id': USER_ID})
    # TODO (as desired)
 
  def play(self):
    """Plays a game from start to finish, attempting to guess the word."""
    # TODO (for now plays interactive with user input; change to have it play automatically!)
    guess = input()
    response = self._session.post(URL, {'guess': guess}).json()
    print(''.join(Fore.WHITE + (Back.BLACK if r is None else Back.GREEN if r else Back.YELLOW) + c
                  for (c, r) in zip(guess, response['progress'])) + Style.RESET_ALL)
    while 'word' not in response:
      guess = input()
      response = self._session.post(URL, {'guess': guess}).json()
      print(''.join(Fore.WHITE + (Back.BLACK if r is None else Back.GREEN if r else Back.YELLOW) + c
                    for (c, r) in zip(guess, response['progress'])) + Style.RESET_ALL)
    if all(response['progress']):
      print('✅')
    else:
      print(f'🔴 ({response["word"]})')
 
 
if __name__ == '__main__':
  player = WordlePlayer()
  player.play()

Leaderboard

As submissions are received, this leaderboard will be updated with the top-performing solutions, with regard to win rate (as long as they're not too slow to test).

<html>

<table> <thead> <tr><th>Rank</th><th>Win Rate</th><th>Test Time (s)</th><th>Memory Usage (kB)</th><th>SLOC (lines)</th><th>User</th></tr> </thead> <tbody id=“leaderboard-table”> </tbody> </table> <script> function updateLeaderboard() { window.fetch(`/~turnin/leaderboard-cs20pf22as14.txt?t=${new Date().getTime()}`, {

method: 'get'

}).then(response ⇒

response.text()

).then(text ⇒ {

let updated = document.getElementById('leaderboard-updated');
updated.innerText = `(Last updated: ${new Date()})`;
let lines = text.split('\n');
let table = document.getElementById('leaderboard-table');
while (table.firstChild)
  table.removeChild(table.firstChild);
for (let i = 0; i < lines.length; ++i) {
  let tokens = lines[i].split(' ').filter(token => token.length > 0);
  if (tokens.length < 2)
    continue;
  let tdRank = document.createElement('td');
  tdRank.textContent = i + 1;
  let tdWinRate = document.createElement('td');
  tdWinRate.textContent = Number(tokens[0]).toFixed(4);
  let tdTime = document.createElement('td');
  tdTime.textContent = Number(tokens[1]).toFixed(4);
  let tdMemUsage = document.createElement('td');
  tdMemUsage.textContent = tokens[2];
  let tdSloc = document.createElement('td');
  tdSloc.textContent = tokens[3];
  let tdUser = document.createElement('td');
  let userLink = document.createElement('a');
  userLink.href = `/~${tokens[4]}/`;
  userLink.target = '_blank';
  userLink.textContent = tokens[4];
  tdUser.appendChild(userLink);
  let tr = document.createElement('tr');
  tr.appendChild(tdRank);
  tr.appendChild(tdWinRate);
  tr.appendChild(tdTime);
  tr.appendChild(tdMemUsage);
  tr.appendChild(tdSloc);
  tr.appendChild(tdUser);
  table.appendChild(tr);
}

}).catch(err ⇒ {

console.log('Something bad happened: ' + err);

});

window.setTimeout(updateLeaderboard, 60000);

} updateLeaderboard(); </script> </html>


Submission

Submit wordle.py via turnin.

Feedback Robot

This project has a feedback robot that will run some tests on your submission and provide you with a feedback report via email within roughly one minute.

Please read the feedback carefully.

Due Date and Point Value

Due at 23:59:59 on the date listed on the syllabus.

Assignment 14 is worth up to 100 points of extra credit. Your grade will be based on your percentage win rate, so grades will range from 0 to 100.

Possible point values per category:
---------------------------------------
Win rate for your player!           100
Possible deductions:
  Style and practices            10–20%
Possible extra credit:
  Submission via Git                 5%
---------------------------------------
cs20pf22as14.txt · Last modified: 2023-03-27 17:22 by 127.0.0.1