鸿蒙NEXT小游戏开发:井字棋

news2025/5/11 5:50:47

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'; // 设置当前玩家为X
    this.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%'); // 设置高度
  }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

deep-sync开源程序插件导出您的 DeepSeek 与 public 聊天

一、软件介绍 文末提供下载 deep-sync开源程序插件导出您的 DeepSeek 与 public 聊天&#xff0c;这是一个浏览器扩展&#xff0c;它允许用户公开、私下分享他们的聊天对话&#xff0c;并使用密码或过期链接来增强 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…

SELinux

一、selinux技术详解 SELinux 概述 SELinux&#xff0c;即 Security-Enhanced Linux&#xff0c;意为安全强化的 Linux&#xff0c;由美国国家安全局&#xff08;NSA&#xff09;主导开发。开发初衷是防止系统资源被误用。在 Linux 系统中&#xff0c;系统资源的访问均通过程…

ES6对函数参数的新设计

ES6 对函数参数进行了新的设计&#xff0c;主要添加了默认参数、不定参数和扩展参数&#xff1a; 不定参数和扩展参数可以认为恰好是相反的两个模式&#xff0c;不定参数是使用数组来表示多个参数&#xff0c;扩展参数则是将多个参数映射到一个数组。 需要注意&#xff1a;不定…

LLaMA Factory微调后的大模型在vLLM框架中对齐对话模版

LLaMA Factory微调后的大模型Chat对话效果&#xff0c;与该模型使用vLLM推理架构中的对话效果&#xff0c;可能会出现不一致的情况。 下图是LLaMA Factory中的Chat的对话 下图是vLLM中的对话效果。 模型回答不稳定&#xff1a;有一半是对的&#xff0c;有一半是无关的。 1、未…

群体智能优化算法-鹈鹕优化算法(Pelican Optimization Algorithm, POA,含Matlab源代码)

摘要 鹈鹕优化算法&#xff08;Pelican Optimization Algorithm, POA&#xff09;是一种灵感来自自然界鹈鹕觅食行为的元启发式优化算法。POA 模拟鹈鹕捕食的两个主要阶段&#xff1a;探索阶段和开发阶段。通过模拟鹈鹕追捕猎物的动态行为&#xff0c;该算法在全局探索和局部开…

在 Blazor 中使用 Chart.js 快速创建数据可视化图表

前言 BlazorChartjs 是一个在 Blazor 中使用 Chart.js 的库&#xff08;支持Blazor WebAssembly和Blazor Server两种模式&#xff09;&#xff0c;它提供了简单易用的组件来帮助开发者快速集成数据可视化图表到他们的 Blazor 应用程序中。本文我们将一起来学习一下在 Blazor 中…

SQL server 2022和SSMS的使用案例1

一&#xff0c;案例讲解 二&#xff0c;实战讲解 实战环境 你需要确保你已经安装完成SQL Server 2022 和SSMS 20.2 管理面板。点此跳转至安装教程 SQL Server2022Windows11 专业工作站SSMS20.2 1&#xff0c;连接数据库 打开SSMS&#xff0c;连接数据库。 正常连接示意图&…

GO语言学习(14)GO并发编程

目录 &#x1f308;前言 1.goroutine&#x1f31f; 2.GMP模型&#x1f31f; 2.1 GMP的由来☀️ 2.2 什么是GMP☀️ 3.channel &#x1f31f; 3.1 通道声明与数据传输&#x1f4a5; 3.2 通道关闭 &#x1f4a5; 3.3 通道遍历 &#x1f4a5; 3.4 Select语句 &#x1f4…

【Audio开发二】Android原生音量曲线调整说明

一&#xff0c;客制化需求 客户方对于音量加减键从静音到最大音量十五个档位区域的音量变化趋势有定制化需求。 二&#xff0c;音量曲线调试流程 Android根据不同的音频流类型定义不同的曲线&#xff0c;曲线文件存放在/vendor/etc/audio_policy_volumes.xml或者default_volu…

spring-security原理与应用系列:HttpSecurity.filters

目录 AnyRequestMatcher WebSecurityConfig HttpSecurity AbstractInterceptUrlConfigurer AbstractAuthenticationProcessingFilter 类图 在前面的文章《spring-security原理与应用系列&#xff1a;securityFilterChainBuilders》中&#xff0c;我们遗留了一个问题&…

JVM生产环境问题定位与解决实战(六):总结篇——问题定位思路与工具选择策略

本文已收录于《JVM生产环境问题定位与解决实战》专栏&#xff0c;完整系列见文末目录 引言 在前五篇文章中&#xff0c;我们深入探讨了JVM生产环境问题定位与解决的实战技巧&#xff0c;从基础的jps、jmap、jstat、jstack、jcmd等工具&#xff0c;到JConsole、VisualVM、MAT的…

并行治理机制对比:Polkadot、Ethereum 与 NEAR

治理是任何去中心化网络的基础。它塑造了社区如何发展、如何为创新提供资金、如何应对挑战以及如何随着时间的推移建立信任。随着 Web3 的不断发展&#xff0c;决定这些生态系统如何做出决策的治理模型也在不断发展。 在最近的一集的【The Decentralized Mic】中, Polkadot 汇…

TDengine tar.gz和docker两种方式安装和卸载

下载地址 3.1.1.0 Linux版本 安装包 下载地址 3.1.1.0 docker 镜像 下载地址 3.1.1.0 Window客户端 1. 将文件上传至服务器后解压 tar -zxvf TDengine-server-3.1.1.0-Linux-x64.tar.gz 2. tar.gz安装 解压文件后&#xff0c;进入相应子目录&#xff0c;执行其中的 install.…

【STM32设计】基于STM32的智能门禁管理系统(指纹+密码+刷卡+蜂鸣器报警)(代码+资料+论文)

本课题为基于单片机的智能门禁系统&#xff0c;整个系统由AS608指纹识别模块&#xff0c;矩阵键盘&#xff0c;STM32F103单片机&#xff0c;OLED液晶&#xff0c;RFID识别模块&#xff0c;继电器&#xff0c;蜂鸣器等构成&#xff0c;在使用时&#xff0c;用户可以录入新的指纹…

java知识梳理(二)

一.lambda表达式 作用&#xff1a;Lambda 表达式在 Java 8 引入&#xff0c;主要用于简化匿名内部类的写法&#xff0c;特别是在函数式编程场景中&#xff0c;比如 函数式接口、流式 API&#xff08;Streams&#xff09;、并发编程等。它让 Java 代码更简洁、可读性更强&#x…

鸿蒙Flutter实战:20. Flutter集成高德地图,同层渲染

本文以同层渲染为例&#xff0c;介绍如何集成高德地图 完整代码见 Flutter 鸿蒙版 Demo 概述 Dart 侧 核心代码如下&#xff0c;通过 OhosView 来承载原生视图 OhosView(viewType: com.shaohushuo.app/customView,onPlatformViewCreated: _onPlatformViewCreated,creation…

AI辅助下基于ArcGIS Pro的SWAT模型全流程高效建模实践与深度进阶应用

目前&#xff0c;流域水资源和水生态问题逐渐成为制约社会经济和环境可持续发展的重要因素。SWAT模型是一种基于物理机制的分布式流域水文与生态模拟模型&#xff0c;能够对流域的水循环过程、污染物迁移等过程进行精细模拟和量化分析。SWAT模型目前广泛应用于流域水文过程研究…

尚语翻译图册翻译|专业图册翻译|北京专业翻译公司推荐|专业文件翻译报价

内容概要 尚语翻译公司聚焦多语种产品图册翻译的竞价推广服务&#xff0c;通过行业垂直化运营构建差异化竞争力。其核心服务覆盖机械制造、医疗器械、电子元件三大领域&#xff0c;依托ISO 17100认证的翻译流程和Trados术语管理系统&#xff0c;实现技术文档的精准转化。为提升…