Let's build a game using JavaScript
Rock, Paper, Scissors game built with Vanilla JavaScript
Hello π, In this article, I'll share how I built my first game written in Javascript and my thought process in making the game. Hope you'll follow along and learn something new. Okay then, let's go over the rules of the game for us to understand the game.
If you've ever played the game called Rock, Paper, Scissors you'll already be familiar with the rules of the game. But if you've never played the game, here are the rules:
Prerequisite
- Basic knowledge of HTML
- Basic knowledge of CSS
- Basic knowledge of the Command Line Interface
- Basic knowledge of JavaScript
Why I took this challenge?
Storytime π, if you're not interested click here to move to the next section.
Before why, this challenge was provided by frontendmentor.io, which is an amazing platform to test and improve your Frontend skills. I'll advise you to check out some of the challenges on the platform yourself.
Click here to download this project file from frontendmentor.io so that you can follow along. It's totally fine if you don't want to get the project file, just reading how I made it is fine.
Before agreeing to attempt this challenge I've always run away from the challenge saying it looks too complex not until I saw that Victor Ikechukwu had completed the challenge. I was amazed that he had already done the challenge I considered hard, so I made up my mind to take up the challenge, how hard could it be? I said.
Here's the live demo if you'll like to play the game:
Set goals I implemented
Another storytime π, if you would like to skip the section click here.
Upon every challenge I take, I'll always want to try something new, so I planned out what I'll like to practice and learn while on the challenge.
- Making use of Dart SCSS/SASS (Syntactically Awesome Style Sheets) which I had never used.
- Making use of just vanilla JavaScript.
- Making use of Functional Programming Paradigm to the best of my abilities and lastly
- Using BEM (Block Element Modifier) methodology for naming class names.
Actually, I got the idea of implementing the functional programming paradigm, after reading this article Functional Programming with JavaScript by Saurabh Kamboj.
I've never been a big fan of the Object-Oriented Paradigm, so why not learn the Functional Programming Paradigm instead, I concluded.
Setup for the project
Whenever a challenge is downloaded from frontendmentor.io all files needed like the HTML file with all the text used for the challenge, the colours used and also the designs for both mobile and the desktop view will be provided.
For the next couple of steps, we'll need Node.js installed first, which comes with NPM (Node Package Manager) for us to get started.
Click here to download Node.js
First thing first, we'll need to run this command npm init -y
on the terminal to keep track of all the dependencies for the project.
Dependencies are the packages that are being used in the code of the project. For example, if my application is working with SASS then there may be a dependency of SASS node package in the application.
Running the above command will generate a package.json
file which shows the dependencies used in the project and also information about the project, for now, there are no dependencies.
Then to install SASS, from NPM (Node Package Manager) through Node, type in the command below on the terminal which installs sass
globally on your system.
npm install -g sass
-g is a flag that represents global
I've written an introductory article about SASS, you could read that by clicking here.
Now that sass
has been installed globally on our system, we'll need to install it on our project directory, to make it work in the directory through this command.
npm install sass
Notice, two files will be added to the project directory
./node_modules
which contains libraries downloaded from npm forsass
to work.
package_lock.json
which contains the several dependenciessass
needs to work.
Yes! finally, we've got SASS installed and ready to be used. Now, the next step is to open the package.json
file. We'll need to add a command that compiles our SASS files into a plain CSS file which the browser understands.
Navigate to the "script"
key, replacing it with the command below
"scripts": {
"start" : "sass --watch scss:css"
},
Once the command is set up, we can watch the changes made in the scss
folder which will be containing all our SCSS modules and then generates a css
folder with the plain CSS, to which the HTML file will be linked to.
From the command above, sass
watches every file in the directory/folder named scss and compiles all the SCSS files into a single CSS file in the directory/folder named css. The CSS file compiled is what the browser can read and interpret.
To initialise the watch, type the command below into the terminal.
npm start
By doing this every change in the scss folder containing the scss
files will be compiled into one css
file.
Don't run the command yet, because we don't have our scss folder created yet
The next step is to create a folder named scss, which will contain all the scss files needed for the project. When the folder is created, create a new file named _base.scss
.
_base.scss
is a partial file that's not compiled until it's imported to the main stylesheet style.scss. This file contains resets, variables, mixins, and any utility classes used throughout the project.
Since we are making use of colours that have been provided in the style-guide.md
file by Frontend mentor, open the style-guide.md
file copy all the colours including their names, into the _bass.scss
file.
After copying the colours, make it have the structure as shown below.
/* _base.scss */
:root {
--scissorsGradient: linear-gradient(hsl(39, 89%, 49%), hsl(40, 84%, 53%));
--paperGradient: linear-gradient(hsl(230, 89%, 62%), hsl(230, 89%, 65%));
--rockGradient: linear-gradient(hsl(349, 71%, 52%), hsl(349, 70%, 56%));
--lizardGradient: linear-gradient(hsl(261, 73%, 60%), hsl(261, 72%, 63%));
--cyan: linear-gradient(hsl(189, 59%, 53%), hsl(189, 58%, 57%));
--darkText: hsl(229, 25%, 31%);
--scoreText: hsl(229, 64%, 46%);
--headerOutline: hsl(217, 16%, 45%);
/* Background */
--radialGradient: linear-gradient(hsl(214, 47%, 23%), hsl(237, 49%, 15%));
}
I decided to make use of the css custom variable for the project instead of scss variable, why? I don't know π.
The next step is to click on the link to the font at the style-guide.md
file to grab it from google fonts. Adding the @import
rule with the font name to the body
selector.
/* _base.scss */
@import url('https://fonts.googleapis.com/css2?family=Barlow+Semi+Condensed:wght@400;600;700&display=swap');
:root {...}
body {
font-family: 'Barlow Semi Condensed', sans-serif;
}
Now that we got all our set up ready, it's time to actually start coding, by inputting the command we failed to run since we didn't have scss folder before.
npm start
By running the command above, a css folder is auto-created containing the style.css
and style.css.map
files.
Let's start coding
Before proceeding any further, let's take a look at the project folder structure, to make sure we are at the same pace.
css
|__ style.css
|__ style.css.map
design
|__ ....
images
|__ ....
node_modules
|__ ....
scss
|__ ....
.gitignore
index.html
main.js
package-lock.json
package.json
README-template.md
README.md
style-guide.md
Okay then, the HTML file is shown below, notice the class names are all named following the BEM (Block Element Modifier) methodology (at least most of them π).
Read this article to know more about BEM
If you're wondering why BEM, it just makes it easier when nesting in SASS, plus a whole lot of advantages. Please find them out using the link above.
<!DOCTYPE html>
<html lang="en">
<head>
...
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<main class="container">
<div class="heading">
<img src="./images/logo.svg" alt="logo" />
<div class="score__display">
<p>Score</p>
<p id="score" aria-live="polite">0</p>
</div>
</div>
<div class="game">
<div class="game__paper__scissors">
<div
class="game__button game__button--paper"
role="button"
aria-label="paper button"
id="paper"
>
<div>
<img src="./images/icon-paper.svg" alt="icon-paper" />
</div>
</div>
<div
class="game__button game__button--scissors"
role="button"
aria-label="Scissors button"
id="scissors"
>
<div>
<img src="./images/icon-scissors.svg" alt="icon-scissors" />
</div>
</div>
</div>
<div
class="game__button game__button--rock rock-button"
role="button"
aria-label="rock button"
id="rock"
>
<div>
<img src="./images/icon-rock.svg" alt="icon-rock" />
</div>
</div>
</div>
<div class="play__again">
<h3 id="winLoseDraw" aria-live="polite">.</h3>
<button id="playAgain" class="btn-sec" aria-label="play again">
Play Again
</button>
</div>
<button class="btn-pry" id="rules">Rules</button>
</main>
<div class="attribution">
Challenge by
<a href="https://www.frontendmentor.io?ref=challenge" target="_blank"
>Frontend Mentor</a
>. Coded by
<a href="https://jomefavourite.github.io/linktree/">Favourite Jome</a>.
</div>
<!-- Modal for the game rules -->
<div class="modal" id="modal">
<div class="modal__container">
<div>
<div>
<h2>Rules</h2>
<img
class="closeBtn desktopCloseBtn"
src="./images/icon-close.svg"
alt="icon-close"
/>
</div>
<img
class="modal-rules"
src="./images/image-rules.svg"
alt="image-rules"
/>
<img
class="closeBtn mobileCloseBtn"
src="./images/icon-close.svg"
alt="icon-close"
/>
</div>
</div>
</div>
<!-- Template for the game -->
<template id="gameTemplate">
<div class="game__grade">
<div>
<div
class="game__button user__button"
role="button"
aria-label="paper button"
id="paper"
>
<div>
<img src="./images/icon-paper.svg" alt="icon-paper" />
</div>
</div>
<div
class="game__button computer__button"
role="button"
aria-label="Scissors button"
id="scissors"
>
<div></div>
</div>
</div>
</div>
</template>
<script src="main.js"></script>
</body>
</html>
From the HTML file above, there's a strange element named template
.
<!-- Template for the game -->
<template id="gameTemplate">
<div class="game__grade">
<div>
<div
class="game__button user__button"
role="button"
aria-label="paper button"
id="paper"
>
<div>
<img src="./images/icon-paper.svg" alt="icon-paper" />
</div>
</div>
<div
class="game__button computer__button"
role="button"
aria-label="Scissors button"
id="scissors"
>
<div></div>
</div>
</div>
</div>
</template>
If you've seen this element before, very good, if not all it does is hide the child elements from the browser and can only be shown using Javascript by adding it to the DOM (Document Object Model).
I got the idea of using the template
element from an article I read, sadly I can't find it.
From MDN, the
template
element is a mechanism for holding HTML that is not to be rendered immediately when a page is loaded but may be instantiated subsequently during runtime using JavaScript.Read more about the template element here
Also, if you've ever seen a Vue.js source code, you'll notice the template
element is used.
Why I used the template element
I used the template
element to hide its child elements from the DOM (Document Object Model) until a certain action/event is done using JavaScript which then appends the child elements of the template
element into the DOM. The implementation is done in the Javascript section.
SASS
As for the styling of our project, we'll be making use of the scss
extension which is more like writing CSS but still with all the SASS
features.
If the statement above seems confusing, then please read this article - Write CSS with Superpowers Using Sass. where I've covered the differences between
SCSS
andSASS
.
Here the folder structure of all the scss
file used in the project.
scss
| _base.scss
| _components.scss
| _desktop.scss
| _medium.scss
| _mixins.scss
| style.scss
The _
before the scss
file, symbolises it's a partial file, which means it should not be compiled until when used in the main style.scss
file.
_base.scss
contains styles such as resets and typographic rules, which are commonly used throughout your project._components.scss
contains all the CSS that handles the layout._desktop.scss
contains all the CSS rules for the desktop view._medium.scss
contains all the CSS rules for the tablet view._mixins.scss
contains all the mixins created for the project.
style.scss
contains all the imports for the above files.
The two main features of Dart SASS I got to try out was @use
rule and @forward
rule.
- The
@use
rule loads mixins, functions, and variables from other Sass stylesheets, and combines CSS from multiple stylesheets together while - The
@forward
rule loads a Sass stylesheet and makes its mixins, functions, and variables available when your stylesheet is loaded with the @use rule.
The two rules mentioned above are part of the newer features of Sass, which is to replace the use of the @import
rule.
JavaScript
Javascript, the brain of the project! Before diving into the functionality of the game, here's a breaking down of how the game should be implemented.
From the image above, you'll notice there are steps needed to be taken, before declaring the winner. The user will have to click on either the Rock, Paper or Scissors button. Then after a few seconds, the computer makes its choice and then finally a winner is declared.
As for the score for the game, I decided to implement it this way, if a user wins the score should be incremented by 1 (+1) while if the computer wins the score should be decremented by 1 (-1).
Now let's dive into the program.
// selecting the DOM
function select(name) {
return document.querySelector(name);
}
function selectAll(name) {
return document.querySelectorAll(name);
}
const rulesBtn = select('#rules');
const closeBtns = selectAll('.closeBtn');
const rock = select('#rock');
const paper = select('#paper');
const scissors = select('#scissors');
const scoreResult = select('#score');
const modal = select('#modal');
From the above code, two functions were declared select()
and selectAll()
, which returns document.querySelector(name)
and document.querySelectorAll(name)
respectively. Using this functions, enables me to select my DOM elements quickly and by typing less.
Also this way I'm following the Functional Programming paradigm rules, which are
Using Pure Functions: Pure Functions are the functions that depend only on their input and nothing else i.e they'll have some input values and based on those input values they'll always return an output. From Functional Programming with JavaScript by Saurabh Kamboj
Avoid Side Effects: Side effects are a modification to the outside world, avoid interacting with the global scope.
There are many more rules, to learn more read these articles Functional Programming with JavaScript and Master the JavaScript Interview: What is Functional Programming?
Next off, a variable score
is declared having a function lS()
which is invoked. The function ls()
checks if the key score
is added to the localStorage. If the key exists then the value is returned as a Number else 0
is returned.
On the next line, the scoreResult.innerHTMl
has its value also to the function ls()
which return the score from the localStorage.
Then several event listeners are declared.
var score = lS(0);
scoreResult.innerHTML = lS(0);
// LocalStorage functionality
function lS(initialValue) {
const localData = localStorage.key('score');
return localData ? Number(localStorage.getItem('score')) : initialValue;
}
// Event Listeners
rock.addEventListener('click', playGame);
paper.addEventListener('click', playGame);
scissors.addEventListener('click', playGame);
rulesBtn.addEventListener('click', () => openCloseModal(modal));
closeBtns.forEach(closeBtn => {
return closeBtn.addEventListener('click', () => openCloseModal(modal));
});
Notice that rock
, paper
and scissors
variables which are the buttons (actually I used the div element with the role of a button in the HTML file but I'm just going to call it buttons because it's clicked on) clicked on by the user to initiate the game has a function to its event listener named `play games.
function playGame() {
const userChoice = this.id;
const computerChoice = computerRandom(['rock', 'paper', 'scissors']);
const game = select('.game');
const {userButton, computerButton} = gameTemplate(userChoice, computerChoice);
const result = rules(userChoice, computerChoice);
game.classList.add('max__width');
setTimeout(() => {
if (result === 'win') {
scoreResult.innerHTML = add();
userButton.classList.add('winner__boxShadow');
}
if (result === 'lose') {
scoreResult.innerHTML = subtract();
computerButton.classList.add('winner__boxShadow');
}
}, 400);
return winLose(result);
}
Now let's go over the function to figure out how it works.
Line 1 in the function
playGame()
:const userChoice = this.id;
This is to get the users choice, since multiple buttons have the same function, we'll need to know what button was clicked.
That's why thethis
keyword is used to get the attribute on the elementid
.
So if the rock button is clicked<div class="game__button ..." role="button" aria-label="rock button" id="rock">...</div>
theid
attribute will be returned.Line 2 in the function:
const computerChoice = computerRandom(['rock', 'paper', 'scissors']);
Here a functioncomputerRandom()
is invoked with its argument as an array that randomly gets an item from the parameter array.function computerRandom(games) { return games[Math.floor(Math.random() * games.length)]; }
At line 5:
const {userButton, computerButton} = gameTemplate(userChoice, computerChoice);
The functiongameTemplate()
which will be returning an object is where all the magic happens. Here's the function that takes the contents of thetemplate
element and adds it to the DOMfunction gameTemplate(userChoice, computerChoice) { if ('content' in document.createElement('template')) { const game = select('.game'); const template = select('#gameTemplate'); game.style.backgroundImage = 'none'; game.children[0].style.display = 'none'; game.children[1].style.display = 'none'; const clone = template.content.cloneNode(true); const gameButton = clone.querySelectorAll('.game__button'); gameButton[0].classList.add(`game__button--${userChoice}`); gameButton[0].children[0].children[0].src = `./images/icon-${userChoice}.svg`; gameButton[1].style.backgroundColor = 'rgba(0, 0, 0, 0.15)'; gameButton[1].children[0].style.display = 'none'; setTimeout(() => { gameButton[1].children[0].style.display = 'flex'; const img = document.createElement('img'); img.setAttribute('src', `./images/icon-${computerChoice}.svg`); img.setAttribute('alt', `icon-${computerChoice}`); gameButton[1].classList.add(`game__button--${computerChoice}`); gameButton[1].children[0].appendChild(img); }, 400); game.appendChild(clone.firstElementChild); return {userButton: gameButton[0], computerButton: gameButton[1]}; } }
I'm sorry I wouldn't go over everything in this function line by line, but I'll go over the main functionalities.
Okay then let's exact the main statements from the above function
function gameTemplate(userChoice, computerChoice) { if ('content' in document.createElement('template')) { const game = select('.game'); const template = select('#gameTemplate'); const clone = template.content.cloneNode(true); const gameButton = clone.querySelectorAll('.game__button'); gameButton[0] // userChoice Button gameButton[1] // computerChoice Button setTimeout(() => { gameButton[1] .... }, 400) game.appendChild(clone.firstElementChild); return {userButton: gameButton[0], computerButton: gameButton[1]}; } return; }
From the function above there's a check if'content'
existdocument.createElement('template'))
which checks if the browser supports the template element.
Next off, the element with the class of.game
is selected, then id of#gameTemplate
. On the next lineconst clone = template.content.cloneNode(true);
, this is to ensure we can clone the contents in thetemplate
element.
Then thegameButton
is selected from the clone. WheregameButton[0]
is the users button andgameButton[1]
is the computer button. Also notice, there's a delay of 400ms ongameButton[1]
so that it appears as if that it takes some time to compute.
Next off, theclone
element gets appended to thegame
element via the DOM and bothgameButton[0]
andgameButton[1]
are returned in an object.{userButton: gameButton[0], computerButton: gameButton[1]}
At line 6 in the
playGame()
function:const result = rules(userChoice, computerChoice);
We'd notice another functionrules()
which is being invoked with the argumentsuserChoice, computerChoice
.
function rules(userChoice, computerChoice) { if (userChoice === computerChoice) { return `draw`; } switch (userChoice) { case 'paper': { return computerChoice === 'rock' ? 'win' : 'lose'; break; } case 'rock': { return computerChoice === 'scissors' ? 'win' : 'lose'; break; } case 'scissors': { return computerChoice === 'paper' ? 'win' : 'lose'; break; } } return null; }
All the function does is to determine who wins, loses and if it's a draw.
At line 20, the returned function is invoked
winLose(result)
with the result as its argument.
This function is to determine the message that will be prompted out if the user wins, loses or if its a draw.
Also the function returns a variableplayAgain
with the event listener which when clicked on the game is played again.function winLose(result) { const playAgainBtn = select('#playAgain'); const playAgain = select('.play__again'); const game = select('.game'); const winLoseDraw = select('#winLoseDraw'); setTimeout(() => { playAgain.style.display = 'block'; if (result === 'win') { winLoseDraw.innerHTML = 'You Win'; } if (result === 'lose') { winLoseDraw.innerHTML = 'You Lose'; } if (result === 'draw') { winLoseDraw.innerHTML = "It's a Draw"; } }, 400); return playAgainBtn.addEventListener('click', () => { playAgainFn(game, playAgain); });
OMG, that's a lot of information to process, I know. From the above steps, we've covered all the main functions that make the game work.
Once again here's the:
Resources
- Different types of dependencies in a Node.js application explained
- Structuring your Sass Projects
- Write CSS with Superpowers Using Sass.
- Functional Programming with JavaScript
- Master the JavaScript Interview: What is Functional Programming?
Conclusion
Was I able to accomplish my set goals for this project, I'd say yes. I did approach the program functionalities in a Functional Programming way, at least to my best abilities even though I know not all functions I used were pure. But hey! I did my best π. I also made use of BEM, SASS and plain JS.
If you've read so far, thank you very much, I hope you've learnt something.
So if you have any feedback, question, advice or any at all you could comment down below or reach me on Twitter
See you next timeβ.