Build a Tenzies Game in React from SCRATCH!

Build a Tenzies Game in React from SCRATCH!

This is my first time turning a project into a blog, so let's get started!

It's a dice game and the idea is to roll the die that are all the same and click any one of the dice to hold it and roll the unheld die. Our goal is to hold the die of the same number and keep rolling the other die until we have held all the die of the same number, upon which we are greeted with a nice little confetti and our "Roll Dice" button changes to "Reset Game" button and when we click it, it will start again from the very beginning. LETS BUILD!

  1. Create an App component and import it into index.js. In the App component, render a <main> element Style everything depending on the design provided.

  2. Add the background color for the body and for the main container.

    Set up a nice enough height for the main component and add a border radius to make it look nicer.

  3. The next thing to be done is to render the 10 die components to the page. Let us first manually 10 hard-coded dies and provide a number between 1 and 6 for the value prop. We do this by first creating a container to hold the 10 instances

    of the Die component and using CSS Grid to lay them out evenly in 2 rows of 5 columns. Now for the separation between the dies, we can use the gap property.

    This looks pretty good except for the fact that it is all crammed at the top. It should be an easy fix by using flexbox on the die-face element (the parent) and using justify-content: center and align-items: center . After styling, it should look something like this:

  4. Now let's come to the fun part. We need a function that returns an array of 10 random numbers between 1-6 inclusive. We shall use the Math.random() object along with Math.ceil() (so that we do not ever select 0) and Math.round() so that we never come across any floating point numbers. The function should look something like this:

     export default function App() {
         function allNewDice() {
             const newDice = []
             for (let i = 0; i < 10; i++) {
                 newDice.push(Math.ceil(Math.random() * 6))
             }
             return newDice
         }
    
  5. Now we need a new state to hold our array of numbers.

     const [dice, setDice] = React.useState(allNewDice())
     const diceElements = dice.map(die => <Die value={die} />)
    
  6. The next thing to be added is a Roll button that when clicked will generate all 10 new die for us. We will be modifying for it to exempt certain "held" die from rolling.

     <button className="roll-dice" onClick={rollDice}>Roll</button>
    

    We will make a new rollDice function that gets triggered every time this button is clicked.

     function rollDice() {
         setDice(allNewDice())
     }
    

    What this function does is that it fires up that random function on each of the 10 dies and shuffles the numbers randomly. After styling the button, our page should look something like this:

  7. Now our next task is to hold the dice so that when we click "Roll", it does not change the dice that are held. To achieve that, we need to update the array of numbers in state to be an array of objects instead. The updated function looks something like this:

     function allNewDice() {
             const newDice = []
             for (let i = 0; i < 10; i++) {
                 newDice.push({
                     value: Math.ceil(Math.random() * 6), 
                     isHeld: false
                 })
             }
             return newDice
         }
    
  8. Now we will be adding some styling to the die that is held to visually discern it from others. We can do this by adding conditional styling to the Die component so that if it's held (isHeld === true), its background color changes to a light green.

    The following code block explains it.

     const styles = {
             backgroundColor: props.isHeld ? "#59E391" : "white"
         }
         return (
             <div className="die-face" style={styles}>
                 <h2 className="die-num">{props.value}</h2>
             </div>
         )
    
  9. Now our goal should be to update the holdDice function to flip the isHeld property on the object in the array that was clicked, based on the id prop passed into the function. The following function should take care of it:

         function holdDice(id) {
             setDice(oldDice => oldDice.map(die => {
                 return die.id === id ? 
                     {...die, isHeld: !die.isHeld} :
                     die
             }))
         }
    
  10. At this point, we now need to update the rollDice function to not just roll all new dice, but instead to look through the existing dice to NOT role any that are being held. The updated function looks something like this:

    function rollDice() {
            setDice(oldDice => oldDice.map(die => {
                return die.isHeld ? // If the die is held
                    die :           // Keep the same die, else    
                    generateNewDie()// Generate new die
            }))
        }
    
  11. We are so close to finishing building this! Our next task is to inform the user that he won whenever all the dies are of the same number. We will accomplish this by using a new state called tenzies. Refer to the code block below:

    React.useEffect(() => {
            const allHeld = dice.every(die => die.isHeld)
            const firstValue = dice[0].value
            const allSameValue = dice.every(die => die.value === firstValue)
            if (allHeld && allSameValue) {
                setTenzies(true)
                console.log("You won!")
            }
        }, [dice])
    

  12. Now for some finishing changes, when the user wins, we want the button to display "New Game" and show some cool confetti. For the confetti part, all we need to do is to import the confetti package and then conditionally render it when the user wins the game. It's a pretty simple 1 line fix!
    {tenzies ? "New Game" : "Roll"}. With that out of the way, let's test our little game!

  13. There is one more thing to fix before we finish this and that is to make sure that when the user presses on "New Game", all the dies are refreshed. The updated function should look something like this:

    function rollDice() {
            if(!tenzies) {
                setDice(oldDice => oldDice.map(die => {
                    return die.isHeld ? 
                        die :
                        generateNewDie()
                }))
            } else {
                setTenzies(false)
                setDice(allNewDice())
            }
        }
    

    And there we have it, our tenzies game is now complete. Thank you for hanging around. Follow me for more such blogs!

Did you find this article valuable?

Support Anubhav Adhikari by becoming a sponsor. Any amount is appreciated!