鸿蒙NEXT小游戏开发:井字棋
1. 引言
井字棋是一款经典的两人对战游戏,简单易懂,适合各个年龄段的玩家。本文将介绍如何使用鸿蒙NEXT框架开发一个井字棋游戏,涵盖游戏逻辑、界面设计及AI对战功能。
2. 开发环境准备
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:Mate 60 Pro
语言:ArkTS、ArkUI

3. 项目结构
在本项目中,我们将创建一个名为 TicTacToe 的组件,包含以下主要部分:
游戏状态管理:使用状态管理来跟踪游戏进程,包括当前玩家、游戏板、游戏是否结束等。
游戏逻辑:实现胜利条件检查、玩家落子、AI落子等功能。
用户界面:设计游戏界面,展示游戏板和玩家的标记。
4. 代码实现
4.1 游戏状态管理
我们使用 @State 注解来管理游戏状态,包括游戏板、当前玩家、游戏是否结束及获胜者。
@State board: GridCell[][] = []; // 游戏板
@State currentPlayer: string = 'X'; // 当前玩家
@State isGameOver: boolean = false; // 游戏是否结束
@State winner: string = ''; // 获胜者
4.2 游戏逻辑
胜利条件检查
我们定义了一个 winningLines 数组,包含所有可能的胜利线路。通过遍历这些线路,检查是否有玩家获胜。
checkForWinner() {for (let line of winningLines) {const mark = this.board[line[0]][line[1]].value;if (mark && mark === this.board[line[2]][line[3]].value && mark === this.board[line[4]][line[5]].value) {this.isGameOver = true;this.winner = mark;return mark;}}
}
玩家落子
玩家点击单元格时,调用 placeMark 方法,更新游戏板并检查胜利条件。
placeMark(rowIndex: number, colIndex: number) {if (!this.isGameOver && this.board[rowIndex][colIndex].value === '') {this.board[rowIndex][colIndex].value = this.currentPlayer;const result = this.checkForWinner();// 处理胜利或平局情况}
}
AI落子
AI会根据当前局势选择最佳落子位置,使用 findWinningMove 方法检查是否有胜利机会,若没有则随机选择。
aiMove() {let bestMove = this.findWinningMove('O') || this.findWinningMove('X') || this.findRandomMove();if (bestMove) {this.placeMark(bestMove[0], bestMove[1]);}
}
4.3 用户界面
使用鸿蒙NEXT的布局组件构建游戏界面,展示游戏板和重新开始按钮。
build() {Column({ space: 20 }) {Flex({ wrap: FlexWrap.Wrap }) {ForEach(this.board, (row) => {ForEach(row, (cell) => {Text(cell.value).onClick(() => { this.placeMark(cell.rowIndex, cell.colIndex); });});});}Button('重新开始').onClick(() => { this.initGame(); });}
}
5. 总结
通过本文的介绍,我们实现了一个简单的井字棋游戏,涵盖了游戏逻辑、状态管理和用户界面设计。鸿蒙NEXT框架提供了强大的组件化开发能力,使得开发过程更加高效。希望这篇文章能帮助你更好地理解鸿蒙NEXT的开发方式,并激发你开发更多有趣的应用。
5.1 完整代码
import { promptAction } from '@kit.ArkUI';// 胜利的线路
const winningLines = [[0, 0, 0, 1, 0, 2], // 水平线1[1, 0, 1, 1, 1, 2], // 水平线2[2, 0, 2, 1, 2, 2], // 水平线3[0, 0, 1, 0, 2, 0], // 垂直线1[0, 1, 1, 1, 2, 1], // 垂直线2[0, 2, 1, 2, 2, 2], // 垂直线3[0, 0, 1, 1, 2, 2], // 对角线 \[0, 2, 1, 1, 2, 0] // 对角线 /
];// 观察者模式
@ObservedV2
class GridCell {@Trace value: string = ""; // 当前格子的值rowIndex: number = 0; // 格子所在的行号colIndex: number = 0; // 格子所在的列号constructor(rowIndex: number, colIndex: number) {this.rowIndex = rowIndex;this.colIndex = colIndex;}
}// 入口
@Entry
@Component
struct TicTacToe {@State board: GridCell[][] = []; // 游戏板@State currentPlayer: string = 'X'; // 当前玩家@State isGameOver: boolean = false; // 游戏是否结束@State winner: string = ''; // 获胜者@State cellSize: number = 120; // 单元格大小@State cellMargin: number = 5; // 单元格边距// 组件即将出现时初始化游戏aboutToAppear(): void {this.board = [[new GridCell(0, 0), new GridCell(0, 1), new GridCell(0, 2)],[new GridCell(1, 0), new GridCell(1, 1), new GridCell(1, 2)],[new GridCell(2, 0), new GridCell(2, 1), new GridCell(2, 2)]];this.initGame(); // 初始化游戏状态}// 重置游戏状态initGame() {for (let i = 0; i < 3; i++) {for (let j = 0; j < 3; j++) {this.board[i][j].value = ''; // 清空所有单元格}}this.currentPlayer = 'X'; // 设置当前玩家为Xthis.isGameOver = false; // 游戏未结束this.winner = ''; // 无获胜者}// 检查是否有玩家获胜checkForWinner() {for (let line of winningLines) { // 遍历所有胜利线路const mark = this.board[line[0]][line[1]].value;if (mark && // 如果有标记mark === this.board[line[2]][line[3]].value && // 并且等于同一行的下一个标记mark === this.board[line[4]][line[5]].value) { // 再次等于同一行的下一个标记this.isGameOver = true; // 游戏结束this.winner = mark; // 设置获胜者return mark; // 返回获胜者的标记}}const allCellsFilled = this.board.flat().every(cell => cell.value !== ''); // 检查所有单元格是否已填满if (allCellsFilled) {this.isGameOver = true; // 游戏结束this.winner = '平局'; // 设置为平局return '平局'; // 返回平局标识}return ''; // 无获胜者}// 玩家落子placeMark(rowIndex: number, colIndex: number) {if (!this.isGameOver && this.board[rowIndex][colIndex].value === '') { // 如果游戏未结束且单元格为空this.board[rowIndex][colIndex].value = this.currentPlayer; // 放置标记const result = this.checkForWinner(); // 检查是否有获胜者if (result) { // 如果有获胜者console.info(`${result} 获胜!`);let message = `${result} 获胜!`; // 设置提示信息if (result === '平局') {message = '平局!'; // 如果是平局}promptAction.showDialog({ // 显示对话框title: `游戏结束`, // 标题message, // 提示信息buttons: [ // 按钮{text: '重新开始', // 文本color: '#ffa500' // 颜色}],}).then(() => {this.initGame(); // 重新开始游戏});} else { // 如果没有获胜者this.currentPlayer = this.currentPlayer === 'X' ? 'O' : 'X'; // 切换玩家if (this.currentPlayer === 'O') { // 如果是AI玩家this.aiMove(); // AI落子}}}}// AI落子aiMove() {let moveFound = false;let bestMove: null | number[] = null;// 寻找最佳落子位置bestMove = this.findWinningMove('O'); // 检查AI是否有胜利机会console.info(`bestMove a:${JSON.stringify(bestMove)}`);if (bestMove) {moveFound = true;} else {bestMove = this.findWinningMove('X'); // 检查玩家是否有胜利机会console.info(`bestMove b:${JSON.stringify(bestMove)}`);if (bestMove) {moveFound = true;} else {bestMove = this.findRandomMove(); // 随机落子console.info(`bestMove c:${JSON.stringify(bestMove)}`);if (bestMove) {moveFound = true;}}}if (moveFound && bestMove) { // 如果找到了合适的落子位置console.info(`bestMove:${JSON.stringify(bestMove)}`);this.placeMark(bestMove[0], bestMove[1]); // 落子}}// 寻找给定玩家是否有机会赢,并返回这样的移动位置findWinningMove(player: string) {for (let line of winningLines) { // 遍历所有胜利线路let missingIndex = -1;let noEmptyCount = 0;for (let i = 0; i < line.length; i += 2) { // 检查每个单元格if (this.board[line[i]][line[i + 1]].value === player) { // 如果是该玩家的标记noEmptyCount++; // 计数} else if (this.board[line[i]][line[i + 1]].value === '') { // 如果为空missingIndex = i; // 记录空格位置}}if (noEmptyCount === 2 && missingIndex != -1) { // 如果有两个标记且有一个空格return [line[missingIndex], line[missingIndex + 1]]; // 返回空格位置}}return null; // 未找到合适位置}// 寻找一个随机的合法落子位置findRandomMove() {let emptyCells: number[][] = []; // 存储空格位置for (let i = 0; i < 3; i++) {for (let j = 0; j < 3; j++) {if (this.board[i][j].value === '') { // 如果单元格为空emptyCells.push([i, j]); // 添加到空格列表}}}if (emptyCells.length > 0) { // 如果有空格return emptyCells[Math.floor(Math.random() * emptyCells.length)]; // 随机选择一个空格}return null; // 未找到空格}// 构建游戏界面build() {Column({ space: 20 }) { // 创建主列布局Flex({ wrap: FlexWrap.Wrap }) { // 创建主行布局ForEach(this.board, (row: GridCell[], _index: number) => { // 遍历每一行ForEach(row, (cell: GridCell, _index: number) => { // 遍历每一个单元格Text(cell.value) // 显示单元格内的文本.width(`${this.cellSize}lpx`) // 设置宽度.height(`${this.cellSize}lpx`) // 设置高度.margin(`${this.cellMargin}lpx`) // 设置边距.fontSize(`${this.cellSize / 2}lpx`) // 设置字体大小.textAlign(TextAlign.Center) // 居中文本.backgroundColor(cell.value === 'X' ? Color.Red : // 设置背景颜色cell.value === 'O' ? Color.Blue : Color.Gray).fontColor(Color.White) // 设置字体颜色.borderRadius(5) // 设置圆角.onClick(() => { // 点击事件this.placeMark(cell.rowIndex, cell.colIndex); // 落子});})})}.width(`${(this.cellSize + this.cellMargin * 2) * 3}lpx`) // 设置宽度Button('重新开始') // 重新开始按钮.width('50%') // 设置宽度.height('10%') // 设置高度.onClick(() => { // 点击事件this.initGame(); // 重新开始游戏});}.width('100%') // 设置宽度.height('100%'); // 设置高度}
}
相关文章:
鸿蒙NEXT小游戏开发:井字棋
1. 引言 井字棋是一款经典的两人对战游戏,简单易懂,适合各个年龄段的玩家。本文将介绍如何使用鸿蒙NEXT框架开发一个井字棋游戏,涵盖游戏逻辑、界面设计及AI对战功能。 2. 开发环境准备 电脑系统:windows 10 开发工具:…...
deep-sync开源程序插件导出您的 DeepSeek 与 public 聊天
一、软件介绍 文末提供下载 deep-sync开源程序插件导出您的 DeepSeek 与 public 聊天,这是一个浏览器扩展,它允许用户公开、私下分享他们的聊天对话,并使用密码或过期链接来增强 Deepseek Web UI。该扩展程序在 Deepseek 界面中添加了一个 “…...
4. 理解Prompt Engineering:如何让模型听懂你的需求
引言:当模型变成“实习生” 想象一下,你新招的实习生总把“帮我写份报告”理解为“做PPT”或“整理数据表”——这正是开发者与大模型对话的日常困境。某金融公司优化提示词后,合同审查准确率从72%飙升至94%。本文将用3个核心法则+5个行业案例,教你用Prompt Engineering让…...
网络编程—网络概念
目录 1 网络分类 1.1 局域网 1.2 广域网 2 常见网络概念 2.1 交换机 2.2 路由器 2.3 集线器 2.4 IP地址 2.5 端口号 2.6 协议 3 网络协议模型 3.1 OSI七层模型 3.2 TCP/IP五层模型 3.3 每层中常见的协议和作用 3.3.1 应用层 3.3.2 传输层 3.3.3 网络层 3.3.4…...
基于Rust与WebAssembly实现高性能前端计算
引言 随着Web应用的复杂性增加,前端开发者经常面临性能瓶颈。传统JavaScript在处理密集型计算任务(如大数据处理或实时图像渲染)时,往往显得力不从心。而Rust语言凭借其高性能和内存安全特性,结合WebAssembly的接近原生…...
MATLAB 代码学习
1. Cell数组 Cell数组用于存储异构数据,每个元素(称为cell)可以包含不同类型的数据(如数值、字符串、矩阵等)。 1.1 创建Cell数组 直接赋值:使用花括号{}定义内容。 students {Alice, 20, [85, 90, 78…...
SELinux
一、selinux技术详解 SELinux 概述 SELinux,即 Security-Enhanced Linux,意为安全强化的 Linux,由美国国家安全局(NSA)主导开发。开发初衷是防止系统资源被误用。在 Linux 系统中,系统资源的访问均通过程…...
Axios 相关的面试题
在跟着视频教程学习项目的时候使用了axios发送请求,但是只是跟着把代码粘贴上去,一些语法规则根本不太清楚,但是根据之前的博客学习了fetch了之后,一看axios的介绍就明白了。所以就直接展示axios的面试题吧 本文主要内容ÿ…...
Spring Cloud 跨云灾备:如何实现5分钟级区域切换?
引言:云原生时代,区域级故障的致命性与应对 在混合云与多云架构中,单个区域的宕机可能导致全局服务瘫痪(如2023年AWS美东区域故障影响超200家金融系统)。传统灾备方案依赖手动切换DNS或冷备集群,恢复时间长…...
ES6对函数参数的新设计
ES6 对函数参数进行了新的设计,主要添加了默认参数、不定参数和扩展参数: 不定参数和扩展参数可以认为恰好是相反的两个模式,不定参数是使用数组来表示多个参数,扩展参数则是将多个参数映射到一个数组。 需要注意:不定…...
爬虫【feapder框架】
feapder框架 1、简单介绍 简介 feapder上手简单、功能强大的Python爬虫框架,内置AirSpider、Spider、Task、Spider、BatchSpider四种爬虫解决不同场景的需求支持断点续爬、监控报警、浏览器渲染、海量数据去重等功能更有功能强大的爬虫管理系统feaplat为其提供方…...
python如何提取html中所有的图片链接
在Python中,你可以使用BeautifulSoup库来解析HTML内容,并提取其中所有的图片链接(即<img>标签的src属性)。以下是一个示例代码,展示了如何做到这一点: 首先,确保你已经安装了BeautifulSo…...
网络协议之系列
网络协议之基础介绍 。 网络协议之清空购物车时都发生了啥? 。...
LLaMA Factory微调后的大模型在vLLM框架中对齐对话模版
LLaMA Factory微调后的大模型Chat对话效果,与该模型使用vLLM推理架构中的对话效果,可能会出现不一致的情况。 下图是LLaMA Factory中的Chat的对话 下图是vLLM中的对话效果。 模型回答不稳定:有一半是对的,有一半是无关的。 1、未…...
群体智能优化算法-鹈鹕优化算法(Pelican Optimization Algorithm, POA,含Matlab源代码)
摘要 鹈鹕优化算法(Pelican Optimization Algorithm, POA)是一种灵感来自自然界鹈鹕觅食行为的元启发式优化算法。POA 模拟鹈鹕捕食的两个主要阶段:探索阶段和开发阶段。通过模拟鹈鹕追捕猎物的动态行为,该算法在全局探索和局部开…...
代理模式-spring关键设计模式,bean的增强,AOP的实现
以下是一个结合代理模式解决实际问题的Java实现案例,涵盖远程调用、缓存优化、访问控制等场景,包含逐行中文注释: 场景描述 开发一个跨网络的文件查看器,需实现: 远程文件访问:通过代理访问网络文件 缓存…...
前端实现单点登录(SSO)的方案
概念:单点登录(Single Sign-On, SSO)主要是在多个系统、多个浏览器或多个标签页之间共享登录状态,保证用户只需登录一次,就能访问多个关联应用,而不需要重复登录。 💡 方案分类 1. 前端级别 SS…...
在 Blazor 中使用 Chart.js 快速创建数据可视化图表
前言 BlazorChartjs 是一个在 Blazor 中使用 Chart.js 的库(支持Blazor WebAssembly和Blazor Server两种模式),它提供了简单易用的组件来帮助开发者快速集成数据可视化图表到他们的 Blazor 应用程序中。本文我们将一起来学习一下在 Blazor 中…...
SQL server 2022和SSMS的使用案例1
一,案例讲解 二,实战讲解 实战环境 你需要确保你已经安装完成SQL Server 2022 和SSMS 20.2 管理面板。点此跳转至安装教程 SQL Server2022Windows11 专业工作站SSMS20.2 1,连接数据库 打开SSMS,连接数据库。 正常连接示意图&…...
【每日算法】Day 16-1:跳表(Skip List)——Redis有序集合的核心实现原理(C++手写实现)
解锁O(log n)高效查询的链表奇迹!今日深入解析跳表的数据结构设计与实现细节,从基础概念到Redis级优化策略,彻底掌握这一平衡树的优雅替代方案。 一、跳表核心思想 跳表(Skip List) 是一种基于多层有序链表的概率型数…...
前沿科技:3D生成领域技术与应用分析
以下是关于3D生成领域的详细分析,涵盖技术发展、应用场景、挑战与未来趋势、市场动态及典型案例: 一、技术发展与核心方法 3D表示方法 显式表示:包括点云、网格(三角形或四边形)和分层深度图像(LDI),适合直接操作和渲染,但细节复杂度高。 隐式表示:如神经辐射场(NeR…...
Spring Boot 3.4.3 基于 JSqlParser 和 MyBatis 实现自定义数据权限
前言 在企业级应用中,数据权限控制是保证数据安全的重要环节。本文将详细介绍如何在 Spring Boot 3.4.3 项目中结合 JSqlParser 和 MyBatis 实现灵活的数据权限控制,通过动态 SQL 改写实现多租户、部门隔离等常见数据权限需求。 一、环境准备 确保开发环境满足以下要求: …...
GO语言学习(14)GO并发编程
目录 🌈前言 1.goroutine🌟 2.GMP模型🌟 2.1 GMP的由来☀️ 2.2 什么是GMP☀️ 3.channel 🌟 3.1 通道声明与数据传输💥 3.2 通道关闭 💥 3.3 通道遍历 💥 3.4 Select语句 Ǵ…...
【Audio开发二】Android原生音量曲线调整说明
一,客制化需求 客户方对于音量加减键从静音到最大音量十五个档位区域的音量变化趋势有定制化需求。 二,音量曲线调试流程 Android根据不同的音频流类型定义不同的曲线,曲线文件存放在/vendor/etc/audio_policy_volumes.xml或者default_volu…...
sass报错,忽略 Sass 弃用警告,降级版本
最有效的方法是创建一个 .sassrc.json 文件来配置 Sass 编译器。告诉 Sass 编译器忽略来自依赖项的警告消息。 解决方案: 1. 在项目根目录创建 .sassrc.json 文件: {"quietDeps": true }这个配置会让 Sass 编译器忽略所有来自依赖项&#x…...
spring-security原理与应用系列:HttpSecurity.filters
目录 AnyRequestMatcher WebSecurityConfig HttpSecurity AbstractInterceptUrlConfigurer AbstractAuthenticationProcessingFilter 类图 在前面的文章《spring-security原理与应用系列:securityFilterChainBuilders》中,我们遗留了一个问题&…...
JVM生产环境问题定位与解决实战(六):总结篇——问题定位思路与工具选择策略
本文已收录于《JVM生产环境问题定位与解决实战》专栏,完整系列见文末目录 引言 在前五篇文章中,我们深入探讨了JVM生产环境问题定位与解决的实战技巧,从基础的jps、jmap、jstat、jstack、jcmd等工具,到JConsole、VisualVM、MAT的…...
数据仓库项目启动与管理
数据仓库项目启动与管理 确定项目 评估项目就绪情况 项目就绪的三个条件 强力型高级业务管理发起人 对数据仓库解决方案的影响有先见之明是所在组织内有影响的领导者要求严格,但是又比较现实,会为其他成员提供强力支持 强制型业务动机 数据仓库系统和战略性业务动机紧密结合…...
并行治理机制对比:Polkadot、Ethereum 与 NEAR
治理是任何去中心化网络的基础。它塑造了社区如何发展、如何为创新提供资金、如何应对挑战以及如何随着时间的推移建立信任。随着 Web3 的不断发展,决定这些生态系统如何做出决策的治理模型也在不断发展。 在最近的一集的【The Decentralized Mic】中, Polkadot 汇…...
利用 PHP 爬虫按关键字搜索淘宝商品
在当今数字化时代,网络爬虫技术已成为获取网络数据的重要手段之一。淘宝作为国内最大的电商平台之一,拥有海量的商品信息。通过 PHP 爬虫技术,我们可以实现按关键字搜索并抓取淘宝商品信息。以下将详细介绍如何使用 PHP 实现这一功能。 一、…...
