The Konami Code is a originally a cheat code that now belongs to the pop culture.
It's a fairly traditional code that consists of the following key sequence: ↑
↑
↓
↓
←
→
←
→
B
A
Initially used in many Konami Games, it then spreads to other video games and even in other software and websites such as in one of the Bank of Canada's website!
Since it has spread so far into the "geek culture", why not incorporating it into your next website?
In today's article, we are going to see how to detect the Konami Code in Svelte by leveraging the concept of stores in order to create a small easter egg 🥚.
Table of Contents
- Referencing the Konami Code
- Detecting the Last Pressed Keys
- Detecting the Konami Code
- Reacting to Its Detection
Referencing the Konami Code
The Konami Code will be a sequence of keys that can either be:
Arrow Up
Arrow Down
Arrow Left
Arrow Right
-
a
in lowercase or uppercase -
b
in lowercase or uppercase
From that point, we can create a custom type to work with:
type KeyCode =
| 'ArrowUp'
| 'ArrowDown'
| 'ArrowLeft'
| 'ArrowRight'
| 'KeyB'
| 'KeyA'
| undefined; // 👈 Other keys are to be ignored
We can then represent the Konami Code as a sequence of KeyCode
s:
const konamiCode: KeyCode[] = [
'ArrowUp',
'ArrowUp',
'ArrowDown',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowLeft',
'ArrowRight',
'KeyB',
'KeyA',
];
Detecting the Last Pressed Keys
Creating a readable store
The last pressed keys sequence will be store that can only be modified when a key is pressed, not from the outside.
For that, Svelte has the notion of readable store.
In our case, we want the store to listen to any key down event when first subscribed and to remove this listener when unsubscribed:
const lastKeyPressed = readable(
new Array<KeyCode>(),
() => {
const handleKeyDown = (event: KeyboardEvent): void => { };
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}
);
And just like that we created a store that will hold a sequence of KeyCode
(or undefined
if a pressed key does not belong to the Konami Code)!
Implementing the handleKeyDown
function
Now that we have our container, we still have to implement the logic that will be executed on each key pressed.
What we would ideally like is to map a pressed key to a KeyCode
and append it to the sequence.
We can start by creating a map that will convert a the KeyboardEvent.key
property to a KeyCode
:
const keyMap: Record<string, KeyCode> = {
ArrowUp: 'ArrowUp',
ArrowDown: 'ArrowDown',
ArrowLeft: 'ArrowLeft',
ArrowRight: 'ArrowRight',
a: 'KeyA',
b: 'KeyB',
A: 'KeyA',
B: 'KeyB',
};
What is left is to use it to detect which KeyCode
has been pressed:
const lastKeyPressed = readable(
new Array<KeyCode>(),
() => {
const handleKeyDown = (event: KeyboardEvent): void => {
const keyPressed = keyMap[event.key];
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}
);
However, we might have the key but we do not have any way to access the current value held by the store.
For that, the function used in readable
takes two additional parameters:
- A
set
function that can be used to set the value of the store - An
update
function that uses a callback to modify the current value of the store
While set
would not help in appending a value, update
definitely will, let's grab it:
const lastKeyPressed = readable(
new Array<KeyCode>(),
(_set, update) => {
const handleKeyDown = (event: KeyboardEvent): void => {
const keyPressed = keyMap[event.key];
update((currentSequence: KeyCode[]) => {
const next = currentSequence.concat(keyPressed);
// 👇 Discards the oldest value if the sequence is too long
if (next.length > konamiCode.length) {
next.splice(0, 1);
}
return next;
});
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}
);
We now have a store that is storing the latest pressed keys and exposes them as sequence of the Konami Code.
Now onward to detecting it!
Detecting the Konami Code
We have several ways of signaling to a consumer when the Konami Code has been entered.
However, as I'm thinking of stores as a kind of RxJS's Observables, I tend to prefer deriving a value from it to keep my code as declarative as possible.
If the concept of "declarative code" is not something you are familiar with, Joshua Morony has some wonderful videos on that subject:
Hopefully, it seems to also be the case for the Svelte core dev team as it is possible to derive a store from another one by using derived
.
To derive a store, the syntax is pretty straightforward:
- As a first parameter, the
derived
function is expecting the store to derive its value from - As a second parameter, we should define a function that takes the value emitted by the store we derived from and infer the value of the current store
In our case, the inferred value will simply be a boolean evaluated to true
if the current sequence matches the Konami Code:
// 👇 Don't forget to export this store!
export const isKonamiCodePressed: Readable<boolean> = derived(
lastKeyPressed,
($pressedKeys: KeyCode[]) => JSON.stringify($pressedKeys) === JSON.stringify(konamiCode)
);
We could have computed the value of
JSON.stringify(konamiCode)
instead of recomputing it everytime but for the sake of this tutorial I will spare you some additional code
Reacting to Its Detection
With our Konami Code detection store, we can now use it in our Svelte app to add a small easter egg.
In a new Svelte component, all we have to do is to subscribe to the store and react to whenever it has been entered:
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import type { Unsubscriber } from 'svelte/store';
import { isKonamiCodePressed } from '$lib/stores/konami';
let unsubscribe: Unsubscriber = () => {};
onMount(() => {
unsubscribe = isKonamiCodePressed.subscribe(
(isKonamiCodePressed: boolean): void => {
if (!isKonamiCodePressed) return;
console.log('Konami Code detected!');
}
);
});
// 👇 Don't forget to remove the event listener
onDestroy(unsubscribe);
</script>
While it works, a console.log
might not be the most funny thing we can do with that.
Let's build something visual and unexpected, like confetti 🎊!
Grab JS Confetti:
npm i js-confetti
And replace the former code with the following one:
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import type { Unsubscriber } from 'svelte/store';
import JSConfetti from 'js-confetti';
import { isKonamiCodePressed } from '$lib/stores/konami';
let unsubscribe: Unsubscriber = () => {};
onMount(() => {
const jsConfetti = new JSConfetti();
unsubscribe = isKonamiCodePressed.subscribe(
(isKonamiCodePressed: boolean): void => {
if (!isKonamiCodePressed) return;
jsConfetti.addConfetti();
}
);
});
onDestroy(unsubscribe);
</script>
You can now use this component on whatever page you want the user to be able to enter the Konami Code !
Wanna see a live example ? Try it out on my landing page!
You can also see the implementation from which this article is inspired from the sources:
pBouillon / pbouillon.github.io
My personnal website
Landing Page
My landing page, built with Svelte 4, deployed with GitHub Actions and hosted on GitHub Pages.
I hope that you learn something useful there!
Photo by Annie Spratt on Unsplash
Top comments (0)