private Bitmap drawBoard(Board b) { Bitmap bitmap = new Bitmap(panelBoard.Width, panelBoard.Height); Graphics g = Graphics.FromImage(bitmap); int drawWidth = (panelBoard.Width - 1) / b.width; int drawHeight = (panelBoard.Height - 1) / b.height; const int pad = 3; for (int boardX = 0; boardX < b.width; boardX++) { for (int boardY = 0; boardY < b.height; boardY++) { int drawX = boardX * drawWidth; int drawY = boardY * drawHeight; //draw grid g.DrawRectangle(Pens.Black, drawX, drawY, drawWidth, drawHeight); //draw player if (!b.fields[boardX, boardY].IsEmpty()) g.DrawImage(b.fields[boardX, boardY].owner.image, drawX, drawY, drawWidth, drawHeight); if (checkBoxHelp.Checked) { //draw help if (b.IsValidMove(b.curPlayer, boardX, boardY)) g.DrawEllipse(new Pen(b.curPlayer.color), drawX + drawWidth / 4 + pad + 1, drawY + drawHeight / 4 + pad + 1, drawWidth / 2 - pad * 2, drawHeight / 2 - pad * 2); } } } return bitmap; }
private static void Main(string[] args) { Board b = new Board(); while (true) { bool playing = true; b.SetForNewGame(); bool isWhite = true; while (playing) { isWhite = !isWhite; Board.Color player = isWhite ? Board.Color.White : Board.Color.Black; Console.Clear(); Console.Write(" "); for (int i = 0; i < 8; i++) { Console.Write($" {i + 1}"); } Console.Write(" X"); Console.WriteLine(); for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { Console.Write(x == 0 ? $"{y + 1} " : " "); Console.ForegroundColor = b.IsValidMove(player, y, x) ? ConsoleColor.Green : ConsoleColor.Red; Console.Write(GetChar(b.GetSquareContents(y, x))); } Console.ResetColor(); Console.WriteLine(); } Console.WriteLine("Y"); if (!b.HasAnyValidMove(player)) { if (!b.HasAnyValidMove(Board.Invert(player))) { Console.WriteLine(b.WhiteCount == b.BlackCount ? "Tie" : $"{(b.WhiteCount > b.BlackCount ? "White" : "Black")} won"); Console.ReadKey(); playing = false; } continue; } Console.WriteLine($"Current player: {(isWhite ? "White (+)" : "Black (-)")}"); Console.WriteLine($"{b.GetValidMoveCount(player)} moves possible"); int nX; int nY; bool first = true; do { if (!first) { Console.WriteLine("Invalid move"); } first = false; Console.Write("x> "); nX = int.Parse(Console.ReadKey().KeyChar.ToString()) - 1; Console.WriteLine(); Console.Write("y> "); nY = int.Parse(Console.ReadKey().KeyChar.ToString()) - 1; Console.WriteLine(); } while (!b.IsValidMove(player, nY, nX)); b.MakeMove(player, nY, nX); } } }
// // This function uses look ahead to evaluate all valid moves for a // given player color and returns the best move it can find. // private ComputerMove GetBestMove(Board board, int color, int depth, int alpha, int beta) { // Initialize the best move. ComputerMove bestMove = new ComputerMove(-1, -1); bestMove.rank = -color * ReversiForm.maxRank; // Find out how many valid moves we have so we can initialize the // mobility score. int validMoves = board.GetValidMoveCount(color); // Start at a random position on the board. This way, if two or // more moves are equally good, we'll take one of them at random. Random random = new Random(); int rowStart = random.Next(8); int colStart = random.Next(8); // Check all valid moves. int i, j; for (i = 0; i < 8; i++) for (j = 0; j < 8; j++) { // Get the row and column. int row = (rowStart + i) % 8; int col = (colStart + j) % 8; if (board.IsValidMove(color, row, col)) { // Update the progress bar for each move when on the // first look ahead depth level. if (depth == 1) this.BeginInvoke(new UpdateStatusProgressDelegate(this.UpdateStatusProgress)); // Make the move. ComputerMove testMove = new ComputerMove(row, col); Board testBoard = new Board(board); testBoard.MakeMove(color, testMove.row, testMove.col); int score = testBoard.WhiteCount - testBoard.BlackCount; // Check the board. int nextColor = -color; int forfeit = 0; bool isEndGame = false; int opponentValidMoves = testBoard.GetValidMoveCount(nextColor); if (opponentValidMoves == 0) { // The opponent cannot move, count the forfeit. forfeit = color; // Switch back to the original color. nextColor = -nextColor; // If that player cannot make a move either, the // game is over. if (!testBoard.HasAnyValidMove(nextColor)) isEndGame = true; } // If we reached the end of the look ahead (end game or // max depth), evaluate the board and set the move // rank. if (isEndGame || depth == this.lookAheadDepth) { // For an end game, max the ranking and add on the // final score. if (isEndGame) { // Negative value for black win. if (score < 0) testMove.rank = -ReversiForm.maxRank + score; // Positive value for white win. else if (score > 0) testMove.rank = ReversiForm.maxRank + score; // Zero for a draw. else testMove.rank = 0; } // It's not an end game so calculate the move rank. else testMove.rank = this.forfeitWeight * forfeit + this.frontierWeight * (testBoard.BlackFrontierCount - testBoard.WhiteFrontierCount) + this.mobilityWeight * color * (validMoves - opponentValidMoves) + this.stabilityWeight * (testBoard.WhiteSafeCount - testBoard.BlackSafeCount) + score; } // Otherwise, perform a look ahead. else { ComputerMove nextMove = this.GetBestMove(testBoard, nextColor, depth + 1, alpha, beta); // Pull up the rank. testMove.rank = nextMove.rank; // Forfeits are cumulative, so if the move did not // result in an end game, add any current forfeit // value to the rank. if (forfeit != 0 && Math.Abs(testMove.rank) < ReversiForm.maxRank) testMove.rank += forfeitWeight * forfeit; // Adjust the alpha and beta values, if necessary. if (color == Board.White && testMove.rank > beta) beta = testMove.rank; if (color == Board.Black && testMove.rank < alpha) alpha = testMove.rank; } // Perform a cutoff if the rank is outside tha alpha-beta range. if (color == Board.White && testMove.rank > alpha) { testMove.rank = alpha; return testMove; } if (color == Board.Black && testMove.rank < beta) { testMove.rank = beta; return testMove; } // If this is the first move tested, assume it is the // best for now. if (bestMove.row < 0) bestMove = testMove; // Otherwise, compare the test move to the current // best move and take the one that is better for this // color. else if (color * testMove.rank > color * bestMove.rank) bestMove = testMove; } } // Return the best move found. return bestMove; }
// // This function uses look ahead to evaluate all valid moves for a // given player color and returns the best move it can find. // private ComputerMove minimax(Board board, int color, int alpha, int beta, int depth = 1) { // Initialize the best move. ComputerMove bestMove = new ComputerMove(-1, -1); bestMove.rank = -color * int.MaxValue; // Start at a random position on the board. This way, if two or // more moves are equally good, we'll take one of them at random. Random random = new Random(); int rowStart = random.Next(10); int colStart = random.Next(10); // Check every square on the board and try to perform // each and every move (up to the given depth) // to calculate best move for the current player. // We are certain that there are valid moves at this point // as we wouldn't be here if there weren't any // checks are performed in the StartTurn function. for (int i = 0; i < 10; ++i) for (int j = 0; j < 10; ++j) { // Get the row and column. int row = (rowStart + i) % 10; int col = (colStart + j) % 10; if (board.IsValidMove(color, row, col)) { // We found a valid move now we copy the board // and try to make that move on the new board // to evaluate its weight. Board tempBoard = new Board(board); tempBoard.MakeMove(color, row, col); // Holds the current move being tested. ComputerMove moveBeingChecked = new ComputerMove(row, col); // Holds the color ID of a player that has no mobility // Initialized to 0 in case both are mobile. int forfeit = 0; // Holds the color ID of the next player. int nextPlayer = -color; // A flag that indicates whether either of // the players is mobile or if game is over. bool gameOver = false; // Just like in StartTurn, after passing a turn // to the next player due to no mobility for the other // we need to check if the new player is mobile // if not then neither can move and game is over. int opponentMobility = tempBoard.GetValidMoveCount(nextPlayer); if (opponentMobility == 0) { forfeit = nextPlayer; nextPlayer = color; if (!tempBoard.HasAnyValidMove(color)) gameOver = true; } if (depth >= lookAheadDepth || gameOver) { // Initialize AI Parameters. if (this.currentColor == Board.White) { moveBeingChecked.rank = heuristicFunction1(tempBoard, forfeit, color, opponentMobility); if (tempBoard.EmptyCount > 0 && Board.isCorner(row, col)) moveBeingChecked.rank += 200; } else moveBeingChecked.rank = heuristicFunction2(tempBoard); } else { ComputerMove nextMove = minimax(tempBoard, nextPlayer, alpha, beta, ++depth); moveBeingChecked.rank = nextMove.rank; // Adjust the alpha and beta values, if necessary. if (color == Board.White && moveBeingChecked.rank > beta) beta = moveBeingChecked.rank; if (color == Board.Black && moveBeingChecked.rank < alpha) alpha = moveBeingChecked.rank; } // If the alpha-beta pruning is enabled // perform a cut off if necessary. if (alphaBeta) { if (color == Board.White && moveBeingChecked.rank > alpha) { moveBeingChecked.rank = alpha; return moveBeingChecked; } if (color == Board.Black && moveBeingChecked.rank < beta) { moveBeingChecked.rank = beta; return moveBeingChecked; } } // If this is the first move tested, assume it is the // best for now. otherwise, compare the test move // to the current best move and take the one that // is better for this color. if (bestMove.row < 0) bestMove = moveBeingChecked; else if (color * moveBeingChecked.rank < color * bestMove.rank) bestMove = moveBeingChecked; } } // Return the best move found. return bestMove; }