I do genetic programming. I have 5 parcours, the first on the left is the easiest parcour.
The first parcour can be solved by place one belt in the middle pointing downwards to solve the problem. If placed in the middle downwards, the puzzle is solved because the iron-ore flows downwards to the robotic arm. The robotic arm grabs the iron-ore and place it into the wooden-box.
To solve the second parcour 3 belts have to be set.
This is the Brain of the Thinker
to mutate:
const int MAX_IDEAS=40000;
enum IdeaType {
MOVE_LEFT, MOVE_RIGHT, MOVE_UP, MOVE_DOWN, INC_MEM1_PTR, DEC_MEM1_PTR,
SET_MEM1_ZERO, INC_MEM1_BY_1, DEC_MEM1_BY_1, INC_MEM1_BY_10,
DEC_MEM1_BY_10, FLIP_MEM1_MEM2, GT_JMP, LT_JMP, EQ_JMP, NEQ_JMP,
LOOKUP_MEM1, STORE_MEM1, PLACE_BELT, GET_OBJECTIVE, ROTATE, FINISH,
FOLLOW_DIRECTION,
// the end
_COUNT
};
class Thinker {
private:
unsigned char memory[255];
unsigned char mem_pointer1, mem_pointer2;
unsigned char posX, posY;
public:
IdeaType ideas[MAX_IDEAS];
unsigned int execute(Playground *p, unsigned int maxExecs,
Objective *obj);
void randomize(int globalSeed);
Thinker* mutate(Thinker* spouse, int globalSeed);
void showIdeas();
};
This is the implementation of the steps to do:
unsigned int Thinker::execute(Playground *p, unsigned int maxExecs,
Objective *obj) {
mem_pointer1 = 0;
mem_pointer2 = 0;
posX = 0;
posY = 0;
unsigned int steps = 0;
for (int ideaNr = 0; ideaNr < MAX_IDEAS; ++ideaNr) {
steps++;
if (ideaNr < 0)
ideaNr = 0;
if (ideaNr > MAX_IDEAS)
ideaNr = MAX_IDEAS - 1;
IdeaType i = ideas[ideaNr];
switch (i) {
case LOOKUP_MEM1:
memory[mem_pointer1] = p->getCell(posX, posY)->getBuilding();
break;
case STORE_MEM1:
memory[mem_pointer1] = mem_pointer1;
break;
case GT_JMP:
if (mem_pointer1 > mem_pointer2) {
ideaNr += (mem_pointer1 - mem_pointer2);
}
break;
case LT_JMP:
if (mem_pointer1 < mem_pointer2) {
ideaNr += (mem_pointer2 - mem_pointer1);
}
break;
case EQ_JMP:
if (mem_pointer1 == mem_pointer2) {
ideaNr += mem_pointer1;
}
break;
case NEQ_JMP:
if (mem_pointer1 != mem_pointer2) {
ideaNr += mem_pointer1;
}
break;
case INC_MEM1_PTR:
mem_pointer1++;
break;
case DEC_MEM1_PTR:
mem_pointer1--;
break;
case SET_MEM1_ZERO:
memory[mem_pointer1] = 0;
break;
case INC_MEM1_BY_1:
memory[mem_pointer1] += 1;
break;
case DEC_MEM1_BY_1:
memory[mem_pointer1] -= 1;
break;
case INC_MEM1_BY_10:
memory[mem_pointer1] += 10;
break;
case DEC_MEM1_BY_10:
memory[mem_pointer1] -= 10;
break;
case FLIP_MEM1_MEM2:
unsigned char l;
l = mem_pointer1;
mem_pointer1 = mem_pointer2;
mem_pointer2 = l;
break;
case PLACE_BELT:
if (p->getCell(posX, posY) == 0) {
return steps;
}
if (p->getCell(posX, posY)->isBuildingLocked()) {
return steps;
}
p->setCell(posX, posY, YELLOW_BELT, NOTHING, DOWN);
break;
case FINISH:
return steps;
case MOVE_UP:
if (posY == 0) {
return steps;
}
posY--;
break;
case MOVE_LEFT:
if (posX == 0) {
return steps;
}
posX--;
break;
case MOVE_RIGHT:
if (p->getCell(posX + 1, 0) == 0) {
return steps;
}
posX++;
break;
case FOLLOW_DIRECTION:
if (p->getCell(posX, posY) == 0) {
return steps;
}
Cell* c;
c = p->getCell(posX, posY);
if(c->getBuilding()==YELLOW_BELT) {
switch (c->getDirection()) {
case DOWN:
if(p->below(p->getCell(posX, posY))==0) {
return steps;
}
posY++;
break;
case UP:
if(posY==0) {
return steps;
}
posY--;
break;
case LEFT:
if(posX==0) {
return steps;
}
posX--;
break;
case RIGHT:
if(p->right(p->getCell(posX, posY))==0) {
return steps;
}
posX++;
break;
default:
break;
}
}
break;
case MOVE_DOWN:
if (p->getCell(0, posY + 1) == 0) {
return steps;
}
posY++;
break;
case GET_OBJECTIVE:
if (obj->row == posY && obj->cell == posX) {
memory[mem_pointer1] = obj->prospection;
} else {
memory[mem_pointer1] = 255;
}
break;
case ROTATE:
if (p->getCell(posX, posY) == 0) {
return steps;
}
if (p->getCell(posX, posY)->isBuildingLocked()) {
return steps;
}
p->getCell(posX, posY)->rotate();
break;
default:
cout << endl << "PROBLEM!!!!; VALUE OUT OF RANGE: " << (int) i
<< endl;
return steps;
break;
}
if (steps > maxExecs) {
cout << "Max ideas reached!" << endl;
break;
}
}
return steps;
}
Before mutation I initialize by this random method:
void Thinker::randomize(int globalSeed) {
srand(globalSeed);
for (int ideaNr = 0; ideaNr < MAX_IDEAS; ++ideaNr) {
int r = rand() % _COUNT;
ideas[ideaNr] = static_cast<IdeaType>(r);
}
}
To mutate two thinker I use this method:
Thinker* Thinker::mutate(Thinker *spouse, int globalSeed) {
Thinker *tt = new Thinker();
srand(globalSeed);
int from = rand() % MAX_IDEAS - 1;
int count = rand() % (MAX_IDEAS - from);
if (count == 0) {
count = 1;
}
int halfMaxIdeas = count / 2 + from;
for (int i = from; i < from + count; i++) {
if (i < halfMaxIdeas) {
tt->ideas[i] = ideas[i];
} else {
tt->ideas[i] = spouse->ideas[i];
}
}
return tt;
}
The rest is like this: There must be always 20,000 thinker. They do all try to solve the puzzle. Those winners are mutated.
Current results:
- From the first 20,000 candidates there where only 138 who could solve the maze successfully.
- I do mutate all the 138 candidates up to 20,000 mutations of those candidates. Then I let them solve the second puzzle.
- From the second puzzle there where only 2 winners.
Only 2 winners is a bad result because they can be mutated but they are much too inbreeded for further healthy candidates.
What do I need a review for?
- Documentation style
- Range check
- Missing important IdeaTypes
- Check if the mutation algorithm do a valid mutation.
- Maybe optimizations