diff --git a/PuzzlePlayer/Maze.cs b/PuzzlePlayer/Maze.cs index de9e46ff48a95827b7cf1618c47c4d6b3247213f..0f44895bab1f926ccc5c84857a27507e469aa218 100644 --- a/PuzzlePlayer/Maze.cs +++ b/PuzzlePlayer/Maze.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; +using System.Threading; +using System.Windows.Forms; namespace PuzzlePlayer_Namespace @@ -35,25 +37,14 @@ namespace PuzzlePlayer_Namespace int[,] mazeState; int[,] visitedCells; - public Maze(int size = 6) + Point playerPos; + List<Point> shortestPath; + + public Maze(int size = 20) { + //drawFactor = 1; // init all 2D array's Reset(size); - - /* - // example thingy (DELETE LATER) - mazeState[0, 0] = 5; - mazeState[1, 0] = 3; - mazeState[2, 0] = 11; - mazeState[0, 1] = 11; - mazeState[1, 1] = 8; - mazeState[2, 1] = 6; - mazeState[0, 2] = 12; - mazeState[1, 2] = 4; - mazeState[2, 2] = 5; - - boardState[0, 2] = 1; - */ } private void Reset(int size) @@ -77,16 +68,16 @@ namespace PuzzlePlayer_Namespace if (cell.left) result += 8; - if (result == 0) - return emptySpace; //if there are no walls then the space is empty + //if (result == 0) + //return emptySpace; //if there are no walls then the space is empty return result; } private (bool,bool,bool,bool) getWallsFromNumber(int number) { - if(number == emptySpace) - return (true,true,true,true); //if the place is empty then it is completly walled in, + //if(number == emptySpace) + // return (true,true,true,true); //if the place is empty then it is completly walled in, //so that the walls can be removed when generating the maze // bitwise and opperations to check each bit @@ -101,12 +92,18 @@ namespace PuzzlePlayer_Namespace public override void Draw(Graphics gr, Rectangle r) { // clear screen - gr.FillRectangle(Brushes.DarkGreen, r); + gr.FillRectangle(Brushes.Wheat, r); Size tilesize = new Size(r.Width / boardState.GetLength(0), r.Height / boardState.GetLength(1)); Pen wall = new Pen(Color.Black, tilesize.Width / 5); - for(int i = 0; i < boardState.GetLength(0); i++) + //debug colors + //Pen wall1 = new Pen(Color.Black, tilesize.Width / 5); + //Pen wall2 = new Pen(Color.Blue, tilesize.Width / 5); + //Pen wall3 = new Pen(Color.Green, tilesize.Width / 5); + //Pen wall4 = new Pen(Color.Purple, tilesize.Width / 5); + + for (int i = 0; i < boardState.GetLength(0); i++) for(int j = 0; j < boardState.GetLength(1); j++) { Rectangle currentRect = @@ -118,7 +115,6 @@ namespace PuzzlePlayer_Namespace // draw board outline gr.DrawRectangle(Pens.LightGray,currentRect); - // drawing walls (bool top,bool right,bool bottom,bool left) = getWallsFromNumber(mazeState[i,j]); @@ -131,11 +127,16 @@ namespace PuzzlePlayer_Namespace gr.DrawLine(wall, currentRect.Left, currentRect.Bottom, currentRect.Right, currentRect.Bottom); if (left) gr.DrawLine(wall, currentRect.Left, currentRect.Bottom, currentRect.Left, currentRect.Top); + } - + // draw an indication of where the start and end from the maze are - gr.DrawString("START", SettingForm.mainFont, Brushes.Red, r.Left, r.Top + tilesize.Height/2); - gr.DrawString("END", SettingForm.mainFont, Brushes.Red, r.Right - tilesize.Width / 4, r.Bottom - tilesize.Height / 2); + + gr.FillRectangle(Brushes.Blue, r.X, r.Y, tilesize.Width, tilesize.Height); + gr.FillRectangle(Brushes.Red, r.X + tilesize.Width*(boardState.GetLength(0)-1), r.Y + tilesize.Height * (boardState.GetLength(1)-1), tilesize.Width, tilesize.Height); + + //gr.DrawString("START", SettingForm.mainFont, Brushes.Red, r.Left, r.Top + tilesize.Height/2); + //gr.DrawString("END", SettingForm.mainFont, Brushes.Red, r.Right - tilesize.Width / 4, r.Bottom - tilesize.Height / 2); } // this methode generates a Maze starting with an empty board @@ -149,11 +150,15 @@ namespace PuzzlePlayer_Namespace Random rnd = new Random(); int x = rnd.Next(mazeState.GetLength(0)); int y = rnd.Next(mazeState.GetLength(0)); - RND_Recursive_DepthFirstSearchMaze(x,y); + + if (Recursive_DepthFirstSearchMazeGenerator(x, y)) + Debug.WriteLine("Maze succesfully generated"); + else + Debug.WriteLine("nope"); } - private bool RND_Recursive_DepthFirstSearchMaze(int x, int y) + private bool Recursive_DepthFirstSearchMazeGenerator(int x, int y) { visitedCells[x, y] = 1; // mark current cell as visited (this is needed for the first cell) @@ -167,16 +172,32 @@ namespace PuzzlePlayer_Namespace foreach ((int nx,int ny) neighbour in validNeigbours) { + if (visitedCells[neighbour.nx, neighbour.ny] == 1) + continue; visitedCells[neighbour.nx,neighbour.ny] = 1; // mark as visited and update all the walls that are effected UpdateWalls(x,y, neighbour.nx,neighbour.ny); - if(RND_Recursive_DepthFirstSearchMaze(neighbour.nx,neighbour.ny)) //recursion + if(BoardComplete()) + return true; + + if(Recursive_DepthFirstSearchMazeGenerator(neighbour.nx,neighbour.ny)) //recursion return true; // if true then the maze is finished and this cell should return true too } return false; // backtrack if all the neighbours are already visited } + private bool BoardComplete() + { + for(int i = 0;i<visitedCells.GetLength(0);i++) + for(int j = 0;j<visitedCells.GetLength(1);j++) + { + if (visitedCells[i, j] == emptySpace) + return false; + } + return true; + } + // update the walls between two cells private void UpdateWalls(int x, int y, int x2, int y2) { @@ -248,21 +269,138 @@ namespace PuzzlePlayer_Namespace // don't have much in common in regards to solving. So most of the code in the abstract Board class is to no use for us here public override SOLUTIONS Solve(bool CheckOnly) { - List<Move> shortestPath = new List<Move>(); - + visitedCells = GetClearBoard(mazeState.GetLength(0)); + boardState = GetClearBoard(mazeState.GetLength(0)); + shortestPath = new List<Point>(); + + shortestPath.Add(new Point(0,0)); // starting point + bool foundSolution = Recursive_DepthFirstSearchMazeSolve(0, 0); + if(CheckOnly && foundSolution) + return SOLUTIONS.UNIQUE; + else if(!CheckOnly && foundSolution) + { + foreach(Point p in shortestPath) + { + boardState[p.X, p.Y] = 1; + } + } return SOLUTIONS.NONE; } + // we solve the maze in almost the same way as we generated the maze + private bool Recursive_DepthFirstSearchMazeSolve(int x, int y) + { + visitedCells[x, y] = 1; // mark current cell as visited (this is needed for the first cell) + + // get a list of all the unvisited neigbour cells + List<(int, int)> validNeigbours = getNeigbours(x, y, emptySpace); + if (validNeigbours.Count == 0) + { + shortestPath.RemoveAt(shortestPath.Count-1); // remove path if we backtrack + return false; // backtrack + } + + validNeigbours.Shuffle(); //shuffle the neigbours around for random results everytime + //(Shuffle methode from the extentions class in the Board.cs file + + foreach ((int nx, int ny) neighbour in validNeigbours) + { + // if the neighbour is already visited or it is a invalid neighbour then continue to the next + if (visitedCells[neighbour.nx, neighbour.ny] == 1 || !isValidNeighbour(x,y,neighbour.nx,neighbour.ny)) + continue; + + visitedCells[neighbour.nx, neighbour.ny] = 1; // mark as visited + shortestPath.Add(new Point(neighbour.nx, neighbour.ny)); // mark as correct path + + // if the bottom right space is reached then we found a solution + if (neighbour.nx == (mazeState.GetLength(0)-1) && neighbour.ny == (mazeState.GetLength(1)-1)) + return true; + + if (Recursive_DepthFirstSearchMazeSolve(neighbour.nx, neighbour.ny)) //recursion + return true; // if true then the maze is finished and this cell should return true too + } + + shortestPath.RemoveAt(shortestPath.Count - 1); // remove path if we backtrack + return false; // backtrack if all the neighbours are already visited + } + + private bool isValidNeighbour(int x, int y, int nx, int ny) + { + // get the walls from the two cells + (bool top, bool right, bool bottom, bool left) first = getWallsFromNumber(mazeState[x, y]); + (bool top, bool right, bool bottom, bool left) second = getWallsFromNumber(mazeState[nx, ny]); + + int dx = x - nx; + int dy = y - ny; + + if (dx == -1) + { + if (!(first.right && second.left)) + return true; + } + else if (dx == 1) + { + if (!(first.left && second.right)) + return true; + } + else if (dy == -1) + { + if (!(first.bottom && second.top)) + return true; + } + else if (dy == 1) + { + if (!(first.top && second.bottom)) + return true; + } + + + return false; + } + // this methode is not needed because we write our own Solve methode protected override List<Move> GetSolveList(int[,] boardToSolve) { return null; } + /* Add later + public override void TileInput(Point p, Keys key) + { + (bool top, bool right, bool bottom, bool left) = getWallsFromNumber(mazeState[p.X,p.Y]); + + switch (key) + { + case Keys.Up | Keys.W: + if (!top) + playerPos.Y++; + break; + case Keys.Down | Keys.S: + if (!bottom) + playerPos.Y--; + break; + case Keys.Right | Keys.D: + if (!right) + playerPos.X++; + break; + case Keys.Left | Keys.A: + if (!left) + playerPos.X--; + break; + } + + // upadte the spaces where the player has walked + boardState[playerPos.X, playerPos.Y] = 1; + } + */ public override void TileInput(Point p, int x) { throw new NotImplementedException(); } + public override void TileClick(Point p, int x) + { + //MessageBox.Show($"{mazeState[p.X, p.Y]}"); + } } }