int _zIndexOfTouchFields = Basics.Infinity(); // The touch fields always have to stay on top #endregion Fields #region Constructors // --- Methods of the class --- /// <summary>The constructor</summary> /// <param name="gameBoard">The GameBoard to consider as the basis</param> public GameBoard(Kalaha.Model.GameBoard gameBoard, Canvas gameBoardCanvas, PointerEventHandler PointerEnteredHouse, PointerEventHandler PointerExitedHouse, RoutedEventHandler TouchFieldSelected) { _gameBoard = gameBoard; _canvasToPaintOn = gameBoardCanvas; KalahPosition kalahPosition; kalahPosition = GetKalahPosition(); CreatePitsAndSetCoordinatesAndSizes(kalahPosition); CreateImagesAndTouchFieldsAndNumFieldsOfHouses(PointerEnteredHouse, PointerExitedHouse, TouchFieldSelected); CreateImagesAndNumFieldsOfKalahs(); }
/// <summary>Visually executes the move.</summary> /// <param name="move">The move to perform</param> /// <param name="pit">The array of visual pits</param> /// <param name="nextMethodToCall">The method to be called once all moves have been performed</param> /// <param name="moveFast">Flag whether or not to move the seeds very fast</param> public async static void ExecuteMove(Move move, Kalaha.View.Model.Pit[] pit, NextMethodToCall nextMethodToCall, bool moveFast) { // Take the seed movement that comes next in the move: SeedMovement seedMovement = move.GetNextSeedMovement(); // Remove this seed movement from the move: move.RemoveSeedmovement(seedMovement); // Get data out of the seed movement: Kalaha.View.Model.Pit fromPit = pit[seedMovement.FromPit]; Kalaha.View.Model.Pit toPit = pit[seedMovement.ToPit]; int numSeeds = seedMovement.NumberOfSeeds; // Prepare the move executor by telling it the total number of seeds: MoveExecutor.PrepareMove(numSeeds); //DEBUG Logging.I.LogMessage("Performing move with " + fromPit + ", " + toPit + ", and " + numSeeds + ".\n"); for (int seedIndex = 0; seedIndex < numSeeds; ++seedIndex) { Seed seedToMove = fromPit.GetSomeSeedAndRemoveIt(); Point newPlace = toPit.FindPlaceForNewSeed(seedToMove.GetWidth(), seedToMove.GetHeight()); Storyboard story = CreateLinearMoveStory(seedToMove.GetImage(), seedToMove.GetTopLeftCorner(), newPlace, moveFast); // Set the zIndex of the seed to a high number it order to put it visually "on top" of the stack: seedToMove.SetZIndex(_seedMoveCounter); _seedMoveCounter++; //DEBUG Logging.I.LogMessage("ExcuteMove (fromPit = " + fromPit + ", toPit = " + toPit + ", numSeeds = " + numSeeds + //DEBUG "), seedIndex = " + seedIndex + " -> Before asyncLock.\n"); // The following line is pretty tricky: We jump out of this method if some other seed is still being visualized. // This causes the complete call stack to return to the original call of the GameBoardPage.ButtonClickedOnPit() method. // Only if one seed is fully animated, the lock is released and the next seed will be animated: // using (var releaser = await _asyncLock.LockAsync()) { //DEBUG Logging.I.LogMessage("ExcuteMove (fromPit = " + fromPit + ", toPit = " + toPit + ", numSeeds = " + numSeeds + //DEBUG "), seedIndex = " + seedIndex + " -> Inside asyncLock.\n"); await story.BeginAsync(); story.Stop(); } // Play the sound when moving the seed: seedToMove.PlayMovingSound(); // Rotate the image to the angle that it had at the time of creation of the seed: seedToMove.RotateImage(); //DEBUG Logging.I.LogMessage("ExcuteMove (fromPit = " + fromPit + ", toPit = " + toPit + ", numSeeds = " + numSeeds + //DEBUG "), seedIndex = " + seedIndex + " -> After asyncLock.\n"); toPit.MoveSeedHere(seedToMove, newPlace); // Check if in all the asynchronous storyboards we are now done with the last storyboard for this move: if (MoveIsCompletelyVisualized()) { //DEBUG Logging.I.LogMessage("This was the last seed to be completed.\n"); // This is the last seed that has been visualized. All aynchronous calls have been executed. // If there is some seed movement left in the original move, we perform this now by calling the visualization method // recursively. If there is nothing left, and some original caller of this method told us what to do once we are done, we do it now: if (move.GetNumberOfSeedMovements() > 0) { //DEBUG Logging.I.LogMessage("Recursively calling the visualizer.\n"); ExecuteMove(move, pit, nextMethodToCall, moveFast); } else { // We are completely done with the move. if (nextMethodToCall != null) { //DEBUG Logging.I.LogMessage("Calling the next method to call.\n"); nextMethodToCall(); } } } } }
/// <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; }