public void FieldOfViewUpdate() { UInt16 range = this.StatSightRange; //Map currentMap = this.InhabitedMap; if (range < 0) { return; } //REMOVE REDUNDANCY HERE BitArray[] update = new BitArray[_inhabitedMap.BoundX]; for (int i = 0; i < _inhabitedMap.BoundX; ++i) { update[i] = new BitArray(_inhabitedMap.BoundY); } for (Int32 i = -range + 1; i < range; i++) { for (Int32 j = -range + 1; j < range; j++) { Coords current = new Coords(CoordsType.Tile, _positionTile.X + i, _positionTile.Y + j); if ( !_myCollider.CheckInBounds(current) || (StaticMathFunctions.DistanceBetweenTwoCoordsEucledean(this._positionTile, current) > range) ) { continue; } bool val = _myVisibilityTracker.RayTracerVisibilityCheckTile(this._positionTile, current, false); update[current.X][current.Y] = val; } } // determine values that were changed for (int i = 0; i < _inhabitedMap.BoundX; ++i) { update[i] = update[i].Xor(_fieldOfView[i]); } // update changes for (int i = 0; i < _inhabitedMap.BoundX; ++i) { for (int j = 0; j < _inhabitedMap.BoundY; ++j) { if (update[i][j]) { bool val = _fieldOfView[i][j]; _fieldOfView[i][j] = !val; //_inhabitedMap.GetTile(i, j).VisibilityUpdate(this, !val); _myVisibilityTracker.VisibilityUpdate(new Coords(CoordsType.Tile, i, j), this, !val); } } } }
/// <summary> /// Returns list of possible moves, sorted by /// 1) amount of increase, 2) distance to influence map source /// THIS METHOD SHOULD BE IMPROVED /// </summary> public List <Direction> PossibleMoves(Coords currentPosition) { List <Direction> dirList = new List <Direction>(); // Dangerous cast? TilePassable currentTile = (TilePassable)this._currentMap.GetTile(currentPosition); for (byte i = 1; i <= 8; i++) { Direction currentDir = (Direction)i; if (currentTile.AllowedMovesCheckInDirection(currentDir)) { dirList.Add(currentDir); } } dirList.Sort( delegate(Direction d1, Direction d2) { Coords c1 = StaticMathFunctions.CoordsNeighboringInDirection(currentPosition, d1); Coords c2 = StaticMathFunctions.CoordsNeighboringInDirection(currentPosition, d2); Int32 returnVal = (this._influenceMap[c1.X, c1.Y]).CompareTo(this._influenceMap[c2.X, c2.Y]); if (returnVal == 0) { returnVal = (StaticMathFunctions.DistanceBetweenTwoCoordsEucledean(c1, currentPosition)).CompareTo (StaticMathFunctions.DistanceBetweenTwoCoordsEucledean(c2, currentPosition)); } return(returnVal); } ); return(dirList); }
/// <summary> /// Analyzes and remembers tile accessibility. Starts at northwest corner and goes through the array, /// checking east / southeast / south / southwest on the current tile and in case of accessibility /// recording the result in both directions. /// </summary> public void AnalyzeTileAccessibility() { Tile currentTile; for (UInt16 i = 0; i < this._xMax; i++) { for (UInt16 j = 0; j < this._yMax; j++) { Tile east, southEast, south, southWest; currentTile = this._tiles[i, j]; if (currentTile is TileImpassable) { continue; } _passabilityMap[i][j] = true; _visibilityMap[i, j] = (currentTile is TilePassable) ? 1 : 0; // Sort of wasteful, hopefully compiler does this smartly if (i < _xMax - 1) { east = this.GetTile(StaticMathFunctions.CoordsNeighboringInDirection(new Coords(CoordsType.Tile, i, j), Direction.East)); if (east is TilePassable) { (currentTile as TilePassable).AllowedMovesSet(Direction.East, true); (east as TilePassable).AllowedMovesSet(Direction.West, true); } } if ((i < _xMax - 1) & (j < _yMax - 1)) { southEast = this.GetTile(StaticMathFunctions.CoordsNeighboringInDirection(new Coords(CoordsType.Tile, i, j), Direction.Southeast)); if (southEast is TilePassable) { (currentTile as TilePassable).AllowedMovesSet(Direction.Southeast, true); (southEast as TilePassable).AllowedMovesSet(Direction.Northwest, true); } } if (j < _yMax - 1) { south = this.GetTile(StaticMathFunctions.CoordsNeighboringInDirection(new Coords(CoordsType.Tile, i, j), Direction.South)); if (south is TilePassable) { (currentTile as TilePassable).AllowedMovesSet(Direction.South, true); (south as TilePassable).AllowedMovesSet(Direction.North, true); } } if ((i > 0) & (j < _yMax - 1)) { southWest = this.GetTile(StaticMathFunctions.CoordsNeighboringInDirection(new Coords(CoordsType.Tile, i, j), Direction.Southwest)); if (southWest is TilePassable) { (currentTile as TilePassable).AllowedMovesSet(Direction.Southwest, true); (southWest as TilePassable).AllowedMovesSet(Direction.Northeast, true); } } } } }
private List <Direction> _PathfinderAStar(Coords start, Coords endTopLeft, Coords endBottomRight, BitArray[] _passabilityMap, hFunction h) { // NOTE: Should later implemented a collision predictor mechanic to work in tandem // with the path-finder to provide better agent behavior. // NOTE: Consider returning the number of tiles scanned in case no path is found. // This will alert a boxed-in creature of its predicament. // NOTE: Introduce a flag for a straight-line initial check(for outdoors environmens and // for when the goal is near). Int32 rangeX = _passabilityMap.Length; Int32 rangeY = _passabilityMap[0].Count; NodeAStar?[,] nodeArray = new NodeAStar?[rangeX, rangeY]; NodeAStar startNode = new NodeAStar(); startNode.costSoFar = 0; startNode.estimatedTotalCost = h(start); nodeArray[start.X, start.Y] = startNode; List <Coords> ListOpen = new List <Coords>(); ListOpen.Add(start); while (ListOpen.Count > 0) { // I have to use this bool the way I've implemented the algo. Consider rewriting. bool resortList = false; Coords currentCoords = ListOpen.First(); // Check to see if goal is reached. //if (currentCoords.Equals(endTopLeft)) if (StaticMathFunctions.CoordinateIsInBox(currentCoords, endTopLeft, endBottomRight)) { break; } NodeAStar currentNode = nodeArray[currentCoords.X, currentCoords.Y].Value; for (byte i = 0; i <= 3; ++i) { Direction currentDir = (Direction)(2 * i + 1); Coords dirCoords = StaticMathFunctions.DirectionToCoords(currentDir); Coords potential = currentCoords + dirCoords; // check if move in dir is allowed if (potential.X >= 0 && potential.X < rangeX && potential.Y >= 0 && potential.Y < rangeY && // bounds check _passabilityMap[potential.X][potential.Y]) // passability check { // Using the simplest cost function possible. Can be easily updated // once tile walkability coefficients are added. Coords newNodePosition = new Coords(CoordsType.General, currentCoords.X + dirCoords.X, currentCoords.Y + dirCoords.Y); float accruedCost = currentNode.costSoFar + Constants.MovementCost[(byte)currentDir]; // Straight line correction if (currentDir == nodeArray[currentCoords.X, currentCoords.Y].Value.connection) { accruedCost -= Constants.PathfinderStraightPathCorrection; } // Check to see if the node under examination is in the closed list. //NodeAStar? oldNode = nodeArray[newNodePosition.X, newNodePosition.Y]; if (nodeArray[newNodePosition.X, newNodePosition.Y] != null) { // If node is in closed list, see if it needs updating. if (nodeArray[newNodePosition.X, newNodePosition.Y].Value.costSoFar > accruedCost) { float expectedAdditionalCost = nodeArray[newNodePosition.X, newNodePosition.Y].Value.estimatedTotalCost - nodeArray[newNodePosition.X, newNodePosition.Y].Value.costSoFar; NodeAStar nodeToAdd = new NodeAStar(currentDir, accruedCost, accruedCost + expectedAdditionalCost); nodeArray[newNodePosition.X, newNodePosition.Y] = nodeToAdd; ListOpen.Add(newNodePosition); resortList = true; } } // Node is in open list. Process it. else { float expectedAdditionalCost = h(newNodePosition); NodeAStar nodeToAdd = new NodeAStar(currentDir, accruedCost, accruedCost + expectedAdditionalCost); nodeArray[newNodePosition.X, newNodePosition.Y] = nodeToAdd; ListOpen.Add(newNodePosition); resortList = true; } } } ListOpen.RemoveAt(0); if (resortList) { ListOpen.Sort( delegate(Coords c1, Coords c2) { float difference = nodeArray[c1.X, c1.Y].Value.estimatedTotalCost - nodeArray[c2.X, c2.Y].Value.estimatedTotalCost; Int32 returnValue = 0; if (difference > 0) { returnValue = 1; } else if (difference < 0) { returnValue = -1; } return(returnValue); } ); } } List <Direction> ListRoute = new List <Direction>(); // Return empty route if the open list is empty, i.e. there is no path to the target // Ideally, the game logic should be fixed so that the search isn't even attempted // if there is no path between the two points. if (ListOpen.Count == 0) { return(ListRoute); } Coords trackbackCoords = endTopLeft; while (trackbackCoords != start) { Direction newDirection = nodeArray[trackbackCoords.X, trackbackCoords.Y].Value.connection; ListRoute.Add(newDirection); trackbackCoords = StaticMathFunctions.CoordsNeighboringInDirection(new Coords(CoordsType.Tile, trackbackCoords), StaticMathFunctions.OppositeDirection(newDirection)); } // Might be faster without reversing //ListRoute.Reverse(); // We skip the reversal, so pick directions from the END of the list. return(ListRoute); }
/// <summary> /// Tile-level (coarse) A* pathfinding. /// </summary> /// <param name="start"> Start Coords </param> /// <param name="endTopLeft"> Goal-box TopLeft Coords </param> /// <param name="endBottomRight"> Goal-box BottomRight Coords </param> /// <param name="h"> Heuristic function </param> /// <returns> Route to goal, as a list of Directions </returns> public List <Direction> PathfinderAStarCoarse(Coords start, Coords endTopLeft, Coords endBottomRight, HeuristicFunction h) { return(this._PathfinderAStar(new Coords(CoordsType.General, start), new Coords(CoordsType.General, endTopLeft), new Coords(CoordsType.General, endBottomRight), this._passabilityMap, delegate(Coords c) { return h(c, StaticMathFunctions.CoordsAverage(endTopLeft, endBottomRight)); })); }
/// Generates the influence map. /// Uses a silly recursive algorithm. /// Stopping conditions: Let's use two, to avoid stupid infinite loops. /// One is a distance threshold check. // Second is a min influence threshold check. /// <summary> /// Generates the influence map. /// Uses a silly recursive algorithm. /// Stopping conditions: Let's use two, to avoid stupid infinite loops. /// One is a distance threshold check. /// Second is a min influence threshold check. /// </summary> public void GenerateInfluenceMap() { // boolean array to keep note of which tiles have been processed //BitArray[,] takenCareOf = new BitArray[_currentMap.BoundX, _currentMap.BoundY]; BitArray[] takenCareOf = new BitArray[_currentMap.BoundX]; for (int i = 0; i < _currentMap.BoundX; ++i) { takenCareOf[i] = new BitArray(_currentMap.BoundY); } takenCareOf[Source.X][Source.Y] = true; // sets up two queues - one for the current pass, one for the next one // distance increments by one at each pass // if too slow, the process should be broken up so it does a number of passes each tick Queue <Coords> currentQueue = new Queue <Coords>(); Queue <Coords> nextQueue = new Queue <Coords>(); currentQueue.Enqueue(_source); UInt32 currentDistance = 0; // main loop // Stopping conditions: the two queues are exhausted, OR InfluenceMapMaxDistance is reached while ( ((currentQueue.Count > 0) & (nextQueue.Count > 0)) | (currentDistance < Constants.InfluenceMapMaxDistance) ) { // Checks if it's time to start the next pass if (currentQueue.Count == 0) { currentQueue = nextQueue; nextQueue = new Queue <Coords>(); currentDistance++; continue; } Coords currentCoords = currentQueue.Peek(); TilePassable currentTile = (TilePassable)CurrentMap.GetTile(currentCoords); // Analyzes the neighbors of the current Tile for possible additions to nextQueue for (byte i = 1; i <= 8; i++) { Direction currentDir = (Direction)i; if (currentTile.AllowedMovesCheckInDirection(currentDir)) { Coords toCheck = StaticMathFunctions.CoordsNeighboringInDirection(currentCoords, currentDir); if (!takenCareOf[toCheck.X][toCheck.Y]) { nextQueue.Enqueue(toCheck); takenCareOf[toCheck.X][toCheck.Y] = true; } } } float newVal = _f(currentDistance); // Check to avert infnite / excessively deep loop if (newVal > Constants.InfluenceMapMinThreshold) { this.SetMapValue(currentCoords, newVal); } currentQueue.Dequeue(); } }