当前位置: 首页 > news >正文

纯血Harmony NETX 5小游戏实践:2048(附源文件)

在移动应用开发领域,2048游戏因其简洁的规则和富有挑战性的玩法成为经典案例。本文将基于鸿蒙OS的ArkUI框架,深入解析2048游戏的实现原理,从数据结构设计到动画交互优化,带您全面了解这款益智游戏的开发全过程。

游戏核心架构与数据模型设计

2048游戏的核心是一个4×4的网格系统,每个单元格可以包含数字2的幂次或为空。在鸿蒙实现中,我们采用二维数组作为基础数据结构:

@State grid: number[][] = [[0, 0, 0, 0],[0, 0, 0, 0],[0, 0, 0, 0],[0, 0, 0, 0]
];

这种设计简洁高效,便于实现网格的遍历、合并和更新操作。游戏初始化时,通过addRandomTile()方法在网格中随机生成两个初始方块(2或4,10%概率生成4):

addRandomTile() {const emptyCells: GeneratedTypeLiteralInterface_1[] = [];// 收集所有空单元格坐标for (let i = 0; i < 4; i++) {for (let j = 0; j < 4; j++) {if (this.grid[i][j] === 0) {emptyCells.push({ row: i, col: j });}}}// 处理无空位的情况if (emptyCells.length === 0 && !this.checkMovesAvailable()) {this.gameOver = true;return;}// 随机放置新方块if (emptyCells.length > 0) {const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];this.grid[randomCell.row][randomCell.col] = Math.random() < 0.1 ? 4 : 2;}
}

数据模型中还包含了分数统计score、游戏状态gameOver和胜利标志gameWon,这些状态通过@State装饰器实现响应式更新,确保UI与数据保持同步。

核心游戏逻辑:移动与合并算法

2048游戏的核心在于四个方向的移动逻辑,其本质是对网格数据的压缩与合并操作。以向左移动为例,核心算法分为两步:压缩非零元素到左侧,然后合并相邻相同元素。

moveLeft() {let moved = false;const newGrid = this.grid.map(row => {const newRow = this.compressAndMerge(row);// 检测数据是否变更if (JSON.stringify(newRow) !== JSON.stringify(row)) {moved = true;}return newRow;});// 仅在数据变更时更新状态if (moved) {this.grid = newGrid;this.checkWinCondition();this.addRandomTile();}
}

compressAndMerge()方法是移动逻辑的核心,它不仅负责将非零元素紧凑排列,还会处理相同元素的合并并计算得分:

compressAndMerge(row: number[]): number[] {const filteredRow = row.filter(val => val !== 0);const mergedRow: number[] = [];let scoreIncrease = 0;for (let i = 0; i < filteredRow.length; i++) {// 合并相邻相同元素if (i < filteredRow.length - 1 && filteredRow[i] === filteredRow[i + 1]) {mergedRow.push(filteredRow[i] * 2);scoreIncrease += filteredRow[i] * 2;// 检查是否达成2048胜利条件if (filteredRow[i] * 2 === 2048) {this.gameWon = true;}i++; // 跳过已合并元素} else {mergedRow.push(filteredRow[i]);}}// 补零操作,保持4格长度while (mergedRow.length < 4) {mergedRow.push(0);}// 更新积分if (scoreIncrease > 0) {this.score += scoreIncrease;}return mergedRow;
}

向右、向上、向下的移动逻辑基于向左移动算法演变而来,通过行列转换和数组反转实现方向适配。例如向右移动时,先将行数据反转,应用向左移动逻辑后再反转回来:

moveRight() {let moved = false;const newGrid = this.grid.map(row => {const reversedRow = [...row].reverse();const mergedRow = this.compressAndMerge(reversedRow);const newRow = mergedRow.reverse();if (JSON.stringify(newRow) !== JSON.stringify(row)) {moved = true;}return newRow;});// 状态更新逻辑与向左移动相同
}

界面渲染与交互体验优化

鸿蒙ArkUI的声明式UI特性让2048游戏的界面实现变得简洁直观。我们通过ForEach循环动态渲染4×4网格,每个单元格的样式根据其值动态变化:

ForEach(this.grid, (row: number[], rowIndex: number) => {Row({ space: 15 }) {ForEach(row, (cell: number, colIndex: number) => {Column() {CellComponent({ value: cell })}.width(60).height(60).borderRadius(20).backgroundColor(this.getCellBackgroundColor(cell)).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)})}.width('100%').justifyContent(FlexAlign.SpaceAround)
})

getCellBackgroundColor()方法根据单元格值返回对应的背景色,实现2048游戏经典的视觉层次感:

getCellBackgroundColor(value: number): ResourceColor {switch (value) {case 0: return '#cdc1b4';case 2: return '#eee4da';case 4: return '#ede0c8';case 8: return '#f2b179';// 省略中间值...case 2048: return '#edc22e';default: return '#3c3a32';}
}

交互控制采用四个方向按钮实现,点击事件绑定对应的移动逻辑:

Row({ space: 20 }) {Button('←').width(60).height(60).fontColor('#ffffff').onClick(() => this.moveLeft()).backgroundColor('#776e65').borderRadius(10);// 其他方向按钮类似...
}

游戏状态管理与边界条件处理

2048游戏的难点在于边界条件的处理,包括:

  1. 游戏结束判断:当网格已满且没有可合并的元素时,游戏结束:
checkMovesAvailable(): boolean {for (let i = 0; i < 4; i++) {for (let j = 0; j < 4; j++) {// 检查右侧和下侧是否有可合并元素if (j < 3 && this.grid[i][j] === this.grid[i][j + 1]) return true;if (i < 3 && this.grid[i][j] === this.grid[i + 1][j]) return true;}}return false;
}
  1. 胜利条件判断:当网格中出现2048时,游戏胜利:
checkWinCondition() {if (!this.gameWon && this.grid.some(row => row.includes(2048))) {this.gameWon = true;}
}
  1. 积分系统:每次合并操作会根据合并后的数值增加积分,例如合并两个1024得到2048时,积分增加2048。

附:源文件

interface GeneratedTypeLiteralInterface_1 {row: number;col: number;
}@Component
export struct play_7 {// 游戏数据矩阵@State grid: number[][] = [[0, 0, 0, 0],[0, 0, 0, 0],[0, 0, 0, 0],[0, 0, 0, 0]];// 积分属性@State score: number = 0;// 游戏状态@State gameOver: boolean = false;@State gameWon: boolean = false;// 初始化游戏aboutToAppear() {this.addRandomTile();this.addRandomTile();this.score = 0; // 初始化积分为0this.gameOver = false;this.gameWon = false;}// 添加随机方块并更新积分addRandomTile() {const emptyCells: GeneratedTypeLiteralInterface_1[] = [];// 找出所有空位for (let i = 0; i < 4; i++) {for (let j = 0; j < 4; j++) {if (this.grid[i][j] === 0) {emptyCells.push({ row: i, col: j });}}}// 如果没有空位则检查游戏结束if (emptyCells.length === 0 && !this.checkMovesAvailable()) {this.gameOver = true;return;}// 随机选择一个位置放置2或4(10%概率)if (emptyCells.length > 0) {const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];this.grid[randomCell.row][randomCell.col] = Math.random() < 0.1 ? 4 : 2;}}// 检查是否有可用移动checkMovesAvailable(): boolean {for (let i = 0; i < 4; i++) {for (let j = 0; j < 4; j++) {// 检查右侧是否有可合并if (j < 3 && this.grid[i][j] === this.grid[i][j + 1]) return true;// 检查下方是否有可合并if (i < 3 && this.grid[i][j] === this.grid[i + 1][j]) return true;}}return false;}// 向左移动逻辑并计算积分moveLeft() {let moved = false;const newGrid = this.grid.map(row => {const newRow = this.compressAndMerge(row);if (JSON.stringify(newRow) !== JSON.stringify(row)) {moved = true;}return newRow;});if (moved) {this.grid = newGrid;this.checkWinCondition();this.addRandomTile();}}// 向右移动逻辑并计算积分moveRight() {let moved = false;const newGrid = this.grid.map(row => {const reversedRow = [...row].reverse();const mergedRow = this.compressAndMerge(reversedRow);const newRow = mergedRow.reverse();if (JSON.stringify(newRow) !== JSON.stringify(row)) {moved = true;}return newRow;});if (moved) {this.grid = newGrid;this.checkWinCondition();this.addRandomTile();}}// 向上移动逻辑并计算积分moveUp() {let moved = false;const newGrid = [...this.grid];for (let col = 0; col < 4; col++) {const column = [newGrid[0][col], newGrid[1][col], newGrid[2][col], newGrid[3][col]];const newColumn = this.compressAndMerge(column);for (let row = 0; row < 4; row++) {if (newGrid[row][col] !== newColumn[row]) {moved = true;}newGrid[row][col] = newColumn[row];}}if (moved) {this.grid = newGrid;this.checkWinCondition();this.addRandomTile();}}// 向下移动逻辑并计算积分moveDown() {let moved = false;const newGrid = [...this.grid];for (let col = 0; col < 4; col++) {const column = [newGrid[3][col], newGrid[2][col], newGrid[1][col], newGrid[0][col]];const newColumn = this.compressAndMerge(column);for (let row = 0; row < 4; row++) {if (newGrid[3 - row][col] !== newColumn[row]) {moved = true;}newGrid[3 - row][col] = newColumn[row];}}if (moved) {this.grid = newGrid;this.checkWinCondition();this.addRandomTile();}}// 压缩并合并单元格,增加积分计算compressAndMerge(row: number[]): number[] {const filteredRow = row.filter(val => val !== 0);const mergedRow: number[] = [];let scoreIncrease = 0;for (let i = 0; i < filteredRow.length; i++) {if (i < filteredRow.length - 1 && filteredRow[i] === filteredRow[i + 1]) {mergedRow.push(filteredRow[i] * 2);scoreIncrease += filteredRow[i] * 2;i++;// 检查是否达成2048胜利条件if (filteredRow[i] * 2 === 2048) {this.gameWon = true;}} else {mergedRow.push(filteredRow[i]);}}// 补零while (mergedRow.length < 4) {mergedRow.push(0);}// 更新积分if (scoreIncrease > 0) {this.score += scoreIncrease;}return mergedRow;}// 检查胜利条件checkWinCondition() {if (!this.gameWon && this.grid.some(row => row.includes(2048))) {this.gameWon = true;}}build() {Column({ space: 20 }) {// 标题区和积分显示Row() {Text('2048').fontSize(36).fontWeight(FontWeight.Bold).textAlign(TextAlign.Center).width('70%').padding({ top: 10, bottom: 10 }).borderRadius(15).backgroundColor('#fdf6ec').height(60)// 积分显示Text(`分数: ${this.score}`).fontSize(24).fontWeight(FontWeight.Bold).textAlign(TextAlign.Center).width('30%').padding({ top: 10, bottom: 10 }).borderRadius(15).backgroundColor('#f1eeee').height(60).shadow({ color: '#ccc', radius: 6 })}.width('90%')// 显示游戏网格Column({ space: 15 }) {ForEach(this.grid, (row: number[], rowIndex: number) => {Row({ space: 15 }) {ForEach(row, (cell: number, colIndex: number) => {// 每个单元格的显示Column() {CellComponent({ value: cell })}.width(60).height(60).borderRadius(20).backgroundColor(this.getCellBackgroundColor(cell)).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).shadow({ color: '#a394894d', radius: 8, offsetX: 2, offsetY: 2 })})}.width('100%').justifyContent(FlexAlign.SpaceAround)})}.width('90%').padding(10).borderRadius(20).backgroundColor('#bbada0').shadow({ color: '#888', radius: 10 })// 添加控制按钮Row({ space: 20 }) {Button('←').width(60).height(60).fontColor('#ffffff').onClick(() => {this.moveLeft()}).backgroundColor('#776e65').borderRadius(10)Button('↑').width(60).height(60).fontColor('#ffffff').onClick(() => {this.moveUp()}).backgroundColor('#776e65').borderRadius(10)Button('↓').width(60).height(60).fontColor('#ffffff').onClick(() => {this.moveDown()}).backgroundColor('#776e65').borderRadius(10)Button('→').width(60).height(60).fontColor('#ffffff').onClick(() => {this.moveRight()}).backgroundColor('#776e65').borderRadius(10)}.width('90%').justifyContent(FlexAlign.Center)// 游戏状态提示if (this.gameOver) {Text('Game Over!').fontSize(28).fontWeight(FontWeight.Bold).fontColor('#ffffff').backgroundColor('#bbada0').padding({ left: 20, right: 20, top: 10, bottom: 10 }).borderRadius(10)}if (this.gameWon) {Text('Congratulations!\nYou reached 2048!').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#ffffff').backgroundColor('#edc22e').padding({ left: 20, right: 20, top: 10, bottom: 10 }).borderRadius(10).textAlign(TextAlign.Center)}}.width('100%').height('100%').justifyContent(FlexAlign.Start).alignItems(HorizontalAlign.Center).padding({ top: 20, bottom: 20 })}// 获取单元格背景颜色getCellBackgroundColor(value: number): ResourceColor{switch (value) {case 0:return '#cdc1b4'case 2:return '#eee4da'case 4:return '#ede0c8'case 8:return '#f2b179'case 16:return '#f59563'case 32:return '#f67c5f'case 64:return '#f65e3b'case 128:return '#edcf72'case 256:return '#edcc61'case 512:return '#edc850'case 1024:return '#edc53f'case 2048:return '#edc22e'default:return '#3c3a32'}}
}@Component
struct CellComponent {@Prop value: numberbuild() {Text(this.value === 0 ? '' : this.value.toString()).fontSize(28).fontColor('#776e65').fontWeight(FontWeight.Bold).fontFamily( 'cursive')}
}
http://www.xdnf.cn/news/977185.html

相关文章:

  • vuetify、nuxt报错lh.at is not a functionlh.at‘ is undefined
  • R语言 | 如何使用R书写html文档?
  • 打造超轻量的仿chatgpt的AI聊天应用
  • IDEA 连接 Docker 一键打镜像
  • LHM深度技术解析:基于多模态Transformer的单图秒级可动画3D人体重建模型
  • 2025.06.11【Ribo-seq】|根据注释文件获取外显子及ORF序列
  • Unity基础-Resources资源动态加载
  • 大模型在输尿管上段积脓预测与治疗方案制定中的应用研究
  • 传输层协议TCP(下)
  • AJAX、Axios 与 Fetch:现代前端数据请求技术对比
  • 提升iOS开发效率:通过KeyMob等工具进行全面性能分析与调试
  • 解决windows下pycharm终端conda无法激活虚拟环境问题
  • IntelliJ IDEA代码提示忽略大小写设置详解
  • TRO警报,Kim Haskins维权进行时:卖猫周边或面临TRO冻结?
  • 【群体结构ADMIXTURE之三】监督分群在祖先成分分析中的应用及原理
  • 建站SEO优化之站点地图sitemap
  • 调试`build.sh` 和用 `CMake` 编译出来的 `.elf` / `.bin` / `.hex` 文件大小或行为不同?
  • 重构技术奇点的路径:三智双融认知大飞跃
  • 如何设计一个用于大规模生产任务的人工智能AI系统
  • OpenSSL 无法验证 DevSidecar 的自签名证书
  • 【数据结构】图论最短路圣器:Floyd算法如何用双矩阵征服负权图?
  • Go 协程(Goroutine)入门与基础使用
  • Go 的 fs 包(1/2):现代文件系统抽象
  • 零基础玩转物联网-串口转以太网模块如何快速实现与HTTP服务器通信
  • Solidity从入门到精通-函数及数据存储和作用域
  • 用 IRify 深入探索 WebShell 中的 Source/Sink 挖掘
  • AWS CloudFormation实战:构建可复用的ECS服务部署模板
  • AWS之混合云
  • 2025年渗透测试面试题总结-长亭科技[社招]应急响应工程师(题目+回答)
  • Roboguide工作站机器人重新安装软件包