Learn how to create the rock, paper, scissor, lizard, spock game in javascript.
It is the modified version of classic rock, paper, scissor game with two extra options which highly reduces the probability of choosing same options by both the players.
By building this game you will learn
- HTML:- How to do layouting so that you can select and update the elements.
- CSS:- Style elements properly and reuse the code.
- Javascript: Create elements dynamically, add and remove dynamic elements, create a bot to compete with.
As this is a little complex project, I will sum up the article by breaking down into in three different parts.
- First, we will see how to structure the elements.
- Second, style the elements.
- Third, handle the functionality with Javascript.
Creating HTML layout of rock, paper, scissor, lizard, Spock game.
If you see the above image carefully, there are basically three sections in which we can divide the layout.
One, where the score and message regarding who won the round will be shown.
<!-- Result --> <section id="result"> <div id="score"> <span id="player1-score" class="points">0</span> : <span id="player2-score" class="points">0</span> </div> <p id="round-message">Choose your option</p> </section>
Two, where the selected option and all available options will be visible for both the players.
<!-- Player 1 area --> <section id="player1" class="players"> <span class="name">Player1</span> <div class="selected-option"> <span class="option" data-index="0"> </span> </div> <div class="available-options"> <span class="option" data-index="0"> <img src="assets/lizard.png" alt="Lizard" title="Lizard" /> </span> <span class="option" data-index="1"> <img src="assets/paper.png" alt="Paper" title="Paper" /> </span> <span class="option" data-index="2"> <img src="assets/rock.png" alt="Rock" title="Rock" /> </span> <span class="option" data-index="3"> <img src="assets/scissor.png" alt="Scissor" title="Scissor" /> </span> <span class="option" data-index="4"> <img src="assets/spock.png" alt="Spock" title="Spock" /> </span> </div> </section> <!-- Player 2 area --> <section id="player2" class="players"> <span class="name">Bot</span> <div class="selected-option"> <span class="option" data-index="0"> </span> </div> <div class="available-options"> <span class="option" data-index="0"> <img src="assets/lizard.png" alt="Lizard" title="Lizard" /> </span> <span class="option" data-index="1"> <img src="assets/paper.png" alt="Paper" title="Paper" /> </span> <span class="option" data-index="2"> <img src="assets/rock.png" alt="Rock" title="Rock" /> </span> <span class="option" data-index="3"> <img src="assets/scissor.png" alt="Scissor" title="Scissor" /> </span> <span class="option" data-index="4"> <img src="assets/spock.png" alt="Spock" title="Spock" /> </span> </div> </section>
Here if you see I have given custom attribute data-index
to all the options because this way it is easy to access and update elements.
The .selected-option
is empty at the beginning, whenever any option is selected it will be added here.
At the last there is a reset button, which will restart the round.
<!-- Reset button --> <span class="reset"><span>Reset</span></span>
Complete HTML code
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Rock, Paper, Scissor, Lizard, Spock Game</title> <link rel="stylesheet" href="./index.css" /> </head> <body> <main> <!-- Result --> <section id="result"> <div id="score"> <span id="player1-score" class="points">0</span> : <span id="player2-score" class="points">0</span> </div> <p id="round-message">Choose your option</p> </section> <!-- Player 1 area --> <section id="player1" class="players"> <span class="name">Player1</span> <div class="selected-option"> <span class="option" data-index="0"> </span> </div> <div class="available-options"> <span class="option" data-index="0"> <img src="assets/lizard.png" alt="Lizard" title="Lizard" /> </span> <span class="option" data-index="1"> <img src="assets/paper.png" alt="Paper" title="Paper" /> </span> <span class="option" data-index="2"> <img src="assets/rock.png" alt="Rock" title="Rock" /> </span> <span class="option" data-index="3"> <img src="assets/scissor.png" alt="Scissor" title="Scissor" /> </span> <span class="option" data-index="4"> <img src="assets/spock.png" alt="Spock" title="Spock" /> </span> </div> </section> <!-- Player 2 area --> <section id="player2" class="players"> <span class="name">Bot</span> <div class="selected-option"> <span class="option" data-index="0"> </span> </div> <div class="available-options"> <span class="option" data-index="0"> <img src="assets/lizard.png" alt="Lizard" title="Lizard" /> </span> <span class="option" data-index="1"> <img src="assets/paper.png" alt="Paper" title="Paper" /> </span> <span class="option" data-index="2"> <img src="assets/rock.png" alt="Rock" title="Rock" /> </span> <span class="option" data-index="3"> <img src="assets/scissor.png" alt="Scissor" title="Scissor" /> </span> <span class="option" data-index="4"> <img src="assets/spock.png" alt="Spock" title="Spock" /> </span> </div> </section> <!-- Reset button --> <span class="reset"><span>Reset</span></span> </main> <script src="./index.js"></script> </body> </html>
Styling the game with CSS.
We have written the style code for each section separately and have used flex-box to aligns the elements.
* { box-sizing: border-box; }
Score area.
main { display: flex; justify-content: center; align-items: center; width: 80%; margin: 0 auto; flex-wrap: wrap; } /* Score style */ #result { flex: 1 100%; } #round-message, #score { text-align: center; } #round-message { font-size: 1.5em; text-transform: capitalize; } #score { font-size: 3em; margin-bottom: 10px; }
Player area
/* Player style */ .players { display: inline-flex; flex-wrap: wrap; flex: 1; padding: 10px 25px; } .selected-option, .name, .available-options { display: inline-flex; flex: 1 100%; padding: 15px 0; } .selected-option, .name { justify-content: center; } .name { font-size: 1.8em; color: #f44336; font-weight: 600; } /* Options Style */ .available-options { justify-content: space-between; } .option { display: inline-flex; justify-content: center; align-items: center; width: 75px; height: 75px; padding: 10px; border-radius: 50%; border: 1px solid #f44336; box-shadow: 0 0 3px; cursor: pointer; } .option.active { border-color: blue; } .option:hover > img { transform: scale(1.1); } .option > img { width: 70%; transition: all 0.2s ease; } .selected-option > .option { width: 110px; height: 110px; } /* Rotate the scissor of second player */ #player2 .option > img[alt="Scissor"] { transform: rotate(180deg); } #player2 .option:hover > img[alt="Scissor"] { transform: rotate(180deg) scale(1.1); } #player2 .option { cursor: not-allowed; background-color: #80cbc4; }
I have rotated the scissor of the second player to give a feel that that it is pointing to the first player.
Reset button
/* Reset button */ .reset { display: inline-flex; flex: 1 100%; justify-content: center; margin-top: 20px; } .reset > span { font-size: 1.8em; cursor: pointer; border: 1px solid red; padding: 10px 15px; background-color: #f44336; box-shadow: 0 0 3px; color: #fff; transition: all 0.2s ease; } .reset > span:hover { color: #000; }
Adding life to rock, paper, scissor, lizard, spock with javascript.
I like to define the constants at the top so that I have idea of how I should define my functions, these are the three constants I have figured out which I will be needing.
- Folder path for where images will be stored.
- Options image path and alt text.
- Rule for which option will win over other.
//Order in which options are available const arr = [ { image: "lizard.png", name: "Lizard", }, { image: "paper.png", name: "Paper", }, { image: "rock.png", name: "Rock", }, { image: "scissor.png", name: "Scissor", }, { image: "spock.png", name: "Spock", }, ]; //Rule for who has win over whom const rule = { Lizard: ["Spock", "Paper"], Paper: ["Rock", "Spock"], Rock: ["Lizard", "Scissor"], Scissor: ["Paper", "Lizard"], Spock: ["Scissor", "Rock"], }; //Folder in which images are stored const imageFolderPath = "assets";
Also I like to pull all the selectors and store them in a variable so that I can reuse them wherever possible, But you can do it however you feel like.
//All the options of player 1 const player1Options = document.querySelectorAll( "#player1 .available-options .option" ); //All the options of bot const botOptions = document.querySelectorAll( "#player2 .available-options .option" ); //Where selected option of player 1 will be shown const playerShowArea = document.querySelector( "#player1 .selected-option .option" ); //Where selected option of bot will be shown const botShowArea = document.querySelector("#player2 .selected-option .option"); //Player 1 and bot score const player1Score = document.querySelector("#player1-score"); const player2Score = document.querySelector("#player2-score"); //Where message will be shown const roundMessage = document.querySelector("#round-message");
Now when player selects an option we have to perform a series of process.
- Show the current selected option for player 1 and highlight the selected option.
- Pick a random option for bot and then highlight the picked option.
- Calculate the result to show who won the round and increase the points.
We will do this when user selects an option and for that we will have to listen to the click event.
player1Options.forEach((e) => { e.addEventListener("click", () => { play(e); }); });
This will invoke the play function whenever the option is selected. Inside the play function we will start all other process.
const play = (e) => { //Get the index of the option selected by player const player1 = e.getAttribute("data-index"); //Number of options available const length = arr.length; //Generate a random number between number of options available for bot const player2 = Math.floor(Math.random() * length); //Show the player1 selected option and highlight it showPlayerOption(player1, playerShowArea); highlightSelectedOption(player1, player1Options); //Show the bot selected option showPlayerOption(player2, botShowArea); highlightSelectedOption(player2, botOptions); //Calculate the result calculateScore(player1, player2); };
If you see I have created two different functions for showing the selected option and highlighting the selected option because I like to keep things separate for different use cases. But you can put them in a single function.
Arguments which these functions take are pretty straight forward.
To show the selected option it takes the index of the selected option and the element where it needs to be shown. Same to highlight the option, It take index and list of options element.
Now lets define these functions.
//Generate an image element const generateImgElement = (index) => { const { image, name } = arr[index]; const imgElement = document.createElement("img"); imgElement.src = `${imageFolderPath}/${image}`; imgElement.alt = name; imgElement.title = name; return imgElement; }; //Show selected option const showPlayerOption = (index, showArea) => { //Append the generated image to the show area const imgElement = generateImgElement(index); showArea.innerHTML = ""; showArea.append(imgElement); }; const highlightSelectedOption = (index, options) => { //Remove the active class from all options options.forEach((e) => { e.classList.remove("active"); }); //Add the active class to the selected option options[index].classList.add("active"); };
Every time an option is selected an image will be added to the show area.
Last but not least, let see the function to calculate the result. When the result is calculated two actions take place.
- Score is updated
- Message is displayed to show who won the round
//Change the score const addScore = (player) => { const { innerHTML } = player; player.innerHTML = Number(innerHTML) + 1; }; //Show the message const showMessage = (msg) => { roundMessage.innerHTML = ""; roundMessage.innerHTML = msg; }; const calculateScore = (player1, player2) => { //Player 1 choice const player1Choice = arr[player1].name; //Bot choice const player2Choice = arr[player2].name; //Get player 1 selected choice rule const player1Strength = rule[player1Choice]; //Check the case and who wins the round if (player1Choice === player2Choice) { showMessage("draw"); } else if (player1Strength.includes(player2Choice)) { //Update the score and show message who won the round addScore(player1Score); showMessage("player 1 wins"); } else { //Update the score and show message who won the round addScore(player2Score); showMessage("Bot wins"); } };
At the end, create a function to reset everything when user clicks on the reset button.
const reset = () => { botShowArea.innerHTML = ""; playerShowArea.innerHTML = ""; roundMessage.innerHTML = "Choose your option"; player2Score.innerHTML = "0"; player1Score.innerHTML = "0"; player1Options.forEach((e) => { e.classList.remove("active"); }); botOptions.forEach((e) => { e.classList.remove("active"); }); }; document.querySelector(".reset").addEventListener("click", reset);