0
101 Mar 14, 2009 at 23:27

This is the most basic (pure logic of just X vs. Y coordinates) AI I created on a whim for Snake:

void CREATE_SNAKE::AI() {

if (Food[1] > Vertical[0]) { if (LastMove != CONTROL[0]) { LastMove = CONTROL[1]; } else { LastMove = CONTROL[2]; } } else
if (Food[1] < Vertical[0]) { if (LastMove != CONTROL[1]) { LastMove = CONTROL[0]; } else { LastMove = CONTROL[2]; } } else
if (Food[0] > Horizontal[0]) { if (LastMove != CONTROL[2]) { LastMove = CONTROL[3]; } else { LastMove = CONTROL[0]; } } else
if (Food[0] < Horizontal[0]) { if (LastMove != CONTROL[3]) { LastMove = CONTROL[2]; } else { LastMove = CONTROL[0]; } }

return;
}


Is it considered AI? Also, I have a book on AI (from 2001 I believe, “AI Techniques for Game Programming”), I’ve never really read it or put that much time/thought into it because I’ve been rather busy with school (was at the time), anyhow.

It has the typical, “Help Bob Through a Maze.” Would that be more probable than what I have now? More or less, I’m clueless when it comes to AI and am looking for opinions on what to do. :)

Thanks!

#### 43 Replies

0
139 Mar 14, 2009 at 23:58

Sure. What you’ve created there is obviously very basic, but it’s AI. To be specific, it’s a rule-based system, because it consists of a set of rules that say “under these conditions, do this”. There are many much larger research and industrial AI systems that are also rule-based, though with thousands or tens of thousands of rules.

Rule-based systems have the advantage that their behavior is more precisely controllable than with other approaches like fuzzy logic or neural networks; on the other hand, they are utterly unable to generalize to situations they haven’t been designed for, and you need someone to build all the individual rules.

0
101 Mar 15, 2009 at 00:06

The main issue with the current AI (rule-based system) is: The Snake has a tendancy to crash into himself, like so:

How exactly would I go about having him plot (ahead of time) a “safe” path to the food where he doesn’t hit himself (or a very low percentage of hitting himself so he isn’t undefeatable)?

0
139 Mar 15, 2009 at 00:16

For that you probably want a path-finding algorithm such as A* (pronounced A-star). You can treat the squares occupied by the snake as impassible. Unfortunately, that “AI Techniques for Game Programming” book doesn’t seem to have anything about pathfinding in it (a very curious omission…) but there is a lot of information about it on the Web.

0
101 Mar 15, 2009 at 00:38

That book does have something relating to “Pathfinding,” but I think it was on the low standard/”quality” end of the scale (not because it’s old but!) because it was a very simple example of how you could do it.

“C:\AI Techniques\example_code\Chapter03\Pathfinder” - Help Bob Through a Maze

However, it’s not applicable to my problem (not that I’m aware of) because how I have the Snake game made.

const int CBobsMap::map[MAP_HEIGHT][MAP_WIDTH] =
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1,
8, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1,
1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1,
1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1,
1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 5,
1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};


Is from one of the source-code files from the example. The Snake map isn’t an array, the Snake itself is an array that travels about, and comparisons determine whether or not his array occupied the same space or if he passed out of bounds (same goes with hitting other Snakes).

I’m aware this is the web, it’s why I’m asking here. :p

0
139 Mar 15, 2009 at 01:41

I’m not sure what you mean about the map not being an array. From the code sample you posted, it sure looks like an array to me. :)

Anyway, as I’ve said, I think A* search is an appropriate algorithm for this. You should now go onto google and do some reading about it - then you can come back here if you have questions. No offense, but I’m not going to spoon-feed you the details. B)

0
148 Mar 15, 2009 at 02:05

AI Techniques for Game Programming is a book on adaptive and evolutionary AIs such as Neural Networks and Genetic Algorithms. It’s a great book and in my opinion deals more with “AI” than simply resorting to “algorithmic” solutions, although that’s not to discourage use of them ;) Do finish that book and when you’re done read up on Evolutionary Neural Networks here http://www.cs.ucf.edu/\~kstanley/. Good stuff.

Back to the OT, you can use an ANN or GA to move the snake, but you will need to train it (lookup supervised neural networks in the book). A good fitness function would be to reward the snake for moving towards the pebble and deduct points if it moves away from if or if it rams into itself or a wall. Although you will need to train it for each level, especially if you throw some weird things in to confuse the ANN. Most will agree using either of these techniques for this game is not a good idea.

Using a rule based system for this kind of game is possible, but would require tons of code and could still lead to collisions. Like Reedbeta said, using the A* algorithm would work very well for this game. I learned that algorithm from this guys webpage: http://www.geocities.com/jheyesjones/astar.html. I don’t know if it’s the original article I read, but it was very clear when I did read it. Just a place for you to start.

0
101 Mar 15, 2009 at 02:23

Reedbeta, I wasn’t asking you to. You mis understand where I’m coming from apparently, but that’s my fault for not being clear.
Speaking of being foggy: That was the example map from “Help Bob Through a Maze” not Snake. In Snake, there is no defined array (map) for the Snake to go through. He floats in space. He is an array himself (which holds the values of where his body is/how long).

This is how he is moved through space:

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


But that isn’t exactly relevant here, other than the fact that I don’t quite see how the books example of Bob and the maze could fit with Snake and pathfinding to the food (avoiding only the exterior walls (simple) and his body (not simple)).

Yes, I’m aware google is my friend, I use him often enough to find links for other people. :)

0
139 Mar 15, 2009 at 03:33

Pathfinding doesn’t require all the locations of the map to be written out in advance and stored in an array. The game space of Snake is still a grid, so you can pathfind through it like through any grid, regardless of what kind of data structure is used to represent it. In this case you’d generate the grid locations on an as-needed basis during the search.

0
102 Mar 15, 2009 at 03:45

Just as a note to the topic: you could map your snake’s position to that grid used by the book you’re reading.

Lets say you have a screen resolution of 640x480. Lets say too that you’re using a grid of 16x16 for the IA.

Simplifying, if you divide the X position of the snake by 40 (that is the same as 640 / 16), you get the X position scaled to the grid resolution that is 16. Dividing the Y position of the snake by 30 (that is, 480/16), you scale the Y coordinate of the snake to the grid as well.

So,

// better if you use reciprocal, but I'm keeping divisions
// to simplify an idea explanation.
float scaleX = (float)screenWidth / gridWidth;
float scaleY = (float)screenHeight / gridHeight;
int scaledSnakeX = (int)(snakeX / scaleX);  // discard precision
int scaledSnakeY = (int)(snakeY / scaleY) // discard precision


You get the position of the snake inside the grid that you’be using for the IA. The features (fruits, for instance) should then be positioned in coordinates that snap entirely to the grid, like 0x0, 16x16, 32x32, etc.

With this you could implement the book’s algorithm with little modifications to your existing code.

EDIT: the code will work for screen of 80x25 as well, if you’re in text mode.

0
101 Mar 15, 2009 at 19:54

Yes, thank you for the comments and posts. I’m just an idiot. I don’t have the map itself (yes, it’s in “Text mode” - DOS shell) as an array, but the Snake itself is an array! Meaning if I passed it as a copy (I’m so used to doing it by reference I forget you can copy things), I could “Move in space ahead of time” without actually moving the Snake or taking 5 years to make a decision.

Thanks again for all the posts, and yes, I’m aware the “playing board” is still a grid (80 width by 25 height or 50 if it’s full screen (true DOS)).

0
101 Mar 18, 2009 at 05:03

Sorry for posting again so early, but! I’m curious… Isn’t this a “dumbed down” version of A*?

0
126 Mar 18, 2009 at 06:54

From what I’ve seen of the video, A* is a more specific application of that only used for finding the shortest path. I don’t think you would need anything that complex for a snake game. If it was me I would have a head class and a body segment class. The head would respond to keyboard commands, each body segment would simply follow it’s leader, or rather the last position of the leader since you would only make changes at the center point of each grid space, and move so many pixels each game loop. Decisions would only need to be made based on the collisions involving the head.

0
101 Mar 18, 2009 at 16:36

I find Amit explains things quite well, with lots of examples. Check out his site for information on path finding.

0
101 Mar 19, 2009 at 05:26

fireside, I’m sorry, I mis interpreted what A* was specifically, and now I know (I think I do!). The Professor talking in the video is explaining a tree of possible moves (not path-finding). Sorry again.

Nodlehs, thank you. I’ll be reading that for a while (lots to read! Many pages).
Before I came on here today, I decided to sit down and actually think on my own without trying to jumble my mind with “designing” one from somebody elses examples n’ whatever else like I usually try and do (learn from the best… or second best).

Here’s my out-come:

bool CREATE_SNAKE::CrashAt(const int Direction) {

int CHECK_H = 0;
int CHECK_V = 0;

if (Direction == CONTROL[0]) { CHECK_V = Vertical[0] - 1; CHECK_H = Horizontal[0]; } else
if (Direction == CONTROL[1]) { CHECK_V = Vertical[0] + 1; CHECK_H = Horizontal[0]; } else
if (Direction == CONTROL[2]) { CHECK_H = Horizontal[0] - 1; CHECK_V = Vertical[0]; } else
if (Direction == CONTROL[3]) { CHECK_H = Horizontal[0] + 1; CHECK_V = Vertical[0]; } else
{ MessageBox(NULL, "ERROR", "ERROR", MB_OK); exit(0); }

/* Hit self */
for (int x = SnakeSize - 1; x > 1; x--) {
if (CHECK_H == Horizontal[x] && CHECK_V == Vertical[x]) {
return true;
}
}

/* Hit wall */
if (CHECK_V == PlaceY || CHECK_V == Height + PlaceY - 1 ||
CHECK_H == PlaceX || CHECK_H == Width + PlaceX - 1) {
return true;
}

return false;
}

void CREATE_SNAKE::AI() {

/*
Up    = CONTROL[0]
Down  = CONTROL[1]
Left  = CONTROL[2]
Right = CONTROL[3]
*/

int Move = 0;
if (Food[1] > Vertical[0]) { Move = 1; }   /* The food is below */
if (Food[1] < Vertical[0]) { Move = 0; }   /* The food is above */
if (Food[0] > Horizontal[0]) { Move = 3; } /* The food is right */
if (Food[0] < Horizontal[0]) { Move = 2; } /* The food is left  */

/* Initially move towards the closest position to the food */
if (LastMove != CONTROL[Move]) { LastMove = CONTROL[Move]; }

/* If the Snake continues to hit himself, try another move */
while (CrashAt(CONTROL[Move])) {

/* Cycle through all four possible moves (North, South, West, and East) */
for (int x = 0; x < 5; x++) {
if (x == 4) { LastMove = CONTROL[Move]; return; }
Move = x;

/* If you found a move that results in NO hit, use it */
if (!CrashAt(CONTROL[Move])) { break; }
}

/* Assign the move */
LastMove = CONTROL[Move];
}

return;
}


It took an over-all average of 60 (1 point per food) to about 120-200+! It’s amazing really. I can’t believe it took me SO long to think of it. Although.. I don’t think that’s considered (or even is) “path-finding,” but more or less avoiding obstacles (which is still considered AI, right?).

Thanks again for the help (and links)… and putting up with my dumb questions. :) Appreciated!

EDIT: Wow! That was alot to read… But says alot too.

Why does this:

Recalculating paths

As time passes we expect the game world to change. A path found some time ago may no longer be the optimal path. It may be worth updating old paths with new information. Listed below are some criteria that could be used for determining when a recalculation is needed:
Every N steps: this guarantees that the information used to calculate the path is not more than N steps old.
Whenever extra CPU time is available: this allows dynamic adjustment of path quality; as more units are deployed, or if the game is running on a slower computer, CPU usage per unit can be decreased.
Whenever the unit turns a corner or passes a waypoint.
Whenever the world near the unit has changed.

The main drawback of path recalculation is that a lot of path information is thrown away. For example, if the path is 100 steps long and it is recalculated every 10 steps, the total number of path steps is 100+90+80+70+60+50+40+30+20+10 = 550. For a path of M steps, approximately M\^2 path steps are computed over time. Therefore path recalculation is not a good idea if you expect to have many long paths. It would be better to reuse the path information instead of throwing it away.

Say that (the bolded text)? Why couldn’t you just hold onto the old path information and just check for the units coordinates? It said earlier that the D* and LPA* algorithms weren’t “Applicable” (for something) and quote, “besides, storing units information doesn’t take that much, only unit coordinates.” I mean, if you already have a path made, and only want to redo the entire! Path-finding again, just to avoid units.. Why not keep the old path and check if the units have crossed into it, and adjust accordingly (like slewing to the left or right by 1 or 2 squares so he’s out of their way)?

Sorry, curious.

EDIT #2: Here’s what I was talking about (part of it):

For a game with lots of moving units, you usually don’t want to keep all that information around, so D* and LPA* aren’t applicable

Earlier above that it said this though:

I personally don’t see much need for ID-A* for finding paths on game maps. ID algorithms tend to increase computation time while reducing memory requirements. In map pathfinding, however, the “nodes” are very small―they are simply coordinates. I don’t see a big win from not storing those nodes.

Why couldn’t you just store the units positions and check them instead of trying to find a whole new path from scratch? :/

0
126 Mar 19, 2009 at 13:15

fireside, I’m sorry, I mis interpreted what A* was specifically, and now I know (I think I do!). The Professor talking in the video is explaining a tree of possible moves (not path-finding). Sorry again.

No problem. I guess I should have read your posts more because I thought you were making a standard snake game, but you’re making an automatic snake game that finds his own food. A* would be useful for that purpose and it’s something you might want to look at later. Nice to see someone having some fun programming a game instead of trying to make the next big MMO.

0
101 Mar 19, 2009 at 16:32

@fireside

No problem. I guess I should have read your posts more because I thought you were making a standard snake game, but you’re making an automatic snake game that finds his own food. A* would be useful for that purpose and it’s something you might want to look at later. Nice to see someone having some fun programming a game instead of trying to make the next big MMO.

I created Snake in less than a day back in December, just to see if I could (all on my own!), and it worked great! It had menus, cheat codes, etc. It was 100% structural/procedural. After a long while (somewhere in mid-February) of thinking on it, I converted it to a more object-oriented method, so I could easily add X Snakes to it. But I was over-tasking myself because I was trying to do too much at once (priorities! Snake == last not first), so I quit messing with it ‘til early March when I had more time.

But since then, I wanted to make a smarter Snake (opponent), something that isn’t dumb as simply follows X, Y coordinates, because (as shown above) he crashes into himself often and it’s very discouraging (as a programmer) and disappointing (as a player). I’ve never been proficient with OOP either, so I saw it as a great opportunity to learn more about the object-oriented paradgim of programming. :)

Most things I write, I end up hard-coding because I’m a hobbyist programmer; I’ve never had a formal education on programming although I’ve programmed for a long time. I’ve never advanced far in the depths of knowledge of programming because I never knew where to look. It’s like being in a library and having no map to say where the books are and what they contain! (Yes, I’m aware of google/MSDN now)

Sorry for the long reply, and being off-topic.. But before I go back on-topic, I have one really important question:
Did I comment my code well enough?

Thanks.

0
101 Jun 15, 2009 at 05:59

I apologize if I came off as being condescending or arrogant 3-4 months ago. It’s how people usually perceive me.

This is rather delayed, which I also apologize for! I have been having personal and private issues which is why I quit logging on so often as well, but that’s aside the point of this post! :)

Here is the best A* based algorithm (I think that’s how you’d refer to it) I could create! It isn’t much of an improvement of what I had prior, but it is an improvement non-the-less. I will post the “required” functions from TextControl after the code, so you can compile it, along with a screen shot of it.

#include <iostream>
#include <vector>
#include <TextControl.h>
using namespace std;

int FoodX = rand() % 70 + 5;
int FoodY = rand() % 25 + 5;

int SnakeX[100];
int SnakeY[100];

vector<int> Instructions;

void CreatePath() {

int DestX = FoodX;
int DestY = FoodY;

int TempX = SnakeX[0];
int TempY = SnakeY[0];

while (TempX != DestX || TempY != DestY) {
if (DestX > TempX) { TempX++; Instructions.push_back(3); }
if (DestX < TempX) { TempX--; Instructions.push_back(2); }
if (DestY > TempY) { TempY++; Instructions.push_back(1); }
if (DestY < TempY) { TempY--; Instructions.push_back(0); }
}

return;
}

void DrawSnake(const int color) {

SetColor(color, BLACK);
for (int x = 10; x > 0; x--) { SnakeX[x] = SnakeX[x - 1]; SnakeY[x] = SnakeY[x - 1]; }
for (int x = 10; x >= 0; x--) { PlaceCursor(SnakeX[x], SnakeY[x]); printf("*"); }

SetColor(GREEN, BLACK);
PlaceCursor(SnakeX[0], SnakeY[0]); printf("@");
if (SnakeX[0] != SnakeX[10] || SnakeY[0] != SnakeY[10]) { PlaceCursor(SnakeX[10], SnakeY[10]); SetColor(BLACK, BLACK); printf(" "); }

return;
}

void RunPath(const int color) {

/* It'll crash sometimes if it randomly is empty */
if (!Instructions.empty()) {

PlaceCursor(0, 1);
SetColor(WHITE, BLACK);
printf("Total moves: %i ", Instructions.size());

PlaceCursor(0, 0);
printf("Initial Snake position: %i, %i", SnakeX[0], SnakeY[0]);

int X = SnakeX[0];
int Y = SnakeY[0];

//PlaceCursor(SnakeX[0], SnakeY[0]);
//SetColor(RED, BLACK);
//printf("x");

SetColor(color, BLACK);
for (int x = 0; x < Instructions.size() - 1; 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(25);

DrawSnake(color);

//PlaceCursor(SnakeX[0], SnakeY[0]);
//printf("x");
}

switch (Instructions.front()) {
case 0: SnakeY[0]--; break;
case 1: SnakeY[0]++; break;
case 2: SnakeX[0]--; break;
case 3: SnakeX[0]++; break;
}

PlaceCursor(FoodX, FoodY);
SetColor(YELLOW, BLACK);
printf("x");

PlaceCursor(31, 0);
SetColor(WHITE, BLACK);
printf("- Current Snake position: %i, %i  ", SnakeX[0], SnakeY[0]);

//Sleep(1000);

/* To show the old path in a faded grey */
SetColor(BLACK, BLACK);
for (int x = 0; x < Instructions.size(); x++) {

PlaceCursor(X, Y);
printf("x");

switch (Instructions.at(x)) {
case 0: Y--; break;
case 1: Y++; break;
case 2: X--; break;
case 3: X++; break;
}
}

/* To ensure the food is erased */
PlaceCursor(FoodX, FoodY);
SetColor(BLACK, BLACK);
printf(" ");
}

return;
}

void NewRun() {

FoodX = rand() % 70 + 5;
FoodY = rand() % 25 + 5;

PlaceCursor(FoodX, FoodY);
SetColor(YELLOW, BLACK);
printf("x");

Instructions.clear();

return;
}

int main() {

ClearConsole(BLACK, BLACK);
RemoveCursor();

srand(time(NULL));

SnakeX[0] = 5;
SnakeY[0] = 5;

while (true) {
CreatePath();
RunPath(BLUE);
NewRun();
}

cin.get();
return 0;
}

• RemoveCursor

void RemoveCursor() {

 /* Remove the cursor (does not work in full screen) */
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursoInfo;
CursoInfo.dwSize = 1;         /* The size of caret */
CursoInfo.bVisible = false;   /* Caret is visible? */
SetConsoleCursorInfo(hConsole, &CursoInfo);

return;  }

• SetColor

enum { BLACK = 0, DARK_BLUE = 1, DARK_GREEN = 2, TEAL = 3, DARK_RED = 4, DARK_PURPLE = 5, GOLD = 6, GREY = 7, DARK_WHITE = 8, BLUE = 9, GREEN = 10, CYAN = 11, RED = 12, PURPLE = 13, YELLOW = 14, WHITE = 15 };

void SetColor(const int foreground) {

 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole, foreground);

return;  }


void SetColor(const int foreground, const int background) {

 int Color = foreground + (background * 16);
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole, Color);

return;  }

• PlaceCursor

void PlaceCursor(const int x, const int y) {

 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

COORD PlaceCursorHere;
PlaceCursorHere.X = x;
PlaceCursorHere.Y = y;

SetConsoleCursorPosition(hConsole, PlaceCursorHere);
return;  }

• ClearConsole

void ClearConsole(const int foreground, const int background) {

 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

COORD coordScreen = { 0, 0 };
DWORD cCharsWritten;
CONSOLE_SCREEN_BUFFER_INFO csbi;
DWORD dwConSize;

if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) { return; }
dwConSize = csbi.dwSize.X * csbi.dwSize.Y;

SetColor(foreground, background);
if (!FillConsoleOutputCharacter(hConsole, (TCHAR) ' ', dwConSize, coordScreen, &cCharsWritten)) { return; }
if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) { return; }
if (!FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten)) { return; }

return;  }


As promised! The picture:

I hope to get some real feedback on this. It’s quite appreciated (no matter how my mannerism is). :)

0
101 Sep 25, 2009 at 06:56

Almost a year later and basically nothing has changed: I can’t think of anything else.

This is what I have (the entire main.cpp):

#include <iostream>
#include <vector>
#include <TextControl.h>
using namespace std;

int TotalRuns = 0;
int TotalCrashes = 0;

int FoodX = rand() % 70 + 5;
int FoodY = rand() % 25 + 5;

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

vector<int> Instructions;

void ShowPath() {

int TempX = SnakeX[0];
int TempY = SnakeY[0];

SetColor(GREEN, BLACK);

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

switch (Instructions.at(x)) {
case 0: TempY--; break;
case 1: TempY++; break;
case 2: TempX--; break;
case 3: TempX++; break;
}

PlaceCursor(TempX, TempY);
printf("!");
}

return;
}

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(X[x], Y[x]);
//SetColor(RED, BLACK);
//printf("X");
//Sleep(500);
//TotalCrashes++;
return true;
}
}

return false;
}

void DrawSnake(const int color, int SnakeX[], int SnakeY[]);

void CreatePath() {

/* Copy food location */
int DestX = FoodX;
int DestY = FoodY;

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];
}

while (TempX[0] != DestX || TempY[0] != DestY) {
if (DestX > TempX[0]) { TempX[0]++; Instructions.push_back(3); }
if (DestX < TempX[0]) { TempX[0]--; Instructions.push_back(2); }
if (DestY > TempY[0]) { TempY[0]++; Instructions.push_back(1); }
if (DestY < TempY[0]) { TempY[0]--; Instructions.push_back(0); }

Sleep(5);

DrawSnake(RED, TempX, TempY);

/* Move the rest of the body */
//for (int x = 10; x > 0; x--) { TempX[x] = TempX[x - 1]; TempY[x] = TempY[x - 1]; }

static int NewMove = 0;

/* Oh noes! He crashed! Randomize a movement */
while (Crash(TempX, TempY)) {

SetColor(CYAN, BLACK);
PlaceCursor(TempX[0], TempY[0]); printf("x");
Sleep(500);

/* Remove the OLD move */
switch (Instructions.back()) {
case 0: TempY[0]++; break;
case 1: TempY[0]--; break;
case 2: TempX[0]++; break;
case 3: TempX[0]--; break;
}

NewMove++;

/* Add the NEW move and ensure there's no crash... */
switch (NewMove) {
case 0: TempY[0]--; break;
case 1: TempY[0]++; break;
case 2: TempX[0]--; break;
case 3: TempX[0]++; break;
}

Instructions.back() = NewMove;

if (NewMove > 4) { NewMove = 0; goto DEAD_SNAKE; }
}

/*
for (int x = 9; x > 1; x--) {
if (TempX[0] == TempX[x] && TempY[0] == TempY[x]) {
PlaceCursor(TempX[x], TempY[x]);
SetColor(RED, BLACK);
printf("X");
Sleep(500);
TotalCrashes++;
}
}
*/
//Sleep(5);
}

//ShowPath();

return;
}

void DrawSnake(const int color, int SnakeX[], int SnakeY[]) {

SetColor(color, BLACK);
for (int x = 10; x > 0; x--) { SnakeX[x] = SnakeX[x - 1]; SnakeY[x] = SnakeY[x - 1]; }
for (int x = 10; x >= 0; x--) { PlaceCursor(SnakeX[x], SnakeY[x]); printf("*"); }

SetColor(GREEN, BLACK);
PlaceCursor(SnakeX[0], SnakeY[0]); printf("@");
if (SnakeX[0] != SnakeX[10] || SnakeY[0] != SnakeY[10]) { PlaceCursor(SnakeX[10], SnakeY[10]); SetColor(BLACK, BLACK); printf(" "); }

return;
}

void RunPath(const int color) {

/* It'll crash sometimes if it randomly is empty */
if (!Instructions.empty()) {

PlaceCursor(0, 1);
SetColor(WHITE, BLACK);
printf("Total moves: %i ", Instructions.size());

PlaceCursor(0, 0);
printf("Initial Snake position: %i, %i", SnakeX[0], SnakeY[0]);

//PlaceCursor(SnakeX[0], SnakeY[0]);
//SetColor(RED, BLACK);
//printf("x");

SetColor(color, BLACK);
for (int x = 0; x < Instructions.size() - 1; 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(15);

DrawSnake(color, SnakeX, SnakeY);

for (int x = 9; x > 2; x--) {
if (SnakeX[0] == SnakeX[x] && SnakeY[0] == SnakeY[x]) {
SetColor(WHITE, BLACK);
PlaceCursor(SnakeX[x], SnakeY[x]);
printf("X");
//Sleep(500);
TotalCrashes++;
}
}

//PlaceCursor(SnakeX[0], SnakeY[0]);
//printf("x");
}

switch (Instructions.front()) {
case 0: SnakeY[0]--; break;
case 1: SnakeY[0]++; break;
case 2: SnakeX[0]--; break;
case 3: SnakeX[0]++; break;
}

PlaceCursor(FoodX, FoodY);
SetColor(YELLOW, BLACK);
printf("x");

PlaceCursor(31, 0);
SetColor(WHITE, BLACK);
printf("- Current Snake position: %i, %i  ", SnakeX[0], SnakeY[0]);

//Sleep(500);

/* To show the old path in a faded grey */
/*
SetColor(BLACK, BLACK);
for (int x = 0; x < Instructions.size(); x++) {

PlaceCursor(X, Y);
printf("x");

switch (Instructions.at(x)) {
case 0: Y--; break;
case 1: Y++; break;
case 2: X--; break;
case 3: X++; break;
}
}

/* To ensure the food is erased */
PlaceCursor(FoodX, FoodY);
SetColor(BLACK, BLACK);
printf(" ");
}

return;
}

void NewRun() {

FoodX = rand() % 70 + 5;
FoodY = rand() % 25 + 5;

PlaceCursor(FoodX, FoodY);
SetColor(YELLOW, BLACK);
printf("x");

Instructions.clear();

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] = 5;
SnakeY[0] = 5;

while (true) {
CreatePath();
RunPath(BLUE);
NewRun();

PlaceCursor(0, 3);
SetColor(WHITE, BLACK);
printf("Total runs: ");
SetColor(CYAN, BLACK);
printf("%d", TotalRuns);

PlaceCursor(0, 4);
SetColor(WHITE, BLACK);
printf("Total crashes: ");
SetColor(RED, BLACK);
printf("%d", TotalCrashes);

TotalRuns++;
}

cin.get();
return 0;
}


He has a 1/8th collect/hit ratio (think that’s how you’d say it).
Every 800,000 foods he collects, he has around 100,000 collisions.

If anybody has any suggestions/changes, let me know. :(

0
101 Sep 27, 2009 at 22:11

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?

0
101 Oct 06, 2009 at 21:57

@poita

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?

0
139 Oct 06, 2009 at 22:42

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.

0
101 Oct 06, 2009 at 23:16

@Reedbeta

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.

Made little to no sense to me.

0
126 Oct 07, 2009 at 00:32

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.

0
101 Oct 07, 2009 at 00:40

@fireside

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.

0
126 Oct 07, 2009 at 01:31

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.

0
139 Oct 07, 2009 at 02:50

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.

0
103 Oct 07, 2009 at 10:37

A* will do snake.

0
101 Oct 07, 2009 at 11:30

@rouncer

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.

0
126 Oct 07, 2009 at 18:02

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.

0
101 Oct 07, 2009 at 22:52

@fireside

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. :)

0
126 Oct 08, 2009 at 01:32

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.

0
101 Oct 08, 2009 at 04:53

@fireside

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.microsoft.com/en-us/library/ms686298(VS.85).aspx

0
126 Oct 08, 2009 at 05:34

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.

0
101 Oct 08, 2009 at 06:09

@fireside

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): http://www.youtube.com/watch?v=clayA9KzqbM

0
139 Oct 08, 2009 at 06:35

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.

0
101 Oct 08, 2009 at 06:44

@Reedbeta

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): http://www.youtube.com/watch?v=R891r2NwENY

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!): http://www.youtube.com/watch?v=hOlbHzQ5vlw
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

0
126 Oct 08, 2009 at 11:19

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.

0
101 Oct 08, 2009 at 20:18

@fireside

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: http://www.youtube.com/watch?v=o9t8bOkp4lI

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);
//}

0
126 Oct 08, 2009 at 21:55

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.

0
101 Oct 08, 2009 at 22:24

@fireside

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.

This game is written using purely ASCII art. I believe I stated that in the first post.
The console has a set “grid” which at the time, I didn’t think of. I don’t know why.
It’s an 80 x 50 grid (80 width, 50 height). The Snake is an “array” and every element of the Snake contains the X or Y position. Here’s a quickie example:

If SnakeX[0] = 60 then it’s on the 60th grid piece (width) - which is the far right
If SnakeX[0] = 0 then it’s on the 0th grid piece (height) - which is the very top

I know what I have to do now, and I’m trying to do it. It’s like the answer’s on the tip of my tongue, but I just can’t grasp it!

Again: This project is written in C++, not Java. I’m not a fan of OOP, nor have I ever been. Everytime I try writting something using objects, people tell me how wrong I am, and never show me the proper way (by example), rather, they attempt to lecture me and do an extremely poor job at it, or just simply say “Go spend money on this and that” and sir, I’m not spending money that I don’t have to. This is a hobby for me, not a job, not a school thing, although I don’t mind learning, I don’t like spending money I don’t have to.

Back to the point: The only thing that’s wrong with the Snake right now, is the fact that he isn’t exactly “looking ahead” by one square, and I know how to fix that, because I’ve done that in the past (before I tried making him have a predetermined “path” to the food). With the prepathing + avoiding the body, he should be able to (in theory) go further than just checking ahead of his body by one square. What I’m hoping is, if he ends up in a rut (all possible moves are crashes), I’ll have him back up and take a different route, and so fourth.

I’m starting to understand the theory of using “Nodes” and I watched that old old link I posted in here, the CS Class showing the “Game-Tree-Algorithm” using Java with the Tic-Tac-Toe example. It really made me think, about how the whole “Fitness” and “Children” or “nodes” would work, and paticuarly how well they’d work with Snake - but as I’ve said before: Before I do that, I’d like to see if I can do this all on my own. It’s an obsessive thing, that I’m actively and consciously doing, and since it’s doing no harm, it’s only making me think and learn, perhaps I’ll be able to see how well an “Algorithm” such as the A* would work in a situation like this.

I can’t quite remember all that much about the A* algorithm, but yes, I read a heck of a lot of tutorials, documentation (articles/”documentation”) on it, and I don’t know why it didn’t sink in then, but hopefully it will now.

Again: Thank you for the support. :)

EDIT: Oh my God, I’m at like the LAST part (minute bug?)…! This works perfectly. 0 collisions for the AI Snake/real Snake!

if (FoodY < TempY[0]) {

Instructions.push_back(UP);
if (!CrashAt(TempX, TempY, UP)) { TempY[0]--; }

while (CrashAt(TempX, TempY, UP)) {

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

PlaceCursor(0, 5);
SetColor(GREEN, BLACK);
printf("%d, %d  ", TempX[0], TempY[0]);
Sleep(1000);

switch (x) {
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;
}

PlaceCursor(TempX[0], TempY[0]);
SetColor(GREEN, BLACK);
printf("X");

/* CRASHED STILL - REVERSE */
if (Crash(TempX, TempY)) {
switch (x) {
case UP: TempY[0]++; break;
case DOWN: TempY[0]--; break;
case LEFT: TempX[0]++; break;
case RIGHT: TempX[0]--; break;
}
} else { break; }

Sleep(250);
}

PlaceCursor(0, 5);
SetColor(GREEN, BLACK);
printf("%d, %d  ", TempX[0], TempY[0]);
}

/* THIS HAS TO DO WITH ONLY UPDATING THE SNAKE'S POSITION AND DRAWING HIM */
/**************************************************************************/

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(" ");
}
}
}[/Code]

EDIT: This is my last edit on this post I think... [i]Last edited by Hyper : Today at 06:27 PM.[/i]

A'lass! Victory is mine! :)
[code]#include <iostream>
#include <vector>
#include <TextControl.h>
using namespace std;

#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3

int SnakeSize = 10;

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 DrawSnake(const int, int*, int*);
bool Crash(const int*, const int*);
bool CrashAt(int*, int*, const int);

vector<int>Instructions;

bool CrashAt(int *SnakeX, int *SnakeY, const int Direction) {

int CHECK_H = 0;
int CHECK_V = 0;

if (Direction == UP) { CHECK_V = SnakeY[0] - 1; CHECK_H = SnakeX[0]; } else
if (Direction == DOWN) { CHECK_V = SnakeY[0] + 1; CHECK_H = SnakeX[0]; } else
if (Direction == LEFT) { CHECK_H = SnakeX[0] - 1; CHECK_V = SnakeY[0]; } else
if (Direction == RIGHT) { CHECK_H = SnakeX[0] + 1; CHECK_V = SnakeY[0]; } else
{ MessageBox(NULL, "ERROR", "ERROR", MB_OK); exit(0); }

/* Hit self */
for (int x = SnakeSize - 1; x > 1; x--) {
if (CHECK_H == SnakeX[x] && CHECK_V == SnakeY[x]) {
return true;
}
}

return false;
}

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 < SnakeSize; x++) {
TempX[x] = SnakeX[x];
TempY[x] = SnakeY[x];
}

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

Instructions.push_back(RIGHT);

if (!CrashAt(TempX, TempY, RIGHT)) { TempX[0]++; } else {
while (CrashAt(TempX, TempY, RIGHT)) {
for (int x = 0; x < 4; x++) {
switch (x) {
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;
}

PlaceCursor(TempX[0], TempY[0]);
SetColor(GREEN, BLACK);
printf("X");

/* CRASHED STILL - REVERSE */
if (CrashAt(TempX, TempY, x)) {
switch (x) {
case UP: TempY[0]++; break;
case DOWN: TempY[0]--; break;
case LEFT: TempX[0]++; break;
case RIGHT: TempX[0]--; break;
}
} else { break; }

Sleep(500);
}
}
}

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(" ");
}
}
}

if (FoodX < TempX[0]) {

Instructions.push_back(LEFT);

if (!CrashAt(TempX, TempY, LEFT)) { TempX[0]--; } else {
while (CrashAt(TempX, TempY, LEFT)) {

for (int x = 0; x < 4; x++) {
switch (x) {
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;
}

PlaceCursor(TempX[0], TempY[0]);
SetColor(GREEN, BLACK);
printf("X");

/* CRASHED STILL - REVERSE */
if (CrashAt(TempX, TempY, x)) {
switch (x) {
case UP: TempY[0]++; break;
case DOWN: TempY[0]--; break;
case LEFT: TempX[0]++; break;
case RIGHT: TempX[0]--; break;
}
} else { break; }

Sleep(500);
}
}
}

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(" ");
}
}

}

if (FoodY > TempY[0]) {

Instructions.push_back(DOWN);

if (!CrashAt(TempX, TempY, DOWN)) { TempY[0]++; } else {
while (CrashAt(TempX, TempY, DOWN)) {

for (int x = 0; x < 4; x++) {
switch (x) {
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;
}

PlaceCursor(TempX[0], TempY[0]);
SetColor(GREEN, BLACK);
printf("X");

/* CRASHED STILL - REVERSE */
if (CrashAt(TempX, TempY, x)) {
switch (x) {
case UP: TempY[0]++; break;
case DOWN: TempY[0]--; break;
case LEFT: TempX[0]++; break;
case RIGHT: TempX[0]--; break;
}
} else { break; }

Sleep(500);
}
}
}

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(" ");
}
}
}

if (FoodY < TempY[0]) {

Instructions.push_back(UP);

if (!CrashAt(TempX, TempY, UP)) { TempY[0]--; } else {
while (CrashAt(TempX, TempY, UP)) {

for (int x = 0; x < 4; x++) {
switch (x) {
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;
}

PlaceCursor(TempX[0], TempY[0]);
SetColor(GREEN, BLACK);
printf("X");

/* CRASHED STILL - REVERSE */
if (CrashAt(TempX, TempY, x)) {
switch (x) {
case UP: TempY[0]++; break;
case DOWN: TempY[0]--; break;
case LEFT: TempX[0]++; break;
case RIGHT: TempX[0]--; break;
}
} else { break; }

Sleep(500);
}
}
}

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(" ");
}
}
}

PlaceCursor(0, 46);
SetColor(YELLOW, BLACK);
cout << "Instruction size: " << Instructions.size();
}

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

PlaceCursor(TempX[0], TempY[0]);
SetColor(YELLOW, BLACK);
printf("x");
}

return;
}

bool Crash(const int *X, const int *Y) {

for (int x = SnakeSize - 1; x > 1; x--) {
if (X[0] == X[x] && Y[0] == Y[x]) {
SimulatedCrashes++;
PlaceCursor(0, 0);
SetColor(RED, BLACK);
printf("%d", SimulatedCrashes);

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);

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

return;
}

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

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 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) {
NewRun();
CreatePath(SnakeX, SnakeY);
RunPath(BLUE, SnakeX, SnakeY, Instructions);
}

cin.get();
return 0;
}


Video showing the working code above: http://www.youtube.com/watch?v=vi050-Y2v-4
Thank you a lot, fireside. I’ll try to convert this into an A* algorithm next.

I just wanted to see if I could do this on my own (and I did actually!). :)

0
101 Oct 10, 2009 at 04:56

If you want to turn this into A* then the first step you’ll want to take is to abstract your (x, y) coordinates into a class.

You said people never showed you how to do this, so I will.

Your coordinate class will look something like this:

struct Point
{
int x, y;
Point(int x, int y) : x(x), y(y) {}
};


Of course you can call it whatever you want. Vector2, Vec2, Coordinate, Coord, Node, Cell, Position. They’re all sensible choices.

Then, instead of writing things like:

int FoodX = 0;
int FoodY = 0;


you’d write

Point Food = Point(0, 0);


(or equivalently)

Point Food(0, 0);


if (FoodY > TempY[0]) {


you’d write

if (Food.y > Temp[0].y) {


Of course, that would first require that you’ve changed TempX and TempY from two arrays of ints into one array of Points:

Point Temp[];

0
101 Oct 12, 2009 at 09:22

@poita

If you want to turn this into A* then the first step you’ll want to take is to abstract your (x, y) coordinates into a class.

You said people never showed you how to do this, so I will.

Your coordinate class will look something like this:

struct Point
{
int x, y;
Point(int x, int y) : x(x), y(y) {}
};


Of course you can call it whatever you want. Vector2, Vec2, Coordinate, Coord, Node, Cell, Position. They’re all sensible choices.

I thank you for that, but I’ve decided to create something to simplify everything.
The mechanics of Snake cloud the actual learning process for me, so here’s this:

#include <stdio.h>
#include <TextControl.h>

#define UP 0
#define DOWN 1
#define LEFT 2
#define RIGHT 3

void RandomlyGenerateMaze(int Walls[][16]) {

int temp = 0;

for (int x = 0; x < 15; x++) {
for (int y = 0; y < 15; y++) {
temp = rand() % 7;
if (Walls[x][y] == 0 && temp > 5) {
Walls[x][y] = 1;
}
}
}

return;
}

void DisplayMaze(int Walls[][16]) {

SetColor(DARK_WHITE, BLACK);

for (int x = 0; x < 15; x++) {
for (int y = 0; y < 15; y++) {
if (Walls[x][y] == 1) {
PlaceCursor(x + 25, y + 10);
printf("#");
}
}
}

return;
}

void ResetMaze(int Walls[][16]) {

for (int x = 1; x < 14; x++) {
for (int y = 1; y < 14; y++) {
PlaceCursor(x + 25, y + 10);
printf(" ");
Walls[x][y] = 0;
}
}

return;
}

bool MovePlayer(const int Direction, const int Walls[][16], int &PlayerX, int &PlayerY) {

switch (Direction) {
case UP: {
if (Walls[PlayerX - 25][(PlayerY + 1) - 12] != 1) {
PlaceCursor(PlayerX, PlayerY);
printf(" ");

PlayerY -= 1;
PlaceCursor(PlayerX, PlayerY);
printf("X");
}
} break;

case DOWN: {
if (Walls[PlayerX - 25][(PlayerY + 1) - 10] != 1) {
PlaceCursor(PlayerX, PlayerY);
printf(" ");

PlayerY += 1;
PlaceCursor(PlayerX, PlayerY);
printf("X");
}
} break;

case LEFT: {
if (Walls[(PlayerX - 1) - 25][PlayerY - 10] != 1) {
PlaceCursor(PlayerX, PlayerY);
printf(" ");

PlayerX -= 1;
PlaceCursor(PlayerX, PlayerY);
printf("X");
}
} break;

case RIGHT: {
if (Walls[(PlayerX + 1) - 25][PlayerY - 10] != 1) {
PlaceCursor(PlayerX, PlayerY);
printf(" ");

PlayerX += 1;
PlaceCursor(PlayerX, PlayerY);
printf("X");
}
} break;

default: return false;
}

return true;
}

int main() {

HANDLE OutHandle = GetStdHandle(STD_OUTPUT_HANDLE);

SetConsoleTitle("Snake - AI Module");
SMALL_RECT windowSize = {0, 0, 79, 49};
SetConsoleWindowInfo(OutHandle, TRUE, &windowSize);

ClearConsole(GREY, BLACK);
RemoveCursor();

int Walls[16][16] = {
{ 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, },
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1  },
};

srand(time(NULL));

PlaceCursor(25, 12);
SetColor(BLACK, GREEN);
printf(" ");
SetColor(GREY, BLACK);

Walls[1][2] = 15;
Walls[13][11] = 5; // The player

RandomlyGenerateMaze(Walls);
DisplayMaze(Walls);

PlaceCursor(39, 21);
SetColor(BLACK, RED);
printf(" ");
SetColor(GREY, BLACK);

int PlayerX = 38;
int PlayerY = 21;

PlaceCursor(PlayerX, PlayerY);
SetColor(CYAN, BLACK);
printf("X");

while (true) {
Sleep(50);

if (GetAsyncKeyState(VK_UP)) { MovePlayer(UP, Walls, PlayerX, PlayerY); } else
if (GetAsyncKeyState(VK_DOWN)) { MovePlayer(DOWN, Walls, PlayerX, PlayerY); } else
if (GetAsyncKeyState(VK_LEFT)) { MovePlayer(LEFT, Walls, PlayerX, PlayerY); } else
if (GetAsyncKeyState(VK_RIGHT)) { MovePlayer(RIGHT, Walls, PlayerX, PlayerY); }

if (Walls[(PlayerX) - 25][PlayerY - 10] == 3) {
SetColor(GREEN, BLACK);
PlaceCursor(PlayerX, PlayerY);
printf("X");
SetColor(GREY, BLACK);
break;
}
}

PlaceCursor(0, 0);
printf("YOU WIN!\n\n");

std::cin.get();
return 0;
}


You are a simple blue X, and start infront of the red square, and are aiming for a green square.
The maps will always be new (randomly generated), so there’s never a dull moment.

Here’s a screen shot of the above code compiled:

Thanks for trying to show me how I should make stuff though, it’s appreciated. :)

0
101 Oct 12, 2009 at 12:54

Well, when I said

If you want to turn this into A* then the first step you’ll want to take is to abstract your (x, y) coordinates into a class.

what I actually meant was

abstract your (x, y) coordinates into a class.

You should do this regardless of what AI you choose to use, and you should still do it for this new game.