diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 890fe59..aa5f087 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -12,23 +12,32 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.0.x - + dotnet-version: 10.0.x + - name: Check Tag id: check-tag run: | if [[ v${{ github.event.ref }} =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo ::set-output name=match::true + echo "match=true" >> $GITHUB_OUTPUT fi - + + - name: Install libgdiplus + run: | + sudo apt-get update + sudo apt-get install -y libgdiplus + sudo ldconfig + # Create symbolic link for .NET runtime to find the library + sudo ln -sf /usr/lib/libgdiplus.so /usr/lib/gdiplus.dll + - name: Run Unit Tests run: | dotnet restore dotnet build + export DOTNET_SYSTEM_DRAWING_ENABLEUNIXSUPPORT=1 dotnet test test/NosCore.PathFinder.Tests -v m - name: Build Artifact @@ -39,23 +48,12 @@ jobs: dotnet build -c Release dotnet pack -c Release -o /tmp/nupkgs -v m -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg dotnet nuget push /tmp/nupkgs/NosCore.PathFinder.${{github.event.ref}}.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} - echo ::set-output name=ARTIFACT_PATH::/tmp/nupkgs/NosCore.PathFinder.${{github.event.ref}}.nupkg - echo ::set-output name=ARTIFACT_NAME::NosCore.PathFinder.${{github.event.ref}}.nupkg - - - name: Gets Latest Release - if: steps.check-tag.outputs.match == 'true' - id: latest_release_info - uses: jossef/action-latest-release-info@v1.1.0 - env: - GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }} + echo "ARTIFACT_PATH=/tmp/nupkgs/NosCore.PathFinder.${{github.event.ref}}.nupkg" >> $GITHUB_OUTPUT + echo "ARTIFACT_NAME=NosCore.PathFinder.${{github.event.ref}}.nupkg" >> $GITHUB_OUTPUT - name: Upload Release Asset if: steps.check-tag.outputs.match == 'true' - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }} + uses: softprops/action-gh-release@v2 with: - upload_url: ${{ steps.latest_release_info.outputs.upload_url }} - asset_path: ${{ steps.build_artifact.outputs.ARTIFACT_PATH }} - asset_name: ${{ steps.build_artifact.outputs.ARTIFACT_NAME }} - asset_content_type: application/zip + files: ${{ steps.build_artifact.outputs.ARTIFACT_PATH }} + token: ${{ secrets.REPO_TOKEN }} diff --git a/src/NosCore.PathFinder.Gui/Database/DataAccessHelper.cs b/src/NosCore.PathFinder.Gui/Database/DataAccessHelper.cs index 8f5fd2a..c1f6d11 100644 --- a/src/NosCore.PathFinder.Gui/Database/DataAccessHelper.cs +++ b/src/NosCore.PathFinder.Gui/Database/DataAccessHelper.cs @@ -23,6 +23,10 @@ public class DataAccessHelper /// public DbContext CreateContext() { + if (_option == null) + { + throw new InvalidOperationException("Database options must be initialized before creating a context. Call Initialize() first."); + } return new NosCoreContext(_option); } diff --git a/src/NosCore.PathFinder.Gui/Database/NosCoreContext.cs b/src/NosCore.PathFinder.Gui/Database/NosCoreContext.cs index e2ae8ce..85f3b55 100644 --- a/src/NosCore.PathFinder.Gui/Database/NosCoreContext.cs +++ b/src/NosCore.PathFinder.Gui/Database/NosCoreContext.cs @@ -11,7 +11,7 @@ namespace NosCore.PathFinder.Gui.Database { public class NosCoreContext : DbContext { - public NosCoreContext(DbContextOptions? options) : base(options) + public NosCoreContext(DbContextOptions options) : base(options) { } diff --git a/src/NosCore.PathFinder.Gui/NosCore.PathFinder.Gui.csproj b/src/NosCore.PathFinder.Gui/NosCore.PathFinder.Gui.csproj index ca74c23..3ac7bfc 100644 --- a/src/NosCore.PathFinder.Gui/NosCore.PathFinder.Gui.csproj +++ b/src/NosCore.PathFinder.Gui/NosCore.PathFinder.Gui.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net10.0 true latest enable @@ -14,13 +14,14 @@ - - - + + - + + + diff --git a/src/NosCore.PathFinder/Brushfire/BrushFire.cs b/src/NosCore.PathFinder/Brushfire/BrushFire.cs index cfcc9dd..6687ff6 100644 --- a/src/NosCore.PathFinder/Brushfire/BrushFire.cs +++ b/src/NosCore.PathFinder/Brushfire/BrushFire.cs @@ -8,9 +8,18 @@ namespace NosCore.PathFinder.Brushfire { + /// + /// Represents a brushfire pathfinding data structure containing pre-computed distance information. + /// public readonly struct BrushFire { - + /// + /// Initializes a new instance of the struct. + /// + /// The origin point of the brushfire. + /// The grid containing computed node information. + /// The width of the grid. + /// The length of the grid. public BrushFire((short X, short Y) origin, Dictionary<(short X, short Y), Node?> brushFireGrid, short width, short length) { @@ -20,14 +29,32 @@ public BrushFire((short X, short Y) origin, Dictionary<(short X, short Y), Node? Width = width; } + /// + /// Gets the origin point of the brushfire. + /// public (short X, short Y) Origin { get; } + /// + /// Gets the grid containing computed node information. + /// public Dictionary<(short X, short Y), Node?> Grid { get; } + /// + /// Gets the length of the grid. + /// public short Length { get; } + /// + /// Gets the width of the grid. + /// public short Width { get; } + /// + /// Gets the value at the specified coordinates. + /// + /// The X coordinate. + /// The Y coordinate. + /// The value at the specified position, or null if not found. public double? this[short x, short y] => Grid.ContainsKey((x, y)) ? Grid[(x, y)]?.Value : null; } } diff --git a/src/NosCore.PathFinder/Brushfire/IMapGridExtension.cs b/src/NosCore.PathFinder/Brushfire/IMapGridExtension.cs index 3ba20e3..fb41594 100644 --- a/src/NosCore.PathFinder/Brushfire/IMapGridExtension.cs +++ b/src/NosCore.PathFinder/Brushfire/IMapGridExtension.cs @@ -10,6 +10,9 @@ namespace NosCore.PathFinder.Brushfire { + /// + /// Extension methods for IMapGrid interface providing neighbor and brushfire functionality. + /// public static class IMapGridExtension { private static readonly List<(short X, short Y)> Neighbours = new List<(short, short)> { @@ -18,6 +21,13 @@ public static class IMapGridExtension (-1, 1), (0, 1), (1, 1) }; + /// + /// Gets the neighboring cells of a given cell in the grid. + /// + /// The map grid. + /// The cell to get neighbors for. + /// If true, includes non-walkable cells; otherwise, only returns walkable neighbors. + /// An enumerable sequence of neighboring cell positions. public static IEnumerable<(short X, short Y)> GetNeighbors(this IMapGrid grid, (short X, short Y) cell, bool includeWalls = false) { return Neighbours.Where(delta => @@ -31,6 +41,14 @@ public static class IMapGridExtension }).Select(delta => ((short)(cell.X + delta.X), (short)(cell.Y + delta.Y))); } + /// + /// Computes a brushfire distance map from a given origin point. + /// + /// The map grid to compute the brushfire on. + /// The origin point for the brushfire. + /// The heuristic to use for distance calculations. + /// The maximum distance to compute (default is 22). + /// A BrushFire structure containing pre-computed distance information. public static BrushFire LoadBrushFire(this IMapGrid mapGrid, (short X, short Y) user, IHeuristic heuristic, short maxDistance = 22) { if (user.X < 0 || user.X >= mapGrid.Width || diff --git a/src/NosCore.PathFinder/Brushfire/JumpNode.cs b/src/NosCore.PathFinder/Brushfire/JumpNode.cs index 35e7939..97fd81d 100644 --- a/src/NosCore.PathFinder/Brushfire/JumpNode.cs +++ b/src/NosCore.PathFinder/Brushfire/JumpNode.cs @@ -8,16 +8,34 @@ namespace NosCore.PathFinder.Brushfire { + /// + /// Represents a node used in Jump Point Search pathfinding algorithm. + /// Extends the base Node class with additional properties for JPS. + /// public class JumpNode : Node { + /// + /// Initializes a new instance of the class. + /// + /// The position of the node in the grid. + /// The value associated with the node. public JumpNode((short X, short Y) position, double? value) : base(position, value) { } + /// + /// Gets or sets the G score (cost from start to this node). + /// public double G { get; set; } + /// + /// Gets or sets the H score (heuristic cost from this node to the goal). + /// public double? H { get; set; } + /// + /// Gets or sets a value indicating whether this node has been opened for evaluation. + /// public bool Opened { get; set; } } } \ No newline at end of file diff --git a/src/NosCore.PathFinder/Brushfire/Node.cs b/src/NosCore.PathFinder/Brushfire/Node.cs index 6d863b6..82afcee 100644 --- a/src/NosCore.PathFinder/Brushfire/Node.cs +++ b/src/NosCore.PathFinder/Brushfire/Node.cs @@ -8,22 +8,45 @@ namespace NosCore.PathFinder.Brushfire { + /// + /// Represents a node in the pathfinding grid with position and value information. + /// public class Node { + /// + /// Initializes a new instance of the class. + /// + /// The position of the node in the grid. + /// The value associated with the node. public Node((short X, short Y) position, double? value) { Value = value; Position = position; } - + + /// + /// Gets or sets a value indicating whether this node has been processed and closed. + /// public bool Closed { get; set; } + /// + /// Gets or sets the parent node in the path. + /// public Node? Parent { get; set; } + /// + /// Gets or sets the F score (G + H) used in A* pathfinding. + /// public double F { get; set; } + /// + /// Gets or sets the position of the node in the grid. + /// public (short X, short Y) Position { get; set; } + /// + /// Gets or sets the value associated with this node. + /// public double? Value { get; set; } } } \ No newline at end of file diff --git a/src/NosCore.PathFinder/Heuristic/OctileDistanceHeuristic.cs b/src/NosCore.PathFinder/Heuristic/OctileDistanceHeuristic.cs index b52f47c..1504011 100644 --- a/src/NosCore.PathFinder/Heuristic/OctileDistanceHeuristic.cs +++ b/src/NosCore.PathFinder/Heuristic/OctileDistanceHeuristic.cs @@ -9,10 +9,18 @@ namespace NosCore.PathFinder.Heuristic { + /// + /// Heuristic that calculates octile distance (diagonal distance allowing 8-directional movement). + /// This heuristic is optimal for grids that allow diagonal movement at sqrt(2) cost. + /// public class OctileDistanceHeuristic : IHeuristic { + /// + /// The square root of 2, used as the cost multiplier for diagonal movement. + /// public readonly double Sqrt2 = Math.Sqrt(2); + /// public double GetDistance((short X, short Y) fromValuedCell, (short X, short Y) toValuedCell) { var iDx = Math.Abs(fromValuedCell.X - toValuedCell.X); diff --git a/src/NosCore.PathFinder/Interfaces/IHeuristic.cs b/src/NosCore.PathFinder/Interfaces/IHeuristic.cs index 90e6fcc..3af6dae 100644 --- a/src/NosCore.PathFinder/Interfaces/IHeuristic.cs +++ b/src/NosCore.PathFinder/Interfaces/IHeuristic.cs @@ -6,8 +6,17 @@ namespace NosCore.PathFinder.Interfaces { + /// + /// Interface for heuristic distance calculation used in pathfinding algorithms. + /// public interface IHeuristic { + /// + /// Calculates the estimated distance between two points. + /// + /// The starting point coordinates. + /// The ending point coordinates. + /// The estimated distance between the two points. public double GetDistance((short X, short Y) from, (short X, short Y) to); } } diff --git a/src/NosCore.PathFinder/Interfaces/IMapGrid.cs b/src/NosCore.PathFinder/Interfaces/IMapGrid.cs index d78c3a8..fa0820d 100644 --- a/src/NosCore.PathFinder/Interfaces/IMapGrid.cs +++ b/src/NosCore.PathFinder/Interfaces/IMapGrid.cs @@ -6,11 +6,35 @@ namespace NosCore.PathFinder.Interfaces { + /// + /// Interface representing a grid-based map for pathfinding. + /// public interface IMapGrid { + /// + /// Gets the width of the map grid. + /// public short Width { get; } + + /// + /// Gets the height of the map grid. + /// public short Height { get; } + + /// + /// Gets the cell value at the specified coordinates. + /// + /// The X coordinate. + /// The Y coordinate. + /// The byte value representing the cell at the given position. public byte this[short x, short y] { get; } + + /// + /// Determines whether the specified position is walkable. + /// + /// The X coordinate to check. + /// The Y coordinate to check. + /// True if the position is walkable; otherwise, false. bool IsWalkable(short currentX, short currentY); } } \ No newline at end of file diff --git a/src/NosCore.PathFinder/Interfaces/IPathfinder.cs b/src/NosCore.PathFinder/Interfaces/IPathfinder.cs index 98c1825..0e56196 100644 --- a/src/NosCore.PathFinder/Interfaces/IPathfinder.cs +++ b/src/NosCore.PathFinder/Interfaces/IPathfinder.cs @@ -8,8 +8,17 @@ namespace NosCore.PathFinder.Interfaces { + /// + /// Interface for pathfinding algorithms that find paths on a grid-based map. + /// public interface IPathfinder { + /// + /// Finds a path between two points on the map grid. + /// + /// The starting coordinates (X, Y). + /// The ending coordinates (X, Y). + /// An enumerable sequence of coordinates representing the path from start to end. IEnumerable<(short X, short Y)> FindPath((short X, short Y) start, (short X, short Y) end); } } \ No newline at end of file diff --git a/src/NosCore.PathFinder/NosCore.PathFinder.csproj b/src/NosCore.PathFinder/NosCore.PathFinder.csproj index 1ae42b2..b77b352 100644 --- a/src/NosCore.PathFinder/NosCore.PathFinder.csproj +++ b/src/NosCore.PathFinder/NosCore.PathFinder.csproj @@ -1,7 +1,7 @@  - net7.0 + net10.0 latest favicon.ico true @@ -21,7 +21,6 @@ icon.png true true - CS1591 @@ -29,10 +28,10 @@ - - - - + + + + diff --git a/src/NosCore.PathFinder/Pathfinder/GoalBasedPathfinder.cs b/src/NosCore.PathFinder/Pathfinder/GoalBasedPathfinder.cs index 5fc6e7b..84338f4 100644 --- a/src/NosCore.PathFinder/Pathfinder/GoalBasedPathfinder.cs +++ b/src/NosCore.PathFinder/Pathfinder/GoalBasedPathfinder.cs @@ -13,18 +13,37 @@ namespace NosCore.PathFinder.Pathfinder { + /// + /// Goal-based pathfinder implementation using brushfire algorithm with caching. + /// This pathfinder pre-computes paths from goals and caches them for performance. + /// public class GoalBasedPathfinder : IPathfinder { private readonly IMapGrid _mapGrid; + + /// + /// Static memory cache for storing pre-computed brushfire data. + /// public static readonly MemoryCache BrushFirecache = new MemoryCache(new MemoryCacheOptions()); private readonly IHeuristic _heuristic; + /// + /// Initializes a new instance of the class. + /// + /// The map grid to use for pathfinding. + /// The heuristic to use for distance calculations. public GoalBasedPathfinder(IMapGrid mapGrid, IHeuristic heuristic) { _mapGrid = mapGrid; _heuristic = heuristic; } + /// + /// Initializes a new instance of the class with a pre-computed brushfire. + /// + /// The map grid to use for pathfinding. + /// The heuristic to use for distance calculations. + /// Pre-computed brushfire data to cache. public GoalBasedPathfinder(IMapGrid mapGrid, IHeuristic heuristic, BrushFire brushfire) : this(mapGrid, heuristic) { CacheBrushFire(brushfire, brushfire.Origin); @@ -68,6 +87,7 @@ private BrushFire CacheBrushFire(BrushFire brushFire, (short X, short Y) start) return brushFire; } + /// public IEnumerable<(short X, short Y)> FindPath((short X, short Y) start, (short X, short Y) end) { List<(short X, short Y)> list = new(); diff --git a/src/NosCore.PathFinder/Pathfinder/JumpPointSearchPathfinder.cs b/src/NosCore.PathFinder/Pathfinder/JumpPointSearchPathfinder.cs index db7810a..e35898b 100644 --- a/src/NosCore.PathFinder/Pathfinder/JumpPointSearchPathfinder.cs +++ b/src/NosCore.PathFinder/Pathfinder/JumpPointSearchPathfinder.cs @@ -13,12 +13,25 @@ namespace NosCore.PathFinder.Pathfinder { + /// + /// Jump Point Search (JPS) pathfinder implementation with caching. + /// JPS is an optimization of A* that skips unnecessary nodes in uniform-cost grids. + /// public class JumpPointSearchPathfinder : IPathfinder { private readonly IMapGrid _mapGrid; + + /// + /// Static memory cache for storing computed paths. + /// public static readonly MemoryCache Cache = new MemoryCache(new MemoryCacheOptions()); private readonly IHeuristic _heuristic; + /// + /// Initializes a new instance of the class. + /// + /// The map grid to use for pathfinding. + /// The heuristic to use for distance calculations. public JumpPointSearchPathfinder(IMapGrid mapGrid, IHeuristic heuristic) { _mapGrid = mapGrid; @@ -57,6 +70,7 @@ public JumpPointSearchPathfinder(IMapGrid mapGrid, IHeuristic heuristic) return path; } + /// public IEnumerable<(short X, short Y)> FindPath((short X, short Y) start, (short X, short Y) end) { if (Cache.TryGetValue((start, end), out IEnumerable<(short X, short Y)>? cachedList)) @@ -149,6 +163,13 @@ private void IdentifySuccessors(JumpNode node, JumpNode?[,] nodes, (short X, sho } } + /// + /// Recursively searches for jump points in the specified direction. + /// + /// The current position to evaluate. + /// The previous position (determines search direction). + /// The destination position. + /// The jump point coordinates if found; otherwise, null. public (short X, short Y)? Jump((short X, short Y) current, (short X, short Y) proposed, (short X, short Y) end) { var x = current.X; diff --git a/test/NosCore.PathFinder.Benchmark/NosCore.PathFinder.Benchmark/NosCore.PathFinder.Benchmark.csproj b/test/NosCore.PathFinder.Benchmark/NosCore.PathFinder.Benchmark/NosCore.PathFinder.Benchmark.csproj index 5d489e5..f166323 100644 --- a/test/NosCore.PathFinder.Benchmark/NosCore.PathFinder.Benchmark/NosCore.PathFinder.Benchmark.csproj +++ b/test/NosCore.PathFinder.Benchmark/NosCore.PathFinder.Benchmark/NosCore.PathFinder.Benchmark.csproj @@ -1,12 +1,12 @@ Exe - net7.0 + net10.0 enable - + diff --git a/test/NosCore.PathFinder.Tests/.editorconfig b/test/NosCore.PathFinder.Tests/.editorconfig new file mode 100644 index 0000000..86d5290 --- /dev/null +++ b/test/NosCore.PathFinder.Tests/.editorconfig @@ -0,0 +1,6 @@ +root = false + +[*.cs] +# Suppress CA1416: Platform compatibility warnings for System.Drawing +# We explicitly enable Unix support via RuntimeHostConfigurationOption +dotnet_diagnostic.CA1416.severity = none diff --git a/test/NosCore.PathFinder.Tests/AssemblyInfo.cs b/test/NosCore.PathFinder.Tests/AssemblyInfo.cs new file mode 100644 index 0000000..2478ab6 --- /dev/null +++ b/test/NosCore.PathFinder.Tests/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Runtime.CompilerServices; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)] + +internal static class ModuleInitializer +{ + [ModuleInitializer] + internal static void Initialize() + { + // Enable System.Drawing.Common Unix support before any static initializers run + // Try multiple switch names for compatibility + System.AppContext.SetSwitch("System.Drawing.EnableUnixSupport", true); + System.Environment.SetEnvironmentVariable("DOTNET_SYSTEM_DRAWING_ENABLEUNIXSUPPORT", "1"); + } +} diff --git a/test/NosCore.PathFinder.Tests/IPathfinderTest.cs b/test/NosCore.PathFinder.Tests/IPathfinderTest.cs index 5d73934..6748265 100644 --- a/test/NosCore.PathFinder.Tests/IPathfinderTest.cs +++ b/test/NosCore.PathFinder.Tests/IPathfinderTest.cs @@ -44,7 +44,7 @@ public void AllPathfinderShouldReturnEnd() .GetTypes() .Where(mytype => mytype.GetInterfaces().Contains(typeof(IPathfinder))).ToList(); - Assert.IsTrue(!pathfindersTypes.Except(pathfinders.Select(s => s.GetType())).Any()); + Assert.AreEqual(0, pathfindersTypes.Except(pathfinders.Select(s => s.GetType())).Count()); foreach (var pathfinder in pathfinders) { diff --git a/test/NosCore.PathFinder.Tests/NosCore.PathFinder.Tests.csproj b/test/NosCore.PathFinder.Tests/NosCore.PathFinder.Tests.csproj index 066585c..90e0997 100644 --- a/test/NosCore.PathFinder.Tests/NosCore.PathFinder.Tests.csproj +++ b/test/NosCore.PathFinder.Tests/NosCore.PathFinder.Tests.csproj @@ -1,19 +1,24 @@  - net7.0 + net10.0 enable false + true - - - - - - - + + + + + + + + + + + diff --git a/test/NosCore.PathFinder.Tests/TestHelper.cs b/test/NosCore.PathFinder.Tests/TestHelper.cs index f638f30..c678fa2 100644 --- a/test/NosCore.PathFinder.Tests/TestHelper.cs +++ b/test/NosCore.PathFinder.Tests/TestHelper.cs @@ -39,11 +39,23 @@ public static void VerifyFile(string linearPathfinderPng, Bitmap bitmap, List