如何使用React構(gòu)建一個(gè)擲骰子的小游戲
這是一個(gè)用 React 構(gòu)建的小游戲應(yīng)用,名為 Tenzies,目標(biāo)是擲骰子,直到所有骰子的值相同。玩家可以“凍結(jié)”某些骰子,使它們?cè)诤罄m(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:自定義組件,用于顯示單個(gè)骰子。
- useState、useRef、useEffect:React hooks,用于管理狀態(tài)、DOM引用和副作用。
- nanoid:用于生成唯一 ID 的庫(kù),確保每個(gè)骰子有唯一標(biāo)識(shí)符。
- Confetti:庫(kù)組件,用于顯示游戲勝利的彩帶特效。
初始化狀態(tài)和引用
export default function App(){ const [dice, setDice] = useState(() => generateAllNewDice()) const buttonRef = useRef(null)
dice:管理 10 個(gè)骰子的數(shù)組狀態(tài)。初始值通過 generateAllNewDice
函數(shù)生成。
buttonRef:引用按鈕 DOM 元素,用于在勝利時(shí)自動(dòng)聚焦。
游戲勝利條件
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í),自動(dòng)讓“新游戲”按鈕獲得焦點(diǎn),提升可用性。
生成新的骰子數(shù)組
function generateAllNewDice() { return new Array(10).fill(0).map(() => ({ value: Math.ceil(Math.random() * 6), isHeld: false, id: nanoid() })) }
創(chuàng)建 10 個(gè)骰子對(duì)象,每個(gè)骰子有:
value
:1 到 6 的隨機(jī)數(shù)。isHeld
:初始為 false,表示未凍結(jié)。id
:唯一標(biāo)識(shí)符,使用 nanoid 生成。
Math.ceil(Math.random() * 6)
是 JavaScript 中生成隨機(jī)整數(shù)的常見方法之一。下面逐步解釋其工作原理:
Math.random()
- 作用:生成一個(gè) 0(包含)到 1(不包含) 的隨機(jī)浮點(diǎn)數(shù)。
- 范圍:
[0, 1)
。
例如:可能的結(jié)果是 0.2345
, 0.9876
, 0.0012
等。
Math.random() * 6
- 作用:將生成的隨機(jī)數(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()
作用:對(duì)數(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()
生成一個(gè)隨機(jī)數(shù),例如0.45
。 - 將該隨機(jī)數(shù)乘以
6
,結(jié)果是2.7
。 - 用
Math.ceil()
對(duì)結(jié)果向上取整,得到3
。
返回結(jié)果
最終的返回值是一個(gè) 1 到 6 的隨機(jī)整數(shù):
- 范圍:
[1, 6]
。 - 為何能覆蓋 1 到 6?
Math.random()
取值為0
時(shí),Math.random() * 6 = 0
,取整后為1
。Math.random()
接近1
時(shí),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()) } }
游戲未勝利時(shí):
對(duì)未凍結(jié)的骰子重新生成隨機(jī)值。
游戲勝利時(shí):
重置游戲,生成新的骰子數(shù)組。
切換凍結(jié)狀態(tài)
function hold(id) { setDice(oldDice => oldDice.map(die => die.id === id ? {...die, isHeld: !die.isHeld} : die )) }
根據(jù)點(diǎn)擊的骰子 id,切換對(duì)應(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),為每個(gè)骰子生成一個(gè) Die 組件。
- hold 函數(shù)傳遞給每個(gè)骰子,用于處理點(diǎn)擊事件。
渲染 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> )
- 勝利時(shí)顯示彩帶效果:通過 Confetti 組件。
- 無障礙支持:aria-live 提供游戲狀態(tài)描述。
- 骰子容器:動(dòng)態(tài)顯示所有 Die 組件。
- 按鈕文本:勝利時(shí)為“New Game”,否則為“Roll”。
代碼的核心邏輯總結(jié)
- 初始狀態(tài)下生成 10 個(gè)隨機(jī)骰子。
- 用戶點(diǎn)擊骰子時(shí),可凍結(jié)當(dāng)前骰子值。
- 點(diǎn)擊按鈕:
– 若游戲未勝利,則重新擲未凍結(jié)的骰子。
– 若游戲勝利,則重置游戲。 - 實(shí)現(xiàn)游戲勝利條件檢測(cè)和 UI 提示(如彩帶效果、自動(dòng)聚焦按鈕)。
2. Die.jsx
export default function Die(props) { const styles = { backgroundColor: props.isHeld ? "#59E391" : "white" }
styles 是一個(gè)動(dòng)態(tài)樣式對(duì)象,用于控制按鈕的背景顏色:
- 如果
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}
- 將動(dòng)態(tài)樣式對(duì)象 styles 應(yīng)用到按鈕的 style 屬性,調(diào)整按鈕的背景顏色。
onClick={props.hold}
- 定義按鈕的點(diǎn)擊事件處理函數(shù)。
- props.hold 是從父組件傳遞的函數(shù),當(dāng)用戶點(diǎn)擊按鈕時(shí)會(huì)觸發(fā)這個(gè)函數(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”}}
- 用于描述按鈕的詳細(xì)信息,提升無障礙性。
- 動(dòng)態(tài)生成描述,例如:Die with value 4, held:骰子的值為 4,已凍結(jié)。Die with value 6, not held:骰子的值為 6,未凍結(jié)。
- 代碼的核心功能 動(dòng)態(tài)顯示骰子的值。
- 提供交互功能,點(diǎn)擊按鈕會(huì)調(diào)用 props.hold,切換凍結(jié)狀態(tài)。通過動(dòng)態(tài)樣式反映骰子的狀態(tài)(凍結(jié)或未凍結(jié))。提供無障礙支持,便于屏幕閱讀器識(shí)別和描述按鈕狀態(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
的計(jì)算中。 - 效果:簡(jiǎn)化布局調(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
字體,不支持時(shí)回退到無襯線字體。 - 背景顏色:深藍(lán)色背景 (
#0B2434
)。 - 布局:
display: flex;
:將body
設(shè)為彈性容器。flex-direction: column;
:子元素垂直排列。justify-content: center;
:子元素在垂直方向上居中。align-items: center;
:子元素在水平方向上居中。
- 高度:
100vh
使body
占據(jù)整個(gè)視口高度。 - 內(nèi)邊距:
20px
,用于給內(nèi)容留出額外空間。
3. div#root
樣式
div#root { height: 100%; width: 100%; max-height: 400px; max-width: 400px; }
- 高度和寬度:默認(rèn)占據(jù)父容器的全部空間。
- 最大尺寸限制:
max-height
和max-width
分別限制為400px
。- 效果:即使父容器較大,
#root
的尺寸也不會(huì)超過 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
字體。
字體粗細(xì):普通粗細(xì) (font-weight: 400
)。
文本對(duì)齊:居中對(duì)齊 (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)懸停時(shí)顯示手型圖標(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;
大號(hào)字體。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)整。 - 背景顏色:深藍(lán)色 (
#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)容不可見。 - 提供無障礙支持,例如為視覺障礙用戶提供額外的語(yǔ)音描述。
總結(jié)
這段 CSS 代碼:
- 設(shè)計(jì)布局:通過彈性盒布局和網(wǎng)格布局組織內(nèi)容。
- 樣式統(tǒng)一性:使用動(dòng)態(tài)樣式、字體和交互效果。
- 無障礙支持:添加屏幕閱讀器友好的隱藏元素(
.sr-only
)。 - 視覺細(xì)節(jié):通過顏色、圓角、陰影和間距提升用戶體驗(yàn)。
到此這篇關(guān)于使用React構(gòu)建一個(gè)擲骰子的小游戲的文章就介紹到這了,更多相關(guān)React擲骰子的小游戲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React?中hooks之?React.memo?和?useMemo用法示例總結(jié)
React.memo是一個(gè)高階組件,用于優(yōu)化函數(shù)組件的性能,通過記憶組件渲染結(jié)果來避免不必要的重新渲染,合理使用React.memo和useMemo可以顯著提升React應(yīng)用的性能,本文介紹React?中hooks之?React.memo?和?useMemo用法總結(jié),感興趣的朋友一起看看吧2025-01-01React hooks如何清除定時(shí)器并驗(yàn)證效果
在React中,通過自定義Hook useTimeHook實(shí)現(xiàn)定時(shí)器的啟動(dòng)與清除,在App組件中使用Clock組件展示當(dāng)前時(shí)間,利用useEffect鉤子在組件掛載時(shí)啟動(dòng)定時(shí)器,同時(shí)確保組件卸載時(shí)清除定時(shí)器,避免內(nèi)存泄露,這種方式簡(jiǎn)化了狀態(tài)管理和副作用的處理2024-10-10React構(gòu)建簡(jiǎn)潔強(qiáng)大可擴(kuò)展的前端項(xiàng)目架構(gòu)
這篇文章主要為大家介紹了React構(gòu)建簡(jiǎn)潔強(qiáng)大可擴(kuò)展的前端項(xiàng)目架構(gòu)實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08React項(xiàng)目如何使用Element的方法步驟
本文主要介紹了React項(xiàng)目如何使用Element的方法步驟,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11React?Hook?Form?優(yōu)雅處理表單使用指南
這篇文章主要為大家介紹了React?Hook?Form?優(yōu)雅處理表單使用指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03