//
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//
namespace MUnique.OpenMU.Pathfinding;
///
/// Base class for grid networks.
///
public abstract class BaseGridNetwork : INetwork
{
///
/// The grid node value of an unreachable grid coordinate.
///
private const byte UnreachableGridNodeValue = 0;
///
/// The bit flag which marks a safezone node.
///
private const byte SafezoneBitFlag = 0b1000_0000;
///
/// The bit mask for the cost of a node.
///
private const byte CostBitMask = 0b0111_1111;
private static readonly sbyte[] DirectionOffsetsX = { 0, 1, 0, -1, 1, 1, -1, -1 };
private static readonly sbyte[] DirectionOffsetsY = { -1, 0, 1, 0, -1, 1, 1, -1 };
private readonly int _numberOfDirections;
private ushort _gridWidth;
private ushort _gridHeight;
///
/// The two-dimensional grid.
/// For each coordinate it contains the cost of traveling to it from a neighbor coordinate.
/// The value of 0 means, that the coordinate is unreachable.
///
private byte[,]? _grid;
///
/// A flag, if safezone nodes should be included in the network.
///
private bool _includeSafezone;
///
/// Initializes a new instance of the class.
///
/// If set to true, diagonal traveling is allowed.
protected BaseGridNetwork(bool allowDiagonals)
{
this._numberOfDirections = allowDiagonals ? 8 : 4;
}
///
public virtual bool Prepare(Point start, Point end, byte[,] grid, bool includeSafezone)
{
this._grid = grid;
this._gridWidth = (ushort)(grid.GetUpperBound(0) + 1);
this._gridHeight = (ushort)(grid.GetUpperBound(1) + 1);
this._includeSafezone = includeSafezone;
return true;
}
///
///
/// Not sure, if the implementation should really filter out nodes based on their status and cost.
///
public IEnumerable GetPossibleNextNodes(Node node)
{
var grid = this._grid ?? throw new InvalidOperationException("Call Prepare before");
for (int i = 0; i < this._numberOfDirections; i++)
{
var calculatedX = node.X + DirectionOffsetsX[i];
var calculatedY = node.Y + DirectionOffsetsY[i];
if ((uint)calculatedX > byte.MaxValue || (uint)calculatedY > byte.MaxValue)
{
continue;
}
var newX = (byte)calculatedX;
var newY = (byte)calculatedY;
if (!this.IsWithinBounds(newX, newY))
{
continue;
}
var gridValue = grid[newX, newY];
if (!this._includeSafezone && (gridValue & SafezoneBitFlag) > 0)
{
continue;
}
var costToNode = gridValue & CostBitMask;
if (costToNode == UnreachableGridNodeValue)
{
continue;
}
var newPoint = new Point(newX, newY);
var newNode = this.GetNodeAt(newPoint);
if (newNode is null || newNode.Status == NodeStatus.Closed)
{
continue;
}
var newG = node.CostUntilNow + costToNode;
if (newNode.Status == NodeStatus.Open && newNode.CostUntilNow <= newG)
{
// The current node has less cost than the previous? then skip this node
continue;
}
newNode.CostUntilNow = newG;
yield return newNode;
}
}
///
public abstract Node? GetNodeAt(Point position);
///
/// Determines whether the coordinates are within bounds of this network.
///
/// The x.
/// The y.
///
/// true if the coordinates are within bounds of this network; otherwise, false.
///
protected virtual bool IsWithinBounds(byte x, byte y)
{
return x < this._gridWidth && y < this._gridHeight;
}
}