diff --git a/AStarPathFinding.csproj b/AStarPathFinding.csproj new file mode 100644 index 0000000..74af5cf --- /dev/null +++ b/AStarPathFinding.csproj @@ -0,0 +1,97 @@ + + + + + Debug + AnyCPU + {18512878-7865-4B81-B622-1CB19BD4C5D3} + WinExe + AStarPathFinding + AStarPathFinding + v4.6.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + packages\RoyT.AStar.3.0.2\lib\netstandard2.0\Roy-T.AStar.dll + + + + + + + + + + + + + + + + + Form + + + Form1.cs + + + Form + + + Form2.cs + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/AStarPathFinding.sln b/AStarPathFinding.sln new file mode 100644 index 0000000..ff2d2a6 --- /dev/null +++ b/AStarPathFinding.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30711.63 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AStarPathFinding", "AStarPathFinding.csproj", "{18512878-7865-4B81-B622-1CB19BD4C5D3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {18512878-7865-4B81-B622-1CB19BD4C5D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18512878-7865-4B81-B622-1CB19BD4C5D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18512878-7865-4B81-B622-1CB19BD4C5D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18512878-7865-4B81-B622-1CB19BD4C5D3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E92EC7CC-15EF-4066-9836-D08A924EFDB9} + EndGlobalSection +EndGlobal diff --git a/App.config b/App.config new file mode 100644 index 0000000..731f6de --- /dev/null +++ b/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AstarPathFinder.cs b/AstarPathFinder.cs new file mode 100644 index 0000000..abd231c --- /dev/null +++ b/AstarPathFinder.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AStarPathFinding +{ + public class AstarNode//길찾기용 노드클래스 + { + public Point axis;//노드의 좌표 + public int G { get; private set; } = 0;//시작점부터의 비용, 가로세로 = 10, 대각선 = 14 + public int H { get; private set; } = 0;//목적지 까지의 최단거리 + public int F { get; private set; } = 0;//총 비용 + public int R { get; private set; } = 0;//로드 비용 + public int L { get; private set; } = 0;//로드 비용 + public Point ParentDirection; + public AstarNode parent { get; private set; } = null;//부모 노드, 길을 찾고 부모를 따라가면 길이생성됨 + + // AstarNode 매소드에 Point로 x, y 좌표값을 받는다. + // G, H, F의 초기값을 지정한다. + public AstarNode(Point axis) + { + this.axis = axis; + G = 0; + H = 0; + F = 0; + R = 0; + L = 0; + } + + public void SetGCost(int gcost) + { + G = gcost; + F = G + H; + // 총비용 = 시작점에서 부터 골까지의 비용 + 목적지 까지의 예상 최단 거리 비용 + } + + public void SetHCost(int hcost) + { + H = hcost; + F = G + H; + // 총비용 = 시작점에서 부터 골까지의 비용 + 목적지 까지의 예상 최단 거리 비용 + } + + public void SetParent(AstarNode parentNode) + { + parent = parentNode; + // 부모노드 선언 + } + + // 초기화 : 총비용, 골까지의 비용, 목적지 까지 예상 비용을 0으로 초기화 + public void Reset() + { + G = 0; + H = 0; + F = 0; + parent = null; + } + } + + class AstarPathFinder + { + //자식 생성용 주변 인덱스 + //좌 하단부터 반시계방향 + readonly Point[] neerAxis = new Point[8] + { + new Point(-1, -1), new Point(0, -1), + new Point(1, -1), new Point(1, 0), + new Point(1, 1), new Point(0, 1), + new Point(-1, 1), new Point(-1, 0) + }; + + eTileState[,] tableStateData;//노드 상태 배열 + AstarNode[,] tableNodeData;//노드 데이터 배열 + + readonly Point tableSize;//테이블의 크기 + + bool bStepMode;//스텝모드인가? + + //오픈리스트, 클로즈리스트 + //접근하기 쉽게 딕셔너리로 선언 + Dictionary dicOpenList = new Dictionary(); + Dictionary dicCloseList = new Dictionary(); + + AstarNode focusNode;//현재 보고있는 노드 + + public AstarPathFinder(Point tableSize, eTileState[,] tableData) + { + this.tableSize = tableSize; + this.tableStateData = tableData; + + tableNodeData = new AstarNode[this.tableSize.X, this.tableSize.Y]; + for(int i = 0; i < this.tableSize.X; i++) + { + for(int j = 0; j < this.tableSize.Y; j++) + { + tableNodeData[i, j] = new AstarNode(new Point(i, j)); + } + } + } + + //초기화 + public void Initialize(bool stepMode = false) + { + if(stepMode) + bStepMode = true; + else + bStepMode = false; + + dicOpenList.Clear(); + dicCloseList.Clear(); + + for(int i = 0; i < tableSize.X; i++) + { + for(int j = 0; j < tableSize.Y; j++) + { + tableNodeData[i, j].Reset(); + switch(tableStateData[i, j]) + { + case eTileState.Start: + case eTileState.Goal: + case eTileState.Wall: + case eTileState.Road: + case eTileState.RRoad: + case eTileState.LRoad: + + break; + default: + tableStateData[i, j] = eTileState.None; + break; + } + } + } + } + + public void PathFind(Panel pn, Point startPoint, Point goalPoint, bool stepMode = false) + { + if(!bStepMode && stepMode||!stepMode) + { + Initialize(stepMode); + focusNode = tableNodeData[startPoint.X, startPoint.Y]; + focusNode.SetHCost((Math.Abs(focusNode.axis.X - goalPoint.X) + Math.Abs(focusNode.axis.Y - goalPoint.Y)) * 10); + dicCloseList.Add(focusNode.axis, focusNode); + } + + bool bFindPath = false;//길을 찾았으면 true, or false + + while(true) + { + if(focusNode.axis.Equals(goalPoint)) + { + bFindPath = true; + break; + } + + //오픈리스트 생성 + CreateOpenList(goalPoint); + + //오픈리스트가 비었다면 길이 없다 판단 + if (dicOpenList.Count <= 0) + { + bFindPath = false; + bStepMode = false; + break; + } + + //오픈리스트의 첫번째 값을 임시로 저장(f값 비교용) + AstarNode tempPathNode = dicOpenList.Values.ToArray()[0]; + + //f비용이 가장 작은 노드로 바꿔준다 + foreach(var iter in dicOpenList.Values) + { + if(tempPathNode.F > iter.F) + tempPathNode = iter; + } + + //이제 포커싱하고있는 노드는 f값이 가장 작은노드 + focusNode = tempPathNode; + + //포커싱하는 노드는 오픈리스트에서 뺀 후 클로즈리스트로 보낸다 + dicOpenList.Remove(focusNode.axis); + dicCloseList.Add(focusNode.axis, focusNode); + tableStateData[focusNode.axis.X, focusNode.axis.Y] = eTileState.Close; + + if(stepMode) break; + } + + //길을 찾았는가 + if(bFindPath) + { + //만일 찾았다면 포커싱하고있던 노드는 도착점이다. + while(focusNode != null) + { + tableStateData[focusNode.axis.X, focusNode.axis.Y] = eTileState.Path; + focusNode = focusNode.parent; + bStepMode = false; + } + } + + tableStateData[startPoint.X, startPoint.Y] = eTileState.Start; + tableStateData[goalPoint.X, goalPoint.Y] = eTileState.Goal; + + pn.Refresh(); + } + + public Point AddPoint(Point left, Point right, int mulRight = 1) + { + return new Point(left.X + right.X * mulRight, left.Y + right.Y * mulRight); + } + + /// + /// 오픈리스트 생성 + /// + void CreateOpenList(Point goalPoint) + { + for (int i = 0; i < 8; i++) + { + AstarNode openNode; + + //예비 자식이 될 노드들의 좌표 계산 + Point tempChildAxis = AddPoint(focusNode.axis, neerAxis[i]); + + //예비 자식노드의 좌표가 테이블좌표 밖일시 후보에서 거른다 + if (tempChildAxis.X < 0 || tempChildAxis.X >= tableSize.X || + tempChildAxis.Y < 0 || tempChildAxis.Y >= tableSize.Y) + continue; + + //g의값은 가로세로 10, 대각선 14 + int gCost = i % 2 == 0 ? 14 : 10; + + + //만일 해당 노드가 오픈리스트에 있다면 g비용이 더 저렴한 노드를 다음 목적지로 선정 + if (dicOpenList.ContainsKey(tempChildAxis)) + { + AstarNode tempOpenNode = dicOpenList[tempChildAxis]; + + //if(tableStateData[tempChildAxis.X, tempChildAxis.Y] == eTileState.Road) + //{ + // tempOpenNode.SetGCost(gCost + focusNode.R); + // tempOpenNode.SetParent(focusNode); + // tempOpenNode.ParentDirection = neerAxis[i]; + // continue; + //} + if (tempOpenNode.G > gCost + focusNode.G) + { + tempOpenNode.SetGCost(gCost + focusNode.G); + tempOpenNode.SetParent(focusNode); + tempOpenNode.ParentDirection = neerAxis[i]; + } + continue; + } + + //만일 예비 자식의 위치가 벽일시 무시한다 + if (tableStateData[tempChildAxis.X, tempChildAxis.Y] == eTileState.Wall) + continue; + + ////만일 예비 자식의 위치가 벽일시 무시한다 + //if (tableStateData[tempChildAxis.X, tempChildAxis.Y] == eTileState.Wall) + // continue; + + //만일 클로즈 리스트에 있어도 무시한다. + if (dicCloseList.ContainsKey(tempChildAxis)) + continue; + + if (tableStateData[tempChildAxis.X, tempChildAxis.Y] == eTileState.Road) + { + openNode = tableNodeData[tempChildAxis.X, tempChildAxis.Y]; + openNode.SetParent(focusNode); + openNode.SetGCost(gCost + openNode.parent.R); + openNode.SetHCost(openNode.parent.R + 1000); + openNode.ParentDirection = neerAxis[i]; + + dicOpenList.Add(tempChildAxis, openNode); + tableStateData[tempChildAxis.X, tempChildAxis.Y] = eTileState.Open; + + + continue; + } + + // !!!길일 때 오픈리스트에 넣어준다. + if (tableStateData[tempChildAxis.X, tempChildAxis.Y] == eTileState.RRoad & Form1.Direction == true) + { + openNode = tableNodeData[tempChildAxis.X, tempChildAxis.Y]; + openNode.SetParent(focusNode); + openNode.SetGCost(gCost + openNode.parent.R); + openNode.SetHCost(openNode.parent.R - 1000); + openNode.ParentDirection = neerAxis[i]; + + dicOpenList.Add(tempChildAxis, openNode); + tableStateData[tempChildAxis.X, tempChildAxis.Y] = eTileState.Open; + + continue; + } + + // !!!길일 때 오픈리스트에 넣어준다. + if (tableStateData[tempChildAxis.X, tempChildAxis.Y] == eTileState.RRoad & Form1.Direction == false) + { + openNode = tableNodeData[tempChildAxis.X, tempChildAxis.Y]; + openNode.SetParent(focusNode); + openNode.SetGCost(gCost + openNode.parent.R); + openNode.SetHCost(openNode.parent.R + 1000); + openNode.ParentDirection = neerAxis[i]; + + dicOpenList.Add(tempChildAxis, openNode); + tableStateData[tempChildAxis.X, tempChildAxis.Y] = eTileState.Open; + + continue; + } + + if (tableStateData[tempChildAxis.X, tempChildAxis.Y] == eTileState.LRoad & Form1.Direction == true) + { + openNode = tableNodeData[tempChildAxis.X, tempChildAxis.Y]; + openNode.SetParent(focusNode); + openNode.SetGCost(gCost + openNode.parent.L); + openNode.SetHCost(openNode.parent.L + 1000); // 휴리스틱 값 수정해서 길 따라갈 수 있게 점수를 낮춰줌 수정 + openNode.ParentDirection = neerAxis[i]; + + dicOpenList.Add(tempChildAxis, openNode); + tableStateData[tempChildAxis.X, tempChildAxis.Y] = eTileState.Open; + + continue; + } + + if (tableStateData[tempChildAxis.X, tempChildAxis.Y] == eTileState.LRoad & Form1.Direction == false) + { + openNode = tableNodeData[tempChildAxis.X, tempChildAxis.Y]; + openNode.SetParent(focusNode); + openNode.SetGCost(gCost + openNode.parent.L); + openNode.SetHCost(openNode.parent.L - 1000); // 휴리스틱 값 수정해서 길 따라갈 수 있게 점수를 낮춰줌 수정 + openNode.ParentDirection = neerAxis[i]; + + dicOpenList.Add(tempChildAxis, openNode); + tableStateData[tempChildAxis.X, tempChildAxis.Y] = eTileState.Open; + + continue; + } + + //대각선일시 자신의 주변을 체크 후 벽이있다면 무시한다(벽끼고 대각선이동 불가) + if (gCost == 14) + { + Point tempNextIndex = AddPoint(focusNode.axis, neerAxis[i + 1]); + Point tempPrevIndex = AddPoint(focusNode.axis, neerAxis[i - 1 < 0 ? 7 : i - 1]); + + if (tableStateData[tempNextIndex.X, tempNextIndex.Y] == eTileState.Wall || + tableStateData[tempPrevIndex.X, tempPrevIndex.Y] == eTileState.Wall) + continue; + } + + //심사에 통과한 노드들은 오픈리스트에 넣어준다. + openNode = tableNodeData[tempChildAxis.X, tempChildAxis.Y]; + openNode.SetParent(focusNode); + openNode.SetGCost(gCost + openNode.parent.G); + openNode.SetHCost((Math.Abs(tempChildAxis.X - goalPoint.X) + Math.Abs(tempChildAxis.Y - goalPoint.Y)) * 10); + openNode.ParentDirection = neerAxis[i]; + + dicOpenList.Add(tempChildAxis, openNode); + tableStateData[tempChildAxis.X, tempChildAxis.Y] = eTileState.Open; + continue; + } + } + + public AstarNode GetAstarNode(Point point) + { + return tableNodeData[point.X, point.Y]; + } + } +} diff --git a/Form1.Designer.cs b/Form1.Designer.cs new file mode 100644 index 0000000..bf9875e --- /dev/null +++ b/Form1.Designer.cs @@ -0,0 +1,222 @@ +namespace AStarPathFinding +{ + partial class Form1 + { + /// + /// 필수 디자이너 변수입니다. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 사용 중인 모든 리소스를 정리합니다. + /// + /// 관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form 디자이너에서 생성한 코드 + + /// + /// 디자이너 지원에 필요한 메서드입니다. + /// 이 메서드의 내용을 코드 편집기로 수정하지 마세요. + /// + private void InitializeComponent() + { + this.pn_Table = new System.Windows.Forms.Panel(); + this.btn_SetStartPoint = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.btn_SetGoalPoint = new System.Windows.Forms.Button(); + this.btn_SetDefault = new System.Windows.Forms.Button(); + this.btn_SetWallPoint = new System.Windows.Forms.Button(); + this.label2 = new System.Windows.Forms.Label(); + this.btn_PathFind = new System.Windows.Forms.Button(); + this.colorDialog1 = new System.Windows.Forms.ColorDialog(); + this.btn_Step = new System.Windows.Forms.Button(); + this.btn_SetRoadPoint = new System.Windows.Forms.Button(); + this.btn_SetLeftRoadPoint = new System.Windows.Forms.Button(); + this.btn_SetRightRoadPoint = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // pn_Table + // + this.pn_Table.BackColor = System.Drawing.SystemColors.Control; + this.pn_Table.Location = new System.Drawing.Point(12, 12); + this.pn_Table.Name = "pn_Table"; + this.pn_Table.Size = new System.Drawing.Size(800, 800); + this.pn_Table.TabIndex = 0; + this.pn_Table.Paint += new System.Windows.Forms.PaintEventHandler(this.pn_Table_Paint); + this.pn_Table.MouseDown += new System.Windows.Forms.MouseEventHandler(this.pn_Table_MouseDown); + this.pn_Table.MouseMove += new System.Windows.Forms.MouseEventHandler(this.pn_Table_MouseMove); + // + // btn_SetStartPoint + // + this.btn_SetStartPoint.Font = new System.Drawing.Font("Consolas", 10F); + this.btn_SetStartPoint.Location = new System.Drawing.Point(818, 47); + this.btn_SetStartPoint.Name = "btn_SetStartPoint"; + this.btn_SetStartPoint.Size = new System.Drawing.Size(125, 30); + this.btn_SetStartPoint.TabIndex = 1; + this.btn_SetStartPoint.Text = "StartPoint"; + this.btn_SetStartPoint.UseVisualStyleBackColor = true; + this.btn_SetStartPoint.Click += new System.EventHandler(this.btn_SetStartPoint_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.BackColor = System.Drawing.SystemColors.GrayText; + this.label1.Font = new System.Drawing.Font("Consolas", 18F, System.Drawing.FontStyle.Bold); + this.label1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(255)))), ((int)(((byte)(255))))); + this.label1.Location = new System.Drawing.Point(818, 12); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(129, 28); + this.label1.TabIndex = 5; + this.label1.Text = "SET_POINT"; + // + // btn_SetGoalPoint + // + this.btn_SetGoalPoint.Font = new System.Drawing.Font("Consolas", 10F); + this.btn_SetGoalPoint.Location = new System.Drawing.Point(818, 83); + this.btn_SetGoalPoint.Name = "btn_SetGoalPoint"; + this.btn_SetGoalPoint.Size = new System.Drawing.Size(125, 30); + this.btn_SetGoalPoint.TabIndex = 6; + this.btn_SetGoalPoint.Text = "GoalPoint"; + this.btn_SetGoalPoint.UseVisualStyleBackColor = true; + this.btn_SetGoalPoint.Click += new System.EventHandler(this.btn_SetGoalPoint_Click); + // + // btn_SetDefault + // + this.btn_SetDefault.Font = new System.Drawing.Font("Consolas", 10F); + this.btn_SetDefault.Location = new System.Drawing.Point(818, 191); + this.btn_SetDefault.Name = "btn_SetDefault"; + this.btn_SetDefault.Size = new System.Drawing.Size(125, 30); + this.btn_SetDefault.TabIndex = 7; + this.btn_SetDefault.Text = "Default"; + this.btn_SetDefault.UseVisualStyleBackColor = true; + this.btn_SetDefault.Click += new System.EventHandler(this.btn_SetDefault_Click); + // + // btn_SetWallPoint + // + this.btn_SetWallPoint.Font = new System.Drawing.Font("Consolas", 10F); + this.btn_SetWallPoint.Location = new System.Drawing.Point(818, 119); + this.btn_SetWallPoint.Name = "btn_SetWallPoint"; + this.btn_SetWallPoint.Size = new System.Drawing.Size(125, 30); + this.btn_SetWallPoint.TabIndex = 8; + this.btn_SetWallPoint.Text = "WallPoint"; + this.btn_SetWallPoint.UseVisualStyleBackColor = true; + this.btn_SetWallPoint.Click += new System.EventHandler(this.btn_SetWallPoint_Click); + // + // label2 + // + this.label2.BackColor = System.Drawing.SystemColors.GradientInactiveCaption; + this.label2.Location = new System.Drawing.Point(816, 230); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(129, 2); + this.label2.TabIndex = 9; + this.label2.Text = " "; + // + // btn_PathFind + // + this.btn_PathFind.Font = new System.Drawing.Font("Consolas", 10F); + this.btn_PathFind.Location = new System.Drawing.Point(818, 245); + this.btn_PathFind.Name = "btn_PathFind"; + this.btn_PathFind.Size = new System.Drawing.Size(125, 30); + this.btn_PathFind.TabIndex = 10; + this.btn_PathFind.Text = "PathFind"; + this.btn_PathFind.UseVisualStyleBackColor = true; + this.btn_PathFind.Click += new System.EventHandler(this.btn_PathFind_Click); + // + // btn_Step + // + this.btn_Step.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.btn_Step.Font = new System.Drawing.Font("Consolas", 10F); + this.btn_Step.Location = new System.Drawing.Point(818, 782); + this.btn_Step.Name = "btn_Step"; + this.btn_Step.Size = new System.Drawing.Size(125, 30); + this.btn_Step.TabIndex = 11; + this.btn_Step.Text = "Step"; + this.btn_Step.UseVisualStyleBackColor = true; + this.btn_Step.Click += new System.EventHandler(this.btn_Step_Click); + // + // btn_SetRoadPoint + // + this.btn_SetRoadPoint.Font = new System.Drawing.Font("Consolas", 10F); + this.btn_SetRoadPoint.Location = new System.Drawing.Point(818, 155); + this.btn_SetRoadPoint.Name = "btn_SetRoadPoint"; + this.btn_SetRoadPoint.Size = new System.Drawing.Size(125, 30); + this.btn_SetRoadPoint.TabIndex = 12; + this.btn_SetRoadPoint.Text = "RoadPoint"; + this.btn_SetRoadPoint.UseVisualStyleBackColor = true; + this.btn_SetRoadPoint.Click += new System.EventHandler(this.btn_SetRoadPoint_Click); + // + // btn_SetLeftRoadPoint + // + this.btn_SetLeftRoadPoint.Font = new System.Drawing.Font("Consolas", 10F); + this.btn_SetLeftRoadPoint.Location = new System.Drawing.Point(820, 313); + this.btn_SetLeftRoadPoint.Name = "btn_SetLeftRoadPoint"; + this.btn_SetLeftRoadPoint.Size = new System.Drawing.Size(125, 30); + this.btn_SetLeftRoadPoint.TabIndex = 13; + this.btn_SetLeftRoadPoint.Text = "LeftRoadPoint"; + this.btn_SetLeftRoadPoint.UseVisualStyleBackColor = true; + this.btn_SetLeftRoadPoint.Click += new System.EventHandler(this.btn_SetLeftRoadPoint_Click); + // + // btn_SetRightRoadPoint + // + this.btn_SetRightRoadPoint.Font = new System.Drawing.Font("Consolas", 10F); + this.btn_SetRightRoadPoint.Location = new System.Drawing.Point(820, 359); + this.btn_SetRightRoadPoint.Name = "btn_SetRightRoadPoint"; + this.btn_SetRightRoadPoint.Size = new System.Drawing.Size(125, 30); + this.btn_SetRightRoadPoint.TabIndex = 14; + this.btn_SetRightRoadPoint.Text = "RightRoadPoint"; + this.btn_SetRightRoadPoint.UseVisualStyleBackColor = true; + this.btn_SetRightRoadPoint.Click += new System.EventHandler(this.btn_SetRightRoadPoint_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.SystemColors.GrayText; + this.ClientSize = new System.Drawing.Size(949, 826); + this.Controls.Add(this.btn_SetRightRoadPoint); + this.Controls.Add(this.btn_SetLeftRoadPoint); + this.Controls.Add(this.btn_SetRoadPoint); + this.Controls.Add(this.btn_Step); + this.Controls.Add(this.btn_PathFind); + this.Controls.Add(this.label2); + this.Controls.Add(this.btn_SetWallPoint); + this.Controls.Add(this.btn_SetDefault); + this.Controls.Add(this.btn_SetGoalPoint); + this.Controls.Add(this.label1); + this.Controls.Add(this.btn_SetStartPoint); + this.Controls.Add(this.pn_Table); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.HelpButton = true; + this.Name = "Form1"; + this.Text = "AStarPathFinder"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Panel pn_Table; + private System.Windows.Forms.Button btn_SetStartPoint; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Button btn_SetGoalPoint; + private System.Windows.Forms.Button btn_SetDefault; + private System.Windows.Forms.Button btn_SetWallPoint; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Button btn_PathFind; + private System.Windows.Forms.ColorDialog colorDialog1; + private System.Windows.Forms.Button btn_Step; + private System.Windows.Forms.Button btn_SetRoadPoint; + private System.Windows.Forms.Button btn_SetLeftRoadPoint; + private System.Windows.Forms.Button btn_SetRightRoadPoint; + } +} + diff --git a/Form1.cs b/Form1.cs new file mode 100644 index 0000000..6fe7463 --- /dev/null +++ b/Form1.cs @@ -0,0 +1,399 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AStarPathFinding +{ + //타일 속성 + //기본, 출발점, 목적지, 벽, 열린리스트, 닫힌리스트, 길 + public enum eTileState + { None, Start, Goal, Wall, Road, RRoad, LRoad, Open, Close, Path } + + public partial class Form1 : Form + { + public static bool Direction { get; set; } // 방향 설정!!! + + //구조체===================== + struct stCurrentAction//사용자의 세팅(타일배치)종류 확인용 구조체 + { + public object sender;//호출한 버튼 + public eTileState actionType;//세팅 타입 + public stCurrentAction(object sender, eTileState type) + { + this.sender = sender; + this.actionType = type; + } + } + //======================구조체 + + //상수======================= + const int TABLE_SIZE = 15;//테이블 사이즈 + readonly Point defaultStartPoint = new Point(5, 6);//디폴트 시작점 + readonly Point defaultGoalPoint = new Point(9, 6);//디폴트 목적지 + readonly Point[] defaultWallPoint = new Point[3]//디폴트 벽 위치 + { new Point(7, 5), new Point(7, 6), new Point(7, 7) }; + //readonly Point[] defaultRoadPoint = new Point[] { }; + + + + readonly Color defaultButtonColor;//디폴트 버튼 색 + //========================상수 + + //렌더링====================== + readonly int tableNodeSize;//노드사이즈 + readonly Pen pen = new Pen(Color.DimGray, 1);//선 그리기용 + readonly Font font = new Font("바탕", 10, FontStyle.Regular);//폰트 + //=======================렌더링 + + //길찾기======================= + eTileState[,] tableData;//테이블 데이터 + AstarPathFinder pathFinder;//길찾기 클래스 + Point startPoint;//시작점 + Point goalPoint;//목표점 + + //=======================길찾기 + + //세팅========================= + stCurrentAction? curAction = null;//현재 사용자가 선택한 배치타입 + //=========================세팅 + + + protected override CreateParams CreateParams + { get { var cp = base.CreateParams; cp.ExStyle |= 0x02000000; return cp; } } + + public Form1() + { + InitializeComponent(); + + defaultButtonColor = Color.LightGray; + + tableNodeSize = pn_Table.Size.Width / TABLE_SIZE; + + startPoint = defaultStartPoint; + goalPoint = defaultGoalPoint; + + tableData = new eTileState[TABLE_SIZE, TABLE_SIZE]; + SetDefault(); + + pathFinder = new AstarPathFinder(new Point(TABLE_SIZE, TABLE_SIZE), tableData); + } + + //테이블 초기화 + void SetDefault() + { + for(int i = 0; i < TABLE_SIZE; i++) + { + for(int j = 0; j < TABLE_SIZE; j++) + { + tableData[i, j] = eTileState.None; + } + } + + tableData[defaultStartPoint.X, defaultStartPoint.Y] = eTileState.Start; + tableData[defaultGoalPoint.X, defaultGoalPoint.Y] = eTileState.Goal; + + foreach(Point iter in defaultWallPoint) + tableData[iter.X, iter.Y] = eTileState.Wall; + + startPoint = defaultStartPoint; + goalPoint = defaultGoalPoint; + } + + //노드 그리기 + void DrawNode(PaintEventArgs e, Color color, AstarNode node) + { + Rectangle rect = new Rectangle(node.axis.X * tableNodeSize, node.axis.Y * tableNodeSize, tableNodeSize, tableNodeSize); + + e.Graphics.FillRectangle(new SolidBrush(color), rect); + e.Graphics.DrawRectangle(pen, rect); + + e.Graphics.DrawString(node.F.ToString(), font, Brushes.Black, rect.X + 1, rect.Y + 4); + e.Graphics.DrawString(node.G.ToString(), font, Brushes.Black, rect.X + 1, rect.Y + tableNodeSize * .7f); + e.Graphics.DrawString(node.H.ToString(), font, Brushes.Black, rect.X + tableNodeSize - 1, rect.Y + tableNodeSize * .7f, new StringFormat(StringFormatFlags.DirectionRightToLeft)); + + if(node.parent != null) + { + Point center = new Point(rect.X + tableNodeSize / 2, rect.Y + tableNodeSize / 2); + RectangleF centerRect = new RectangleF(new PointF(center.X - 2.5f, center.Y - 2.5f), new SizeF(5, 5)); + e.Graphics.DrawLine(pen, center, pathFinder.AddPoint(center, node.ParentDirection, -8)); + e.Graphics.FillRectangle(new SolidBrush(Color.DimGray), centerRect); + } + } + + /// + /// 버튼 선택시 들어옴, 현재 선택한 배치타입 설정 + /// + /// null 일경우 초기화 + /// None 일경우 취소 + void SetCurrentAction(object sender, eTileState type) + { + if(type != eTileState.Open)//오픈은 스텝모드,,.. + pathFinder.Initialize(); + + if(curAction != null) + { + (curAction.Value.sender as Button).BackColor = defaultButtonColor; + + if(type == eTileState.None || type == curAction.Value.actionType) + { + curAction = null; + return; + } + } + + if(sender != null) + { + curAction = new stCurrentAction(sender, type); + + if(type != eTileState.None) + (sender as Button).BackColor = Color.LightSeaGreen; + } + } + + private void pn_Table_Paint(object sender, PaintEventArgs e) + { + for(int i = 0; i < TABLE_SIZE; i++) + { + for(int j = 0; j < TABLE_SIZE; j++) + { + Color color = new Color(); + + switch(tableData[i, j]) + { + case eTileState.None: + break; + case eTileState.Start: + color = Color.IndianRed; + break; + case eTileState.Goal: + color = Color.LightGreen; + break; + case eTileState.Wall: + color = Color.CornflowerBlue; + break; + case eTileState.Open: + color = Color.LemonChiffon; + break; + case eTileState.Close: + color = Color.BurlyWood; + break; + case eTileState.Path: + color = Color.Plum; + break; + case eTileState.Road: + color = Color.Yellow; + break; + case eTileState.LRoad: + color = Color.DarkRed; + break; + case eTileState.RRoad: + color = Color.DarkBlue; + break; + } + DrawNode(e, color, pathFinder.GetAstarNode(new Point(i, j))); + } + } + } + + private void btn_SetStartPoint_Click(object sender, EventArgs e) + { + SetCurrentAction(sender, eTileState.Start); + } + + private void btn_SetGoalPoint_Click(object sender, EventArgs e) + { + SetCurrentAction(sender, eTileState.Goal); + } + + private void btn_SetWallPoint_Click(object sender, EventArgs e) + { + SetCurrentAction(sender, eTileState.Wall); + } + + private void btn_SetDefault_Click(object sender, EventArgs e) + { + SetCurrentAction(sender, eTileState.None); + + SetDefault(); + pn_Table.Refresh(); + } + + private void btn_SetRoadPoint_Click(object sender, EventArgs e) + { + SetCurrentAction(sender, eTileState.Road); + } + + private void btn_SetLeftRoadPoint_Click(object sender, EventArgs e) + { + SetCurrentAction(sender, eTileState.LRoad); + } + + private void btn_SetRightRoadPoint_Click(object sender, EventArgs e) + { + SetCurrentAction(sender, eTileState.RRoad); + } + + private void pn_Table_MouseDown(object sender, MouseEventArgs e) + { + int x = e.X / tableNodeSize; + int y = e.Y / tableNodeSize; + + if(tableData[x, y] == eTileState.Start || tableData[x, y] == eTileState.Goal) + { + return; + } + + if(curAction != null) + { + switch(curAction.Value.actionType) + { + case eTileState.Start: + if(tableData[x, y] == eTileState.Wall) break; + + tableData[startPoint.X, startPoint.Y] = eTileState.None; + + startPoint = new Point(x, y); + SetCurrentAction(null, eTileState.None); + tableData[startPoint.X, startPoint.Y] = eTileState.Start; + + break; + case eTileState.Goal: + if(tableData[x, y] == eTileState.Wall) break; + + tableData[goalPoint.X, goalPoint.Y] = eTileState.None; + + goalPoint = new Point(x, y); + SetCurrentAction(null, eTileState.None); + tableData[goalPoint.X, goalPoint.Y] = eTileState.Goal; + + break; + case eTileState.Wall: + if(tableData[x, y] == eTileState.Start || tableData[x, y] == eTileState.Goal) + break; + + if(tableData[x, y] == eTileState.Wall) + { + tableData[x, y] = eTileState.None; + break; + } + + tableData[x, y] = eTileState.Wall; + break; + case eTileState.Road: + if (tableData[x, y] == eTileState.Start || tableData[x, y] == eTileState.Goal) + break; + + if (tableData[x, y] == eTileState.Road) + { + tableData[x, y] = eTileState.None; + break; + } + + tableData[x, y] = eTileState.Road; + break; + case eTileState.LRoad: + if (tableData[x, y] == eTileState.Start || tableData[x, y] == eTileState.Goal) + break; + + if (tableData[x, y] == eTileState.LRoad) + { + tableData[x, y] = eTileState.None; + break; + } + + tableData[x, y] = eTileState.LRoad; + break; + case eTileState.RRoad: + if (tableData[x, y] == eTileState.Start || tableData[x, y] == eTileState.Goal) + break; + + if (tableData[x, y] == eTileState.RRoad) + { + tableData[x, y] = eTileState.None; + break; + } + + tableData[x, y] = eTileState.RRoad; + break; + default: + break; + } + } + (sender as Panel).Refresh(); + } + + private void pn_Table_MouseMove(object sender, MouseEventArgs e) + { + //if(e.Button == MouseButtons.Left) + //{ + // int x = e.X / tableNodeSize; + // int y = e.Y / tableNodeSize; + + // if (this.tableData[x, y] == eTileState.Start || this.tableData[x, y] == eTileState.Goal) + // { + // return; + // } + + // if (curAction != null) + // { + // switch (curAction.Value.actionType) + // { + // case eTileState.Road: + // if (tableData[x, y] == eTileState.Start || tableData[x, y] == eTileState.Goal) + // break; + + // if (tableData[x, y] == eTileState.Road) + // { + // tableData[x, y] = eTileState.None; + // break; + // } + + // tableData[x, y] = eTileState.Road; + // break; + // default: + // break; + // } + // } + // (sender as Panel).Refresh(); + //} + } + + public bool clickState { get; set; } + + private void btn_PathFind_Click(object sender, EventArgs e) + { + //SetCurrentAction(null, eTileState.None); + //pathFinder.PathFind(pn_Table, startPoint, goalPoint); + + { + if (clickState) + { + SetCurrentAction(null, eTileState.None); + pathFinder.PathFind(pn_Table, startPoint, goalPoint); + clickState = false; + Direction = false; + } + else + { + SetCurrentAction(null, eTileState.None); + pathFinder.PathFind(pn_Table, goalPoint, startPoint); + clickState = true; + Direction = true; + } + } + } + + private void btn_Step_Click(object sender, EventArgs e) + { + SetCurrentAction(null, eTileState.Open); + + pathFinder.PathFind(pn_Table, startPoint, goalPoint, true); + } + } +} \ No newline at end of file diff --git a/Form1.resx b/Form1.resx new file mode 100644 index 0000000..4b31373 --- /dev/null +++ b/Form1.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + True + + \ No newline at end of file diff --git a/Form2.Designer.cs b/Form2.Designer.cs new file mode 100644 index 0000000..383a721 --- /dev/null +++ b/Form2.Designer.cs @@ -0,0 +1,40 @@ + +namespace AStarPathFinding +{ + partial class Form2 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 450); + this.Text = "Form2"; + } + + #endregion + } +} \ No newline at end of file diff --git a/Form2.cs b/Form2.cs new file mode 100644 index 0000000..076bbb1 --- /dev/null +++ b/Form2.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AStarPathFinding +{ + public partial class Form2 : Form + { + public Form2() + { + InitializeComponent(); + } + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..2cd3f06 --- /dev/null +++ b/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace AStarPathFinding +{ + static class Program + { + /// + /// 해당 응용 프로그램의 주 진입점입니다. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..968562b --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해 +// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면 +// 이러한 특성 값을 변경하세요. +[assembly: AssemblyTitle("AStarPathFinding")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AStarPathFinding")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에 +// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면 +// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요. +[assembly: ComVisible(false)] + +// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다. +[assembly: Guid("18512878-7865-4b81-b622-1cb19bd4c5d3")] + +// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다. +// +// 주 버전 +// 부 버전 +// 빌드 번호 +// 수정 버전 +// +// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호가 자동으로 +// 지정되도록 할 수 있습니다. +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs new file mode 100644 index 0000000..15f82ea --- /dev/null +++ b/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// 이 코드는 도구를 사용하여 생성되었습니다. +// 런타임 버전:4.0.30319.42000 +// +// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면 +// 이러한 변경 내용이 손실됩니다. +// +//------------------------------------------------------------------------------ + +namespace AStarPathFinding.Properties +{ + + + /// + /// 지역화된 문자열 등을 찾기 위한 강력한 형식의 리소스 클래스입니다. + /// + // 이 클래스는 ResGen 또는 Visual Studio와 같은 도구를 통해 StronglyTypedResourceBuilder + // 클래스에서 자동으로 생성되었습니다. + // 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여 + // ResGen을 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// 이 클래스에서 사용하는 캐시된 ResourceManager 인스턴스를 반환합니다. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AStarPathFinding.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 이 강력한 형식의 리소스 클래스를 사용하여 모든 리소스 조회에 대해 현재 스레드의 CurrentUICulture 속성을 + /// 재정의합니다. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Properties/Resources.resx b/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs new file mode 100644 index 0000000..60452f8 --- /dev/null +++ b/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace AStarPathFinding.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Properties/Settings.settings b/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages.config b/packages.config new file mode 100644 index 0000000..96eea62 --- /dev/null +++ b/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file