Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

PacSnake

0.00/5 (No votes)
17 Jun 2005 3  
PacSnake is a mix of the classic PacMan game and the classic Snake game.

Sample Image - PacSnake.gif

Introduction


First of all, PacSnake is now available as an Android game in the Google Play Store. It uses the same techniques as described in this article, but has many more features, like smooth snake movement, many levels, etc. I will soon write a new article describing the snake movement and more Android specific issues that I've come across. Click here.


PacSnake is a game that is inspired by PacMan and the Snake game. The object of the game is to move the snake through the maze and eat all the food. When the snake hits his own body, it dies.

The game was very interesting to write because it uses a lot of different techniques (which are explained in more detail later).

  • XML: The game uses XML to read the different levels. It also uses XML file for skins and for storing/reading user preferences.
  • Graphics, obviously. It uses a backbuffer to write graphics to screen, so the screen doesn't flicker each time it's repainted.
  • Object oriented: I tried to use an object oriented approach while developing the game.
  • Sounds: The game uses sounds.

The game uses level sets and skins:

  • Level sets: A level set consists of one or more levels. You can create you own levels, put them in a level set file and that you can play the new levels.
  • Skins: You can also create your own skins and put them in the game. A skin is a collection of graphics that decide how the game looks. You can for example make a 'fire' skin or a 'metal' skin.

The Classes

I'll explain each class (object) that the game uses here in detail:

Snake

The Snake object is responsible for moving the snake, checking for directions and collisions and for getting the snake images.

The Snake consists of an array of rectangles. Each rectangle represents a body part of the snake (head, body or tail). Each rectangle has a position and a direction. This way, it's possible for each body part to move in a different direction (when the snake turns in corners). Using rectangles, it's easy to detect is the snake bumps into itself and dies. Here's the piece of code that checks for collisions:

/// <summary>
/// Check if the head of the snake crashed into a body part
/// </summary>
/// <returns>True if snake bumps into itself, otherwise false</returns>
public bool CheckCollision()
{
    for (int i = 1; i < length; i++)
        if (body[i].IntersectsWith(body[0]))
        {
            isDead = true;
            return true;
        }

    return false;
}

The code takes the rectangle that represents the snake's head (which is the first in the array) and checks if it overlaps with any of the other body parts. If so, game over!

GameData

This is a fairly simple class which keeps track of the current level, remembers the user's preferences like playing sounds or not, and updating/reading the setting to an XML file on disk. The last point is worth showing here:

/// <summary>
/// Reads the game variables from the savegame. If there's no savegame
/// we use default values.
/// </summary>
public void ReadSavegame()
{
    string filename = path + "\\savegame.xml";
    XmlDocument doc = new XmlDocument();

    if (File.Exists(filename))
    {
        doc.Load(filename);
        skinFilename = doc.SelectSingleNode("//Skin").InnerText;
    }
    else
    {
        skinFilename = path + "\\skins\\ice.xml";

        // Create new file savegame.xml
        XmlTextWriter writer = new XmlTextWriter(filename, null);
        writer.Formatting = Formatting.Indented;
        writer.Indentation = 2;
        writer.WriteProcessingInstruction("xml",
            "version='1.0' encoding='ISO-8859-1'");
        writer.WriteStartElement("savegame");
        writer.Close();

        // Load XML document and create all elements
        doc.Load(filename);
        XmlNode root = doc.DocumentElement;
        XmlElement skin = doc.CreateElement("Skin");
        skin.InnerText = path + "\\skins\\ice.xml";

        // Add all elements to the root element
        root.AppendChild(skin);

        // Save file
        doc.Save(filename);
    }
}

First, we check if a savegame exists. A savegame doesn't exist when the game is started for the first time. In this case, we create an XML file and store the users preferences. In this case, we only have one setting, but there may come more settings in the future as the game gets bigger. It uses the XML namespace, and when you examine the code, you can see it's pretty straightforward.

Level

An important part of the Level class is drawing the level. Here's the piece of code responsible for that:

/// <summary>
/// This method draws the level. It takes each item in the level and
/// draws the correspoding image.
/// </summary>
public Bitmap Draw(int levelWidth, int levelHeight, Skin skin)
{
    img = new Bitmap(levelWidth, levelHeight);
    g = Graphics.FromImage(img);

    g.DrawImage(skin.Background, 0, 0);

    // Draw the level
    for (int i = 0; i < width; i++)
    {
        for (int j = 0; j < height; j++)
        {
            Image image = GetLevelImage(itemsInLevel[i, j], skin);

            // No free space
            if (image != null)
                g.DrawImage(image, mazeOffsetX + i * 20,
                    mazeOffsetY + j * 20, 20, 20);

            Item item = itemsInLevel[i, j];
            if (item.IsCandy && !item.IsCandyEaten)
                g.DrawImage(skin.Food, item.Rect.X, item.Rect.Y);
        }
    }

    return img;
}

First, we draw a background, which is stored in the Skin class. The level is stored in a two-dimensional array. This array consists of Item objects. An item object tells us which item of the level it holds (like a different wall or a piece of food). The array is read row by row, column by column, and for every item a corresponding image is drawn. The maze offsets you see in the code are for drawing the level in the center of the screen in case the level is smaller than the screen size.

LevelSetInfo

The LevelSetInfo class holds all the information about a certain level set. A level set consists of different levels that are stored in an XML file. This way, you can create your own levels by putting them in an XML file and simply copying this file in the levels directory.

LevelSet

LevelSet inherits from LevelSetInfo and in this class, we also add the levels. While we use the LevelSetInfo class for displaying the level set information on screen (we don't need to know how the levels look), we use the LevelSet for reading the levels when we actually want to play the game.

LevelSetInfoCollection

This class simply holds a collection of LevelSetInfo objects.

Skin

The game uses skins for graphics. This means that you can create your own graphics if you wish. The Skin class loads these graphics from the skin directory. The location of these graphic files are stored in an XML file. The class reads the location from the XML files and retrieves the graphics from disc.

SkinCollection

Holds a collection of skins that are found in the skins directory.

WinMM

This is a helper class for playing sounds. It is a class I found on the web and for more details about playing the sounds, check the website mentioned in the source of this class.

Board

Board is the main form of the game. It draws the level, snake, etc.. It also uses a timer to move the snake and it responds to key presses. This is shown in the following method:

/// <summary>
/// Handles the key presses. When the game is running it controls the
/// snake. When the game is not running it controls the other keys to
/// start the game, etc...
/// </summary>
private void AKeyDown(object sender, KeyEventArgs e)
{
    switch (gameData.GameStatus)
    {
        // If the game is in progress, we control the snake
        case GameStatus.InProgress:
            switch (e.KeyData.ToString())
            {
                case "Left":
                    snake.DesiredDirection = MoveDirection.Left;
                    break;
                case "Right":
                    snake.DesiredDirection = MoveDirection.Right;
                    break;
                case "Up":
                    snake.DesiredDirection = MoveDirection.Up;
                    break;
                case "Down":
                    snake.DesiredDirection = MoveDirection.Down;
                    break;
                default:
                    // Don't respond on other key presses
                    break;
            }
            break;
        // If the snake has died we press a random key to restart
        case GameStatus.SnakeDied:
            InitializeGame();
            break;
        // If the level is finished we press a key to start next
        case GameStatus.LevelFinished:
            // If this wasn't the last level, start next level
            if (gameData.CurrentLevel < levelSet.NrOfLevelsInSet)
            {
                gameData.CurrentLevel++;
                level = levelSet[gameData.CurrentLevel - 1];
                InitializeGame();
            }
            else
                gameData.GameStatus = GameStatus.WaitingForKey;

            break;
        // Level is ready to start, let's wait for a key
        case GameStatus.WaitingForKey:
            gameData.GameStatus = GameStatus.InProgress;
            gameTicker.Interval = level.Speed;
            gameTicker.Enabled = true;
            mnuSkin.Enabled = false;
            break;
    }
}

FormLevelSet

Before we start the game, we have to choose which level set we want to play. This is done by this form.

FormSkins

A simple form where we can choose which skin we want to use in the game.

Enjoy!

History

  • 17th June, 2005: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here