Creating a Rock, Paper, Scissors Game in Angular
Photo by Julio Casado on Unsplash
Hello there! In this blog post, we are going to create a rock, paper, scissors game in Angular. If you are an experienced Angular developer, you might not find something new in this article.
This is for beginners, but I will also assume you know enough Angular because I will not be explaining all of the basic stuff (there are a ton of tutorials about it where it is covered beautifully).
I also want to show you how easy it is to create something in Angular. We’re going to use a little bit of Angular Material and CSS Grid to make things quickly. With that out of the way, let’s get down to it.
You can check the final product here.
If you want to follow along, click on this stackblitz project here.
1. Layout
I want to create two cards (for you and Player 2). Feel free to do anything you want here, but I will use two columns, using CSS Grid to have two equal cards.
Go to app.component.html and add:
<mat-toolbar color="primary">
Rock Paper Scissors
</mat-toolbar>
<div class="content">
<!-- You -->
<mat-card>
</mat-card>
<!-- Player 2 -->
<mat-card>
</mat-card>
</div>
and in your app.component.css:
.content{
display:grid;
grid-template-columns: 1fr 1fr;
}
Now our project looks like this:
not much here
2. Content
Next, we’re going to add a bit of text and we’re going to use fab buttons for displaying the score. We’re also going to use Font Awesome for the icons for rock, paper, and scissors.
<mat-toolbar color="primary">
Rock Paper Scissors
</mat-toolbar>
<div class="content">
<!-- You -->
<mat-card>
<mat-card-content>
<div class="content1">
<h2> You</h2>
<button mat-fab color="primary" class="score">
0
</button>
</div>
<h3 > Choose your weapon: </h3>
<div class="content2">
<i class="fa fa-hand-rock-o"></i>
<i class="fa fa-hand-paper-o"></i>
<i class="fa fa-hand-scissors-o"></i>
</div>
</mat-card-content>
</mat-card>
<!-- Player 2 -->
<mat-card>
<div class="content1">
<h2> Player 2</h2>
<button mat-fab color="accent" class="score">0</button>
</div>
<br><br>
<div class="flex-container flex-center" >
<i class="fa fa-hand-rock-o"></i>
<i class="fa fa-hand-paper-o" ></i>
<i class="fa fa-hand-scissors-o" ></i>
</div>
</mat-card>
</div>
If you've noticed the card for player 2, I used Flexbox when displaying the icons, because I’m only going to show the icons one at a time and I want it to be centered. For now, all of them are displayed and we will get back to that in a minute.
For the CSS, I’m going to use CSS grid again (don’t judge me, I’m practicing my CSS skills ) for .content1 and .content2.
.content1{
display:grid;
grid-template-columns: 2fr 1fr;
}
.content2{
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.score{
margin-top:.5em;
margin-left:2em;
font-size:1.5em
}
.fa{
cursor:pointer;
font-size:5em;
}
Now the app looks like this:
3. Logic
Here are the variables we need. Go to app.component.ts and add this:
scores = [0 , 0]; // store the scores. index 0 is you. index 1 is player 2.
weapons = [
'rock',
'paper',
'scissors'
];
playerSelected = -1;
enemySelected = -1;
loading= false; // we're going to show a loading spinner when waiting for the enemy pick.
isResultShow = false;
// theResult - 0 winner
// 1 lose
// 2 tie
theResult = 0
Next, we’re going to create a function that we’re going to bind to a click event when choosing a weapon.
pick( weapon: number): void {
// return immediately when still loading. You don't want
// the user to spam the button.
if(this.loading) return;
this.loading = true;
this.playerSelected = weapon;
//create a delay to simulate enemy's turn.
setTimeout( () => {
this.loading = false;
// generate a number from 0 -2
const randomNum = Math.floor(Math.random() * 3 ) ;
this.enemySelected = randomNum;
this.checkResult();
this.isResultShow = true;
}, Math.floor(Math.random() * 500 ) +200);
}
reset(): void {
this.scores = [0,0];
}
checkResult(): void {
const playerPick = this.playerSelected;
const enemyPick = this.enemySelected;
// if you and the enemy have the same weapon, then it is a tie.
if( playerPick == enemyPick)
{
this.theResult = 2;
}
// let's say you picked rock ( 0 )
// and the enemy picked paper ( 1 )
// you lose because ( 0 - 1 + 3 ) % 3 is equal to 2.
// when you picked rock ( 0 )
// and the enemy picked scissor ( 2 )
// you win because ( 0 - 2 + 3) % 3 is equal to 1.
// when you picked scissor ( 2 )
// and the enemy picked paper ( 1 )
// you win because ( 2 - 1 + 3 ) % 3 is equal to 1. 4 % 3 is 1.
// Hope you get the picture.
else if ( (playerPick - enemyPick + 3)% 3 == 1) {
// YOU WIN
this.theResult = 0;
this.scores[0] = this.scores[0]+1;
}
else{
// YOU LOSE
this.theResult = 1;
this.scores[1] = this.scores[1]+1;
}
}
Now we’re going to modify the app.component.html again to bind to our functions and variables.
<div class="content">
<mat-card>
<mat-card-content>
<div class="content1">
<h2> You</h2>
<button mat-fab color="primary" class="score">{{ scores[0] }}</button>
</div>
<h3 > Choose your weapon: </h3>
<div class="content2">
<i (click)="pick(0)" class="fa fa-hand-rock-o"></i>
<i (click)="pick(1)" class="fa fa-hand-paper-o"></i>
<i (click)="pick(2)" class="fa fa-hand-scissors-o"></i>
</div>
</mat-card-content>
</mat-card>
<mat-card>
<mat-card-content>
<div class="content1">
<h2> Player 2</h2>
<button mat-fab color="accent" class="score">{{ scores[1] }}</button>
</div>
<br><br>
<div *ngIf="enemySelected !== -1" class="flex-container flex-center" >
<i *ngIf="enemySelected === 0" class="fa fa-hand-rock-o"></i>
<i *ngIf="enemySelected === 1" class="fa fa-hand-paper-o"></i>
<i *ngIf="enemySelected === 2" class="fa fa-hand-scissors-o"></i>
</div>
</mat-card-content>
</mat-card>
</div>
We bind pick() function to our rock, paper, and scissors icons on click event and pass the corresponding values.
Also, the icons for player 2 are now hidden and only show after you've picked your weapon.
We are almost done and the only thing left to do is display the results.
4. Showing results
We are going to display if you win, lose, or tie, and add a button to reset the scores. Also, we are going to show a loading spinner to add some effects when the delay is not yet finished.
Here’s the code to add at the end of your app.component.html.
<div *ngIf="!loading && isResultShow" class="flex-container flex-center" style="flex-direction: column">
<div [ngSwitch]="theResult">
<ng-template [ngSwitchCase]= "0"> <h1> You're the winner! </h1></ng-template>
<ng-template [ngSwitchCase]= "1"> <h1> You lose. </h1> </ng-template>
<ng-template [ngSwitchCase]= "2"> <h1>It's a tie! </h1> </ng-template>
<ng-template ngSwitchDefault> </ng-template>
</div>
<p> Pick again to continue or </p>
<button (click)="reset()" mat-raised-button color="primary"> Reset score</button>
</div>
<div class="flex-container flex-center" *ngIf="loading">
<mat-spinner></mat-spinner>
</div>
Note that I used ngSwitch when displaying the results. You can also use *ngIf/Else if you like, but I think ngSwitch is better suited here.
5. Bonus — Highlighting the selected weapon.
The app is now working but I want to add some highlight to the selected weapon. I wanted to use the primary color from Angular material and after some experiments, I was able to get it by using the background property.
.selectedWeapon{
color: background;
}
Using NgClass to add the class for the selected weapon:
<mat-card-content>
<div class="content1">
<h2>You</h2>
<button mat-fab color="primary" class="score">{{ scores[0] }}</button>
</div>
<h3 > Choose your weapon: </h3>
<div class="content2">
<i (click)="pick(0)" [ngClass]="{'selectedWeapon':(playerSelected === 0)}" class="fa fa-hand-rock-o"></i>
<i (click)="pick(1)" [ngClass]="{'selectedWeapon':(playerSelected === 1)}" class="fa fa-hand-paper-o"></i>
<i (click)="pick(2)" [ngClass]="{'selectedWeapon':(playerSelected === 2)}" class="fa fa-hand-scissors-o"></i>
</div>
</mat-card-content>
That’s pretty much it. Congratulations!
You can get the full code here.