DEV Community

Cover image for Master React by Building Popsaga - A Simple JavaScript Game in 30 minutes
Samson Andrew
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.

I'll keep this article short and simple!

What are we building?

localhost_3000_

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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 );
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)