Snake Collision
elamre

Hello all,
Im trying to develop a simple 2 player snake game. Everything so far so good. Im only having troubles with my collision system. Maybe somebody can help me with this?
Im using Allegro 4.9.9.2 because 5 doesnt seem to like my Dev-C++ very much.
This is my collision function:

bool Snake::collision()
    {
         for(int i=0; i<100;i++){
             if((x+1)==xPrevious[i] && y==yPrevious[i-1]){
                  return false;
                  }
             }
         
     }

and my files
http://www.2shared.com/file/fHg8Hli4/Try_2.html
The collision code is in snake.cpp.
with real kind regards, Elmar.

 Sliding text? 

gnolam
  1. When asking for help, always say what the problem actually is. Is it not compiling? Is it crashing? Is it producing undesired results? If so, what are the desired results? Help people help you by telling them what to look for.

  2. When posting code, put it in <code></code> tags. Use the edit button to add them.

elamre

Ok sorry! :)
and yeah the code tags, i was trying to use them but i used [code][/code].

It is perfectly fine running. Not crashing nor problems with compiling.
However once ingame, i cant move any direction using this code:

    void Snake::moveRight()
    {    
         if(!collision()){  
         if(x>610)
                         x=10;
         toPrevious();
         x+=10;
         }
         else
                           textout_ex( screen, font, "Me gusta Collision Y", 320, 240, makecol(0, 0, 255), makecol(0, 0, 0));  
    }

So it detects a collision even though there is none!

Edgar Reynaldo
elamre said:

bool Snake::collision()
    {
         for(int i=0; i<100;i++){
             if((x+1)==xPrevious[i] && y==yPrevious[i-1]){
                  return false;
                  }
             }
         
     }

Think about what you are doing - you are checking if x plus one is the same as any of the last 100 x values and the current y is the same as the yPrevious value one before the current (i) yPrevious value. Does that make sense?

Try storing your snake values in a std::list :

#SelectExpand
1struct segment { 2 int x; 3 int y; 4} 5 6class snake { 7private : 8 list<segment> body; 9 segment head; 10 11public : 12 void UpdateSnake() { 13 if (!body.empty() && !body.grow()) { 14 body.erase(body.begin()); 15 } 16 body.push_back(head); 17 head.x += xdir; 18 head.y += ydir; 19 dead = CheckCollision(); 20 } 21 bool CheckCollision() { 22 for (list<segment>::iterator it = body.begin() ; it != body.end() ; ++it) { 23 if ((it->x == head.x) && (it->y == head.y)) {return true;} 24 } 25 return false; 26 } 27 28};

elamre

Well to me it makes a lot of sense to be honest. if it wants to go up, it will check first if the above position is equal to one of the previous positions.
And with your code, Thankx. But i have no clue how to use this, i have added it to my snake, but the list doesnt have a type, and when making it int it doesnt work. (Im unknown with lists). Also where to call for update snake? where does head.x and head.y reffer to? and the body.empty and body.grow are listfunctions i assume?
And then the iterator, The iterator is unknown. And it is unable to process It.

thanks for your help until now :). But im still a bit stuck with this problem.

Edgar Reynaldo
elamre said:

Well to me it makes a lot of sense to be honest. if it wants to go up, it will check first if the above position is equal to one of the previous positions.

But you're still only checking if it moves up, and you don't know if it has. And, you're not checking the right index for the previous Y value (You should use i, not i - 1).

Quote:

And with your code, Thankx. But i have no clue how to use this, i have added it to my snake, but the list doesnt have a type, and when making it int it doesnt work. (Im unknown with lists). Also where to call for update snake? where does head.x and head.y reffer to? and the body.empty and body.grow are listfunctions i assume?
And then the iterator, The iterator is unknown. And it is unable to process It.

That's okay, I'll walk you through it.

A list is a C++ container template defined in the system header list and contained in the std namespace. This means to use it you will need to :

#include <list> // include the header that defines list
using std::list;// tell the compiler we will be using list to refer to std::list

To use a template, you need to tell it what type it will be using.

list<segment> body;// declares a list of segment objects named body

To iterate over a list, you need to use a list<TYPE>::iterator. iterators are just an efficient way to move back and forth over the objects contained in the list (or more generally, container).

#SelectExpand
1 2// list iteration method one 3list<segment>::iterator it = body.begin();// The list::begin() method returns an iterator pointing to the beginning of the list. It may return the same value as list::end(). 4while (it != body.end()) { 5 segment& s = *it;// iterators point to the elements stored in the list 6 printf("s.x = %i , s.y = %i\n" , s.x , s.y); 7 ++it;// move the iterator to point to the next element 8} 9 10// list iteration method two 11for (list<segment>::iterator it = body.begin() ; it != body.end() ; ++it) { 12 segment& s = *it;// using a reference allows us to modify the object stored in the list if we want to 13 printf("s.x = %i , s.y = %i\n" , s.x , s.y); 14}

For more information on the Standard Template Library containers (like list), take a look here :
http://www.sgi.com/tech/stl/index.html

And I actually made a typo. body.grow() doesn't exist - it is not a member of the list class. What I meant was that if the snake will grow on this update, then you should not erase the tail of the snake (which is stored at the beginning of the list). So, something more like this :

--growcounter;
bool grow = (growcounter == 0);
if (growcounter == 0) {growcounter = time_between_growth;}
if (!body.empty() && !grow) {
   body.erase(body.begin());
}

If you have any more questions or can't get it to work, post your errors here, and your questions too.

elamre

Im tying to implent the code(and having a hard time with it) But doesnt the segment structure have to be declared in the snake class aswell? or can i just declare it wherever i want?

Edit:
Nvm i found it out.
However the iteration methods arent totally clear to me, Where do i have to use them? i see that one is the collision check?

Edgar Reynaldo

The segment structure can be declared anywhere before a class or function that uses it. This means it can go in a separate header, or in the same header as the one for your snake class, before you declare it.

Be specific with your problems.

elamre
// list iteration method one
list<segment>::iterator it = body.begin();// The list::begin() method returns an iterator pointing to the beginning of the list. It may return the same value as list::end().
while (it != body.end()) {
   segment& s = *it;// iterators point to the elements stored in the list
   printf("s.x = %i , s.y = %i\n" , s.x , s.y);
   ++it;// move the iterator to point to the next element
}

// list iteration method two
for (list<segment>::iterator it = body.begin() ; it != body.end() ; ++it) {
   segment& s = *it;// using a reference allows us to modify the object stored in the list if we want to
   printf("s.x = %i , s.y = %i\n" , s.x , s.y);
}

Im getting what this code is doing (only the printf function, u suppose this is like cout?) But have no clue where to use it. And your last piece of code (with the growcounter) i suppose this is my grow() function?

Edgar Reynaldo

If you don't know what printf does, you need to get yourself a C/C++ reference guide to look at. This one might be okay :
http://www.cplusplus.com/
http://www.cplusplus.com/reference/clibrary/

You'll want to iterate over the list when you check each segment in the body list against the head segment to find out if the snake ran over itself and died.

Thomas Fjellstrom

I'd just like to say that 4.9.9.2 IS allegro 5, just a WIP/Alpha version.

And if you're having problems with the 5.0.x releases, don't hesitate to ask for help on the installation forum.

elamre

@Thomas,
Yeah, i could probably find the problem with google. but if it aint broken, dont fix it! :) And this allegro is working perfectly fine for me, and has all the functions i need ;)

OT,
First of all, thanks for the help :).
second off all:
I have no clue how to declare the grow member

#SelectExpand
1struct segment { 2 int x; 3 int y; 4 int growcounter; 5 void grow(); 6 7 void segment::grow() 8 { 9 --growcounter; 10 bool grow = (growcounter == 0); 11 if (growcounter == 0) {growcounter = 5;} 12 if (!body.empty() && !grow) { 13 body.erase(body.begin()); 14 } 15};

But still it says that class std::list<........> 'has no member named 'grow';
Also ive added my project again, maybe somebody can see what im doing wrong there?

Edgar Reynaldo

You don't need a separate grow function, and the growth variables should be in your snake class, not your segment class. To combine the examples I gave would give you :

void Snake::UpdateSnake() {
   --growcounter;
   bool grow = (growcounter == 0);
   if (growcounter == 0) {growcounter = time_between_growth;}
   if (!body.empty() && !grow) {
      body.erase(body.begin());
   }
   body.push_back(head);
   head.x += xdir;
   head.y += ydir;
   dead = CheckCollision(); 
}

growcounter and time_between_growth are both members of the snake class.

elamre

Ah oh ok, thx.
And the xdir and ydir are my snake x and y values rght?

Edgar Reynaldo

xdir and ydir are the x and y directions, or velocities, which I assume are 1, or however wide and tall a square or segment is. Only call Snake::Update when you want your snake to move (ie, not on every frame). Better yet, rename Update to Move, and make a new Update function :

void Snake::Update() {
   --update_counter;
   if (update_counter == 0) {
      Move();
      update_counter = time_til_next_update;
   }
}

elamre

I have no clue on how to do it anymore :s.

So what what you gave me:
An update function with variables update_counter and a set value time_til_next_update, I should declare this int's in my class i suppose.
This part of code i get, so my time till next update variable is basicly the speed if im correct?
Then the updateSnake, this one i dont get. It sets the tail values, right?
but i dont really get the grow (boolean) and the growCounter.
And how to use these functions. I think i should use the Update() function in my main, which will use the renamed Update_Snake (Move() ) function. But when do i have to use my move function then?

    void Snake::movePlayer()
    {
            clear_keybuf();
            
            if (key[KEY_UP]) moveUp();        
            else if (key[KEY_DOWN]) moveDown();    
            else if (key[KEY_RIGHT])moveRight();
            else if (key[KEY_LEFT])moveLeft();
            Draw();
}

Thanks for keeping so patient, and helping me so much! Much appreciated! :)

Edgar Reynaldo
#SelectExpand
1void Snake::CheckInputs() { 2 if (key[KEY_RIGHT]) { 3 xdir = 1;// or however wide a square / segment is 4 ydir = 0; 5 } 6 if (key[KEY_LEFT]) { 7 xdir = -1;// or the reverse of above 8 ydir = 0; 9 } 10 if (key[KEY_UP]) { 11 xdir = 0; 12 ydir = -1; 13 } 14 if (key[KEY_DOWN]) { 15 xdir = 0; 16 ydir = 1; 17 } 18}

That way, the direction will be set until the next time someone presses a key. You don't have to move right away when they press a key. It's up to you though. If you want them to move whenever you press a key, just add in Move() to the if clauses there after setting the direction.

while (!quit) {
   while (ticks < 1) {rest(1);}
   snake.CheckInputs();
   snake.Update();
   snake.Display();
}

elamre

Owh btw, how can i do the drawing for the tail? just by reading out all the segments? (so in a for loop, like i did for the array tail)

Edgar Reynaldo

Just iterate over them, like I showed you earlier.

elamre

Yeah, its the same idea :).
Now i reworked my whole code, no errors. runs. But i cant seem to be able to move the snakes head.
Ive used your exact pieces, using this for the drawing:
textout_ex( buffer, font, "@", head.x, head.y, makecol( 255, 0, 0), makecol( 0, 0, 0) );
and in the constructor(int x, int y):

 head.x=x;
 head.y=y;

So it draws the initial position of the head. But does not change when pressing a key! =/

int main(){
    Setup setup(640,480);
    Snake snake(200,100);
    while ( !key[KEY_ESC] ){
        snake.CheckInputs();
        snake.Update();
        snake.Draw();
        rest(50);
    }    
    return 0;  
}

my main (i know using rest is not very good, ill change that later on).

Edgar Reynaldo

Then there is something wrong with CheckInputs or Update. Post both of them as they are now.

elamre
#SelectExpand
1void Snake::Update() { 2 --update_counter; 3 if (update_counter == 0) { 4 Move(); 5 update_counter = 10; 6 } 7} 8 9void Snake::CheckInputs() { 10clear_keybuf(); 11 if (key[KEY_RIGHT]) { 12 xdir = 1;// or however wide a square / segment is 13 ydir = 0; 14 } 15 if (key[KEY_LEFT]) { 16 xdir = -1;// or the reverse of above 17 ydir = 0; 18 } 19 if (key[KEY_UP]) { 20 xdir = 0; 21 ydir = -1; 22 } 23 if (key[KEY_DOWN]) { 24 xdir = 0; 25 ydir = 1; 26 } 27}

It doesnt draw anything anymore now =/

Edgar Reynaldo
elamre said:

So it draws the initial position of the head. But does not change when pressing a key! =/

elamre said:

It doesnt draw anything anymore now =/

So is it drawing or isn't it?

Just post everything you have, in a zip file if necessary.

elamre

Here it is :).

I cant seem to attach the file :s.
And its not drawing no :(

http://www.2shared.com/file/rCTGYtr8/Try3.html

Edgar Reynaldo

First problem - your Snake::Snake(int,int) constructor doesn't initialize all it's values. This means update_counter,xdir,ydir,growcounter may all be zero, but they aren't guaranteed to be. So when you hit these lines the counter variables subtract endlessly and are never equal to zero :

#SelectExpand
1void Snake::Update() {
2 --update_counter;
3 if (update_counter == 0) {
4 Move(); 5 update_counter = 10; 6 } 7} 8 9void Snake::Move() {
10 --growcounter;
11 bool grow = (growcounter == 0);
12 if (growcounter == 0) {
13 growcounter = 5; 14 } 15 if (!body.empty() && !grow) { 16 body.erase(body.begin()); 17 } 18 body.push_back(head); 19 head.x += xdir; 20 head.y += ydir; 21 //dead = CheckCollision(); 22}

You still need to add in drawing the body to Snake::Draw as well.

elamre

AH that was my problem, totally forgot about that. Also i tried to make them all 0, but that wouldnt work either. But i forgot that it FIRST substracts from the value, and only after that checks. Hence my problem.
And about the tail, i know that i didnt have it implented yet, i wanted to make the movement work first :).
So now the tail!

Ok the tail works great, my only problem is the collision checking,

i use

while(snake.Dead() == false){
the functions to run the game are here
}
return 0;

where

bool Snake::Dead(){
return dead
}

However i die instantly! :(

EDIT:
Nvm, i figured it out. Played a bit with the code, came to an understanding that the snakes head is a structure as well. And made a startup timer.
So now the engine is done. Thank you really much! Youve helped me develop a simple snake game, and gave me a base understanding of how iterator work.

Still, even though this is done, why didnt my old code work? (other then the fact i used +1 on the wrong location)

Kind regards, ELmar

Edgar Reynaldo

Well, for one thing, you didn't keep track of how many segments were currently in the body, you checked against all 100 positions in the array regardless of current size. I never saw any more of your older code, so I couldn't say why it didn't work.

elamre

The rest is in my first post.
I know, i only checked it for 100 segments, but i could've changed that (for example by making a bigger array and copying all the positions in that). Or i could've made it with a vector.

EDIT
Also i have a question about the structure declared in the class. So i want to make it multiplayer for example. Then ill have to check if the player 2 head x and y coordinates dont touch that of player 1. THat is no problem. But then ill need another for statement witht the iterator, and have no clue how to do this.
This is what i had in mind:

#SelectExpand
1if(player1.getX() == player2.getX){ 2 if(player1.getY() == player2.getY()) 3 Collision = true; 4 } 5 6int Snake::getX() 7{ 8 return head.x; 9} 10//This was the easy part 11//But for the tail to check??? 12 for (list<player2.segment>::iterator it = player2.body.begin() ; it != player2.body.end() ; ++it) 13{ 14segment& s = *it; 15 if ((it->x == player1.x) && (it->y == player1.y)) { 16 Collision = true;} 17 }

I know the variables are private, but wouldnt it help if i made them public? that way i can easier check for a collision between tail and the head of another snake.

Edgar Reynaldo

bool CheckForSnakeCollision(const Snake& s1 , const Snake& s2) {
   // check head one against body two
   // check head two against body one
}

You don't need to make anything public or use a friend declaration either, but you could :

class Snake {
// ...
   friend CheckForSnakeCollision(const Snake& s1 , const Snake& s2);
};

Or you can use getter variables that return const references :

class Snake {
// ...
   const list<segment>& Body() {return body;}
   const segment& Head() {return head;}
};

elamre

hmm, the friend one i dont get really. so it grants a class the same permissions as another class? But why would i want that in this case?
The getter,
Yeah that basicly, and then use the collision check i stated in the previous post?
and the first option, this one is not really clear to me. Ill try to play around with it.

Also, isnt this very inefficient? checking for 2 lists if there are any collisions?

Edgar Reynaldo
elamre said:

hmm, the friend one i dont get really. so it grants a class the same permissions as another class? But why would i want that in this case?

You can either friend a function prototype which gives it access to private members of the class, or you can friend another class, doing the same thing.

Quote:

The getter,
Yeah that basicly, and then use the collision check i stated in the previous post?
and the first option, this one is not really clear to me. Ill try to play around with it.

Use Snake::Body() and Snake::Head() on opposing snakes, and iterate over each body segment, checking it against the head of the other snake.

Quote:

Also, isnt this very inefficient? checking for 2 lists if there are any collisions?

Imagine your snakes are 1000 segments long, and you only check each time a snake moves, that is only (60/ticks_per_move)*1000 checks every second. If you moved on every frame, that would be 60000*2 checks, or 120000. Computers are capable of millions of calculations per second, so don't worry about things like that yet. Only look into them if they are actually causing you problems. If you're really worried, then use a vector instead of a list. That gives you direct access instead of indirect access, but it also makes adding and removing segments take longer (which is still probably the least of your worries). In short, don't worry about it.

elamre

Haha, ok so the calculations should be just fine :).
Also for the checking part, i dont really see how i can read the values of the structure. Ive added the following functions (as you suggested).

const list<segment>& Body()  
{return body;}
const segment& Head() 
{return head;}

Now to retrieve the values of the following code:

bool CheckForSnakeCollision(const Snake& s1 , const Snake& s2) {
   for (s1.list<segment>::iterator it = s1.body.begin() ; it != s1.body.end() ; ++it) {
      if ((it->x == s2.get_x) && (it->y == s2.get_y)) {
                 return true;}
      }
   return false;
}
//where get_y and get_x:
int Snake::get_x()
{
   return head.x
}
//same goes for get_y, but then returning head.y

But obviously this wont work. I cant seem to find a way to call the first part of the for loop (The iterator part) from another object. Should this be done by getters as well???

Edgar Reynaldo

I forgot you need to make the getters 'const' :

#SelectExpand
1class Snake { 2 const list<segment>& Body() const {return body;} 3 const segment& Head() const {return head;} 4}; 5 6bool SnakeVsSnake(const Snake& s1 , const Snake& s2) { 7 const list<segment>& s1body = s1.Body(); 8 const list<segment>& s2body = s2.Body(); 9 const segment& s1head = s1.Head(); 10 const segment& s2head = s2.Head(); 11 for (list<segment>::const_iterator it = s1body.begin() ; it != s1body.end() ; ++it) { 12 if ((it->x == s2head.x) && (it->y == s2head.y)) { 13 return true;// head two hit body one 14 } 15 } 16 for (list<segment>::const_iterator it = s2body.begin() ; it != s2body.end() ; ++it) { 17 if ((it->x == s1head.x) && (it->y == s1head.y)) { 18 return true;// head one hit body two 19 } 20 } 21 return false; 22}

And you'll still need to check each snake head vs its own body as well.

elamre

Nice!
I have only but 2 questions, its about the functions and the use of objects.
Say ive made the following function:

void showCollision(const Snake& p1, const Snake& p2, const Snake& p3, const Snake& p4, Setup S1)
{
     if(SnakeVsSnake(p1,p2)==1){
        S1.DrawText("Player 2 hit player 1");
        p2.stopMoving();
        }
     else if(SnakeVsSnake(p1,p2)==2){
        S1.DrawText("Player 1 hit player 2");
        p1.stopMoving();
        }
}

Now this works fine! for 1 thing, i cant use p2/p1.stopMoving. This is because we are using a pointer here were we dont need that(right?) and thats also why it IS working for the S1.DrawText(const *char).
However, i only know 1 way to fix this, namely:

void showCollision(const Snake& p1, Snake P1, const Snake& p2, Snake P2, const Snake& p3, Snake P3, const Snake& p4, Sake P4, Setup S1)
{
     if(SnakeVsSnake(p1,p2)==1){
        S1.DrawText("Player 2 hit player 1");
        P2.stopMoving();
        }
     else if(SnakeVsSnake(p1,p2)==2){
        S1.DrawText("Player 1 hit player 2");
        P1.stopMoving();
        }
}

However then we get a code with 9 arguments! where i think only 5 are needed(at max)is there a way to go from const& p1 to p1?
also the error it gives:

main.cpp passing `const Snake' as `this' argument of `bool Snake::stopMoving()' discards qualifiers 

Edgar Reynaldo

If you need to modify a snake object (by using Snake::StopMoving()) then you should not be using a 'const Snake&', but a 'Snake&' only. That way you can still modify it. The warning came up because you are trying to call a non-const member function on a const Snake object.

To simplify code, you can use an array (this example uses a modified SnakeVsSnake called SnakeHitSnake that only checks if snake 1 hit snake 2 but not the other way around) :

void ShowCollision(....) {
   Snake& snakes[4] = {p1 , p2 , p3 , p4};// Or use a Snake* [4].
   const char* text[4] = {"p1" , "p2" , "p3" , "p4"};
   char* buf[1024];
   for (unsigned int i = 0 ; i < 4 ; ++i) {
      for (unsigned int j = i + 1 ; j < 4 ; ++j) {
         if (SnakeHitSnake(snakes[i] , snakes[j])) {
            snakes[i].StopMoving();
            sprintf(buf , "%s hit %s!" , text[i] , text[j]);
         }
      }
   }
}

elamre

Both of your methods arent working. Ive been looking around a bit, and cant find a proper way to do it.
I found a way that makes an array of new objects

     Snake* myObject[4];
     int i;
     Snake * objectPointer;
     for (i = 0; i < 50; i++)
     {
         objectPointer = new Snake;

     }

But then ill have to copy all the values of the reference object, for a dummy object. and i think this is wasting resources a bit much.
So my guess is that i need to get rid of the constant in order to make my reference object functions work. However i do not know how to easily do this.

Also a total different question, is there a function to draw a string in allegro? Ive been searching a lot lately, but all i can come up with are 2 functions which use const * char. And i cant seem to convert a string to a const char (Or i have to use functions which require runtime librarys, in which case i cant port it to for example linux anymore)

thx :)

Edgar Reynaldo

Define "aren't working".

Why are you trying to make 50 new snakes?

elamre said:

So my guess is that i need to get rid of the constant in order to make my reference object functions work. However i do not know how to easily do this.

Edgar said:

If you need to modify a snake object (by using Snake::StopMoving()) then you should not be using a 'const Snake&', but a 'Snake&' only. That way you can still modify it. The warning came up because you are trying to call a non-const member function on a const Snake object.

Just don't use the 'const' keyword - in the member function declaration, or in the function prototype.

elamre said:

Also a total different question, is there a function to draw a string in allegro? Ive been searching a lot lately, but all i can come up with are 2 functions which use const * char. And i cant seem to convert a string to a const char (Or i have to use functions which require runtime librarys, in which case i cant port it to for example linux anymore)

See the manual : http://www.allegro.cc/manual/5/
Specifically, the font addon : http://www.allegro.cc/manual/5/font.html

To get a const char* from a std::string, use the string::c_str() method :

string s = "Lalalala";
printf("%s\n" , s.c_str());

elamre

Yeah the 50 was a mistake :p. It was from an example i found.
Ill try and get of the const then :).
Also the s.c_str() is not working. Ive tried it before and googled it. But i cannot come up with a solution for that problem. And yes i did include <String> and even <CString> yet no succes! :(
And i know how to draw a char to the screen, but i want to be able to do it with strings :).

also not working = it cant convert const snake to snake*. Thats when using the following code:

Snake *snakes[4] = {p1,p2,p3,p4};

Edgar Reynaldo
elamre said:

Also the s.c_str() is not working. Ive tried it before and googled it. But i cannot come up with a solution for that problem. And yes i did include <String> and even <CString> yet no succes! :(

#include <string>
using std::string;// so you can refer to it as 'string', instead of 'std::string'.

Quote:

also not working = it cant convert const snake to snake*. Thats when using the following code:

Snake *snakes[4] = {p1,p2,p3,p4};

I told you, if you need to modify a Snake object, don't use a const reference or pointer. Also, you would have to use &p1, etc... to take the address of the objects in your example code.

Thread #608851. Printed from Allegro.cc