Introduction
In this lesson, we will be creating a C# form that creates and solves a maze using a recursive technique. It will cover the creation of the maze creator using PictureBoxes and solving the maze.
Project Goals
- Goal 1: Create a maze creator using PictureBoxes. The PictureBoxes are referenced by a multi-dimensional array, maze tiles, for easy access and manipulation.
- Goal 2: Solve the maze using a recursive technique marking the correct path with the color gray. If the maze cannot be solved, then show a dialogue box.
- Goal 3: Make a reset button that will remove the gray coloring and remark the start and finish in light blue. The start is in the top right corner, and the finish is in the bottom left.
![1.gif]()
Create a New Project
I'm using Visual C# 2010 for this lesson. Create a new Windows Form Application. I have named mine as MCS_Week_003, and if you've chosen a different name, then it will have to be changed in code.
Creating the Interface
Once you have created your project, a new form will be available. Add the following controls using the toolbox in the form designer. Note the squares are picture boxes with thier background color changed. This, in code, is BackColor. Name them as the following.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Your_Project_Name
{
public partial class Form1 : Form
{
private Color currentColor = Color.White;
private int XTILES = 25;
private int YTILES = 25;
private int TILESIZE = 10;
private PictureBox[,] mazeTiles;
public Form1()
{
InitializeComponent();
createNewMaze();
}
private void createNewMaze()
{
mazeTiles = new PictureBox[XTILES, YTILES];
for (int i = 0; i < XTILES; i++)
{
for (int j = 0; j < YTILES; j++)
{
mazeTiles[i, j] = new PictureBox();
int xPosition = (i * TILESIZE) + 13;
int yPosition = (j * TILESIZE) + 45;
mazeTiles[i, j].SetBounds(xPosition, yPosition, TILESIZE, TILESIZE);
if ((i == 0 && j == 0) || (i == XTILES - 1 && j == YTILES - 1))
{
mazeTiles[i, j].BackColor = Color.LightBlue;
}
else
{
mazeTiles[i, j].BackColor = Color.White;
mazeTiles[i, j].Click += PictureBox_Click;
}
this.Controls.Add(mazeTiles[i, j]);
}
}
}
private void pictureBox1_Click(object sender, EventArgs e)
{
currentColor = Color.White;
}
private void pictureBox2_Click(object sender, EventArgs e)
{
currentColor = Color.Black;
}
private void PictureBox_Click(object sender, EventArgs e)
{
((PictureBox)sender).BackColor = currentColor;
}
private void button1_Click(object sender, EventArgs e)
{
bool[,] alreadySearched = new bool[XTILES, YTILES];
if (!solveMaze(0, 0, alreadySearched))
{
MessageBox.Show("Maze cannot be solved.");
}
}
private void button2_Click(object sender, EventArgs e)
{
for (int i = 0; i < XTILES; i++)
{
for (int j = 0; j < YTILES; j++)
{
if (mazeTiles[i, j].BackColor == Color.Gray)
{
mazeTiles[i, j].BackColor = Color.White;
}
}
}
mazeTiles[0, 0].BackColor = Color.LightBlue;
mazeTiles[XTILES - 1, YTILES - 1].BackColor = Color.LightBlue;
}
private bool solveMaze(int xPos, int yPos, bool[,] alreadySearched)
{
bool correctPath = false;
bool shouldCheck = true;
if (xPos >= XTILES || xPos < 0 || yPos >= YTILES || yPos < 0)
{
shouldCheck = false;
}
else
{
if (mazeTiles[xPos, yPos].BackColor == Color.LightBlue && (xPos != 0 || yPos != 0))
{
correctPath = true;
shouldCheck = false;
}
if (mazeTiles[xPos, yPos].BackColor == Color.Black)
{
shouldCheck = false;
}
if (alreadySearched[xPos, yPos])
{
shouldCheck = false;
}
}
if (shouldCheck)
{
alreadySearched[xPos, yPos] = true;
correctPath = correctPath || solveMaze(xPos + 1, yPos, alreadySearched);
correctPath = correctPath || solveMaze(xPos, yPos + 1, alreadySearched);
correctPath = correctPath || solveMaze(xPos - 1, yPos, alreadySearched);
correctPath = correctPath || solveMaze(xPos, yPos - 1, alreadySearched);
}
if (correctPath)
{
mazeTiles[xPos, yPos].BackColor = Color.Gray;
}
return correctPath;
}
}
}
![2.gif]()
Adding a Color Selector
The maze builder will allow the user to create walls and paths by clicking on the maze tiles. The color will be determined by the last PictureBox selected and will be white by default. Create a global variable of the Color class naming it currentColor. The addition is below.
Adding the Variable currentColor
Next, make pixtureBox1 and pictureBox2 clickable by adding the following method to the form. cs. A quick way to do this is to double-click the object in the designer.
Making the White Picture Box Clickable
Making the Black Picture Box Clickable
Creating the Maze Tiles
The maze contains a grid of picture boxes that will be used to store the walls, path, start, and finish depending on their color. We will make the maze expandable using XTILES and YTILES to determine the number of tiles in the maze. The size of each tile will be determind by TILESIZE. Feel free to change these values to any non-negative and zero number. The PictureBoxes will be stored in the mazeTiles array. Add the following variables to your Form1.cs
Adding more Variables
There needs to be a way to create the maze easily with one call from a method. The createNewMaze() method will create a grid of picture boxes. It makes the top left and bottom right light blue and all other tiles white and clickable, calling the method PictureBox_Click(). The last line, this.Controls.Add(mazeTile[i,j]); adds the PictureBox controls to the form allowing them to be displayed. Add the following code to your form. cs.
Creating The Maze PictureBoxes
Making the PictureBoxes Clickable
The following code in the last code block made the PictureBoxes clickable calling the method PictureBox_Click(). The += is used add the event plus any click events that may have been already added.
Making the White Picture Box Clickable
Next, make the PictureBox_Click method by adding the following method to your Form1.cs. This code will set the PictureBox's BackColor property to currentColor.
Adding the PictureBox_Click Method
Run your project, and you should be able to change the color of the PictureBoxes. You can experiment with the constants at the top of the code file to manipulate the tiles.
Constants
![3.gif]()
The Recursive Algorithm
Recursion is the act of a method calling itself and is the key to solving the maze.
Arguments
- xPos - the x coordinate to search
- yPos - the y coordinate to search
- already searched - bool array of searched elements
Return
- Bool - is it the right path?
Algorithm
- If out of boundary, return false
- If at a wall, return false
- If at finish return true
- If not returned and not
Already searched; search the tile
Search
Marking in already searched and Check right, down, left, up and return and if one returns the correct path return true.
![4.gif]()
Direction Precedence
Checks right, down, up, then up. Will not be the shortest path.
Making the Solve Button Clickable
Before calling the solveMaze recursive method, there needs to be a way to prevent an infinite loop. To prevent one, we are adding an alreadySearched bool array and passing it as an argument through the solveMaze recursive method. This will prevent paths that are alreadysearched from being searched again.
The solveMaze() recursive method is then called starting at tile 0,0 and if it returns false a message box will be shown displaying "Maze can not be solved".
![5.gif]()
Adding the Solve Button Code
Solving the Maze
The following code will search the maze one tile at a time, and if a correct path is found, the path will be marked in gray. To understand the code run through it tile by tile. You may want to shrink the amount of tiles with XTILES and YTILES. Add the following method to your Form1.cs.
Creating the solveMaze() Method
The order of precedence is derived from the order of the searchMaze() calls. You can experiment by changing the order, for example making the algorithm move down before traveling right. You can also try to modify this algorithm into highlighting all correct paths.
Using the Maze Program
The maze solver is now complete, and you can start creating mazes for the computer to solve.
Create a maze with multiple solutions. Why does the program choose one path over another? Why doesn't it highlight both paths? Once you understand the answers to these questions, you will have a better understanding of this algorithm and recursion.
![6.gif]()
Resetting the Maze
The last thing needed is a reset button that changes the grey tiles to white and the start and finish back to light blue. Add the following code to your Form1.cs, and you are finished. I hope you have enjoyed this resource. If you have any questions or comments, feel free to email me at koflaherty@masteringcsharp.com. The full source code is below.
Adding the Reset Button Code
Complete Source Code (Form1.cs)
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Your_Project_Name
{
public partial class Form1 : Form
{
private Color currentColor = Color.White;
private int XTILES = 25;
private int YTILES = 25;
private int TILESIZE = 10;
private PictureBox[,] mazeTiles;
public Form1()
{
InitializeComponent();
createNewMaze();
}
private void createNewMaze()
{
mazeTiles = new PictureBox[XTILES, YTILES];
for (int i = 0; i < XTILES; i++)
{
for (int j = 0; j < YTILES; j++)
{
mazeTiles[i, j] = new PictureBox();
int xPosition = (i * TILESIZE) + 13;
int yPosition = (j * TILESIZE) + 45;
mazeTiles[i, j].SetBounds(xPosition, yPosition, TILESIZE, TILESIZE);
if ((i == 0 && j == 0) || (i == XTILES - 1 && j == YTILES - 1))
{
mazeTiles[i, j].BackColor = Color.LightBlue;
}
else
{
mazeTiles[i, j].BackColor = Color.White;
mazeTiles[i, j].Click += PictureBox_Click;
}
this.Controls.Add(mazeTiles[i, j]);
}
}
}
private void pictureBox1_Click(object sender, EventArgs e)
{
currentColor = Color.White;
}
private void pictureBox2_Click(object sender, EventArgs e)
{
currentColor = Color.Black;
}
private void PictureBox_Click(object sender, EventArgs e)
{
((PictureBox)sender).BackColor = currentColor;
}
private void button1_Click(object sender, EventArgs e)
{
bool[,] alreadySearched = new bool[XTILES, YTILES];
if (!solveMaze(0, 0, alreadySearched))
{
MessageBox.Show("Maze cannot be solved.");
}
}
private void button2_Click(object sender, EventArgs e)
{
for (int i = 0; i < XTILES; i++)
{
for (int j = 0; j < YTILES; j++)
{
if (mazeTiles[i, j].BackColor == Color.Gray)
{
mazeTiles[i, j].BackColor = Color.White;
}
}
}
mazeTiles[0, 0].BackColor = Color.LightBlue;
mazeTiles[XTILES - 1, YTILES - 1].BackColor = Color.LightBlue;
}
private bool solveMaze(int xPos, int yPos, bool[,] alreadySearched)
{
bool correctPath = false;
bool shouldCheck = true;
if (xPos >= XTILES || xPos < 0 || yPos >= YTILES || yPos < 0)
{
shouldCheck = false;
}
else
{
if (mazeTiles[xPos, yPos].BackColor == Color.LightBlue && (xPos != 0 || yPos != 0))
{
correctPath = true;
shouldCheck = false;
}
if (mazeTiles[xPos, yPos].BackColor == Color.Black)
{
shouldCheck = false;
}
if (alreadySearched[xPos, yPos])
{
shouldCheck = false;
}
}
if (shouldCheck)
{
alreadySearched[xPos, yPos] = true;
correctPath = correctPath || solveMaze(xPos + 1, yPos, alreadySearched);
correctPath = correctPath || solveMaze(xPos, yPos + 1, alreadySearched);
correctPath = correctPath || solveMaze(xPos - 1, yPos, alreadySearched);
correctPath = correctPath || solveMaze(xPos, yPos - 1, alreadySearched);
}
if (correctPath)
{
mazeTiles[xPos, yPos].BackColor = Color.Gray;
}
return correctPath;
}
}
}