public bool CanMove(GameFieldGrid grid, GameMoveType move) { var emptylocation = grid.GetUnusedItemLocation (); // There always must be an empty field! if(emptylocation == null) { throw new InvalidOperationException ("Game field does not have an empty item location!"); } switch(move) { case GameMoveType.MoveItemAbove: return emptylocation.Row > 0; case GameMoveType.MoveItemBelow: return emptylocation.Row < grid.Rows - 1; case GameMoveType.MoveItemLeft: return emptylocation.Column > 0; case GameMoveType.MoveItemRight: return emptylocation.Column < grid.Columns - 1; default: return false; } }
/// <summary> /// Gets the accessible adjacent locations of a specified location. /// A location is accessible if it's top, bottom, left or right of the location within the grid's ranges and is not a treasure. /// </summary> /// <returns>The accessible adjacent locations.</returns> /// <param name="gameGrid">Game grid.</param> /// <param name="location">Location.</param> /// <param name="ignoreLocation">Ignore this location no matter what type of treasure it has.</param> /// <param name="currentActiveTreasure">The current active treasure is never considered a wall.</param> public static List<GridLocation> GetAccessibleAdjacentLocations(GameFieldGrid gameGrid, GridLocation location, TreasureType currentActiveTreasure, GridLocation ignoreLocation = null) { var checkLocations = new List<GridLocation> (); // Check the item to the top. if(location.Row > 0) { var targetLocation = new GridLocation (location.Row - 1, location.Column); bool ignore = ignoreLocation != null && targetLocation.Equals (ignoreLocation); bool isActiveTreasure = gameGrid.GetItem (targetLocation).Treasure == currentActiveTreasure; if (isActiveTreasure || ignore || IsPassableTreasureType (targetLocation, gameGrid)) { checkLocations.Add (targetLocation); } } // Check the item below. if(location.Row < gameGrid.Rows - 1) { var targetLocation = new GridLocation (location.Row + 1, location.Column); var ignore = ignoreLocation != null && targetLocation.Equals (ignoreLocation); bool isActiveTreasure = gameGrid.GetItem (targetLocation).Treasure == currentActiveTreasure; if (isActiveTreasure || ignore || IsPassableTreasureType (targetLocation, gameGrid)) { checkLocations.Add (targetLocation); } } // Check the item to the left. if(location.Column > 0) { var targetLocation = new GridLocation (location.Row, location.Column - 1); var ignore = ignoreLocation != null && targetLocation.Equals (ignoreLocation); bool isActiveTreasure = gameGrid.GetItem (targetLocation).Treasure == currentActiveTreasure; if (isActiveTreasure || ignore || IsPassableTreasureType (targetLocation, gameGrid)) { checkLocations.Add (targetLocation); } } // Check the item to the right. if(location.Column < gameGrid.Columns - 1) { var targetLocation = new GridLocation (location.Row, location.Column + 1); var ignore = ignoreLocation != null && targetLocation.Equals (ignoreLocation); bool isActiveTreasure = gameGrid.GetItem (targetLocation).Treasure == currentActiveTreasure; if (isActiveTreasure || ignore || IsPassableTreasureType (targetLocation, gameGrid)) { checkLocations.Add (targetLocation); } } return checkLocations; }
/// <summary> /// Returns a two dimensional grid where each field contains the distance from the start point (the empty field). /// </summary> /// <returns>The path.</returns> /// <param name="gameGrid">Game grid.</param> /// <param name="fromLocation">the location to start from.</param> /// <param name="currentActiveTreasure">The current active treasre can always be stepped onto.</param> public static Grid2D<int> PreparePathGrid(GameFieldGrid gameGrid, GridLocation fromLocation, TreasureType currentActiveTreasure) { if(gameGrid == null) { throw new ArgumentNullException ("gameGrid"); } if(fromLocation == null) { throw new ArgumentNullException ("fromLocation"); } // Create a grid as big as the game field. The content of each item is the distance from the staring point (the empty spot). var pathGrid = new Grid2D<int> (gameGrid.Rows, gameGrid.Columns, pathFindingMaxVal); // Use a Disjkstra algorithm to find a path from the empty spot to the current active treasure. // First, set the presumed target location to a distance of 0. pathGrid.SetItem (fromLocation, 0); // Start calculating distances. while(true) { // Set to true if at least one field was changed. If nothing is changed during one loop, we're done. bool gridChanged = false; // Loop all fields until each field contains a distance value. for (int row = 0; row < gameGrid.Rows; row++) { for (int col = 0; col < gameGrid.Columns; col++) { // Distance is one more than it was at the current location. Set for the surrounding fields. int newDistance = pathGrid.GetItem (row, col); if(newDistance != pathFindingMaxVal) { newDistance++; } var checkLocations = AIHelpers.GetAccessibleAdjacentLocations(gameGrid, new GridLocation(row, col), currentActiveTreasure); foreach(var checkLocation in checkLocations) { // Remember the distance to the start point. int currentDistance = pathGrid.GetItem (checkLocation); if(newDistance < currentDistance) { pathGrid.SetItem (checkLocation, newDistance); gridChanged = true; } } } } // Bail out of the algorithm if we visited all nodes. if(!gridChanged) { break; } } return pathGrid; }
/// <summary> /// Figures out if a specific item location is considered a "wall" for the path finding algorithm. /// A wall is defined as a treasure that is not the current active treasure card. /// The prediction is not 100% correct because the AI is not supposed to know the location of each and /// every treasure. Randomization and statistics are used to reduce the chance the AI will detect a wall. /// </summary> /// <returns><c>true</c> if the AI thinks it can move to the location otherwise, <c>false</c>.</returns> /// <param name="location">Location to check.</param> /// <param name="gameField">Game field to use.</param> public static bool IsPassableTreasureType(GridLocation location, GameFieldGrid gameField) { var item = gameField.GetItem (location.Row, location.Column); // A location can be moved to if the treasure is "None". if(item.Treasure != TreasureType.None) { // It's a wall. AI cannot go there. return false; } // It is not a wall. AI can go there. return true; }
/// <summary> /// Gets a random move. Used if the AI does not know what to do and is guessing. /// </summary> /// <returns>The random move.</returns> /// <param name="gameGrid">Game grid.</param> /// <param name="fromLocation">From location.</param> /// <param name="currentActiveTreasure">The current active treasure. This can also be stepped onto.</param> public static GameMoveType GetRandomMove(GameFieldGrid gameGrid, GridLocation fromLocation, TreasureType currentActiveTreasure) { var possibleTargetLocations = AIHelpers.GetAccessibleAdjacentLocations (gameGrid, fromLocation, currentActiveTreasure); var platformServices = ServiceContainer.Resolve<IPlatformServices> (); // Pick a random target location. var toLocation = possibleTargetLocations [platformServices.GetRandomNumber (0, possibleTargetLocations.Count)]; // Return the required move to get to the picked location. var randomMove = AIHelpers.GetMoveToAdjacentLocation (fromLocation, toLocation); return randomMove; }
/// <summary> /// Gets the fields that have to be followed to come to a specific location. /// </summary> /// <returns>The path to location.</returns> /// <param name="fromLocation">the location from where to start path finding. Must be the same that was passed to PreparePathGrid()</param> /// <param name="toLocation">the location to find the path to starting at the unused location.</param> /// <param name="gameGrid">Game grid.</param> /// <param name="pathGrid">Populated path grid.</param> /// <param name="currentActiveTreasure">The current active treasre can always be stepped onto.</param> public static List<GridLocation> GetPathToLocation(GameFieldGrid gameGrid, Grid2D<int> pathGrid, GridLocation fromLocation, GridLocation toLocation, TreasureType currentActiveTreasure) { if(gameGrid == null) { throw new ArgumentNullException ("gameGrid"); } if(pathGrid == null) { throw new ArgumentNullException ("pathGrid"); } if(toLocation == null) { throw new ArgumentNullException ("toLocation"); } // This will hold the reversed path. var path = new List<GridLocation> { // The target always belongs to the path. new GridLocation (toLocation.Row, toLocation.Column) }; // Loop until the path has been completed. while(true) { // From the current target location check all possible moves. The algorithm works its way from the target to the start, so the path will be reversed. var checkLocations = AIHelpers.GetAccessibleAdjacentLocations (gameGrid, toLocation, currentActiveTreasure, fromLocation); // Loop the possible moves and remember the one with the lowest distance towards the start location. int lowestDistance = pathFindingMaxVal; GridLocation lowestLocation = null; foreach(var adjacentLocation in checkLocations) { int distance = pathGrid.GetItem (adjacentLocation); if(distance < lowestDistance) { lowestDistance = distance; lowestLocation = adjacentLocation; // The new target location is the lowest of the possible locations. toLocation = lowestLocation; } } if(lowestLocation != null) { // Insert at 0 to have the path in the correct reversed order. path.Insert(0, lowestLocation); if(lowestLocation.Equals(fromLocation)) { // Reached the start location. break; } } else { // No lowest location found. Bail out. break; } } return path; }
/// <summary> /// Gets the location of the current active treasure card. /// Randomization and statistics are applied to prevent the prevention from being 100% correct. This makes the AI more human. /// If the AI cannot remember the location, a random location will be returned. /// </summary> /// <returns>The target treasure location.</returns> /// <param name="gameField">Game field.</param> /// <param name="activeTreaure">Active treaure.</param> public static GridLocation GetActiveTreasureLocation(GameFieldGrid gameField, TreasureType activeTreaure) { // Find the item that has the active treasure. var activeTreasureItem = gameField.FirstOrDefault (item => item.Treasure == activeTreaure); // Get the location of the item. var location = gameField.GetItemLocation (activeTreasureItem); return location; }