/// <summary> /// Game theory algorithm to maximize the possible return value. /// If the maximum recursion depth is reached or the current player's pits are all empty, the difference of the /// Kalahs is regarded as the gains of the respective recursion path. /// Otherwise, in a loop each non-empty pit is moved, and the minimizer for this move is called. /// </summary> /// <param name="gameBoard">The game board that is currently valid in the recursion path</param> /// <param name="currentPlayer">The current player</param> /// <param name="currentRecursionDepth">The current recursion depth we are already in</param> /// <param name="maxRecursionDepth">The maximum recursion depth (a constant)</param> /// <returns></returns> private int Maximizer(Kalaha.Model.GameBoard gameBoard, Player currentPlayer, int currentRecursionDepth, int maxRecursionDepth) { // Logging.I.LogMessage("Maximizer: entering depth " + currentRecursionDepth + Environment.NewLine, // Logging.LogLevel.DeepDebug); gameBoard.LogBoard(currentPlayer, Opponent(currentPlayer), Logging.LogLevel.DeepDebug); if (gameBoard.PlayerOnlyHasEmptyHouses(currentPlayer) || gameBoard.PlayerOnlyHasEmptyHouses(Opponent(currentPlayer))) { // One player's pits are all empty. // --> Calculate the difference between the two Kalahs: int retValue = gameBoard.GetKalahOfPlayer(currentPlayer).GetNumberofSeeds() - gameBoard.GetKalahOfPlayer(Opponent(currentPlayer)).GetNumberofSeeds(); if (Rules.I.CaptureSeedsAtEndOfGame()) { // The rule to capture all seeds in the houses at the end is enabled // --> Take these seeds into account, too: retValue = retValue + gameBoard.SumOfAllSeedsOfPlayer(currentPlayer) - gameBoard.SumOfAllSeedsOfPlayer(Opponent(currentPlayer)); } // Logging.I.LogMessage("Maximizer on depth " + currentRecursionDepth + ": Pits empty. Returning " + // retValue + "." + Environment.NewLine, // Logging.LogLevel.DeepDebug); return retValue; } if (currentRecursionDepth == maxRecursionDepth) { // We have reached the "bottom" of the recursion. // --> Return the difference of the two Kalahs the maximum value: int retValue = gameBoard.GetKalahOfPlayer(currentPlayer).GetNumberofSeeds() - gameBoard.GetKalahOfPlayer(Opponent(currentPlayer)).GetNumberofSeeds(); // Logging.I.LogMessage("Maximizer on depth " + currentRecursionDepth + ": maxRecursionDepth " + maxRecursionDepth + // " reached. Returning " + retValue + "." + Environment.NewLine, // Logging.LogLevel.DeepDebug); return retValue; } // We are still in the recursion path and drill deeper down. int max = -Basics.Infinity(); for (int pitIndex = 0; pitIndex < gameBoard.GetNumOfHousesPerPlayer(); ++pitIndex) { // Create a copy of the current gameboard and take the copy to perform the move of one pit: Kalaha.Model.GameBoard localGameBoard = gameBoard.GetACopy(); if (localGameBoard.GetPitOfPlayer(currentPlayer, pitIndex).ContainsASeed()) { // The pit of the current pitIndex has a seed in it, so perform the move now: bool lastSeedFellIntoOwnKalah = false; bool lastSeedFellIntoEmptyOwnPit = false; int lastSeedsPit = -1; // Logging.I.LogMessage("Maximizer on depth " + currentRecursionDepth + ": Moving pit " + // pitIndex + "." + Environment.NewLine, // Logging.LogLevel.DeepDebug); localGameBoard.MoveHouse(currentPlayer, pitIndex, ref lastSeedFellIntoOwnKalah, ref lastSeedFellIntoEmptyOwnPit, ref lastSeedsPit, false); // Do not generate the move's intermediate steps on a seed level int opt = -Basics.Infinity(); if (Rules.I.PlayAgainWhenLastSeedInOwnKalah() && lastSeedFellIntoOwnKalah) { // It is the same player's turn again. // --> Continue with the maximizer recursion: opt = Maximizer(localGameBoard, currentPlayer, currentRecursionDepth + 1, maxRecursionDepth); // Logging.I.LogMessage("Maximizer on depth " + currentRecursionDepth + ": Got back opt " + // opt + " from Maximizer one level deeper." + Environment.NewLine, // Logging.LogLevel.DeepDebug); } else { // Check if the "winning opposite pit" rule applies: if (lastSeedFellIntoEmptyOwnPit) { // The last seed fell into an own empty house. This may mean that we can execute the capture move. int numOfSeedsOfOpponentsHouse = gameBoard.GetOppositeHouse(currentPlayer, lastSeedsPit).GetNumberofSeeds(); bool captureMoveMayBeExecuted = false; bool captureOpponentsHouse = false; // Find out if under the current rule setting a capture move may be executed and whether the opponent's house may be captured: Rules.I.GetCapturePermissions(numOfSeedsOfOpponentsHouse, ref captureMoveMayBeExecuted, ref captureOpponentsHouse); if (captureMoveMayBeExecuted) { // Logging.I.LogMessage("Maximizer on depth " + currentRecursionDepth + // ": The last seed fell into the empty house #" + (lastSeedsPit + 1) + ".\n", // Logging.LogLevel.DeepDebug); localGameBoard.PlayerWinsHouses(currentPlayer, lastSeedsPit, captureOpponentsHouse, false); } else { // Logging.I.LogMessage("Maximizer on depth " + currentRecursionDepth + // ": The last seed fell into the empty house #" + (lastSeedsPit + 1) + // " but rules prohibit the capture.\n", // Logging.LogLevel.DeepDebug); } } // It is the opponent's turn. // --> Continue with the minimizer recursion: opt = Minimizer(localGameBoard, Opponent(currentPlayer), currentRecursionDepth + 1, maxRecursionDepth); // Logging.I.LogMessage("Maximizer on depth " + currentRecursionDepth + ": Got back opt " + // opt + " from Minimizer one level deeper." + Environment.NewLine, // Logging.LogLevel.DeepDebug); } if (currentRecursionDepth == 1) { // We are in the highest layer of the recursion, so we store the optimum we got // in the respective array of maxima: _maxValue[pitIndex] = opt; // Logging.I.LogMessage("Maximizer on depth " + currentRecursionDepth + ": maxValue[" + pitIndex + // "] = " + opt + "." + Environment.NewLine, // Logging.LogLevel.DeepDebug); } // In any case we are interested in the best result we get from the deeper levels: if (opt >= max) { // We have found a new maximum. max = opt; // Logging.I.LogMessage("Maximizer on depth " + currentRecursionDepth + ": New maximum found: " + max + // "." + Environment.NewLine, // Logging.LogLevel.DeepDebug); } } } // Return the maximum we found across all possible moves: return max; }