如何使用React構(gòu)建一個擲骰子的小游戲
這是一個用 React 構(gòu)建的小游戲應用,名為 Tenzies,目標是擲骰子,直到所有骰子的值相同。玩家可以“凍結(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 的庫,確保每個骰子有唯一標識符。
- 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])
當 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
:唯一標識符,使用 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,切換對應骰子的 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é)當前骰子值。
- 點擊按鈕:
– 若游戲未勝利,則重新擲未凍結(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 應用到按鈕的 style 屬性,調(diào)整按鈕的背景顏色。
onClick={props.hold}
- 定義按鈕的點擊事件處理函數(shù)。
- props.hold 是從父組件傳遞的函數(shù),當用戶點擊按鈕時會觸發(fā)這個函數(shù)。
- 通常,props.hold 用于切換 isHeld 狀態(tài),凍結(jié)或解凍當前骰子。
aria-pressed={props.isHeld}
- 用于無障礙支持。
- 指定按鈕的按下狀態(tài):true:表示當前骰子已被“按下”或凍結(jié)。
- false:表示當前骰子未被按下。幫助屏幕閱讀器用戶了解當前狀態(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
設置為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
設為彈性容器。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
(兩行,每行高度自適應內(nèi)容)。 - 列模板:
repeat(5, 1fr)
(5 列,每列寬度相等)。
- 行模板:
gap: 20px;
:網(wǎng)格單元之間的間距。
- 底部邊距:
margin-bottom: 40px;
。
8. 通用按鈕樣式
button { font-family: Karla, sans-serif; cursor: pointer; }
- 字體:
Karla
。 - 鼠標樣式:
cursor: pointer;
鼠標懸停時顯示手型圖標。
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
設置為1px
。- 使用
clip
和overflow
確保內(nèi)容不可見。 - 提供無障礙支持,例如為視覺障礙用戶提供額外的語音描述。
總結(jié)
這段 CSS 代碼:
- 設計布局:通過彈性盒布局和網(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應用的性能,本文介紹React?中hooks之?React.memo?和?useMemo用法總結(jié),感興趣的朋友一起看看吧2025-01-01React構(gòu)建簡潔強大可擴展的前端項目架構(gòu)
這篇文章主要為大家介紹了React構(gòu)建簡潔強大可擴展的前端項目架構(gòu)實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08React?Hook?Form?優(yōu)雅處理表單使用指南
這篇文章主要為大家介紹了React?Hook?Form?優(yōu)雅處理表單使用指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03