/// <summary>Moves all seeds into the respective Kalahs.</summary> public Move CollectFinalSeeds(Player firstPlayer, Player secondPlayer) { Move move = new Move(); Player currentPlayer = firstPlayer; for (int playerIndex = 0; playerIndex < 2; ++playerIndex) { Pit currentPlayersKalah = GetKalahOfPlayer(currentPlayer); int internalIndexOfKalah = GetInternalIndexOfKalah(currentPlayer); for (int pitIndex = 0; pitIndex < _numHousesPerPlayer; ++pitIndex) { Pit currentPit = GetPitOfPlayer(currentPlayer, pitIndex); if (currentPit.GetNumberofSeeds() > 0) { int currentPitsInternalIndex = GetInternalPitIndex(currentPlayer, pitIndex); move.AddSeedMovement(new SeedMovement(currentPitsInternalIndex, internalIndexOfKalah, currentPit.GetNumberofSeeds())); currentPlayersKalah.AddSeeds(currentPit.GetNumberofSeeds()); currentPit.RemoveAllSeeds(); } } currentPlayer = secondPlayer; } return move; }
/// <summary> /// Returns the next undo move to be taken together with the player whose turn it is after that move. /// The undo move is removed from the internal list of the UndoManager. /// </summary> /// <param name="undoMove">The move to undo the last move that one of the two players made.</param> /// <param name="nextPlayer">The player whose turn it is after the undoMove</param> public void GetNextUndoMove(ref Move undoMove, ref Player nextPlayer) { if (_moveList.Count == 0) { throw new PSTException("UndoManager.GetNextUndoMove: No move left."); } // Store the respective first elements of the internal lists in the reference parameters: undoMove = _moveList[0]; nextPlayer = _playerList[0]; // Remove both entries from the lists: _moveList.RemoveAt(0); _playerList.RemoveAt(0); }
/// <summary> /// Returns the backward move of the given move. /// A "backward move" of a move has all the seed movements in the converse order, and each seed movement has swapped "FromPit" and "ToPit". /// </summary> public Move GetBackwardMove() { Move backMove = new Move(); // Walk backwards through the list of seed movements: for (int index = _seedMovementList.Count - 1; index >= 0; --index) { // Take the seed movement at position "index": SeedMovement origSeedMovement = _seedMovementList[index]; // Create a new seed movement that has swapped FromPit and ToPit: SeedMovement backSeedMovement = new SeedMovement(origSeedMovement.ToPit, origSeedMovement.FromPit, origSeedMovement.NumberOfSeeds); // Add the new seed movement to the backward move: backMove.AddSeedMovement(backSeedMovement); } return backMove; }
/// <summary> /// Remembers the given move as a move that has to be added to the last "normal" move of the same player. /// This is supposed to be used for capture moves or "final" moves. /// </summary> /// <param name="move">The move to remember</param> /// <param name="currentPlayer">The player who made the move</param> public void RememberFollowUpMove(Move move, Player currentPlayer) { // We need an existing player list: if (_playerList.Count == 0) { throw new PSTException("UndoManager.RememberFollowUpMove: player list is empty."); } // We need an existing move list: if (_moveList.Count == 0) { throw new PSTException("UndoManager.RememberFollowUpMove: move list is empty."); } // We can only add a follow-up move for the same player as exists as the first entry in the list of players: if (_playerList[0] != currentPlayer) { throw new PSTException("UndoManager.RememberFollowUpMove: Unexpected player: " + currentPlayer + ". Expected " + _playerList[0] + "."); } // Get the move's backward move: Move backMove = move.GetBackwardMove(); // Integrate the backward move into the move that is the first in the already existing list of "undo moves": _moveList[0].IntegrateMove(backMove); }
/// <summary> /// Remembers the given move as a "normal" by the given player. /// </summary> /// <param name="move">The move to remember</param> /// <param name="currentPlayer">The player who made the move</param> public void RememberNormalNove(Move move, Player currentPlayer) { // A "normal" move is quite easy to remember: We store a new list entry with the move's "backward move". // Get the move's backward move: Move backMove = move.GetBackwardMove(); // Add the backward move and the current player at the beginnings of the respecive lists: _moveList.Insert(0, backMove); _playerList.Insert(0, currentPlayer); }
/// <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> /// Adds the SeedMovements of "moveToIntegrate" in the parameter /// </summary> /// <param name="move"></param> /// <returns></returns> public void IntegrateMove(Move moveToIntegrate) { for (int index = 0; index < moveToIntegrate.GetNumberOfSeedMovements(); ++index) { AddSeedMovementToTheFront(moveToIntegrate.GetSeedMovement(index)); } }
/// <summary> /// The given player wins his own pit (given by "ownPitIndex") and the opposite pit. Both pit's contents move into /// the player's Kalah.</summary> /// <param name="player">The player who wins the pit's contents</param> /// <param name="ownPitIndex">The index of the player's pit</param> /// <param name="captureOpponentsHouse">A flag whether the seeds in the opponent's house may be captured, too</param> /// <param name="returnMove">A flag whether or not to return the move in detail</param> /// <returns>The details of this move</returns> public Move PlayerWinsHouses(Player player, int ownPitIndex, bool captureOpponentsHouse, bool returnMove) { Pit ownPit = GetPitOfPlayer(player, ownPitIndex); Pit kalahToBeFilled = GetKalahOfPlayer(player); int internalKalahIndex = -1; // Only used when generating a Move Move move = null; if (returnMove) { move = new Move(); internalKalahIndex = GetInternalIndexOfKalah(player); } if (captureOpponentsHouse) { // Also the opponent's house shall be captured. Pit opponentsPit = GetOppositeHouse(player, ownPitIndex); if (returnMove) { // Move the opponent's seeds to the player's Kalah: move.AddSeedMovement(new SeedMovement(GetInternalIndexOfOppositeHouse(player, ownPitIndex), internalKalahIndex, opponentsPit.GetNumberofSeeds())); } kalahToBeFilled.AddSeeds(opponentsPit.GetNumberofSeeds()); opponentsPit.RemoveAllSeeds(); } if (returnMove) { // Move the one seed from the own house into the player's Kalah: move.AddSeedMovement(new SeedMovement(GetInternalPitIndex(player, ownPitIndex), internalKalahIndex, 1)); } // Fill the own Kalah with the own seed and remove the own seed from the house: kalahToBeFilled.AddSeeds(1); ownPit.RemoveAllSeeds(); return move; }
/// <summary> /// Performs the move given in the parameter. /// </summary> /// <param name="move">The move to perform</param> public void PerformMove(Move move) { //DEBUG Logging.I.LogMessage("Entering GameBoard.PerformMove().\n"); // Go through all seed movements of the move and add and remove seeds of the respective pits: for (int index = 0; index < move.GetNumberOfSeedMovements(); ++index) { SeedMovement seedMovement = move.GetSeedMovement(index); //DEBUG Logging.I.LogMessage(" Removing " + seedMovement.NumberOfSeeds + " seeds from pit " + seedMovement.FromPit + //DEBUG " and adding them to pit " + seedMovement.ToPit + ".\n"); _pit[seedMovement.FromPit].RemoveSeeds(seedMovement.NumberOfSeeds); _pit[seedMovement.ToPit].AddSeeds(seedMovement.NumberOfSeeds); } }
/// <summary>Selects the given house and distributes the seeds of that house among /// the following pits, one seed per pit. Takes into account the direction of sowing, depending on the rules.</summary> /// <param name="player">The player whose pit is to be moved</param> /// <param name="houseIndexToBeMoved">The index of the house to be moved</param> /// <param name="lastSeedFellInOwnKalah">A reference parameter which gets the information whether the /// <param name="lastSeedFellIntoEmptyOwnHouse">A reference parameter which gets the information whether the /// last seed fell into empty pit that is owned by the player.</param> /// <param name="lastSeedsHouse">If "lastSeedFellIntoEmptyOwnHouse" is true, then this reference parameter /// gets the index of the house that the last seed fell into. Otherwise "lastSeedsHouse" gets the value -1 (for "undefined"). /// <exception cref="KalahaException">Index out of range</exception> public Move MoveHouse(Player player, int houseIndexToBeMoved, ref bool lastSeedFellIntoOwnKalah, ref bool lastSeedFellIntoEmptyOwnHouse, ref int lastSeedsHouse, bool returnMove) { if ((houseIndexToBeMoved < 0) || (houseIndexToBeMoved >= _numHousesPerPlayer)) { throw new PSTException("MovePit: houseIndex out of range: " + houseIndexToBeMoved); } lastSeedFellIntoOwnKalah = false; lastSeedFellIntoEmptyOwnHouse = false; lastSeedsHouse = -1; // Create a move object if this is requested by the caller of the method: Move move = null; if (returnMove) { move = new Move(); } // Calculate the array index of the house from which we take the seeds out: int internalHouseIndex = houseIndexToBeMoved + (player.GetId() * (_numHousesPerPlayer + 1)); // Keep the number of seeds to be distributed in mind: int numOfSeeds = _pit[internalHouseIndex].GetNumberofSeeds(); // The distribution of seeds is done in a fast way, using the internal array structure. // Find out about the sowing direction: It is clockwise if the rule is simply defined that way or if the rule // is set to "Cross-Kalah" and the number of seeds is odd: bool sowingDirectionIsClockwise = ((Rules.I.GetDirectionOfSowing() == Rules.DirectionOfSowing.Clockwise) || ((Rules.I.GetDirectionOfSowing() == Rules.DirectionOfSowing.CrossKalah) && ((numOfSeeds % 2) == 1))); // Empty the pit we start from: _pit[internalHouseIndex].RemoveAllSeeds(); // Distribute the seeds: int maybeOneMore = 0; Pit kalahOfOpponent = GetKalahOfOpponent(player); int loopIndex = 0; for (; loopIndex < numOfSeeds+maybeOneMore; ++loopIndex) { // Depending on the sowing direction, we set the index of the pit that gets a seed accordingly: int pitIndex = -1; if (sowingDirectionIsClockwise) { pitIndex = (internalHouseIndex + 10*_numPitsInTotal - loopIndex - 1) % _numPitsInTotal; } else { pitIndex = (internalHouseIndex + 1 + loopIndex) % _numPitsInTotal; } if (_pit[pitIndex] != kalahOfOpponent) { // This is not the Kalah of the opponent --> Add a seed: _pit[pitIndex].AddASeed(); if (returnMove) { // Store this step in the move: move.AddSeedMovement(new SeedMovement(internalHouseIndex, pitIndex, 1)); } } else { // This is the opponent's Kalah --> Do not add a seed but instead increase the loop by 1: ++maybeOneMore; } } // Calculate which pit the last seed fell into, depending on the sowing direction: int pitOfLastSeed = -1; if (sowingDirectionIsClockwise) { pitOfLastSeed = (internalHouseIndex + 10*_numPitsInTotal - loopIndex) % _numPitsInTotal; } else { pitOfLastSeed = (internalHouseIndex + loopIndex) % _numPitsInTotal; } lastSeedFellIntoOwnKalah = (_pit[pitOfLastSeed] == GetKalahOfPlayer(player)); if (!lastSeedFellIntoOwnKalah) { // Check whether the last seed fell into an empty own pit: if ((_pit[pitOfLastSeed].GetNumberofSeeds() == 1) && IsPlayersOwnPit(player, pitOfLastSeed)) { // Yes, it did. lastSeedFellIntoEmptyOwnHouse = true; // We have to calculate the player's pit from the internal pit: lastSeedsHouse = pitOfLastSeed % (_numHousesPerPlayer + 1); } } return move; }
/// <summary>Shows the given move in detail.</summary> /// <param name="move">The move to be shown</param> /// <param name="nextMethodToCall">The method that shall be called after the visualization has finished</param> /// <param name="moveFast">Flag whether or not to move the seeds very fast</param> public void VisualizeMove(Move move, NextMethodToCall nextMethodToCall, bool moveFast = false) { MoveExecutor.ExecuteMove(move, _pit, nextMethodToCall, moveFast); }