├── mazes ├── 6Kj2X.png ├── braid.gif ├── mazebig.jpg ├── BigRound.jpg ├── maze2_1_0640x0480.jpg └── 200 by 200 delta maze.png ├── README.md ├── src ├── JPS.Demo │ ├── App.config │ ├── Properties │ │ ├── Settings.settings │ │ ├── Settings.Designer.cs │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── Program.cs │ ├── Map.Designer.cs │ ├── Demo.cs │ ├── JPS.Demo.csproj │ ├── Demo.resx │ ├── Map.resx │ ├── Demo.Designer.cs │ └── Map.cs ├── JPS │ ├── PathingNode.cs │ ├── FastPriorityQueueNode.cs │ ├── GridLocation.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── JPS.csproj │ ├── Pathfinder.cs │ ├── Grid.cs │ └── FastPriorityQueue.cs ├── JPS.sln ├── .gitattributes └── .gitignore └── LICENSE /mazes/6Kj2X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamieG/JumpPointSearch/HEAD/mazes/6Kj2X.png -------------------------------------------------------------------------------- /mazes/braid.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamieG/JumpPointSearch/HEAD/mazes/braid.gif -------------------------------------------------------------------------------- /mazes/mazebig.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamieG/JumpPointSearch/HEAD/mazes/mazebig.jpg -------------------------------------------------------------------------------- /mazes/BigRound.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamieG/JumpPointSearch/HEAD/mazes/BigRound.jpg -------------------------------------------------------------------------------- /mazes/maze2_1_0640x0480.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamieG/JumpPointSearch/HEAD/mazes/maze2_1_0640x0480.jpg -------------------------------------------------------------------------------- /mazes/200 by 200 delta maze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamieG/JumpPointSearch/HEAD/mazes/200 by 200 delta maze.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jump Point Search (JPS) 2 | Jump Point Search (JPS) is an optimization to the A* search algorithm for uniform-cost grids. 3 | -------------------------------------------------------------------------------- /src/JPS.Demo/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/JPS.Demo/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/JPS.Demo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace JPS.Demo 5 | { 6 | internal static class Program 7 | { 8 | /// 9 | /// The main entry point for the application. 10 | /// 11 | [STAThread] 12 | private static void Main() 13 | { 14 | Application.EnableVisualStyles(); 15 | Application.SetCompatibleTextRenderingDefault(false); 16 | Application.Run(new Demo ()); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/JPS/PathingNode.cs: -------------------------------------------------------------------------------- 1 | namespace JPS 2 | { 3 | internal class PathingNode : FastPriorityQueueNode 4 | { 5 | public PathingNode(int x, int y) 6 | : this(new GridLocation(x, y)) 7 | { 8 | } 9 | 10 | public PathingNode(GridLocation location) 11 | { 12 | Location = location; 13 | } 14 | 15 | public GridLocation Location { get; private set; } 16 | 17 | public double? H { get; set; } 18 | public double F { get; set; } 19 | public double G { get; set; } 20 | public bool Opened { get; set; } 21 | public bool Closed { get; set; } 22 | //public bool IsNavigable { get; set; } 23 | public PathingNode Parent { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /src/JPS/FastPriorityQueueNode.cs: -------------------------------------------------------------------------------- 1 | namespace JPS 2 | { 3 | internal class FastPriorityQueueNode 4 | { 5 | /// 6 | /// The Priority to insert this node at. Must be set BEFORE adding a node to the queue 7 | /// 8 | public double Priority { get; set; } 9 | 10 | /// 11 | /// Used by the priority queue - do not edit this value. 12 | /// Represents the order the node was inserted in 13 | /// 14 | public long InsertionIndex { get; set; } 15 | 16 | /// 17 | /// Used by the priority queue - do not edit this value. 18 | /// Represents the current position in the queue 19 | /// 20 | public int QueueIndex { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jamie Gould 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/JPS.Demo/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace JPS.Demo.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/JPS/GridLocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace JPS 4 | { 5 | public struct GridLocation 6 | { 7 | public static GridLocation Empty = new GridLocation(-1, -1); 8 | 9 | public bool Equals(GridLocation other) 10 | { 11 | return X == other.X && Y == other.Y; 12 | } 13 | 14 | public override bool Equals(object obj) 15 | { 16 | if (ReferenceEquals(null, obj)) return false; 17 | return obj is GridLocation && Equals((GridLocation) obj); 18 | } 19 | 20 | public override int GetHashCode() 21 | { 22 | unchecked 23 | { 24 | return (X*397) ^ Y; 25 | } 26 | } 27 | 28 | public readonly int X; 29 | public readonly int Y; 30 | 31 | public GridLocation(int x, int y) 32 | { 33 | X = x; 34 | Y = y; 35 | } 36 | 37 | public static bool operator ==(GridLocation a, GridLocation b) 38 | { 39 | return a.X == b.X && a.Y == b.Y; 40 | } 41 | 42 | public static bool operator !=(GridLocation a, GridLocation b) 43 | { 44 | return !(a == b); 45 | } 46 | 47 | public override string ToString() 48 | { 49 | return string.Format("X: {0}, Y: {1}", X, Y); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/JPS/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("JPS")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JPS")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | 25 | [assembly: Guid("c742dcec-aa85-4697-b4fd-5008329a0844")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | 38 | [assembly: AssemblyVersion("1.0.0.0")] 39 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /src/JPS.Demo/Map.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace JPS.Demo 2 | { 3 | sealed partial class Map 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.SuspendLayout(); 32 | // 33 | // Map 34 | // 35 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 36 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 37 | this.Name = "Map"; 38 | this.Size = new System.Drawing.Size(670, 628); 39 | this.ResumeLayout(false); 40 | 41 | } 42 | 43 | #endregion 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/JPS.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JPS", "JPS\JPS.csproj", "{C742DCEC-AA85-4697-B4FD-5008329A0844}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JPS.Demo", "JPS.Demo\JPS.Demo.csproj", "{AEC520F4-75E7-4354-A7BB-FD7E4AD52EA9}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {C742DCEC-AA85-4697-B4FD-5008329A0844}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {C742DCEC-AA85-4697-B4FD-5008329A0844}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {C742DCEC-AA85-4697-B4FD-5008329A0844}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {C742DCEC-AA85-4697-B4FD-5008329A0844}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {AEC520F4-75E7-4354-A7BB-FD7E4AD52EA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AEC520F4-75E7-4354-A7BB-FD7E4AD52EA9}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AEC520F4-75E7-4354-A7BB-FD7E4AD52EA9}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AEC520F4-75E7-4354-A7BB-FD7E4AD52EA9}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /src/JPS.Demo/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("JPS.Demo")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JPS.Demo")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | 25 | [assembly: Guid("aec520f4-75e7-4354-a7bb-fd7e4ad52ea9")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | 38 | [assembly: AssemblyVersion("1.0.0.0")] 39 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /src/.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/JPS/JPS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C742DCEC-AA85-4697-B4FD-5008329A0844} 8 | Library 9 | Properties 10 | JPS 11 | JPS 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | -------------------------------------------------------------------------------- /src/JPS.Demo/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace JPS.Demo.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("JPS.Demo.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/JPS.Demo/Demo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Drawing; 5 | using System.Windows.Forms; 6 | 7 | namespace JPS.Demo 8 | { 9 | public sealed partial class Demo : Form 10 | { 11 | private bool[,] _map; 12 | 13 | public Demo() 14 | { 15 | InitializeComponent(); 16 | DoubleBuffered = true; 17 | 18 | CreateGrid(this, null); 19 | } 20 | 21 | private void CreateGrid(object sender, EventArgs e) 22 | { 23 | var rnd = new Random(); 24 | 25 | ctlMap.ClearCells(); 26 | ctlMap.ClearPath(); 27 | 28 | _map = new bool[ctlMap.Cols, ctlMap.Rows]; 29 | for (var x = 0; x <= _map.GetUpperBound(0); x++) 30 | for (var y = 0; y < _map.GetUpperBound(1); y++) 31 | { 32 | _map[x, y] = rnd.Next(0, 500) <= 490; 33 | if (!_map[x, y]) 34 | ctlMap.SetCell(new GridLocation(x, y), Color.Bisque); 35 | } 36 | } 37 | 38 | private void LoadImage(object sender, EventArgs e) 39 | { 40 | var openFileDialog = new OpenFileDialog(); 41 | if (openFileDialog.ShowDialog(this) != DialogResult.OK) 42 | return; 43 | 44 | ctlMap.ClearCells(); 45 | ctlMap.ClearPath(); 46 | 47 | using (var mazeImage = (Bitmap) Image.FromFile(openFileDialog.FileName)) 48 | { 49 | ctlMap.Cols = mazeImage.Width; 50 | ctlMap.Rows = mazeImage.Height; 51 | _map = new bool[ctlMap.Cols, ctlMap.Rows]; 52 | for (var x = 0; x <= _map.GetUpperBound(0); x++) 53 | for (var y = 0; y < _map.GetUpperBound(1); y++) 54 | { 55 | Color c = mazeImage.GetPixel(x, y); 56 | 57 | Int32 gs = (Int32) (c.R*0.3 + c.G*0.59 + c.B*0.11); 58 | 59 | _map[x, y] = gs > 200; 60 | if (!_map[x, y]) 61 | ctlMap.SetCell(new GridLocation(x, y), Color.Coral); 62 | } 63 | } 64 | } 65 | 66 | private GridLocation _start = GridLocation.Empty; 67 | private GridLocation _goal = GridLocation.Empty; 68 | 69 | private void FindPath(object sender, EventArgs e) 70 | { 71 | if (_start == GridLocation.Empty) 72 | { 73 | MessageBox.Show(@"Please select a Start location!", Text, MessageBoxButtons.OK, MessageBoxIcon.Asterisk); 74 | return; 75 | } 76 | 77 | if (_goal == GridLocation.Empty) 78 | { 79 | MessageBox.Show(@"Please select a Goal location!", Text, MessageBoxButtons.OK, MessageBoxIcon.Asterisk); 80 | return; 81 | } 82 | 83 | var grid = new Grid(_map); 84 | 85 | ctlMap.ClearPath(); 86 | 87 | var pathfinder = new Pathfinder(_start, _goal, grid); 88 | 89 | var stopwatch = new Stopwatch(); 90 | stopwatch.Start(); 91 | List path = pathfinder.FindPath(); 92 | stopwatch.Stop(); 93 | if (path != null) 94 | { 95 | ctlMap.SetPath(path); 96 | MessageBox.Show($"Path Found, Nodes {path.Count} in {stopwatch.ElapsedMilliseconds}ms", "Pathing", MessageBoxButtons.OK, MessageBoxIcon.Information); 97 | } 98 | else 99 | MessageBox.Show("No path found!", "Pathing", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); 100 | } 101 | 102 | private void CellMouseDown(object sender, CellMouseDownEventArgs e) 103 | { 104 | if (rdoWall.Checked) 105 | { 106 | _map[e.Location.X, e.Location.Y] = !_map[e.Location.X, e.Location.Y]; 107 | 108 | if (_map[e.Location.X, e.Location.Y]) 109 | ctlMap.ClearCell(e.Location); 110 | else 111 | ctlMap.SetCell(e.Location, Color.Gray); 112 | } 113 | else if (rdoGoal.Checked) 114 | { 115 | ctlMap.Goal = e.Location; 116 | _goal = e.Location; 117 | 118 | } 119 | else if (rdoStart.Checked) 120 | { 121 | ctlMap.Start = e.Location; 122 | _start = e.Location; 123 | } 124 | 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /src/JPS.Demo/JPS.Demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AEC520F4-75E7-4354-A7BB-FD7E4AD52EA9} 8 | WinExe 9 | Properties 10 | JPS.Demo 11 | JPS.Demo 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Form 51 | 52 | 53 | Demo.cs 54 | 55 | 56 | UserControl 57 | 58 | 59 | Map.cs 60 | 61 | 62 | 63 | 64 | Demo.cs 65 | 66 | 67 | Map.cs 68 | 69 | 70 | ResXFileCodeGenerator 71 | Resources.Designer.cs 72 | Designer 73 | 74 | 75 | True 76 | Resources.resx 77 | 78 | 79 | SettingsSingleFileGenerator 80 | Settings.Designer.cs 81 | 82 | 83 | True 84 | Settings.settings 85 | True 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | {c742dcec-aa85-4697-b4fd-5008329a0844} 94 | JPS 95 | 96 | 97 | 98 | 105 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | *.DotSettings 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | 144 | # TODO: Un-comment the next line if you do not want to checkin 145 | # your web deploy settings because they may include unencrypted 146 | # passwords 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # NuGet Packages 151 | *.nupkg 152 | # The packages folder can be ignored because of Package Restore 153 | **/packages/* 154 | # except build/, which is used as an MSBuild target. 155 | !**/packages/build/ 156 | # Uncomment if necessary however generally it will be regenerated when needed 157 | #!**/packages/repositories.config 158 | # NuGet v3's project.json files produces more ignoreable files 159 | *.nuget.props 160 | *.nuget.targets 161 | 162 | # Microsoft Azure Build Output 163 | csx/ 164 | *.build.csdef 165 | 166 | # Microsoft Azure Emulator 167 | ecf/ 168 | rcf/ 169 | 170 | # Microsoft Azure ApplicationInsights config file 171 | ApplicationInsights.config 172 | 173 | # Windows Store app package directory 174 | AppPackages/ 175 | BundleArtifacts/ 176 | 177 | # Visual Studio cache files 178 | # files ending in .cache can be ignored 179 | *.[Cc]ache 180 | # but keep track of directories ending in .cache 181 | !*.[Cc]ache/ 182 | 183 | # Others 184 | ClientBin/ 185 | [Ss]tyle[Cc]op.* 186 | ~$* 187 | *~ 188 | *.dbmdl 189 | *.dbproj.schemaview 190 | *.pfx 191 | *.publishsettings 192 | node_modules/ 193 | orleans.codegen.cs 194 | 195 | # RIA/Silverlight projects 196 | Generated_Code/ 197 | 198 | # Backup & report files from converting an old project file 199 | # to a newer Visual Studio version. Backup files are not needed, 200 | # because we have git ;-) 201 | _UpgradeReport_Files/ 202 | Backup*/ 203 | UpgradeLog*.XML 204 | UpgradeLog*.htm 205 | 206 | # SQL Server files 207 | *.mdf 208 | *.ldf 209 | 210 | # Business Intelligence projects 211 | *.rdl.data 212 | *.bim.layout 213 | *.bim_*.settings 214 | 215 | # Microsoft Fakes 216 | FakesAssemblies/ 217 | 218 | # GhostDoc plugin setting file 219 | *.GhostDoc.xml 220 | 221 | # Node.js Tools for Visual Studio 222 | .ntvs_analysis.dat 223 | 224 | # Visual Studio 6 build log 225 | *.plg 226 | 227 | # Visual Studio 6 workspace options file 228 | *.opt 229 | 230 | # Visual Studio LightSwitch build output 231 | **/*.HTMLClient/GeneratedArtifacts 232 | **/*.DesktopClient/GeneratedArtifacts 233 | **/*.DesktopClient/ModelManifest.xml 234 | **/*.Server/GeneratedArtifacts 235 | **/*.Server/ModelManifest.xml 236 | _Pvt_Extensions 237 | 238 | # LightSwitch generated files 239 | GeneratedArtifacts/ 240 | ModelManifest.xml 241 | 242 | # Paket dependency manager 243 | .paket/paket.exe 244 | 245 | # FAKE - F# Make 246 | .fake/ -------------------------------------------------------------------------------- /src/JPS/Pathfinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace JPS 7 | { 8 | public class Pathfinder 9 | { 10 | public static readonly double Sqrt2 = Math.Sqrt(2); 11 | private readonly GridLocation _goal; 12 | private readonly Grid _grid; 13 | 14 | private readonly FastPriorityQueue _open; 15 | private readonly GridLocation _start; 16 | 17 | public Pathfinder(GridLocation start, GridLocation goal, Grid grid) 18 | { 19 | _start = start; 20 | _goal = goal; 21 | _grid = grid; 22 | 23 | _open = new FastPriorityQueue(4096); 24 | } 25 | 26 | public List FindPath() 27 | { 28 | var startNode = new PathingNode(_start) {F = 0, G = 0, Opened = true}; 29 | 30 | _open.Enqueue(startNode, startNode.F); 31 | 32 | while (_open.Count != 0) 33 | { 34 | PathingNode node = _open.Dequeue(); 35 | 36 | node.Closed = true; 37 | 38 | if (node.Location == _goal) 39 | return Trace(node); 40 | 41 | IdentitySuccessors(node); 42 | } 43 | 44 | return null; 45 | } 46 | 47 | private List Trace(PathingNode node) 48 | { 49 | var path = new List {node.Location}; 50 | while (node.Parent != null) 51 | { 52 | node = node.Parent; 53 | path.Add(node.Location); 54 | } 55 | path.Reverse(); 56 | return path; 57 | } 58 | 59 | private void IdentitySuccessors(PathingNode node) 60 | { 61 | foreach (PathingNode neighbour in _grid.Neighbours(node)) 62 | { 63 | GridLocation jumpPoint = Jump(neighbour.Location, node.Location); 64 | if (jumpPoint != GridLocation.Empty) 65 | { 66 | PathingNode jumpNode = _grid[jumpPoint]; 67 | 68 | if (jumpNode.Closed) 69 | continue; 70 | 71 | double d = Heuristic(Math.Abs(jumpPoint.X - node.Location.X), Math.Abs(jumpPoint.Y - node.Location.Y)); 72 | double ng = node.G + d; 73 | 74 | if (!jumpNode.Opened || ng < jumpNode.G) 75 | { 76 | jumpNode.G = ng; 77 | if (!jumpNode.H.HasValue) 78 | jumpNode.H = Heuristic(Math.Abs(jumpPoint.X - _goal.X), Math.Abs(jumpPoint.Y - _goal.Y)); 79 | jumpNode.F = jumpNode.G + jumpNode.H.Value; 80 | jumpNode.Parent = node; 81 | 82 | if (!jumpNode.Opened) 83 | { 84 | _open.Enqueue(jumpNode, jumpNode.F); 85 | jumpNode.Opened = true; 86 | } 87 | else 88 | { 89 | _open.UpdatePriority(jumpNode, jumpNode.F); 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 97 | private double Heuristic(int dx, int dy) 98 | { 99 | return dx < dy ? (Sqrt2 - 1)*dx + dy : (Sqrt2 - 1)*dy + dx; 100 | } 101 | 102 | public GridLocation Jump(GridLocation current, GridLocation proposed) 103 | { 104 | int x = current.X; 105 | int y = current.Y; 106 | int dx = current.X - proposed.X; 107 | int dy = current.Y - proposed.Y; 108 | 109 | if (!_grid.IsNavigable(x, y)) 110 | return GridLocation.Empty; 111 | 112 | if (_goal == current) 113 | return current; 114 | 115 | // Diagonal 116 | if (dx != 0 && dy != 0) 117 | { 118 | if ((_grid.IsNavigable(x - dx, y + dy) && !_grid.IsNavigable(x - dx, y)) || 119 | (_grid.IsNavigable(x + dx, y - dy) && !_grid.IsNavigable(x, y - dy))) 120 | return current; 121 | 122 | if (Jump(new GridLocation(x + dx, y), current) != GridLocation.Empty || 123 | Jump(new GridLocation(x, y + dy), current) != GridLocation.Empty) 124 | return current; 125 | } 126 | // Cardinal 127 | else 128 | { 129 | if (dx != 0) 130 | { 131 | // Horizontal 132 | if ((_grid.IsNavigable(x + dx, y + 1) && !_grid.IsNavigable(x, y + 1)) || 133 | (_grid.IsNavigable(x + dx, y - 1) && !_grid.IsNavigable(x, y - 1))) 134 | return current; 135 | } 136 | else 137 | { 138 | // Vertical 139 | if ((_grid.IsNavigable(x + 1, y + dy) && !_grid.IsNavigable(x + 1, y)) || 140 | (_grid.IsNavigable(x - 1, y + dy) && !_grid.IsNavigable(x - 1, y))) 141 | return current; 142 | } 143 | } 144 | 145 | if (_grid.IsNavigable(x + dx, y) || _grid.IsNavigable(x, y + dy)) 146 | return Jump(new GridLocation(x + dx, y + dy), current); 147 | 148 | return GridLocation.Empty; 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /src/JPS/Grid.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace JPS 6 | { 7 | public class Grid 8 | { 9 | private static readonly GridLocation[] Directions = 10 | { 11 | // Cardinal 12 | new GridLocation(-1, 0), // W 13 | new GridLocation(1, 0), // E 14 | new GridLocation(0, 1), // N 15 | new GridLocation(0, -1), // S 16 | // Diagonal 17 | new GridLocation(-1, -1), // NW 18 | new GridLocation(-1, 1), // SW 19 | new GridLocation(1, -1), // NE 20 | new GridLocation(1, 1) // SE 21 | }; 22 | 23 | private readonly int _boundsMaxX; 24 | private readonly int _boundsMaxY; 25 | 26 | private readonly int _boundsMinX; 27 | private readonly int _boundsMinY; 28 | 29 | private readonly PathingNode[,] _grid; 30 | private readonly bool[,] _navigable; 31 | 32 | public Grid(bool[,] navigable) 33 | { 34 | _boundsMinX = 0; 35 | _boundsMaxX = navigable.GetUpperBound(0); 36 | _boundsMinY = 0; 37 | _boundsMaxY = navigable.GetUpperBound(1); 38 | 39 | _navigable = navigable; 40 | 41 | // Initialise the Grid 42 | _grid = new PathingNode[_boundsMaxX + 1, _boundsMaxY + 1]; 43 | for (var x = _boundsMinX; x <= _boundsMaxX; x++) 44 | for (var y = _boundsMinY; y <= _boundsMaxY; y++) 45 | _grid[x, y] = new PathingNode(x, y); 46 | } 47 | 48 | internal PathingNode this[int x, int y] { get { return _grid[x, y]; } } 49 | internal PathingNode this[GridLocation location] { get { return _grid[location.X, location.Y]; } } 50 | 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public bool IsNavigable(int x, int y) 53 | { 54 | return InBounds(x, y) && _navigable[x, y]; 55 | } 56 | 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | public bool InBounds(int x, int y) 59 | { 60 | return x >= _boundsMinX && x <= _boundsMaxX && 61 | y >= _boundsMinY && y <= _boundsMaxY; 62 | } 63 | 64 | internal IEnumerable Neighbours(PathingNode node) 65 | { 66 | if (node.Parent != null) 67 | { 68 | GridLocation n = node.Location; 69 | GridLocation p = node.Parent.Location; 70 | 71 | GridLocation dNorm = new GridLocation( 72 | (n.X - p.X)/Math.Max(Math.Abs(n.X - p.X), 1), 73 | (n.Y - p.Y)/Math.Max(Math.Abs(n.Y - p.Y), 1)); 74 | 75 | // Diagonal 76 | if (dNorm.X != 0 && dNorm.Y != 0) 77 | { 78 | if (IsNavigable(n.X, n.Y + dNorm.Y)) 79 | yield return _grid[n.X, n.Y + dNorm.Y]; 80 | 81 | if (IsNavigable(n.X + dNorm.X, n.Y)) 82 | yield return _grid[n.X + dNorm.X, n.Y]; 83 | 84 | if ((IsNavigable(n.X, n.Y + dNorm.Y) || IsNavigable(n.X + dNorm.X, n.Y)) && IsNavigable(n.X + dNorm.X, n.Y + dNorm.Y)) 85 | yield return _grid[n.X + dNorm.X, n.Y + dNorm.Y]; 86 | 87 | if (!IsNavigable(n.X - dNorm.X, n.Y) && IsNavigable(n.X, n.Y + dNorm.Y) && IsNavigable(n.X - dNorm.X, n.Y + dNorm.Y)) 88 | yield return _grid[n.X - dNorm.X, n.Y + dNorm.Y]; 89 | 90 | if (!IsNavigable(n.X, n.Y - dNorm.Y) && IsNavigable(n.X + dNorm.X, n.Y) && IsNavigable(n.X + dNorm.X, n.Y - dNorm.Y)) 91 | yield return _grid[n.X + dNorm.X, n.Y - dNorm.Y]; 92 | } 93 | // Cardinal 94 | else 95 | { 96 | if (dNorm.X == 0) 97 | { 98 | if (IsNavigable(n.X, n.Y + dNorm.Y)) 99 | { 100 | yield return _grid[n.X, n.Y + dNorm.Y]; 101 | 102 | if (!IsNavigable(n.X + 1, n.Y) && IsNavigable(n.X + 1, n.Y + dNorm.Y)) 103 | yield return _grid[n.X + 1, n.Y + dNorm.Y]; 104 | 105 | if (!IsNavigable(n.X - 1, n.Y) && IsNavigable(n.X - 1, n.Y + dNorm.Y)) 106 | yield return _grid[n.X - 1, n.Y + dNorm.Y]; 107 | } 108 | } 109 | else if (IsNavigable(n.X + dNorm.X, n.Y)) 110 | { 111 | yield return _grid[n.X + dNorm.X, n.Y]; 112 | 113 | if (!IsNavigable(n.X, n.Y + 1) && IsNavigable(n.X + dNorm.X, n.Y + 1)) 114 | yield return _grid[n.X + dNorm.X, n.Y + 1]; 115 | 116 | if (!IsNavigable(n.X, n.Y - 1) && IsNavigable(n.X + dNorm.X, n.Y - 1)) 117 | yield return _grid[n.X + dNorm.X, n.Y - 1]; 118 | } 119 | } 120 | } 121 | else 122 | { 123 | for (var i = 0; i < Directions.Length; i++) 124 | { 125 | int propX = node.Location.X + Directions[i].X; 126 | int propY = node.Location.Y + Directions[i].Y; 127 | 128 | if (IsNavigable(propX, propY)) 129 | { 130 | yield return this[propX, propY]; 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /src/JPS.Demo/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /src/JPS.Demo/Demo.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /src/JPS.Demo/Map.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /src/JPS.Demo/Demo.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace JPS.Demo 2 | { 3 | sealed partial class Demo 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.btnCreateGrid = new System.Windows.Forms.Button(); 32 | this.btnFindPath = new System.Windows.Forms.Button(); 33 | this.ctlMap = new JPS.Demo.Map(); 34 | this.btnLoadImage = new System.Windows.Forms.Button(); 35 | this.rdoStart = new System.Windows.Forms.RadioButton(); 36 | this.rdoGoal = new System.Windows.Forms.RadioButton(); 37 | this.rdoWall = new System.Windows.Forms.RadioButton(); 38 | this.grpSetSelect = new System.Windows.Forms.GroupBox(); 39 | this.grpSetSelect.SuspendLayout(); 40 | this.SuspendLayout(); 41 | // 42 | // btnCreateGrid 43 | // 44 | this.btnCreateGrid.Location = new System.Drawing.Point(12, 12); 45 | this.btnCreateGrid.Name = "btnCreateGrid"; 46 | this.btnCreateGrid.Size = new System.Drawing.Size(97, 31); 47 | this.btnCreateGrid.TabIndex = 0; 48 | this.btnCreateGrid.Text = "Create Grid"; 49 | this.btnCreateGrid.UseVisualStyleBackColor = true; 50 | this.btnCreateGrid.Click += new System.EventHandler(this.CreateGrid); 51 | // 52 | // btnFindPath 53 | // 54 | this.btnFindPath.Location = new System.Drawing.Point(12, 49); 55 | this.btnFindPath.Name = "btnFindPath"; 56 | this.btnFindPath.Size = new System.Drawing.Size(97, 31); 57 | this.btnFindPath.TabIndex = 1; 58 | this.btnFindPath.Text = "Find Path"; 59 | this.btnFindPath.UseVisualStyleBackColor = true; 60 | this.btnFindPath.Click += new System.EventHandler(this.FindPath); 61 | // 62 | // ctlMap 63 | // 64 | this.ctlMap.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 65 | | System.Windows.Forms.AnchorStyles.Left) 66 | | System.Windows.Forms.AnchorStyles.Right))); 67 | this.ctlMap.Cols = 256; 68 | this.ctlMap.DrawGrid = false; 69 | this.ctlMap.Location = new System.Drawing.Point(133, 12); 70 | this.ctlMap.Name = "ctlMap"; 71 | this.ctlMap.Rows = 256; 72 | this.ctlMap.SelectedCell = null; 73 | this.ctlMap.Size = new System.Drawing.Size(764, 640); 74 | this.ctlMap.TabIndex = 2; 75 | this.ctlMap.CellMouseDown += new System.EventHandler(this.CellMouseDown); 76 | // 77 | // btnLoadImage 78 | // 79 | this.btnLoadImage.Location = new System.Drawing.Point(12, 86); 80 | this.btnLoadImage.Name = "btnLoadImage"; 81 | this.btnLoadImage.Size = new System.Drawing.Size(97, 31); 82 | this.btnLoadImage.TabIndex = 3; 83 | this.btnLoadImage.Text = "Load Maze"; 84 | this.btnLoadImage.UseVisualStyleBackColor = true; 85 | this.btnLoadImage.Click += new System.EventHandler(this.LoadImage); 86 | // 87 | // rdoStart 88 | // 89 | this.rdoStart.AutoSize = true; 90 | this.rdoStart.Checked = true; 91 | this.rdoStart.Location = new System.Drawing.Point(16, 29); 92 | this.rdoStart.Name = "rdoStart"; 93 | this.rdoStart.Size = new System.Drawing.Size(47, 17); 94 | this.rdoStart.TabIndex = 0; 95 | this.rdoStart.TabStop = true; 96 | this.rdoStart.Text = "Start"; 97 | this.rdoStart.UseVisualStyleBackColor = true; 98 | // 99 | // rdoGoal 100 | // 101 | this.rdoGoal.AutoSize = true; 102 | this.rdoGoal.Location = new System.Drawing.Point(16, 52); 103 | this.rdoGoal.Name = "rdoGoal"; 104 | this.rdoGoal.Size = new System.Drawing.Size(47, 17); 105 | this.rdoGoal.TabIndex = 1; 106 | this.rdoGoal.Text = "Goal"; 107 | this.rdoGoal.UseVisualStyleBackColor = true; 108 | // 109 | // rdoWall 110 | // 111 | this.rdoWall.AutoSize = true; 112 | this.rdoWall.Location = new System.Drawing.Point(16, 75); 113 | this.rdoWall.Name = "rdoWall"; 114 | this.rdoWall.Size = new System.Drawing.Size(46, 17); 115 | this.rdoWall.TabIndex = 2; 116 | this.rdoWall.Text = "Wall"; 117 | this.rdoWall.UseVisualStyleBackColor = true; 118 | // 119 | // grpSetSelect 120 | // 121 | this.grpSetSelect.Controls.Add(this.rdoStart); 122 | this.grpSetSelect.Controls.Add(this.rdoWall); 123 | this.grpSetSelect.Controls.Add(this.rdoGoal); 124 | this.grpSetSelect.Location = new System.Drawing.Point(12, 123); 125 | this.grpSetSelect.Name = "grpSetSelect"; 126 | this.grpSetSelect.Size = new System.Drawing.Size(97, 108); 127 | this.grpSetSelect.TabIndex = 4; 128 | this.grpSetSelect.TabStop = false; 129 | this.grpSetSelect.Text = "Setting"; 130 | // 131 | // Demo 132 | // 133 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 134 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 135 | this.ClientSize = new System.Drawing.Size(909, 663); 136 | this.Controls.Add(this.grpSetSelect); 137 | this.Controls.Add(this.btnLoadImage); 138 | this.Controls.Add(this.ctlMap); 139 | this.Controls.Add(this.btnFindPath); 140 | this.Controls.Add(this.btnCreateGrid); 141 | this.Name = "Demo"; 142 | this.Text = "Jump Point Search Demo"; 143 | this.grpSetSelect.ResumeLayout(false); 144 | this.grpSetSelect.PerformLayout(); 145 | this.ResumeLayout(false); 146 | 147 | } 148 | 149 | #endregion 150 | 151 | private System.Windows.Forms.Button btnCreateGrid; 152 | private System.Windows.Forms.Button btnFindPath; 153 | private Map ctlMap; 154 | private System.Windows.Forms.Button btnLoadImage; 155 | private System.Windows.Forms.RadioButton rdoStart; 156 | private System.Windows.Forms.RadioButton rdoGoal; 157 | private System.Windows.Forms.RadioButton rdoWall; 158 | private System.Windows.Forms.GroupBox grpSetSelect; 159 | } 160 | } 161 | 162 | -------------------------------------------------------------------------------- /src/JPS.Demo/Map.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Windows.Forms; 6 | 7 | namespace JPS.Demo 8 | { 9 | public sealed partial class Map : UserControl 10 | { 11 | private readonly SortedList _brushes = new SortedList(); 12 | 13 | public readonly Color[,] _cells = new Color[4096, 4096]; 14 | 15 | private GridLocation? _selectedCell; 16 | 17 | public event EventHandler CellMouseDown; 18 | 19 | public bool DrawGrid { get; set; } 20 | 21 | public int Margin = 4; 22 | 23 | public Map() 24 | { 25 | InitializeComponent(); 26 | 27 | DoubleBuffered = true; 28 | 29 | MouseMove += Mousing; 30 | MouseDown += (s, e) => _mouseDown = true; 31 | MouseUp += (s, e) => _mouseDown = false; 32 | } 33 | 34 | private bool _mouseDown; 35 | private GridLocation _LastMouseDownLocation; 36 | private GridLocation _mouseDownLocation = GridLocation.Empty; 37 | 38 | public int Rows { get; set; } 39 | public int Cols { get; set; } 40 | 41 | public double RowHeight => (Height - Margin*2d)/Rows; 42 | public double ColWidth => (Width - Margin*2d)/Cols; 43 | 44 | public GridLocation? SelectedCell 45 | { 46 | get { return _selectedCell; } 47 | set 48 | { 49 | if (_selectedCell != value) 50 | { 51 | _selectedCell = value; 52 | Invalidate(); 53 | } 54 | } 55 | } 56 | 57 | public void SetCell(GridLocation location, Color color) 58 | { 59 | _cells[location.X, location.Y] = color; 60 | Invalidate(); 61 | } 62 | 63 | public void ClearCell(GridLocation location) 64 | { 65 | _cells[location.X, location.Y] = default(Color); 66 | Invalidate(); 67 | } 68 | 69 | public void ClearCells() 70 | { 71 | for (var x = 0; x <= Cols; x++) 72 | for (var y = 0; y <= Rows; y++) 73 | _cells[x,y] = default(Color); 74 | 75 | Invalidate(); 76 | } 77 | 78 | private void Mousing(object sender, MouseEventArgs e) 79 | { 80 | var gridX = (int) (((double) e.X - Margin)/ColWidth); 81 | var gridY = (int) (((double) e.Y - Margin)/RowHeight); 82 | 83 | if (gridX >= 0 && gridX <= Cols - 1 && gridY >= 0 && gridY <= Rows - 1) 84 | { 85 | SelectedCell = new GridLocation(gridX, gridY); 86 | 87 | if (_mouseDown) 88 | { 89 | _mouseDownLocation = SelectedCell.Value; 90 | 91 | if (_LastMouseDownLocation != _mouseDownLocation) 92 | { 93 | _LastMouseDownLocation = _mouseDownLocation; 94 | OnCellMouseDown(new CellMouseDownEventArgs(_LastMouseDownLocation)); 95 | _mouseDownLocation = default(GridLocation); 96 | } 97 | } 98 | } 99 | else 100 | _mouseDown = false; 101 | } 102 | 103 | protected override void OnPaint(PaintEventArgs e) 104 | { 105 | e.Graphics.Clear(Color.White); 106 | 107 | if (DrawGrid) 108 | { 109 | for (var x = 0; x <= Cols; x++) 110 | e.Graphics.DrawLine(Pens.DarkGray, 111 | (int) (x*ColWidth + Margin), Margin, 112 | (int) (x*ColWidth + Margin), (int) (Rows*RowHeight + Margin)); 113 | 114 | for (var y = 0; y <= Rows; y++) 115 | e.Graphics.DrawLine(Pens.DarkGray, Margin, (int) (y*RowHeight + Margin), 116 | (int) (Cols*ColWidth + Margin), (int) (y*RowHeight + Margin)); 117 | } 118 | 119 | for (var x = 0; x <= Cols; x++) 120 | for (var y = 0; y <= Rows; y++) 121 | { 122 | if (_cells[x, y] != default(Color)) 123 | { 124 | Brush brush; 125 | if (_brushes.ContainsKey(_cells[x, y].ToArgb())) 126 | brush = _brushes[_cells[x, y].ToArgb()]; 127 | else 128 | _brushes.Add(_cells[x, y].ToArgb(), brush = new SolidBrush(_cells[x, y])); 129 | 130 | e.Graphics.FillRectangle(brush, 131 | (int) (x*ColWidth + Margin), 132 | (int) (y*RowHeight + Margin), 133 | (int) ColWidth+2, (int) RowHeight+2); 134 | } 135 | } 136 | 137 | if (_path != null && _path.Any()) 138 | for (int i = 1; i < _path.Count; i++) 139 | e.Graphics.DrawLine(Pens.Green, 140 | (int) (_path[i - 1].X*ColWidth + Margin + (int) (ColWidth/2)), (int) (_path[i - 1].Y*RowHeight + Margin + (int) (RowHeight/2)), 141 | (int) (_path[i].X*ColWidth + Margin + (int) (ColWidth/2)), (int) (_path[i].Y*RowHeight + Margin + (int) (RowHeight/2))); 142 | 143 | if (_start != GridLocation.Empty) 144 | { 145 | e.Graphics.FillRectangle(Brushes.DarkRed, 146 | (int) (_start.X*ColWidth + Margin), 147 | (int) (_start.Y*RowHeight + Margin), 148 | (int) ColWidth+2, (int) RowHeight+2); 149 | } 150 | 151 | if (_goal != GridLocation.Empty) 152 | { 153 | e.Graphics.FillRectangle(Brushes.DarkGreen, 154 | (int) (_goal.X*ColWidth + Margin), 155 | (int) (_goal.Y*RowHeight + Margin), 156 | (int) ColWidth+2, (int) RowHeight+2); 157 | } 158 | 159 | if (SelectedCell.HasValue) 160 | { 161 | e.Graphics.FillRectangle(Brushes.DodgerBlue, 162 | (int) (SelectedCell.Value.X*ColWidth + Margin), 163 | (int) (SelectedCell.Value.Y*RowHeight + Margin), 164 | (int) ColWidth+2, (int) RowHeight+2); 165 | } 166 | } 167 | 168 | 169 | private void OnCellMouseDown(CellMouseDownEventArgs e) 170 | { 171 | CellMouseDown?.Invoke(this, e); 172 | } 173 | 174 | private List _path; 175 | 176 | public void SetPath(List path) 177 | { 178 | _path = path; 179 | Invalidate(); 180 | } 181 | 182 | public void ClearPath() 183 | { 184 | _path = null; 185 | Invalidate(); 186 | } 187 | 188 | private GridLocation _start; 189 | private GridLocation _goal; 190 | public GridLocation Start 191 | { 192 | get { return _start; } 193 | set { _start = value; Invalidate();} 194 | } 195 | 196 | public GridLocation Goal 197 | { 198 | get { return _goal; } 199 | set { _goal = value; Invalidate();} 200 | } 201 | } 202 | 203 | public class CellMouseDownEventArgs : EventArgs 204 | { 205 | public GridLocation Location { get; } 206 | 207 | public CellMouseDownEventArgs(GridLocation location) 208 | { 209 | Location = location; 210 | } 211 | } 212 | } -------------------------------------------------------------------------------- /src/JPS/FastPriorityQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace JPS 7 | { 8 | /// 9 | /// An implementation of a min-Priority Queue using a heap. Has O(1) .Contains()! 10 | /// See https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp/wiki/Getting-Started for more information 11 | /// 12 | /// The values in the queue. Must extend the FastPriorityQueueNode class 13 | internal class FastPriorityQueue : IEnumerable 14 | where T : FastPriorityQueueNode 15 | { 16 | private T[] _nodes; 17 | private long _numNodesEverEnqueued; 18 | 19 | /// 20 | /// Instantiate a new Priority Queue 21 | /// 22 | /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) 23 | public FastPriorityQueue(int maxNodes) 24 | { 25 | Count = 0; 26 | _nodes = new T[maxNodes + 1]; 27 | _numNodesEverEnqueued = 0; 28 | } 29 | 30 | /// 31 | /// Returns the number of nodes in the queue. 32 | /// O(1) 33 | /// 34 | public int Count { get; private set; } 35 | 36 | /// 37 | /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once 38 | /// Count == MaxSize), 39 | /// attempting to enqueue another item will cause undefined behavior. O(1) 40 | /// 41 | public int MaxSize { get { return _nodes.Length - 1; } } 42 | 43 | /// 44 | /// Removes every node from the queue. 45 | /// O(n) (So, don't do this often!) 46 | /// 47 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 48 | public void Clear() 49 | { 50 | Array.Clear(_nodes, 1, Count); 51 | Count = 0; 52 | } 53 | 54 | /// 55 | /// Returns (in O(1)!) whether the given node is in the queue. O(1) 56 | /// 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | public bool Contains(T node) 59 | { 60 | return _nodes[node.QueueIndex] == node; 61 | } 62 | 63 | /// 64 | /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. 65 | /// If the queue is full, the result is undefined. 66 | /// If the node is already enqueued, the result is undefined. 67 | /// O(log n) 68 | /// 69 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 70 | public void Enqueue(T node, double priority) 71 | { 72 | node.Priority = priority; 73 | Count++; 74 | _nodes[Count] = node; 75 | node.QueueIndex = Count; 76 | node.InsertionIndex = _numNodesEverEnqueued++; 77 | CascadeUp(_nodes[Count]); 78 | } 79 | 80 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 81 | private void Swap(T node1, T node2) 82 | { 83 | //Swap the nodes 84 | _nodes[node1.QueueIndex] = node2; 85 | _nodes[node2.QueueIndex] = node1; 86 | 87 | //Swap their indicies 88 | int temp = node1.QueueIndex; 89 | node1.QueueIndex = node2.QueueIndex; 90 | node2.QueueIndex = temp; 91 | } 92 | 93 | //Performance appears to be slightly better when this is NOT inlined o_O 94 | private void CascadeUp(T node) 95 | { 96 | //aka Heapify-up 97 | int parent = node.QueueIndex/2; 98 | while (parent >= 1) 99 | { 100 | T parentNode = _nodes[parent]; 101 | if (HasHigherPriority(parentNode, node)) 102 | break; 103 | 104 | //Node has lower priority value, so move it up the heap 105 | Swap(node, parentNode); //For some reason, this is faster with Swap() rather than (less..?) individual operations, like in CascadeDown() 106 | 107 | parent = node.QueueIndex/2; 108 | } 109 | } 110 | 111 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 112 | private void CascadeDown(T node) 113 | { 114 | //aka Heapify-down 115 | int finalQueueIndex = node.QueueIndex; 116 | while (true) 117 | { 118 | T newParent = node; 119 | int childLeftIndex = 2*finalQueueIndex; 120 | 121 | //Check if the left-child is higher-priority than the current node 122 | if (childLeftIndex > Count) 123 | { 124 | //This could be placed outside the loop, but then we'd have to check newParent != node twice 125 | node.QueueIndex = finalQueueIndex; 126 | _nodes[finalQueueIndex] = node; 127 | break; 128 | } 129 | 130 | T childLeft = _nodes[childLeftIndex]; 131 | if (HasHigherPriority(childLeft, newParent)) 132 | { 133 | newParent = childLeft; 134 | } 135 | 136 | //Check if the right-child is higher-priority than either the current node or the left child 137 | int childRightIndex = childLeftIndex + 1; 138 | if (childRightIndex <= Count) 139 | { 140 | T childRight = _nodes[childRightIndex]; 141 | if (HasHigherPriority(childRight, newParent)) 142 | { 143 | newParent = childRight; 144 | } 145 | } 146 | 147 | //If either of the children has higher (smaller) priority, swap and continue cascading 148 | if (newParent != node) 149 | { 150 | //Move new parent to its new index. node will be moved once, at the end 151 | //Doing it this way is one less assignment operation than calling Swap() 152 | _nodes[finalQueueIndex] = newParent; 153 | 154 | int temp = newParent.QueueIndex; 155 | newParent.QueueIndex = finalQueueIndex; 156 | finalQueueIndex = temp; 157 | } 158 | else 159 | { 160 | //See note above 161 | node.QueueIndex = finalQueueIndex; 162 | _nodes[finalQueueIndex] = node; 163 | break; 164 | } 165 | } 166 | } 167 | 168 | /// 169 | /// Returns true if 'higher' has higher priority than 'lower', false otherwise. 170 | /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false 171 | /// 172 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 173 | private bool HasHigherPriority(T higher, T lower) 174 | { 175 | return higher.Priority < lower.Priority || 176 | // ReSharper disable once CompareOfFloatsByEqualityOperator 177 | (higher.Priority == lower.Priority && higher.InsertionIndex < lower.InsertionIndex); 178 | } 179 | 180 | /// 181 | /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. 182 | /// If queue is empty, result is undefined 183 | /// O(log n) 184 | /// 185 | public T Dequeue() 186 | { 187 | T returnMe = _nodes[1]; 188 | Remove(returnMe); 189 | return returnMe; 190 | } 191 | 192 | /// 193 | /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain. 194 | /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior 195 | /// O(n) 196 | /// 197 | public void Resize(int maxNodes) 198 | { 199 | var newArray = new T[maxNodes + 1]; 200 | int highestIndexToCopy = Math.Min(maxNodes, Count); 201 | for (var i = 1; i <= highestIndexToCopy; i++) 202 | { 203 | newArray[i] = _nodes[i]; 204 | } 205 | _nodes = newArray; 206 | } 207 | 208 | /// 209 | /// Returns the head of the queue, without removing it (use Dequeue() for that). 210 | /// If the queue is empty, behavior is undefined. 211 | /// O(1) 212 | /// 213 | public T First 214 | { 215 | get 216 | { 217 | return _nodes[1]; 218 | } 219 | } 220 | 221 | /// 222 | /// This method must be called on a node every time its priority changes while it is in the queue. 223 | /// Forgetting to call this method will result in a corrupted queue! 224 | /// Calling this method on a node not in the queue results in undefined behavior 225 | /// O(log n) 226 | /// 227 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 228 | public void UpdatePriority(T node, double priority) 229 | { 230 | node.Priority = priority; 231 | OnNodeUpdated(node); 232 | } 233 | 234 | private void OnNodeUpdated(T node) 235 | { 236 | //Bubble the updated node up or down as appropriate 237 | int parentIndex = node.QueueIndex/2; 238 | T parentNode = _nodes[parentIndex]; 239 | 240 | if (parentIndex > 0 && HasHigherPriority(node, parentNode)) 241 | { 242 | CascadeUp(node); 243 | } 244 | else 245 | { 246 | //Note that CascadeDown will be called if parentNode == node (that is, node is the root) 247 | CascadeDown(node); 248 | } 249 | } 250 | 251 | /// 252 | /// Removes a node from the queue. The node does not need to be the head of the queue. 253 | /// If the node is not in the queue, the result is undefined. If unsure, check Contains() first 254 | /// O(log n) 255 | /// 256 | public void Remove(T node) 257 | { 258 | //If the node is already the last node, we can remove it immediately 259 | if (node.QueueIndex == Count) 260 | { 261 | _nodes[Count] = null; 262 | Count--; 263 | return; 264 | } 265 | 266 | //Swap the node with the last node 267 | T formerLastNode = _nodes[Count]; 268 | Swap(node, formerLastNode); 269 | _nodes[Count] = null; 270 | Count--; 271 | 272 | //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate 273 | OnNodeUpdated(formerLastNode); 274 | } 275 | 276 | public IEnumerator GetEnumerator() 277 | { 278 | for (var i = 1; i <= Count; i++) 279 | yield return _nodes[i]; 280 | } 281 | 282 | IEnumerator IEnumerable.GetEnumerator() 283 | { 284 | return GetEnumerator(); 285 | } 286 | } 287 | } --------------------------------------------------------------------------------