Master React by Building Popsaga - A Simple JavaScript Game in 30 minutes
Samson Andrew
Posted on July 27, 2021
Introduction
If you are looking for a simple project to test your React skills, you have just found a great article for that.
I'll keep this article short and simple!
What are we building?
Link to Source code on GitHub available at the end of this article
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
Posted on July 27, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024
November 27, 2024