Hey folks. I’m Darrien Glasser, the main dev on the Typeracer project. The project has been going for almost a year and has been through some significant changes while doing its best to keep the core idea intact.
Typeracer is a terminal typing game where the goal is to type out a bit of text from a passage as fast as you can.
Recently we cut the 2.0.0 branch off and released it to the general public, and today we’re going to take a quick at where it started and how it has progressed over time.
Notes
Before we begin - it’s worth noting we were waiting on tui-rs, our terminal rendering library to add PartialEq and Clone on the Text object in a number of places very early on.
There are a number of early commits that reference either a local tui repo, or a git repo usually on my personal GitHub which no longer exists.
If you are building these commits, consider using:
|
|
To ensure they build properly.
The beginning
Let’s look back at history, before there was even a release. The first working commit: 3476353d45c7dd330d33cac40567587d7b6d90b6
You’re going to notice this is looks kind of similar to the current Typeracer interface. It’s got 2 boxes. Type in one, show the passage in the other.
The first noticeable difference though is that it only works in a fully maximized terminal (dimensions about 170x44).
Some other things to notice:
- It doesn’t tell you when you’re complete
- It doesn’t tell you if you made a mistake
- It only has the one passage shown
- There was a strange backspace bug
Backspace would delete the character before it but wouldn’t move back. This would stay with the project for a while.
It was also tiny!
|
|
I don’t think we’ll ever see <100 lines of code after this point.
There isn’t much more exciting about this, so let’s move on.
Pre-First release
We still hadn’t cut off a first release, but we were getting there! The first commit that had a nice semblance of a UI.
It also calculated WPM, but poorly.
Commit: 4e6f7e711c560d0df0a463c01063f4d349d93106
For folks who are not WPM aficionados, modern WPM is not calculated via the literal number of words typed every minute, but rather 5 letters is considered a word.
This was later fixed and made the default behavior, although you are still free
to switch back to the old behavior at any time with typeracer --legacy-wpm
We’re at about 300 lines of code now depending on how you count it.
|
|
First release: version-0.9
At this point, we’d added in the ability to read the simple lang-pack format
The lang-pack version used was 0.1 which is the same as the current one, except it didn’t have any git stuff in it.
We used reqwest to download the latest lang pack and jeez it was bulky. The finalized binary size (post stripping) was 4.5 megabytes and we only had about 500 lines of code!
|
|
The UI is mostly the same except there was this UI for when the latest lang pack was downloaded.
You’ll note that the title of the UI was “checking bounds” (bug) which was because I also added a bounds checking UI that would stop the user from moving forward if the screen wasn’t big enough (AKA 170x40). It took a while for this to get removed.
Also the backspace bug still haunted me :(
v1.0.1, the real first release
Look at that UI :D
Really quite nice and simple. This was also the first release that added generalized CLI args.
There was only one arg. The new kid on the block was --read-text
which let you
take a passage as a CLI arg.
v1.0.0 had a bug that would make Typeracer panic if you didn’t run without
--read-text
making this the first official release.
We’re at roughly 600 lines of code now too!
|
|
v1.0.5 Readline basics
Jumping ahead a little bit, because the next few releases I was far too excited about the project, and pushing out releases very fast.
v1.0.5 added the ability to skip passages, and the C-u
delete all letters
readline binding. It would also display the shortcuts on the bottom of the
screen before they started typing.
By now we had also switched the WPM algorithm to the standard 5 chars == 1 word
rather than 1 word == 1 word. The --legacy-wpm
option existed to revert if
desired.
v1.10.0 Ringbuffers for days
That’s right, we didn’t go straight to v1.1.0, we made the cardinal sin of going to 1.10.0. I did it, and I’d do it again π€
We had made some exciting progress since v1.0.5.
You’ll see there was an error indicator, and errors now showed up in the same bright red as they do today, but the real exciting newest feature was history.
You can go back up to 20 passages in the past using a super efficient ringbuffer like data structure.
This was the last release to use reqwest to fetch lang packs. It was also around this time I got a lot of help from Jeffas.
With one of the many things he did, he removed that darned backspace bug. To get around it we asked tui to completely remove the cursor and emulate it by manually highlighting the letter the user was on.
I appreciate all my contributors, but he put in quite a bit of work around this time!
Also at this point we had broken the 1k LoC boundary.
|
|
v1.1.1 Friendship with reqwest ended, now libgit is my best friend
At this point, we moved away from downloading tarballs in releases of data packs to using libgit to manage language packs.
This was more configurable for us, and also cut stripped binary size to 1.8MiB. It also dropped build times by a huge amount.
Originally the lang pack was stored in the same repo as Typeracer, but after a single iteration, we moved it away to keep git activity of the two separate, and to keep downloaded size down.
Checking bounds title still persisted in the downloader UI at this point.
The upgrade path to everything before this release was a little bugged thanks to my lack of experience with libgit.
For 1.0.9, 1.10.0, or 1.1.0 to run, 1.0.8 had to be run first.
We do our best not to ever break the user experience, so this slipped right on by.
This release also unified a bunch of our keypresses: e.g. C-c
is always exit
or back, C-n
is always next passage, and more.
v1.2.1 Configurability time
It was about time this program got a config file. It didn’t really do a lot yet, but it was there in case you wanted it.
You could configure the lang pack main repo to point to one of your own git repos if you wanted. Nice :D
We also hit about 1500+ lines of code too! We’re getting to be a medium sized project.
|
|
v1.2.2 Scaling time
I love this release, because for the first time in ages, the UI scales. Hacked together in a few hours back after my interview with Google, I put together a really simple version of CSS scaling rules and a styles.css (styles.rs) that takes values based on screen dimensions to scale the UI.
With it, now we have a UI that nicely scales to screen size.
|
|
It’s so tiny :D
I did not get the Google job though, but I guess this is a nice consolation.
v1.4.3 QoL features, colors and combos
These sets of releases are some of my favorites because they had some really simple, but nice to have features.
One of the simplest is combo: get a combo of 60 (do not mess up for 60 consecutive keystrokes) and the boxes change color to a fun green color. The shade is a little different if you hadn’t made a mistake yet.
We also introduced instant death mode (make a mistake and you lose):
This was also around the time I made it so words would disappear from the main box as you typed them (and set as default). It ensured the user could always type the full passage and made it so your eyes didn’t have to dart around when typing.
I had a lot of fun making it!
Oh and we’ve also gone past the 2kLoC achievement too!
|
|
The most important thing though is that the title in the repo installer finally no longer said Checking bounds.
v1.6.0 Full wide character support
Rust may support UTF-8 nicely, but it turns out you can do terrible things very easily anyway, because the unicode standard is interesting.
One of the folks using Typeracer was interested in trying to use it to type out Chinese passages.
Well you know what, why not? It was the beginning of the COVID pandemic in America and I wasn’t going outside.
Wide character support also lets me do terrible things like this:
|
|
If you did this in an earlier release though, it would have crashed and burned.
Note this is impossible to align properly on by website using the code formatter. It looks good in a terminal though, I promise.
v1.7.0 Stop - it’s graph time π
This is the first release where I retained data across runs. Using SQLite it was surprisingly painless.
I had a number of requests for adding a graph of how users had done over time, so here it was.
You’ll note the days shown are not conventional dates, but rather how many days back from your last play.
I didn’t want to show based on the current date since it would really skew results.
The numbers could easily show “real” dates, but British people would get mad at my American dates, and if I picked ISO dates, Americans would be confused, so days since last play it is.
v2.0.0
The final release (for now) put out just a few days ago. Now with support for multiple lang packs, you can use the main lang pack and all its quotes without forking it, adding your own stuff, and then having to stay in sync with the main pack.
We also set up some real SQL migration using refinery, and made it real easy to add new game modes.
The final code count comes out to almost 4k LoC
|
|
Weighing in as a solid mid-sized project.
Wrap up
I had a great time implementing all of this. It was a superb opportunity to learn Rust and I made a program I really enjoy using, and hopefully other folks do to!