Typing Game
Source
<script></script>
Game paused. Click here to play.
Logbook
Wed Feb 21 11:05:13 PST 2024
Today's prompt for the RC Creative Coding group was:
Go to an extreme, move back to a more comfortable place
I chewed on it while I ate my lunch. I was reading an article about feedback loops the night before and I thought to play with that idea. I'm not sure how I jumped from there to typing games. But the vision in my head was of a typing game with a goal, and that goal being in a feedback loop with your current typing speed. The faster you typed, the faster the target speed would get. So in order to continue to hit your goal, you'd probably want a consistent pace, to keep the goal consistent.
Since I took time for lunch, I only gave myself some 40 minutes to work on this which included writing out this idea.
I had never made a typing game before. I assumed I would take a some large list of words, and randomize them, and ask the user to type them in order, tracking their progress. I decided to start there and add my concept of speed tracking and goals only after. I didn't want to take too much time planning.
I didn't want to think about where my random words were coming from, so I just searched online for a generator.
For some reason, Hyperscript didn't love when I put my script blog inside another element. Apparently it only respected top level script tags? This didn't work, but the "FancyScriptTag" outside of the details element did work correctly?
<details>
<summary>Code</summary>
<FancyScriptTag type="text/hyperscript">
{`log 'Online'`}
</FancyScriptTag>
</details>
It was relatively quick to see the progress of typing by manipulation of the text nodes. This first version was confusing since there was some whitespace issues. I played with the CSS whitespace mode to see if I could match reality to how I felt the whitespace should work. I fixed the whitespace problem not with CSS but by not putting newlines between the span elements.
I implemented outlines and an underline on the next letter and a consistent color scheme that I liked quite a bit. Then I did some renaming.
Fri Feb 23 02:59:06 PM PST 2024
Based on some positive feedback from the RC game dev interest group, I made a few adjustments to the page and to the instructions. First I made the code hidden by default, with a button to unhide it.
Future: I wanted to put the code after the game in the HTML, but I didn't know how to do that with Hyperscript. It seemed like Hyperscript required the behavior to be defined prior to the install
, and that could only happen by putting it higher in the document order. I thought about alternatively using the CSS order
property, but that seemed like a lot of hubub.
I had second thoughts about the button, however. My main call to action was to play the game, not to see the source code! Instead I entirely hid the code.
Next I wanted to work on the instructions. Why have the instructions outside the box of the game? This isn't 1985 with game manuals sold in jewel cases alongside cartridges. So I put the instructions inside the box.
I ran into a very annoying bug in my code to do with my extensive use of innerText
, where new lines were swapped for <br />
when I set the current innerText
back to itself with minor modifications. That was mixed with another confusing bug where the visibility and display CSS values were affecting my game code. Because I was using DOM manipulations, somehow using display:none;
caused innerText
to not see and manipulate items. I wasn't sure how to deal with that. Maybe I should switch to the less confusing, simpler innerHTML
?
Sat Feb 24 09:19:08 PM PST 2024
After my challenges with newlines, I decided to ensure all my words in the source file were on one line so that no newlines were introduced and thus no surprising <br />
appeared. I also applied white-space: pre;
to the span
which held the next letter to type, as that prevented it from disappearing when it only had a space in it. Additionally, I added a monospace font to my site so that each space character would take up a consistent amount of horizontal room. This also had the effect of differentiating the instructions from the typing game which felt right.
Next I made a bit of a progression system. Instead of having the instructions outside the game, I had the player type the instructions themselves. When they finished with that, they'd automatically progress to the next stage.
I wanted some visual feedback when I typed the wrong letter, so I made a small CSS animation. This required an interesting dive into both Hyperscript and Tailwind documentation. If I were not using either of these tools, or only one of them, it would have been simpler, but instead I had to add a Tailwind class, then wait for the transitionend
event, and then remove it. Perhaps a Hyperscript afficionado could tell me how to do it in a more straightforward manner.
I also had to adjust my keydown
handler to not take the press of a shift key or control key as a mistaken character. And i found that if I hit the space bar twice quickly, I'd screw up Hyperscript's event synchronization, so I disabled that by saying on every keydown
instead of just on keydown
. This worked swimmingly.
Finally I had complete-feeling basis for a typing game. So I felt comfortable stepping forward towards my lofty ideas about feedback loops. First I researched a bit about how typing speed calculators usually handle typing speed. It would be simple to simply measure the amount of time between when the player began typing a particular word and finished typing that word, disregarding spaces, and then average those times together, but that felt strange given the random nature of word lengths. Perhaps there was some ideal or average word length, and that's what most "words-per-minute" measurements were based on?
The search "how is words per minute calculated" yielded many articles. I started with the Wikipedia entry. As I'd predicted (maybe I already knew this in the depths of my subconscious? I'm not usually this clever), the article confirmed that, "the definition of each "word" is often standardized to be five characters or keystrokes long in English." A detailed article on how to calculate "Raw WPM" at 100utils.com was illuminating as well. Also, the page itself was amazing with an unironic marquee and a lovely textured background image. I was thankful for the math being spelled out so dilligently, and I was doubly thankful that the site pointed out the importance of penalizing errors in typing speeds. The site called the calculation which included error penalties "Net WPM".
The Raw WPM calculation was spelled out as "all keystrokes divided by 5, further divided by the total time".
The Net WPM was "all keystrokes divided by 5, minus the number of uncorrected errors left, all divided by the total time".
The article also pointed out that accuracy alone may be an interesting metric, which was simply the percentage of the number of correct keystrokes divided by the total keystrokes.
Note that when I said keystrokes, I meant characters on the keyboard. I did not mean to propose that when the shift key is pressed that be counted as a separate keystroke. Maybe "characters" would be better, or as the 100utils people say, "typed entities." Either way, I think you catch my meaning.
Future: I hadn't considered the effect errors might have on my feedback loop concept. I circled back to the idea once I had tried out my original idea.
I wanted to measure a rolling average WPM so that it would update as the player changed their speed in real time. I'd use the change in the rolling average over time as a slope, and I'd set a goal based on the trajectory of that slope. So, if the player typed faster in the current second than the last second, they'd be encouraged to go faster still in the next second via an even higher goal.
Future: Since I wasn't sure how this would work in practice, I wanted to start by visualizing these calculations on a running graph. I thought I'd try out d3.js, which I had used a long time ago but not recently.
Mon Feb 26 09:58:02 AM PST 2024
I started by timing the total amount of time it took to complete each stage. Then I thought I'd count how many characters were in the stage, and divide that by five, then divide that by the time. That wouldn't be the rolling average I wanted yet, but it would be a good start. All the pieces on the game board.
That seemed to work well. I guess the only verification I could make on the math was my own beliefs about my typing speed, which seemed to hover between 75 and 100 (raw) WPM depending on my focus.
Future: I found a bug where if I blurred the game (which displayed the pause screen) while the "wrong character" CSS transition was occurring, then the class would never be erased and the indicator would stay in the bad state (red) forever! How dreadful. I also observed this same problem by some combination of hitting too many wrong answers and write answers together quickly. I wasn't easily able to reproduce this situation though.
Future: One improvement I wanted to make was when my timer started. I didn't want to start the timer on a particular level until the player typed their first character. Right now, I could feel when I played a sense of urgency when a new set of words appeared to start typing right away. Instead, I'd like to know that I could take a moment and stretch my fingers or prepare myself otherwise before I started typing, and I wouldn't be penalized for that. So in addition to changing when I started timing, I also felt I needed to convey that to the player some how. Perhaps with a visual queue of a timer. I didn't immediately make this change because it felt like some complex state, where my keydown tracker would also have to understand if it were game-time. I felt the need from this and other sources to change my game to use a statechart like XState. I wasn't ready to do that yet.
I was eager to make a chart, but I determined that the chart wouldn't be too interesting until I had a rolling average, and thus more data points to put on that chart. Right now my chart would only have one data point after every level. So I changed to a rolling average. Up to this point I was logging the WPM to the console, so I first changed that to display in the play area itself.
Future: I changed from a separate WPM calculation per stage to a rolling average every 5 characters
Fri Mar 1 04:46:20 PM PST 2024
At Game Dev Interest Group at RC someone made the comment that the visual indication was too subtle. At the time, the indicator was a quick color tween from the normal amber to red. I agreed it was likely too little contrast especially if someone had colorblindness. I had the idea to add some motion to the indicator as well. Yannick suggested that motion be only horizontal, to suggest the letter was shaking its head "no" at the player. So I tried that out quickly. I tried out Tailwind's transition-transform
.
Because I was transforming two properties, I had to specify both of them in one rule. If I used two transition-
rules, the latter overrode the former. So I had to use transition-[color,transform]
to allow both to work.
Unfortunately, to get the transition-transform
to work, I had to set the span to inline-block
which set me back to some of my previous whitespace related woes. Now the space character shifted around depending on where it was.
Sat Mar 2 03:58:33 PM PST 2024
After much play and feeling annoyed, I realized I could avoid the use of inline-block
if instead of using transform
, I used position: relative;
and left: -1;
to shake the character. It worked smoothly!