Eater Game in C#




Description

This is a simple game written in C# in which the user moves a packman like player around the form and gobbles up red dots. The object is to get all the dots in as quick a time as you can. The design for the game is shown below:






Fig 2 - The UML for this game was reverse engineered using WithClass 2000

By examining the design, you can see that the eater game is not so difficult to understand because its simply a group of classes that are aggregates of the Form.

Each instance of class draws itself on the Form and the methods of the class are called from the form to exercise the class's behavior. 

The Sequence of Events is shown in the WithClass UML Diagram below after an arrow key to the right is pressed:



Fig 3 - Sequence of Events after a Right Key is pressed and the Eater hits a stone

The diagram above shows the flow of methods through the objects when a right arrow key is pressed and for those of you new to UML its known as a sequence diagram. The messages on the arrows map back to methods in the classes and each box with a vertical line represents an instance of the class in our Eater Game.

The code for the sequence above is shown below:

Listing 1 - KeyDown Event Handler

// KeyDown Event handled by the Form
private void Form1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
// Invalidate the Eater before moving it
string result = e.KeyData.ToString();
Invalidate(TheEater.GetFrame());
switch (result)
{
case "Left":
// Move the Eater to the Left
TheEater.MoveLeft(ClientRectangle);
Invalidate(TheEater.GetFrame());
break;
case "Right":
// Move the Eater to the Right
TheEater.MoveRight(ClientRectangle);
Invalidate(TheEater.GetFrame());
break;
case "Up":
// Move the Eater to the Up
TheEater.MoveUp(ClientRectangle);
Invalidate(TheEater.GetFrame());
break;
case "Down":
// Move the Eater to the Down
TheEater.MoveDown(ClientRectangle);
Invalidate(TheEater.GetFrame());
break;
default:
break;
}
// Check to see if the Eater ate a stone
int hit = CheckIntersection();
if (hit != -1)
{
// The Eater Ate a Stone, Increment the score, play a sound and remove the stone
TheScore.Increment();
PlaySoundInThread("hit.wav");
Invalidate(TheScore.GetFrame());
Invalidate(((Stone)Stones[hit]).GetFrame());
Stones.RemoveAt(hit);
// Check to see if the game is over
if (Stones.Count == 0)
{
// Game is over , all the stones are eaten, show the time it took to eat them
MessageBox.Show("You Win!\nYour time is " + TheTime.TheString + " seconds.");
Application.Exit();
}
}

In the Key Handler code above, each case of movement is handled for the Eater player.  If the Eater finds a stone, the score is incremented and a stone is removed.

Drawing of the stones, the eater, the score, and the timer is all handled by the each of the respective classes.  Below is the OnPaint method for the form to draw the board:

Listing 2 - Paining the Game Board

private
void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillRectangle(Brushes.White, 0, 0,
this.ClientRectangle.Width,
ClientRectangle.Height);
// draw the score
TheScore.Draw(g);
// draw the time
TheTime.Draw(g, TheSeconds);
// draw the stones
for (int i = 0; i < Stones.Count; i++)
{
((Stone)Stones[i]).Draw(g);
}
// also draw the eater
TheEater.Draw(g);
}

Animation of the Eater is handled by using two bitmaps.  One bitmap has the eater with its mouth open,  the other with its mouth closed.  The Eater is drawn on the odd pixels with the mouth open and the even pixels with the mouth closed.  It also checks to see if it moved horizontally the last time or vertically.  This could probably be expanded to 8 images, two for each direction the eater moves in.

Listing 3 - Drawing the Eater

public void Draw(Graphics g)
{
Rectangle destR =
new Rectangle(Position.X, Position.Y, EaterImage.Width,
EaterImage.Height);
Rectangle srcR =
new Rectangle(0,0, EaterImage.Width, EaterImage.Height);
// make it look like the mouth is moving
if ( ((Position.X % 2 == 1) && ((Position.X - LastPositionX) != 0)) ||
((Position.Y % 2 == 1) && ((Position.Y - LastPositionY) != 0))
)
g.DrawImage(EaterImage, destR, srcR, GraphicsUnit.Pixel);
else
g.DrawImage(EaterImage2, destR, srcR, GraphicsUnit.Pixel);
LastPositionX = Position.X;
LastPositionY = Position.Y;
}

Invalidation

Back in the days when people were programming Macintoshes (remember the toolbox!), even before Windows 3.1 came into existence there was the concept of Invalidating regions of the window.  I must admit its not an intuitive concept, but seems to work well. The concept goes that when erasing and redrawing an area of the window,  you don't simply erase and redraw the area.  You "Invalidate" the part of the window you want redrawn.  This minimizes flicker in that area.  The actual drawing is done in the OnPaint Routine (Or Form1_Paint in this example).  Anotherwords you invalidate a rectangular region you want to redraw,  this forces the Form1_Paint to be called and go through the entire Form1_Paint routine.  Everything in Form1_Paint is executed, but only the area of the screen that you Invalidated is actually drawn.  In the Listing 1 Key Handler, the Eater area is first invalidated to erase the original position of the Eater and then the new area of where the eater is being drawn to after the move is invalidated.  The effect to the eye is that the eater is actually moving.  The eater can be made to "speed up" by increasing the increment at which the Position is changed when the key is being pressed.

Improvements

I was actually thinking of a few things that could be introduced to the game to give it more of a twist.  The game can be changed to have the timer time down and the score could be based on the number of stones eaten in that time.  A maze object could be introduced on the board to make it a little more challenging to get the stones.  Maybe a bullet object could be created to allow the eater to shoot at the stones.  Perhaps you'll see Eater II  or Super Eater in the near future ;-)