/* * 1. Find the destination tile (where the player is). * 2. Put the starting tile (where the monster is) on the OPEN list. It's starting cost is zero. * 3. While the OPEN list is not empty, and a path isn't found: * 1. Get the tile from the OPEN list with the lowest movement cost. Let's call it the CURRENT tile. * 2. If this is the destination tile, the path has been found. Exit the loop now. * 3. Find the tiles to which you can immediately walk to from this tile. These would the tiles around this tile, which don't contain obstacles. Call these tiles "successors". * 4. For each successor: * 1. Set the successor's parent to the CURRENT tile. * 2. Set the successor's movement cost to the parent's movement cost, plus 1 (for diagonal movements, add more if it takes longer to go diagonally in your game). * 3. If the successor doesn't exist on either the OPEN list or the CLOSED list, add it to the OPEN list. Otherwise, if the successor's movement cost is lower than the movement cost of the same tile on one of the lists, delete the occurrences of the successor from the lists add the successor to the OPEN list Otherwise, if the successor's movement cost is higher than that of the same tile on one of the lists, get rid of the successor * 5. Delete the CURRENT tile from the OPEN list, and put it on the CLOSED list. * 4. If the while loop has been ended because the OPEN list is empty, there is no path. * 5. If this is not the case, the last tile pulled from the OPEN list, and its parents, describe the shortest path (in reverse order - i.e. from the player to the monster - you should read the list of tiles back to front). */ public Tile GetShortestDistanceDirectionToPlayer(Mobile mobile, Location playerLocation, Location mobileLocation) { //Set up lists for tracking tiles while pathfinding var openLocations = new List <PathLocation>(); var closedLocations = new List <PathLocation>(); List <PathLocation> successorLocations = null; PathLocation currentLocation = null; //Keep a PathLocation for the mobile. It is needed at the end when walking the child parent relationship //It is used to filter out the location of the mobile, so we don't try to move to where the //mobile already is. PathLocation mobilePathLocation = new PathLocation(mobileLocation, 0, null); //Add the mobiles location as a starting point openLocations.Add(mobilePathLocation); //Continue to check for paths until we've looked at all possible tiles (tiles that the mobile can move into //from it's original location) while (openLocations.Count > 0) { //Get the tile with the least movement cost. There may be several that have equal costs. //Just take the first one int minMovementCost = openLocations.Min(o => o.MovementCost); currentLocation = openLocations.Where(o => o.MovementCost == minMovementCost).First(); //If we have found the players location with a tile that has the least associated movement //cost, then we are done. if (currentLocation.ThisLocation == playerLocation) { break; } //Init the adject tiles scanning loops var topStart = currentLocation.ThisLocation.Top - 1 < 1 ? 1 : currentLocation.ThisLocation.Top - 1; var leftStart = currentLocation.ThisLocation.Left - 1 < 1 ? 1 : currentLocation.ThisLocation.Left - 1; //Make sure the successor list is empty. It will be filled wiht tiles that can be moved to from, //the current location. successorLocations = new List <PathLocation>(); //TODO a similar loop to this is used in Mobile.CanAttack. See if can refactor //Go through each of the tiles adjacent to the current location and add it to the successor list //if the mobile can move to it (ie. it's not a wall or another mobile, etc...) for (int top = topStart; top <= currentLocation.ThisLocation.Top + 1; top++) { for (int left = leftStart; left <= currentLocation.ThisLocation.Left + 1; left++) { var potentialTile = this.GetTileForLocation(new Location(left, top)); if (mobile.CanMoveOnPath(potentialTile)) { //Add the adjacent tile to the successor list setting its movement cost //and parent tile (the current location adjacenct to this one). successorLocations.Add(new PathLocation(potentialTile.Location, currentLocation.MovementCost + 1, currentLocation)); } } } //Go through the successors and determine what to do with them foreach (var successorLocation in successorLocations) { //See if the successor location is already on the open or closed locations list var onOpenLocations = openLocations.Where(o => o.ThisLocation == successorLocation.ThisLocation); var onClosedLocations = closedLocations.Where(o => o.ThisLocation == successorLocation.ThisLocation); //If the successor is NOT on either the closed or open list, then add it to open. This means //we need probably need to do more pathfinding from this successor. if (onOpenLocations.Count() < 1 && onClosedLocations.Count() < 1) { openLocations.Add(successorLocation); } //If this successor has a lower movement cost than the same location with a higher movement cost, //the remove it from both lists and add it to the open list. This means we have found a shorter //path (the lower movement cost) and so we will want to continue pathfinding from this successor. else if ((onOpenLocations.Count() > 0 && successorLocation.MovementCost < onOpenLocations.Min(o => o.MovementCost)) || (onClosedLocations.Count() > 0 && successorLocation.MovementCost < onClosedLocations.Min(o => o.MovementCost))) { openLocations.RemoveAll(o => onOpenLocations.Contains(o)); closedLocations.RemoveAll(o => onClosedLocations.Contains(o)); openLocations.Add(successorLocation); } //If this successor has a greater movement cost, then throw it out. This means this successor is //through a longer path that we don't want to persue. else if ((onOpenLocations.Count() > 0 && successorLocation.MovementCost > onOpenLocations.Min(o => o.MovementCost)) || (onClosedLocations.Count() > 0 && successorLocation.MovementCost > onClosedLocations.Min(o => o.MovementCost))) { ; //Do nothing (effectively get rid of the successor } //Note that we did nothing with successors that have == movement costs. We want to make sure //we check all the paths from it, so it will be skipped or removed at a later point. } //We are done with the current location at this point. Move it to the closed list. openLocations.Remove(currentLocation); closedLocations.Add(currentLocation); currentLocation = null; } //If current location is null, there is no path to the player. if (currentLocation == null) { return(null); } //If we get to this point with a current location, that location is the player. PathLocation locationToMoveTo = currentLocation.ParentPathLocation; //Now walk backwards from the player location through the parents to the mobile, but stop //at the tile before the mobile. while (locationToMoveTo.ParentPathLocation != null && locationToMoveTo.ParentPathLocation.ThisLocation != mobilePathLocation.ThisLocation) { locationToMoveTo = locationToMoveTo.ParentPathLocation; } //Return the tile adjancent to the mobile that we would like to move to. this tile //is part of the shortest path at this point (of course the path can change later if the player moves). return(this.GetTileForLocation(locationToMoveTo.ThisLocation)); }
public PathLocation(Location currentLocation, int movementCost, PathLocation parentPathLocation) { this.ThisLocation = currentLocation; this.ParentPathLocation = parentPathLocation; this.MovementCost = movementCost; }
/* 1. Find the destination tile (where the player is). 2. Put the starting tile (where the monster is) on the OPEN list. It's starting cost is zero. 3. While the OPEN list is not empty, and a path isn't found: 1. Get the tile from the OPEN list with the lowest movement cost. Let's call it the CURRENT tile. 2. If this is the destination tile, the path has been found. Exit the loop now. 3. Find the tiles to which you can immediately walk to from this tile. These would the tiles around this tile, which don't contain obstacles. Call these tiles "successors". 4. For each successor: 1. Set the successor's parent to the CURRENT tile. 2. Set the successor's movement cost to the parent's movement cost, plus 1 (for diagonal movements, add more if it takes longer to go diagonally in your game). 3. If the successor doesn't exist on either the OPEN list or the CLOSED list, add it to the OPEN list. Otherwise, if the successor's movement cost is lower than the movement cost of the same tile on one of the lists, delete the occurrences of the successor from the lists add the successor to the OPEN list Otherwise, if the successor's movement cost is higher than that of the same tile on one of the lists, get rid of the successor 5. Delete the CURRENT tile from the OPEN list, and put it on the CLOSED list. 4. If the while loop has been ended because the OPEN list is empty, there is no path. 5. If this is not the case, the last tile pulled from the OPEN list, and its parents, describe the shortest path (in reverse order - i.e. from the player to the monster - you should read the list of tiles back to front). */ public Tile GetShortestDistanceDirectionToPlayer(Mobile mobile, Location playerLocation, Location mobileLocation) { //Set up lists for tracking tiles while pathfinding var openLocations = new List<PathLocation>(); var closedLocations = new List<PathLocation>(); List<PathLocation> successorLocations = null; PathLocation currentLocation = null; //Keep a PathLocation for the mobile. It is needed at the end when walking the child parent relationship //It is used to filter out the location of the mobile, so we don't try to move to where the //mobile already is. PathLocation mobilePathLocation = new PathLocation(mobileLocation, 0, null); //Add the mobiles location as a starting point openLocations.Add(mobilePathLocation); //Continue to check for paths until we've looked at all possible tiles (tiles that the mobile can move into //from it's original location) while (openLocations.Count > 0) { //Get the tile with the least movement cost. There may be several that have equal costs. //Just take the first one int minMovementCost = openLocations.Min(o => o.MovementCost); currentLocation = openLocations.Where(o => o.MovementCost == minMovementCost).First(); //If we have found the players location with a tile that has the least associated movement //cost, then we are done. if (currentLocation.ThisLocation == playerLocation) { break; } //Init the adject tiles scanning loops var topStart = currentLocation.ThisLocation.Top - 1 < 1 ? 1 : currentLocation.ThisLocation.Top - 1; var leftStart = currentLocation.ThisLocation.Left - 1 < 1 ? 1 : currentLocation.ThisLocation.Left - 1; //Make sure the successor list is empty. It will be filled wiht tiles that can be moved to from, //the current location. successorLocations = new List<PathLocation>(); //TODO a similar loop to this is used in Mobile.CanAttack. See if can refactor //Go through each of the tiles adjacent to the current location and add it to the successor list //if the mobile can move to it (ie. it's not a wall or another mobile, etc...) for (int top = topStart; top <= currentLocation.ThisLocation.Top + 1; top++) { for (int left = leftStart; left <= currentLocation.ThisLocation.Left + 1; left++) { var potentialTile = this.GetTileForLocation(new Location(left, top)); if (mobile.CanMoveOnPath(potentialTile)) { //Add the adjacent tile to the successor list setting its movement cost //and parent tile (the current location adjacenct to this one). successorLocations.Add(new PathLocation(potentialTile.Location, currentLocation.MovementCost + 1, currentLocation)); } } } //Go through the successors and determine what to do with them foreach (var successorLocation in successorLocations) { //See if the successor location is already on the open or closed locations list var onOpenLocations = openLocations.Where(o => o.ThisLocation == successorLocation.ThisLocation); var onClosedLocations = closedLocations.Where(o => o.ThisLocation == successorLocation.ThisLocation); //If the successor is NOT on either the closed or open list, then add it to open. This means //we need probably need to do more pathfinding from this successor. if (onOpenLocations.Count() < 1 && onClosedLocations.Count() < 1) { openLocations.Add(successorLocation); } //If this successor has a lower movement cost than the same location with a higher movement cost, //the remove it from both lists and add it to the open list. This means we have found a shorter //path (the lower movement cost) and so we will want to continue pathfinding from this successor. else if ((onOpenLocations.Count() > 0 && successorLocation.MovementCost < onOpenLocations.Min(o => o.MovementCost)) || (onClosedLocations.Count() > 0 && successorLocation.MovementCost < onClosedLocations.Min(o => o.MovementCost))) { openLocations.RemoveAll(o => onOpenLocations.Contains(o)); closedLocations.RemoveAll(o => onClosedLocations.Contains(o)); openLocations.Add(successorLocation); } //If this successor has a greater movement cost, then throw it out. This means this successor is //through a longer path that we don't want to persue. else if ((onOpenLocations.Count() > 0 && successorLocation.MovementCost > onOpenLocations.Min(o => o.MovementCost)) || (onClosedLocations.Count() > 0 && successorLocation.MovementCost > onClosedLocations.Min(o => o.MovementCost))) { ; //Do nothing (effectively get rid of the successor } //Note that we did nothing with successors that have == movement costs. We want to make sure //we check all the paths from it, so it will be skipped or removed at a later point. } //We are done with the current location at this point. Move it to the closed list. openLocations.Remove(currentLocation); closedLocations.Add(currentLocation); currentLocation = null; } //If current location is null, there is no path to the player. if (currentLocation == null) { return null; } //If we get to this point with a current location, that location is the player. PathLocation locationToMoveTo = currentLocation.ParentPathLocation; //Now walk backwards from the player location through the parents to the mobile, but stop //at the tile before the mobile. while ( locationToMoveTo.ParentPathLocation != null && locationToMoveTo.ParentPathLocation.ThisLocation != mobilePathLocation.ThisLocation) { locationToMoveTo = locationToMoveTo.ParentPathLocation; } //Return the tile adjancent to the mobile that we would like to move to. this tile //is part of the shortest path at this point (of course the path can change later if the player moves). return this.GetTileForLocation(locationToMoveTo.ThisLocation); }