I was asked to implement the Konami Code in a website I'm currently working on. It should do the following:
Change Background Image
Play sound
Bring some pop-up
What's the easiest way to achieve this using javascript?
I was asked to implement the Konami Code in a website I'm currently working on. It should do the following:
Change Background Image
Play sound
Bring some pop-up
What's the easiest way to achieve this using javascript?
Place the code below in a file js/konami.js
and reference it in the body of your html file like this: <script src="js/konami.js"></script>
// a key map of allowed keys
var allowedKeys = {
37: 'left',
38: 'up',
39: 'right',
40: 'down',
65: 'a',
66: 'b'
};
// the 'official' Konami Code sequence
var konamiCode = ['up', 'up', 'down', 'down', 'left', 'right', 'left', 'right', 'b', 'a'];
// a variable to remember the 'position' the user has reached so far.
var konamiCodePosition = 0;
// add keydown event listener
document.addEventListener('keydown', function(e) {
// get the value of the key code from the key map
var key = allowedKeys[e.keyCode];
// get the value of the required key from the konami code
var requiredKey = konamiCode[konamiCodePosition];
// compare the key with the required key
if (key == requiredKey) {
// move to the next key in the konami code sequence
konamiCodePosition++;
// if the last key is reached, activate cheats
if (konamiCodePosition == konamiCode.length) {
activateCheats();
konamiCodePosition = 0;
}
} else {
konamiCodePosition = 0;
}
});
function activateCheats() {
document.body.style.backgroundImage = "url('images/cheatBackground.png')";
var audio = new Audio('audio/pling.mp3');
audio.play();
alert("cheats activated");
}
EDIT: changed the sequence to b, a instead of a, b. Thanks for the comment!
EDIT 2: reset the konamiCodePosition to 0 after activateCheats was called. Thanks for the comment!
compact version:
function onKonamiCode(cb) {
var input = '';
var key = '38384040373937396665';
document.addEventListener('keydown', function (e) {
input += ("" + e.keyCode);
if (input === key) {
return cb();
}
if (!key.indexOf(input)) return;
input = ("" + e.keyCode);
});
}
onKonamiCode(function () {alert('\o/')})
My own compact and cleaned version inspired by the answers here:
let cursor = 0;
const KONAMI_CODE = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
document.addEventListener('keydown', (e) => {
cursor = (e.keyCode == KONAMI_CODE[cursor]) ? cursor + 1 : 0;
if (cursor == KONAMI_CODE.length) activate();
});
In this case, the activate()
function is called when triggered.
Silentdrummer has a good answer. I'm not entirely sure, but I think it could end up taking up too much memory on typing intensive pages. It's good practice to reset. Either way, here's an alternative.
// Cheat Codes
neededkeys = [38,38,40,40,37,39,37,39,66,65], started = false, count = 0;
$(document).keydown(function(e) {
key = e.keyCode;
if (!started) {
if (key == 38) {
started = true;
}
}
if (started) {
if (neededkeys[count] == key) {
count++;
} else {
reset();
}
if (count == 10) {
reset();
// Do your stuff here
alert('Cheat Codes Activated');
$('body').css('background-color', '#FFA8A8');
// Turn down for what
var s=document.createElement('script');
s.setAttribute('src','https://nthitz.github.io/turndownforwhatjs/tdfw.js');
document.body.appendChild(s);
}
} else {
reset();
}
});
function reset() {
started = false;
count = 0;
}
as a typescript module
const Konami = (() => {
// up, up, down, down, left, right, left, right, b, a, enter
const SEQUENCE: Array<number> = [
38,
38,
40,
40,
37,
39,
37,
39,
66,
65,
13,
];
let head: number = 0;
let isActive: boolean = false;
let callback: Function | undefined;
const start = (cb: Function): void => {
if (isActive) {
return;
}
window.addEventListener("keydown", onKeyDown);
callback = cb;
isActive = true;
};
const stop = (): void => {
if (!isActive) {
return;
}
isActive = false;
window.removeEventListener("keydown", onKeyDown);
};
const onKeyDown = (event) => {
if (event.keyCode === SEQUENCE[head]) {
head++;
if (head === SEQUENCE.length) {
if (callback instanceof Function) {
callback();
}
head = 0;
}
} else {
head = 0;
}
};
return {
start,
stop,
};
})();
export default Konami;
implementation:
Konami.start(() => { alert("konami sequence entered!"); });
notes: SEQUENCE is an array of the expected inputs. by using the head
var, the order checking and number of correct inputs is maintained. it also provides a simple way to restart if input deviates from the sequence. it also eliminates the needs for a "count" var.
This is a solution I came up with around 3 or 4 years ago. In my case I chose keyUp to keep it separate from any actions that occur from keyDown events. Also there is no need to specify what keys are allowable since the for loop checks which key was released against all the keys on the keyboard.
var konamicode = [38,38,40,40,37,39,37,39,66,65];
var kc=0;
function checker() {
if (kc==10) {
// What you want to occur when code matches goes in here.
kc=0; // This resets the sequence.
alert("It Worked!");
}
}
function keyUp(e) {
var keynum;
if (window.event) { keynum = event.keyCode; }
else if (e.which) { keynum = e.which; }
for (i = 0; i < 222; i++) { // The 222 represents all the keys on the keyboard.
var kx=konamicode[kc]; // kx represents the current position in the code sequence.
if (keynum == i) {
// Checks to see if key matches sequence, and resets sequence if it doesn't.
if (i!=kx){kc=0;} else {kc++;}
}
}
checker();
}
Here's a simple version which accepts a callback function and optionally a list of key codes, defaulting to the konami code.
We demonstrate by showing the key codes as typed and reporting when a konami code is hit. We also run another one looking for the keys "K", "O", "N", "A", "M", and "I". (We inefficiently keep looking for our output element. This is just for demonstration, so I don't think it matters.)
const onKonami = (action, seq = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65]) => {
const target = seq .join (',')
let current = []
window.addEventListener ('keydown', (e) => {
current = current .concat (e.keyCode) .slice (-seq.length)
if (current .join (',') == target) {action (current)}
})
}
onKonami (
(seq) => {document .getElementById ('output') .innerHTML += `\nKonami code found: [${seq}]\n`}
)
onKonami (
(seq) => {document .getElementById ('output') .innerHTML += `\nThe word Konami code found: [${seq}]\n`},
[...'KONAMI'].map(c => c .charCodeAt (0))
)
<p>Click here and then type</p><p>Try the Konami code (up, up, down, down, left, right, left, right, b, a)</p><p><b>Codes:</b></p><pre id="output"></pre>
<script>document.addEventListener('keydown', e => document .getElementById('output') .innerHTML += e.keyCode + ' - ')</script>
We keep a current
array of the last n
keystrokes, where n
is the length of our target sequence. If that sequence matches our target, we fire the callback.
It would not be difficult to pass also an event target, so we only listen when a particular element has focus. I leave that as an exercise.
I really liked Peter's answer, so I made it namespaced and made the callback optional. I also used jquery because I like it ¯\_(ツ)_/¯
var Konami = Konami || {};
Konami.key = '38384040373937396665';
Konami.onCode = function(callback) {
var input = '';
$(document).on("keydown", function(e) {
input += ("" + e.keyCode);
if (input === Konami.key) {
if(typeof callback == 'undefined') {
return alert("⬆⬆⬇⬇⬅➡⬅➡");
}
else {
return callback();
}
}
if (!Konami.key.indexOf(input)) return;
input = ("" + e.keyCode);
});
}
Konami.offCode = function() {
$(document).off("keydown");
}
Konami.onCode();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
To create your own "Konami Code" add the following Code snippet in your HTML Code. PS: Change the const secretCode to ... what ever you want :). With the current code you have to type 'arrow up' button, then 'h' then 'i' and last but not least the 'arrow down' button.
Questions? Please ask.
<script>
const pressed = [];
const secretCode = 'ArrowUphiArrowDown';
window.addEventListener('keyup', (e) => {
console.log(e.key);
pressed.push(e.key);
pressed.splice(-secretCode.length - 1, pressed.length - secretCode.length);
if(pressed.join('').includes(secretCode)) {
console.log("Any source code that will be executed if you enter the correct code.");
}
console.log(pressed);
})
</script>
Piggybacking off Ehsan Kia,
I haven't seen anyone handling cases where the up key could be pressed 3+ times, and technically the code would have been input correctly.
Minified it a bit because the conditionals got long.
let c = 0;
const kCode = [38,38,40,40,37,39,37,39,66,65];
document.addEventListener('keydown', (e) => {
c = (e.keyCode == kCode[c] ? c + 1 : (e.keyCode-38 ? 0 : (c ? (kCode[c-1] == 38 ? c : 0) : 0)));
if(c == kCode.length) activate();
});
Use the following code.
const keySequence = [];
let konamiString = '';
const konamiCode = [
'ArrowUp',
'ArrowUp',
'ArrowDown',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowLeft',
'ArrowRight',
'b',
'a'
];
document.addEventListener('keydown', function(e) {
// To make sure it freezes the scroll when
// the first two keypresses are "ArrowUp"
if (keySequence[0] === 'ArrowUp' && keySequence[1] === 'ArrowUp' && e.key === 'ArrowDown') {
e.preventDefault();
}
});
document.addEventListener('keyup', function(e) {
const doc = document.documentElement;
const top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
// This make sure it only work
// when the window `scrollTop` is 0.
if (top === 0) {
keySequence.push(e.key);
keySequence.splice(-konamiCode.length - 1, keySequence.length - konamiCode.length);
konamiString = konamiCode.join('');
if (keySequence.join('').includes(konamiString)) {
// Trigger your easter egg here
}
}
});
The code also checks for the scrollTop
of the window
so that it won't scroll down when the first two keypresses are "ArrowUp" and the scrollTop
of the window
is 0
I'm already using this code on my blog without any hiccup.
As mentioned in comments, the currently accepted answer does not work if user presses "up" 3 times instead of twice.
If we think about this problem, we should realize that the flaw in the approach is assuming a single "input sequence". What I mean by this is that every time the user presses the first key of the cheat code, we should consider that this might be a new input sequence.
Reflecting on this flaw, a simple solution is to keep track of multiple input sequences, not just a single input sequence. As such, all of the answers here that use a single integer inputPosition
(or whatever it might be called) are going to be flawed*.
*To be clear, a solution could use a single integer inputPosition
if it did some "reverse walk" check when a sequence is broken to see if a part of it could be the beginning of a new sequence, but that would be difficult to write and unpleasant to read.
So,here is my version:
const keyCodesByLabel = {
left: 37,
up: 38,
right: 39,
down: 40,
a: 65,
b: 66
};
const konamiCode = [
'up',
'up',
'down',
'down',
'left',
'right',
'left',
'right',
'b',
'a'
];
const konamiKeyCodes = konamiCode.map(label => keyCodesByLabel[label]);
const activateCheats = () => {
alert("godmode enabled");
};
var inputPositions = [];
const incrementOrRemove = (inputPositions, keyCode) => {
return inputPositions.reduce((acc, inputPosition, i, arr) => {
if (keyCode == konamiKeyCodes[inputPosition]) {
inputPosition++;
if (inputPosition == konamiCode.length) {
inputPositions = [];
activateCheats();
arr.splice(1); // eject early by mutating iterated copy
return [];
} else {
acc.push(inputPosition);
return acc;
}
} else {
return acc;
}
}, []);
};
const handleKeyCode = keyCode => {
if (keyCode == konamiKeyCodes[0]) {
inputPositions.push(0);
}
if (inputPositions.length > 0) {
inputPositions = incrementOrRemove(inputPositions, keyCode);
}
};
document.addEventListener('keydown', ({ keyCode }) =>
handleKeyCode(keyCode)
);
inputPositions
starts as an empty array.
When a key is pressed:
0
is added to the array.inputPositions
array is not empty, the array is "reduced" to another array, whereby we either:During the reduce
, if an element equals the length of the cheat code, we know the cheat code has been successfully entered. Reset inputPositions
to empty array, exit the reduce
and call activateCheats
or whatever you want to happen.
Note that this solution is more powerful than just handling the "up up up" case.
For example, imagine the cheat code is "a a b a a c". If user enters "a a b a a b", they should (rightly) expect to be able to enter "a a c" and activate cheats. But any solution that resets the "position" when sequence breaks will not catch it. More importantly, any solution that checks if pressed key is the initial key will not catch it. The only holistic solution is to have an array of input positions.
this is my answer with using JQuery :
var konamiCode = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65, 13];
var emptyArray = [];
$(document).keyup (function (e){
emptyArray.push(e.keyCode);
if (JSON.stringify(konamiCode) === JSON.stringify(emptyArray)) {
alert('there you go')
}
});