
【引言】
井字棋是一款经典的两人对战游戏,简单易懂,适合各个年龄段的玩家。本文将介绍如何使用鸿蒙NEXT框架开发一个井字棋游戏,涵盖游戏逻辑、界面设计及AI对战功能。
【开发环境准备】
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:Mate 60 Pro
语言:ArkTS、ArkUI
【项目结构】
在本项目中,我们将创建一个名为 TicTacToe 的组件,包含以下主要部分:
游戏状态管理:使用状态管理来跟踪游戏进程,包括当前玩家、游戏板、游戏是否结束等。
游戏逻辑:实现胜利条件检查、玩家落子、AI落子等功能。
用户界面:设计游戏界面,展示游戏板和玩家的标记。
【代码实现】
- 游戏状态管理
我们使用 @State 注解来管理游戏状态,包括游戏板、当前玩家、游戏是否结束及获胜者。
- 游戏逻辑
胜利条件检查
我们定义了一个 winningLines 数组,包含所有可能的胜利线路。通过遍历这些线路,检查是否有玩家获胜。
玩家落子
玩家点击单元格时,调用 placeMark 方法,更新游戏板并检查胜利条件。
AI落子
AI会根据当前局势选择最佳落子位置,使用 findWinningMove 方法检查是否有胜利机会,若没有则随机选择。
- 用户界面
使用鸿蒙NEXT的布局组件构建游戏界面,展示游戏板和重新开始按钮。
总结
通过本文的介绍,我们实现了一个简单的井字棋游戏,涵盖了游戏逻辑、状态管理和用户界面设计。鸿蒙NEXT框架提供了强大的组件化开发能力,使得开发过程更加高效。希望这篇文章能帮助你更好地理解鸿蒙NEXT的开发方式,并激发你开发更多有趣的应用。
【完整代码】
import { promptAction } from '@kit.ArkUI';
const winningLines = [
[0, 0, 0, 1, 0, 2],
[1, 0, 1, 1, 1, 2],
[2, 0, 2, 1, 2, 2],
[0, 0, 1, 0, 2, 0],
[0, 1, 1, 1, 2, 1],
[0, 2, 1, 2, 2, 2],
[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';
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') {
this.aiMove();
}
}
}
}
aiMove() {
let moveFound = false;
let bestMove: null | number[] = null;
bestMove = this.findWinningMove('O');
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%');
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.