回复
鸿蒙NEXT开发案例:井字棋
zhongcx
发布于 2024-11-30 17:52
浏览
0收藏
【引言】
井字棋是一款经典的两人对战游戏,简单易懂,适合各个年龄段的玩家。本文将介绍如何使用鸿蒙NEXT框架开发一个井字棋游戏,涵盖游戏逻辑、界面设计及AI对战功能。
【开发环境准备】
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:Mate 60 Pro
语言:ArkTS、ArkUI
【项目结构】
在本项目中,我们将创建一个名为 TicTacToe 的组件,包含以下主要部分:
游戏状态管理:使用状态管理来跟踪游戏进程,包括当前玩家、游戏板、游戏是否结束等。
游戏逻辑:实现胜利条件检查、玩家落子、AI落子等功能。
用户界面:设计游戏界面,展示游戏板和玩家的标记。
【代码实现】
- 游戏状态管理
我们使用 @State 注解来管理游戏状态,包括游戏板、当前玩家、游戏是否结束及获胜者。
@State board: GridCell[][] = []; // 游戏板
@State currentPlayer: string = 'X'; // 当前玩家
@State isGameOver: boolean = false; // 游戏是否结束
@State winner: string = ''; // 获胜者
- 游戏逻辑
胜利条件检查
我们定义了一个 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]);
}
}
- 用户界面
使用鸿蒙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(); });
}
}
总结
通过本文的介绍,我们实现了一个简单的井字棋游戏,涵盖了游戏逻辑、状态管理和用户界面设计。鸿蒙NEXT框架提供了强大的组件化开发能力,使得开发过程更加高效。希望这篇文章能帮助你更好地理解鸿蒙NEXT的开发方式,并激发你开发更多有趣的应用。
【完整代码】
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%'); // 设置高度
}
}
分类
标签
赞
收藏
回复
相关推荐