Genetic Algorithm Maze Solver

Started by
18 comments, last by DanBrink 16 years, 4 months ago
Thank you for going into such detail Steadtler, I never would've thought of doing the maze that way. I think I have the class set up decently, I'm having trouble with CanPass function. I'm getting numbers like 61, 189, whereas I should be getting true/false. If I do CanPass with a 0 for X it always gives me a 0, anything higher results in some seemingly random integer.
Quote:
const int maxsize = 15; //max 10x10 maze;

class mazemap
{
public:
mazemap(int sizex, int sizey)//constructor
{
_sizex = sizex;
_sizey = sizey;
}
void Build(){//fill your buffer in this operator
for(int i = 0; i<=_sizey; i++)
{
for(int j = 0; j<=_sizex; j++)
{
SetPass(i,j,false);
}
}
}
bool CanPass(int x, int y)//returns the value of the buffer at this point
{
return _map[y * _sizex + x];
}
private:

void SetPass(int x, int y, bool value) //set a value in the buffer
{
_map[y * _sizex + x] = value;
}
bool _map[maxsize]; //buffer containing the wall/passage value
int _sizex;
int _sizey; //sizex*sizey need to be smaller than maxsize

};

int main(void)
{
mazemap map(5,3);
map.Build();
cout << "(1,1)=" << map.CanPass(1,1);
srand ( time(NULL) ); //prepare
getch(); //Wait for a key before closing the program.
return 0;
}


Thanks in advance to anyone who takes the time to read this.
Advertisement

Lets see...

First, you ought to put your definitions in a .cpp file, and your declarations in a .h file, just for clarity.

Then in your "build" operator, you have a buffer overflow.

for(int i = 0; i<=_sizey; i++)for(int j = 0; j<=_sizex; j++)


should be

for(int i = 0; i<_sizey; i++)for(int j = 0; j<_sizex; j++)


this means that your operators _sizex and _sizey and probably other stuff were overwritten with zeros. Its a good idea to use asserts in operators like setpass and canpass to verify the validity of the inputs...

Also, remember that "false" is defined as zero while "true" is ANYTING but the zero-value.

Bufferoverflows like that are why you want to learn to use STL in due time :)

Maybe you ought to move this to the begginer's forum; its not exactly an AI discussion anymore.
Don't take this the wrong way, but why don't you try programming this in something like Python?
The only thing I'm confused by at the moment is if I set all the maze values to false (Through the two for loops) Why am I still getting non-zero values when I do a check pass?

Also, Crivens, my teacher was all excited about getting me to use C++. I didn't have much choice in the matter. I think I'll be able to complete this, I've still got a little less then a week and I'm just looking for a very rough, working draft of the program (Although, I'm taking this long just to setup the maze which I thought I'd have done on the first day).

Thanks for all the help guys, if any moderator would like to move this to Beginner's forum feel free too.
** Ignore this post, read my post a few posts down from this one **

First thing you need to get rid of those implicit type cast dependencies.

Have the return logic for your canPass function be based on conditional logic. I.e.:
if(_map[y * _sizex + x]==0||_map[y * _sizex + x]==2) return true;
else return false;

Have your setPass function take an int instead of a boolean (this will also allow you to use this same function to set the exit tile).

Your reliance on the compiler to appropriately cast from boolean to int and back may very well be the cause of your current problems.

Basically never do this:
someInteger = someBoolean;

An explicit way would be:
someInteger = someBoolean?1:0;
someBoolean = someInteger==0?false:true;
but don't use these in your project either.

[Edited by - Deadpenguin on November 29, 2007 2:31:50 AM]
Quote:Original post by Deadpenguin
First thing you need to get rid of those implicit type cast dependencies.

Have the return logic for your canPass function be based on conditional logic. I.e.:
if(_map[y * _sizex + x]==0||_map[y * _sizex + x]==2) return true;
else return false;

Have your setPass function take an int instead of a boolean (this will also allow you to use this same function to set the exit tile).

Your reliance on the compiler to appropriately cast from boolean to int and back may very well be the cause of your current problems.

Basically never do this:
someInteger = someBoolean;

An explicit way would be:
someInteger = someBoolean?1:0;
someBoolean = someInteger==0?false:true;
but don't use these in your project either.


I agree on principle, but I fail to see which line(s) in the code provided perform an implicit cast. Must be the lemonade getting to my head.

However, replacing all bool's with int's would allow you to reuse this class as your "gene" class.
Whoops, looks like I switched to the decaf tea too soon. =)

Somehow my brain melded the two code postings by DanBrink and I completely missed that he had redefined his map as a set of boolean values. Doh! (In his original code posting it was a set of integers and his map consisted of values 0-2). My assumption was that 2 was meant to be an exit tile/node.

Also I did not go into depth about implicit type conversions, which the OP may not be familiar with, so an explanation for completeness:

An implicit type conversion is when you assign a value of one type to a variable of another type. (Example: someDouble = someInt) The compiler has to decide how to handle this or throw a type error. In most cases the compiler will attempt to convert your right hand value to the type of the left hand variable. So when you type (someBool = someInt) the compiler will attempt to convert that integer value to a boolean value. This can have unexpected results if you are inexperienced with how the compiler will convert that value.

Had you still been using an array of integers that might have been the problem... =)

Anyways, to the problem at hand. I ran your code and took a good look at it (no dyslexia this time!) and I did see one mistake:

for(int i = 0; i<=_sizey; i++)
{
for(int j = 0; j<=_sizex; j++)
{
SetPass(i,j,false);

Your setPass function takes (x,y,bool) but you're giving it (y,x,bool) since i=y and j=x. That will cause some parts of the array to not be filled as well as over flow. Specifically that will fill index[0,1,2,5,6,7,10,11,12,15,16,17,20,21,22] with zeros.

Switching those lines to:
for(int i = 0; i<=_sizex; i++)
{
for(int j = 0; j<=_sizey; j++)
{
SetPass(i,j,false);

resolved the issue you were having in my compiled version.

I'm just happy I redeemed myself and thank you to Steadtler for not rubbing my nose in my sleepy mistake. =)
Genetic algorithms aren't very good for maze problems. My human brain uses sentient intelligence (version 3.21 beta IIRC) and I know that my highest rate of success would be to simply turn left at every possible opportunity. In a maze purely made of up of unknown decisions (turn left or right or just go straight), there's no wrong or right answer. Genetic algorithms are good for coming to a definable conclusion not random decisions. Unless the algorithm can evolve to eliminate areas, I don't see this working out very well for you (then again, that may be your answer).

You could just present the code for the algorithm and write a paper on why genetic algorithms don't work for mazes, using your experience on the subject.
Quit screwin' around! - Brock Samson
That's exactly what I plan on doing coderx75, I just need to get the code working (or at least looking like it'll work)
Alright guys, I've coded a bit further and I'm now at the crossover stage of my program. Only problem is, I'm getting stuck at one fitness. I output the best fitness after every crossover and it gets stuck on its first fitness, 9.

This is probably the last step in my project as I won't be adding in mutation (probably anyway)
Quote:
#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <conio.h>

const int maxsize = 15; //max 10x10 maze;
const int maxx = 5;
const int maxy = 3;
const int looplimit = 10; //number of times the program loops through process
const int genesize = 19;
const int popsize = 50;

//MAZE CLASS
class mazemap
{
public:
mazemap(int sizex, int sizey)//constructor
{
_sizex = sizex;
_sizey = sizey;
}
mazemap()
{

}
void Build(){//fill your buffer in this operator
for(int i = 0; i<_sizey; i++)
{
for(int j = 0; j<_sizex; j++)
{
SetPass(j,i,0);
}
}
SetPass(3,0,1); //some walls
SetPass(1,1,1);
}
int CanPass(int x, int y)//returns the value of the buffer at this point
{
if(_map[y * _sizex + x]==0||_map[y * _sizex + x]==2) return 0;
else return 1;
}
private:

void SetPass(int x, int y, int value) //set a value in the buffer
{
_map[y * _sizex + x] = value;
}
int _map[maxsize]; //buffer containing the wall/passage value
int _sizex;
int _sizey; //sizex*sizey need to be smaller than maxsize

};

//GENE CLASS
class gene
{
public:
gene() //Constructor
{
_x = 0;
_y = 0;
randomizePath();

}

char getDirection(int index)
{
if(index <= genesize)
{
return(_path[index]);
}
else
{
return 'T'; //getting a T will mean an error
}
}

void SetPath(int index, char dir)
{
_path[index] = dir;
}
void outputCoords()
{
cout << "X: " << _x << " Y: " << _y;
}
double getFitness()
{
return _fitness;
}
void fitness(mazemap maze)
{
for(int n = 0; n <= genesize; n++)
{
switch(_path[n])
{
case 'N': if( maze.CanPass(_x,_y-1)==0 && _y-1 >= 0)
{
_y--;
}
break;

case 'S': if( maze.CanPass(_x,_y+1)== 0 && _y+1 <= maxy)
{
_y++;
}
break;
case 'W': if( maze.CanPass(_x-1,_y) == 0 && _x-1 >= 0)
{
_x--;
}
break;
case 'E': if( maze.CanPass(_x+1,_y) == 0 && _x+1 <= maxx)
{
_x++;
}
break;
}
_fitness = round(sqrt( ((5-_x)*(5-_x)) + ((3*-_y)*(3*-_y))));
}

}

private:


char _path[genesize]; //path of gene
double _fitness;
int _x, _y;
void randomizePath()
{
for(int in = 0; in <= genesize; in++)
{
int n = rand() % 4 + 1;
//cout << n;
switch(n)
{
case 1: SetPath(in,'N'); break;
case 2: SetPath(in,'W'); break;
case 3: SetPath(in,'S'); break;
case 4: SetPath(in,'E'); break;
default: SetPath(in,'N');
} //end switch
} //end for loop
}//end function
};



int main(void)
{
srand ( time(NULL) ); //prepare
mazemap map(maxx,maxy);
map.Build();
gene pop[popsize], oldpop[popsize];

/* for(int i = 0; i<3; i++) //show the map
{
for(int j = 0; j<5; j++)
{
cout << map.CanPass(j,i);
}
cout << "\n";
}
*/


while(pop[0].getFitness()!= 0) // while maze isn't completed
{

for(int n = 0; n <= popsize; n++) //Compute fitness of population
{
pop[n].fitness(map);
}
//SORTING POPULATION BY FITNESS
int i, j, flag = 1; // set flag to 1 to begin initial pass
gene temp; // holding variable
int arrayLength = popsize + 1;
for(i = 1; (i <= arrayLength) && flag; i++)
{
flag = 0;
for (j=0; j < (arrayLength -1); j++)
{
if (pop[j+1].getFitness() > pop[j].getFitness()) // ascending order simply changes to <
{
temp = pop[j]; // swap elements
pop[j] = pop[j+1];
pop[j+1] = temp;
flag = 1; // indicates that a swap occurred.
}
}
}

//END SORTING

for(int i = popsize; i >= 0; i--) // Breeding
{
int parenta = rand() % 10 + 1;
int parentb = rand() % 10 + 1;
int crossover = rand() % genesize-1 + 1;//point where parenta and parentb's genes cross
for(int j = 0; j <= genesize; j++)
{
if(j <= crossover)
{
pop.SetPath(j, pop[parenta].getDirection(j));
}
else
{
pop.SetPath(j, pop[parentb].getDirection(j));
}
}
pop[j].fitness(map); // record the fitness of the newly created gene
}

cout << pop[0].getFitness() << "\n";
}
for(int n = 0; n <= popsize; n++) //show genes' fitness
{
cout << n << ":" << pop[n].getFitness() << " ";
pop[n].outputCoords();
cout << " \n" ;
}
getch(); //Wait for a key before closing the program.
return 0;
}



I pretty much made some messy work-arounds and I wouldn't mind finishing with one. I don't care if its pretty, I just want it to work for the most part.

Thanks for reading and thanks to all of those who've helped me on this program before.

EDIT: After running some tests, it messes up in the cross over part. No idea how though.

This topic is closed to new replies.

Advertisement