cover-img

How to create a Rock Paper Scissors game using JavaScript and an AI opponent

7 March, 2023

3

3

1

Contributors

Rock Paper Scissors is a classic hand game where players make shapes with their hands representing rock, paper, or scissors. In this tutorial, we'll create a version of Rock Paper Scissors that you can play against an AI opponent. We'll use JavaScript and the DOM to create the game interface and game logic.

Prerequisites

Getting started

Let's start by creating an HTML file named index.html and a Javascript file named app.js. In your HTML file, add the following code:

<!DOCTYPE html>
<html>
<head>
<title>Rock Paper Scissors</title>
<link rel="stylesheet" href="styles.css">
<script src="brain.js"></script>
</head>
<body>
<script src="app.js"></script>
</body>
</html>

This creates a basic HTML structure with a link to a CSS file called styles.css and a script tag to include the brain.js file and another script tag for our app.js file.

Usually I would place the JS and CSS files in their own directories nested in an assets directory, but for the sake of simplicity, all the files in this tutorial are in the same directory.

That's all we need to do with index.html, you can save and close the file.

Creating the UI

Next we're going to create the user interface. Open app.js and add the following code:

let pattern = [];
let scorePlayer = 0;
let scoreAI = 0;
let playerChoice = 0;
let aiChoice = 0;
let gameCount = 0;
let winningMessage = '';

gameArea.id = 'game-area';
const headerOne = document.createElement('h1');
headerOne.innerText = 'Javascript AI Rock Paper Scissors';
gameArea.append(headerOne);

const scoreBoard = document.createElement('div');
scoreBoard.id = 'score-board';
const scoreBoardPlayerP = document.createElement('p');
scoreBoardPlayerP.innerText = 'Player';
scoreBoard.append(scoreBoardPlayerP);

const playerScore = document.createElement('span');
playerScore.id = 'player-score';
playerScore.className = 'score';
playerScore.innerText = 0;
scoreBoardPlayerP.append(playerScore);

const scoreBoardAiP = document.createElement('p');
scoreBoardAiP.innerText = 'Computer';
scoreBoard.append(scoreBoardAiP);

const aiScore = document.createElement('span');
aiScore.id = 'ai-score';
aiScore.className = 'score';
aiScore.innerText = 0;
scoreBoardAiP.append(aiScore);
gameArea.append(scoreBoard);

const playerBtnArea = document.createElement('div');
playerBtnArea.id = 'player-btn-area';
playerBtnArea.className = 'btn-area';
const playerBtnAreaHeader = document.createElement('span');
playerBtnAreaHeader.className = 'btn-header';
playerBtnAreaHeader.innerText = 'Player';
playerBtnArea.append(playerBtnAreaHeader);

const rockButton = document.createElement('button');
rockButton.id = 'rock-button';
rockButton.className = 'btn';
rockButton.setAttribute('data-id', 1);
const rockButtonSpan = document.createElement('span');
rockButtonSpan.innerText= 'R';
rockButton.append(rockButtonSpan);
playerBtnArea.append(rockButton);

const paperButton = document.createElement('button');
paperButton.id = 'paper-button';
paperButton.className = 'btn';
paperButton.setAttribute('data-id', 2);
const paperButtonSpan = document.createElement('span');
paperButtonSpan.innerText = 'P';
paperButton.append(paperButtonSpan);
playerBtnArea.append(paperButton);

const scissorsButton = document.createElement('button');
scissorsButton.id = 'scissors-button';
scissorsButton.className = 'btn';
scissorsButton.setAttribute('data-id', 3);
const scissorsButtonSpan = document.createElement('span');
scissorsButtonSpan.innerText = 'S';
scissorsButton.append(scissorsButtonSpan);
playerBtnArea.append(scissorsButton);
gameArea.append(playerBtnArea);

const aiBtnArea = document.createElement('div');
aiBtnArea.id = 'ai-btn-area';
aiBtnArea.className = 'btn-area';
const aiBtnAreaHeader = document.createElement('span');
aiBtnAreaHeader.className = 'btn-header';
aiBtnAreaHeader.innerText = 'Computer';
aiBtnArea.append(aiBtnAreaHeader);

const airockButton = document.createElement('button');
airockButton.id = 'ai-rock-button';
airockButton.className = 'btn';
const airockButtonSpan = document.createElement('span');
airockButtonSpan.innerText = 'R';
airockButton.append(airockButtonSpan);
aiBtnArea.append(airockButton);

const aipaperButton = document.createElement('button');
aipaperButton.id = 'ai-paper-button';
aipaperButton.className = 'btn';
aipaperButton.setAttribute('data-id', 2);
const aipaperButtonSpan = document.createElement('span');
aipaperButtonSpan.innerText = 'P';
aipaperButton.append(aipaperButtonSpan);
aiBtnArea.append(aipaperButton);

const aiscissorsButton = document.createElement('button');
aiscissorsButton.id = 'ai-scissors-button';
aiscissorsButton.className = 'btn';
aiscissorsButton.setAttribute('data-id', 3);
const aiscissorsButtonSpan = document.createElement('span');
aiscissorsButtonSpan.innerText = 'S';
aiscissorsButton.append(aiscissorsButtonSpan);
aiBtnArea.append(aiscissorsButton);
gameArea.append(aiBtnArea);

const notification = document.createElement('div');
notification.id = 'notification';
gameArea.append(notification);
document.body.append(gameArea);

The first few lines of the code initialise variables that will be used to keep track of the game's state, including the current pattern of moves, the player's score, the computer's score, and the game count.

The following lines of code create HTML elements and appends them to the game area, including a header, score board, buttons for the player, and buttons for the computer. Each button is given a unique ID and icon, and the player's buttons are given a "data-id" attribute to represent which move they correspond to (rock, paper, or scissors).

Finally, the code creates a notification area and appends it to the game area, and appends the entire game area to the body of the HTML document.

Now, when you refresh the page, you should see something like this

Basic UI without styling

I'm going to leave the styling up to you and won't include any in this tutorial. Your imagination's your limit when it comes to how you want it to look, don't be afraid to experiment and play about with the style in the console of your browser and see what you can come up with.

Adding functionality

So now we have our basic UI, we need to add in some functionality. First we're going to create the button click events and the function to set the player's input. Back to app.js, enter the following code

function setBtnEvents(){
rockButton.addEventListener('click', playerInput);
paperButton.addEventListener('click', playerInput);
scissorsButton.addEventListener('click', playerInput);
}

function removeBtnEvents(){
rockButton.removeEventListener('click', playerInput);
paperButton.removeEventListener('click', playerInput);
scissorsButton.removeEventListener('click', playerInput);
}

function playerInput(){
removeBtnEvents();
let choice = this.getAttribute('data-id');
this.className = 'btn-active';
playerChoice = parseInt(choice);
gameCount++;
setTimeout(aiPrediction, 2000);
setTimeout(checkWin, 3000);
}

Let's break down what each function here is doing:

  • setBtnEvents(): This function adds event listeners to therockButton, paperButton, and scissorsButton buttons. When they are clicked, the playerInput() function is called. This function is used to set up the game at the beginning or after a round ends.
  • removeBtnEvents(): This function removes the event listeners from the same three buttons (rockButton, paperButton, and scissorsButton). This is used to prevent the player from clicking any buttons while the computer is making its move and the game is determining the winner of the round.
  • playerInput(): This function is called when one of the three buttons is clicked. It removes the event listeners from the buttons (using removeBtnEvents()), records the player's choice of rock, paper, or scissors (by getting the data-id attribute of the clicked button), sets the playerChoice variable to this value, increments the gameCount variable, and schedules two more functions to be called after a delay: aiPrediction() (which will predict the computer's choice) and checkWin() (which will determine the winner of the round). The delay is implemented using the setTimeout() function.

Next we create the functions for the computer moves

function prepareData(){
if (pattern.length < 1) {
for(let i = 0; i < 10; i++){
pattern.push(Math.floor(Math.random() * 3) + 1);
}
}
}

function updatePattern(){
if (gameCount !== 0){
pattern.shift();
pattern.push(playerChoice);
}
}

function aiPrediction(){
prepareData();
const net = new brain.recurrent.LSTMTimeStep();
net.train([pattern], { iterations: 100, log: false });
const predicted = net.run(pattern);
const roundedPredicted = Math.round(predicted);
aiChoice = (1 <= roundedPredicted && roundedPredicted <= 3)? (roundedPredicted % 3) + 1 : 1;
document.getElementById('ai-'+stringOf(aiChoice)+'-button').className = 'btn-active';
updatePattern();
}
  • prepareData: This function initializes a pattern array with 10 random numbers between 1 and 3 if the array is empty. The pattern array is used to train the AI to predict the player's moves.
  • updatePattern: This function updates the pattern array with the player's choice, after removing the oldest element of the array. This is done after each round of the game.
  • The aiPrediction() function is responsible for generating the AI's choice based on the current pattern of the player's choices.

First, it calls the prepareData() function to ensure that the pattern array has at least 10 elements in it. If the pattern array has less than 10 elements, prepareData() will add 10 new random values to the array.

Next, the function creates a new instance of the brain.recurrent.LSTMTimeStep() neural network. It trains the network on the pattern array, with the iterations option set to 100 to run the training 100 times.

After training, the function calls net.run(pattern) to get a prediction from the neural network for the next element in the sequence, which will be the next choice of the player. It rounds the predicted value to the nearest integer using Math.round(), and then calculates the AI's choice using (roundedPredicted % 3) + 1. This will give a value between 1 and 3, which is then assigned to the aiChoice variable.

The function then sets the class of the AI's choice button to btn-active using document.getElementById() and className. Finally, the updatePattern() function is called to update the pattern array with the latest player's choice.

Finally, we build the checkWin function and the function which will convert the chosen integers to their respected strings of either rock, paper or scissors:

function checkWin(){
if(playerChoice === aiChoice){
winningMessage = 'draw';
}
else if(
(playerChoice === 1 && aiChoice === 3) ||
(playerChoice === 3 && aiChoice === 2) ||
(playerChoice === 2 && aiChoice === 1)
){
winningMessage = 'You win';
scorePlayer++;
playerBtnArea.getElementsByClassName('btn-active')[0].className = 'btn-win';
aiBtnArea.getElementsByClassName('btn-active')[0].className = 'btn-lose';
}
else{
winningMessage = 'Computer wins';
scoreAI++;
aiBtnArea.getElementsByClassName('btn-active')[0].className = 'btn-win';
playerBtnArea.getElementsByClassName('btn-active')[0].className = 'btn-lose';
}

notification.innerText = winningMessage;
playerScore.innerText = scorePlayer;
aiScore.innerText = scoreAI;

setTimeout(function(){
notification.innerText = '';
if(winningMessage != 'draw'){
document.getElementsByClassName('btn-win')[0].className = 'btn';
document.getElementsByClassName('btn-lose')[0].className = 'btn';
}else{
document.getElementsByClassName('btn-active')[0].className = 'btn';
document.getElementsByClassName('btn-active')[0].className = 'btn';
}
setBtnEvents();
}, 1000);
}

function stringOf(integer){
return (integer == 1)? 'rock' : ((integer == 2)? 'paper' : ((integer == 3)? 'scissors' : ''));
}

The checkWin() function is called after the AI makes its prediction and it determines who won the round of rock-paper-scissors.

If the player and the AI made the same choice, the winningMessage variable is set to "draw". If the player wins, the winningMessage is set to "You win" and the player's score is incremented. If the AI wins, the winningMessage is set to "Computer wins" and the AI's score is incremented.

In each of these cases, the appropriate button is highlighted to indicate whether it was the winning or losing button. After a short delay, the notification area is cleared and the button classes are reset to their default state, and the button event listeners are added back so the player can make another choice.

The stringOf() function takes an integer argument and returns a string representing the choice corresponding to the integer value: "rock" for 1, "paper" for 2, "scissors" for 3, or an empty string if the argument is not 1, 2, or 3. This function is used to dynamically generate the ID of the button that corresponds to the AI's choice, so that the appropriate button can be highlighted as the winning or losing button.

All that's left to do is run the setBtnEvents function in app.js

setBtnEvents();

And that's it. Now when you refresh the page, you should be able to play and see basic notifications of win, lose, draw and see the scores increment. Again, I won't add in the styling in this tutorial, but here's something I managed to come up with

javascript ai RPS with style

The full code

index.html

<!DOCTYPE html>
<html>
<head>
<title>Rock Paper Scissors</title>
<link rel="stylesheet" href="styles.css">
<script src="brain.js"></script>
</head>
<body>
<script src="app.js"></script>
</body>
</html>

app.js

let pattern = [];
let scorePlayer = 0;
let scoreAI = 0;
let playerChoice = 0;
let aiChoice = 0;
let gameCount = 0;
let winningMessage = '';

const gameArea = document.createElement('div');
gameArea.id = 'game-area';
const headerOne = document.createElement('h1');
headerOne.innerText = 'Javascript AI Rock Paper Scissors';
gameArea.append(headerOne);

const scoreBoard = document.createElement('div');
scoreBoard.id = 'score-board';
const scoreBoardPlayerP = document.createElement('p');
scoreBoardPlayerP.innerText = 'Player';
scoreBoard.append(scoreBoardPlayerP);

const playerScore = document.createElement('span');
playerScore.id = 'player-score';
playerScore.className = 'score';
playerScore.innerText = 0;
scoreBoardPlayerP.append(playerScore);

const scoreBoardAiP = document.createElement('p');
scoreBoardAiP.innerText = 'Computer';
scoreBoard.append(scoreBoardAiP);

const aiScore = document.createElement('span');
aiScore.id = 'ai-score';
aiScore.className = 'score';
aiScore.innerText = 0;
scoreBoardAiP.append(aiScore);
gameArea.append(scoreBoard);

const playerBtnArea = document.createElement('div');
playerBtnArea.id = 'player-btn-area';
playerBtnArea.className = 'btn-area';
const playerBtnAreaHeader = document.createElement('span');
playerBtnAreaHeader.className = 'btn-header';
playerBtnAreaHeader.innerText = 'Player';
playerBtnArea.append(playerBtnAreaHeader);

const rockButton = document.createElement('button');
rockButton.id = 'rock-button';
rockButton.className = 'btn';
rockButton.setAttribute('data-id', 1);
const rockButtonSpan = document.createElement('span');
rockButtonSpan.className = 'icon-hand-grab-o';
rockButton.append(rockButtonSpan);
playerBtnArea.append(rockButton);

const paperButton = document.createElement('button');
paperButton.id = 'paper-button';
paperButton.className = 'btn';
paperButton.setAttribute('data-id', 2);
const paperButtonSpan = document.createElement('span');
paperButtonSpan.className = 'icon-hand-paper-o';
paperButton.append(paperButtonSpan);
playerBtnArea.append(paperButton);

const scissorsButton = document.createElement('button');
scissorsButton.id = 'scissors-button';
scissorsButton.className = 'btn';
scissorsButton.setAttribute('data-id', 3);
const scissorsButtonSpan = document.createElement('span');
scissorsButtonSpan.className = 'icon-hand-scissors-o';
scissorsButton.append(scissorsButtonSpan);
playerBtnArea.append(scissorsButton);
gameArea.append(playerBtnArea);

const aiBtnArea = document.createElement('div');
aiBtnArea.id = 'ai-btn-area';
aiBtnArea.className = 'btn-area';
const aiBtnAreaHeader = document.createElement('span');
aiBtnAreaHeader.className = 'btn-header';
aiBtnAreaHeader.innerText = 'Computer';
aiBtnArea.append(aiBtnAreaHeader);

const airockButton = document.createElement('button');
airockButton.id = 'ai-rock-button';
airockButton.className = 'btn';
const airockButtonSpan = document.createElement('span');
airockButtonSpan.className = 'icon-hand-grab-o';
airockButton.append(airockButtonSpan);
aiBtnArea.append(airockButton);

const aipaperButton = document.createElement('button');
aipaperButton.id = 'ai-paper-button';
aipaperButton.className = 'btn';
aipaperButton.setAttribute('data-id', 2);
const aipaperButtonSpan = document.createElement('span');
aipaperButtonSpan.className = 'icon-hand-paper-o';
aipaperButton.append(aipaperButtonSpan);
aiBtnArea.append(aipaperButton);

const aiscissorsButton = document.createElement('button');
aiscissorsButton.id = 'ai-scissors-button';
aiscissorsButton.className = 'btn';
aiscissorsButton.setAttribute('data-id', 3);
const aiscissorsButtonSpan = document.createElement('span');
aiscissorsButtonSpan.className = 'icon-hand-scissors-o';
aiscissorsButton.append(aiscissorsButtonSpan);
aiBtnArea.append(aiscissorsButton);
gameArea.append(aiBtnArea);

const notification = document.createElement('div');
notification.id = 'notification';
gameArea.append(notification);
document.body.append(gameArea);

setBtnEvents();

function setBtnEvents(){
rockButton.addEventListener('click', playerInput);
paperButton.addEventListener('click', playerInput);
scissorsButton.addEventListener('click', playerInput);
}

function removeBtnEvents(){
rockButton.removeEventListener('click', playerInput);
paperButton.removeEventListener('click', playerInput);
scissorsButton.removeEventListener('click', playerInput);
}

function playerInput(){
removeBtnEvents();
let choice = this.getAttribute('data-id');
this.className = 'btn-active';
playerChoice = parseInt(choice);
gameCount++;
setTimeout(aiPrediction, 2000);
setTimeout(checkWin, 3000);
}

function prepareData(){
if (pattern.length < 1) {
for(let i = 0; i < 10; i++){
pattern.push(Math.floor(Math.random() * 3) + 1);
}
}
}

function updatePattern(){
if (gameCount !== 0){
pattern.shift();
pattern.push(playerChoice);
}
}

function aiPrediction(){
prepareData();
const net = new brain.recurrent.LSTMTimeStep();
net.train([pattern], { iterations: 100, log: false });
const predicted = net.run(pattern);
const roundedPredicted = Math.round(predicted);
aiChoice = (1 <= roundedPredicted && roundedPredicted <= 3)? (roundedPredicted % 3) + 1 : 1;
document.getElementById('ai-'+stringOf(aiChoice)+'-button').className = 'btn-active';
updatePattern();
}

function checkWin(){
if(playerChoice === aiChoice){
winningMessage = 'draw';
}
else if(
(playerChoice === 1 && aiChoice === 3) ||
(playerChoice === 3 && aiChoice === 2) ||
(playerChoice === 2 && aiChoice === 1)
){
winningMessage = 'You win';
scorePlayer++;
playerBtnArea.getElementsByClassName('btn-active')[0].className = 'btn-win';
aiBtnArea.getElementsByClassName('btn-active')[0].className = 'btn-lose';
}
else{
winningMessage = 'Computer wins';
scoreAI++;
aiBtnArea.getElementsByClassName('btn-active')[0].className = 'btn-win';
playerBtnArea.getElementsByClassName('btn-active')[0].className = 'btn-lose';
}

notification.innerText = winningMessage;
playerScore.innerText = scorePlayer;
aiScore.innerText = scoreAI;

setTimeout(function(){
notification.innerText = '';
if(winningMessage != 'draw'){
document.getElementsByClassName('btn-win')[0].className = 'btn';
document.getElementsByClassName('btn-lose')[0].className = 'btn';
}else{
document.getElementsByClassName('btn-active')[0].className = 'btn';
document.getElementsByClassName('btn-active')[0].className = 'btn';
}
setBtnEvents();
}, 1000);
}

function stringOf(integer){
return (integer == 1)? 'rock' : ((integer == 2)? 'paper' : ((integer == 3)? 'scissors' : ''));
}

Sources

The full script is available on Github

Click here to play the game


3

3

1

ozboware
PHP and Javascript app developer

More Articles