Introduction
In order to play, you need a Pocket PC enabled device with the .NET Compact Framework 1.1 installed. To install, simply copy the mypacman.exe file to a new folder on your device. There is apparently no need for any companion files because the three Wav sound files and two GIF image files are incorporated into the executable as "embedded resources".
To start playing the game, press Enter, and then use the direction keys to guide the Pacman. The object of the game is not to be eaten by the red ghosts. There are four purple "yummies" that when eaten, convert the vile red ghosts into gentle blue ones which can then be eaten. This "free time" is limited and its extension is portrayed by a color bar that appears below the scores and starts getting smaller. You have three lives. Once you eat all the dots, you enter a new level in which the "scared" ghosts time gets reduced. Starting the second level, the red ghosts start "tracking" the Pacman (as opposed to randomly switching direction), and you got to be extra careful!
Once you lose all your lives, the game enters a demo mode, flashing a screen and ghosts moving around. To start playing again, simply press Enter.
The current game has only one maze design, so as an improvement, you people can design new mazes for different levels and also design new types of rewards and "villains".
Using the code
This project contains 7 classes in seven source files: GameForm.cs, GhostsManager.cs, PacmanManager.cs, Sound.cs, Sprite.cs, StopWatch.cs, and GameFont.cs. The first three are the important classes; I will describe their functionality briefly. The code is fairly well commented so you can gather conclusions from there too.
The main class of the project is GameForm
. It holds all the game flow logic and contains as members, among others, an instance of a GhostsManager
class and a PacmanManager
class. It is derived from System.Windows.Forms.Form
class and is responsible for the main game loop and all the graphics. The key component of this game is an array of 15x15 bytes which is responsible for defining the maze, dots, and yummys:
private byte[] LEVEL1DATA =
{
19,26,18,26,26,26,22,7,19,26,26,26,18,26,22,
37,7,21,3,10,14,21,13,21,11,10,6,21,7,37,
.....
.....
25,26,26,26,26,26,24,26,24,26,26,26,26,26,28
};
The playing ground dimensions are defined by the following constants: BLOCKSIZE
= 16, NROFBLOCKS
= 15, and SCRSIZE
= NROFBLOCKS
x BLOCKSIZE
.
The game playing area is composed by NROFBLOCKS
x NROFBLOCKS
blocks, each BLOCKSIZE
pixels wide. This means that the playing ground is a square of SCRSIZE
pixels wide.
The bit values in every byte of the array decide if the associated block has or has-not (starting with the LSB) a left maze wall, upper maze roof, right maze wall, lower maze floor, dot, and yummy. There are two bits left which are not used and can be used to add more features to the maze in a future enhancement.
Double buffering is used to avoid flickering. At the start of the game, the whole maze is loaded onto a Graphics
object conveniently called mazeGraphics
. On each iteration of the game loop, this Graphics
object is updated according to if a dot or yummy has been consumed, and then the whole Bitmap
object, mazeBitmap
, represented by mazeGraphics
is blitted onto the main offscreen Graphics
object, offScreenGraphics
. Note that the only change to mazeGraphics
is a removal of a dot or yummy, and the whole maze does not need to be constructed in its entirety on every iteration:
public void DotConsumed(int arraypos, Sprite pacman)
{
...
if ((ch&16)!=0) {
...
mazeGraphics.FillRectangle(brush,
pacman.x+BLOCKSIZE/2,pacman.y+BLOCKSIZE/2,3,3);
screendata[arraypos]=(byte)(ch&15);
score++;
}
if ((ch&32)!=0) {
...
mazeGraphics.FillRectangle(brush,
pacman.x+BLOCKSIZE/3,
pacman.y+BLOCKSIZE/3,BLOCKSIZE/3,BLOCKSIZE/3);
screendata[arraypos]=(byte)(ch&15);
score+=5;
}
}
and
private void DrawMaze()
{
offScreenGraphics.DrawImage(mazeBitmap, 0, 0,
this.ClientRectangle, GraphicsUnit.Pixel);
}
The other two important classes are GhostsManager
and PacmanManager
.
PacmanManager
, as its name indicates, manages the movement and display of the Pacman. The Pacman is encapsulated in a Sprite
object. All the views of the Pacman are included in PacMans.gif. The GameForm
delegates the KeyEventHandler
(after grabbing focus with a call to this.focus()
) calls to the keydown()
method of PacmanManager
which updates the requested movement directions of the Pacman. The important concept here is that a Pacman is guaranteed to fill each block it visits because it moves in each iteration of the game loop in increments of PACMANSPEED
=2 which is a factor of BLOCKSIZE
=16. When the Pacman moves fully into a block, it will change direction if the last requested turn in direction is valid, i.e., not crashing into a maze wall:
public void MovePacMan(GameForm form)
{
byte ch;
if (reqdx==-pacman.dx && reqdy==-pacman.dy)
{
pacman.dx=reqdx;
pacman.dy=reqdy;
viewdx=pacman.dx;
viewdy=pacman.dy;
}
else if (pacman.x%GameForm.BLOCKSIZE==0 &&
pacman.y%GameForm.BLOCKSIZE==0) {
arraypos=pacman.x/GameForm.BLOCKSIZE +
GameForm.NROFBLOCKS*(pacman.y/GameForm.BLOCKSIZE);
ch = form.screendata[arraypos];
....
....
OnDotConsumed(arraypos, pacman);
}
DrawPacMan();
}
GhostsManager
is the class in charge of handling all movements and display of the ghosts. The ghosts are contained in an array of Sprite
objects, and in the first level of play, they turn in direction randomly. The GhostsManager
is also in charge of detecting a collision with the Pacman and setting the appropriate variables. If the game is in "scared" mode and a ghost is hit, it will get "killed" and disappear in the place of impact and will be reborn in the center of the playing field. If a ghost is hit in playing mode (when they are red), a life will be subtracted from the player. All possible speeds of the ghosts are also factors of BLOCKSIZE
.
private int[] VALIDSPEEDS = { 1,2,4,8 };
Points of Interest
A point of interest is the event handler method DotConsumed(int arraypos, Sprite pacman)
in GameForm.cs. This is subscribed to the PacManager.OnDotConsumed
delegate instance. Every time a Pacman has consumed a dot or yummy, the parent class GameForm
will be notified through this mechanism so it can update mazeGraphics
and remove it from the display.
pacManager.OnDotConsumed += new PacmanManager.DotConsumedHandler(this.DotConsumed);
public delegate void DotConsumedHandler(int arraypos, Sprite pacman);
public event DotConsumedHandler OnDotConsumed;
Another point of interest is the tracking algorithm (a simple form of Artificial Intelligence) the ghosts use after the first level of play is done. When possible, it will only consider directions of movement in which the ghost reduces the x-y distances between itself and the Pacman:
if(form.pacManager.pacman.y<ghosts[i].y || goRandom)
{
dx[count]=0;
dy[count]=-1;
count++;
}