鸿蒙 ArkUI 实现 2048 小游戏

devtools/2025/3/1 0:33:02/

2048 是一款经典的益智游戏,玩家通过滑动屏幕合并相同数字的方块,最终目标是合成数字 2048。本文基于鸿蒙 ArkUI 框架,详细解析其实现过程,帮助开发者理解如何利用声明式 UI 和状态管理构建此类游戏。

一、核心数据结构与状态管理

1. 游戏网格与得分

游戏的核心是一个 4x4 的二维数组,用于存储每个格子的数字。通过 @State 装饰器管理网格状态,确保数据变化时 UI 自动刷新:

@State grid: number[][] = Array(4).fill(0).map(() => Array(4).fill(0));
@State score: number = 0; // 当前得分
@State bestScore: number = 0; // 历史最高分
2. 初始化游戏

initGame 方法负责重置网格、添加初始方块并重置得分。通过 addNewTile 在随机空位生成新方块(90% 概率生成 2,10% 概率生成 4):

initGame() {this.grid = this.grid.map(() => Array(4).fill(0));this.addNewTile();this.addNewTile();this.score = 0;
}

二、滑动逻辑与合并算法

1. 方向处理与矩阵旋转

游戏支持 上下左右四个方向的滑动。为简化代码逻辑,通过矩阵旋转将不同方向的移动统一转换为 左移操作

  • 左移:直接处理每一行。
  • 右移:将行反转后左移,再反转回来。
  • 上移/下移:旋转矩阵为行,处理后恢复为列。
// 矩阵旋转辅助方法
const rotate = (matrix: number[][]) => {return matrix[0].map((_, i) => matrix.map(row => row[i]).reverse());
};
2. 单行合并逻辑

每行处理分为三步:

  1. 移除空格:过滤出非零数字。
  2. 合并相同数字:相邻相同数字合并,并累加得分。
  3. 补齐长度:填充零至长度为 4。
const moveRow = (row: number[]) => {let newRow = row.filter(cell => cell !== 0);for (let i = 0; i < newRow.length - 1; i++) {if (newRow[i] === newRow[i + 1]) {newRow[i] *= 2;this.score += newRow[i]; // 得分累加newRow.splice(i + 1, 1);}}return [...newRow, ...Array(4 - newRow.length).fill(0)];
};

三、游戏结束判断

游戏结束的条件是 网格填满且无相邻可合并的方块。通过以下步骤检测:

  1. 检查是否有空格:存在空格则游戏未结束。
  2. 横向检测:遍历每一行,检查是否有相邻相同数字。
  3. 纵向检测:遍历每一列,检查是否有相邻相同数字。
isGameOver(): boolean {if (this.grid.some(row => row.includes(0))) return false;// 横向和纵向检测逻辑// ...return true;
}

四、UI 实现与交互设计

1. 网格渲染

使用 Grid 组件动态生成 4x4 网格,每个 GridItem 根据数字值显示不同背景色和文字颜色:

Grid() {ForEach(this.grid, (row: number[], i) => {ForEach(row, (value: number, j) => {GridItem() {Text(value ? `${value}` : '').backgroundColor(this.getTileColor(value)).fontColor(this.getTextColor(value));}})})
}

2. 触摸事件处理
通过 onTouch 监听滑动事件,计算起始和结束坐标的差值,判断滑动方向:

onTouch((event) => {if (event.type === TouchType.Down) {this.startX = event.touches[0].x;this.startY = event.touches[0].y;} else if (event.type === TouchType.Up) {const deltaX = event.touches[0].x - this.startX;const deltaY = event.touches[0].y - this.startY;// 判断方向并调用 move 方法}
});

五、本地存储与动画效果

1. 最高分持久化

使用 PreferencesUtil 存储和读取最高分,确保数据在应用重启后保留:

aboutToAppear() {this.bestScore = PreferencesUtil.getNumberSync("bestScore");
}// 更新最高分
if (this.score > this.bestScore) {PreferencesUtil.putSync('bestScore', this.score);
}
2. 动画与视觉效果

每个方块的文字变化添加了 150ms 的渐变动画,提升用户体验:

Text(value ? `${value}` : '').animation({ duration: 150, curve: Curve.EaseOut });

六、完整代码

import { HashMap } from '@kit.ArkTS'
import { AppUtil, PreferencesUtil, ToastUtil } from '@pura/harmony-utils'// index.ets
@Entry
@Component
struct Game2048 {@State grid: number[][] = Array(4).fill(0).map(() => Array(4).fill(0)) // 4x4游戏网格@State score: number = 0 // 当前得分@State bestScore: number = 0 // 历史最高分private startX: number = 0 // 触摸起始X坐标private startY: number = 0 // 触摸起始Y坐标// 生命周期方法:页面即将显示时触发aboutToAppear() {this.initGame()this.bestScore = PreferencesUtil.getNumberSync("bestScore") // 读取本地存储的最高分}// 初始化游戏initGame() {this.grid = this.grid.map(() => Array(4).fill(0)) // 重置网格this.addNewTile() // 添加两个新方块this.addNewTile() // 重置当前得分this.score = 0}addNewTile() {const emptyCells: [number, number][] = [] // 收集空单元格坐标this.grid.forEach((row, i) => {row.forEach((cell, j) => {if (cell === 0) {emptyCells.push([i, j])}})})if (emptyCells.length > 0) {let n = Math.floor(Math.random() * emptyCells.length) // 随机选择空单元格const i = emptyCells[n][0]const j = emptyCells[n][1]this.grid[i][j] = Math.random() < 0.9 ? 2 : 4 // 90%概率生成2,10%概率生成4}}// 处理移动逻辑move(direction: 'left' | 'right' | 'up' | 'down') {let newGrid = this.grid.map(row => [...row]) // 创建网格副本let moved = false // 移动标志位// 矩阵旋转辅助方法const rotate = (matrix: number[][]) => {return matrix[0].map((_, i) => matrix.map(row => row[i]).reverse())}const rotateReverse = (matrix: number[][]) => {return matrix[0].map((_, i) => matrix.map(row => row[row.length - 1 - i]))}// 处理单行移动和合并const moveRow = (row: number[]) => {let newRow = row.filter(cell => cell !== 0) // 移除空格for (let i = 0; i < newRow.length - 1; i++) {if (newRow[i] === newRow[i + 1]) { // 合并相同数字newRow[i] *= 2this.score += newRow[i] // 更新得分newRow.splice(i + 1, 1) // 移除合并后的元素}}// 补齐长度while (newRow.length < 4) {newRow.push(0)}return newRow}// 根据方向处理移动switch (direction) {case 'left':newGrid.forEach((row, i) => newGrid[i] = moveRow(row))breakcase 'right':newGrid.forEach((row, i) => newGrid[i] = moveRow(row.reverse()).reverse())breakcase 'up':let rotatedDown = rotate(newGrid)rotatedDown.forEach((row, i) => rotatedDown[i] = moveRow(row.reverse()).reverse())newGrid = rotateReverse(rotatedDown)breakcase 'down':let rotatedUp = rotate(newGrid)rotatedUp.forEach((row, i) => rotatedUp[i] = moveRow(row))newGrid = rotateReverse(rotatedUp)break}moved = JSON.stringify(newGrid) !== JSON.stringify(this.grid) // 判断是否发生移动this.grid = newGridif (moved) {this.addNewTile() // 移动后添加新方块if (this.score > this.bestScore) { // 更新最高分this.bestScore = this.scorePreferencesUtil.putSync('bestScore', this.bestScore) //保存最高分}}if (this.isGameOver()) { // 游戏结束检测ToastUtil.showToast('游戏结束!')}}// 游戏结束判断isGameOver(): boolean {// 检查空格子if (this.grid.some(row => row.includes(0))) {return false}// 检查横向可合并for (let i = 0; i < 4; i++) {for (let j = 0; j < 3; j++) {if (this.grid[i][j] === this.grid[i][j + 1]) {return false}}}// 检查纵向可合并for (let j = 0; j < 4; j++) {for (let i = 0; i < 3; i++) {if (this.grid[i][j] === this.grid[i + 1][j]) {return false}}}return true}build() {Column() {// 分数显示行Row() {Text(`得分: ${this.score}`).fontSize(20).margin(10)Text(`最高分: ${this.bestScore}`).fontSize(20).margin(10)Button('新游戏').onClick(() => this.initGame()).margin(10)}.margin({top:px2vp(AppUtil.getStatusBarHeight()) })// 游戏网格Grid() {ForEach(this.grid, (row: number[], i) => {ForEach(row, (value: number, j) => {GridItem() {Text(value ? `${value}` : '').textAlign(TextAlign.Center).fontSize(24).fontColor(this.getTextColor(value)).width('100%').height('100%').backgroundColor(this.getTileColor(value)).animation({duration: 150,curve: Curve.EaseOut})}.key(`${i}-${j}`)})})}.columnsTemplate('1fr 1fr 1fr 1fr')    // 4等分列.rowsTemplate('1fr 1fr 1fr 1fr')       // 4等分行.width('90%').aspectRatio(1)                        // 保持正方形.margin(10).onTouch((event) => {                  // 触摸事件处理if (event.type === TouchType.Down) {this.startX = event.touches[0].xthis.startY = event.touches[0].y} else if (event.type === TouchType.Up) {const deltaX = event.touches[0].x - this.startXconst deltaY = event.touches[0].y - this.startY// 根据滑动方向判断移动if (Math.abs(deltaX) > Math.abs(deltaY)) {deltaX > 0 ? this.move('right') : this.move('left')} else {deltaY > 0 ? this.move('down') : this.move('up')}}})}.width('100%')}// 获取方块背景色getTileColor(value: number): string {const colors = new HashMap<number, string>()colors.set(0, '#CDC1B4')colors.set(2, '#EEE4DA')colors.set(4, '#EDE0C8')colors.set(8, '#F2B179')colors.set(16, '#F59563')colors.set(32, '#F67C5F')colors.set(64, '#F65E3B')colors.set(128, '#EDCF72')colors.set(256, '#EDCF72')colors.set(512, '#EDCC61')colors.set(1024, '#EDC850')colors.set(2048, '#EDC22E')return colors.get(value) || '#CDC1B4'}// 获取文字颜色getTextColor(value: number): Color {return value > 4 ? Color.White : Color.Black}
}

http://www.ppmy.cn/devtools/163483.html

相关文章

1. EXCEL基础、界面介绍《AI赋能Excel 》

欢迎来到滔滔讲AI。 Excel表格是一种强大的电子表格软件&#xff0c;它不仅可以用来存储和组织数据&#xff0c;还可以进行复杂的计算、数据分析和可视化。无论是在工作,学习,还是日常生活中&#xff0c;Excel都经常用到&#xff0c;帮助人们管理和分析大量数据&#xff0c;做出…

浅谈Linux中的软件包管理器——基于ubuntu环境

文章目录 1. 为什么要使用软件包管理器1.1 使用源码1.2 使用rpm安装包1.3 使用apt软件包管理器 2. 如何使用apt2.1 软件的安装和卸载2.2 查找和搜素软件包2.3 更新并升级软件包2.4 清理缓存 3. 从apt到系统生态 1. 为什么要使用软件包管理器 在Linux中&#xff0c;有三种软件安…

Python学习第十七天之PyTorch保姆级安装

PyTorch安装与部署 一、准备工作二、pytorch介绍三、CPU版本pytorch安装1. 创建虚拟环境2. 删除虚拟环境1. 通过环境名称删除2. 通过环境路径删除 3. 配置镜像源4. 安装pytorch1. 首先激活环境变量2. 进入pytorch官网&#xff0c;找到安装指令 5. 验证pytorch是否安装成功 四、…

火语言RPA--Excel设置列宽

【组件功能】&#xff1a;为Excel内指定列设置列宽 配置预览 列名 支持T或# Excel文档的列名&#xff0c;从字母A开始。 列宽样式 指定列宽&#xff1a;指定列宽数值 内容自适配&#xff1a;根据内容自动设置列宽 列宽 支持T或# 列宽值单位字符宽度&#xff0c;一个汉字两…

【deepseek】本地部署+RAG知识库挂载+对话测试

文章目录 前言一、Deepseek模型下载(以7B为例)二、RAG本地知识库挂载三、创建本地对话脚本四、结果展示 前言 本文主要涵盖Deepseek在ubuntu系统中的部署全流程&#xff0c;包括模型的下载、系统部署、本地文档向量化、向量列表存储、RAG知识库挂载、对话测试等内容 一、Deeps…

二、IDE集成DeepSeek保姆级教学(使用篇)

各位看官老爷好&#xff0c;如果还没有安装DeepSeek请查阅前一篇 一、IDE集成DeepSeek保姆级教学(安装篇) 一、DeepSeek在CodeGPT中使用教学 1.1、Edit Code 编辑代码 选中代码片段 —> 右键 —> CodeGPT —> Edit Code, 输入自然语言可编辑代码&#xff0c;点击S…

CNN:卷积网络中设计1×1夹在主要卷积核如3×3前后的作用

话不多说直接上图举例&#xff1a; 像在 ResNet 的 Bottleneck 结构 中&#xff0c;1x1 卷积 被放置在 3x3 卷积 的前后&#xff0c;这种设计有以下几个关键作用和优势&#xff1a; 1. 降低计算复杂度 问题&#xff1a;直接使用 3x3 卷积计算量较大&#xff0c;尤其是当输入和…

Selenium 不同语言绑定版本的官方操作文档获取途径(科学上网)

Selenium 不同语言绑定版本的官方操作文档获取途径 Selenium 是一个强大的自动化测试工具&#xff0c;支持多种编程语言绑定。以下为你详细介绍不同语言绑定版本的官方操作文档获取途径。 一、Python 语言绑定 1.1 官方文档 地址&#xff1a;Selenium Python 官方文档内容概…