Maze Path Algorithm

Good day, dear community.

Background



One fine day, walking the expanses of the Internet, a maze was found. It became interesting to know its passage and taking a walk on the network, I still could not find a working software implementation, the solution to the maze.

That's actually it:




The working day was boring, the mood was excellent. A goal, means and desire are available. The conclusion is obvious, we will pass.



History



For a convenient solution, you must have the existing image of the maze, lead to the type of two-dimensional array. Each element of which can take one of 3 values:

const
  WALL=-1;
  BLANK=-2;
  DEADBLOCK=-3;


First, I want to show the functions for scanning the image of the labyrinth, followed by writing data to the array, and the function of generating a new image, based on data from the array:
Image Scan:

...
var
  N:integer=600;
  LABIRINT:array[0..600,0..600] of integer;
...
var bit:TBitmap;
    i,j:integer;
begin
bit:=TBitmap.Create;
If OpenDialog1.Execute then
  begin
  bit.LoadFromFile(OpenDialog1.FileName);
  for i:=0 to N do
    for j:=0 to N do
      if bit.Canvas.Pixels[j,i]=clWhite then
        LABIRINT[j,i]:=BLANK else LABIRINT[j,i]:=WALL;
  bit.Free;
...
  end;
end;
...


Image Generation:

...
var
  N:integer=600;
  LABIRINT:array[0..600,0..600] of integer;
...
procedure genBitmap;
var bit:TBitmap;
    i,j:Integer;
begin
bit:=TBitmap.Create;
bit.Width:=N+1;
bit.Height:=N+1;
for i:=0 to N do
  for j:=0 to N do
    begin
    if LABIRINT[i,j]=BLANK then bit.Canvas.Pixels[i,j]:=clWhite //
      else
        if LABIRINT[i,j]=WALL then bit.Canvas.Pixels[i,j]:=clBlack
          else bit.Canvas.Pixels[i,j]:=clRed;
    end;
  bit.SaveToFile('tmp.bmp');
  bit.Free;
end;
...




For starters, you need to re-save the image as a monochrome bmp, in order to have 2 colors white or black. If you look closely at the maze, then it has a wall 2 pixels thick and a road 4 pixels thick. It would be ideal to make the wall and road thickness 1 pixel. To do this, it is necessary to rebuild the image, divide the image into 3, that is, remove every 2nd and 3rd, row and column of pixels from the picture (this will not affect the correctness and patency of the maze).

Prepared Figure:


Image width and height: 1802 pixels.



1. We use the image scanning function.
2. Rebuild the image:

...
var
  N:integer=1801;
  LABIRINT:array[0..1801,0..1801] of integer;
...
procedure rebuildArr2;
var i,j:integer;
begin
for i:=0 to ((N div 3) ) do
  for j:=0 to ((N div 3) ) do
    LABIRINT[i,j]:=LABIRINT[i*3,j*3];
N:=N div 3;
end;
...


3. Generate the rebuilt image.

The result of the procedure:


Image width and height: 601 pixels.



And so, we have an image of the labyrinth of the desired type, now the most interesting is the search for all the options for passing the labyrinth. What do we have? An array with the recorded values ​​WALL - wall and BLANK - road.

There was one unsuccessful attempt to find the passage of the maze using the wave algorithm. Why unsuccessful, in all attempts, this algorithm led to a "Stack Overflow" error. I am 100% sure that using it, you can find the passage, but there was a fuse to come up with something more interesting.

The idea did not come immediately, there were several realizations of the passage, which worked for about 3 minutes in time, after which the inspiration came: “what if you look not for the paths, but for paths that do not lead to the maze and mark them as dead ends”.

The algorithm is as follows:
Perform a recursive function on all points of the labyrinth's roads:
1. If we stand on the road and around us there are 3 walls, mark the place where we stand as a dead end, otherwise we exit the function;
2. We pass to the place which is not a wall from point No. 1, and we repeat point No. 1;

Software implementation:

...
var
  N:integer=600;
  LABIRINT:array[0..600,0..600] of integer;
...
procedure setBlankAsDeadblockRec(x,y:integer);
var k:integer;
begin
k:=0;
if LABIRINT[x,y]=blank then
  begin
  if LABIRINT[x-1,y]<>BLANK then k:=k+1;
  if LABIRINT[x,y-1]<>BLANK then k:=k+1;
  if LABIRINT[x+1,y]<>BLANK then k:=k+1;
  if LABIRINT[x,y+1]<>BLANK then k:=k+1;
  if k=4 then LABIRINT[x,y]:=DEADBLOCK;
  if k=3 then
    begin
    LABIRINT[x,y]:=DEADBLOCK;
    if LABIRINT[x-1,y]=BLANK then setBlankAsDeadblockRec(x-1,y);
    if LABIRINT[x,y-1]=BLANK then setBlankAsDeadblockRec(x,y-1);
    if LABIRINT[x+1,y]=BLANK then setBlankAsDeadblockRec(x+1,y);
    if LABIRINT[x,y+1]=BLANK then setBlankAsDeadblockRec(x,y+1);
    end;
  end;
end;
procedure setDeadblock;
var i,j:integer;
begin
for i:=1 to N-1 do
  for j:=1 to N-1 do
    setBlankAsDeadblockRec(i,j);
end;
...


Conclusion



I got a “complete” working algorithm that can be used to find all the maze walks. The latest in speed exceeded all expectations. I hope my little work will benefit someone or encourage new thoughts.

The program code and the maze traversed:
//Прошу не бить ногами за использованный язык программирования.
unit Unit1;
interface
uses
  Windows, Graphics, Forms, Dialogs, ExtCtrls, StdCtrls, Controls, Classes;
const
  WALL=-1;
  BLANK=-2;
  DEADBLOCK=-3;
type
  TForm1 = class(TForm)
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
  N:integer=600;
  LABIRINT:array[0..600,0..600] of integer;
implementation
{$R *.dfm}
procedure genBitmap;
var bit:TBitmap;
    i,j:Integer;
begin
bit:=TBitmap.Create;
bit.Width:=N+1;
bit.Height:=N+1;
for i:=0 to N do
  for j:=0 to N do
    begin
    if LABIRINT[i,j]=BLANK then bit.Canvas.Pixels[i,j]:=clWhite //
      else
        if LABIRINT[i,j]=WALL then bit.Canvas.Pixels[i,j]:=clBlack
          else bit.Canvas.Pixels[i,j]:=clRed;
    end;
  bit.SaveToFile('tmp.bmp');
  bit.Free;
end;
procedure rebuildArr2;
var i,j:integer;
begin
for i:=0 to ((N div 3) ) do
  for j:=0 to ((N div 3) ) do
    LABIRINT[i,j]:=LABIRINT[i*3,j*3];
N:=N div 3;
end;
procedure setBlankAsDeadblockRec(x,y:integer);
var k:integer;
begin
k:=0;
if LABIRINT[x,y]=blank then
  begin
  if LABIRINT[x-1,y]<>BLANK then k:=k+1;
  if LABIRINT[x,y-1]<>BLANK then k:=k+1;
  if LABIRINT[x+1,y]<>BLANK then k:=k+1;
  if LABIRINT[x,y+1]<>BLANK then k:=k+1;
  if k=4 then LABIRINT[x,y]:=DEADBLOCK;
  if k=3 then
    begin
    LABIRINT[x,y]:=DEADBLOCK;
    if LABIRINT[x-1,y]=BLANK then setBlankAsDeadblockRec(x-1,y);
    if LABIRINT[x,y-1]=BLANK then setBlankAsDeadblockRec(x,y-1);
    if LABIRINT[x+1,y]=BLANK then setBlankAsDeadblockRec(x+1,y);
    if LABIRINT[x,y+1]=BLANK then setBlankAsDeadblockRec(x,y+1);
    end;
  end;
end;
procedure setDeadblock;
var i,j:integer;
begin
for i:=1 to N-1 do
  for j:=1 to N-1 do
    setBlankAsDeadblockRec(i,j);
end;
procedure TForm1.Button1Click(Sender: TObject);
var bit:TBitmap;
    i,j:integer;
begin
bit:=TBitmap.Create;
If OpenDialog1.Execute then
  begin
  bit.LoadFromFile(OpenDialog1.FileName);
  for i:=0 to N do
    for j:=0 to N do
      if bit.Canvas.Pixels[j,i]=clWhite then
        LABIRINT[j,i]:=BLANK else LABIRINT[j,i]:=WALL;
  bit.Free;
  setDeadblock;
  genBitmap;
  end;
end;
end.






To find the shortest path, it is planned to apply the wave algorithm to the found maze walks. It would be interesting to hear what other algorithms can be used to quickly find a path in a large maze?

Also popular now: