Overview

Website: https://xkcd.prout.tech

Source: https://github.com/eugene-prout/xkcd-trainer

This project originates from a game that I sometimes play with my girlfriend, it goes like this:

  1. She opens a random XKCD comic
  2. She reads me the comic number and the title
  3. I try to describe the comic to her

I’m getting pretty good at it.

The webpage is designed to take the role of the question-asker. It has a bank of comics and randomly presents them to the player showing only the number and title. When the player is ready to see the answer, they click the card to reveal the comic.

The project started in the summer of 2022 (the night before my first day at Williams), it looked very different then and I didn’t progress past one night of development. Now in a completely different form, it is finished.

Construction

The comics are from a dataset on Hugging Face. This dataset stores them as JOSNL (JSON Lines). I hadn’t seen this format before and it seems pretty handy. I first wanted to have the data included on the webpage so there was no need to make any API calls. As the dataset contains more than just comic titles, names, and links, I could have stripped it down to what I wanted and packaged it with the frontend. However, it seemed more flexible to store the data in a separate place which the frontend can retreive.

This led to me making a small Python API to expose the dataset. A quick jq --slurp . <dataset.jsonl >data.json transformed the data into an object which could be loaded by Python. The API has one endpoint /random which takes a query string parameter for how many comments to return.

After making the backend, I created a Vue-based website to show the cards. This is simple and a bit sloppy but it works. It uses a package to handle showing the correct face of the card and the flip to show the other side (CSS transitions under-the-hood) . The frontend loads the data in chunks of 50 cards, and loads the next batch when the user is near the end of the current batch.

Deployment

Normally I would split the frontend and backend into separate repository. For this project I knew they would be small and wanted to try moving them into one repository. It worked well, the frontend is stored under /frontend, the backend is under /backend. The CI scripts to build Docker images only trigger on pushes to the correct folder. This means changes in the /frontend folder will only trigger the frontend CI pipeline, likewise for the backend.

One gain of having all parts of the project in the same repo is that the “glue” code between them can be checked into source control and be distributed alongside the source. For example, a script to run the development server and a Caddyfile to proxy them is included in the repo, as well as a docker-compose.yaml to run a production build. Without this mono-repo stlye structure I’m not sure where these files would have been stored.