Advent of JavaScript, Day 4
Challenge #4 is creating a computer keyboard that jiggles the key it wants you to press:
With the project files downloaded and codesandbox
’d into a live CodeSandbox, I’m ready to get going!
User Requirements
Again, let’s start with the User Requirements and speculate how I can solve these:
- See the computer keyboard centered on the page
Done already via styles.css
.
- A random letter will start to jiggle.
The animation already exists via styles.css
’ jiggle
class.
As for choosing a random letter, each <button>
has a data-key
that indicates what should be pressed (e.g. TAB
).
I can randomly pick one of these (ideally, from a dwindling list, so repeats don’t happen) and toggle jiggle
.
- The user should type the same key that’s jiggling, and it will stop.
I’ll need to double-check the KeyboardEvent
properties
to support SHIFT
& possibly others.
Otherwise, string matching against event.key
should be enough!
- A new, random key will start jiggling
I can have a randomizeKey
function that will randomly pick a key from the <button>
s and toggle jiggle
.
Wiring it Up
So, this was actually a little more fun than I expected!
-
Getting a list of
keys
is straightforward:let keys = Array.from(document.querySelectorAll('[data-key]'))
-
So is toggling the
jiggle
class:if (currentKey) currentKey.classList.remove('jiggle') currentKey = keys[i] currentKey.classList.add('jiggle')
-
And listening to events:
window.addEventListener('keyup', (event) => { if (event.key.toUpperCase() === currentKey.getAttribute('data-key')) { randomizeKey() } })
I also listen to
click
events, so I can click the Caps lock button (since I have it mapped toEscape
on my keyboard):window.addEventListener('click', (event) => { if (event.target.hasAttribute('data-key')) { randomizeKey() } })
Algorithms
But, what I haven’t done before was swapping variables without an intermediate value and an optimal, simple shuffling algorithm.
-
Swapping variables without an intermediate value is easy with ES6:
;[a, b] = [b, a]
For this use-case, I swapped the random
i
key with key at theend
of the array:;[keys[i], keys[end]] = [keys[end], keys[i]]
-
Next, I wanted to see if there’s an official algorithm to what I proposed above, and there is: Fisher-Yates Shuffle
There’s a fantastic visualization from Mike Bostocks:
const randomizeKey = () => { // If there are no available keys left, restart the shuffle if (end === 0) end = keys.length // Pick a random index from 0 to the end of the array, // then shorten the array i = Math.floor(Math.random() * end--) // Stop jiggling the current key if (currentKey) currentKey.classList.remove('jiggle') currentKey = keys[i] currentKey.classList.add('jiggle') // Swap random key with the key at the end. // This ensures it won't get picked again since it's been "shuffled" out to the end ;[keys[i], keys[end]] = [keys[end], keys[i]] }
- Initially, the
end
index is the length of the array. - We randomly pick
i
from 0 to theend
of the array. - We decrement
end
by 1 so that we can move the random keys to the end of the array. - Using the previous swap algorithm, we swap the randomly selected key with the untouched key at the end.
- After we finish shuffling and the
end
is0
, we reset it back tokeys.length
& reshuffle the shuffled array all over again.
- Initially, the
Demo
I spent the most time actually writing this blog post and porting over Mike Bostocks’ visualization :)