private int Minimize(CheckersNode node, int depth) { if (depth == 0) { return(node.GetScore()); } GenerateChildNodes(node); for (int i = 0; i < node.children.Count; i++) { node.UpdateScore(false, Maximize(node.children[i], depth - 1)); } return(node.GetScore()); }
//Reference:https://www.youtube.com/watch?v=zp3VMe0Jpf8 private CheckersNode RunMinimax(int player) { CheckersNode root = null; if (player == 0) { root = new CheckersNode(size, player, null, player1Tokens, player2Tokens); } else { root = new CheckersNode(size, player, null, player2Tokens, player1Tokens); } root.BoardState = mainBoard; int depth = maxMinimaxDepth; Maximize(root, depth); return(root.GetMaxChildNode()); }
private void AddMoveChildNode(Token token, int[,] board, int x, int y, Vector3 position, CheckersNode node, List <Token> opponentTokens, List <Token> playerTokens, bool isJump) { int originalX = token.xPosition; int originalY = token.yPosition; board[originalX, originalY] = -1; board[x, y] = token.player; token.gameObject.transform.position = position; CheckersNode childNode = new CheckersNode(size, (token.player + 1) % 2, node, opponentTokens, playerTokens); childNode.BoardState = board; childNode.xStart = originalX; childNode.yStart = originalY; childNode.xEnd = x; childNode.yEnd = y; childNode.isJump = isJump; board[originalX, originalY] = token.player; board[x, y] = -1; token.gameObject.transform.position = new Vector3(originalX, originalY, -1.0f); node.children.Add(childNode); }
private void GenerateChildNodes(CheckersNode node) { int[,] board = node.BoardState; List <Token> playerTokens = new List <Token>(); for (int i = 0; i < node.playerTokens.Count; i++) { Token token = new Token(node.playerTokens[i]); playerTokens.Add(token); } List <Token> opponentTokens = new List <Token>(); for (int i = 0; i < node.opponentTokens.Count; i++) { Token token = new Token(node.opponentTokens[i]); opponentTokens.Add(token); } bool hasJumped = false; //Find all possible moveable token positions for (int i = 0; i < playerTokens.Count; i++) { Token token = playerTokens[i]; if (token.IsAlive) { //Check jumps int x = token.xPosition - 2; int y = token.yPosition + (token.player == 0 ? 2 : -2); Vector3 position = new Vector3(x, y); if (IsValidJumpPosition(position, token, board)) { RemoveJumpedToken(position, token, opponentTokens, board); AddMoveChildNode(token, board, x, y, position, node, opponentTokens, playerTokens, true); RevertJumpedToken(position, token, opponentTokens, board); hasJumped = true; } x = token.xPosition + 2; position = new Vector3(x, y); if (IsValidJumpPosition(position, token, board)) { RemoveJumpedToken(position, token, opponentTokens, board); AddMoveChildNode(token, board, x, y, position, node, opponentTokens, playerTokens, true); RevertJumpedToken(position, token, opponentTokens, board); hasJumped = true; } //We always want to prioritise jumping if (!hasJumped) { //Check moves x = token.xPosition - 1; y = token.yPosition + (token.player == 0 ? 1 : -1); position = new Vector3(x, y); if (IsValidMovePosition(position, token, board)) { AddMoveChildNode(token, board, x, y, position, node, opponentTokens, playerTokens, false); } x = token.xPosition + 1; position = new Vector3(x, y); if (IsValidMovePosition(position, token, board)) { AddMoveChildNode(token, board, x, y, position, node, opponentTokens, playerTokens, false); } } if (token.IsKing) { //Check king jumps x = token.xPosition - 2; y = token.yPosition + (token.player == 0 ? -2 : 2); position = new Vector3(x, y); if (IsValidJumpPosition(position, token, board)) { RemoveJumpedToken(position, token, opponentTokens, board); AddMoveChildNode(token, board, x, y, position, node, opponentTokens, playerTokens, true); RevertJumpedToken(position, token, opponentTokens, board); hasJumped = true; } x = token.xPosition + 2; position = new Vector3(x, y); if (IsValidJumpPosition(position, token, board)) { RemoveJumpedToken(position, token, opponentTokens, board); AddMoveChildNode(token, board, x, y, position, node, opponentTokens, playerTokens, true); RevertJumpedToken(position, token, opponentTokens, board); hasJumped = true; } if (!hasJumped) { //Check king moves x = token.xPosition - 1; y = token.yPosition + (token.player == 0 ? -1 : 1); position = new Vector3(x, y); if (IsValidMovePosition(position, token, board)) { AddMoveChildNode(token, board, x, y, position, node, opponentTokens, playerTokens, false); } x = token.xPosition + 1; position = new Vector3(x, y); if (IsValidMovePosition(position, token, board)) { AddMoveChildNode(token, board, x, y, position, node, opponentTokens, playerTokens, false); } } } } } }
private int Simulate(CheckersNode node, int winPlayer) { int[,] boardState = node.BoardState; List <Token> playerTokens = new List <Token>(); List <Token> opponentTokens = new List <Token>(); int currentPlayer = node.player; for (int i = 0; i < node.playerTokens.Count; i++) { Token playerToken = new Token(node.playerTokens[i]); Token opponentToken = new Token(node.opponentTokens[i]); playerTokens.Add(playerToken); opponentTokens.Add(opponentToken); } int score = 0; int currentMoveCount = moveCount; int winner = -1; bool instaLose = false; while (winner == -1 && currentMoveCount < maxNumberOfMoves) { int index; Vector3 destination; bool isJump; //Get random move GetRandomMove(boardState, playerTokens, out index, out destination, out isJump); if (index == -1) { //insta lose fam instaLose = true; break; } //Move Token token = playerTokens[index]; if (isJump) { RemoveJumpedToken(destination, token, opponentTokens, boardState); } int originalX = token.xPosition; int originalY = token.yPosition; boardState[originalX, originalY] = -1; boardState[(int)destination.x, (int)destination.y] = token.player; token.gameObject.transform.position = destination; //Swap players currentPlayer = (currentPlayer + 1) % 2; List <Token> temp = new List <Token>(playerTokens); playerTokens.Clear(); playerTokens.AddRange(opponentTokens); opponentTokens.Clear(); opponentTokens.AddRange(temp); temp.Clear(); winner = CheckForWin(boardState, true); currentMoveCount++; } if (instaLose) { if (currentPlayer == winPlayer) { score = 0; } else { for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { if (boardState[i, j] == winPlayer) { score++; } } } } } else { if (winner == -1) { winner = CheckForWin(boardState, false); } if (winPlayer == winner) { for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { if (boardState[i, j] == winPlayer) { score++; } } } } } return(score); }
//Reference:https://www.youtube.com/watch?v=UXW2yZndl7U private CheckersNode RunMCTS(int player) { CheckersNode root = null; if (player == 0) { root = new CheckersNode(size, player, null, player1Tokens, player2Tokens); } else { root = new CheckersNode(size, player, null, player2Tokens, player1Tokens); } root.BoardState = mainBoard; DateTime start = DateTime.Now; int maxDepth = 0; while ((DateTime.Now - start).TotalMilliseconds < maxMCTSTime) { CheckersNode current = root; int depth = 0; //Selection while (current.children.Count != 0) { current = current.GetChildWithBestUCB1(); depth++; } if (maxDepth < depth) { maxDepth = depth; } //Simulation if (current.totalPlayouts == 0) { int score = Simulate(current, player); //Back Propagate current.BackPropagate(score); } else { //Expansion GenerateChildNodes(current); } } totalMCTSDepth += maxDepth; if (root.children.Count > 0) { //Find node with best score float bestScore = root.children[0].GetScore() / root.children[0].totalPlayouts; int selectedIndex = 0; for (int i = 1; i < root.children.Count; i++) { float score = root.children[i].GetScore() / root.children[i].totalPlayouts; if (bestScore < score) { bestScore = score; selectedIndex = i; } } return(root.children[selectedIndex]); } else { return(null); } }
// Update is called once per frame void Update() { if (!stopGame) { if (activePlayer == 0) { if (Input.GetMouseButtonDown(0) && !doubleAI) { RaycastHit hit; Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (currentPlayState == PlayState.SELECTION) { if (Physics.Raycast(ray, out hit, Mathf.Infinity, LayerMask.GetMask("Token"))) { selectedToken = GetSelectedToken(player1Tokens, hit.transform.position); if (selectedToken != null && selectedToken.IsAlive) { currentPlayState = PlayState.PLACEMENT; } else { selectedToken = null; } } } else { if (Physics.Raycast(ray, out hit, Mathf.Infinity, LayerMask.GetMask("Board"))) { Vector3 hitPosition = hit.transform.position; if (IsValidMovePosition(hitPosition, selectedToken, mainBoard)) { UpdateSelectedTokenPosition(hitPosition, selectedToken, mainBoard); stopGame = CheckForWin(mainBoard, true) != -1; gameText.text = "Player " + (activePlayer + 1) + " wins!"; activePlayer = (activePlayer + 1) % 2; moveCount++; } else if (IsValidJumpPosition(hitPosition, selectedToken, mainBoard)) { RemoveJumpedToken(hitPosition, selectedToken, player2Tokens, mainBoard); UpdateSelectedTokenPosition(hitPosition, selectedToken, mainBoard); stopGame = CheckForWin(mainBoard, true) != -1; gameText.text = "Player " + (activePlayer + 1) + " wins!"; activePlayer = (activePlayer + 1) % 2; moveCount++; } } } } if (doubleAI) { bool instaLose = false; if (moveCount == 0) { //Reference:http://www.quadibloc.com/other/bo1211.htm //Make the best move possible (5,2) to (6,3) Vector3 tokenPosition = new Vector3(5, 2, -1.0f); Vector3 movePosition = new Vector3(6, 3); Token tokenToMove = GetSelectedToken(player1Tokens, tokenPosition); UpdateSelectedTokenPosition(movePosition, tokenToMove, mainBoard); } else { //AI code CheckersNode node = RunMCTS(activePlayer); mctsTurns++; if (node != null) { Vector3 tokenPosition = new Vector3(node.xStart, node.yStart, -1.0f); Vector3 movePosition = new Vector3(node.xEnd, node.yEnd); Token tokenToMove = GetSelectedToken(player1Tokens, tokenPosition); Debug.Assert(tokenToMove.IsAlive); if (node.isJump) { RemoveJumpedToken(movePosition, tokenToMove, player2Tokens, mainBoard); } UpdateSelectedTokenPosition(movePosition, tokenToMove, mainBoard); } else { instaLose = true; } } if (!instaLose) { stopGame = CheckForWin(mainBoard, true) != -1; if (stopGame) { gameText.text = "Player " + (activePlayer + 1) + " wins!"; player1Wins++; } activePlayer = (activePlayer + 1) % 2; moveCount++; } else { //Player 2 wins stopGame = true; gameText.text = "Player " + (((activePlayer + 1) % 2) + 1) + " wins!"; player2Wins++; } } } else { //AI code DateTime start = DateTime.Now; CheckersNode node = RunMinimax(activePlayer); totalMinimaxTime += (DateTime.Now - start).TotalMilliseconds; minimaxTurns++; if (node != null) { Vector3 tokenPosition = new Vector3(node.xStart, node.yStart, -1.0f); Vector3 movePosition = new Vector3(node.xEnd, node.yEnd); Token tokenToMove = GetSelectedToken(player2Tokens, tokenPosition); Debug.Assert(tokenToMove.IsAlive); if (node.isJump) { RemoveJumpedToken(movePosition, tokenToMove, player1Tokens, mainBoard); } UpdateSelectedTokenPosition(movePosition, tokenToMove, mainBoard); stopGame = CheckForWin(mainBoard, true) != -1; if (stopGame) { gameText.text = "Player " + (activePlayer + 1) + " wins!"; player2Wins++; } activePlayer = (activePlayer + 1) % 2; moveCount++; } else { //Player 1 wins stopGame = true; gameText.text = "Player " + (((activePlayer + 1) % 2) + 1) + " wins!"; player1Wins++; } } if (moveCount == maxNumberOfMoves) { stopGame = true; int winner = CheckForWin(mainBoard, false); gameText.text = "Player " + (winner + 1) + " wins!"; if (winner == 0) { player1Wins++; } else { player2Wins++; } } if (!stopGame) { gameText.text = "Player " + (activePlayer + 1); } DummyObjectPool.Instance.ResetAll(); } else { currentIteration++; if (currentIteration < numberOfIterations) { ResetBoard(); } else { if (gameText.text != "Finished") { gameText.text = "Finished"; WriteLog(); Application.Quit(); } } } }