├── 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 | }
--------------------------------------------------------------------------------