如何使用React構(gòu)建一個擲骰子的小游戲
這是一個用 React 構(gòu)建的小游戲應(yīng)用,名為 Tenzies,目標(biāo)是擲骰子,直到所有骰子的值相同。玩家可以“凍結(jié)”某些骰子,使它們在后續(xù)擲骰中保持不變。

1. App.jsx
import Die from "../public/components/Die"
import { useState, useRef, useEffect } from "react"
import { nanoid } from "nanoid"
import Confetti from "react-confetti"- Die:自定義組件,用于顯示單個骰子。
- useState、useRef、useEffect:React hooks,用于管理狀態(tài)、DOM引用和副作用。
- nanoid:用于生成唯一 ID 的庫,確保每個骰子有唯一標(biāo)識符。
- Confetti:庫組件,用于顯示游戲勝利的彩帶特效。
初始化狀態(tài)和引用
export default function App(){
const [dice, setDice] = useState(() => generateAllNewDice())
const buttonRef = useRef(null)dice:管理 10 個骰子的數(shù)組狀態(tài)。初始值通過 generateAllNewDice 函數(shù)生成。
buttonRef:引用按鈕 DOM 元素,用于在勝利時自動聚焦。
游戲勝利條件
const gameWon = dice.every(die => die.isHeld) && dice.every(die => die.value === dice[0].value)
游戲勝利的條件:
- 所有骰子的 isHeld 屬性為 true(即凍結(jié))。
- 所有骰子的值相同。
處理副作用
useEffect(() => {
if (gameWon) {
buttonRef.current.focus()
}
}, [gameWon])當(dāng) gameWon 為 true 時,自動讓“新游戲”按鈕獲得焦點,提升可用性。
生成新的骰子數(shù)組
function generateAllNewDice() {
return new Array(10).fill(0).map(() => ({
value: Math.ceil(Math.random() * 6),
isHeld: false,
id: nanoid()
}))
}創(chuàng)建 10 個骰子對象,每個骰子有:
value:1 到 6 的隨機數(shù)。isHeld:初始為 false,表示未凍結(jié)。id:唯一標(biāo)識符,使用 nanoid 生成。
Math.ceil(Math.random() * 6) 是 JavaScript 中生成隨機整數(shù)的常見方法之一。下面逐步解釋其工作原理:
Math.random()
- 作用:生成一個 0(包含)到 1(不包含) 的隨機浮點數(shù)。
- 范圍:
[0, 1)。
例如:可能的結(jié)果是 0.2345, 0.9876, 0.0012 等。
Math.random() * 6
- 作用:將生成的隨機數(shù)放大至 0 到 6(不包含 6) 的范圍。
- 范圍:
[0, 6)。
示例: 如果 Math.random() 返回 0.2,則 0.2 * 6 = 1.2。
如果 Math.random() 返回 0.9,則 0.9 * 6 = 5.4。
Math.ceil()
作用:對數(shù)字向上取整,返回大于等于該數(shù)的最小整數(shù)。
- 例如:
Math.ceil(1.2)返回2。 Math.ceil(5.4)返回6。Math.ceil(0)返回0。
綜合步驟
Math.ceil(Math.random() * 6) 的完整過程:
- 調(diào)用
Math.random()生成一個隨機數(shù),例如0.45。 - 將該隨機數(shù)乘以
6,結(jié)果是2.7。 - 用
Math.ceil()對結(jié)果向上取整,得到3。
返回結(jié)果
最終的返回值是一個 1 到 6 的隨機整數(shù):
- 范圍:
[1, 6]。 - 為何能覆蓋 1 到 6?
Math.random()取值為0時,Math.random() * 6 = 0,取整后為1。Math.random()接近1時,Math.random() * 6接近6,取整后為6。
擲骰子邏輯
function rollDice() {
if (!gameWon) {
setDice(oldDice => oldDice.map(die =>
die.isHeld ?
die :
{...die, value: Math.ceil(Math.random() * 6)}
))
} else {
setDice(generateAllNewDice())
}
}游戲未勝利時:
對未凍結(jié)的骰子重新生成隨機值。
游戲勝利時:
重置游戲,生成新的骰子數(shù)組。
切換凍結(jié)狀態(tài)
function hold(id) {
setDice(oldDice => oldDice.map(die =>
die.id === id ?
{...die, isHeld: !die.isHeld} :
die
))
}根據(jù)點擊的骰子 id,切換對應(yīng)骰子的 isHeld 狀態(tài)。
顯示骰子
const diceElements = dice.map(dieObj =>
(<Die
key={dieObj.id}
value={dieObj.value}
isHeld={dieObj.isHeld}
hold={() => hold(dieObj.id)}
/>)
)- 使用 map 遍歷 dice 狀態(tài),為每個骰子生成一個 Die 組件。
- hold 函數(shù)傳遞給每個骰子,用于處理點擊事件。
渲染 UI
return (
<main>
{gameWon && <Confetti />}
<div aria-live="polite" className="sr-only">
{gameWon && <p>Congratulations! You won! Press "New Game" to start again.</p>}
</div>
<h1 className="title">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">
{diceElements}
</div>
<button ref={buttonRef} className="roll-dice" onClick={rollDice}>
{gameWon ? "New Game" : "Roll"}
</button>
</main>
)- 勝利時顯示彩帶效果:通過 Confetti 組件。
- 無障礙支持:aria-live 提供游戲狀態(tài)描述。
- 骰子容器:動態(tài)顯示所有 Die 組件。
- 按鈕文本:勝利時為“New Game”,否則為“Roll”。
代碼的核心邏輯總結(jié)
- 初始狀態(tài)下生成 10 個隨機骰子。
- 用戶點擊骰子時,可凍結(jié)當(dāng)前骰子值。
- 點擊按鈕:
– 若游戲未勝利,則重新擲未凍結(jié)的骰子。
– 若游戲勝利,則重置游戲。 - 實現(xiàn)游戲勝利條件檢測和 UI 提示(如彩帶效果、自動聚焦按鈕)。
2. Die.jsx
export default function Die(props) {
const styles = {
backgroundColor: props.isHeld ? "#59E391" : "white"
}styles 是一個動態(tài)樣式對象,用于控制按鈕的背景顏色:
- 如果
props.isHeld為 true(表示該骰子被凍結(jié)),背景顏色為綠色 (#59E391)。 - 如果
props.isHeld為 false(表示未凍結(jié)),背景顏色為白色。
樣式切換使用戶能夠直觀地看到凍結(jié)狀態(tài)。
return (
<button
style={styles}
onClick={props.hold}
aria-pressed={props.isHeld}
aria-label={`Die with value ${props.value},
${props.isHeld ? "held" : "not held"}`}
>
{props.value}
</button>
)style={styles}
- 將動態(tài)樣式對象 styles 應(yīng)用到按鈕的 style 屬性,調(diào)整按鈕的背景顏色。
onClick={props.hold}
- 定義按鈕的點擊事件處理函數(shù)。
- props.hold 是從父組件傳遞的函數(shù),當(dāng)用戶點擊按鈕時會觸發(fā)這個函數(shù)。
- 通常,props.hold 用于切換 isHeld 狀態(tài),凍結(jié)或解凍當(dāng)前骰子。
aria-pressed={props.isHeld}
- 用于無障礙支持。
- 指定按鈕的按下狀態(tài):true:表示當(dāng)前骰子已被“按下”或凍結(jié)。
- false:表示當(dāng)前骰子未被按下。幫助屏幕閱讀器用戶了解當(dāng)前狀態(tài)。
aria-label={Die with value ${props.value}, ${props.isHeld ? “held” : “not held”}}
- 用于描述按鈕的詳細信息,提升無障礙性。
- 動態(tài)生成描述,例如:Die with value 4, held:骰子的值為 4,已凍結(jié)。Die with value 6, not held:骰子的值為 6,未凍結(jié)。
- 代碼的核心功能 動態(tài)顯示骰子的值。
- 提供交互功能,點擊按鈕會調(diào)用 props.hold,切換凍結(jié)狀態(tài)。通過動態(tài)樣式反映骰子的狀態(tài)(凍結(jié)或未凍結(jié))。提供無障礙支持,便于屏幕閱讀器識別和描述按鈕狀態(tài)。
3. index.css
1. 通用選擇器 *
* {
box-sizing: border-box;
}- 作用:將所有元素的
box-sizing設(shè)置為border-box。 box-sizing: border-box;:- 包括內(nèi)容、內(nèi)邊距 (
padding) 和邊框 (border) 的寬度和高度在width和height的計算中。 - 效果:簡化布局調(diào)整,避免意外的尺寸增大。
- 包括內(nèi)容、內(nèi)邊距 (
2. body 樣式
body {
font-family: Karla, sans-serif;
margin: 0;
background-color: #0B2434;
padding: 20px;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}- 字體:
font-family: Karla, sans-serif;使用Karla字體,不支持時回退到無襯線字體。 - 背景顏色:深藍色背景 (
#0B2434)。 - 布局:
display: flex;:將body設(shè)為彈性容器。flex-direction: column;:子元素垂直排列。justify-content: center;:子元素在垂直方向上居中。align-items: center;:子元素在水平方向上居中。
- 高度:
100vh使body占據(jù)整個視口高度。 - 內(nèi)邊距:
20px,用于給內(nèi)容留出額外空間。
3. div#root 樣式
div#root {
height: 100%;
width: 100%;
max-height: 400px;
max-width: 400px;
}- 高度和寬度:默認占據(jù)父容器的全部空間。
- 最大尺寸限制:
max-height和max-width分別限制為400px。- 效果:即使父容器較大,
#root的尺寸也不會超過 400 像素。
- 效果:即使父容器較大,
4. main 樣式
main {
background-color: #F5F5F5;
height: 100%;
border-radius: 5px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
}- 背景顏色:淺灰色 (
#F5F5F5)。 - 圓角:
border-radius: 5px;使容器的邊角圓滑。 - 布局: 彈性布局,子元素垂直排列。
justify-content: space-evenly;:在主軸上均勻分布子元素,間隔相等。align-items: center;:子元素在交叉軸(水平方向)上居中。
5. .title 樣式
.title {
font-size: 40px;
margin: 0;
}字體大小:40px。
邊距:margin: 0; 去除外邊距。
6. .instructions 樣式
.instructions {
font-family: 'Inter', sans-serif;
font-weight: 400;
margin-top: 0;
text-align: center;
}字體:優(yōu)先使用 Inter 字體。
字體粗細:普通粗細 (font-weight: 400)。
文本對齊:居中對齊 (text-align: center)。
7. .dice-container 樣式
.dice-container {
display: grid;
grid-template: auto auto / repeat(5, 1fr);
gap: 20px;
margin-bottom: 40px;
}- 布局:CSS 網(wǎng)格布局。
grid-template:- 行模板:
auto auto(兩行,每行高度自適應(yīng)內(nèi)容)。 - 列模板:
repeat(5, 1fr)(5 列,每列寬度相等)。
- 行模板:
gap: 20px;:網(wǎng)格單元之間的間距。
- 底部邊距:
margin-bottom: 40px;。
8. 通用按鈕樣式
button {
font-family: Karla, sans-serif;
cursor: pointer;
}- 字體:
Karla。 - 鼠標(biāo)樣式:
cursor: pointer;鼠標(biāo)懸停時顯示手型圖標(biāo)。
9. .dice-container button 樣式
.dice-container button {
height: 50px;
width: 50px;
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.15);
border-radius: 10px;
border: none;
background-color: white;
font-size: 1.75rem;
font-weight: bold;
}- 按鈕尺寸:固定寬高
50px。 - 陰影:
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.15);添加輕微的陰影效果。 - 圓角:
border-radius: 10px;。 - 無邊框:
border: none;。 - 背景顏色:白色。
- 字體樣式:
font-size: 1.75rem;大號字體。font-weight: bold;加粗。
10. .roll-dice 按鈕樣式
button.roll-dice {
height: 50px;
white-space: nowrap;
width: auto;
padding: 6px 21px;
border: none;
border-radius: 6px;
background-color: #5035FF;
color: white;
font-size: 1.2rem;
}- 尺寸:高度固定為
50px,寬度根據(jù)內(nèi)容調(diào)整。 - 背景顏色:深藍色 (
#5035FF)。 - 字體顏色:白色。
- 內(nèi)邊距:
6px(垂直)和21px(水平)。無邊框,并帶有圓角。
11. .sr-only 樣式
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}- 作用:隱藏元素,但保留給屏幕閱讀器使用。
- 關(guān)鍵屬性:
position: absolute;:從文檔流中移除。width和height設(shè)置為1px。- 使用
clip和overflow確保內(nèi)容不可見。 - 提供無障礙支持,例如為視覺障礙用戶提供額外的語音描述。
總結(jié)
這段 CSS 代碼:
- 設(shè)計布局:通過彈性盒布局和網(wǎng)格布局組織內(nèi)容。
- 樣式統(tǒng)一性:使用動態(tài)樣式、字體和交互效果。
- 無障礙支持:添加屏幕閱讀器友好的隱藏元素(
.sr-only)。 - 視覺細節(jié):通過顏色、圓角、陰影和間距提升用戶體驗。
到此這篇關(guān)于使用React構(gòu)建一個擲骰子的小游戲的文章就介紹到這了,更多相關(guān)React擲骰子的小游戲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React?中hooks之?React.memo?和?useMemo用法示例總結(jié)
React.memo是一個高階組件,用于優(yōu)化函數(shù)組件的性能,通過記憶組件渲染結(jié)果來避免不必要的重新渲染,合理使用React.memo和useMemo可以顯著提升React應(yīng)用的性能,本文介紹React?中hooks之?React.memo?和?useMemo用法總結(jié),感興趣的朋友一起看看吧2025-01-01
React構(gòu)建簡潔強大可擴展的前端項目架構(gòu)
這篇文章主要為大家介紹了React構(gòu)建簡潔強大可擴展的前端項目架構(gòu)實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08
React?Hook?Form?優(yōu)雅處理表單使用指南
這篇文章主要為大家介紹了React?Hook?Form?優(yōu)雅處理表單使用指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03

