# Is this considered "AI?"

43 replies to this topic

### #21Hyper

Valued Member

• Members
• 195 posts

Posted 06 October 2009 - 09:57 PM

poita said:

Well I would suggest using A* like everyone else has said. What you have there is nothing like A*.

What was it about A* that you didn't understand?

I don't know, everything? The entire idea is illogical.
That, and the fact people say A* as if it has meaning.
According to the few links I've read, A* is never the same.
The examples I've read have 20,000 things that aren't required, meaning I have to try and learn what's for the Win32 crap, and what's valid.
People can't make a simple basic program with the bare neccessities to show the A* algorithm.

Either way, it doesn't really matter. I'm just posting what I slowly change, as I change it, and speaking of which:
I found three or four logical bugs in that.

This is the newest version (and I'm confused as to why it does this now):
#include <iostream>

#include <vector>

#include <TextControl.h>

using namespace std;

int TotalRuns = 0;

int TotalCrashes = 0;

int FoodX = 0;

int FoodY = 0;

int SnakeX[100] = {'\0'};

int SnakeY[100] = {'\0'};

vector<int> Instructions;

void DrawSnake(const int, int, int);

bool Crash(const int X[], const int Y[]) {

for (int x = 9; x > 1; x--) {

if (X[0] == X[x] && Y[0] == Y[x]) {

PlaceCursor(0, 45);

SetColor(RED, BLACK);

printf("X: %d  ", X[0]);

PlaceCursor(0, 46);

SetColor(RED, BLACK);

printf("Y: %d  ", Y[0]);

//Sleep(250);

return true;

}

}

return false;

}

void CreatePath() {

Instructions.clear();

int TempX[100] = {'\0'};

int TempY[100] = {'\0'};

/* Copy Snake location */

for (int x = 0; x < 100; x++) {

TempX[x] = SnakeX[x];

TempY[x] = SnakeY[x];

}

int TempNum = 0;

while (TempX[0] != FoodX || TempY[0] != FoodY) {

if (FoodX > TempX[0]) {

TempX[0]++; Instructions.push_back(3);

while (Crash(TempX, TempY)) {

switch (TempNum) {

case 0: { TempX[0]++; } break;

case 1: { TempY[0]--; } break;

case 2: { TempX[0]++; } break;

case 3: { TempY[0]--; } break;

}

Instructions.back() = TempNum;

PlaceCursor(0, 35); printf("%d", TempNum);

TempNum++;

Sleep(250);

if (TempNum >= 4) { break; }

}

}

if (FoodX < TempX[0]) {

TempX[0]--; Instructions.push_back(2);

while (Crash(TempX, TempY)) {

switch (TempNum) {

case 0: { TempX[0]++; } break;

case 1: { TempY[0]--; } break;

case 2: { TempX[0]++; } break;

case 3: { TempY[0]--; } break;

}

Instructions.back() = TempNum;

PlaceCursor(0, 35); printf("%d", TempNum);

TempNum++;

Sleep(250);

if (TempNum >= 4) { break; }

}

}

if (FoodY > TempY[0]) {

TempY[0]++; Instructions.push_back(1);

while (Crash(TempX, TempY)) {

switch (TempNum) {

case 0: { TempX[0]++; } break;

case 1: { TempY[0]--; } break;

case 2: { TempX[0]++; } break;

case 3: { TempY[0]--; } break;

}

Instructions.back() = TempNum;

PlaceCursor(0, 35); printf("%d", TempNum);

TempNum++;

Sleep(250);

if (TempNum >= 4) { break; }

}

}

if (FoodY < TempY[0]) {

TempY[0]--; Instructions.push_back(0);

while (Crash(TempX, TempY)) {

switch (TempNum) {

case 0: { TempX[0]++; } break;

case 1: { TempY[0]--; } break;

case 2: { TempX[0]++; } break;

case 3: { TempY[0]--; } break;

}

Instructions.back() = TempNum;

PlaceCursor(0, 35); printf("%d", TempNum);

TempNum++;

Sleep(250);

if (TempNum >= 4) { break; }

}

}

TempNum = 0;

//Sleep(50);

//DrawSnake(RED, TempX, TempY);

}

SetColor(CYAN, BLACK);

PlaceCursor(25, 0);

printf("AI PATH COMPLETED");

Sleep(500);

PlaceCursor(25, 0);

printf("                 ");

return;

}

void DrawSnake(const int color, int Xz[], int Yz[]) {

SetColor(color, BLACK);

for (int x = 10; x > 0; x--) { Xz[x] = Xz[x - 1]; Yz[x] = Yz[x - 1]; }

for (int x = 10; x >= 0; x--) { PlaceCursor(Xz[x], Yz[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(Xz[0], Yz[0]); printf("@");

//PlaceCursor(Xz[10], Yz[10]); SetColor(BLACK, BLACK); printf(" ");

if (Xz[0] != Xz[10] || Yz[0] != Yz[10]) { PlaceCursor(Xz[10], Yz[10]); SetColor(BLACK, BLACK); printf(" "); }

return;

}

void RunPath(const int color) {

/* It'll crash sometimes if it randomly is empty */

if (!Instructions.empty()) {

SetColor(color, BLACK);

for (int x = 0; x < Instructions.size(); x++) {

switch (Instructions.at(x)) {

case 0: SnakeY[0]--; break;

case 1: SnakeY[0]++; break;

case 2: SnakeX[0]--; break;

case 3: SnakeX[0]++; break;

}

Sleep(50);

DrawSnake(color, SnakeX, SnakeY);

if (Crash(SnakeX, SnakeY)) { for (int x = 0; x < 100; x++) { SnakeX[x] = 5; SnakeY[x] = 5; } Instructions.clear(); }

}

}

return;

}

void NewRun() {

FoodX = rand() % 70 + 5;

FoodY = rand() % 25 + 5;

PlaceCursor(FoodX, FoodY);

SetColor(YELLOW, BLACK);

printf("x");

Instructions.clear();

return;

}

void SpawnFood(const int X, const int Y) {

if (SnakeX[0] == FoodX && SnakeY[0] == FoodY) {

FoodX = X;

FoodY = Y;

PlaceCursor(FoodX, FoodY);

SetColor(YELLOW, BLACK);

printf("x");

} else {

SetColor(CYAN, BLACK);

PlaceCursor(0, 0);

printf("SnakeX: %d  ", SnakeX[0]);

PlaceCursor(0, 1);

printf("SnakeY: %d  ", SnakeY[0]);

}

return;

}

int main() {

HANDLE OutHandle = GetStdHandle(STD_OUTPUT_HANDLE);

SetConsoleTitle("Snake - AI Module");

SMALL_RECT windowSize = {0, 0, 79, 49};

SetConsoleWindowInfo(OutHandle, TRUE, &windowSize);

ClearConsole(BLACK, BLACK);

RemoveCursor();

srand(time(NULL));

SnakeX[0] = 60;

SnakeY[0] = 25;

FoodX = 60;

FoodY = 25;

Instructions.clear();

while (true) {

SpawnFood(25, 25);

CreatePath();

RunPath(BLUE);

SpawnFood(25, 40);

CreatePath();

RunPath(BLUE);

SpawnFood(30, 20);

CreatePath();

RunPath(BLUE);

SpawnFood(25, 20);

CreatePath();

RunPath(BLUE);

SpawnFood(35, 20);

CreatePath();

RunPath(BLUE);

}

cin.get();

return 0;

}

Specifically what's confusing is: The Snake (when "thinking") does not follow the same "rules" as the Snake when it's really running. They follow two different paths, which is fucking whacko. The AI Snake will take an actual SHORTER and ANGLED path, when the real Snake is forced to take UP then RIGHT then UP then RIGHT (no angled movements). This time, I'm dead-positive there isn't a logical error, as you can clearly see the code, it uses the same DrawSnake function, and it uses the same vector for instructions.

Thanks for any help/advice (not that I'm expecting any anymore). :)

Snake AI not shown: Snake AI hidden (YouTube)
Snake AI shown: Snake AI (YouTube)

I'm pretty sure the error I'm looking for is located in (or has to do with): DrawSnake(const, int, int)!

The logical error has nothing to do with DrawSnake function.
I'm starting to think perhaps it's how I have the instructions set up...
Both the AI and the "real" Snake share the same instruction vector - perhaps that's it?
“You may be disappointed if you fail, but you are doomed if you don't try.”
Beverly Sills

### #22Reedbeta

DevMaster Staff

• Administrators
• 5307 posts
• LocationBellevue, WA

Posted 06 October 2009 - 10:42 PM

Here's a basic, sane introduction to A* that I found with five seconds of googling. It's not done 20,000 different ways; it's really quite a simple and specific algorithm. Of course this article discusses the algorithm at a high level, meaning it'll say things like "look for the lowest cost square on the open list" and it's your job to turn that into a for-loop iterating over an std::vector, or whatever data structure you happen to use.
reedbeta.com - developer blog, OpenGL demos, and other projects

### #23Hyper

Valued Member

• Members
• 195 posts

Posted 06 October 2009 - 11:16 PM

Reedbeta said:

Here's a basic, sane introduction to A* that I found with five seconds of googling. It's not done 20,000 different ways; it's really quite a simple and specific algorithm. Of course this article discusses the algorithm at a high level, meaning it'll say things like "look for the lowest cost square on the open list" and it's your job to turn that into a for-loop iterating over an std::vector, or whatever data structure you happen to use.

Seen it, read it, downloaded the examples months ago.
Made little to no sense to me.
“You may be disappointed if you fail, but you are doomed if you don't try.”
Beverly Sills

### #24fireside

Senior Member

• Members
• 1587 posts

Posted 07 October 2009 - 12:32 AM

Well, I would stick with the a* tutorial until it made sense. It's the one I used. The thing about it is that once you get it, you can re-use it for many situations where as the algorithm you are using only works for one specific purpose. The whole idea is to write as much generic code as possible. It might be all right to look at some of the ways he did things in the example, but it's usually best to write your own code. You should also be able to separate out the usable functions from the starter win32, etc functions. Start your own project and copy and paste them in.
Currently using Blender and Unity.

### #25Hyper

Valued Member

• Members
• 195 posts

Posted 07 October 2009 - 12:40 AM

fireside said:

Well, I would stick with the a* tutorial until it made sense. It's the one I used. The thing about it is that once you get it, you can re-use it for many situations where as the algorithm you are using only works for one specific purpose. The whole idea is to write as much generic code as possible. It might be all right to look at some of the ways he did things in the example, but it's usually best to write your own code. You should also be able to separate out the usable functions from the starter win32, etc functions.

That doesn't even apply to the game of Snake.
There's more than a single variable, a hell of a lot more than one.
“You may be disappointed if you fail, but you are doomed if you don't try.”
Beverly Sills

### #26fireside

Senior Member

• Members
• 1587 posts

Posted 07 October 2009 - 01:31 AM

It applies to finding the shortest path around objects, which applies to the game of snake. Making decisions as to the place to go is another separate problem which should have a separate function, or functions. What I would do would be to keep an array or map of the squares that the snake takes up plus the locations of fruits and walls, etc. and update it on every move that the snake makes. There are always many ways to solve the problem. There's nothing wrong with continuing with what you are doing but it's hardly fair to have someone else debug it for you. You've been doing it too long. Break the problem into smaller components and find ways to test and eliminate them until you find the logic error.
Currently using Blender and Unity.

### #27Reedbeta

DevMaster Staff

• Administrators
• 5307 posts
• LocationBellevue, WA

Posted 07 October 2009 - 02:50 AM

I'm not sure what you mean by "a single variable". The algorithm fundamentally finds a path from one point to another on the grid (or, more generally, any graph), avoiding obstacles. That sounds like exactly what's needed for a game of snake: the snake has to get to the food, avoiding obstacles. It's complicated somewhat by the snakes themselves being moving obstacles, but you should at least be able to get wall-avoidance working as a first step.

If there's more to that particular tutorial that doesn't make sense, feel free to post further questions about it.
reedbeta.com - developer blog, OpenGL demos, and other projects

### #28rouncer

Senior Member

• Members
• 2722 posts

Posted 07 October 2009 - 10:37 AM

A* will do snake.
you used to be able to fit a game on a disk, then you used to be able to fit a game on a cd, then you used to be able to fit a game on a dvd, now you can barely fit one on your harddrive.

### #29Hyper

Valued Member

• Members
• 195 posts

Posted 07 October 2009 - 11:30 AM

rouncer said:

A* will do snake.

Thank you very much, for the extremely helpful \$.02.
I think that donation might buy 1/1000th of a teddy-bear for an abused child.
“You may be disappointed if you fail, but you are doomed if you don't try.”
Beverly Sills

### #30fireside

Senior Member

• Members
• 1587 posts

Posted 07 October 2009 - 06:02 PM

I still appreciate what you are doing here compared to most people who approach programming. You've found out that AI is a pretty difficult subject and takes some thought. A lot of people have the idea they are going to pick an engine and somehow that's going to magically make a game for them. The two most common programming concepts for AI are finite state machines and path finding. That doesn't mean you need to look into them right now. Good luck with it and let us know if you find a solution. Looking at your code, I think breaking it into smaller functions might help. Whenever I have similar code written more than once, I know I should have broken it down further. I also make a small test program that only contains the area where the program is failing, most of the time only returning numbers rather than graphics.
Currently using Blender and Unity.

### #31Hyper

Valued Member

• Members
• 195 posts

Posted 07 October 2009 - 10:52 PM

fireside said:

I still appreciate what you are doing here compared to most people who approach programming. You've found out that AI is a pretty difficult subject and takes some thought. A lot of people have the idea they are going to pick an engine and somehow that's going to magically make a game for them. The two most common programming concepts for AI are finite state machines and path finding. That doesn't mean you need to look into them right now. Good luck with it and let us know if you find a solution. Looking at your code, I think breaking it into smaller functions might help. Whenever I have similar code written more than once, I know I should have broken it down further. I also make a small test program that only contains the area where the program is failing, most of the time only returning numbers rather than graphics.

I do that to - it's how I've come this far with the game of Snake as is.
It's also how I'm trying to "fix" (improve) the Snake's AI. I wrote it almost a year ago and it works fine. I've just always wanted something better than a "basic logic-system" or however Reedbeta said it.

I'll try to break it up into smaller functions today, but, I'm pretty sure there's just something wrong with how it's compiling, because I've proven that before (with the Sleep function) - with a sleep it does this "normal" predicted expected thing, without the sleep (which shouldn't matter), it changes the entire output. It's like having a cout vs. printf, there should be no real difference of how it runs (it should still display in the same place, the same letter/number/whatever).

I never thought AI was simple, and thank you for the actual encouragement.
Finite State Machines (or FSM for short) is something I've read before in the past two years, except the book was for creating maps using GMax, so it didn't do much other than show a potential "Game save" mechagnism, and something else. Wasn't there to teach you how to write FSM or use it (per se), anyways.

I'll try to figure something out. I keep resorting back to the non-pathed AI idea, where he just avoids as he goes, rather than creating an actual "path..." which is what I want but, you can't always get what you want, so, that's apart of life.

Thanks again. :)
“You may be disappointed if you fail, but you are doomed if you don't try.”
Beverly Sills

### #32fireside

Senior Member

• Members
• 1587 posts

Posted 08 October 2009 - 01:32 AM

If a Sleep function helps, which I would assume is a game pause, then you may be threading some place. To keep from threading, I usually make sure a function returns a value and then use the value in the main loop. A void function that changes a global or something could turn into an unfinished thread where the loop has actually started around another time. Sometimes that doesn't hurt, sometimes it does. If you need sequential logic then you have to be careful not to thread. It's a common cause for glitchy behavior, at least for me. In general, I try to avoid it as much as possible. The game loop should have everything cleaned up and taken care of each loop.
Currently using Blender and Unity.

### #33Hyper

Valued Member

• Members
• 195 posts

Posted 08 October 2009 - 04:53 AM

fireside said:

If a Sleep function helps, which I would assume is a game pause, then you may be threading some place. To keep from threading, I usually make sure a function returns a value and then use the value in the main loop. A void function that changes a global or something could turn into an unfinished thread where the loop has actually started around another time. Sometimes that doesn't hurt, sometimes it does. If you need sequential logic then you have to be careful not to thread. It's a common cause for glitchy behavior, at least for me. In general, I try to avoid it as much as possible. The game loop should have everything cleaned up and taken care of each loop.

Sleep() is apart of the Win32 API I believe.
Defined in Windows.h, here's an MSDN link: http://msdn.microsof...298(VS.85).aspx
“You may be disappointed if you fail, but you are doomed if you don't try.”
Beverly Sills

### #34fireside

Senior Member

• Members
• 1587 posts

Posted 08 October 2009 - 05:34 AM

Yeah, so what you are doing is suspending the main game loop thread for a duration, which could give another thread enough time to finish. I'm not saying that's it, but it might be something to check out.
Currently using Blender and Unity.

### #35Hyper

Valued Member

• Members
• 195 posts

Posted 08 October 2009 - 06:09 AM

fireside said:

Yeah, so what you are doing is suspending the main game loop thread for a duration, which could give another thread enough time to finish. I'm not saying that's it, but it might be something to check out.

I thought a program was created as a single thread unless otherwise specifically explicitly stated.
EDIT: This is what I have so far (I started from the ground up - the logic error is in "CreatePath" somewhere):

#include <iostream>

#include <vector>

#include <TextControl.h>

using namespace std;

#define UP 0

#define DOWN 1

#define LEFT 2

#define RIGHT 3

int FoodX = 0;

int FoodY = 0;

void CreatePath(int*, int*); // THE LOGIC ERROR LIES HERE

void RunPath(const int, int*, int*);

void SpawnFood(const int, const int);

void DrawSnake(const int, int*, int*, int);

bool Crash(const int*, const int*, const int);

vector<int>Instructions;

void CreatePath(int *SnakeX, int *SnakeY) {

Instructions.clear();

int TempX[100] = {'\0'};

int TempY[100] = {'\0'};

/* Copy Snake location */

for (int x = 0; x < 10; x++) {

TempX[x] = SnakeX[x];

TempY[x] = SnakeY[x];

}

int TempNum = 0;

int Cycles = 0;

while (TempX[0] != FoodX || TempY[0] != FoodY) {

if (FoodX > TempX[0]) {

TempX[0]++; Instructions.push_back(RIGHT);

if (Crash(TempX, TempY, 10)) { TempX[0]--; }

while (Crash(SnakeX, SnakeY, 10)) {

srand(time(NULL));

Instructions.back() = rand() % 4;

}

}

if (FoodX < TempX[0]) {

TempX[0]--; Instructions.push_back(LEFT);

if (Crash(TempX, TempY, 10)) { TempX[0]++; }

while (Crash(SnakeX, SnakeY, 10)) {

srand(time(NULL));

Instructions.back() = rand() % 4;

}

}

if (FoodY > TempY[0]) {

TempY[0]++; Instructions.push_back(DOWN);

if (Crash(TempX, TempY, 10)) { TempY[0]--; }

while (Crash(SnakeX, SnakeY, 10)) {

srand(time(NULL));

Instructions.back() = rand() % 4;

}

}

if (FoodY < TempY[0]) {

TempY[0]--; Instructions.push_back(UP);

if (Crash(TempX, TempY, 10)) { TempY[0]++; }

while (Crash(SnakeX, SnakeY, 10)) {

srand(time(NULL));

Instructions.back() = rand() % 4;

}

}

TempNum = 0;

//Sleep(50);

//DrawSnake(RED, TempX, TempY, 10);

if (TempX[0] == FoodX && TempY[0] == FoodY) { break; }

Cycles++;

PlaceCursor(0, 0);

SetColor(GREEN, BLACK);

printf("%d  ", Cycles);

}

SetColor(CYAN, BLACK);

PlaceCursor(25, 0);

printf("AI PATH COMPLETED");

Sleep(500);

PlaceCursor(25, 0);

printf("                 ");

return;

}

bool Crash(const int *SnakeX, const int *SnakeY, const int SnakeSize) {

for (int x = SnakeSize; x > 1; x--) {

if (SnakeX[0] == SnakeX[x] && SnakeY[0] == SnakeY[x]) {

PlaceCursor(SnakeX[x], SnakeY[x]);

SetColor(RED, BLACK);

printf("X");

PlaceCursor(0, 45);

SetColor(RED, BLACK);

printf("X: %d  ", SnakeX[0]);

PlaceCursor(0, 46);

SetColor(RED, BLACK);

printf("Y: %d  ", SnakeY[0]);

Sleep(250);

return true;

}

}

return false;

}

void RunPath(const int color, int *SnakeX, int *SnakeY, vector<int> Instructions) {

/* It'll crash sometimes if it randomly is empty */

if (!Instructions.empty()) {

SetColor(color, BLACK);

for (int x = 0; x < Instructions.size(); x++) {

switch (Instructions.at(x)) {

case 0: SnakeY[0]--; break; // UP

case 1: SnakeY[0]++; break; // DOWN

case 2: SnakeX[0]--; break; // LEFT

case 3: SnakeX[0]++; break; // RIGHT

}

Sleep(50);

DrawSnake(color, SnakeX, SnakeY, 10);

if (Crash(SnakeX, SnakeY, 10)) { Sleep(250); }

}

}

return;

}

void DrawSnake(const int color, int *SnakeX, int *SnakeY, int SnakeSize) {

SetColor(color, BLACK);

for (int x = SnakeSize; x > 0; x--) { SnakeX[x] = SnakeX[x - 1]; SnakeY[x] = SnakeY[x - 1]; }

for (int x = SnakeSize; x > 0; x--) { PlaceCursor(SnakeX[x], SnakeY[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(SnakeX[0], SnakeY[0]); printf("@");

// If his head, is NOT on his tail (specifically his "tail") - draw a black square

if (SnakeX[0] != SnakeX[SnakeSize] || SnakeY[0] != SnakeY[SnakeSize]) {

PlaceCursor(SnakeX[SnakeSize], SnakeY[SnakeSize]);

SetColor(BLACK, BLACK);

printf(" ");

}

PlaceCursor(0, 48);

SetColor(RED, BLACK);

printf("Snake's current size: ");

SetColor(WHITE, BLACK);

printf("%d", SnakeSize);

PlaceCursor(0, 49);

SetColor(WHITE, BLACK);

for (int x = SnakeSize; x > 0; x--) {

printf("[");

SetColor(CYAN, BLACK);

printf("%d", SnakeX[x]);

SetColor(WHITE, BLACK);

printf(",");

SetColor(GREEN, BLACK);

printf("%d", SnakeY[x]);

SetColor(WHITE, BLACK);

printf("] ");

}

return;

}

void SpawnFood(const int X, const int Y) {

FoodX = X;

FoodY = Y;

PlaceCursor(FoodX, FoodY);

SetColor(YELLOW, BLACK);

printf("x");

return;

}

int main() {

HANDLE OutHandle = GetStdHandle(STD_OUTPUT_HANDLE);

SetConsoleTitle("Snake - AI Module");

SMALL_RECT windowSize = {0, 0, 79, 49};

SetConsoleWindowInfo(OutHandle, TRUE, &windowSize);

ClearConsole(BLACK, BLACK);

RemoveCursor();

srand(time(NULL));

int SnakeX[100] = {'\0'};

int SnakeY[100] = {'\0'};

for (int x = 0; x < 10; x++) {

SnakeX[x] = 35 + x;

SnakeY[x] = 25;

}

SpawnFood(15, 15);

CreatePath(SnakeX, SnakeY);

RunPath(BLUE, SnakeX, SnakeY, Instructions);

SpawnFood(45, 15);

CreatePath(SnakeX, SnakeY);

RunPath(BLUE, SnakeX, SnakeY, Instructions);

cin.get();

return 0;

}

This is the output of the program above (yay for YouTube):
“You may be disappointed if you fail, but you are doomed if you don't try.”
Beverly Sills

### #36Reedbeta

DevMaster Staff

• Administrators
• 5307 posts
• LocationBellevue, WA

Posted 08 October 2009 - 06:35 AM

It is. Are you using any libraries? It's possible that something else is creating a thread without your knowledge, although that would be rather strange.
reedbeta.com - developer blog, OpenGL demos, and other projects

### #37Hyper

Valued Member

• Members
• 195 posts

Posted 08 October 2009 - 06:44 AM

Reedbeta said:

It is. Are you using any libraries? It's possible that something else is creating a thread without your knowledge, although that would be rather strange.

I'm using:
- iostream
- windows
- vector
- TextControl (which contains the "wrapper" functions (PlaceCursor/SetColor))

All I know is I've narrowed it down to CreatePath() function thus far, so I'm happy. :)
I just found the logic error that's eluded me until now (and you won't believe what it was)!

Question: Where was the logic error at?
Answer: It was in the CreatePath (yay for being right!).

        if (FoodX > TempX[0]) {

Instructions.push_back(3);

TempX[0]++;

}

...

Sleep(50);

DrawSnake(RED, TempX, TempY, 10);

SHOULD HAVE BEEN MORE LIKE THIS:

        if (FoodX > TempX[0]) {

Instructions.push_back(3);

TempX[0]++;

Sleep(250);

DrawSnake(RED, TempX, TempY, 10);

}

...

//Sleep(50);

//DrawSnake(RED, TempX, TempY, 10);

I'm still not entirely sure (even though I've written all of this over the course of this/last year), but somehow, it was making the AI Snake go angled.
Ironically, the angled effect is closer (less moves), but illegal by Snakes rules of no angular movements (rather, UP then RIGHT then UP then RIGHT).

Now that that's out of the way, I can try and figure out how to "fix" the AI the rest of the way, and make him actually avoid his own body.
I'm assuming those few logic flaws had something to do with my hindered ability of making this all work and come together.

Video (YouTube of course) of the Snake AI working as it should (NOT angled movements):

Yet another edit (second edit now, first was: Last edited by Hyper : Today at 02:35 AM)...! As I've come to my success (partially). :)
Here's the relevant code (the Snake ACTUALLY avoids the body by going UP x2 squares then down like stairs):

    while (TempX[0] != FoodX || TempY[0] != FoodY) {

if (FoodX > TempX[0]) {

Instructions.push_back(RIGHT);

TempX[0]++;

if (Crash(TempX, TempY, 10)) {

TempX[0]--;

Instructions.back() = UP;

TempY[0]--;

}

for (int x = 10; x > 0; x--) { TempX[x] = TempX[x - 1]; TempY[x] = TempY[x - 1]; }

if (DisplayAISnake) {

Sleep(AIWait);

SetColor(RED, BLACK);

for (int x = 10; x > 0; x--) { PlaceCursor(TempX[x], TempY[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(TempX[0], TempY[0]); printf("@");

// If his head, is NOT on his tail (specifically his "tail") - draw a black square

if (TempX[0] != TempX[10] || TempY[0] != TempY[10]) {

PlaceCursor(TempX[10], TempY[10]);

SetColor(BLACK, BLACK);

printf(" ");

}

}

}

if (FoodX < TempX[0]) {

Instructions.push_back(LEFT);

TempX[0]--;

if (Crash(TempX, TempY, 10)) { TempX[0]++; }

for (int x = 10; x > 0; x--) { TempX[x] = TempX[x - 1]; TempY[x] = TempY[x - 1]; }

if (DisplayAISnake) {

Sleep(AIWait);

SetColor(RED, BLACK);

for (int x = 10; x > 0; x--) { PlaceCursor(TempX[x], TempY[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(TempX[0], TempY[0]); printf("@");

// If his head, is NOT on his tail (specifically his "tail") - draw a black square

if (TempX[0] != TempX[10] || TempY[0] != TempY[10]) {

PlaceCursor(TempX[10], TempY[10]);

SetColor(BLACK, BLACK);

printf(" ");

}

}

}

if (FoodY > TempY[0]) {

Instructions.push_back(DOWN);

TempY[0]++;

if (Crash(TempX, TempY, 10)) {

TempY[0]--;

Instructions.back() = UP;

TempY[0]--;

}

for (int x = 10; x > 0; x--) { TempX[x] = TempX[x - 1]; TempY[x] = TempY[x - 1]; }

if (DisplayAISnake) {

Sleep(AIWait);

SetColor(RED, BLACK);

for (int x = 10; x > 0; x--) { PlaceCursor(TempX[x], TempY[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(TempX[0], TempY[0]); printf("@");

// If his head, is NOT on his tail (specifically his "tail") - draw a black square

if (TempX[0] != TempX[10] || TempY[0] != TempY[10]) {

PlaceCursor(TempX[10], TempY[10]);

SetColor(BLACK, BLACK);

printf(" ");

}

}

}

if (FoodY < TempY[0]) {

Instructions.push_back(UP);

TempY[0]--;

if (Crash(TempX, TempY, 10)) { TempY[0]++; }

for (int x = 10; x > 0; x--) { TempX[x] = TempX[x - 1]; TempY[x] = TempY[x - 1]; }

if (DisplayAISnake) {

Sleep(AIWait);

SetColor(RED, BLACK);

for (int x = 10; x > 0; x--) { PlaceCursor(TempX[x], TempY[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(TempX[0], TempY[0]); printf("@");

// If his head, is NOT on his tail (specifically his "tail") - draw a black square

if (TempX[0] != TempX[10] || TempY[0] != TempY[10]) {

PlaceCursor(TempX[10], TempY[10]);

SetColor(BLACK, BLACK);

printf(" ");

}

}

}

NewMove = 4;

}

Oh my God, I'm so excited and smiling non-stop! :D

Oh also, I could use DrawSnake to update the Snake (makes it "shorter") but! It gives me less control.
If I use that single line call, I'm able to save up on whitespace, sure, but I'm *forced* to display the AI Snake.
I'm required to have that there, because if it doesn't move the body with the instructions, it can't check for crashes...
SO! Yeah. Now I have to live with 8 * 4 (32) line's give or take, but it's totally worth it I think.

I hate having 5,000 lines. I try to conserve on the whitespace, but oh well. Life goes on. :D

Here's Snake avoiding his own body (crash-course!):
Here's the same Snake on a longer video: http://www.youtube.com/watch?v=vMGwzDAuHr4

Last edit of the night (previous edit: Today at 04:11 AM): I'm going to bed.
This is what I have (I think the AI's messing up at some point, it suicides yet continues so the REAL Snake crashes):
#include <iostream>

#include <vector>

#include <TextControl.h>

using namespace std;

#define UP 0

#define DOWN 1

#define LEFT 2

#define RIGHT 3

int SimulatedCrashes = 0;

int TotalCrashes = 0;

int FoodX = 0;

int FoodY = 0;

bool DisplayAISnake = true;

int AIWait = 50;

void CreatePath(int*, int*);

void RunPath(const int, int*, int*);

void SpawnFood(const int, const int);

void DrawSnake(const int, int*, int*, int);

bool Crash(const int*, const int*, const int);

vector<int>Instructions;

void CreatePath(int *SnakeX, int *SnakeY) {

Sleep(50);

PlaceCursor(0, 46);

SetColor(YELLOW, BLACK);

cout << "Instruction size:   ";

Instructions.clear();

int TempX[100] = {'\0'};

int TempY[100] = {'\0'};

/* Copy Snake location */

for (int x = 0; x < 10; x++) {

TempX[x] = SnakeX[x];

TempY[x] = SnakeY[x];

}

int NewMove = 0;

while (TempX[0] != FoodX || TempY[0] != FoodY) {

if (FoodX > TempX[0]) {

Instructions.push_back(RIGHT);

if (!Crash(TempX, TempY, 10)) { TempX[0]++; }

while (Crash(TempX, TempY, 10)) {

NewMove = rand() % 4;

switch (NewMove) {

case UP: TempY[0]--; Instructions.back() = UP; break;

case DOWN: TempY[0]++; Instructions.back() = DOWN; break;

case LEFT: TempX[0]--; Instructions.back() = LEFT; break;

case RIGHT: TempX[0]++; Instructions.back() = RIGHT; break;

}

/* CRASHED STILL - REVERSE */

if (Crash(TempX, TempY, 10)) {

switch (NewMove) {

case UP: TempY[0]++; break;

case DOWN: TempY[0]--; break;

case LEFT: TempX[0]++; break;

case RIGHT: TempX[0]--; break;

}

}

}

PlaceCursor(25, 0);

SetColor(GREEN, BLACK);

printf("CRASH: Right");

Sleep(500);

printf("            ");

for (int x = 10; x > 0; x--) { TempX[x] = TempX[x - 1]; TempY[x] = TempY[x - 1]; }

if (DisplayAISnake) {

Sleep(AIWait);

SetColor(RED, BLACK);

for (int x = 10; x > 0; x--) { PlaceCursor(TempX[x], TempY[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(TempX[0], TempY[0]); printf("@");

// If his head, is NOT on his tail (specifically his "tail") - draw a black square

if (TempX[0] != TempX[10] || TempY[0] != TempY[10]) {

PlaceCursor(TempX[10], TempY[10]);

SetColor(BLACK, BLACK);

printf(" ");

}

}

}

if (FoodX < TempX[0]) {

Instructions.push_back(LEFT);

if (!Crash(TempX, TempY, 10)) { TempX[0]--; }

while (Crash(TempX, TempY, 10)) {

NewMove = rand() % 4;

switch (NewMove) {

case UP: TempY[0]--; Instructions.back() = UP; break;

case DOWN: TempY[0]++; Instructions.back() = DOWN; break;

case LEFT: TempX[0]--; Instructions.back() = LEFT; break;

case RIGHT: TempX[0]++; Instructions.back() = RIGHT; break;

}

/* CRASHED STILL - REVERSE */

if (Crash(TempX, TempY, 10)) {

switch (NewMove) {

case UP: TempY[0]++; break;

case DOWN: TempY[0]--; break;

case LEFT: TempX[0]++; break;

case RIGHT: TempX[0]--; break;

}

}

}

PlaceCursor(25, 0);

SetColor(GREEN, BLACK);

printf("CRASH: Left ");

Sleep(500);

printf("            ");

for (int x = 10; x > 0; x--) { TempX[x] = TempX[x - 1]; TempY[x] = TempY[x - 1]; }

if (DisplayAISnake) {

Sleep(AIWait);

SetColor(RED, BLACK);

for (int x = 10; x > 0; x--) { PlaceCursor(TempX[x], TempY[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(TempX[0], TempY[0]); printf("@");

// If his head, is NOT on his tail (specifically his "tail") - draw a black square

if (TempX[0] != TempX[10] || TempY[0] != TempY[10]) {

PlaceCursor(TempX[10], TempY[10]);

SetColor(BLACK, BLACK);

printf(" ");

}

}

}

if (FoodY > TempY[0]) {

Instructions.push_back(DOWN);

if (!Crash(TempX, TempY, 10)) { TempY[0]++; }

while (Crash(TempX, TempY, 10)) {

NewMove = rand() % 4;

switch (NewMove) {

case UP: TempY[0]--; Instructions.back() = UP; break;

case DOWN: TempY[0]++; Instructions.back() = DOWN; break;

case LEFT: TempX[0]--; Instructions.back() = LEFT; break;

case RIGHT: TempX[0]++; Instructions.back() = RIGHT; break;

}

/* CRASHED STILL - REVERSE */

if (Crash(TempX, TempY, 10)) {

switch (NewMove) {

case UP: TempY[0]++; break;

case DOWN: TempY[0]--; break;

case LEFT: TempX[0]++; break;

case RIGHT: TempX[0]--; break;

}

}

}

PlaceCursor(25, 0);

SetColor(GREEN, BLACK);

printf("CRASH: Up   ");

Sleep(500);

printf("            ");

for (int x = 10; x > 0; x--) { TempX[x] = TempX[x - 1]; TempY[x] = TempY[x - 1]; }

if (DisplayAISnake) {

Sleep(AIWait);

SetColor(RED, BLACK);

for (int x = 10; x > 0; x--) { PlaceCursor(TempX[x], TempY[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(TempX[0], TempY[0]); printf("@");

// If his head, is NOT on his tail (specifically his "tail") - draw a black square

if (TempX[0] != TempX[10] || TempY[0] != TempY[10]) {

PlaceCursor(TempX[10], TempY[10]);

SetColor(BLACK, BLACK);

printf(" ");

}

}

}

if (FoodY < TempY[0]) {

Instructions.push_back(UP);

if (!Crash(TempX, TempY, 10)) { TempY[0]--; }

while (Crash(TempX, TempY, 10)) {

NewMove = rand() % 4;

switch (NewMove) {

case UP: TempY[0]--; Instructions.back() = UP; break;

case DOWN: TempY[0]++; Instructions.back() = DOWN; break;

case LEFT: TempX[0]--; Instructions.back() = LEFT; break;

case RIGHT: TempX[0]++; Instructions.back() = RIGHT; break;

}

/* CRASHED STILL - REVERSE */

if (Crash(TempX, TempY, 10)) {

switch (NewMove) {

case UP: TempY[0]++; break;

case DOWN: TempY[0]--; break;

case LEFT: TempX[0]++; break;

case RIGHT: TempX[0]--; break;

}

}

}

PlaceCursor(25, 0);

SetColor(GREEN, BLACK);

printf("CRASH: Down ");

Sleep(500);

printf("            ");

for (int x = 10; x > 0; x--) { TempX[x] = TempX[x - 1]; TempY[x] = TempY[x - 1]; }

if (DisplayAISnake) {

Sleep(AIWait);

SetColor(RED, BLACK);

for (int x = 10; x > 0; x--) { PlaceCursor(TempX[x], TempY[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(TempX[0], TempY[0]); printf("@");

// If his head, is NOT on his tail (specifically his "tail") - draw a black square

if (TempX[0] != TempX[10] || TempY[0] != TempY[10]) {

PlaceCursor(TempX[10], TempY[10]);

SetColor(BLACK, BLACK);

printf(" ");

}

}

}

PlaceCursor(0, 46);

SetColor(YELLOW, BLACK);

cout << "Instruction size: " << Instructions.size();

NewMove = 4;

}

/*

SetColor(CYAN, BLACK);

PlaceCursor(25, 0);

printf("AI PATH COMPLETED");

Sleep(500);

PlaceCursor(25, 0);

printf("                 ");

*/

return;

}

bool Crash(const int *SnakeX, const int *SnakeY, const int SnakeSize) {

for (int x = SnakeSize; x > 1; x--) {

if (SnakeX[0] == SnakeX[x] && SnakeY[0] == SnakeY[x]) {

SimulatedCrashes++;

PlaceCursor(0, 0);

SetColor(RED, BLACK);

printf("%d", SimulatedCrashes);

/*

PlaceCursor(SnakeX[x], SnakeY[x]);

SetColor(RED, BLACK);

printf("X");

PlaceCursor(0, 45);

SetColor(RED, BLACK);

printf("X: %d  ", SnakeX[0]);

PlaceCursor(0, 46);

SetColor(RED, BLACK);

printf("Y: %d  ", SnakeY[0]);

Sleep(50);

*/

return true;

}

}

return false;

}

void RunPath(const int color, int *SnakeX, int *SnakeY, vector<int> Instructions) {

/* It'll crash sometimes if it randomly is empty */

if (!Instructions.empty()) {

SetColor(color, BLACK);

for (int x = 0; x < Instructions.size(); x++) {

switch (Instructions.at(x)) {

case UP: SnakeY[0]--; break;

case DOWN: SnakeY[0]++; break;

case LEFT: SnakeX[0]--; break;

case RIGHT: SnakeX[0]++; break;

}

//Sleep(50);

DrawSnake(color, SnakeX, SnakeY, 10);

if (Crash(SnakeX, SnakeY, 10)) { TotalCrashes++; PlaceCursor(0, 1); SetColor(GREEN, BLACK); printf("%d", TotalCrashes); Sleep(250); }

}

}

return;

}

void DrawSnake(const int color, int *SnakeX, int *SnakeY, int SnakeSize) {

SetColor(color, BLACK);

for (int x = SnakeSize; x > 0; x--) { SnakeX[x] = SnakeX[x - 1]; SnakeY[x] = SnakeY[x - 1]; }

for (int x = SnakeSize; x > 0; x--) { PlaceCursor(SnakeX[x], SnakeY[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(SnakeX[0], SnakeY[0]); printf("@");

// If his head, is NOT on his tail (specifically his "tail") - draw a black square

if (SnakeX[0] != SnakeX[SnakeSize] || SnakeY[0] != SnakeY[SnakeSize]) {

PlaceCursor(SnakeX[SnakeSize], SnakeY[SnakeSize]);

SetColor(BLACK, BLACK);

printf(" ");

}

PlaceCursor(0, 47);

SetColor(RED, BLACK);

printf("Snake's current size: ");

SetColor(WHITE, BLACK);

printf("%d", SnakeSize);

PlaceCursor(0, 48);

SetColor(WHITE, BLACK);

for (int x = SnakeSize; x > 0; x--) {

printf("[");

SetColor(CYAN, BLACK);

printf("%d", SnakeX[x]);

SetColor(WHITE, BLACK);

printf(",");

SetColor(GREEN, BLACK);

printf("%d", SnakeY[x]);

SetColor(WHITE, BLACK);

printf("] ");

}

return;

}

void SpawnFood(const int X, const int Y) {

FoodX = X;

FoodY = Y;

PlaceCursor(FoodX, FoodY);

SetColor(YELLOW, BLACK);

printf("x");

return;

}

void NewRun() {

FoodX = rand() % 70 + 5;

FoodY = rand() % 25 + 5;

PlaceCursor(FoodX, FoodY);

SetColor(YELLOW, BLACK);

printf("x");

return;

}

int main() {

HANDLE OutHandle = GetStdHandle(STD_OUTPUT_HANDLE);

SetConsoleTitle("Snake - AI Module");

SMALL_RECT windowSize = {0, 0, 79, 49};

SetConsoleWindowInfo(OutHandle, TRUE, &windowSize);

ClearConsole(BLACK, BLACK);

RemoveCursor();

srand(time(NULL));

int SnakeX[100] = {'\0'};

int SnakeY[100] = {'\0'};

/* Spawn point for the Snake */

SnakeX[0] = 35;

SnakeY[0] = 25;

while (true) {

//SpawnFood(15, 15);

NewRun();

CreatePath(SnakeX, SnakeY);

RunPath(BLUE, SnakeX, SnakeY, Instructions);

}

cin.get();

return 0;

}

Night y'all! Thanks again for the help (and encouragement). <3
“You may be disappointed if you fail, but you are doomed if you don't try.”
Beverly Sills

### #38fireside

Senior Member

• Members
• 1587 posts

Posted 08 October 2009 - 11:19 AM

Quote

It is. Are you using any libraries? It's possible that something else is creating a thread without your knowledge, although that would be rather strange.

Oh really? I had an engine guy specifically warn me about it, but it was in java and an applet. I think he may have been using more than one thread in his engine, though. Beats me. I still feel a lot safer getting return values from functions. I screw up a lot and I hate debugging. I think one example he gave was acting on a listener rather than throwing a variable flag in the main loop and acting on it there. I think it makes sense because you're not actually calling the listener in the main loop. Really, though, just for my own peace of mind, I'd rather think about doing things in one main loop. Also, using as many static variables as possible so only one thing is going on. Maybe I'm just being paranoid. One thing for sure is I'm not an uber programmer. That's why I prefer managed languages. I'm probably the last person who should be giving coding advice. I do manage to get things working, though. That's why I like OO. I can keep expanding on things that have already been adequately tested. Your code, Hyper, gives me a headache looking at it because it's not separated into objects, etc. Personal preference, I guess. Glad you got it working.
Currently using Blender and Unity.

### #39Hyper

Valued Member

• Members
• 195 posts

Posted 08 October 2009 - 08:18 PM

fireside said:

Oh really? I had an engine guy specifically warn me about it, but it was in java and an applet. I think he may have been using more than one thread in his engine, though. Beats me. I still feel a lot safer getting return values from functions. I screw up a lot and I hate debugging. I think one example he gave was acting on a listener rather than throwing a variable flag in the main loop and acting on it there. I think it makes sense because you're not actually calling the listener in the main loop. Really, though, just for my own peace of mind, I'd rather think about doing things in one main loop. Also, using as many static variables as possible so only one thing is going on. Maybe I'm just being paranoid. One thing for sure is I'm not an uber programmer. That's why I prefer managed languages. I'm probably the last person who should be giving coding advice. I do manage to get things working, though. That's why I like OO. I can keep expanding on things that have already been adequately tested. Your code, Hyper, gives me a headache looking at it because it's not separated into objects, etc. Personal preference, I guess. Glad you got it working.

This isn't Java, there isn't a "listener," and everything is started in the main function. It branches out. Basically, here's what it does:
- main function
- Spawn food (we'll say 15, 15)
- Create a path from Snake's current position (we'll say 65, 15)
- Run that path (run through the instructions of the vector)
- Repeat

My code isn't working the way I'd like it to, there's still some flaws; But these are no longer logic flaws causing massive arbitrary behavoir, now it's boiled down to me actually making the Snake actively avoid his own body while creating the instruction list (the "instruction list" is a vector of integers).

This is what I currently have (and it seems to be faulty at some times):
if (FoodX > TempX[0]) {

Instructions.push_back(RIGHT);

if (!Crash(TempX, TempY)) { TempX[0]++; }

while (Crash(TempX, TempY)) {

NewMove = rand() % 4;

switch (NewMove) {

case UP: TempY[0]--; Instructions.back() = UP; break;

case DOWN: TempY[0]++; Instructions.back() = DOWN; break;

case LEFT: TempX[0]--; Instructions.back() = LEFT; break;

case RIGHT: TempX[0]++; Instructions.back() = RIGHT; break;

}

/* CRASHED STILL - REVERSE */

if (Crash(TempX, TempY)) {

switch (NewMove) {

case UP: TempY[0]++; break;

case DOWN: TempY[0]--; break;

case LEFT: TempX[0]++; break;

case RIGHT: TempX[0]--; break;

}

}

}

for (int x = SnakeSize; x > 0; x--) { TempX[x] = TempX[x - 1]; TempY[x] = TempY[x - 1]; }

if (DisplayAISnake) {

Sleep(AIWait);

SetColor(RED, BLACK);

for (int x = SnakeSize; x > 0; x--) { PlaceCursor(TempX[x], TempY[x]); printf("*"); }

SetColor(GREEN, BLACK);

PlaceCursor(TempX[0], TempY[0]); printf("@");

// If his head, is NOT on his tail (specifically his "tail") - draw a black square

if (TempX[0] != TempX[SnakeSize] || TempY[0] != TempY[SnakeSize]) {

PlaceCursor(TempX[SnakeSize], TempY[SnakeSize]);

SetColor(BLACK, BLACK);

printf(" ");

}

}

}

The AI Snake will sometimes arbitrarly crash into himself when there's clearly an open spot, then (and this is what's odd), he'll CONTINUE to move even though it's a crash, and he KNOWS it's a crash. Here's a video (I'm so abusing YouTube) demonstrating what I mean, and I'll even put an annotation or whatever to point out when it occurs, because I don't feel like freezing Snake:

Thanks for the feedback (I hope).

EDIT: OK, there's something extremely flawed with how I have this setup using the randomized movements... I'm going to look back at my old old code from 2008 now, also... With the following code, he fails utterly. He simulates 40+ crashes, and has 1-2 REAL crashes (blue Snake), and even then, he somehow "misses" the food but "hits" it in his mind.

//while (true) {

SpawnFood(15, 15);

//NewRun();

CreatePath(SnakeX, SnakeY);

RunPath(BLUE, SnakeX, SnakeY, Instructions);

SpawnFood(65, 15);

CreatePath(SnakeX, SnakeY);

RunPath(BLUE, SnakeX, SnakeY, Instructions);

//}

“You may be disappointed if you fail, but you are doomed if you don't try.”
Beverly Sills

### #40fireside

Senior Member

• Members
• 1587 posts

Posted 08 October 2009 - 09:55 PM

Something you might want to think about for future projects is a path class. That's what I did, anyway. I instantiate it with the width and the height and then fill in an array with positions of walls, etc, and have a findPath function in it which returns an array of squares/nodes whatever.
Currently using Blender and Unity.

#### 1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users