Samson Andrew

Posted on

# Master React by Building Popsaga - A Simple JavaScript Game in 30 minutes

### Introduction

If you are looking for a simple project to test your React skills, you have just found a great article for that.

### What are we building?

We are building Popsaga - a JavaScript popping game.
1. Our game will generate 20 random values between 1 and 50 without repetition
2. We will set a counter which would be a fraction of the number of items to be popped
3. Our aim is to pop all the even numbers from the generated list within the given duration but maintain the initial arrangement on the game board
4. The game will end with a loss if we fail to pop all the required items before the counter reaches 0, or with a winning if we are able to pop all the required items within the given duration
5. We will implement our solution with React

### Implementation

There are always a thousand and one ways of solving a problem, so do we have in programming too. But I'm going to show you how I tackled the challenges outlined above.

#### 1. Generating random 20 values between 1 and 50 without repetition

``````  let seeds = [];
while (seeds.length < 20) {
seeds.push(Math.floor(Math.random() * 50) + 1);
}
seeds = [...new Set(seeds)];
// try it with do...while
``````

`Math.random()` returns a value between 0 and 1, we multiply this value with 50 and call `Math.floor()` on the result to get a number rounded down to the nearest whole number, this would give us a value between 0 and 49. We added 1 to the result so as to get a value between 1 and 50 as required.

After pushing to the `seeds` array, we created a unique array with the `Set` object.

#### 2. Setting a counter

Now that we have our seeds array, let's count how many even numbers are present:

``````const target = seeds.filter(even => even % 2 === 0).length;
const duration = Math.ceil(target * 0.85);
``````

We called `filter` method on the seeds array and used the modulo/remainder operator to check if we get zero after diving the value with 2. Any value that passes this test is an even number.

We set the duration by multiplying the number of even items by 0.85.

#### 3. Popping items without modifying the board arrangement

This is where the task gets more interesting. Our initial thought might be to use either `shift` or `pop` method of array, but this could only be used if we're removing items from the beginning or the end of the array.

`Splice` and `slice` can work if only we don't care about modifying the original array or we want to keep our own copy of the array for mutation respectively. But this is how I solved this stage:

``````  const popped = [];
const buttonClick = i => {
if (i % 2 === 0) {
popped.push(i);
}
}
// When I need to update the game board
seeds.map((box) => (popped.find(item => item === box)) ? true : false );
``````

I created an empty array called `popped` where I kept track of the popped values without touching the original array. When I need to update the game board, I check the values that have been popped and adjust the UI respectively. Cool?

#### 4. Keeping track of loss or winning

``````  const timer = setInterval(() => {
if (won) clearInterval(timer);
else if (duration === 0) {
lost = true;
clearInterval(timer)
} else duration--;
}, 1000);
``````

During the next tick of the timer, we check if the game has been won so we can clear the timer. If the game has not been won, we check the duration, if the timer has reached zero, it means the game was lost else, we decrease the duration and wait for the next tick.

#### Putting it all together with React

``````import React, {useState, useEffect} from 'react';
import './App.css';
function Seed(props) {
return <div className={"seed" + (props.used)} onClick={props.onClick}>{props.name}</div>
}
function App() {
let seeds = [];
do {
seeds.push(Math.floor(Math.random() * 50) + 1);
} while (seeds.length < 20);
seeds = [...new Set(seeds)];
const [target] = useState(seeds.filter(even => even % 2 === 0).length);
const [boxes, setBoxes] = useState({active: seeds, popped: []});
const [duration, setDuration] = useState(Math.ceil(target * 0.85));
const [won, setWon] = useState(false);
const [lost, setLost] = useState(false);
const [start, setStart] = useState(false);
const buttonClick = i => {
if (!start || won || lost || duration === 0) return;
if (i % 2 === 0) {
setBoxes({...boxes, popped: [...boxes.popped, i]});
}
}
useEffect(() => {
setWon(target === boxes.popped.length);
}, [target, boxes]);
useEffect(() => {
if(start) {
const timer = setInterval(() => {
if (won) clearInterval(timer);
else if (duration === 0) {setLost(true); clearInterval(timer)}
else setDuration(duration => duration - 1);
}, 1000);
return () => clearInterval(timer);
}
}, [won, duration, start]);
return (
<div className="game-board">
<div className="timer">{duration}{!start && <div className="start" onClick={() => setStart(true)}>START</div>}</div>
<div className="info-box">
{
won ? <><p>Game Over</p><div className="state green">You Won</div></> :
lost ? <><p>Game Over</p><div className="state red">You lost</div></> :
target - boxes.popped.length > 0 ?
<><p>Remove all even numbers</p><div className="state blue">{target - boxes.popped.length} More</div></> : ""
}
</div>
<div className={"seeds-box"+ (!start ? ' ready' : '')}>{
boxes.active.map(box => <Seed
key={box}
used={(boxes.popped.find(i => i === box)) ? " used" : ""}
name={box}
onClick={() => buttonClick(box)} />
)
}</div>
</div>
)
}
export default App;
``````

### Summary

We have learnt how to use our JavaScript skills to solve some basic tasks. I skipped the React part to keep this article short. I will answer all questions in the comment section.

### Conclusion

The solutions provided in this article are not the best ones to use. You can take the challenges and approach them from another direction. That's the beauty of programming.

I would love to see what you come up with. Don't forget to drop a link in the comments when you're done. Think of what you can do more, like adding a reset button after a loss or winning or setting a different target like popping all values divisible by 5.

You may want to bookmark this article and check back for an update.

Source code available in its GitHub Repo