Solo Project 2
Capstone Project #1 - Tenzies
Step-by-Step Guide to Setting Up the Vite + React Project
Project Setup
(Project Name: react-scrimba-tenzies-app)

Step 1: Create the Vite + React Project
Open your terminal and navigate to the directory where you want to create the project.
Run the following command to create a Vite project with React:
npm create vite@latest react-scrimba-tenzies-app --template react
npm create vite@latest
: Initializes a new Vite project.react-scrimba-tenzies-app
: Specifies the project name.--template react
: Specifies the React template for Vite.
Navigate into the project directory:
cd react-scrimba-tenzies-app
Install dependencies:
npm install
Step 2: Modify the Project Structure
Inside the
src
folder, create a new file for theApp
component:src/App.jsx
Move the existing App component code into this file.
Update the
main.jsx
file to import and render theApp
component.
Step 3: Implement the App Component
Create a separate App component (
src/App.jsx
):import React from 'react'; function App() { return ( <main> {/* Content for Tenzies App will go here */} </main> ); } export default App;
Update
src/main.jsx
:import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <App /> </React.StrictMode> );
Step 4: Add Styling to the Project
Inside the
src
folder, create a CSS file for global styles:src/index.css
Add the following styles to
index.css
:body { margin: 0; padding: 20px; font-family: Arial, sans-serif; background-color: #0B2334; height: 100vh; display: flex; flex-direction: column; } div#root { height: 100%; width: 100%; max-height: 400px; max-width: 400px } main { background-color: #F5F5F5; margin: 0; mheight: 100%; display: flex; justify-content: center; align-items: center; border-radius: 5px; }
Import the CSS file into
main.jsx
:import './index.css';
Step 5: Test the Setup
Run the development server:
npm run dev
Open your browser and navigate to the URL provided by the terminal (usually
http://localhost:5173
).You should see a blank page with the
main
element styled with a white background, centered on a dark blue body.
Additional Notes
This setup serves as a starting point for the "Playing Tenzies" app.
You can now proceed to implement the game logic and additional components within the
App
component.
Next Steps
Plan the app layout: Determine what components you’ll need for the Tenzies game.
Set up state management: Use React's
useState
anduseEffect
hooks as necessary.Add interactivity and styles: Implement the core functionality and style the components.
Create a Die child component that takes a 'value' prop
It should render a button with that value displayed.
render 10 instances of the Die component
Provide a number between 1-6 for the value on each row.
Style the <main> element and <Die> component : -Create a container to hold the 10 instances of the Die component, and use CSS grid to lay them out evenly in 2 rows of 5 columns.
Use flexbox on main to center the dice container in the center of the page.
Step 1: Update the App Component
Create a separate App component (
src/App.jsx
):import React from 'react'; import Die from './Die'; function App() { const diceValues = [1, 2, 3, 4, 5, 6, 1, 2, 3, 4]; return ( <main> <div className="dice-container"> {diceValues.map((value, index) => ( <Die key={index} value={value} /> ))} </div> </main> ); } export default App;
Create the Die component (
src/Die.jsx
):import React from 'react'; function Die({ value }) { return ( <button className="die"> {value} </button> ); } export default Die;
Step 2: Add Styling to the Project
Inside the
src
folder, updateindex.css
to include styles for the layout and components:body { margin: 0; font-family: Arial, sans-serif; background-color: #0B2334; display: flex; justify-content: center; align-items: center; height: 100vh; } main { display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; } .dice-container { display: grid; grid-template-columns: repeat(5, 1fr); grid-gap: 10px; background-color: #F5F5F5; padding: 20px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .die { width: 50px; height: 50px; font-size: 20px; font-weight: bold; color: #333; background-color: #fff; border: 2px solid #ccc; border-radius: 8px; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: all 0.2s ease-in-out; } .die:hover { background-color: #ddd; transform: scale(1.1); }
Step 3: Test the Setup
Run the development server:
npm run dev
Open your browser and navigate to the URL provided by the terminal (usually
http://localhost:5173
).You should see 10 dice rendered in a grid layout, centered on the page, with values between 1-6.
Additional Notes
This setup creates a reusable
Die
component.You can now proceed to implement the game logic and interactions.
Next Steps
Add interactivity: Implement functionality to toggle dice states (e.g., clicked/unselected).
Enhance styles: Customize the appearance to match your desired theme.
Write a function (generateNewDice) that returns an array of 10 random numbers between 1-6 inclusive in App.jsx component.
Step 1: Update the App Component
Create a separate App component (
src/App.jsx
):import React from 'react'; import Die from './Die'; function generateNewDice() { return Array.from({ length: 10 }, () => Math.floor(Math.random() * 6) + 1); } function App() { const diceValues = generateNewDice(); // generate new dice return ( <main> <div className="dice-container"> {diceValues.map((value, index) => ( <Die key={index} value={value} /> ))} </div> </main> ); } export default App;
Step 2: Test the Setup
Run the development server:
npm run dev
Open your browser and navigate to the URL provided by the terminal (usually
http://localhost:5173
).You should see 10 dice rendered in a grid layout, centered on the page, with random values between 1-6.
Update the App.jsx
component to use the useState
hook for managing the array of 10 random dice values generated by the generateNewDice
function.
App.jsx
component to use the useState
hook for managing the array of 10 random dice values generated by the generateNewDice
function.Step 3: Update the App Component
Modify a App component (
src/App.jsx
):import React, { useState } from 'react'; import Die from './Die'; function generateNewDice() { return Array.from({ length: 10 }, () => Math.floor(Math.random() * 6) + 1); } function App() { const [diceValues, setDiceValues] = useState(generateNewDice());//use useState return ( <main> <div className="dice-container"> {diceValues.map((value, index) => ( <Die key={index} value={value} /> ))} </div> </main> ); } export default App;
Add the Roll Dice
button functionality to the App.jsx
component, including a rollDice
function to regenerate the dice values and updated button styles
Roll Dice
button functionality to the App.jsx
component, including a rollDice
function to regenerate the dice values and updated button stylesStep 1: Update the App Component
Modify a App component (
src/App.jsx
):import React, { useState } from 'react'; import Die from './Die'; function generateNewDice() { return Array.from({ length: 10 }, () => Math.floor(Math.random() * 6) + 1); } function App() { const [diceValues, setDiceValues] = useState(generateNewDice()); function rollDice() { setDiceValues(generateNewDice()); } return ( <main> <div className="dice-container"> {diceValues.map((value, index) => ( <Die key={index} value={value} /> ))} </div> <button className="roll-button" onClick={rollDice}>Roll Dice</button> </main> ); } export default App;
Step 2: Add Styling to the Project
Inside the
src
folder, updateindex.css
to include styles for the layout and components:body { margin: 0; font-family: Arial, sans-serif; background-color: #0B2334; display: flex; justify-content: center; align-items: center; height: 100vh; } main { display: flex; flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%; } .dice-container { display: grid; grid-template-columns: repeat(5, 1fr); grid-gap: 10px; background-color: #F5F5F5; padding: 20px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .die { width: 50px; height: 50px; font-size: 20px; font-weight: bold; color: #333; background-color: #fff; border: 2px solid #ccc; border-radius: 8px; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: all 0.2s ease-in-out; } .die:hover { background-color: #ddd; transform: scale(1.1); } .roll-button { margin-top: 20px; padding: 10px 20px; font-size: 18px; font-weight: bold; color: #fff; background-color: #4CAF50; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s ease; } .roll-button:hover { background-color: #45a049; }
Step 3: Test the Setup
Run the development server:
npm run dev
Open your browser and navigate to the URL provided by the terminal (usually
http://localhost:5173
).You should see 10 dice rendered in a grid layout, centered on the page, with random values between 1-6. Clicking the "Roll Dice" button generates a new set of random numbers.
Update the array of numbers in state to be an array of objects instead. Each object should look like: { value: , isHeld: false }.
Now diceValues is die object to iterate over to Die component.
The
Die
component now receives these as props : value and isHeld
Step 1: Implement the App Component
Create a separate App component (
src/App.jsx
):import React, { useState } from 'react'; import Die from './Die'; function generateNewDice() { return Array.from({ length: 10 }, () => ({ value: Math.floor(Math.random() * 6) + 1, isHeld: false })); } // generate dice object with props value and isHeld function App() { const [diceValues, setDiceValues] = useState(generateNewDice()); function rollDice() { setDiceValues(generateNewDice()); } return ( <main> <div className="dice-container"> {diceValues.map((die, index) => ( <Die key={index} value={die.value} isHeld={die.isHeld} /> ))} </div> <button className="roll-button" onClick={rollDice}>Roll Dice</button> </main> ); } export default App;
Update the Die component (
src/Die.jsx
):import React from 'react'; function Die({ value, isHeld }) { return ( <button className={`die ${isHeld ? 'held' : ''}`}> {value} </button> ); } export default Die;
Add conditional styling to the Die component so that if it's held (isHeld === true), its background color changes to a light green (#59E391)
The
Die
component now has conditional styling, changing its background color to light green (#59E391
) if theisHeld
property istrue
.
// Updated App.jsx
import React, { useState } from 'react';
import Die from './Die';
function generateNewDice() {
return Array.from({ length: 10 }, () => ({
value: Math.floor(Math.random() * 6) + 1,
isHeld: false })
);
}
function App() {
const [diceValues, setDiceValues] = useState(generateNewDice());
function rollDice() {
setDiceValues(generateNewDice());
}
return (
<main>
<div className="dice-container">
{diceValues.map((die, index) => (
<Die key={index} value={die.value} isHeld={die.isHeld} />
))}
</div>
<button className="roll-button" onClick={rollDice}>Roll Dice</button>
</main>
);
}
export default App; code
Step 2: Test the Setup
Run the development server:
npm run dev
Open your browser and navigate to the URL provided by the terminal (usually
http://localhost:5173
).You should see 10 dice rendered in a grid layout, centered on the page, with random values between 1-6. Clicking the "Roll Dice" button generates a new set of random dice objects. If a die is marked as held, its background color changes to light green (#59E391).
Create a function hold
that takes id
as a parameter at App.jsx parent component. Then, implement how to pass that function down to each instance of the Die component so when each one is clicked, it logs its own unique ID property.
hold
that takes id
as a parameter at App.jsx parent component. Then, implement how to pass that function down to each instance of the Die component so when each one is clicked, it logs its own unique ID property.The
hold
function has been added to theApp.jsx
component, and it logs the uniqueid
of each die when clicked. This function is passed as a prop to theDie
components, which call it on click.


// Updated App.jsx
import React, { useState } from 'react';
import Die from './Die';
function generateNewDice() {
return Array.from({ length: 10 }, (_, index) => ({
id: index,
value: Math.floor(Math.random() * 6) + 1,
isHeld: false
}));
}
function App() {
const [diceValues, setDiceValues] = useState(generateNewDice());
function rollDice() {
setDiceValues(generateNewDice());
}
function hold(id) {
console.log(`Die with ID ${id} clicked`);
}
return (
<main>
<div className="dice-container">
{diceValues.map((die) => (
<Die
key={die.id}
id={die.id}
value={die.value}
isHeld={die.isHeld}
hold={hold}
/>
))}
</div>
<button className="roll-button" onClick={rollDice}>Roll Dice</button>
</main>
);
}
export default App;
Update the hold
function to flip the isHeld
property on the object in the array that was clicked, based on the id
prop passed into the function.
hold
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
hold
function now toggles theisHeld
property for the clicked die, updating the state based on itsid
. Clicking a die will visually indicate its held state by changing its background color.
Step 1: Update the App Component
Modify a App component (
src/App.jsx
):
// Updated App.jsx
import React, { useState } from 'react';
import Die from './Die';
function generateNewDice() {
return Array.from({ length: 10 }, (_, index) => ({
id: index,
value: Math.floor(Math.random() * 6) + 1,
isHeld: false
}));
}
function App() {
const [diceValues, setDiceValues] = useState(generateNewDice());
function rollDice() {
setDiceValues(generateNewDice());
}
function hold(id) {
setDiceValues((prevDiceValues) =>
prevDiceValues.map((die) =>
die.id === id ? { ...die, isHeld: !die.isHeld } : die
)
);
} //isHeld is now toggled
return (
<main>
<div className="dice-container">
{diceValues.map((die) => (
<Die
key={die.id}
id={die.id}
value={die.value}
isHeld={die.isHeld}
hold={hold}
/>
))}
</div>
<button className="roll-button" onClick={rollDice}>Roll Dice</button>
</main>
);
}
export default App;
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
.
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
rollDice
function has been updated to preserve theisHeld
state for dice that are held, re-rolling only those that are not.
Step 1: Update the App Component
Modify a App component (
src/App.jsx
):
// Updated App.jsx
import React, { useState } from 'react';
import Die from './Die';
function generateNewDice() {
return Array.from({ length: 10 }, (_, index) => ({
id: index,
value: Math.floor(Math.random() * 6) + 1,
isHeld: false
}));
}
function App() {
const [diceValues, setDiceValues] = useState(generateNewDice());
function rollDice() {
setDiceValues((prevDiceValues) =>
prevDiceValues.map((die) =>
die.isHeld ? die : { ...die, value: Math.floor(Math.random() * 6) + 1 }
)
);
}
function hold(id) {
setDiceValues((prevDiceValues) =>
prevDiceValues.map((die) =>
die.id === id ? { ...die, isHeld: !die.isHeld } : die
)
);
}
return (
<main>
<div className="dice-container">
{diceValues.map((die) => (
<Die
key={die.id}
id={die.id}
value={die.value}
isHeld={die.isHeld}
hold={hold}
/>
))}
</div>
<button className="roll-button" onClick={rollDice}>Roll Dice</button>
</main>
);
}
export default App;
Step 2. Add Instructions & Styles
// App.jsp code snippet
<main>
<h1 className="title">Play Tenzies</h1>
<p className="instructions">Roll until all dice are the same.
Click each die to freeze it at its current value between rolls.</p>
...
</main>
// App.css snippet
.instructions {
font-family: 'Inter', sans-serif;
font-weight: 400;
margin-top: 0;
text-align: center;
}
Step 3: Test the Setup
Run the development server:
npm run dev
Open your browser and navigate to the URL provided by the terminal (usually
http://localhost:5173
).You should see 10 dice rendered in a grid layout, centered on the page. Clicking any die toggles its
isHeld
state, and clicking the "Roll Dice" button only re-rolls dice that are not held.
We want to indicate to the user that the game is over if (1) all the dice are held, and (2) all the dice have the same value. We need to save a gameWon
value in state.
gameWon
value in state.The game now checks if all dice are held and have the same value, marking the game as won with a
gameWon
state.When the game is won, a "You won!" message appears, and the button changes to "New Game."
Step 1: Update the App Component
Modify a App component (
src/App.jsx
):
With useState & useEffect : The game now checks if all dice are held and have the same value, marking the game as won with a
gameWon
state. When the game is won, a "You won!" message appears, and the button changes to "New Game."Without useState & useEffect : The
App.jsx
has been updated to indicate the game is over if all dice are held and have the same value, without usinguseState
oruseEffect
for the game over logic.
// App.jsx with useState and useEffect
import React, { useState, useEffect } from 'react';
import Die from './Die';
function generateNewDice() {
return Array.from({ length: 10 }, (_, index) => ({
id: index,
value: Math.floor(Math.random() * 6) + 1,
isHeld: false
}));
}
function App() {
const [diceValues, setDiceValues] = useState(generateNewDice());
const [gameWon, setGameWon] = useState(false); // ameWon state
useEffect(() => {
const allHeld = diceValues.every((die) => die.isHeld);
const allSameValue = diceValues.every((die) => die.value === diceValues[0].value);
if (allHeld && allSameValue) {
setGameWon(true);
}
}, [diceValues]);
function rollDice() {
if (!gameWon) { // not gameWon
setDiceValues((prevDiceValues) =>
prevDiceValues.map((die) =>
die.isHeld ? die : { ...die, value: Math.floor(Math.random() * 6) + 1 }
)
);
}
}
function hold(id) {
setDiceValues((prevDiceValues) =>
prevDiceValues.map((die) =>
die.id === id ? { ...die, isHeld: !die.isHeld } : die
)
);
}
return (
<main>
{gameWon && <h1 className="blinking-text game-won">You won!</h1>}
<div className="dice-container">
{diceValues.map((die) => (
<Die
key={die.id}
id={die.id}
value={die.value}
isHeld={die.isHeld}
hold={hold}
/>
))}
</div>
<button className="roll-button" onClick={rollDice}>
{gameWon ? "New Game" : "Roll Dice"}
</button>
</main>
);
}
export default App;
Step 2: Add Styling to the Project
Inside the
src
folder, updateindex.css
to include styles for the layout and components:
// Update App.css
.game-won {
font-size: 24px;
color: #fff;
margin-bottom: 20px;
}
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.blinking-text {
text-align: center;
margin-top: 20%;
font-size: 2.8rem;
font-weight: bold;
color: green;
animation: blink 1s infinite;
}
Add a feature to make the confetti drop when the game is won!
Step 1: Update the App Component
Install
react-confetti
npm install react-confetti
Update the App Component (
src/App.jsx
):import React, { useState } from 'react'; import Die from './Die'; import Confetti from 'react-confetti'; //import react-confetti package function generateNewDice() { return Array.from({ length: 10 }, (_, index) => ({ id: index, value: Math.floor(Math.random() * 6) + 1, isHeld: false })); } function App() { const [diceValues, setDiceValues] = useState(generateNewDice()); function rollDice() { setDiceValues((prevDiceValues) => prevDiceValues.map((die) => die.isHeld ? die : { ...die, value: Math.floor(Math.random() * 6) + 1 } ) ); } function hold(id) { setDiceValues((prevDiceValues) => prevDiceValues.map((die) => die.id === id ? { ...die, isHeld: !die.isHeld } : die ) ); } // Check for game over condition without using useState or useEffect const allHeld = diceValues.every((die) => die.isHeld); const allSameValue = diceValues.every((die) => die.value === diceValues[0].value); const gameWon = allHeld && allSameValue; return ( <main> {gameWon && <Confetti />} {gameWon && <h1 className="game-won">You won!</h1>} <div className="dice-container"> {diceValues.map((die) => ( <Die key={die.id} id={die.id} value={die.value} isHeld={die.isHeld} hold={hold} /> ))} </div> <button className="roll-button" onClick={() => setDiceValues(generateNewDice())}> {gameWon ? "New Game" : "Roll Dice"} </button> </main> ); } export default App;
Step 2: Test the Setup
Run the development server:
npm run dev
Open your browser and navigate to the URL provided by the terminal (usually
http://localhost:5173
).You should see 10 dice rendered in a grid layout, centered on the page. If all the dice are held and have the same value, a "You won!" message is displayed, a confetti animation drops, and the "Roll Dice" button changes to "New Game."
Allow the user to play a new game when the button is clicked.
Step 1: Implement the App Component
The "Roll Dice" button now allows the user to start a new game when the game is won, by generating a fresh set of dice.
Update the App Component (
src/App.jsx
):import React, { useState } from 'react'; import Die from './Die'; import Confetti from 'react-confetti'; function generateNewDice() { return Array.from({ length: 10 }, (_, index) => ({ id: index, value: Math.floor(Math.random() * 6) + 1, isHeld: false })); } function App() { const [diceValues, setDiceValues] = useState(()=>generateNewDice()); function rollDice() { if (gameWon) { //add conditional setDiceValues(generateNewDice()); } else { setDiceValues((prevDiceValues) => prevDiceValues.map((die) => die.isHeld ? die : { ...die, value: Math.floor(Math.random() * 6) + 1 } ) ); } } function hold(id) { setDiceValues((prevDiceValues) => prevDiceValues.map((die) => die.id === id ? { ...die, isHeld: !die.isHeld } : die ) ); } // Check for game over condition without using useState or useEffect const allHeld = diceValues.every((die) => die.isHeld); const allSameValue = diceValues.every((die) => die.value === diceValues[0].value); const gameWon = allHeld && allSameValue; return ( <main> {gameWon && <Confetti />} {gameWon && <h1 className="game-won">You won!</h1>} <div className="dice-container"> {diceValues.map((die) => ( <Die key={die.id} id={die.id} value={die.value} isHeld={die.isHeld} hold={hold} /> ))} </div> <button className="roll-button" onClick={rollDice}> {gameWon ? "New Game" : "Roll Dice"} </button> </main> ); } export default App;
Step 2: Test the Setup
Run the development server:
npm run dev
Open your browser and navigate to the URL provided by the terminal (usually
http://localhost:5173
).You should see 10 dice rendered in a grid layout, centered on the page. If all the dice are held and have the same value, a "You won!" message is displayed, a confetti animation drops, and the "Roll Dice" button changes to "New Game."
There is an issue on App.jsx that allow holding different dice number combination . The hold function should keep tract not only same "isHeld" flag but also should not allow selection and hold for different dice number. Only the same dice numbers are kept and tracked. Modify the code.
The
hold
function has been updated to ensure that only dice with the same value as the first held die can be held.
Update the hold function in App Component (
src/App.jsx
):import React, { useState } from 'react'; import Die from './Die'; import Confetti from 'react-confetti'; function generateNewDice() { return Array.from({ length: 10 }, (_, index) => ({ id: index, value: Math.floor(Math.random() * 6) + 1, isHeld: false })); } function App() { const [diceValues, setDiceValues] = useState(generateNewDice()); function rollDice() { if (gameWon) { setDiceValues(generateNewDice()); } else { setDiceValues((prevDiceValues) => prevDiceValues.map((die) => die.isHeld ? die : { ...die, value: Math.floor(Math.random() * 6) + 1 } ) ); } } //The hold function has been updated to ensure that only dice //with the same value as the first held die can be held. function hold(id) { //updated - const firstHeldDie = diceValues.find((die) => die.isHeld); setDiceValues((prevDiceValues) => prevDiceValues.map((die) => { if (die.id === id) { if (!firstHeldDie || firstHeldDie.value === die.value) { return { ...die, isHeld: !die.isHeld }; } } return die; }) ); } // Check for game over condition without using useState or useEffect const allHeld = diceValues.every((die) => die.isHeld); const allSameValue = diceValues.every((die) => die.value === diceValues[0].value); const gameWon = allHeld && allSameValue; return ( <main> {gameWon && <Confetti />} {gameWon && <h1 className="game-won">You won!</h1>} <div className="dice-container"> {diceValues.map((die) => ( <Die key={die.id} id={die.id} value={die.value} isHeld={die.isHeld} hold={hold} /> ))} </div> <button className="roll-button" onClick={rollDice}> {gameWon ? "New Game" : "Roll Dice"} </button> </main> ); } export default App;
Result (Outro)

Challenge: Add Timer Feature to Tenzie Game
Add the timer feature to the game. It updates every second while the game is active and resets when a new game begins. The timer is displayed bellow the "Roll Dice" button.
The timer functionality ensures it stops when the game ends and resumes when a new game starts.
The program updates are needed in the function rollDice() conditioned on gameWon . It needs useEffect to handle the game timer.
// Some code
import React, { useState, useEffect } from 'react'
import Die from './Die'
import Confetti from 'react-confetti'
import './App.css'
function generateNewDice() {
return Array.from({ length: 10 }, (_, index) => ({
id: index,
value: Math.floor(Math.random() * 6) + 1,
isHeld: false,
}))
}
function AppTenzies() {
const [diceValues, setDiceValues] = useState(() => generateNewDice())
const [timer, setTimer] = useState(0)
const [isGameActive, setIsGameActive] = useState(false)
// Check for game over condition without using useState or useEffect
const allHeld = diceValues.every((die) => die.isHeld)
const allSameValue = diceValues.every(
(die) => die.value === diceValues[0].value,
)
const gameWon = allHeld && allSameValue
useEffect(() => {
let interval
if (isGameActive && !gameWon) {
interval = setInterval(() => {
setTimer((prevTimer) => prevTimer + 1)
}, 1000)
}
return () => clearInterval(interval)
}, [isGameActive, gameWon])
function rollDice() {
if (gameWon) {
setDiceValues(generateNewDice())
setTimer(0)
setIsGameActive(false)
} else {
setIsGameActive(true)
setDiceValues((prevDiceValues) =>
prevDiceValues.map((die) =>
die.isHeld
? die
: { ...die, value: Math.floor(Math.random() * 6) + 1 },
),
)
}
}
function hold(id) {
const firstHeldDie = diceValues.find((die) => die.isHeld)
setDiceValues((prevDiceValues) =>
prevDiceValues.map((die) => {
if (die.id === id) {
if (!firstHeldDie || firstHeldDie.value === die.value) {
return { ...die, isHeld: !die.isHeld }
}
}
return die
}),
)
}
return (
<main>
{gameWon && <Confetti />}
{gameWon && <h1 className="blinking-text game-won">You won!</h1>}
<h1 className="title">Play Tenzies</h1>
<p className="instructions">
Roll until all dice are the same. Click each die to freeze it at its
current value between rolls.
</p>
<div className="dice-container">
{diceValues.map((die) => (
<Die
key={die.id}
id={die.id}
value={die.value}
isHeld={die.isHeld}
hold={hold}
/>
))}
</div>
<button className="roll-button" onClick={rollDice}>
{gameWon ? 'New Game' : 'Roll Dice'}
</button>
<p className="timer">Time: {timer} seconds</p>
</main>
)
}
export default AppTenzies
Code Explanation
Purpose of
useEffect
:This
useEffect
sets up a side effect to handle the game timer.It ensures that the timer starts or continues only when the game is active and stops when the game ends.
Dependency Array:
[isGameActive, gameWon]
is the dependency array. The effect re-runs whenever:The game becomes active (
isGameActive
changes totrue
).The game is won (
gameWon
changes totrue
).
Logic Inside
useEffect
:
If
isGameActive
istrue
andgameWon
isfalse
:A
setInterval
is created that updates thetimer
state every second (1000ms
).
When the game ends (either
isGameActive
becomesfalse
orgameWon
becomestrue
), the interval is cleared, stopping the timer.
setInterval
and Cleanup:
setInterval
: Creates a repeating task that increments the timer by 1 second (setTimer((prevTimer) => prevTimer + 1)
).Cleanup Function:
return () => clearInterval(interval);
Ensures that the interval is cleared whenever the effect is re-executed or the component unmounts. This prevents multiple intervals from running simultaneously, which could cause memory leaks or unexpected behavior.
Why Include
gameWon
in the Dependency Array?
When the game is won (
gameWon
becomestrue
), the timer must stop immediately. IncludinggameWon
ensures the effect re-runs and clears the interval as soon as the game is over.
How It Works During the Game:
When the game starts (
isGameActive
becomestrue
), the timer begins to increment every second.When the game ends (either manually by winning the game or resetting the game), the interval is cleared to stop the timer.
Clicking "New Game" resets the timer to
0
and reactivates the timer with a fresh interval.
Last updated