diff --git a/PuzzlePlayer/Binary.cs b/PuzzlePlayer/Binary.cs index 9a07818a78db858a17d5baa682ba71e0469f1cf0..d4c851b79497830622fda9612d43a75502355fed 100644 --- a/PuzzlePlayer/Binary.cs +++ b/PuzzlePlayer/Binary.cs @@ -215,21 +215,8 @@ namespace PuzzlePlayer_Namespace return null; } - ///* - // shuffle the choices in the list around to get different results every time - // First answer from: https://stackoverflow.com/questions/273313/randomize-a-listt - Random random = new Random(); - int n = choices.Count; - while (n > 1) - { - n--; - int rand = random.Next(n + 1); - Move copy = choices[rand]; - choices[rand] = choices[n]; - choices[n] = copy; - } - //*/ - + choices.Shuffle(); //shuffle the choices around for random results everytime + //(Shuffle methode from the extentions class in the Board.cs file // do the algorithm for every move foreach (Move m in choices) diff --git a/PuzzlePlayer/Board.cs b/PuzzlePlayer/Board.cs index 7b38b2bd37c9e71306f965bc96dff8e2dd461e8f..93e1ea62e3826195c3e639bd1a74958827b535fa 100644 --- a/PuzzlePlayer/Board.cs +++ b/PuzzlePlayer/Board.cs @@ -72,10 +72,11 @@ namespace PuzzlePlayer_Namespace return false; } } + public abstract void Draw(Graphics gr, Rectangle r); // a methode for solving the whole board. It uses the private SolveStep methode untill the whole board is solved // it has one parameter. setting this to true will only give a return value without changing the current boardState - public SOLUTIONS Solve(bool CheckOnly) + public virtual SOLUTIONS Solve(bool CheckOnly) { // two variables for storing the result and the next solveStep int[,] result = (int[,])boardState.Clone(); @@ -136,4 +137,26 @@ namespace PuzzlePlayer_Namespace // performs a left/right (X) click on tile P, changing its value public virtual void TileClick(Point p, int x) { } } + + + // static class for extentions + public static class Extensions + { + private static readonly Random rng = new Random(); + + // shuffle the elements in a list around to get different results every time from the generator algoritms + // First answer from: https://stackoverflow.com/questions/273313/randomize-a-listt + public static void Shuffle<T>(this IList<T> list) + { + int n = list.Count; + while (n > 1) + { + n--; + int k = rng.Next(n + 1); + T value = list[k]; + list[k] = list[n]; + list[n] = value; + } + } + } } diff --git a/PuzzlePlayer/Maze.cs b/PuzzlePlayer/Maze.cs index 7ff7d1d2de5eeed3f3f2000055972c0a0ba7d22d..7d0b0efd251708c4487b84dbc9c1e62eff5085c0 100644 --- a/PuzzlePlayer/Maze.cs +++ b/PuzzlePlayer/Maze.cs @@ -4,18 +4,21 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; +using System.Security.Policy; using System.Text; using System.Threading.Tasks; +using System.Xml.XPath; using static System.Runtime.InteropServices.JavaScript.JSType; namespace PuzzlePlayer_Namespace { /* - * all the info about the maze is stored in the mazeState int[,] - * this way the boardState can be used to keep track off the spaces that the player has visited + * all the info about where the walls in the maze are is stored in the mazeState int[,] + * the info of wich cells are already visited by the generator methode are stored in the visitedCells + * the boardState is used to keep track of the spaces that the player has visited while solving the maze * - * if the player has not yet visited a space then it is equal to the emptyspace constant - * if the player did visit the space then it is equal to 1 + * if the player/generator has not yet visited a space then it is equal to the emptyspace constant + * if the player/generator did visit the space then it is equal to 1 * * The maze board consists of cells with a number between 1 and 15 * the number represents where the walls are @@ -36,13 +39,17 @@ namespace PuzzlePlayer_Namespace class Maze : Board { int[,] mazeState; + int[,] visitedCells; - public Maze(int size = 3) + public Maze(int size = 6) { + // init all 2D array's boardState = GetClearBoard(size); mazeState = GetClearBoard(size); + visitedCells = GetClearBoard(size); - // example thingy + /* + // example thingy (DELETE LATER) mazeState[0, 0] = 5; mazeState[1, 0] = 3; mazeState[2, 0] = 11; @@ -54,20 +61,21 @@ namespace PuzzlePlayer_Namespace mazeState[2, 2] = 5; boardState[0, 2] = 1; + */ } // two funcions to go from number to walls and back - private int getNumberFromWalls(bool top, bool right, bool bottom, bool left) + private int getNumberFromWalls((bool top, bool right, bool bottom, bool left) cell) { int result = 0; - if (top) + if (cell.top) result++; - if (right) + if (cell.right) result += 2; - if (bottom) + if (cell.bottom) result += 4; - if (left) + if (cell.left) result += 8; if (result == 0) @@ -79,7 +87,8 @@ namespace PuzzlePlayer_Namespace private (bool,bool,bool,bool) getWallsFromNumber(int number) { if(number == emptySpace) - return (false,false,false,false); //if the place is empty then there are no walls + 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 bool top = (number & 1) != 0; @@ -92,6 +101,9 @@ namespace PuzzlePlayer_Namespace public override void Draw(Graphics gr, Rectangle r) { + // clear screen + gr.FillRectangle(Brushes.DarkGreen, r); + Size tilesize = new Size(r.Width / boardState.GetLength(0), r.Height / boardState.GetLength(1)); Pen wall = new Pen(Color.Black, tilesize.Width / 5); @@ -127,15 +139,126 @@ namespace PuzzlePlayer_Namespace gr.DrawString("END", SettingForm.mainFont, Brushes.Red, r.Right - tilesize.Width / 4, r.Bottom - tilesize.Height / 2); } - protected override List<Move> GetSolveList(int[,] boardToSolve) + // this methode generates a Maze starting with an empty board + // it uses the randomized depth first search algoritm as described on this wiki page: + // https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_depth-first_search + public override void Generate() { - throw new NotImplementedException(); + // clear visitedCells + visitedCells = GetClearBoard(mazeState.GetLength(0)); + + Random rnd = new Random(); + int x = rnd.Next(mazeState.GetLength(0)); + int y = rnd.Next(mazeState.GetLength(0)); + RND_Recursive_DepthFirstSearchMaze(x,y); + } - // https://en.wikipedia.org/wiki/Maze_generation_algorithm#Randomized_depth-first_search - public override void Generate() + private bool RND_Recursive_DepthFirstSearchMaze(int x, int y) { - throw new NotImplementedException(); + 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) + 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) + { + 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 + 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 + } + + // update the walls between two cells + private void UpdateWalls(int x, int y, int x2, int y2) + { + // 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[x2, y2]); + + int dx = x - x2; + int dy = y - y2; + + if(dx == -1) + { + first.right = false; + second.left = false; + } + else if (dx == 1) + { + first.left = false; + second.right = false; + } + else if (dy == -1) + { + first.bottom = false; + second.top = false; + } + else if (dy == 1) + { + first.top = false; + second.bottom = false; + } + + // update the walls + mazeState[x, y] = getNumberFromWalls(first); + mazeState[x2, y2] = getNumberFromWalls(second); + } + + // checks all the neighbours for the checkFor param (this is used to get either the visited or the unvisited neighbours) + private List<(int,int)> getNeigbours(int x, int y, int checkFor) + { + List<(int,int)> result = new List<(int,int)> (); + + if(x-1 >= 0) + if(visitedCells[x-1,y] == checkFor) + result.Add((x-1,y)); + if (y - 1 >= 0) + if (visitedCells[x, y - 1] == checkFor) + result.Add((x, y - 1)); + + if (x + 1 < visitedCells.GetLength(0)) + if (visitedCells[x + 1, y] == checkFor) + result.Add((x + 1, y)); + if (y + 1 < visitedCells.GetLength(1)) + if (visitedCells[x, y + 1] == checkFor) + result.Add((x, y + 1)); + /* + for (int checkX = x-1;checkX <= x+1; checkX++) + for(int checkY = y-1;checkY <= y+1;checkY++) + { + // check if checkX/Y are within bounds + if(checkX >= 0 && checkY >=0 && checkX < visitedCells.GetLength(0) && checkY < visitedCells.GetLength(1)) + if(visitedCells[checkX,checkY] == checkFor) + result.Add((checkX,checkY)); + } + */ + return result; + } + + // overrite the Solve methode because a maze and an ordinary board puzzle like a sudoku or binary puzzle + // 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>(); + + + return SOLUTIONS.NONE; + } + + // this methode is not needed because we write our own Solve methode + protected override List<Move> GetSolveList(int[,] boardToSolve) + { + return null; } public override void TileInput(Point p, int x)