写点什么

鸿蒙开发案例:实现一个带 AI 的井字游戏(Tic Tac Toe)

作者:zhongcx
  • 2024-10-12
    广东
  • 本文字数:3930 字

    阅读完需:约 13 分钟

井字游戏(Tic Tac Toe)是一个经典的两人游戏,玩家轮流在 3x3 的网格中放置标记(通常是“X”和“O”),目的是成为第一个在水平、垂直或对角线上获得三个连续标记的玩家。本文将介绍如何使用 ArkUI 框架实现一个带简单 AI 的井字游戏。

实现细节

1. 游戏状态

游戏状态包括游戏板(board)、当前玩家(currentPlayer)、游戏是否结束(isGameOver)以及获胜者(winner)。

2. 初始化游戏

在 aboutToAppear 生命周期方法中初始化游戏板,并调用 initGame 方法来重置游戏状态。

3. 玩家落子

placeMark 方法允许玩家在指定位置放置标记,并检查游戏是否结束。如果游戏结束,显示对话框询问是否重新开始。

4. AI 落子

aiMove 方法负责 AI 的落子逻辑,首先检查 AI 是否有胜利的机会,然后检查玩家是否有胜利的机会并阻止之,如果两者都不存在,则随机选择一个空格落子。

5. 胜利条件检查

checkForWinner 方法遍历所有可能的胜利线路,并检查是否有玩家赢得了游戏。

6. 随机落子

findRandomMove 方法从所有空格中随机选择一个位置进行落子。

完整代码

import { promptAction } from '@kit.ArkUI';
const winningLines = [ [0, 0, 0, 1, 0, 2], // Horizontal 1 [1, 0, 1, 1, 1, 2], // Horizontal 2 [2, 0, 2, 1, 2, 2], // Horizontal 3 [0, 0, 1, 0, 2, 0], // Vertical 1 [0, 1, 1, 1, 2, 1], // Vertical 2 [0, 2, 1, 2, 2, 2], // Vertical 3 [0, 0, 1, 1, 2, 2], // Diagonal \ [0, 2, 1, 1, 2, 0]// Diagonal /];
@ObservedV2class GridCell { @Trace value: string = ""; // 当前格子的值 rowIndex: number = 0; // 格子所在的行号 colIndex: number = 0; // 格子所在的列号
constructor(rowIndex: number, colIndex: number) { this.rowIndex = rowIndex; this.colIndex = colIndex; }}@Entry@Componentstruct 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%'); // 设置高度 }}
复制代码


用户头像

zhongcx

关注

还未添加个人签名 2024-09-27 加入

还未添加个人简介

评论

发布
暂无评论
鸿蒙开发案例:实现一个带AI的井字游戏(Tic Tac Toe)_zhongcx_InfoQ写作社区