Introducing Tradle

I love Wordle. I also love Quordle, Heardle, Lewdle, and Semantle. (Okay, I hate Semantle). And I love trading.

So I decided to make Tradle. It's the Wordle for all of the finance lovers out there. It's a mash-up of trading terms, investing terms, prominent people in the investing space, notable companies, and some crypto related terms too.

Here's a rundown of how I approached building it.


Game Mechanics

The Code
This was the easiest part, and took me around two hours. I used the open-source Reactle code, which was 99% of everything done and working out of the box.

The Solution set
Most of the time spent here was thinking of all the five-letter trading-related words I could, and then I scoured Investopedia's dictionary for any extra ones and inspiration.

I ended up with 100 words in the solution set, but that will grow in time.

Some of the solution set (spoilers)

The solution set is made up of anything money-related I could think of, but the themes seem to be:

  • Trading-related terms, like ALPHA, CALLS, TSLAQ. Many of the greeks are five letters, which is nice.
  • Investing-related terms, like CAPEX and EBITA (yes, it is EBITDA without the D. Apparently it's a real term)
  • Notable companies, like APPLE.
  • Notable people in the investing sphere, like LYNCH, THIEL, BEZOS
  • Crypto related words. There's a handful of five-letter coin names, it seems. I failed my own tradle when FLOKI came up.

Daily or infinite play?
I've kept the Wordle spirit and made this a daily game. There are only 100 words, after all.

Personally I think Wordle and Wordle-clone makers should have a free daily version, and then a paid infinite play version. I would do that if I thought anyone would pay for it 😆.

The tweaks
I did change the source code a little, namely:

  • showing a description of the solution word at the end of the game
  • basic UI changes - making the colours match this site, and removing some fluff
  • removing the dark-mode option. Felt bad doing this as a lover of dark mode, but it's embedded into this website where everything is white, and I couldn't figure out how to make it not look stupid otherwise

Key take-aways: I occasionally work on my front-end skills with projects like this, but the industry and tooling as a whole move faster than I'm improving... so I'm actually just getting worse all the time. Feels bad 😭.


Hosting the game on this site

Setting up the game app: 2 hours.
Trying to get my site to host two apps instead of one: 50 hours, blood, sweat, and tears.  

Prior site set-up
This blog is a Ghost application running in Docker that I'm self-hosting on GCP's free tier. If you're interested in how I did that, it was largely following this tutorial. I've stuck Cloudflare in front of it (also free tier), mostly so I can throw in path-based routing logic via Cloudflare workers, which typically powers things that nobody ever sees like racheldoji.com/lololol.

Why am I self-hosting ghost instead of paying $9 a month for it to be taken care of for me? Great question 😅.

Changes to add Tradle (nginx, docker-compose)
Hosting both the ghost blog and the Tradle app at once was going to require some changes.

So I set up a docker-compose file that would be responsible for running:

  • the ghost docker app
  • the tradle docker app (...which later turned into static hosting from a build folder instead)
  • dockerised nginx to use as a reverse proxy

Basically, all manner of weird things broke when I did this.

Even when I had the site in a state where tradle.racheldoji.com loaded tradle, and racheldoji.com loaded the blog, I suddenly couldn't log into the blog anymore as an admin to write and edit posts.

This was a bad set-up where essentially I had:

  • requests to racheldoji.com looked like https, with the cute little 🔒 lock 🔒 and everything, but were really only https to cloudflare
  • cloudflare was sending requests via http to the GCP server, because i had no certificates set up (roast me)
  • after going behind the dockerised nginx reverse proxy, the ghost blog got hella confused about whether it was http or https. Many broken links ensued. Login was broken. Infinite redirect loops ensued.

So I figured now was as good a time as any to get certificates working, so I followed this tutorial on setting up certificates with letsencrypt, certbot, and docker-compose, and cried a lot. This is genuinely a bad solution for what I want to do in future, because every extra subdomain I want a certificate for now, I am going to have deep pains in achieving. But for now, problem solved.

Then I had to change config everywhere. In Cloudflare, I turned https to always on, and set SSL to full (strict) mode. On my server, I made sure ghost was configured with https://racheldoji.com as the url. In nginx, I have a catch-all for *.racheldoji.com that always redirects requests to port 80 to 443 apart from the certificate challenge location block.

Needing to get configuration just right in 500 different places is a pain.

CI set-up
I do local development of Tradle on my laptop. I also build it on my laptop. Then I commit the mother of all sins, push the build directory to github, and then SSH into my GCP server and pull the build directory down. Do I win any devops prizes?

(...but seriously how do competent people do stuff like this without spinning up a new server with the new code, cutting traffic over, winding down the old server...?)


Integrating the game into a blog page

If you play Tradle, you'll notice it has the same menu heading bar as any other page on this blog, although it's not hosted within the ghost app itself. I wanted this so that Tradle players might consider exploring some other parts of the site outside of the game. Here's how that works.

Making a page was just like making any other ghost blog post. I created a page called tradle, that lives at racheldoji.com/tradle. Then I had to get the tradle  app embedded.

Ghost has markdown support, and markdown is a subset of HTML - this literally blew my mind, it's whacked. So, I added some markdown, and chucked in an iframe that loads naked-tradle.racheldoji.com, which is where I'm hosting the raw tradle app.

This didn't work great out of the box, as you could only see the top smidgeon of the game:

tradle in an iframe out-of-the-box

Luckily, ghost lets you inject code on any page you want. I already have some site-wide code injection that adds the youtube logo up in the top right of the menu for instance, but this was my first page-specific code injection.

I added a bunch of CSS to make sure the Tradle game was visible, centred, and some other good UI stuff.

I also added some custom JS to ensure the Tradle iframe was focussed. Otherwise, you'd be typing guesses aimlessly without the guess boxes filling out.


Mapping the tradle subdomain to the path URL

AKA using the subdomain URL (tradle.racheldoji.com ) as the link, instead of the path-based URL, racheldoji.com/tradle.

This was a massive pain. Long story short, here's the nginx config:

server {
    server_name tradle.racheldoji.com;
    listen 443 ssl;
    # ssl stuff redacted for brevity
    
    location = / {
        proxy_pass http://blog:2368/tradle$request_uri;
    }
        
    location / {
        proxy_pass http://blog:2368$request_uri;
    }
}

blog is the docker container running ghost. The second location block without the tradle redirect is because... I don't really know. Without it, the page loads with completely broken CSS, and a bunch of failed network requests to CSS and JS files. Something something relative paths, I guess.


The Final Boss

So, everything is set up. One last run-through to see if everything works.

BUT SHARING DOESN'T WORK?! The Share button is meant to copy the game to your clipboard. This appeared to be doing absolutely nothing.

Sharing definitely isn't the main point of playing, but it's pretty pointy.

Found this lovely error in the console:

That link takes you to a page called Deprecating Permissions in Cross-Origin Iframes. Absolute heart-sinking moment when I realised the iframe setup was not going to work. All that CSS and JS injection finicking for naught.

... turned out it was a very scary non-error, and I just had to add allow="clipboard-write" to the <iframe> tag. All good.


And now it's done. Whew. Hit me up if you've got five-letter finance words you want to see.

Was it worth it?

Absolutely not. The time it took setting this up will not be outweighed by the joy some might get from playing. I thought this was a two hour project, and I was kind of right - the game mechanics were. The rest was all pain.

But every future project will be easier now, so 🤷‍♀️.

Happy playing! You can send me feedback here.