Rock, paper, scissors game in JavaScript

Muhammad Saqib Ilyas
12 min readJul 22, 2023

--

Summary

In this article, I’ll show you how to develop a Rock, Paper, Scissors game in JavaScript. It’ll be a one-player game, i.e., computer vs the user. Our emphasis will be on the game’s logic with JavaScript and not on styling.

By following along, you’ll learn the following:

  • Setting up radio buttons and a button in an HTML form
  • Manipulating the DOM in JavaScript
  • Attaching and defining event listeners to form controls in JavaScript

Rules of the game

Skip ahead if you know the rules of the game. But here they are just in case:

  • The player and the computer choose one of rock, paper, scissors
  • If both players chose the same item, it is a tie
  • If one player chooses rock and the other chooses paper, the second one wins, because paper wraps rock
  • If one player chooses rock and the other chooses scissors, the first one wins, because rock breaks scissors
  • If one player chooses scissors and the other chooses paper, the first one wins, because scissors cut paper

Problem analysis

Let’s break the problem down into smaller sub-problems, so that we can solve it systematically. Here is what we need to do:

  • Create a web page on which the game will be played.
  • Allow the learner to select from rock, paper, and scissors. Update the web page with the player’s choice.
  • Implement a computer player that randomly picks from rock, paper, scissors. Update the web page with the computer’s choice.
  • Decide who won and display the result on the screen.

The Setup

Follow these steps to get started:

  • Create a folder for the code
  • Inside that folder, create an HTML file index.html
  • Inside the same folder, create a JavaScript file app.js
  • Also inside the same folder, create a CSS file style.css

The HTML

Here are the contents of theindex.html file.:

<!DOCTYPE HTML>
<HTML lang="en">
<head>
<meta charset="utf-8">
<title>Rock Paper Scissors</title>
<link rel='stylesheet' href='style.css'>
</head>
<body>
<h2>Computer Choice: <span id="computer-choice"></span></h2>
<h2>Your Choice: <span id="user-choice"></span></h2>
<h2>Result: <span id="result"></span></h2>
<input type="radio" id="rock" name="choice" value="rock">
<label for="rock">Rock</label><br>
<input type="radio" id="paper" name="choice" value="paper">
<label for="paper">Paper</label><br>
<input type="radio" id="scissors" name="choice" value="scissors">
<label for="scissors">Scissors</label>
<button id="play" disabled>Play</button>
<script src="app.js" charset="utf-8"></script>
</body>
</HTML>

We have the DOCTYPE tag to tell the browser that we’ll use HTML 5. We’ve set lang = en to declare the language as English, and charset='utf-8' to declare the UTF 8 character encoding. These are often not necessary, but a good idea for accessibility.

We first created h2 elements to show the computer and player one choices. Inside the h2 elements, we’ve placed span elements to hold the values picked by both players. In the HTML, these are empty. We’ll assign the text inside these span elements in JavaScript.

For the player to choose an item, we’ve used radio buttons, created using the input and the label tags. The input tag creates the radio button, with type attribute set to radio. The name attribute for all radio buttons is set to the same value choice, so that the browser knows that they belong to the same group, and only one can be selected at a time.

The id attributes for all radio buttons, and span elements, are set appropriately, because we’ll want to access these in JavaScript. For the radio button labels, you’ll notice that the label tags have the for attribute set equal to the id attribute of the corresponding radio button.

Note that I have disabled the play button. My thought process, here, is that until the player makes a choice with the radio button, there’s no point pushing the play button and invoking its click event handler.

Note that we’ve placed the script tag referencing the JavaScript file at the end of the body tag. This is a best practice, so that the web page rendering does not get blocked by JavaScript execution.

The CSS file

We wouldn’t bother with the styling of the application. However, I’ve set the text-transform style for the span elements to capitalize so that we can use words in lowercase in JavaScript and have the browser worry about rendering “scissors” as “Scissors”, for example. Here are the contents of the style.css file:

span {
text-transform: capitalize;
}

The preliminary code

Let’s start by getting handles to the necessary HTML elements, and declaring an array that holds the three strings that the computer may randomly choose from. To get programmatic access to an HTML element using its unique id, we can use the document.getElementById library function. We’ll pass the uniqueid values that we set in the index.html file earlier.

Place this in theapp.js:

const compChoice = document.getElementById('computer-choice')
const yourChoice = document.getElementById('user-choice')
const resultDisplay = document.getElementById('result')
const playButton = document.getElementById('play')
const possibleChoices = ['rock', 'paper', 'scissors']

The first thing that I want to do is to enable the play button as soon as a radio button is clicked. To do that, I need to add an event listener to all the radio buttons that sets the disabled attribute of the play button to false.

Append the following to the JavaScript code we have so far:

const choices = document.getElementsByName('choice')
choices.forEach((c) => {
c.addEventListener('click', () => {
playButton.disabled = false
})
})

We could have picked the radio buttons one by one using their id attributes, and the getElementyById function, but it is more convenient and less error prone to access all the radio buttons using a single variable. This is possible, because they all have the same name attribute. We used the getElementsByName function, this time. Note the plural “Elements” in the function name. It returns a collection of all HTML elements with the name value specified as the argument.

To iterate over the choices button collection, I’ve used the functional programming style of forEach. The forEach function invokes a function for each value in the collection on which it is called. Since we are saying choices.forEach, there’ll be one call to a callback function for each value in the choices collection, which holds all of our radio buttons. The callback function is for us to specify as an argument to the forEach function. I used an anonymous function, here. The variable c will be assigned each radio button, one by one, in each call.

Since we wanted to add an event listener that enables the play button when radio button is clicked, we call the addEventListener function on c. We also specified click as the event that we want to handle. A callback function needs to be passed as the second argument of the addEventListener function. Again, we used an anonymous function, here. Since we already have access to the play button courtesy of the variable playButton, all that we needed to do was playButton.disabled = false.

Handling the play button click

We’ll add an anonymous function as the click event listener on the play button. The first thing that we’d like to know when the play button is clicked, is the player’s choice. This is what we’ll start with:

playButton.addEventListener('click', (e) => {
const buttonsArray = Array.from(choices)
const selected = buttonsArray.filter((b)=> b.checked)
})

I wanted to obtain the particular radio button that was checked in the choices collection. I could’ve done if-else, here. But why not use filter. Note that choices, is a collection of HTML elements, not a JavaScript array. That means, that we can’t call filter on it. So, we first convert the collection of HTML elements into an array with Array.from. The argument to this function is the collection choices.

Now, we call filter on the buttonsArray using an anonymous callback function. This function will be called for every element in that array. For each element, b, we specify a boolean condition b.checked. The filter function returns all the elements in the buttonsArray for which this boolean condition evaluates to true. Since the array is based on radio buttons, we are confident that it’ll return an array with one element — the radio button element which was selected.

How do we extract the string associated with the radio button? Because of the way we defined the radio buttons (<input type=”radio” id=”rock” name=”choice” value=”rock”>, for example), we may use the id property. Now, let’s set the player choice span to this string.

Displaying the player choice

Since we’ll have to display the computer choice, too, I’ll define a function that adds a string to a span element, given both of these as arguments. Here’s the code:

function addTextToSpan(spanControl, text) {
spanControl.textContent = text
}

We replace the textContent of the span element with the argument passed to the function. Now, let’s go back to the play button click handler that were working on, earlier, and call this function, to display the player’s choice:

playButton.addEventListener('click', (e) => {
const buttonsArray = Array.from(choices)
const selected = buttonsArray.filter((b)=> b.checked)
addTextToSpan(yourChoice, selected[0].id);
})

By now, the player will see the play button initially disabled. Once they click on any of the radio buttons, the play button will be enabled. If they click on the play button, they’ll see the choice that they made in front of “Your Choice” on the page.

The game AI

I admit it isn’t really an AI, but, anyway. We want the computer to make a random choice. We want it to pick a value at random from the possibleChoices array. So, we need to generate a random index.

JavaScript provides a Math library with several useful functions. One of these is the Math.random function, which returns a number randomly picked between 0 and 1. In fact, the random value can be 0, but it is always less than 1. It is a fractional value, like 0.42245.

A fractional value can’t be used as an array index. It has to be an integer. How do we convert a randomly drawn fractional value between 0 and 1, to an integer value between 0 and 1?

If we multiply this random value with 3 (the size of the array), the results will be in the range 0 to 3. Why? Because the smallest randomly drawn number we had was 0, multiply that by 3, and you still have 0. The largest randomly drawn number was 1, multiply that by 3 and you have 3. Since any other randomly drawn value will be greater than 0, and less than 1, the result after multiplication with 3, will be between 0 and 3.

Great, so now we have a fractional random value, that lies between 0 and 3. We still don’t have an integer. There are two ways that you can convert a fractional number to an integer: ceiling and floor.

Ceiling is when you pick the smallest integer greater than the fractional value. Let’s consider how we might mentally calculate the ceiling of a fractional number. Imagine the value 0.113. Do you replace it with 0 or 1, or 2, or 3, or so on? Which one of these is the smallest integer greater than 0.113? 0 isn’t greater than 0.113, so that’s out of the question. 1 is greater than 0.113. So, are 2, and 3, and so on. But 1 is the smallest of these values. So, the ceiling of 0.113 is 1. The following illustration demonstrates this:

Illustration for the ceiling method.
With ceiling, all values between two consecutive integers are mapped to the larger one.

Floor is when you pick the greatest integer smaller than the fractional value. What’s the floor of 0.113? Is it 0, 1, 2, 3, …? Well, 1, 2, 3, … are eliminated because they aren’t smaller than 0.113. That leaves us with 0 (and the negative numbers, which don’t satisfy the “greatest” part of the definition). So, the floor of 0.113 is 0. The following illustration demonstrates this:

Illustration for the floor method.
With floor, all values between two consecutive integers are mapped to the smaller one.

So, if we take the ceiling of the random number times 3, then we get a positive integer that is either 0, 1, 2, or 3. On the other hand, if we take the floor, then we get a positive integer that is either 0, 1, or 2. Since we have to pick from one of three values (rock, paper, scissors), we’ll take the floor.

Here’s the function that makes a random choice, sets the HTML element to show the player what the computer chose, and returns the chosen value so that the calling function may decide who won.

function generateComputerChoice() {    
const randomNumber = Math.floor(Math.random() * possibleChoices.length)
const computerChoice = possibleChoices[randomNumber]
addTextToSpan(compChoice, possibleChoices[randomNumber])
return computerChoice
}

Now, let’s call this function in the play button click handler that we were building earlier:

playButton.addEventListener('click', (e) => {
const buttonsArray = Array.from(choices)
const selected = buttonsArray.filter((b)=> b.checked)
addTextToSpan(yourChoice, selected[0].id);
const randChoice = generateComputerChoice()
})

Deciding the result

We’ll extend the play button handler to decide who won. we’ll define a function that takes two arguments: the player’s choice, and the computer’s choice, and decides who won. Here’s the code:

function showResult(userChoice, computerChoice) {
if (userChoice === computerChoice) {
addTextToSpan(resultDisplay, 'tied')
}
else if (userChoice === 'rock') {
if (computerChoice === 'paper') {
addTextToSpan(resultDisplay, 'you lost')
}
else if (computerChoice == 'scissors') {
addTextToSpan(resultDisplay, 'you won')
}
}
else if (userChoice === 'paper') {
if (computerChoice === 'scissors') {
addTextToSpan(resultDisplay, 'you lost')
}
else if (computerChoice == 'rock') {
addTextToSpan(resultDisplay, 'you won')
}
}
else if (userChoice === 'scissors') {
if (computerChoice === 'rock') {
addTextToSpan(resultDisplay, 'you lost')
}
else if (computerChoice == 'paper') {
addTextToSpan(resultDisplay, 'you won')
}
}
}

We first checked if there’s a tie, because that’s easy to check. We used the === JavaScript operator. Note that we didn’t use the == operator, because it isn’t safe — it may do type coercion to deduce equality among different types of data.

Further, we’ve encoded the game’s rules into the else if parts. Each of the player’s choices becomes one else if. If the player chooses rock, at this point, the computer’s choice was either paper, or scissors. Why could it not be rock, too? Because we are in the else if part already, and that possibility was exhausted in the if part at the beginning. That’s why each else if block has two possibilities.

Notice that we’re calling our addTextToSpan function to display the result on the page. Let’s now add a call to this function in the play button click handler that we were working on:

playButton.addEventListener('click', (e) => {
const buttonsArray = Array.from(choices)
const selected = buttonsArray.filter((b)=> b.checked)
addTextToSpan(yourChoice, selected[0].id);
const randChoice = generateComputerChoice()
showResult(selected[0].id, randChoice)
})

With this addition, our game is nearly complete. There’s only one improvement I’d like to make to it. Once the result of a game is decided, I’d like the radio buttons to be reset, again, and the play button disabled, so that the learner can start another round of the game. Here’s the modified event handler:

playButton.addEventListener('click', (e) => {
const buttonsArray = Array.from(choices)
const selected = buttonsArray.filter((b)=> b.checked)
addTextToSpan(yourChoice, selected[0].id);
const randChoice = generateComputerChoice()
showResult(selected[0].id, randChoice)
e.target.disabled = true;
choices.forEach((b)=> {
b.checked = false;
})
})

Since this is the click handler for the play button, and the event source is specified in the anonymous callback function argument (the e parameter), the play button itself is available through the e.target property. So, we needed to do e.target.disabled = true. Also, we iterated over the choices collection to reset all the radio buttons, using the forEach function.

The complete code

You may download the complete code from this github repository. But, here’s the JavaScript app.js file:

const compChoice = document.getElementById('computer-choice')
const yourChoice = document.getElementById('user-choice')
const resultDisplay = document.getElementById('result')
const playButton = document.getElementById('play')
const choices = document.getElementsByName('choice')
const possibleChoices = ['rock', 'paper', 'scissors']

choices.forEach((c) => {
c.addEventListener('click', () => {
playButton.disabled = false
})
})

playButton.addEventListener('click', (e) => {
const buttonsArray = Array.from(choices)
const selected = buttonsArray.filter((b)=> b.checked)
addTextToSpan(yourChoice, selected[0].id);
const randChoice = generateComputerChoice()
showResult(selected[0].id, randChoice)
e.target.disabled = true;
choices.forEach((b)=> {
b.checked = false;
})
})

function generateComputerChoice() {
const randomNumber = Math.floor(Math.random() * possibleChoices.length)
const computerChoice = possibleChoices[randomNumber]
addTextToSpan(compChoice, possibleChoices[randomNumber])
return computerChoice
}

function addTextToSpan(spanControl, text) {
while(spanControl.firstChild) {
spanControl.removeChild(spanControl.firstChild)
}
spanControl.appendChild(document.createTextNode(text))
}

function showResult(userChoice, computerChoice) {
if (userChoice === computerChoice) {
addTextToSpan(resultDisplay, 'tied')
}
else if (userChoice === 'rock') {
if (computerChoice === 'paper') {
addTextToSpan(resultDisplay, 'you lost')
}
else if (computerChoice == 'scissors') {
addTextToSpan(resultDisplay, 'you won')
}
}
else if (userChoice === 'paper') {
if (computerChoice === 'scissors') {
addTextToSpan(resultDisplay, 'you lost')
}
else if (computerChoice == 'rock') {
addTextToSpan(resultDisplay, 'you won')
}
}
else if (userChoice === 'scissors') {
if (computerChoice === 'rock') {
addTextToSpan(resultDisplay, 'you lost')
}
else if (computerChoice == 'paper') {
addTextToSpan(resultDisplay, 'you won')
}
}
}

The corresponding HTML file index.html:

<!DOCTYPE HTML>
<HTML lang="en">
<head>
<meta charset="utf-8">
<title>Rock Paper Scissors</title>
<link rel='stylesheet' href='style.css'>
</head>
<body>
<h2>Computer Choice: <span id="computer-choice"></span></h2>
<h2>Your Choice: <span id="user-choice"></span></h2>
<h2>Result: <span id="result"></span></h2>
<input type="radio" id="rock" name="choice" value="rock">
<label for="rock">Rock</label><br>
<input type="radio" id="paper" name="choice" value="paper">
<label for="paper">Paper</label><br>
<input type="radio" id="scissors" name="choice" value="scissors">
<label for="scissors">Scissors</label>
<button id="play" disabled>Play</button>
<script src="app.js" charset="utf-8"></script>
</body>
</HTML>

The super-simple CSS file style.css:

span {
text-transform: capitalize;
}

--

--

Muhammad Saqib Ilyas

A computer science teacher by profession. I love teaching and learning programming. I like to write about frontend development, and coding interview preparation