Want to improve your JavaScript skills, while also showing your appreciation for the Big Bang Theory?
Well, good news! In this tutorial, I’ll walk you through how to code and create a more complex version of the classic rock-paper-scissors game i.e. rock, paper, scissors, lizard, spock!
I've got your back with the CSS, including the media queries for seamless responsive design on smartphones. This way, we can dive right into the game's functionality.
Our game will feature dynamic elements, such as:
Moreover, we'll explore JavaScript modules and import confetti functions from a separate file.
You can find the code to work on this project here:
If you stumbled upon this page simply due to your love of the Big Bang Theory and want to learn to code (or more likely you're having some trouble learning to code), then I highly recommend you check out Andrei’s Coding Bootcamp course.
I personally learned to code from Andrei many years ago using his Coding Bootcamp (Complete Web Developer). Thanks to that, I've been a Developer for 5+ years now and even become an instructor myself as well.
It all started from taking Andrei's Coding Bootcamp and I'm just one of 1,000s of students that have a similar story to mine.
So if you want to learn to code and actually make a career out of it, there is still no better place in my mind!
Andrei didn't even ask me to say any of this but I am anyways because I have been in your shoes as a complete beginner.
It makes sense that it's one of the most popular and highly-rated coding bootcamps online. He is constantly updating it (more than any other coding bootcamp I've seen), he goes way above and beyond, and actually makes learning fun.
And web development is a great career and not just because the money is good.
... but the money is good: The average salary for a Full-Stack Web Developer at the moment is around $117,800 in the US.
Not bad eh!?
Alternatively, if you’re already an aspiring Developer or Junior Developer looking to improve your skills by building some JavaScript projects (that you can also add to your portfolio), then I'd love to help you out.
The project we're going to walk through in this tutorial is just 1 of the 20 projects you'll get to build in my JavaScript portfolio projects course.
If you find yourself getting stuck somewhere in this tutorial, then it might be helpful for you to see me building the project step-by-step alongside you (plus you'll be able to ask me questions directly on Discord).
Oh, and FYI: If you join Zero To Mastery, you get access to all of the courses on ZTM, not just the two I've mentioned.
Alright, with that out of the way, let’s get building this JavaScript game project shall we?
First things first, there is a template for this project that will make it easier to follow along.
Open it in Visual Studio Code (VSC) and launch it with a live server.
Now that you have the project template, let's start building our HTML:
<div class="game-container">
<!-- Header -->
<div class="header">
<h1>Rock Paper Scissors Lizard Spock</h1>
</div>
<!-- Player Container -->
<div class="player-container" id="player">
<h2>You - <span id="playerScore">0</span><span class="choice" id="playerChoice"> --- Choice</span></h2>
Next, we need to grab the icons for the game images. I recommend you use FontAwesome icons.
They have all the necessary icons to create this game, as seen below:
We'll then use these icons as buttons to select as our choice in each match.
Start by adding your icons in the player container. (If you use other icons, be sure to reflect that here).
<i class="far fa-hand-rock" title="Rock" id="playerRock"></i>
<i class="far fa-hand-paper" title="Paper" id="playerPaper"></i>
<i class="far fa-hand-scissors" title="Scissors" id="playerScissors"></i>
<i class="far fa-hand-lizard" title="Lizard" id="playerLizard" ></i>
<i class="far fa-hand-spock" title="Spock" id="playerSpock"></i>
</div>
Next, we need to make a computer container for us to play against, so go ahead and duplicate the player container for the computer container, as the code is almost identical.
You just need to replace all instances of 'player' with 'computer', but you can do that with the multi-select tool:
<!-- Computer Container -->
<div class="player-container" id="computer">
<h2>Computer - <span id="computerScore">0</span><span class="choice" id="computerChoice"> --- Choice</span></h2>
<i class="far fa-hand-rock" title="Rock" id="computerRock"></i>
<i class="far fa-hand-paper" title="Paper" id="computerPaper"></i>
<i class="far fa-hand-scissors" title="Scissors" id="computerScissors"></i>
<i class="far fa-hand-lizard" title="Lizard" id="computerLizard"></i>
<i class="far fa-hand-spock" title="Spock" id="computerSpock"></i>
</div>
Lastly, add a reset icon and a container to display the results:
<!-- Reset Icon -->
<i class="fas fa-sync-alt reset-icon" title="Reset"></i>
<!-- Results Text -->
<div class="result-container">
<h3 class="result-text" id="resultText">You Won!</h3>
</div>
</div>
Save your file and check the progress. It should end up looking like this:
Not bad right? It needs some improvements, but we can refine these in our CSS.
I've included most of the CSS for this tutorial in the project template, but let’s go over a few key aspects of how we can improve, what we’re going to refine, and why.
Let's start with the body background. We want it to be slightly gray so that it stands out on white screen. Right now it kinds of blends:
So go ahead and add a color to help it pop, like so:
background: whitesmoke;
In theory, you could use any color you want.
Next, let's style the game container itself, and give it some shading, so it pops even more, like so:
Not bad right? We can edit it with the following code:
.game-container {
width: 530px;
height: 600px;
background: white;
box-shadow: 0 10px 20px -5px rgba(0, 0, 0, 0.5);
border-radius: 5px;
}
The background looks good, but the layout is a little bit of a mess.
With that in mind, let’s fix the header div and the icons' layout. Additionally, we'll check the mobile responsiveness of our container.
Let’s start by adjusting the height and border radius of the header, as it’s currently not rounded, like the bottom of the body.
border-top-left-radius: 5px;
border-top-right-radius: 5px;
height: 100px;
Save and check, and it looks much better.
Now, let's examine the smartphone vertical view.
It’s running right up to the edge of the screen. Ideally, we want the margins on the left and right to display the shadow.
To fix this, at the bottom of our CSS, I've added several media queries, to update the game container width:
.game-container {
width: 95%;
Save and check.
Fantastic. Now it looks great on both large smartphones and iPhones.
Ok, let’s do 3 things to improve the icons:
We can do this like so...
#player .far,
#player .choice {
color: dodgerblue;
cursor: pointer;
}
#computer .far,
#computer .choice {
color: rgb(235, 43, 52);
}
Save and check.
The splash of color looks fantastic!
Next, we'll add a selected class, for when one of our icons is clicked. Let's test it by adding the class manually to our paper element in the HTML:
.selected {
color: black;
}
Use !important
to override the color property in this case:
.selected {
color: black !important;
}
Save and check to see if it works!
Now that you know it works, go ahead and remove the !important
override. We’ll add the functionality in just a second, but we have some final tweaks to do first.
Currently, there’s a large margin on the right-hand side of the icons, so let’s remove the margin on the last icon in each row:
#player .far:last-of-type,
#computer .far:last-of-type {
margin-right: 0;
}
We're done with our styling so now it's time to move on to functionality!
We know that the highlighted icon should turn black, however, we haven’t added the functionality for that to work just yet, so let’s do it now.
First, create the constants for the HTML elements that we want to reference:
const playerScoreEl = document.getElementById('playerScore');
const playerChoiceEl = document.getElementById('playerChoice');
const computerScoreEl = document.getElementById('computerScore');
const computerChoiceEl = document.getElementById('computerChoice');
const resultText = document.getElementById('resultText');
const playerRock = document.getElementById('playerRock');
const playerPaper = document.getElementById('playerPaper');
const playerScissors = document.getElementById('playerScissors');
const playerLizard = document.getElementById('playerLizard');
const playerSpock = document.getElementById('playerSpock');
const computerRock = document.getElementById('computerRock');
const computerPaper = document.getElementById('computerPaper');
const computerScissors = document.getElementById('computerScissors');
const computerLizard = document.getElementById('computerLizard');
const computerSpock = document.getElementById('computerSpock');
const allGameIcons = document.querySelectorAll('.far');
Next, create the selection logic for the player icons by adding an onclick
attribute to the HTML:
onclick="select('rock')"
X5
Now, create the select function in JavaScript:
// Passing player selection value and styling icons
function select(playerChoice) {
console.log(playerChoice);
}
When we save and check the console, we can see the value is being passed through.
When we click on an icon, it’s being selected.
Now, let's use this to figure out our selection logic for our CSS, as we want the clicked icon to turn black and update the choice text as well.
Remove the console log and use a switch statement:
// Add 'selected' styling & playerChoice
switch (playerChoice) {
case 'rock':
playerRock.classList.add('selected');
playerChoiceEl.textContent = ' --- Rock';
break;
case 'paper':
playerPaper.classList.add('selected');
playerChoiceEl.textContent = ' --- Paper';
break;
case 'scissors':
playerScissors.classList.add('selected');
playerChoiceEl.textContent = ' --- Scissors';
break;
case 'lizard':
playerLizard.classList.add('selected');
playerChoiceEl.textContent = ' --- Lizard';
break;
case 'spock':
playerSpock.classList.add('selected');
playerChoiceEl.textContent = ' --- Spock';
break;
default:
break;
}
Save and check.
It works, but still needs a little work.
Why? Well, updating the text selection choice works fine, but once we select an icon, it stays selected permanently, which is not great.
We want it to change between each new selection, as well as keep updating the text (which currently works).
resetSelected
functionWe can solve this by adding another function to reset the icons after each selection. We'll use the allGameIcons
array for this.
Start by creating the resetSelected
function:
// Reset all 'selected' icons, remove confetti
function resetSelected() {
allGameIcons.forEach((icon) => {
icon.classList.remove('selected');
});
}
Then, call resetSelected
from the select function, before assigning the selected class:
function select(playerChoice) {
resetSelected();
Save and check to see if it works.
The player selection is fully functional now, so in the next step, we'll focus on the computer-related functionality.
In this section, we'll create logic for the computer to make a random selection and display the selection like how we're displaying the player selection.
First, create a new function named checkResult
which will call other functions:
function select(playerChoice) {
checkResult();
// Call functions to process turn
function checkResult() {
resetSelected();
computerRandomChoice();
Then, create a variable at the top for containing a string of the computerChoice
:
let computerChoice = '';
Now, use a Math method called random
to get a random number. We will divide this into 5 ranges and select based on that:
// Random computer choice
function computerRandomChoice() {
const computerChoiceNumber = Math.random();
console.log(computerChoiceNumber);
Save and check.
Now, use a series of if statements to change the computerChoice
value, like so:
if (computerChoiceNumber < 0.2) {
computerChoice = 'rock';
} else if (computerChoiceNumber <= 0.4) {
computerChoice = 'paper';
} else if (computerChoiceNumber <= 0.6) {
computerChoice = 'scissors';
} else if (computerChoiceNumber <= 0.8) {
computerChoice = 'lizard';
} else {
computerChoice = 'spock';
}
console.log(computerChoice);
Save and check.
Next, reuse the same functionality from the player select function and create a new function called displayComputerChoice
:
// Add 'selected' styling & computerChoice
function displayComputerChoice() {
switch (computerChoice) {
case 'rock':
computerRock.classList.add('selected');
computerChoiceEl.textContent = ' --- Rock';
break;
case 'paper':
computerPaper.classList.add('selected');
computerChoiceEl.textContent = ' --- Paper';
break;
case 'scissors':
computerScissors.classList.add('selected');
computerChoiceEl.textContent = ' --- Scissors';
break;
case 'lizard':
computerLizard.classList.add('selected');
computerChoiceEl.textContent = ' --- Lizard';
break;
case 'spock':
computerSpock.classList.add('selected');
computerChoiceEl.textContent = ' --- Spock';
break;
default:
Break;
However, this won’t work on its own.
We also need to call the displayComputerChoice
function in the checkResult
function:
// Call functions to process turn
function checkResult() {
resetSelected();
computerRandomChoice();
displayComputerChoice();
Save and check, and it should look like this:
The computer selection is now working! In the next step, we will implement the logic of the game.
So far, we’ve created a working project that follows the rules for a standard game of rock, paper, scissors. However, the rules are different in rock, paper, scissors, lizard, spock, so we need to adjust this in the code.
As a quick recap:
But now we also have:
Phew!
Fortunately, it’s not too complex to code these additional rules, and in this section, we'll create the logic of the game itself.
So let’s work through this.
We have an object with each possible choice, containing its own object that has an array of the two choices it defeats:
const choices = {
rock: { defeats: ['scissors', 'lizard'] },
paper: { defeats: ['rock', 'spock'] },
scissors: { defeats: ['paper', 'lizard'] },
lizard: { defeats: ['paper', 'spock'] },
spock: { defeats: ['scissors', 'rock'] },
};
Create global variables for the score:
let playerScoreNumber = 0;
let computerScoreNumber = 0;
Then update the select
and checkResult
functions to pass the playerChoice
as a parameter:
function select(playerChoice) {
checkResult(playerChoice);
function checkResult(playerChoice) {
...
updateScore(playerChoice);
Then go ahead and create the updateScore
function:
// Check result, increase scores, update resultText
function updateScore(playerChoice) {
console.log(playerChoice, computerChoice);
Add in the ability to check for a tie:
if (playerChoice === computerChoice) {
resultText.textContent = "It's a tie.";
}
And then check to see if it works…
Bazinga!
Now to the real logic. We’ll create an else
statement if we are not tied.
Within that statement, we will create a choice constant, which will call an item from the choices array, matching the playerChoice
to a key name:
} else {
const choice = choices[playerChoice];
console.log(choice);
First, we will modify our console log and see how it works.
console.log(choice.defeats.indexOf(computerChoice));
Save and check.
As you can see in the example, we chose rock. The array then clarifies that rock can beat both scissors and lizard. Because the computer chose scissors, we won!
Go ahead and play a few rounds to check that it all works, and keep an eye on the code as you do this.
Notice that when the player wins the value is 0 or 1, which means that the computer choice was found in the defeats array as the first or second item.
If it is not in the array, it returns -1.
So, now, we know that if the number is greater than -1, then the player has won.
Let’s add in the logic to display this.
if (choice.defeats.indexOf(computerChoice) > -1) {
resultText.textContent = 'You Won!';
playerScoreNumber++;
playerScoreEl.textContent = playerScoreNumber;
We already know that when the value is 0 or 1 , then the player wins, the score goes up and the message says “You won”.
However, when the computer wins nothing happens, so let’s fix that, and make it say “You lost” when the player loses.
We just need to add an else statement and mirror what we were doing for updating our text and the score.
} else {
resultText.textContent = 'You Lost!';
computerScoreNumber++;
computerScoreEl.textContent = computerScoreNumber;
}
Simple!
The game logic is now complete! In the next step, we will add the ability to reset everything and start over.
In this section, we'll create a function to reset everything and start over. This will include resetting the score, player and computer choices, and selected icons.
First, let's clean up the HTML by removing any unnecessary text that was initially hardcoded, since our JavaScript is now populating the content.
resetAll
functionThis function will:
resetSelected
functionIt looks like this:
// Reset score & playerChoice/computerChoice
function resetAll() {
playerScoreNumber = 0;
computerScoreNumber = 0;
playerScoreEl.textContent = playerScoreNumber;
computerScoreEl.textContent = computerScoreNumber;
playerChoiceEl.textContent = '';
computerChoiceEl.textContent = '';
resultText.textContent = '';
resetSelected();
}
In the HTML file, add the onclick
attribute for the resetAll
function to the "Reset" button:
<!-- Reset Icon -->
<i class="fas fa-sync-alt reset-icon" title="Reset" onclick="resetAll()"> </i>
Since we want the initial score values to be displayed when the page loads, call the resetAll
function on startup:
// On startup, set initial values
resetAll();
Save your changes and check the result in your browser.
The reset functionality should now be working as expected. When you click the "Reset" button, the scores, choices, and selected icons will reset to their initial state.
By following these steps, you've added a reset function to the game. In the next step, we'll work on making winning a round a little more exciting.
In this section, we'll add confetti functionality to the game.
Basically, when the player wins a round, the confetti show. Then, it will stop immediately if the next round is a tie or if the player loses.
We'll use an external script for the confetti effect, so go to the following link and download the confetti.js file.
Unzip the downloaded file, then copy and paste the confetti.js file into your project folder. Then, go ahead and open the file in your code editor (e.g., Visual Studio Code).
// Confetti.js - downloaded from https://www.cssscript.com/confetti-falling-animation/
Copy the entire content of confetti.js and paste it at the bottom of your main JavaScript file.
We will need to modify it slightly, as we want to be able to call the functions, so we will remove the function wrapping everything, IIFE
(immediately-invoked function expression)
To test the confetti effect, call the startConfetti
function when the page loads:
startConfetti();
Save your changes and check the result in your browser.
The confetti effect should look something like this:
Adjust the confetti settings according to your preference.
For example
You can change the maxCount
to 100 and the animationSpeed
to 10.
Now, we need to call the startConfetti
, stopConfetti
, and removeConfetti
functions at the appropriate places in our code.
First, remove the startConfetti
function call at the bottom of the script.
Then, call the startConfetti
function when the player wins a round, by adding it inside the if
statement that checks if the player has won:
if (choice.defeats.indexOf(computerChoice) > -1) {
startConfetti();
Call the stopConfetti
and removeConfetti
functions inside the resetSelected
function to stop the confetti effect every time the user selects an icon:
function resetSelected() {
...
stopConfetti();
removeConfetti();
Save your changes and test the result in your browser. The confetti effect should start when the player wins and stop instantly when the next round begins.
You've now added confetti functionality to the game. In the next step, you can refactor the code and learn how to implement modules in JavaScript for better organization and separation of concerns.
Recommended reading references:
In this step, we'll refactor our project to use ES Modules for better organization and separation of concerns. This will involve making changes to the script.js, confetti.js, and index.html files.
First, cut the confetti-related code from your script.js file and paste it into the confetti.js file.
Then, at the bottom of the confetti.js file, add the following line to export the confetti functions:
export { startConfetti, stopConfetti, removeConfetti };
In the script.js file, import the confetti functions at the top:
import { startConfetti, stopConfetti, removeConfetti } from './confetti.js';
In the index.html file, add the type attribute of module to the script.js:
<script src="script.js" type="module"></script>
Alright so let’s see if this works.
Test the confetti functionality by calling the startConfetti
function at the bottom of the script.js file:
startConfetti();
Save your changes and check the result in your browser. The confetti effect should still start as expected.
Once you can see that it works, let’s tweak this.
Go ahead and remove the startConfetti
function call from the bottom of the script.js file. Then save your changes and try selecting an icon in your browser.
The confetti effect may not work as expected due to the module's scope. To fix this, you can pass the functions from the script.js file to the global window object.
Below your two functions (resetAll
and select
), add the following lines:
window.resetAll = resetAll;
window.select = select;
Save your changes and test the result in your browser, and everything should work as expected.
Congratulations! You have successfully refactored your project to use ES Modules. This enhances the organization and separation of concerns in your code.
So there you have it. Hopefully, you’ve been following along and building as you read through - if not, then get it built now and improve that portfolio to prep for that dev interview!
Although this is a fairly fun project, we’ve actually covered a lot of different topics here.
No joke - By completing this tutorial, you've gained valuable skills in web development and deepened your understanding of how HTML, CSS, and JavaScript work together to create dynamic and interactive web applications. You can now take these skills and apply them to your own projects or continue learning and exploring more advanced topics in web development.
If you want more projects like this, be sure to check out my 20 Advanced JavaScript Projects course.
Or, if you struggled with building this game, and want a deep dive back into JavaScript, go ahead and check out ZTM’s Complete Web Developer course.