Snake - using the GDI+
Written by Mike James   
Tuesday, 04 August 2009
Article Index
Snake - using the GDI+
Getting started
Persistent graphics
Snake

Snake

 

snake1

The red curve runs around the screen in a  snake-like way

 

The game of Snake is a a long-time favorite mainly due to the way it works well on portable devices such as mobile phones. It is not difficult to program and it’s an easy way of finding out more about the GDI and graphics in general.

So start a new C# Windows project called “Snake” and place a timer control on it. The “screen” is going to be divided up into a grid of blocks representing a possible location for the snake’s head or body segment. In this case each block is 10x10 and the screen is 50 by 50 blocks. To make this information available to the rest of the program we need some global constants variables and object:

public partial class Form1 : Form
{
const int cx=10,cy=10;
Size CSize = new Size(cx, cy);
Size SSize=new Size(50*cx ,50*cy);
int vx, vy;

Notice that we are using Size objects to store the width and height of a shape. The variables vx and vy are used to store the snake’s horizontal and vertical velocity.

The snake is made up of a list of filled rectangles and the location and size of each is stored in an array of Rectangle objects:

Rectangle[] Snake = new Rectangle[50];
int Head = 15, Tail = 0;

The head of the snake is initially stored in Snake[Head] and the tail in Snake[Tail]. We also need a global Bitmap and a Graphics object:

Image OffScreen;
Graphics GForm;

We might as well use the form’s constructor to initialise everything :

public Form1()
{
InitializeComponent();

First we set the form’s client area to the correct size for the game:

 this.ClientSize= new Size(
SSize.Width, SSize.Height);

We could use the background bitmap to make the graphics persistent but unfortunately updating this at high rate produces flashing so instead we are going to use a bitmap buffer to build up the new graphic and then transfer it to the form when we are ready.

The bitmap needs to be the same size as the client area of the form and we also need the graphics object associated with the form so we can transfer the bitmap to it later on:

OffScreen = new Bitmap(
this.ClientSize.Width,
this.ClientSize.Height);
GForm = this.CreateGraphics();

We could initialise all 16 locations of the snake’s body but in practice all we have to do is set the head’s position and the size in all of the array elements:

Snake[Head].Location = new Point(
11*CSize.Width,
7*CSize.Height);
for (int i = 0;
 i <= Snake.GetUpperBound(0); i++)
Snake[i].Size = CSize;

Notice how using an array of Rectangle objects makes it easy to set the location using a point object and the size using Size object.

Finally we initialise the snake’s velocity and the timer:

 vx = 1; vy =1;
timer1.Interval = 100;
timer1.Enabled = true;
}

Most of the rest of the work goes on in the Timer’s Tick event handler. Use the Properties dialog box to automatically add this event handler to your program.

The first thing we have to do is compute the new position of the snake’s head based on its current position by adding to it the velocity scaled by the size of each graphics block. The only problem is that you don’t want the snake to disappear off the edge of the form. The simplest solution is to “wrap” the co-ordinates around so that going off the edge of the form brings the snake back on at the opposing edge.

This can be achieved using modular arithmetic and the modular arithmetic operator %:

private void timer1_Tick(
object sender, EventArgs e)
{
Point NewHead = Snake[Head].Location;
NewHead.X =
 (NewHead.X + vx * cx + 50 * cx)
% (50 * cx);
NewHead.Y =
(NewHead.Y + vy * cy + 50 * cy)
% (50 * cy);

This looks complicated but it simply ensures that the co-ordinates never get bigger than the form’s drawing area and they never go negative.

The new head location is stored in the next array element and the whole snake moves up by one in the sense that the old head becomes part of the body and the next to last block becomes the new tail. The only complication is that the array has to be treated as if it was “circular” to stop it running out of space.

This is just a matter of using modular arithmetic again which makes the head and tail pointers go back to 0 after reaching 49:

Head = (Head +1) % 50;
Tail = (Tail +1) % 50;

Now that we have the position of the new head in the array we can store it:

Snake[Head].Location = NewHead;

All that is left is to blank out the tail position by writing a block in the form’s background colour and write the new head in a colour of our choice. We also need a graphics object created from the bitmap:

Graphics G = Graphics.FromImage(
OffScreen);
G.FillRectangle(
SystemBrushes.Control,
Snake[Tail]);
G.FillRectangle(
Brushes.Red,
Snake[Head]);

Finally we draw the bitmap to the form:

 GForm.DrawImage(OffScreen, 0, 0);
}

If you run this program at this stage you will see a red snake slowly appear, segment by segment and then move off in a fixed direction. When it reaches the edge of the screen it appears on the other side. Seeing it in action should make some of the subtler points of how it works clear. For example, to animate a snake you only ever have to draw the new position of the head and blank out the existing tail’s position. When the snake starts off it grows by one segment per movement until the tail “catches up” with first head position when it starts to move.

Key events

The next step is to control the direction of the snake using the keyboard.  Each time you press a key you generate a keypress event and to interact with the keyboard you have to write event handlers.

The easiest way to do this is to use the designer. Select events in the properties window and double click on the Keydown event to generate a suitable handler.

private void Form1_KeyDown(
object sender,
  KeyEventArgs e)
{

The KeyEventArgs object has a KeyCode property which can be used to discover which key is pressed. The Keys collection has a set of built-in keycodes that you can use to test against. The simplest thing to do is to use a switch statement to detect the right and left arrow keys. When the right arrow key is pressed the snake's velocity is rotated clockwise and when the left arrow key is pressed it is rotated anticlockwise:

 int nvx, nvy;
switch(e.KeyCode)
{
case Keys.Left:
nvx = Math.Sign(vx + vy);
nvy = Math.Sign(-vx + vy);
vx = nvx;
vy = nvy;
break;
case Keys.Right:
nvx = Math.Sign(vx - vy);
nvy = Math.Sign(vx + vy);
vx = nvx;
vy = nvy;
break;
}
}

The arithmetic “rotates” the velocity 45 degrees each time one of the keys is pressed using a well known technique based on a rotation matrix. You can easily handle more key presses in the same sort of way.

Where next?

The program currently lets us take charge of a snake and “drive” it around the screen. The next step is mostly just application of the same ideas. You need to add obstacles for the snake to avoid, rewards for the snake to eat and so on. The basic graphics techniques explained here can be used in almost any program to good effect.

If you would like the code for this project then register and click on CodeBin.

<ASIN:1568811543>

<ASIN:159059035X>

<ASIN:1592000932>



Last Updated ( Wednesday, 26 August 2009 )