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;
}