During the early months of the COVID-19 outbreak, I applied for a job where the interviewers sent me a coding test. The objective? To write a
Paper Rock Scissors Game in HTML, CSS and JavaScript.
 |
Paper Rock Scissors |
At the time, they requested that I keep the GitHub repository for the code private. It's been five years and I figure A.I has rendered their fun little tests moot. In any case, I rewrote my code from scratch and hopefully my style now has improved.
The Stated Requirements
1. Implement a Paper Rock Scissors Game in HTML/CSS/JavaScript.
2. The game should be extensible to include Spock and Lizard.
3. The game should include an option for the computer to play against itself.
Let's begin!
Here's some starting HTML boilerplate. In the CSS, we set the general font face and font size. I've set divs to have a
red outline. In the JavaScript, we want an object,
paprocksc. I know, that's quite the name, eh?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Paper Rock Scisscors</title>
<style>
body
{
text-align: center;
font-size: 14px;
font-family: verdana;
}
div { outline: 1px solid red; }
</style>
<script>
let paprocksc =
{
};
</script>
</head>
<body>
</body>
</html>
Let's get some HTML structure out of the way. We have a header. We also have the CSS class
gameContainer styling the outermost div.
<body>
<h1>Paper, Rock, Scissors</h1>
<div class="gameContainer">
</div>
</body>
Then two divs, each styled using
player, and
left and
right respectively. They have different ids. And let's have a hr tag after all this.
<body>
<h1>Paper, Rock, Scissors</h1>
<div class="gameContainer">
<div class="player left">
</div>
<div class="player right">
</div>
<hr />
</div>
</body>
In the CSS,
gameContainer occupies a certain width and height.
player occupies roughly half the width.
left makes its element float left, and
right does the exact opposite.
<style>
body
{
text-align: center;
font-size: 14px;
font-family: verdana;
}
div { outline: 1px solid red; }
.gameContainer
{
width: 100%;
height: 600px;
}
.player
{
width: 45%;
height: 300px;
}
.left
{
float: left;
}
.right
{
float: right;
}
</style>
And hr, that comes right after these two divs, has the clear property set to both so that we don't end up with collapsing issues.
<style>
body
{
text-align: center;
font-size: 14px;
font-family: verdana;
}
div { outline: 1px solid red; }
.gameContainer
{
width: 100%;
height: 600px;
}
.player
{
width: 45%;
height: 300px;
}
.left
{
float: left;
}
.right
{
float: right;
}
hr
{
clear: both;
}
</style>
This is what it looks like!
We're going to use this image next. I made this myself - just a
white question mark over a
black square.
 |
symbol.jpg |
In each div styled using the
player CSS class, we have one div. They have different ids, and are styled using the
symbol CSS class. Because we want them to be floated a certain direction, we style them using
right and
left, respectively. And we hard-code the background image to use
symbol.jpg.
<div class="player left">
<div id="player_0" class="symbol right" style="background-image: url(symbol.jpg);"></div>
</div>
<div class="player right">
<div id="player_1" class="symbol left" style="background-image: url(symbol.jpg);"></div>
</div>
In the CSS,
symbol is a circle, thus width and height properties are the same and
border-radius is set to 50%. The background-related properties ensure that the background image used covers the entire element.
outline is set to a thick
white line. These are the defaults.
.right
{
float: right;
}
.symbol
{
width: 200px;
height: 200px;
border-radius: 50%;
margin-top: 10px;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
outline: 15px solid rgb(255, 255, 255);
}
hr
{
clear: both;
}
Take a look at how it's coming together. We're going to add some placeholders for scores.
Here we have a div. It's styled using
score and
right. Inside it is a small header.
<div class="player left">
<div class="score right"><small>You</small><br /></div>
<div id="player_0" class="symbol right" style="background-image: url(symbol.jpg);"></div>
</div>
<div class="player right">
<div id="player_1" class="symbol left" style="background-image: url(symbol.jpg);"></div>
</div>
We'll also have a span tag, with an id. The default value is 0.
<div class="player left">
<div class="score right"><small>You</small><br /><span id="score_0">0</span></div>
<div id="player_0" class="symbol right" style="background-image: url(symbol.jpg);"></div>
</div>
<div class="player right">
<div id="player_1" class="symbol left" style="background-image: url(symbol.jpg);"></div>
</div>
Repeat this for the other side...
<div class="player left">
<div class="score right"><small>You</small><br /><span id="score_0">0</span></div>
<div id="player_0" class="symbol right" style="background-image: url(symbol.jpg);"></div>
</div>
<div class="player right">
<div class="score right"><small>You</small><br /><span id="score_0">0</span></div>
<div id="player_1" class="symbol left" style="background-image: url(symbol.jpg);"></div>
</div>
... but make sure the div is styled using
left, instead. The span tag id should be different, and the header is for the computer.
<div class="player left">
<div class="score right"><small>You</small><br /><span id="score_0">0</span></div>
<div id="player_0" class="symbol right" style="background-image: url(symbol.jpg);"></div>
</div>
<div class="player right">
<div class="score left"><small>Computer</small><br /><span id="score_1">0</span></div>
<div id="player_1" class="symbol left" style="background-image: url(symbol.jpg);"></div>
</div>
In the CSS,
score has a defined width and height, and font. We align the text center. The small tag within score will be half the font size.
.symbol
{
width: 200px;
height: 200px;
border-radius: 50%;
margin-top: 10px;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
outline: 15px solid rgb(255, 255, 255);
}
.score
{
width: 150px;
height: 50px;
font-weght: bold;
font-size: 2em;
text-align: center;
}
.score small
{
font-size: 0.5em;
}
hr
{
clear: both;
}
Looks good. Let's do a dashboard next.
After the hr tag, add a div. Style it using the CSS class
dashboard.
<hr />
<div class="dashboard">
</div>
Inside it, add a button. Style it using the CSS classes
symbolBtn and
hidden.
<hr />
<div class="dashboard">
<button class="symbolBtn hidden"></button>
</div>
Add five more buttons.
<hr />
<div class="dashboard">
<button class="symbolBtn hidden"></button>
<button class="symbolBtn hidden"></button>
<button class="symbolBtn hidden"></button>
<button class="symbolBtn hidden"></button>
<button class="symbolBtn hidden"></button>
<button class="symbolBtn hidden"></button>
</div>
Now, we're going to be using more images I generated using A.I. They are crude
black-and-
white drawings of hand signs - Paper, Rock, Scissors, Lizard and Spock.
 |
symbol_paper.jpg |
 |
symbol_rock.jpg |
 |
symbol_scissors.jpg |
 |
symbol_lizard.jpg |
 |
symbol_spock.jpg |
Add these images as content in the buttons. We'll limit the width to 80 pixels.
<hr />
<div class="dashboard">
<button class="symbolBtn hidden"><img src="symbol_paper.jpg" width="80" /></button>
<button class="symbolBtn hidden"><img src="symbol_rock.jpg" width="80" /></button>
<button class="symbolBtn hidden"><img src="symbol_scissors.jpg" width="80" /></button>
<button class="symbolBtn hidden"><img src="symbol_spock.jpg" width="80" /></button>
<button class="symbolBtn hidden"><img src="symbol_lizard.jpg" width="80" /></button>
</div>
In the CSS,
dashboard has a defined with and height, and is positioned in the middle via the
margin property.
.score small
{
font-size: 0.5em;
}
.dashboard
{
width: 90%;
height: 200px;
margin: 0 auto 0 auto;
}
hr
{
clear: both;
}
We style
symbolBtn by making it a 100 pixel square, with rounded corners and a bit of spacing on the right.
.score small
{
font-size: 0.5em;
}
.dashboard
{
width: 90%;
height: 200px;
margin: 0 auto 0 auto;
}
.symbolBtn
{
width: 100px;
height: 100px;
border-radius: 5px;
margin-right: 5px;
}
hr
{
clear: both;
}
You may also want to generally style buttons to have a
grey background when hovered over.
.gameContainer
{
width: 100%;
height: 600px;
}
button:hover
{
background-color: rgb(100, 100, 100);
}
.player
{
width: 45%;
height: 300px;
}
Now you can see the buttons! And what happens when you mouse over one of them.
Now add the
hidden CSS class, and the buttons should disappear.
.symbolBtn
{
width: 100px;
height: 100px;
border-radius: 5px;
margin-right: 5px;
}
.hidden
{
display: none;
}
hr
{
clear: both;
}
Near the title, let's add some controls. First, we want a drop-down list with the id
ddlRange. It determines if the user is playing with just Paper, Rock and Scissors, or the full extended version. The values are 3 and 5, representing the number of hand symbols available.
<h1>Paper, Rock, Scissors</h1>
<select id="ddlRange">
<option value="3">Classic</option>
<option value="5">Extended</option>
</select>
<div class="gameContainer">
Then we have a checkbox with the id
cbAuto. This controls whether or not the computer plays against itself or the user.
<h1>Paper, Rock, Scissors</h1>
<select id="ddlRange">
<option value="3">Classic</option>
<option value="5">Extended</option>
</select>
<input type="checkbox" id="cbAuto">Auto
<div class="gameContainer">
We then have two buttons, ids
btnStart and
btnStop.
btnStop is hidden, but both buttons are styled using the CSS class
topBtn.
<h1>Paper, Rock, Scissors</h1>
<select id="ddlRange">
<option value="3">Classic</option>
<option value="5">Extended</option>
</select>
<input type="checkbox" id="cbAuto">Auto
<button id="btnStart" class="topBtn">Start</button>
<button id="btnStop" class="topBtn hidden">Stop</button>
<div class="gameContainer">
Styling for
topBtn is just a matter of visuals. I'm sticking to the
black-
white theme.
.gameContainer
{
width: 100%;
height: 600px;
}
.topBtn
{
width: 5em;
height: 2em;
background-color: rgb(0, 0, 0);
color: rgb(255, 255, 255);
font-weight: bold;
border-radius: 3px;
}
button:hover
{
background-color: rgb(100, 100, 100);
}
There be your controls!
That's it for the UI! Turn the
red outline for divs off.
div { outline: 0px solid red; }
The paprocksc object
We're going to define some properties. First,
symbols. This is a collection of all the hand symbols used in the game.
let paprocksc =
{
symbols:
[
]
};
Each one is an object with the
name property with values such as "paper", "rock", etc. The
beats property is an array, and it defines which symbol the current symbol, well, beats. For example, Paper beats Rock and Spock.
let paprocksc =
{
symbols:
[
{
name: "paper",
beats: ["rock", "spock"]
}
]
};
And that's how we populate the rest of symbols.
let paprocksc =
{
symbols:
[
{
name: "paper",
beats: ["rock", "spock"]
},
{
name: "rock",
beats: ["lizard", "scissors"]
},
{
name: "scissors",
beats: ["paper", "lizard"]
},
{
name: "spock",
beats: ["scissors", "rock"]
},
{
name: "lizard",
beats: ["spock", "paper"]
}
]
};
range defines how many elements of symbols to use. The default is 3, which means we use the first 3 elements - Paper, Rock and Scissors.
auto defines whether or not the computer is playing against itself, and by default it's
false.
let paprocksc =
{
symbols:
[
{
name: "paper",
beats: ["rock", "spock"]
},
{
name: "rock",
beats: ["lizard", "scissors"]
},
{
name: "scissors",
beats: ["paper", "lizard"]
},
{
name: "spock",
beats: ["scissors", "rock"]
},
{
name: "lizard",
beats: ["spock", "paper"]
}
],
range: 3,
auto: false
};
Next we have
score and
round, both of which are arrays with two elements.
score is an array of integers which keeps track of rounds won.
round keeps track of who's using what symbol. In both cases, the first element pertains to the user, and the second to the computer.
let paprocksc =
{
symbols:
[
{
name: "paper",
beats: ["rock", "spock"]
},
{
name: "rock",
beats: ["lizard", "scissors"]
},
{
name: "scissors",
beats: ["paper", "lizard"]
},
{
name: "spock",
beats: ["scissors", "rock"]
},
{
name: "lizard",
beats: ["spock", "paper"]
},
],
range: 3,
auto: false,
score: [0, 0],
round: ["", ""]
};
Lastly, we have
active and
timer.
active just determines if the buttons are clickable, and
timer is relevant only during Auto mode.
let paprocksc =
{
symbols:
[
{
name: "paper",
beats: ["rock", "spock"]
},
{
name: "rock",
beats: ["lizard", "scissors"]
},
{
name: "scissors",
beats: ["paper", "lizard"]
},
{
name: "spock",
beats: ["scissors", "rock"]
},
{
name: "lizard",
beats: ["spock", "paper"]
},
],
range: 3,
auto: false,
score: [0, 0],
round: ["", ""],
active: false,
timer: null
};
Now, on to methods! We first have
start() and
stop(), and I hope these are self-explanatory.
let paprocksc =
{
symbols:
[
{
name: "paper",
beats: ["rock", "spock"]
},
{
name: "rock",
beats: ["lizard", "scissors"]
},
{
name: "scissors",
beats: ["paper", "lizard"]
},
{
name: "spock",
beats: ["scissors", "rock"]
},
{
name: "lizard",
beats: ["spock", "paper"]
},
],
range: 3,
auto: false,
score: [0, 0],
round: ["", ""],
active: false,
timer: null,
start: function()
{
},
stop: function()
{
}
};
showOptions() shows and hides symbol buttons according to which should be available. And from there, we have
chooseSymbol(), which takes the choice, represented by the parameter
symbol, and processes it.
start: function()
{
},
stop: function()
{
},
showOptions: function()
{
},
chooseSymbol: function(symbol)
{
}
};
Then we have what I like to call the other "show" methods.
showSymnbol() takes
index to determine which placeholder to show
symbol in.
showScore() displays the current score.
showColor() changes the outline of the placeholder indicated by
index, to the color determined by
colorCode.
showOptions: function()
{
},
chooseSymbol: function(symbol)
{
},
showSymbol: function(index, symbol)
{
},
showScore: function()
{
},
showColor: function(index, colorCode)
{
}
};
whowins() is the parent method that calls
xbeatsy() to determine if one symbol beats the other.
showSymbol: function(index, symbol)
{
},
showScore: function()
{
},
showColor: function(index, colorCode)
{
},
whowins: function()
{
},
xbeatsy: function(x, y)
{
}
};
I'd like to begin with some of the simpler "show" methods. Like this one,
showSymbol(). We declare
imgUrl as the string "symbol.jpg". If the value for
symbol is
not an empty string, we add an underscore and symbol to the filename, e.g, "symbol_scissors.jpg".
showSymbol: function(index, symbol)
{
var imgUrl = "symbol" + (symbol === "" ? "" : "_" + symbol) + ".jpg";
},
Then we have
player. It either references the div
player_0 or
player_1.
showSymbol: function(index, symbol)
{
var imgUrl = "symbol" + (symbol === "" ? "" : "_" + symbol) + ".jpg";
var player = document.getElementById("player_" + index);
},
This last line populates the desired div with the correct image background.
showSymbol: function(index, symbol)
{
var imgUrl = "symbol" + (symbol === "" ? "" : "_" + symbol) + ".jpg";
var player = document.getElementById("player_" + index);
player.style.backgroundImage = "url(" + imgUrl + ")";
},
Now for
showScore(). This is straightforward. We declare
score0 and
score1, and set them to the elements
score_0 and
score_1, respectively.
showScore: function()
{
var score0 = document.getElementById("score_0");
var score1 = document.getElementById("score_1");
},
We then populate those elements with the values of the
score array. As mentioned previously, the first element is for the user and the second is for the computer.
showScore: function()
{
var score0 = document.getElementById("score_0");
var score1 = document.getElementById("score_1");
score0.innerHTML = this.score[0];
score1.innerHTML = this.score[1];
},
And then we have
showColor().
colorCode will be a RGB value, and this is how we define it. First, we have an object,
codes. Then we have the
win,
loss,
draw and
end properties, each of which is a RGB value. A win is
green, a loss is
red, a draw is
grey, and the neutral state,
end, is
white.
showColor: function(index, colorCode)
{
var codes =
{
win: "rgb(0, 255, 0)",
loss: "rgb(255, 0, 0)",
draw: "rgb(200, 200, 200)",
end: "rgb(255, 255, 255)",
};
},
As with
showSymbol(),
index defines the div to work on.
showColor: function(index, colorCode)
{
var codes =
{
win: "rgb(0, 255, 0)",
loss: "rgb(255, 0, 0)",
draw: "rgb(200, 200, 200)",
end: "rgb(255, 255, 255)",
};
var symbol = document.getElementById("player_" + index);
},
We will give that div's outline a color based on the RGB value that
colorCode points to in
codes.
showColor: function(index, colorCode)
{
var codes =
{
win: "rgb(0, 255, 0)",
loss: "rgb(255, 0, 0)",
draw: "rgb(200, 200, 200)",
end: "rgb(255, 255, 255)",
};
var symbol = document.getElementById("player_" + index);
symbol.style.outline = "15px solid " + codes[colorCode];
},
Now for
showOptions()! In here, we decide which of the symbol buttons in the dashboard to show or hide. Create an array,
btns, by running
getElementsByClassName() and passing in the class name
symbolBtn. Then use a
For loop to iterate through them.
showOptions: function()
{
var btns = document.getElementsByClassName("symbolBtn");
for (var i = 0; i < btns.length; i ++)
{
}
},
In the loop, check if
i is less than the
range property.
showOptions: function()
{
var btns = document.getElementsByClassName("symbolBtn");
for (var i = 0; i < btns.length; i ++)
{
if (i < this.range)
{
}
else
{
}
}
},
If it is, we ensure that the button is displayed. If not, it is hidden. That means that in Classic Mode, where
range is 3, only the first 3 buttons will be shown.
showOptions: function()
{
var btns = document.getElementsByClassName("symbolBtn");
for (var i = 0; i < btns.length; i ++)
{
if (i < this.range)
{
btns[i].className = "symbolBtn";
}
else
{
btns[i].className = "symbolBtn hidden";
}
}
},
One last method!
We'll do
xbeatsy(). This method determines if symbol
x beats symbol
y. For this, we first need to find information about symbol
x. Use the
filter() method on
symbols, returning the element where
name matches
x. Assign the resultant array to the variable
symbol.
xbeatsy: function(x, y)
{
var symbol = this.symbols.filter((s)=> { return s.name === x});
}
Here, the
return statement's value is either
true or
false. We ask if the
beats array of the first (and only) element of
symbol, contains
y. If it does,
x beats
y. If not, then it returns
false.
xbeatsy: function(x, y)
{
var symbol = this.symbols.filter((s)=> { return s.name === x});
return (symbol[0].beats.indexOf(y) > -1);
}
These methods don't seem to do much, or have anything to do with each other. But fear not; all shall be revealed shortly. Join us for the next part of this web tutorial!
Next
Running the game.