Wednesday September 22, 2021
 home | about | contact | Donations
 Programming.. With C# and .Net...

Snake

(Creating a simple Demo Game)

Its on all the mobile phones today...such a simple game...but it can be really addictive :)  But how do we go about doing it in C#?  Well hold on...I'll take you through some simple principles of building your own snake game.

Lets take a look at how our snake will move...from there we can put a simple peudo code approximation for the movement of our snake together....once where sure how we are going do do that...the rest is easy.

As we can see...a direction value is important....as you would expect for a snake...the head moves in the direction we are moving, and the body simply trails the heads old position.  Using this we can say:

BODY[1] = BODY[0];

...

Of course that won't work....why?  Well we would need to save BODY[0] before we write over it, as our following line needs it.  A better approach is to update our snake from the tail...and discard the last value...as we know from the sketch above...the tails old position is just thrown away.

With this, lets make the assumption that our snake is an array....where BODY[0] will be the head and it will have a length BODYLENGTH....which tells us BODY[BODYLENGTH-1] is its tail.....lets do a piece of code for that:

 // Assume we have a body length of 10 int BodyLength=10; for(int i=BodyLength; i>0; i--) {       Body[i] = Body[i-1]; }// End for loop i // Update our new snakes head pos Body[0] = Body[0]+Dir;

This is our snakes movement......we would call this each second or half a second and update a single position of movement.... we would adjust the time delay between updates for faster or slower movement.

For our first piece of code, I'll do a small program that uses the cursor keys....but it will only move when the key is pressed.... so you press the up key and the snake moves up one section....this way we can check that our snakes body is doing it all properly.

 DownloadSourceCode   Our first piece of code, and if I say so myself, its a dam great one.  Of course its only simple...but all the best idea's start simple.  Once you have the snake moving around the rest is easy.  Its just a matter of collision detection and graphics from then on. Those who run the demo might notice that the screen flickers now and then when we move our snake around...this is because we arn't double buffering our background...so you see the background getting cleared then repainted.  Don't worry...we'll fix that in a bit.

How our code works is easy if you break up what happens.... we first setup our array of Rectangle values called Body in the stSnake structure to a set of fixed values....these value aren't anything special....I just chose a point then incremented bock after bock down from the head until I'd intilised all enough snake body parts for the snake's length.

 Code: snake.cs using System; using System.Drawing; using System.Windows.Forms;     // We have a seperate snake structure, which tells us the snakes position // etc... this is so we could have an array of stSnake's...for multiplayer // later on if we wanted :) public struct stSnake {       public Rectangle   Dir;        // Direction our snake is moving       public int         iLength;    // How many squares long it is       public Rectangle[] Body;       // Array of positions of each body part       public int         BodySize;   // Size of each body part       }// End stSnake     class CSnakeGame : System.Windows.Forms.Form {       public stSnake m_Snake;         private void OnPaint(object sender, System.Windows.Forms.PaintEventArgs e) [...]           void MoveBody()       {             Rectangle Dir = m_Snake.Dir;             Dir.X = m_Snake.Dir.X * m_Snake.BodySize;             Dir.Y = m_Snake.Dir.Y * m_Snake.BodySize;               // Body Length             int BodyLength=m_Snake.iLength;             for(int i=BodyLength; i>0; i--)             {                   m_Snake.Body[i] = m_Snake.Body[i-1];               }// End for loop i             // Update our new snakes head pos             m_Snake.Body[0].X = m_Snake.Body[0].X + Dir.X;             m_Snake.Body[0].Y = m_Snake.Body[0].Y + Dir.Y;         }// End of MoveBody()         private void AKeyDown(object sender, System.Windows.Forms.KeyEventArgs e)       {             string result = e.KeyData.ToString();             switch (result)             {                   case "Left":                         m_Snake.Dir.X = -1;  m_Snake.Dir.Y = 0;                         break;                   case "Right":                         m_Snake.Dir.X = 1;   m_Snake.Dir.Y = 0;                         break;                   case "Up":                         m_Snake.Dir.X = 0;   m_Snake.Dir.Y = -1;                         break;                   case "Down":                         m_Snake.Dir.X = 0;   m_Snake.Dir.Y = 1;                         break;                   default:                         break;             }// End switch(..)               MoveBody();   // Update our snakes new position             Invalidate(); // Invalidate our screen so it gets redrawn       }// End of AKeyDown(..)           private void InitializeComponent()       {             this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.AKeyDown);             this.Paint   += new System.Windows.Forms.PaintEventHandler(this.OnPaint);                 m_Snake.Dir.X = 0;             m_Snake.Dir.Y = 1;             m_Snake.iLength=10;             m_Snake.Body = new Rectangle[50];             m_Snake.BodySize = 10;             int xstartpos = 30;             int ystartpos = 30;             for(int i=0; i

The snake only moves when we press a key, so each time you press a cursor key...(Up,Down,Left or Right) the snake moves in that direction once.... in the final version though, we would call the MoveBody() and Drawing code from our timer...so the snake keeps moving in that direction until we press a key and change its direction.

Going off screen

Well our snake moves around okay...but what about when it reaches the edges?  Should that indicate that the games over, or should it scroll from the right to the left hand side?  Well I decided to have it scroll...as if I wanted to keep the snake on screen I would only have to put a wall around the edge and check for a collision.  So to do this scrolling effect, we only need to add in an additional few lines in our MoveBody() function as shown:

 Code: snake.cs ....       void MoveBody()       {             Rectangle Dir = m_Snake.Dir;             Dir.X = m_Snake.Dir.X * m_Snake.BodySize;             Dir.Y = m_Snake.Dir.Y * m_Snake.BodySize;               // Body Length             int BodyLength=m_Snake.iLength;             for(int i=BodyLength; i>0; i--)             {                   m_Snake.Body[i] = m_Snake.Body[i-1];               }// End for loop i             // Update our new snakes head pos             m_Snake.Body[0].X = m_Snake.Body[0].X + Dir.X;             m_Snake.Body[0].Y = m_Snake.Body[0].Y + Dir.Y;               // New ** New ** New ** New ** New ** New ** New ** New ** New **             // If our snakes head is outside the screen, then we scroll it             // to the opposite side             Rectangle r = this.ClientRectangle;             // Check the left and right first             if( m_Snake.Body[0].X < 0 ) m_Snake.Body[0].X = r.Right;             if( m_Snake.Body[0].X > r.Right ) m_Snake.Body[0].X = 0;             // Check the top and bottom             if( m_Snake.Body[0].Y < 0 ) m_Snake.Body[0].Y = r.Bottom;             if( m_Snake.Body[0].Y > r.Bottom ) m_Snake.Body[0].Y = 0;         }// End of MoveBody() ...

Growing?

If our snake eats certain types of food it will go faster...but also, certain foods will make our snake grow in length.  So we need to do a Grow(int iLength) function...and we can call it, and say how much we want our snake to grow by.  I chose to pass a length, as I thought that we could have it grow by larger amounts for advanced levels?  ...well it gives us a little flexibility for later on, we can always just fix it at one if we want :)

Hmmm....now I looked at the code...and I thought to myself, its not going to be easy... as for example, if we want to have the snake grow by 2...we don't want its tail or head to suddenly shoot out by 2...it could hit an obstacle or cause a problem.  So I decided that the snake will grow from its tail, and will only grow from the old positions...so if for example we grow by 3...this means that the tail will seem not to move for 3 steps...so the snake grows by 3 body parts.... it means that I had to add a bit of code to the MoveBody() function...

So altering the MoveBody() and Grow(..) function.... I added in the next part of the code.  One other thing...I added a iGrowLength variable to the stSnake structure...so it can determine if our snake needs to grow :)

I've pasted all the code below...with the exception of some of the code being commented out so its not to large...but you an download it if you click on the code link....  It should be the same as before but for the added grow code.

 Code snake.cs using System; using System.Drawing; using System.Windows.Forms;     // We have a seperate snake structure, which tells us the snakes position // etc... this is so we could have an array of stSnake's...for multiplayer // later on if we wanted :) public struct stSnake {       public Rectangle   Dir;        // Direction our snake is moving       public int         iLength;    // How many squares long it is       public Rectangle[] Body;       // Array of positions of each body part       public int         BodySize;   // Size of each body part             public int         iGrowLength; }// End stSnake     class CSnakeGame : System.Windows.Forms.Form {       public stSnake m_Snake;         private void OnPaint(object sender, System.Windows.Forms.PaintEventArgs e)[...]           void MoveBody()       {             Rectangle Dir = m_Snake.Dir;             Dir.X = m_Snake.Dir.X * m_Snake.BodySize;             Dir.Y = m_Snake.Dir.Y * m_Snake.BodySize;               // Make sure the end element is correct incase we grow             m_Snake.Body[m_Snake.iLength] = m_Snake.Body[m_Snake.iLength-1];               // Body Length             int BodyLength=m_Snake.iLength;             for(int i=BodyLength; i>0; i--)             {                   m_Snake.Body[i] = m_Snake.Body[i-1];               }// End for loop i             // Update our new snakes head pos             m_Snake.Body[0].X = m_Snake.Body[0].X + Dir.X;             m_Snake.Body[0].Y = m_Snake.Body[0].Y + Dir.Y;               // If our snakes head is outside the screen, then we scroll it             // to the opposite side             Rectangle r = this.ClientRectangle;             // Check the left and right first             if( m_Snake.Body[0].X < 0 ) m_Snake.Body[0].X = r.Right;             if( m_Snake.Body[0].X > r.Right ) m_Snake.Body[0].X = 0;             // Check the top and bottom             if( m_Snake.Body[0].Y < 0 ) m_Snake.Body[0].Y = r.Bottom;             if( m_Snake.Body[0].Y > r.Bottom ) m_Snake.Body[0].Y = 0;               // Snake grows as it moves             if( m_Snake.iGrowLength > 0 )             {                   // Add an extra block to the end of our snake                   m_Snake.iLength++;                   m_Snake.iGrowLength--;             }// End if         }// End of MoveBody()         void Grow(int iGrowLength)       {             m_Snake.iGrowLength += iGrowLength;                   }// End of Grow(..)         private void AKeyDown(object sender, System.Windows.Forms.KeyEventArgs e)       {             string result = e.KeyData.ToString();             switch (result)             {                   case "Left":                         m_Snake.Dir.X = -1;  m_Snake.Dir.Y = 0;                         break;                   case "Right":                         m_Snake.Dir.X = 1;   m_Snake.Dir.Y = 0;                         break;                   case "Up":                         m_Snake.Dir.X = 0;   m_Snake.Dir.Y = -1;                         break;                   case "Down":                         m_Snake.Dir.X = 0;   m_Snake.Dir.Y = 1;                         break;                   default:                         break;             }// End switch(..)               MoveBody();   // Update our snakes new position             Invalidate(); // Invalidate our screen so it gets redrawn               // DEBUG ** DEBUG ** DEBUG ** DEBUG ** DEBUG ** DEBUG ** DEBUG **             // To test that our grow function works, I call the functino here...but             // it means that the snake never stops growing...it won't be here             // in our release version!             Grow(1);       }// End of AKeyDown(..)           private void InitializeComponent()       {                         ......commented out code                         m_Snake.iGrowLength = 0;       }// End of InitializeComponent()         public CSnakeGame()[...]         // Our program entry point.       static void Main() [...] }

Timer - moving on its own

Well our snake at the moment only moves when we press one of the cursor keys.... but we need to have it move by itself.  Which brings us to the timer.  We set up a timer function which gets called one or twice a second, depending upon or snakes speed.  The timer function will call our MoveBody() and OnPaint() function.... Simple?  Each little addition brings us closer to our finished game :)

 Code: snake.cs ...... [Cutting of the changed code].......                     case "Down":                         m_Snake.Dir.X = 0;   m_Snake.Dir.Y = 1;                         break;                   default:                         break;             }// End switch(..)         }// End of AKeyDown(..)         private void OnTimer(object sender, System.EventArgs e)       {             MoveBody();   // Update our snakes new position             Invalidate(); // Invalidate our screen so it gets redrawn         }// End of OnTimer(..)           private void InitializeComponent()       {             this.KeyDown     += new System.Windows.Forms.KeyEventHandler(this.AKeyDown);             this.Paint       += new System.Windows.Forms.PaintEventHandler(this.OnPaint);                         // --- Timer --- //             this.Ticker = new System.Windows.Forms.Timer();             this.Ticker.Interval = 200;             this.Ticker.Tick += new System.EventHandler(this.OnTimer);             Ticker.Enabled = true;               m_Snake.Dir.X = 0;             m_Snake.Dir.Y = 1; ......

With those altered lines of code, we now have a zooming snake!  Yup...watch it go ..wahooo....try changing the timer interval speed from 200 to 25, and its like a speeding bullet then :)

Food And Collision Detection

At random positions on the screen we need to put food ... but we also need to check if our snake has eaten them, the reason for the collision detection.  So we need a food member variable...now rather than put it in the class...I decided to put it in a new structure called stGameData.... which will hold global game info, like game over, number of food ...food positions etc.

Next, how is our food going to appear..and where will we do the checking of being eaten?  Well I thought that for every five moves a new food would appear....up to a max of 2 foods... so in our OnTimer(..) function, we'll call a FoodUpdate() function..which will do all the work in there for us :)

Well its a mad piece of code the FoodUpdate() one...loads of checking for this...and checking for that... as we can't randomly place food on top of our snake...and we need to check if our snakes eaten it..... but most of the code is checking.

 Code: snake.cs // snake.cs // csc /t:winexe snake.cs   using System; using System.Drawing; using System.Windows.Forms;     // We have a seperate snake structure, which tells us the snakes position // etc... this is so we could have an array of stSnake's...for multiplayer // later on if we wanted :) public struct stSnake {       public Rectangle   Dir;        // Direction our snake is moving       public int         iLength;    // How many squares long it is       public Rectangle[] Body;       // Array of positions of each body part       public int         BodySize;   // Size of each body part             public int         iGrowLength;// How much it needs to grow by }// End stSnake   public struct stGameData {       public int         iNumFoods;       public Rectangle[] Food;       public bool[]      bFoodAlive;       public int         iFoodSize;   }// End stGameData   class CSnakeGame : System.Windows.Forms.Form {       private stSnake                      m_Snake; // Snake data       private stGameData                   m_Data;  // Game Information       private System.Windows.Forms.Timer   Ticker;  // Our game timer         private void OnPaint(object sender, System.Windows.Forms.PaintEventArgs e)       {             ....... cut out to keep down on size               // --- Draw Food --- //             int iNumFoods = m_Data.iNumFoods;             for(int i=0; i

One of the main reasons I use the 'Rectangle' structure a lot, is so I can use the simple collision detection of 'IntersectsWith(..)' function to check if our snake or food collide.  I can also use this to check if our snakes head collides with its head later on.

GameOver

At the moment the way it is, our snake is importal....nothing can kill it...it can eat and eat and go anywhere...  So we can't have that... a rule of snakes is that it's head can't make contact with its body.  So I've added a function called 'ChkGameOver()' to our OnTimer(..) function...so we constantly check in there to see if our snakes head has touched its body...if so, I set a varible called bGameOver in our stGameData structure to true!  Telling us its all over!

One final thing.... using this GameOver information, if we find that its all over...we display a 'Game Over' message and pause the game until the player presses Enter.  Its only simple but it gives the game a purpose.  We've not added scores yet... but we can do that in a bit.

 Code: snake.cs .....   public struct stGameData {       public bool        bGameOver;  // If false, then the game is over         public int         iNumFoods;       public Rectangle[] Food;       public bool[]      bFoodAlive;       public int         iFoodSize;   }// End stGameData   class CSnakeGame : System.Windows.Forms.Form {       private stSnake                      m_Snake; // Snake data       private stGameData                   m_Data;  // Game Information       private System.Windows.Forms.Timer   Ticker;  // Our game timer         private void OnPaint(object sender, System.Windows.Forms.PaintEventArgs e)       {             Rectangle r = this.ClientRectangle;               System.Drawing.Graphics g = e.Graphics;             // Clear the screen to a specific colour             //g.FillRectangle(System.Drawing.Brushes.Blue, r);               /* --- Draw Snake ---- */             int BodyLength=m_Snake.iLength;             for(int i=0; i

Score And Reset

One final thing is needed for our Snake game, is the ability to keep track of score and when the game is over to restart our game back to its starting conditions.  This is simply a matter of putting the data initialisation code from "InitializeComponent()" function into a ResetGame(..) function that we can call when we need to set the game all back to its start.

 DownloadSourceCode   I've added a few lines of code so that you can see the score, and put in a ResetGame(..) function in... so now when you die it displays the gameover message....waiting for you to press Enter and start the game again from scratch.   I've not added any speedups....or different food types...which shouldn't be to hard to add on.  Just use different colours for the different foods...also it might be better to have bonus foods that only appear for a short amount of time :)

What Next?

Well the next step would be to use a back buffer for you graphics...instead of rendering directly to the screen surface.  Once you have that done, I would recommend improving the graphics... the game looks fantastically better if you where to use small bitmaps for the food...and gave you snake a head and a tail :)

A bonus would be to do mazes.... so that level 2 would have obstacles that you must avoid...walls etc....tunnels...

And for the final elite version, a multi-player option would be good.  Which isn't to hard to implement....as we have "stSnake m_Snake" currently....for a 4 player we could change this to an array "stSnake m_Snake[4]"...... of course how everyone is going to share the keyboard I don't know.  Definitely worth making it into a network game?  Great stuff to improve you networking skills.

 Visitor: 9534626 { 229.27.38.75 } Copyright (c) 2002-2020 xbdev.net - All rights reserved. Designated articles, tutorials and software are the property of their respective owners.