// This gets called it "manual" is false. It's where the AI is actually given the reigns. public override void DoMove() { VBoard internalVBoard = new VBoard(bm, playerNumber); Vector3Int bestMove = new Vector3Int(-1, -1, -1); bool doesWin = false; bestMove = internalVBoard.PickMove(numFutureTurnsCalculated, priorWeight, ref doesWin); // now, if the player does win, we have to make them win in the least number of possible moves. Debug.Log("Does Win? " + doesWin); if (!doesWin) { for (int i = numFutureTurnsCalculated - 1; i >= 0; i--) { doesWin = false; Vector3Int newBestMove = internalVBoard.PickMove(i, priorWeight, ref doesWin); if (!doesWin) { break; } else { bestMove = newBestMove; } } } if (bestMove.x < 0) { Debug.Log("Error with the AI - couldn't find a valid move!"); } pieces[bestMove.x].RealizeMove(new Vector2Int(bestMove.y, bestMove.z)); bm.hasFinishedMove = true; }
// evaluates the board from player1's perspective // we maximize iff curPlayer is true // doesn't work if n<0! float EvaluateBoard(int n, float priorWeight, ref bool doesWin) { if (n == 0) { return(EvaluateBoard(ref doesWin)); } CheckIfWin(ref doesWin); if (doesWin == true) { return(0); } // short-circuit if you are going to win in fewer moves! float optimalValue = curPlayer ? Mathf.NegativeInfinity : Mathf.Infinity; Vector2Int[] myPieces = curPlayer ? player1Pieces : player2Pieces; for (int i = 0; i < myPieces.Length; i++) { if (doesWin) { break; } Vector2Int piece = myPieces[i]; foreach (Vector2Int moveToPos in GetMoves(piece)) { if (doesWin) { break; } VBoard newBoard = new VBoard(this); newBoard.boardArr[piece.x, piece.y] = false; newBoard.boardArr[moveToPos.x, moveToPos.y] = true; (curPlayer ? newBoard.player1Pieces : newBoard.player2Pieces)[i] = moveToPos; float newValue = newBoard.EvaluateBoard(n - 1, priorWeight, ref doesWin); if (doesWin || (curPlayer ? (newValue > optimalValue) : (newValue < optimalValue))) { optimalValue = newValue; } } } float currentValue = EvaluateBoard(ref doesWin); return(currentValue * priorWeight + optimalValue * (1 - priorWeight)); }
// HERE we finally produce an optimal move for the AI. // The Vector3Int is really just a hack - the first coordinate is the // id of the marble to move, and the second and third coordinates are the new position. // if the first coordinate is negative, an error has occured. // just like with EvaluateBoard(int), we maximize iff we are player1 public Vector3Int PickMove(int n, float priorWeight, ref bool doesWin) { Vector3Int ret = new Vector3Int(-1, -1, -1); if (n == 0) { return(ret); } float optimalValue = curPlayer ? Mathf.NegativeInfinity : Mathf.Infinity; Vector2Int[] myPieces = curPlayer ? player1Pieces : player2Pieces; for (int i = 0; i < myPieces.Length; i++) { if (doesWin) { break; } Vector2Int piece = myPieces[i]; foreach (Vector2Int moveToPos in GetMoves(piece)) { if (doesWin) { break; } VBoard newBoard = new VBoard(this); newBoard.boardArr[piece.x, piece.y] = false; newBoard.boardArr[moveToPos.x, moveToPos.y] = true; (curPlayer ? newBoard.player1Pieces : newBoard.player2Pieces)[i] = moveToPos; float newValue = newBoard.EvaluateBoard(n - 1, priorWeight, ref doesWin); if (doesWin || (curPlayer ? (newValue > optimalValue) : (newValue < optimalValue))) { ret = new Vector3Int(i, moveToPos.x, moveToPos.y); optimalValue = newValue; } } } Debug.Log("Optimal Value: " + optimalValue); return(ret); }
// use this to copy a VBoard, e.g. to test out certain moves: public VBoard(VBoard original) { boardArr = new bool[width, height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { boardArr[i, j] = original.boardArr[i, j]; } } player1Pieces = new Vector2Int[original.player1Pieces.Length]; for (int i = 0; i < original.player1Pieces.Length; i++) { player1Pieces[i] = original.player1Pieces[i]; } player2Pieces = new Vector2Int[original.player2Pieces.Length]; for (int i = 0; i < original.player2Pieces.Length; i++) { player2Pieces[i] = original.player2Pieces[i]; } curPlayer = !original.curPlayer; originalPlayer = original.originalPlayer; }