Sunday, 11 May 2025

Web Tutorial: Paper Rock Scissors Game (Part 1/2)

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.

No comments:

Post a Comment