介绍
在以前的博客文章中,我们设置了我们的FourInARowGame
班级字段。
现在,我们准备开始实施玩家移动并相应地更新游戏状态。
创建方法
我们将在称为playMove()
:
的FourInARowGame
类中创建新方法开始
export default class FourInARowGame {
// ..
static createBoard() {
// ..
}
playMove(columnIndex) {
return {
board: this.currentBoard,
winner: Constants.PlayerColor.NONE,
status: {
value: Constants.MoveStatus.SUCCESS,
},
winLine: [],
};
}
}
注意:我们只是现在返回模拟数据。
,我们将在以后正确实现此方法。 playMove
方法实际上采用了columnIndex
参数。通过此输入,将尝试采取行动,并将评估并返回尝试移动的结果。上面的代码示例显示返回的a MoveResult
对象。它可以包含:
- 董事会的当前状态
- 获胜者
- 移动的状态
- 胜利线 - 如果发现,则位于连续4个代币的一系列位置。
与我们的状态机互动
暴露我们的状态机对象
现在,要尝试我们的代码,我们将尝试与浏览器的四圈状态机进行交互。
首先,我们需要公开对象,以便浏览器的控制台可以访问它。我们可以做到这一点的一种方法是将FourInARowGame
类的实例添加到window
对象。让我们现在就这样做。用以下内容替换src/index.js
的内容:
import FourInARowGame from "./FourInARowGame.js";
window.fourInARowGame = new FourInARowGame();
现在,确保A HTTP服务器正在项目的根部运行,并导航到Web浏览器中的服务器地址。
打开网络浏览器中的控制台窗口,然后输入:window.fourInARowGame
。您应该看到FourInARowGame
实例对象的输出。在Google Chrome中看起来像这样:
FourInARowGame {startingColor: 'yellow', currentTurn: 'yellow', status: 'start', board: Array(6)}
也有扩展对象的选项,因此您可以查看有关对象的更多详细信息。
如果您看不到FourInARow
实例对象的某种表示形式,请浏览您的代码并检查是否正确遵循说明!
玩(假)举动
现在,让我们在浏览器控制台窗口中从FourInARow
实例中调用playMove
方法。
首先让我们将状态机存储在一个更易于引用的变量中,称为game
:
let game = window.fourInARowGame;
现在,将columnIndex
参数设置为0
:
调用playMove
方法
game.playMove(0);
您的浏览器控制台窗口应输出类似于我们之前讨论的MoveResult
对象的东西:
{board: Array(6), winner: 'none', status: {…}, winLine: Array(0)}
扩展时,我们将获得更多详细信息:
{board: Array(6), winner: 'none', status: {…}, winLine: Array(0)}
board: (6) [Uint8Array(7), Uint8Array(7), Uint8Array(7), Uint8Array(7), Uint8Array(7), Uint8Array(7)]
status: {value: 'success'}
winLine: []
winner: "none"
[[Prototype]]: Object
现在,我们知道了调用playMove()
后所期望的结果类型,让我们正确实施该方法,以便它根据董事会状态返回真实数据。
实施该方法(这次是真实的!)
首先,我们将在我们的FourInARowGame
类中更新playMove()
方法,以便在允许玩家进行移动之前检查游戏的当前状态。
对于玩家在比赛结束时能够执行动作是没有意义的。
这也是一个很好的机会,可以将游戏的当前状态从“开始”更新为“正在进行”。
playMove(columnIndex) {
switch (this.status) {
case Constants.GameStatus.START:
this.status = GameStatus.IN_PROGRESS;
break;
case Constants.GameStatus.DRAW:
case Constants.GameStatus.WIN:
// The game is over at this point so
// re-evaluate the latest board, returning the same game status
// and board details.
// TODO: Implement this properly
console.log("Game already ended in win or draw. re-evaluating latest game state);
default:
break;
}
}
赢得胜利或抽奖时,我们将重新评估最新的游戏状态。
现在,让我们继续专注于允许玩家采取改变游戏状态的举动。
我们将首先创建一种称为performMove()
的方法:
performMove(columnIndex) {
// ...
}
现在我们需要创建一个可以修改的当前板的副本,而无需更改this.currentBoard
的值。
为此,我们将创建一个称为deepBoardCopy()
的静态方法:
static deepBoardCopy(oldBoard) {
let newBoard = new Array(Constants.BoardDimensions.ROWS);
for (let rowIndex = 0; rowIndex < Constants.BoardDimensions.ROWS; rowIndex++) {
newBoard[rowIndex] = new Uint8Array(Constants.BoardDimensions.COLUMNS);
for (let columnIndex = 0; columnIndex < Constants.BoardDimensions.COLUMNS; columnIndex++) {
newBoard[rowIndex][columnIndex] = oldBoard[rowIndex][columnIndex];
}
}
return newBoard;
}
现在将木板副本存储在称为nextBoard
的变量中:
performMove(columnIndex) {
let nextBoard = FourInARowGame.deepBoardCopy(this.currentBoard);
}
我们需要做的接下来要做的就是实际执行板上的动作,为此,我们将创建一种称为tryPerformMove()
的方法:
tryPerformMove(columnIndex, nextBoard) {
let isMoveValid = false;
for (let i = nextBoard.length - 1; i > -1; i--) {
let boardRow = nextBoard[i];
let boardPosition = boardRow[columnIndex];
if (boardPosition !== Constants.BoardToken.NONE) {
continue;
}
boardRow[columnIndex] = FourInARowGame.playerColorToBoardToken(this.currentTurn);
isMoveValid = true;
break;
}
if (!isMoveValid) {
return {
status: Constants.MoveStatus.INVALID,
};
}
return {
status: Constants.MoveStatus.SUCCESS,
board: nextBoard
};
}
上面的方法检查板上的列中的播放器从底部到顶部指定的空位置,然后尝试将当前播放器的令牌添加到董事会位置。
然后返回结果。
这里有一种静态方法称为playerColorToBoardToken
,这将集合在板位置中的值设置为与当前播放器的颜色相关的数字值。
也将其添加到FourInARowGame
类:
static createBoard() {
// ...
}
static playerColorToBoardToken(playerColor) {
switch (playerColor) {
case Constants.PlayerColor.YELLOW:
return Constants.BoardToken.YELLOW;
case Constants.PlayerColor.RED:
return Constants.BoardToken.RED;
default:
return Constants.BoardToken.NONE;
}
}
现在,我们将返回到performMove()
方法,并将当前的游戏板设置为从tryPerformMove()
方法返回的对象中的板。
performMove(columnIndex) {
let nextBoard = FourInARowGame.deepBoardCopy(this.currentBoard);
let moveAttemptResult = this.tryPerformMove(columnIndex, nextBoard);
if (moveAttemptResult.status === Constants.MoveStatus.INVALID) {
return {
board: nextBoard,
winner: Constants.PlayerColor.NONE,
status: {
message: "Returned column is filled",
value: Constants.MoveStatus.INVALID
},
winLine: []
}
}
// From this point, the board move was successful.
this.currentBoard = moveAttemptResult.board;
}
如果moveAttemptResult.status
的值是MoveStatus.Invalid
,则我们返回一个MoveResult
,该MoveResult
与我们在face playMove()
方法实现中返回的移动数据相同。
最后,我们需要创建一种称为evaluateGame
的方法,该方法将用于检查游戏的状态是否已更改,然后返回MoveResult
。
目前,我们将仅返回成功举动的MoveResult
,这也表明游戏仍在进行中。
evaluateGame(board) {
// From this point, we can assume that a successful move was made and the game will
// continue on.
return {
board: board,
winner: Constants.PlayerColor.NONE,
status: {
value: Constants.MoveStatus.SUCCESS
},
winLine: []
};
}
现在,MoveResult
根据玩家放置的令牌显示了更新的板值。
要结束我们最初的适当的playMove()
方法实现,我们将在我们的playMove()
中添加几行。
playMove(columnIndex) {
switch (this.status) {
case Constants.GameStatus.START:
this.status = Constants.GameStatus.IN_PROGRESS;
break;
case Constants.GameStatus.DRAW:
case Constants.GameStatus.WIN:
// The game is over at this point so
// re-evaluate the latest board, returning the same game status
// and board details.
// TODO: Implement this properly
console.log("Game already ended in win or draw. re-evaluating latest game state);
default:
break;
}
let moveResults = this.performMove(columnIndex);
this.currentTurn = this.currentTurn === Constants.PlayerColor.YELLOW
? Constants.PlayerColor.RED
: Constants.PlayerColor.YELLOW;
return moveResults;
}
我们执行动作,将当前转弯更新为对方的玩家的转弯,然后返回移动的结果。
测试我们的更改
我们添加了很多更改,让我们测试它们。
假设您在项目根部运行本地HTTP服务器,请导航到Web浏览器中的服务器地址,然后再次打开浏览器控制台窗口。
现在在浏览器控制台中输入以下行:
let game = window.fourInARowGame;
现在,将playMove
方法与columnIndex
参数设置为1
,而不是0
,以将令牌添加到游戏板上的第二列中:
game.playMove(1);
现在您应该看到返回的MoveResult
对象。如果扩展对象,然后扩展了board
字段,您会发现1
的值已添加到板上第二列的底部。
如果您这样做,恭喜!
如果没有,请仔细浏览此帖子,以检查您可能犯的错误。