使用React构建一个掷骰子的小游戏

这是一个用 React 构建的小游戏应用,名为 Tenzies,目标是掷骰子,直到所有骰子的值相同。玩家可以“冻结”某些骰子,使它们在后续掷骰中保持不变。

在这里插入图片描述

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,用于管理状态、DOM引用和副作用。
  • nanoid:用于生成唯一 ID 的库,确保每个骰子有唯一标识符。
  • Confetti:库组件,用于显示游戏胜利的彩带特效。

初始化状态和引用

export default function App(){const [dice, setDice] = useState(() => generateAllNewDice())const buttonRef = useRef(null)

dice:管理 10 个骰子的数组状态。初始值通过 generateAllNewDice 函数生成。
buttonRef:引用按钮 DOM 元素,用于在胜利时自动聚焦。

游戏胜利条件

const gameWon = dice.every(die => die.isHeld) && dice.every(die => die.value === dice[0].value)

游戏胜利的条件:

  • 所有骰子的 isHeld 属性为 true(即冻结)。
  • 所有骰子的值相同。

处理副作用

useEffect(() => {if (gameWon) {buttonRef.current.focus()}
}, [gameWon])

gameWontrue 时,自动让“新游戏”按钮获得焦点,提升可用性。

生成新的骰子数组

function generateAllNewDice() {return new Array(10).fill(0).map(() => ({value: Math.ceil(Math.random() * 6),isHeld: false,id: nanoid()}))
}

创建 10 个骰子对象,每个骰子有:

  • value:1 到 6 的随机数。
  • isHeld:初始为 false,表示未冻结。
  • id:唯一标识符,使用 nanoid 生成。

Math.ceil(Math.random() * 6) 是 JavaScript 中生成随机整数的常见方法之一。下面逐步解释其工作原理:


Math.random()

  • 作用:生成一个 0(包含)到 1(不包含) 的随机浮点数。
  • 范围[0, 1)
    • 例如:可能的结果是 0.2345, 0.9876, 0.0012 等。

Math.random() * 6

  • 作用:将生成的随机数放大至 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()

  • 作用:对数字向上取整,返回大于等于该数的最小整数。
  • 例如:
    • Math.ceil(1.2) 返回 2
    • Math.ceil(5.4) 返回 6
    • Math.ceil(0) 返回 0

综合步骤

Math.ceil(Math.random() * 6) 的完整过程:

  1. 调用 Math.random() 生成一个随机数,例如 0.45
  2. 将该随机数乘以 6,结果是 2.7
  3. Math.ceil() 对结果向上取整,得到 3

返回结果

最终的返回值是一个 1 到 6 的随机整数

  • 范围[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())}
}

游戏未胜利时:

  • 对未冻结的骰子重新生成随机值。

游戏胜利时:

  • 重置游戏,生成新的骰子数组。

切换冻结状态

function hold(id) {setDice(oldDice => oldDice.map(die =>die.id === id ?{...die, isHeld: !die.isHeld} :die))
}

根据点击的骰子 id,切换对应骰子的 isHeld 状态。

显示骰子

const diceElements = dice.map(dieObj => (<Die key={dieObj.id} value={dieObj.value} isHeld={dieObj.isHeld}hold={() => hold(dieObj.id)}/>)
)
  • 使用 map 遍历 dice 状态,为每个骰子生成一个 Die 组件。
  • hold 函数传递给每个骰子,用于处理点击事件。

渲染 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 提供游戏状态描述。
  • 骰子容器:动态显示所有 Die 组件。
  • 按钮文本:胜利时为“New Game”,否则为“Roll”。

代码的核心逻辑总结

  • 初始状态下生成 10 个随机骰子。
  • 用户点击骰子时,可冻结当前骰子值。
  • 点击按钮:
    – 若游戏未胜利,则重新掷未冻结的骰子。
    – 若游戏胜利,则重置游戏。
  • 实现游戏胜利条件检测和 UI 提示(如彩带效果、自动聚焦按钮)。

2. Die.jsx

export default function Die(props) {const styles = {backgroundColor: props.isHeld ? "#59E391" : "white"}

styles 是一个动态样式对象,用于控制按钮的背景颜色:

  • 如果 props.isHeld 为 true(表示该骰子被冻结),背景颜色为绿色 (#59E391)。
  • 如果 props.isHeld 为 false(表示未冻结),背景颜色为白色。

样式切换使用户能够直观地看到冻结状态。

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}

  • 将动态样式对象 styles 应用到按钮的 style 属性,调整按钮的背景颜色。

onClick={props.hold}

  • 定义按钮的点击事件处理函数。
  • props.hold 是从父组件传递的函数,当用户点击按钮时会触发这个函数。
  • 通常,props.hold 用于切换 isHeld 状态,冻结或解冻当前骰子。

aria-pressed={props.isHeld}

  • 用于无障碍支持。
  • 指定按钮的按下状态:
  • true:表示当前骰子已被“按下”或冻结。
  • false:表示当前骰子未被按下。
  • 帮助屏幕阅读器用户了解当前状态。

aria-label={Die with value ${props.value}, ${props.isHeld ? “held” : “not held”}}

  • 用于描述按钮的详细信息,提升无障碍性。
  • 动态生成描述,例如:
  • Die with value 4, held:骰子的值为 4,已冻结。
  • Die with value 6, not held:骰子的值为 6,未冻结。

代码的核心功能

  • 动态显示骰子的值。
  • 提供交互功能,点击按钮会调用 props.hold,切换冻结状态。
  • 通过动态样式反映骰子的状态(冻结或未冻结)。
  • 提供无障碍支持,便于屏幕阅读器识别和描述按钮状态。

3. index.css

1. 通用选择器 *

* {box-sizing: border-box;
}
  • 作用:将所有元素的 box-sizing 设置为 border-box
  • box-sizing: border-box;
    • 包括内容、内边距 (padding) 和边框 (border) 的宽度和高度在 widthheight 的计算中。
    • 效果:简化布局调整,避免意外的尺寸增大。

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 占据整个视口高度。
  • 内边距20px,用于给内容留出额外空间。

3. div#root 样式

div#root {height: 100%;width: 100%;max-height: 400px;max-width: 400px;
}
  • 高度和宽度:默认占据父容器的全部空间。
  • 最大尺寸限制max-heightmax-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;
}
  • 字体:优先使用 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 网格布局。
    • grid-template
      • 行模板:auto auto(两行,每行高度自适应内容)。
      • 列模板:repeat(5, 1fr)(5 列,每列宽度相等)。
    • gap: 20px;:网格单元之间的间距。
  • 底部边距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,宽度根据内容调整。
  • 背景颜色:深蓝色 (#5035FF)。
  • 字体颜色:白色。
  • 内边距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;
}
  • 作用:隐藏元素,但保留给屏幕阅读器使用。
  • 关键属性
    • position: absolute;:从文档流中移除。
    • widthheight 设置为 1px
    • 使用 clipoverflow 确保内容不可见。
    • 提供无障碍支持,例如为视觉障碍用户提供额外的语音描述。

总结

这段 CSS 代码:

  • 设计布局:通过弹性盒布局和网格布局组织内容。
  • 样式统一性:使用动态样式、字体和交互效果。
  • 无障碍支持:添加屏幕阅读器友好的隐藏元素(.sr-only)。
  • 视觉细节:通过颜色、圆角、阴影和间距提升用户体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/492904.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue 文本域 展示的内容格式要和填写时保持一致

文本域 展示的内容格式要和填写时保持一致 <el-inputtype"textarea":rows"5"placeholder"请输入内容"v-model"formCredit.point"style"width:1010px;" > </el-input> 样式加个&#xff1a; white-space: pre-w…

[Linux] 信号保存与处理

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;青果大战linux 总有光环在陨落&#xff0c;总有新星在闪烁 信号的保存 下面的概…

【Axure高保真原型】伸缩表单

今天和大家分享伸缩表单的原型模板&#xff0c;效果包括在需要填写内容较多时&#xff0c;可以对填写内容进行分类&#xff0c;然后通过点击上下箭头&#xff0c;收起或展开对应的信息。这个模版里面包含了输入框、下拉列表、选择器、上次图片共多种种常用的元件&#xff0c;后…

InternVL简读

InternVL: Scaling up Vision Foundation Models and Aligning for Generic Visual-Linguistic Tasks 1. Introduction 需要解决的问题&#xff1a; existing VLLMs [5, 81, 131, 177, 187] commonly employ lightweight “glue” layers, such as QFormer [81] or linear pr…

从源码分析swift GCD_DispatchGroup

前言&#xff1a; 最近在写需求的时候用到了DispatchGroup&#xff0c;一直没有深入去学习&#xff0c;既然遇到了那么就总结下吧。。。。 基本介绍&#xff1a; 任务组&#xff08;DispatchGroup&#xff09; DispatchGroup 可以将多个任务组合在一起并且监听它们的完成状态。…

AFL-Fuzz 的使用

AFL-Fuzz 的使用 一、工具二、有源码测试三、无源码测试 一、工具 建议安装LLVM并使用afl-clang-fast或afl-clang-lto进行编译&#xff0c;这些工具提供了更现代和高效的插桩技术。您可以按照以下步骤安装LLVM和afl-clang-fast&#xff1a; sudo apt update sudo apt install…

ONES 功能上新|ONES Copilot、ONES Wiki 新功能一览

ONES Copilot 可基于工作项的标题、描述、属性信息&#xff0c;对工作项产生的动态和评论生成总结。 针对不同类型的工作项&#xff0c;总结输出的内容有对应的侧重点。 应用场景&#xff1a; 在一些流程步骤复杂、上下游参与成员角色丰富的场景中&#xff0c;工作项动态往往会…

EasyGBS国标GB28181平台P2P远程访问故障排查指南:客户端角度的排查思路

在现代视频监控系统中&#xff0c;P2P&#xff08;点对点&#xff09;技术因其便捷性和高效性而被广泛应用。然而&#xff0c;当用户在使用P2P远程访问时遇到设备不在线或无法访问的问题时&#xff0c;有效的排查方法显得尤为重要。本文将从客户端的角度出发&#xff0c;详细探…

Soul Android端稳定性背后的那些事

前言&#xff1a;移动应用的稳定性对于用户体验和产品商业价值都有着至关重要的作用。应用崩溃会导致关键业务中断、用户留存率下降、品牌口碑变差、生命周期价值下降等影响&#xff0c;甚至会导致用户流失。因此&#xff0c;稳定性是APP质量构建体系中最基本和最关键的一环。当…

mfc140u.dll是什么文件?如何解决mfc140u.dll丢失的相关问题

遇到“mfc140u.dll文件丢失”的错误通常影响应用程序的运行&#xff0c;这个问题主要出现在使用Microsoft Visual C环境开发的软件中。mfc140u.dll是一个重要的系统文件&#xff0c;如果它丢失或损坏&#xff0c;会导致相关程序无法启动。本文将简要介绍几种快速有效的方法来恢…

mybatis分页插件的使用

1. 引入依赖包 <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>6.1.0</version> </dependency> 2 添加分页插件配置 2.1 使用配置类的方式&#xff08;推荐&#xff09…

手机便签哪个好用?手机桌面便签app下载推荐

在快节奏的现代生活中&#xff0c;我们常常需要记录一些重要的信息和灵感&#xff0c;以便于日后查阅和回顾。手机便签软件因其便携性和易用性&#xff0c;成为了我们日常生活中不可或缺的工具。无论是购物清单、待办事项、灵感记录还是重要笔记&#xff0c;手机便签都能帮助我…

Zabbix6.0升级为6.4

为了体验一些新的功能&#xff0c;比如 Webhook 和问题抑制等&#xff0c;升级个小版本。 一、环境信息 1. 版本要求 一定要事先查看官方文档&#xff0c;确认组件要求的版本&#xff0c;否则版本过高或者过低都会出现问题。 2. 升级前后信息 环境升级前升级后操作系统CentOS…

怎么将pdf中的某一个提取出来?介绍几种提取PDF中页面的方法

怎么将pdf中的某一个提取出来&#xff1f;传统上&#xff0c;我们可能通过手动截取屏幕或使用PDF阅读器的复制功能来提取信息&#xff0c;但这种方法往往不够精确&#xff0c;且无法保留原文档的排版和格式。此外&#xff0c;很多时候我们需要提取的内容可能涉及多个页面、多个…

LeetCode:101. 对称二叉树

跟着carl学算法&#xff0c;本系列博客仅做个人记录&#xff0c;建议大家都去看carl本人的博客&#xff0c;写的真的很好的&#xff01; 代码随想录 LeetCode&#xff1a;101. 对称二叉树 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输…

力扣-图论-18【算法学习day.68】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…

深度学习之目标检测——RCNN

Selective Search 背景:事先不知道需要检测哪个类别,且候选目标存在层级关系与尺度关系 常规解决方法&#xff1a;穷举法&#xff0c;在原始图片上进行不同尺度不同大小的滑窗&#xff0c;获取每个可能的位置 弊端&#xff1a;计算量大&#xff0c;且尺度不能兼顾 Selective …

【计算机视觉基础CV】03-深度学习图像分类实战:鲜花数据集加载与预处理详解

本文将深入介绍鲜花分类数据集的加载与处理方式&#xff0c;同时详细解释代码的每一步骤并给出更丰富的实践建议和拓展思路。以实用为导向&#xff0c;为读者提供从数据组织、预处理、加载到可视化展示的完整过程&#xff0c;并为后续模型训练打下基础。 前言 在计算机视觉的深…

Linux 中的 mkdir 命令:深入解析

在 Linux 系统中&#xff0c;mkdir 命令用于创建目录。它是文件系统管理中最基础的命令之一&#xff0c;广泛应用于日常操作和系统管理中。本文将深入探讨 mkdir 命令的功能、使用场景、高级技巧&#xff0c;并结合 GNU Coreutils 的源码进行详细分析。 1. mkdir 命令的基本用法…

电商数据采集电商,行业数据分析,平台数据获取|稳定的API接口数据

电商数据采集可以通过多种方式完成&#xff0c;其中包括人工采集、使用电商平台提供的API接口、以及利用爬虫技术等自动化工具。以下是一些常用的电商数据采集方法&#xff1a; 人工采集&#xff1a;人工采集主要是通过基本的“复制粘贴”的方式在电商平台上进行数据的收集&am…