进行四排 - 第3部分:进行动作
#javascript #html #教程 #game

介绍

在以前的博客文章中,我们设置了我们的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的值已添加到板上第二列的底部。

如果您这样做,恭喜!

如果没有,请仔细浏览此帖子,以检查您可能犯的错误。