public void TestMoveItemAbove() { var interactionService = ServiceContainer.Resolve<IGameInteractionService> (); var gameState = new GameState (); interactionService.InitGameState (gameState); var emptyLoc = gameState.GameField.GetUnusedItemLocation (); var emptyItem = gameState.GameField.GetItem (emptyLoc.Row, emptyLoc.Column); int centerRow = gameState.GameField.Rows / 2; int centerCol = gameState.GameField.Columns / 2; // Move the empty item to the center to have identical test conditions. var oldItem = gameState.GameField.SetItem (centerRow, centerCol, emptyItem); gameState.GameField.SetItem (emptyLoc.Row, emptyLoc.Column, oldItem); // Update empty location. emptyLoc = gameState.GameField.GetUnusedItemLocation (); emptyItem = gameState.GameField.GetItem (emptyLoc.Row, emptyLoc.Column); var itemAbove = gameState.GameField.GetItem (emptyLoc.Row - 1, emptyLoc.Column); var movedPyramid = interactionService.Move (gameState, GameMoveType.MoveItemAbove); // Ensure that the returned item really is the one from above. Assert.That (movedPyramid.Id == itemAbove.Id); }
public override GameMoveType GetNextMove(GameState gameState, IGameInteractionService interactionService) { var move = base.GetNextMove (gameState, interactionService); // Sleep to allow better visualization. // TODO: Sleep? Or make async? Thread.Sleep (1000); return move; }
/// <summary> /// Creates the game. /// </summary> /// <returns>The game.</returns> /// <param name="platformServices">Platform services.</param> /// <param name="loggingService">Logging service. Can be NULL.</param> /// <param name="visualizationService">Instance of a service that allows the game state to be visualized.</param> /// <param name="continuePreviousGame">If set to <c>true</c> continue previous game.</param> /// <param name="progress">Progress.</param> public static async Task<PyramidGame> CreateGame(IPlatformServices platformServices, ILoggingService loggingService, IGameVisualizationService visualizationService, IPlayer localPlayer, IPlayer opponentPlayer, bool continuePreviousGame, IProgress<GameAction> progress = null) { if(platformServices == null) { throw new ArgumentNullException ("platformServices", "Platform services are required to perform platform specific tasks."); } if(progress != null) { progress.Report (GameAction.RegisteringServices); } // Register all required services. GameServiceRegistrar.RegisterServices (platformServices, loggingService); // Create a game state. GameState gameState = null; // Let the interaction service initialize the game state. var gameInteractionService = ServiceContainer.Resolve<IGameInteractionService>(); if(continuePreviousGame) { var persistenceService = ServiceContainer.Resolve<IPersistenceService> (); if(progress != null) { progress.Report (GameAction.LoadingGameState); } gameState = await persistenceService.LoadGameStateAsync (); } // Init a new game? if(gameState == null) { gameState = new GameState (); gameInteractionService.InitGameState (gameState); } // Create an instance of the game itself. var game = new PyramidGame () { GameState = gameState, // This can be NULL. Then nothing will be displayed. VisualizationService = visualizationService, LocalPlayer = localPlayer, OpponentPlayer = opponentPlayer }; return game; }
public GameMoveType GetNextMove(GameState gameState, IGameInteractionService interactionService) { while(true) { var move = GameMoveType.None; var key = Console.ReadKey ().Key; switch(key) { // Quit the program if ESC is pressed. case ConsoleKey.Escape: if(this.GameQuitRequested != null) { this.GameQuitRequested (); } return GameMoveType.None; case ConsoleKey.UpArrow: // Arrow up = move the item below the empty space UP. move = GameMoveType.MoveItemBelow; break; case ConsoleKey.DownArrow: // Arrow down = move the item above the empty space DOWN. move = GameMoveType.MoveItemAbove; break; case ConsoleKey.LeftArrow: // Arrow left = move the item right of the empty space LEFT. move = GameMoveType.MoveItemRight; break; case ConsoleKey.RightArrow: // Arrow right = move the item left of the empty space RIGHT. move = GameMoveType.MoveItemLeft; break; default: Console.Beep (); break; } if(interactionService.CanMove(gameState.GameField, move)) { return move; } } }
public async Task<bool> PersistGameStateAsync (GameState gameState) { try { using(var stream = this.platformServices.GetGameStatePersistenceStream(true)) using(var writer = new StreamWriter(stream)) { // Save score. await writer.WriteLineAsync (string.Format("localplayerscore:{0}", gameState.LocalPlayerScore)); await writer.WriteLineAsync (string.Format("opponentplayerscore:{0}", gameState.OpponentPlayerScore)); // Save current player. await writer.WriteLineAsync (string.Format("currentplayer:{0}", gameState.CurrentPlayer.ToString())); // Save card stack. var cardsString = new StringBuilder(); foreach(TreasureType card in gameState.Cards) { cardsString.Append(card.ToString()).Append("|"); } await writer.WriteLineAsync (string.Format("cards:{0}", cardsString.ToString())); // Save game field items. for(int row = 0; row < gameState.GameField.Rows; row++) { var fieldString = new StringBuilder(); for(int col = 0; col < gameState.GameField.Columns; col++) { var item = gameState.GameField.GetItem (row, col); fieldString.AppendFormat ("{0},{1},{2}|", item.Id, item.Pyramid.ToString (), item.Treasure.ToString ()); } await writer.WriteLineAsync (string.Format("field:{0}|{1}", row, fieldString.ToString())); } } } catch(Exception ex) { return false; } return true; }
public void TestInitGameState() { Exception error = null; var interactionService = ServiceContainer.Resolve<IGameInteractionService> (); var gameState = new GameState (); // Just check that the init routine does not crash. // The correct content of the game state can only be tested by visualizing it. try { interactionService.InitGameState (gameState); } catch(Exception ex) { error = ex; } Assert.IsNull (error, "Error: " + error); // Check that the card deck is properly filled. Assert.AreEqual (gameState.Cards.Count, 36, "Amount of cards incorrect."); }
public void TestCanMove() { var interactionService = ServiceContainer.Resolve<IGameInteractionService> (); var gameState = new GameState (); interactionService.InitGameState (gameState); var emptyLoc = gameState.GameField.GetUnusedItemLocation (); var emptyItem = gameState.GameField.GetItem (emptyLoc.Row, emptyLoc.Column); int centerRow = gameState.GameField.Rows / 2; int centerCol = gameState.GameField.Rows / 2; // Move the empty item to the center to have identical test conditions. var oldItem = gameState.GameField.SetItem (centerRow, centerCol, emptyItem); gameState.GameField.SetItem (emptyLoc.Row, emptyLoc.Column, oldItem); Assert.IsTrue (interactionService.CanMove (gameState.GameField, GameMoveType.MoveItemAbove), "Cannot move item above down"); Assert.IsTrue (interactionService.CanMove (gameState.GameField, GameMoveType.MoveItemBelow), "Cannot move item below up"); Assert.IsTrue (interactionService.CanMove (gameState.GameField, GameMoveType.MoveItemLeft), "Cannot move item left right"); Assert.IsTrue (interactionService.CanMove (gameState.GameField, GameMoveType.MoveItemRight), "Cannot move item right left"); }
public async Task<GameState> LoadGameStateAsync () { var gameState = new GameState (); try { using (var stream = this.platformServices.GetGameStatePersistenceStream(false)) using (var reader = new StreamReader(stream)) { while(!reader.EndOfStream) { var data = GetLineInfo (await reader.ReadLineAsync ()); PopulateGameState (gameState, data); } } } catch(Exception ex) { return null; } return gameState; }
/// <summary> /// Gets a random location on the board and avoids treasures. /// </summary> /// <returns>The random location avoiding treasures.</returns> /// <param name="gameState">Game state.</param> protected virtual GridLocation GetRandomLocationAvoidingTreasures(GameState gameState) { var platformServices = ServiceContainer.Resolve<IPlatformServices> (); var loggingService = ServiceContainer.Resolve<ILoggingService> (); GridLocation location; bool continueRandomization = false; // Loop until a (presumably) empty location was found. do { // Get a location that has not been visited recently. bool isRememberedLocation = false; do { location = new GridLocation (platformServices.GetRandomNumber (0, gameState.GameField.Rows), platformServices.GetRandomNumber (0, gameState.GameField.Columns)); isRememberedLocation = this.lastVisitedLocations.Contains (location); #if EXTENSIVE_LOGGING loggingService.Log("Randomized location '{0}' was picked previously? {1}.", location, isRememberedLocation); #endif } while(isRememberedLocation); // Remember the location to prevent visiting it again immediately. this.lastVisitedLocations.Enqueue (location); // Now check if the location has a treasure. If yes, we don't want to go there unless it is the current active treasure. var treasureMemory = this.HasLocationPresumablyTreasure(location); // We want to pick a location that we don't know about. if(treasureMemory == LocationMemory.DontKnow) { // Let's head there if the AI does not know about the location. continueRandomization = false; #if EXTENSIVE_LOGGING loggingService.Log("Randomized location. AI does not know what's at '{0}'. Active treasure: {1}.", location, gameState.GetActiveCard()); #endif } else if(treasureMemory == LocationMemory.HasNothing) { // If the AI knows that there is nothing at the location, it won't go there. continueRandomization = true; #if EXTENSIVE_LOGGING loggingService.Log("Randomized location. AI knows that there is nothing at '{0}'. Active treasure: {1}.", location, gameState.GetActiveCard()); #endif } else if(treasureMemory == LocationMemory.HasTreasure) { // If there is a treasure, the AI will avoid it, untless it is the current searched treasure. var treasureItem = gameState.GameField.GetItem(location).Treasure; if(treasureItem == gameState.GetActiveCard()) { continueRandomization = false; } else { // Keep searching a new random location. continueRandomization = true; } #if EXTENSIVE_LOGGING loggingService.Log("Randomized location. AI knows there is a treasure '{0}'. Active treasure: {1}.", location, gameState.GetActiveCard()); #endif } } while(continueRandomization); return location; }
public virtual GameMoveType GetNextMove(GameState gameState, IGameInteractionService interactionService) { var loggingService = ServiceContainer.Resolve<ILoggingService> (); var emptyLocation = gameState.GameField.GetUnusedItemLocation (); // Prepare a path to the target treasure if it hasn't happened yet. The AI decides first what path it will take, then it will follow it until // it fails or until it reaches the treasure. The current path is reset in UpdatePlayer() because this means that it's the other player's turn. if (this.currentPath == null || this.currentPath.Count <= 0) { #if EXTENSIVE_LOGGING loggingService.Log ("AI calculates a new path."); #endif // Populate the path finding algorithm and start at the unused item location. var populatedGrid = AIHelpers.PreparePathGrid (gameState.GameField, emptyLocation, gameState.GetActiveCard()); // Get the location of the current active card. This may return NULL if the AI cannot remember the location. GridLocation currentTreasureLocation = this.GetPresumedLocationOfTreasure(gameState.GetActiveCard()); // The AI has no clue where the active treasure is. It will pick a random location to move to. if (currentTreasureLocation == null) { currentTreasureLocation = this.GetRandomLocationAvoidingTreasures (gameState); #if EXTENSIVE_LOGGING loggingService.Log ("AI has no clue where '{0}' is located. Will move to random location {1}.", gameState.GetActiveCard(), currentTreasureLocation); #endif } // Log how AI calculates the distances. #if EXTENSIVE_LOGGING loggingService.Log ("AI sees the grid with these distances, starting at {0} to reach (presumed!) {1} at {2}:", emptyLocation, gameState.GetActiveCard(), currentTreasureLocation); for (int row = 0; row < populatedGrid.Rows; row++) { string s = string.Empty; for (int col = 0; col < populatedGrid.Columns; col++) { s += populatedGrid.GetItem (row, col).ToString ("000") + " |"; } loggingService.Log (s); } #endif // Find the path from the current unused location to the treasure, avoiding all other treasures if possible. var pathList = AIHelpers.GetPathToLocation (gameState.GameField, populatedGrid, emptyLocation, currentTreasureLocation, gameState.GetActiveCard()); // We need to reverse it to get the corret order when copying onto a stack. pathList.Reverse (); // Copy the path onto a stack for better processing. this.currentPath = new Stack<GridLocation>(pathList); pathList = null; // Log the path the AI will go. #if EXTENSIVE_LOGGING loggingService.Log ("AI's path to (presumed!) {0}:", gameState.GetActiveCard()); for (int row = 0; row < populatedGrid.Rows; row++) { string s = string.Empty; for (int col = 0; col < populatedGrid.Columns; col++) { var isPath = (from pathItem in this.currentPath where pathItem.Row == row && pathItem.Column == col select pathItem).Any (); if (isPath) { s += populatedGrid.GetItem (row, col).ToString ("000") + "X|"; } else { s += populatedGrid.GetItem (row, col).ToString ("000") + " |"; } } loggingService.Log (s); } #endif // A correct path has to contain at least two locations: the start and the target. if (this.currentPath.Count < 2) { // The AI failed and could not find a path from the unused location to the active treasure. Stupid AI. Should not happen and means there is a problem in the algorithm. loggingService.Log ("AI path is invalid. Path length: [{0}]. Next move will be random.", this.currentPath.Count.ToString ()); // Return a random move. var randomMove = AIHelpers.GetRandomMove (gameState.GameField, emptyLocation, gameState.GetActiveCard()); loggingService.Log ("AI moves randomly: {0}", randomMove); // Bail out. return randomMove; } else { // The found path is valid. Remove the first entry because this is the start location. No need to move, we're already there. // TODO: Verify that if the next treaure is the one that is currently active, the AI will spot that it is already uncovered. this.currentPath.Pop (); } } // Here the current path has either just been populated or we're following a previously populated path. // Get next location to move to. var moveLocation = this.currentPath.Pop (); // Remember that we visited that location. We want to avoid it for the next couple of moves. this.lastVisitedLocations.Enqueue (moveLocation); // Remember the treasure that has just been discoverd fully. this.LearnAboutTreasure (gameState.GameField.GetItem(moveLocation).Treasure, moveLocation); // Convert location to a move. var move = AIHelpers.GetMoveToAdjacentLocation (emptyLocation, moveLocation); loggingService.Log ("AI will move from [{0}] to [{1}], this is a move of type [{2}].", emptyLocation, moveLocation, move); // The AI forgets a bit after each move. this.ForgetTreasures (); return move; }
public void InitGameState(GameState gameState) { var platformServices = ServiceContainer.Resolve<IPlatformServices> (); int id = 1; var pyramidType = PyramidType.PyramidA; // Distribute the treasures randomly on the board. var randomizedTreasures = DefaultGameInteractionService.GetRandomizedTreasures (); // Map from index of the treasure to the treasure. var treasureIndexes = new Dictionary<int, TreasureType>(); int row = 0; int col = 0; foreach(var treasure in randomizedTreasures) { // Get a random location within the 2x2 square. int randomRowAdd = platformServices.GetRandomNumber (0, 2); int randomColAdd = platformServices.GetRandomNumber (0, 2); // Get the index of the location. int treasureIndex = (row + randomRowAdd) * gameState.GameField.Columns + col + randomColAdd; // Remember the index and the treasure. treasureIndexes.Add (treasureIndex, treasure); // Treasures are hidden in 2x2 squares. Exactly one treasure per square. Move to next 2x2 square. col += 2; if(col >= gameState.GameField.Columns) { row += 2; col = 0; } } // Find a random spot for the initial empty item location. This may not be on top of a treasure. int emptyItemLocationIndex; do { emptyItemLocationIndex = platformServices.GetRandomNumber (0, gameState.GameField.Rows * gameState.GameField.Columns); } while(treasureIndexes.ContainsKey (emptyItemLocationIndex)); // Populate the game field. int index = 0; for(row = 0; row < gameState.GameField.Rows; row++) { for(col = 0; col < gameState.GameField.Columns; col++) { var item = new GameFieldItem () { Id = id++, Pyramid = pyramidType, Treasure = TreasureType.None }; // If we come across the initial empty location, remove the pyramid. if(index == emptyItemLocationIndex) { item.Pyramid = PyramidType.None; } // If there's a treasure, hide it. if(treasureIndexes.ContainsKey(index)) { item.Treasure = treasureIndexes[index]; } gameState.GameField.SetItem (row, col, item); // Alternate pyramids to make the field look nicer. pyramidType = pyramidType == PyramidType.PyramidA ? PyramidType.PyramidB : PyramidType.PyramidA; index++; } } // Shuffle the stack of cards to draw from. Each treasure is there three times. // First 12 score 1 each, second 12 score 2 each, third 12 score 3 each. var randomizedCards = DefaultGameInteractionService.GetRandomizedTreasures (3); gameState.Cards = new Stack<TreasureType> (randomizedCards); }
public override GameMoveType GetNextMove(GameState gameState, IGameInteractionService interactionService) { var move = base.GetNextMove (gameState, interactionService); return move; }
/// <summary> /// Draws the game field. /// </summary> /// <param name="gameState">Game state.</param> public void DrawGameField(GameState gameState) { int right = gameState.GameField.Columns * colScale + gameState.GameField.Columns; // Show the current player's name. this.Write (0, right + 5, ConsoleColor.Gray, "Current Player: {0}", gameState.CurrentPlayer.ToString ()); // Show the current card to be searched. this.Write (2, right + 5, ConsoleColor.Gray, "Search treasure: {0}", gameState.GetActiveCard()); this.Write (3, right + 5, ConsoleColor.Gray, "Treasures left: {0}", gameState.Cards.Count); // Show the scores. this.Write (5, right + 5, ConsoleColor.Gray, "Local player score: {0}", gameState.LocalPlayerScore); this.Write (6, right + 5, ConsoleColor.Gray, "Opponent player score: {0}", gameState.OpponentPlayerScore); // Draw a grid. for (int row = 0; row < gameState.GameField.Rows; row++) { for (int col = 0; col < gameState.GameField.Columns; col++) { this.DrawHorizLine (row, col); this.DrawVertLine (row, col); } } // Draw the bottom most horizontal line. for (int col = 0; col < gameState.GameField.Columns; col++) { this.DrawHorizLine (gameState.GameField.Rows, col); } // Draw the right most vertical line./ for (int row = 0; row < gameState.GameField.Rows; row++) { this.DrawVertLine (row, gameState.GameField.Columns); } // Draw the bottom right corner of the game field. screen.SetItem (gameState.GameField.Rows * rowScale + gameState.GameField.Rows, gameState.GameField.Columns * colScale + gameState.GameField.Columns, '+'.ToColorChar(gridColor)); // Draw representations of the game field content. for (int row = 0; row < gameState.GameField.Rows; row++) { for (int col = 0; col < gameState.GameField.Columns; col++) { GameFieldItem item = gameState.GameField.GetItem (row, col); ColorChar pyramidChar = new ColorChar(); switch(item.Pyramid) { case PyramidType.PyramidA: pyramidChar = 'X'.ToColorChar (ConsoleColor.White, ConsoleColor.DarkRed); break; case PyramidType.PyramidB: pyramidChar = 'X'.ToColorChar (ConsoleColor.White, ConsoleColor.Blue); break; case PyramidType.None: pyramidChar = ' '.ToColorChar (ConsoleColor.Black); break; default: throw new InvalidOperationException ("Unhandled pyramid type!"); } for(int i = 0; i < rowScale; i++) { for(int j = 0; j < colScale; j++) { screen.SetItem (row * rowScale + row + 1 + i, col * colScale + col + 1 + j, pyramidChar); } } // If there's a treasure, show the first few characters to ease debugging. if (item.Treasure != TreasureType.None) { // By default show treasure if it is at the empty location. if (this.ShowTreasureNames || pyramidChar.C == ' ') { var treasureString = item.Treasure.ToString (); for (int j = 0; j < colScale; j++) { if (j < treasureString.Length) { screen.SetItem (row * rowScale + row + 1, col * colScale + col + 1 + j, treasureString [j].ToColorChar (ConsoleColor.Black, ConsoleColor.Yellow)); } else { screen.SetItem (row * rowScale + row + 1, col * colScale + col + 1 + j, ' '.ToColorChar (ConsoleColor.Black, ConsoleColor.Yellow)); } } } } } } }
public void UpdateScene(GameState gameState) { // Draw the game board into the virtual screen. this.DrawGameField (gameState); // Copy the virtual screen content to the console. Console.BackgroundColor = ConsoleColor.Black; Console.Clear (); for(int row = 0; row < screen.Rows; row++) { for(int col = 0; col < screen.Columns; col++) { var colorChar = screen.GetItem (row, col); Console.ForegroundColor = colorChar.Color; Console.BackgroundColor = colorChar.BackgroundColor; Console.Write(colorChar.C); } Console.WriteLine (); } // Clear the virtual screen for the next loop. for (int row = 0; row < screen.Rows; row++) { for (int col = 0; col < screen.Columns; col++) { screen.SetItem (row, col, '.'.ToColorChar(ConsoleColor.Black, ConsoleColor.Black)); } } }
private static void PopulateGameState(GameState gameState, Tuple<string, string> data) { switch(data.Item1) { case "localplayerscore": gameState.LocalPlayerScore = XmlConvert.ToInt32 (data.Item2); break; case "opponentplayerscore": gameState.OpponentPlayerScore = XmlConvert.ToInt32 (data.Item2); break; case "currentplayer": gameState.CurrentPlayer = (PlayerType)Enum.Parse (typeof(PlayerType), data.Item2, true); break; case "cards": var cardData = data.Item2.Split (new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); foreach(string cardName in cardData) { var card = (TreasureType)Enum.Parse (typeof(TreasureType), cardName, true); gameState.Cards.Push (card); } break; case "field": var colData = data.Item2.Split (new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); // First number is the row index. int rowIndex = XmlConvert.ToInt32 (colData [0]); int colIndex = 0; for (int i = 1; i < colData.Length; i++) { var itemInfo = colData[i].Split (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var item = new GameFieldItem (); item.Id = XmlConvert.ToInt32 (itemInfo[0]); item.Pyramid = (PyramidType)Enum.Parse (typeof(PyramidType), itemInfo [1], true); item.Treasure = (TreasureType)Enum.Parse (typeof(TreasureType), itemInfo [2], true); gameState.GameField.SetItem (rowIndex, colIndex, item); colIndex++; } break; } }
public void SetUp() { Util.RegisterServices (); this.gameState = new GameState () { GameField = new GameFieldGrid(), CurrentPlayer = PlayerType.Local, Cards = new Stack<TreasureType>(), LocalPlayerScore = 10, OpponentPlayerScore = 20 }; // Push two cards for testing. this.gameState.Cards.Push (TreasureType.Candle); this.gameState.Cards.Push (TreasureType.Computer); int id = 0; var pyramid = PyramidType.None; var treasure = TreasureType.None; for(int row = 0; row < GameFieldGrid.ROWS; row++) { for(int col = 0; col < GameFieldGrid.COLUMNS; col++) { // Hide four treasures. if(row == 2 && col == 2) { treasure = TreasureType.Candle; } else if(row == 3 && col == 3) { treasure = TreasureType.Computer; } if(row == 4 && col == 4) { treasure = TreasureType.Dog; } if(row == 5 && col == 5) { treasure = TreasureType.Glasses; } var item = new GameFieldItem() { Id = id++, Pyramid = pyramid, Treasure = treasure }; this.gameState.GameField.SetItem (row, col, item); // Alternate pyramids. if(pyramid == PyramidType.None) { pyramid = PyramidType.PyramidA; } else if(pyramid == PyramidType.PyramidA) { pyramid = PyramidType.PyramidB; } else if(pyramid == PyramidType.PyramidB) { pyramid = PyramidType.PyramidA; } } } }
public GameMoveType GetNextMove(GameState gameState, IGameInteractionService interactionService) { return GameMoveType.None; }
public GameFieldItem Move(GameState gameState, GameMoveType move) { // The item that was discovered by moving a pyramid. GameFieldItem itemToMove = null; var location = gameState.GameField.GetUnusedItemLocation (); // There always must be an empty field! if(location == null) { throw new InvalidOperationException ("Game field does not have an empty item location!"); } var emptyItem = gameState.GameField.GetItem (location.Row, location.Column); switch(move) { case GameMoveType.MoveItemAbove: // Get the pyramid above the empty field and move it down. itemToMove = gameState.GameField.GetItem (location.Row - 1, location.Column); break; case GameMoveType.MoveItemBelow: // Get the pyramid below the empty field and move it up. itemToMove = gameState.GameField.GetItem (location.Row + 1, location.Column); break; case GameMoveType.MoveItemLeft: // Get the pyramid left of the empty field and move it right. itemToMove = gameState.GameField.GetItem (location.Row, location.Column - 1); break; case GameMoveType.MoveItemRight: // Get the pyramid right of the empty field and move it left. itemToMove = gameState.GameField.GetItem (location.Row, location.Column + 1); break; } // The pyramid that was on the moved item is now where the empty spot was. emptyItem.Pyramid = itemToMove.Pyramid; itemToMove.Pyramid = PyramidType.None; return itemToMove; }