static void Main(string[] args) { var board = new Board(); var computer1 = new Computer(); var computer2 = new Computer(); var computerOneMove = true; Cell nextMove; while (true) { board.Print(); if (computerOneMove) { nextMove = computer1.NextMove(board.GetBoard()); board.SetValue(nextMove.X, nextMove.Y, computer1.MySymbol); } else { nextMove = computer2.NextMove(board.GetBoard()); board.SetValue(nextMove.X, nextMove.Y, computer2.MySymbol); } if (board.CheckWin(nextMove.X, nextMove.Y)) { board.Print(); if (computerOneMove) { Console.WriteLine("Computer ONE wins!"); } else { Console.WriteLine("Computer TWO wins!"); } break; } else if (board.CheckDraw()) { board.Print(); Console.WriteLine("Draw game!"); break; } else { computerOneMove = !computerOneMove; } } }
/// <summary> /// Находит наилучший ход для заданной позиции /// </summary> /// <param name="currentState">Текущее состояние игровой доски</param> /// <returns>Возвращает лучший ход для заданной позиции</returns> public Cell NextMove(char[,] currentState) { var board = new Board(); for (var i = 0; i < Board.N; i++) { for (var j = 0; j < Board.N; j++) { if (currentState[i, j] != '_') { board.SetValue(i, j, currentState[i, j]); } } } MySymbol = board.OccupiedCells % 2 == 0 ? 'x' : 'o'; OpponentSymbol = MySymbol == 'x' ? 'o' : 'x'; // Первый ход в игре. if (board.OccupiedCells == 0) { return(new Cell(Board.N / 2, Board.N / 2)); } var desiredCells = GetDesiredCells(board); var bestMove = new Cell(desiredCells[0].X, desiredCells[0].Y); var weightOfBestMove = Int32.MinValue; var alpha = Int32.MinValue; var beta = Int32.MaxValue; foreach (Cell cell in desiredCells) { var weightOfCurrentMove = MinimaxAlphaBeta(board, cell.X, cell.Y, Depth, true, alpha, beta); if (weightOfCurrentMove > weightOfBestMove) { weightOfBestMove = weightOfCurrentMove; alpha = weightOfBestMove; bestMove.X = cell.X; bestMove.Y = cell.Y; } } return(bestMove); }
/// <summary> /// Оценивает текущее состояние доски /// </summary> /// <param name="board">Текущее состояние игровой доски</param> /// <param name="isMax">Показывает кто ходил последним. Если равно /// true, значит я ходил последним</param> /// <returns>Возвращает оценку текущего состояния доски</returns> private int EvaluationOfCurrentState(Board board, bool isMax) { int[] myAttacks = { 0, 0, 0, 0 }; int[] opponentAttacks = { 0, 0, 0, 0 }; var potentialOpenFour = false; for (var i = 0; i < Board.N; i++) { for (var j = 0; j < Board.N; j++) { if (board.GetValue(i, j) == '_') { // В зависимости от того, кто ходил последним проверяем закончит ли игру оппонент(или я) // в один ход, и есть ли у него(или у меня) потенциальная открытая четверка. if (board.CheckExistenceOfAdjacentSymbol(i, j, isMax ? OpponentSymbol : MySymbol)) { board.SetValue(i, j, isMax ? OpponentSymbol : MySymbol); if (board.CheckWin(i, j)) { board.SetValue(i, j, '_'); return(isMax ? Int32.MinValue : Int32.MaxValue); } else if (board.CheckDraw()) { board.SetValue(i, j, '_'); return(0); } else if (board.CheckOpenFour(i, j)) { potentialOpenFour = true; } board.SetValue(i, j, '_'); } } else { var weightOfAttack = 0; if (board.CheckRowAttack(i, j, ref weightOfAttack)) { if (board.GetValue(i, j) == MySymbol) { myAttacks[weightOfAttack]++; } else { opponentAttacks[weightOfAttack]++; } } if (board.CheckColumnAttack(i, j, ref weightOfAttack)) { if (board.GetValue(i, j) == MySymbol) { myAttacks[weightOfAttack]++; } else { opponentAttacks[weightOfAttack]++; } } // Не проверяем главные диагонали длиной меньше пяти. if (((i <= 3) && (j >= 11) && (j - i >= 11)) || ((i >= 11) && (j <= 3) && (i - j >= 11))) { if (board.CheckMainDiagonalAttack(i, j, ref weightOfAttack)) { if (board.GetValue(i, j) == MySymbol) { myAttacks[weightOfAttack]++; } else { opponentAttacks[weightOfAttack]++; } } } // Не проверяем побочные диагонали длиной меньше пяти. if (((i <= 3) && (j <= 3) && (i + j <= 3)) || ((i >= 11) && (j >= 11) && (i + j >= 25))) { if (board.CheckAntiDiagonalAttack(i, j, ref weightOfAttack)) { if (board.GetValue(i, j) == MySymbol) { myAttacks[weightOfAttack]++; } else { opponentAttacks[weightOfAttack]++; } } } } } } if (potentialOpenFour) { return(isMax ? Int32.MinValue : Int32.MaxValue); } var multiplier = 1; var eval = 0; for (var i = 1; i < 4; i++) { eval += myAttacks[i] * multiplier - opponentAttacks[i] * multiplier * 10; multiplier *= 100; } return(eval); }
/// <summary> /// Алгоритм minimax с альфа-бета отсечениями /// </summary> /// <param name="board">Текущее состояние игровой доски</param> /// <param name="i">Первая координата</param> /// <param name="j">Вторая координата</param> /// <param name="depth">Глубина рекурсии</param> /// <param name="isMax">Показывает кто ходил последним. Если равно /// true, значит я ходил последним, т.е. мой ход максимизируется</param> /// <param name="alpha">Нижняя граница состояний игры, найденная /// для игрока на текущий момент</param> /// <param name="beta">Верхняя граница состояний игры, найденная /// для игрока на текущий момент</param> /// <returns>Возвращает оценку хода</returns> private int MinimaxAlphaBeta(Board board, int i, int j, int depth, bool isMax, int alpha, int beta) { board.SetValue(i, j, isMax ? MySymbol : OpponentSymbol); if (board.CheckWin(i, j)) { board.SetValue(i, j, '_'); return(isMax ? Int32.MaxValue : Int32.MinValue); } else if (board.CheckDraw()) { board.SetValue(i, j, '_'); return(0); } if (depth == 0) { var eval = EvaluationOfCurrentState(board, isMax); board.SetValue(i, j, '_'); return(eval); } var desiredCells = GetDesiredCells(board); // Ход противника. if (isMax) { var weightOfOpponentTreeNode = Int32.MaxValue; // Минимизация веса хода оппонента. foreach (Cell cell in desiredCells) { var weightOfMyTreeNode = MinimaxAlphaBeta(board, cell.X, cell.Y, depth - 1, false, alpha, beta); if (weightOfMyTreeNode < weightOfOpponentTreeNode) { weightOfOpponentTreeNode = weightOfMyTreeNode; } if (weightOfOpponentTreeNode < beta) { beta = weightOfOpponentTreeNode; } if (alpha >= beta) { break; } } board.SetValue(i, j, '_'); return(weightOfOpponentTreeNode); } // Мой ход. else { var weightOfMyTreeNode = Int32.MinValue; // Максимизация веса моего хода. foreach (Cell cell in desiredCells) { var weightOfOpponentTreeNode = MinimaxAlphaBeta(board, cell.X, cell.Y, depth - 1, true, alpha, beta); if (weightOfOpponentTreeNode > weightOfMyTreeNode) { weightOfMyTreeNode = weightOfOpponentTreeNode; } if (weightOfMyTreeNode > alpha) { alpha = weightOfMyTreeNode; } if (alpha >= beta) { break; } } board.SetValue(i, j, '_'); return(weightOfMyTreeNode); } }