Tuesday, August 27, 2013

Snake Game in C++ : Part 1

Snake Game in CPP : Part 1
Snake Game In cPP : Part 1
After creating the blog I decided to post a tutorial to create a snake game in c++ using only text mode.
This program I created as a project for my college
This tutorial is for beginners.
This is not Complete Game because it dont't contain any title screen or score




For those who don’t know this is the game where player control a snake , when snake eat food it grows in size and get points . Game ends when snake collide with wall or collide with itself.

Requirement
DEV C++ IDE http://www.bloodshed.net/or use any other you like
Basic knowledge of c++ .


Get Started
Before creating our game we have to think what we are going to make. That is the snake game in c++ using only text mode.

Now look at the structure of snake game.
There is a snake which player controls
There is obstacle wall.
There is food which make snake grow in length.

Design of game

Lets Program the game

Launch Dev C++ and create new c++ file. To keep it simple it will be a single file program.

First include the basic header file.

#include <iostream>
#include <windows.h>
#include <stdlib.h>
#include <conio.h>

using namespace std;

Now create a stucture to store our snake position this is double linklist I will explain it later.

typedef struct tailpos
{
int x;   // x cordinate of the snake
int y;  // y cordinate of the snake
struct tailpos *next;   // pointer to next node
struct tailpos *prev;   // pointer to previous node 
} tail;

// d used to set the direction
int d=4; // up = 1 , down = 2 , left =3 , right = 4;

A variable ‘d’ which will store direction .you can declare this variable inside class also.

The snake class

class snake
{
public:  
      
       int foodx,foody;             // position of the food
      
       HANDLE console_handle; // handle for the console      
       COORD cur_cord;        // COORD struct to keep the coordinate
      
       /*
       tail struct ==>
                      start = pointer to first part of body
                      current = pointer to last node
                      newtail = temp node for new tail
      
       */
      
       tail *start,*current,*newtail;
       snake();  // construct to initalise tail variable to null
       void insert(int x , int y); // insert the body of snake append to next node
       void draw();    // draw the snake
       void drawWall(); // draw the wall
       void move();    // control the movement
       bool collision();  //check snake collision with itself or wall
       void drawfood(int x=0); // draw food to new position if x==1     
     
};


Looking at the code you can understand what it will do

For storing snake position you can use 2d array. But i prefer linklist . For those who don’t know let me explain it.

 By using this data structure we can easily store and retrive snake parts position
For more information ... http://en.wikipedia.org/wiki/Linked_list


Now implement snake class methods:

  The constructor
   
snake::snake()
{
start = NULL; // start node point to null
current = NULL; // same
newtail = NULL;  // same 
console_handle=GetStdHandle( STD_OUTPUT_HANDLE );  // getting console handle

foodx=12;                    // starting food positions
foody=14;   
}

  The drawwall method this will draw boundary for our snake game

    void snake::drawWall()
{
     /*
     for drawing wall you can use variables to store wall starting and ending position
     */
     // draw left column
     cur_cord.X=0;
     for(int y=0;y<=30;y++)
           {
            cur_cord.Y=y;
            SetConsoleCursorPosition(console_handle,cur_cord);
            cout << '#';
           }
    // draw top row  
       cur_cord.Y=0;
       for(int x=0;x<=30;x++)
           {
            cur_cord.X=x;
            SetConsoleCursorPosition(console_handle,cur_cord);
            cout << '#';
           }
    // draw right column
     cur_cord.X=30;
     for(int y=0;y<=30;y++)
           {
            cur_cord.Y=y;
            SetConsoleCursorPosition(console_handle,cur_cord);
            cout << '#';
           }
          
     // draw bottom row
     cur_cord.Y=30;
       for(int x=0;x<=30;x++)
           {
            cur_cord.X=x;
            SetConsoleCursorPosition(console_handle,cur_cord);
            cout << '#';
           }   
       

}  


Draw the food for our snake

void snake::drawfood(int x)
{
tail *tmp;
tmp=start->next;
if(x==1) // draw new food
        {
                         foodx=rand()%20+7;  // rand number between 2 to 27
                         foody=rand()%20+7;

                        /*check if food not created on snake body
                         so we will loop through all snake node and check
                         if snake body pos meet with food position
                         if yes the generate new position else continue
                         */

                         while(tmp->next!=NULL)
                       {
                            if(foodx == tmp->x && foody == tmp->y)
                            {
                            drawfood(1);
                            }                                                
                                              
                            tmp=tmp->next;                
                       }                    
                                         
        }
}

Increasing snake length . when snake eat food its length should increase . For this
We have insert method
void snake :: insert(int x , int y)
{
     // check if start is null
     if(start == NULL)
     {
     newtail = new tail;
     newtail->x=x;
     newtail->y=y;
     newtail->next=NULL;
     newtail->prev=NULL;
     start=newtail;
     current=newtail;
     }
     else            // insert new node to start node
     {
     newtail = new tail;
     newtail->x=x;
     newtail->y=y;
     newtail->next=NULL;
     newtail->prev=current;  
     current->next=newtail;
     current=newtail;
     }
     
}

This method just append a new tailposition structure to the tailpos structure.

Here ‘start’ varaible points to node1 where as ‘current’ variable points to node3 i.e the last node.

After appending the new node.
After appending the new node the ‘current’ variable points to newely created last node i.e node4.

Now the best part how to move snake ? So the tails follow the head.
You know in game tails follow the head every where it goes.
So first Question how to move snake head?
Ans : Simple we have to print snake head at new cordinate at console every cycle.
         We can increment the current X,Y position of console. To move snake in specified direction.

Q: How to make tail follow the head ?
Ans : The tailpos structure which keep position of every part of snake. We just have to move value of head to its tail and value of first tail should move to next tail till last and increment position of head . In simple way remove value of last tail put previous tail value in it .

This way position of snake parts is shifted

The code to move:

void snake::move()
{
tail *tmp,*cur; // cur no use

tmp =current; // point tmp to last node
//cur =start->next;

while(tmp->prev!=NULL)
                      {
                      tmp->x=tmp->prev->x;        // copy val from revious to last
                      tmp->y=tmp->prev->y;
                      tmp=tmp->prev;              // set tmp to previous node
                     
                      }
/*
check for the value of d in order to change its direction
*/
if(d==1)
start->y--;

if(d==2)
start->y++; 

if(d==3)
start->x--;

if(d==4)
start->x++;                 
                     
}

The move method is called every cycle.

Collision Detection:

To check collision we can simpily compare snake head posiotion with
1)     Its tail
2)     Wall
3)     Food

Code to collision:
bool snake::collision()
{
                       tail *tmp;
                       tmp=start->next;
                       //check collision with itself
                       while(tmp->next!=NULL)
                       {
                            if(start->x == tmp->x && start->y == tmp->y)
                            return true;
                                                    
                                              
                            tmp=tmp->next;                
                       }
                       //check collision with food
                       if(start->x == foodx && start->y == foody)
                       {
                       insert(foodx,foody);          
                       drawfood(1);  // draw food at new position
                      
                       }
                       //check collision with wall
                         //collision top
                          for(int x=0;x<=30;x++)
                           {
                                  if(start->x == x && start->y == 0)
                                  {
                                         return true;                
                                  }
                            
                           }
                           //collision left
                          for(int y=0;y<=30;y++)
                           {
                                  if(start->x == 0 && start->y == y)
                                  {
                                         return true;                
                                  }
                            
                           }
                            //collision right
                          for(int y=0;y<=30;y++)
                           {
                                  if(start->x == 30 && start->y == y)
                                  {
                                         return true;                
                                  }
                            
                           }
                           //collision bottom
                          for(int x=0;x<=30;x++)
                           {
                                  if(start->x == x && start->y == 30)
                                  {
                                         return true;                
                                  }                            
                           }                         
                                                return false;
     }

Rendering the game:
void snake::draw()
{

drawWall(); // draw wall

tail *tmp;
tmp=start;

while(tmp!=NULL)
                      {
                        cur_cord.X=tmp->x;
                        cur_cord.Y=tmp->y;
                        SetConsoleCursorPosition(console_handle,cur_cord);
                        //cout << "*" << cur_cord.X <<"-" <<  cur_cord.Y  ;
                        cout << "#"; // print any value you want
                        tmp=tmp->next;                          
                      }
//draw the food
 cur_cord.X=foodx;
 cur_cord.Y=foody;
 SetConsoleCursorPosition(console_handle,cur_cord);
  cout << "?"; // print any value you want
 
}

This method prints wall , snake , food at the console.

The game loop:
The game loop which is repeated every time to draw snake,take input and process , and detect collision
int main()
{
snake ob;            // snake object
ob.insert(10,5); // insert snake body
ob.insert(10,4); // insert snake body
ob.insert(10,3);
ob.insert(10,2);

//ob.draw();
int dir=1; // initial direction
while(1) // main loop
{
ob.draw(); // draw food snake wall
Sleep(100); // wait after draw better to use a timer
system("CLS"); // clearthe screen
 

          if(ob.collision())// check for collision
          {
            cout << " you died ";
            break;               
            }

            if(kbhit())// check if keyboard key is pressed
            {
             switch(getch())
             {
             case 'w':d=1;
             break;
             case 's':d=2;
             break; 
             case 'a':d=3;
             break;
             case 'd':d=4;
             break;
             case 'm':ob.insert(10,7);
             break;                               
              
               }
             

               }
              
 ob.move();  // move the snake
}


// to wait for user input
getch();
return 0;   
}

I have used W,A,S,D key to control .
This is just the core of the game there is no welcome screen nor end screen . and there is score .
You can implement these your self.

The main problem in the game screen flicker every time. This problem can be elminated which I will discuss in Next Part. OR implement it your self thats easy.

I am sorry because The post is not formatted nor any synatx hilighter. I I am just getting started with blog.Maybe I will update it later

Here is the complete code copy and paste it



#include <iostream>
#include <windows.h>
#include <stdlib.h>
#include <conio.h>

using namespace std;

//==== struct list;
typedef struct tailpos
{
int x;   // x cordinate of the snake
int y;  // y cordinate of the snake
struct tailpos *next;   // pointer to next node
struct tailpos *prev;   // pointer to previous node  
} tail;

// d used to set the direction
int d=4; // up = 1 , down = 2 , left =3 , right = 4;


/*
SNAKE CLASS 
            contain variable of tailpos structure
            this class handle movement of the snake
            contain the draw function

*/
class snake
{
public:
       int wallsX, wallsY;          // wall start position
       int walleX, walleY;          // wall end position
       int wall[10][5];
       
       int foodx,foody;
       
       HANDLE console_handle; // handle for the console       
       COORD cur_cord;        // COORD struct to keep the coordinate
       
       /*
       tail struct ==>
                      start = pointer to first part of body
                      current = pointer to last node
                      newtail = temp node for new tail
       
       */
       
       tail *start,*current,*newtail;        
       snake();  // construct to initalise tail variable to null
       void insert(int x , int y); // insert the body of snake append to next node
       void draw();    // draw the snake
       void drawWall(); // draw the wall
       void move();    // control the movement
       bool collision();  //check snake collision with itself or wall
       void drawfood(int x=0);
       
      
};

// implement snake class
snake::snake()
{
start = NULL;
current = NULL;
newtail = NULL;    
console_handle=GetStdHandle( STD_OUTPUT_HANDLE );  // getting console handle 

foodx=12;
foody=14;    
}

void snake :: insert(int x , int y)
{
     // check if start is null
     if(start == NULL)
     {
     newtail = new tail;
     newtail->x=x;
     newtail->y=y;
     newtail->next=NULL;
     newtail->prev=NULL;
     start=newtail;
     current=newtail;
     }
     else            // insert new node to start node
     {
     newtail = new tail;
     newtail->x=x;
     newtail->y=y;
     newtail->next=NULL;
     newtail->prev=current;   
     current->next=newtail; 
     current=newtail; 
     }

     
}

/*
move :
      contain main logic to move the snake by incrementing and decrementing 
      the X or Y cordinate
      position of first part is moved to the next part. copying is done in Reverse order that 
      is from last to first .
      Last node position is updated with previous node position and at last. Start node Cordinate
      X or y is inc or dec.

*/

void snake::move()
{
tail *tmp,*cur; // cur no use 

tmp =current; // point tmp to last node
//cur =start->next;

while(tmp->prev!=NULL)
                      {
                      tmp->x=tmp->prev->x;        // copy val from revious to last
                      tmp->y=tmp->prev->y;
                      tmp=tmp->prev;              // set tmp to previous node
                      
                      }
/*
check for the value of d in order to change its direction
*/
if(d==1)
start->y--; 

if(d==2)
start->y++;  

if(d==3)
start->x--;

if(d==4)
start->x++;                  
                      
}

/*
draw :
      draw snake part according to their position
      traverse the node from start to end
*/
void snake::draw()
{
     /*
cur_cord.X=10;
cur_cord.Y=5;

SetConsoleCursorPosition(console_handle,cur_cord);
cout << "hi";   
*/

drawWall(); // draw wall

tail *tmp;
tmp=start;

while(tmp!=NULL)
                      {
                        cur_cord.X=tmp->x;
                        cur_cord.Y=tmp->y;
                        SetConsoleCursorPosition(console_handle,cur_cord);
                        //cout << "*" << cur_cord.X <<"-" <<  cur_cord.Y  ; 
                        cout << "#"; // print any value you want
                        tmp=tmp->next;                          
                      }
//draw the food
 cur_cord.X=foodx;
 cur_cord.Y=foody;
 SetConsoleCursorPosition(console_handle,cur_cord);
  cout << "?"; // print any value you want
  
}

void snake::drawWall()
{
     int size = sizeof(wall)/sizeof(wall[0][0]);
     // draw left column
     cur_cord.X=0;
     for(int y=0;y<=30;y++) 
           {
            cur_cord.Y=y;
            SetConsoleCursorPosition(console_handle,cur_cord);
            cout << '#';
           }
    // draw top row   
       cur_cord.Y=0;
       for(int x=0;x<=30;x++) 
           {
            cur_cord.X=x;
            SetConsoleCursorPosition(console_handle,cur_cord);
            cout << '#';
           }
    // draw right column 
     cur_cord.X=30;
     for(int y=0;y<=30;y++) 
           {
            cur_cord.Y=y;
            SetConsoleCursorPosition(console_handle,cur_cord);
            cout << '#';
           }
           
     // draw bottom row 
     cur_cord.Y=30;
       for(int x=0;x<=30;x++) 
           {
            cur_cord.X=x;
            SetConsoleCursorPosition(console_handle,cur_cord);
            cout << '#';
           }
    
          
}

void snake::drawfood(int x)
{
tail *tmp;
tmp=start->next;
if(x==1) // draw new food
        {
                         foodx=rand()%20+7;  // rand number between 2 to 27
                         foody=rand()%20+7;
                         // check if food not created on snake body
                         while(tmp->next!=NULL)
                       {
                            if(foodx == tmp->x && foody == tmp->y)
                            {
                            drawfood(1); // draw food at new position 
                            }
                                                     
                                               
                            tmp=tmp->next;                 
                       }
                         
                         
                         
        }
     
}


/*
decect if snake collided with it self or wall or food
*/
bool snake::collision()
{
                       tail *tmp;
                       tmp=start->next;
                       //check collision with itself
                       while(tmp->next!=NULL)
                       {
                            if(start->x == tmp->x && start->y == tmp->y)
                            return true;
                                                     
                                               
                            tmp=tmp->next;                 
                       }
                       //check collision with food
                       if(start->x == foodx && start->y == foody)
                       {
                       insert(foodx,foody);           
                       drawfood(1);
                       
                       }
                       //check collision with wall
                         //collision top
                          for(int x=0;x<=30;x++) 
                           {
                                  if(start->x == x && start->y == 0)
                                  {
                                         return true;                 
                                  }
                             
                           }
                           //collision left
                          for(int y=0;y<=30;y++) 
                           {
                                  if(start->x == 0 && start->y == y)
                                  {
                                         return true;                 
                                  }
                             
                           }
                            //collision right
                          for(int y=0;y<=30;y++) 
                           {
                                  if(start->x == 30 && start->y == y)
                                  {
                                         return true;                 
                                  }
                             
                           }
                           //collision bottom
                          for(int x=0;x<=30;x++) 
                           {
                                  if(start->x == x && start->y == 30)
                                  {
                                         return true;                 
                                  }
                             
                           }
                           
                         
                       return false;  
     
}

//===== main function
int main()
{


snake ob;            // snake object
ob.insert(10,5); // insert snake body
ob.insert(10,4); // insert snake body
ob.insert(10,3);
ob.insert(10,2);

//ob.draw();
int dir=1; // initial direction
while(1) // main loop
{
ob.draw(); // draw food snake wall
Sleep(100); // wait after draw better to use a timer
system("CLS"); // clearthe screen 
  

          if(ob.collision())// check for collision
          {
            cout << " you died ";
            break;                
            }

            if(kbhit())// check if keyboard key is pressed
            {
             switch(getch())
             {
             case 'w':d=1;
             break;
             case 's':d=2;
             break;  
             case 'a':d=3;
             break;
             case 'd':d=4;
             break; 
             case 'm':ob.insert(10,7);
             break;                                
               
               }
              

               }
               
 ob.move();  // move the snake
}


// to wait for user input
getch();
return 0;    
}























3 comments: