Creating games in the browser is a great way to practice your JavaScript skills and have fun at the same time!
In this tutorial, How To Build A Simon Game With Javascript, we’ll develop the traditional Simon Game with JavaScript. The object of the game is to repeat a sequence of random tile clicks created by the game. After every round, the sequence turns progressively longer and extra complex which makes it tougher to recollect.

Typically, you’ll have 4 totally different tiles, every with a novel color and sound which is activated when pressed. The sound aids the participant in remembering the sequence and the game ends if the player misses a step within the sequence.
Prerequisites How To Build A Simon Game With Javascript
This tutorial assumes basic data of JavaScript and the DOM.
Getting began How To Build A Simon Game With Javascript
Grab the HTML and CSS records data for the sport on GitHub. The directions for setting up the project are within the included README file. You can even follow the tutorial utilizing JSFiddle if you prefer.

As mentioned earlier, around begins when the game activates a number of tiles in random order and ends when the participant reproduces the order by pressing the tiles. On the subsequent round, the variety of tiles in the sequence will increase by one.
Let’s begin by creating an array to keep track of the unique sequence of tile clicks and a second array for the human sequence:
let sequence = [];
let humanSequence = [];
Next, select the start button and create a model-new startGame() operate that might be executed when this button is clicked.
const startButton = document.querySelector('.js-start');
function startGame() {
startButton.classList.add('hidden');
}
startButton.addEventListener('click on', startGame);
At this level, as soon as the start button is pressed, will in all probability be hidden. The .info component also must come into view as a result of that’s the place standing messages will be displayed. It has the hidden class by applied default within the HTML which has been styled with display: none; within the CSS so that’s what needs to be eliminated as quickly as the game starts.
<span class="data js-info hidden"></span>
Update your JavaScript file as shown beneath:
const startButton = document.querySelector('.js-start');
const info = document.querySelector('.js-info');
function startGame() {
startButton.classList.add('hidden');
info.classList.remove('hidden');
info.textContent = 'Wait for the computer';
}
Now, as soon as the beginning button is clicked, the .info element shows a message telling the consumer to attend for the sequence to finish.
Start the subsequent round How To Build A Simon Game With Javascript
Create a new level variable beneath humanSequence. It’s how we’ll hold observation of the variety of rounds that have been performed up to now.
let level = 0;
Next, create a new nextRound() operate simply above startGame() as shown within the snippet under. The function of this function is to start the following sequence of tile clicks.
function nextRound() {
level += 1;
// copy all the elements in the `sequence` array to `nextSequence`
const nextSequence = [...sequence];
}
Each time nextRound() invoked, the level variable is incremented by 1 and the next sequence is prepared. Each new spherical builds upon the earlier one so what we want to do is copy the prevailing order of button presses and add a model new random one to it. We’ve accomplished the previous on the last line of nextRound(), so let’s now add a brand new random button press to the sequence.
Create a model new nextStep() perform just above nextRound():
function nextStep() {
const tiles = ['red', 'green', 'blue', 'yellow'];
const random = tiles[Math.floor(Math.random() * tiles.length)];
return random;
}
The tiles variable accommodates the colours for each button on the game board. Notice how the values correspond with the values of the data-tile property in the HTML.
<div class="tile tile-red" data-tile="red"></div>
<div class="tile tile-green" data-tile="green"></div>
<div class="tile tile-blue" data-tile="blue"></div>
<div class="tile tile-yellow" data-tile="yellow"></div>
We must get a random value from the array every time nextStep() is executed, and we’re capable of achieving that by utilizing the Math.random() perform together with Math.floor(). The former returns a floating-point, pseudo-random quantity within the range 0 to lower than 1.

That’s not very helpful to us at the moment. It must be transformed to a legitimate index for the tiles array (0, 1, 2, or 3 in this case) so that a random value is from the array could be retrieved every time. Multiplying the value from Math.random() with the length of tiles (which is 4) ensures that the vary of the random quantity is now between zero and less than four (instead of 0 and fewer than 1).

Still, those fractional values usually are not legitimate array indexes, so Math.floor() is used to around the numbers all the means down to the biggest integer lower than or equal to the given value. This offers us entire integers between zero and three which can be utilized to retrieve a random value from the tiles array.

Let’s utilise the return worth of the nextStep() operate in nextRound() as proven under:
function nextRound() {
level += 1;
const nextSequence = [...sequence];
nextSequence.push(nextStep());
}
What happens right here is that when nextStep() is executed, it returns a random worth from the tiles array (“red”, “blue”, “green”, or “yellow”), and the worth is added to the end of the nextSequence() array alongside any values from the previous spherical.
Take a breather, and see the complete code at the end of this step.CHECKPOINT
Play the following round How To Build A Simon Game With Javascript
The next step is to actually play the subsequent spherical by activating the tiles on the display in the right order. Add the following features above nextStep() in your JavaScript file:
function activateTile(color) {
const tile = document.querySelector(`[data-tile='${color}']`);
const sound = document.querySelector(`[data-sound='${color}']`);
tile.classList.add('activated');
sound.play();
setTimeout(() => {
tile.classList.remove('activated');
}, 300);
}
function playRound(nextSequence) {
nextSequence.forEach((color, index) => {
setTimeout(() => {
activateTile(color);
}, (index + 1) * 600);
});
}
The playRound() function takes a sequence array and iterates over it. It then uses the setTimeout() function to call the activateTile() at 600 millisecond intervals for every value within the sequence. The reason setTimeout() is used here is to add a man-made delay between each button press. Without it, the tiles within the sequence shall be activated all at once.
The specified variety of milliseconds in the setTimeout() function modifications on each iteration. The first button within the sequence is activated after 600ms, the following one after 1200ms (600ms after the first), the third one after 1800ms, and so on.
In the activateTile() perform, the worth of color is used to pick out the appropriate tile and audio components. In the HTML file, notice how the data-sound attribute on the audio elements correspond to the button colors.
<audio src="https://s3.amazonaws.com/freecodecamp/simonSound1.mp3" data-sound="red" ></audio>
<audio src="https://s3.amazonaws.com/freecodecamp/simonSound2.mp3" data-sound="green" ></audio>
<audio src="https://s3.amazonaws.com/freecodecamp/simonSound3.mp3" data-sound="blue" ></audio>
<audio src="https://s3.amazonaws.com/freecodecamp/simonSound4.mp3" data-sound="yellow" ></audio>
The activated class is added to the selected tile, and the play() methodology is triggered on the selected audio factor causing the linked mp3 file in the src attribute to be performed. After 300 milliseconds, the activated class is eliminated once more. The impact is that every tile is activated for 300ms, and there are 300ms between tile activations within the sequence.
Finally, call playRound() within the nextRound() operate and call nextRound() within the startGame() operate as proven below:
function nextRound() {
level += 1;
const nextSequence = [...sequence];
nextSequence.push(nextStep());
playRound(nextSequence);
}
function startGame() {
startButton.classList.add('hidden');
info.classList.remove('hidden');
info.textContent = 'Wait for the computer';
nextRound();
}
Now, once you hit the begin button, the first round will start and a random button might be activated on the board.

Take a breather, and see the complete code at the end of this step.checkpoint
The player’s flip
Once the computer begins a round by activating the following sequence of tiles, the participant needs to finish the spherical by repeating the sample of tile activations in the proper order. If a step is missed along the finest way, the game ends and resets.
Select the heading and tile container parts under the info variable:
const heading = document.querySelector('.js-heading');
const tileContainer = document.querySelector('.js-container');
Next, create a humanTurn perform that signifies that the pc is completed with the round, and that it’s time for the participant to repeat the sequence:
function humanTurn(level) {
tileContainer.classList.remove('unclickable');
info.textContent = `Your turn: ${level} Tap${level > 1 ? 's' : ''}`;
}
The first step is to take away the unclickable class from the tile container. This class prevents the buttons from being pressed when the game has not began and when the AI just isn’t completed with the sequence of presses.
.unclickable {
pointer-events: none;
}
On the next line, the contents of the info factor is changed to indicate that the participant can start to repeat the sequence. It additionally reveals what quantity of faucets needs to be entered.
The humanTurn() function must be executed after the computer’s sequence is over so we can not call it instantly. We need to add a man-made delay and calculate when the pc might be accomplished with the sequence of button taps.
function nextRound() {
level += 1;
const nextSequence = [...sequence];
nextSequence.push(nextStep());
playRound(nextSequence);
sequence = [...nextSequence];
setTimeout(() => {
humanTurn(level);
}, level * 600 + 1000);
}
The setTimeout() operate above executes humanTurn() one second after the last button in the sequence is activated. The total duration of the sequence corresponds to the present level multiplied by 600ms which is the duration for every tile in the sequence. The sequence variable is also assigned to the updated sequence.
In the subsequent replace to nextRound(), the unclickable class is added to the tile container when the spherical starts, and the contents of the info and heading components are up to date.
function nextRound() {
level += 1;
tileContainer.classList.add('unclickable');
info.textContent = 'Wait for the computer';
heading.textContent = `Level ${level} of 20`;
const nextSequence = [...sequence];
nextSequence.push(nextStep());
playRound(nextSequence);
sequence = [...nextSequence];
setTimeout(() => {
humanTurn(level);
}, level * 600 + 1000);
}

The subsequent step is to detect the player’s button taps and resolve whether to maneuver to the following spherical or finish the game. Add the next event listener slightly below the one for startButton:
tileContainer.addEventListener('click', event => {
const { tile } = event.target.dataset;
if (tile) handleClick(tile);
});
In the occasion listener above, the worth of data-tile on the factor that was clicked is accessed and saved within the tile variable. If the value is not an empty string (for parts with out the data-tile attribute), the handleClick() perform is executed with the tile value as its solely argument.
Create the handleClick() operate simply above startGame() as proven under:
function handleClick(tile) {
const index = humanSequence.push(tile) - 1;
const sound = document.querySelector(`[data-sound='${tile}']`);
sound.play();
const remainingTaps = sequence.length - humanSequence.length;
if (humanSequence.length === sequence.length) {
humanSequence = [];
info.textContent = 'Success! Keep going!';
setTimeout(() => {
nextRound();
}, 1000);
return;
}
info.textContent = `Your turn: ${remainingTaps} Tap${
remainingTaps > 1 ? 's' : ''
}`;
}
This function pushes the tile worth to the humanSequence array and shops its index in the index variable. The corresponding sound for the button is played and the remaining steps within the sequence are calculated and up to date on the display.
The if block compares the size of the humanSequence array to sequence array. If they’re equal, it means that the round is over and the following round can begin. At that point, the humanSequence array is reset and the nextRound() operate is called after one second. The delay is to permit the person to see the success message, in any other case, it is not going to appear in any respect as a result of it’s going to get overwritten instantly.

Take a breather, and see the complete code at the end of this step.checkpoint
Compare the sequences
We want to check the order by which the player taps the buttons to the order of the sequence generated by the sport. If the order does not match, the sport resets and a message is present alerting the player to the failure.
Create a new resetGame() operate for this purpose above humanTurn():
function resetGame(text) {
alert(text);
sequence = [];
humanSequence = [];
level = 0;
startButton.classList.remove('hidden');
heading.textContent = 'Simon Game';
info.classList.add('hidden');
tileContainer.classList.add('unclickable');
}
This performance shows an alert and restores the sport to its authentic state. Let’s use it in handleClick as shown below:
function handleClick(tile) {
const index = humanSequence.push(tile) - 1;
const sound = document.querySelector(`[data-sound='${tile}']`);
sound.play();
const remainingTaps = sequence.length - humanSequence.length;
if (humanSequence[index] !== sequence[index]) {
resetGame('Oops! Game over, you pressed the wrong tile');
return;
}
if (humanSequence.length === sequence.length) {
humanSequence = [];
info.textContent = 'Success! Keep going!';
setTimeout(() => {
nextRound();
}, 1000);
return;
}
info.textContent = `Your turn: ${remainingTaps} Tap${
remainingTaps > 1 ? 's' : ''
}`;
}
If the worth of the factor retrieved by the index in each the sequence and humanSequence arrays do not match, it means the player made a wrong flip. At that time, an alert is displayed and the sport resets.
Take a breather, and see the complete code at the end of this step.checkpoint
Prepare a finish state
The recreation largely works but we have to introduce an end state where the player wins the sport. I’ve picked 20 rounds, but you can use any number you want. The traditional Simon Game ended after 35 rounds.
Here’s the bit that ends the game if the user reaches and completes the 20th round:
function handleClick(tile) {
const index = humanSequence.push(tile) - 1;
const sound = document.querySelector(`[data-sound='${tile}']`);
sound.play();
const remainingTaps = sequence.length - humanSequence.length;
if (humanSequence[index] !== sequence[index]) {
resetGame('Oops! Game over, you pressed the wrong tile');
return;
}
if (humanSequence.length === sequence.length) {
if (humanSequence.length === 20) {
resetGame('Congrats! You completed all the levels');
return
}
humanSequence = [];
info.textContent = 'Success! Keep going!';
setTimeout(() => {
nextRound();
}, 1000);
return;
}
info.textContent = `Your turn: ${remainingTaps} Tap${
remainingTaps > 1 ? 's' : ''
}`;
}
Once the 20th spherical is accomplished, a congratulatory message is displayed and the game resets. Make sure to try it out and see if you can attain stage 20 without failing.
Conclusion
In this tutorial, we developed a functioning Simon recreation with JavaScript. I hope you had lots of enjoyable building it. You can take it additional by experimenting with different designs or by including additional options. There is also a number of clones you’ll be able to be taught from and use as an inspiration, How To Build A Simon Game With Javascript.
Thanks for studying, and happy coding!