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

Sokoban Pro

0.00/5 (No votes)
6 Jan 2005 1  
Sokoban Pro is a modern version of the classic Sokoban puzzle game.

Sample Image - SokobanPro.jpg

Introduction

Sokoban Pro is a modern version of the classic Sokoban puzzle game. The game rules are extremely simple, yet the game is very challenging and addictive. The rule of the game is to move all the boxes in the right places. You can only push a box, not pull. You can undo your last moves by pressing U.

A new version of the game is now available on the Sokoban Pro website. Because all the most important stuff (like reading/writing XML, moving and drawing) is in the source code here on CodeProject, I've decided to leave the first, original version here. The latest version of the game, you can download at the Sokoban Pro website.

The latest version (v1.0b) includes the following improvements over the version on CodeProject:

  • 'Official' beta release
  • Added menus
  • I found it irritating that the window size changed with each level, so it has fixed size now
  • You can jump between different levels, but only to levels that you've finished before
  • 1-level undo functionality
  • Splash screen and icons
  • Fixed many bugs
  • Probably some other stuff, too

When the game starts, you can create a new player or select an existing player. Since Sokoban Pro saves your progress, you can also choose if you want to continue your previous game. After creating a player, you can choose a level set. A level set contains as many levels as you like. Sokoban Pro comes with the first 40 levels from the original BoxWorld game. Level sets are stored in XML files, which means that you can download level sets from the different Sokoban sites on the Internet. You can also create your own levels. Sokoban Pro will automatically recognize the level sets when you put them in the levels directory.

Your Save Game is also an XML file. It saves the last played level set and the last played level so you can continue where you left off the previous time you played the game. It also saves the levels that you've finished and the scores (number of moves and pushes). If you retry a level and your performance is better, your scores are updated.

Basically, the game consists of the following classes:

  • LevelSet - Contains all the information about a level set (author information, number of levels, etc..). It also loads the levels from the level set XML into memory.
  • Level - Represents a level inside a level set. The most important thing that happens here is that it keeps track of all your moves. It updates the items in a level when the player moves or pushes a box. It updates the corresponding graphics. It implements undo functionality, and lastly, it draws the level on the screen.
  • PlayerData - Keeps track of all the player information. Basically, it reflects your SaveGame.
  • Board (Form) - The main form handles all the player input and initializes all objects.
  • Players (Form) - Lets you create a new player or select an existing one.
  • Levels (Form) - Lets you select a level set that you want to play.

The application makes use of reading and writing XML files. Take, for example, the following method - SaveLevel() - which saves the player data after the player finished a level.

public void SaveLevel(Level level)
{
    XmlDocument doc = new XmlDocument();
    doc.Load(filename);

    XmlNode lastFinishedLvl = doc.SelectSingleNode("//lastFinishedLevel");
            lastFinishedLvl.InnerText = level.LevelNr.ToString();

    XmlNode setName = doc.SelectSingleNode("/savegame/levelSets/" +
                "levelSet[@title = \"" + level.LevelSetName + "\"]");
    XmlNode nodeLevel = setName.SelectSingleNode("level[@levelNr = " +
                level.LevelNr + "]");

    if (nodeLevel == null)
    {            
        XmlElement nodeNewLevel = doc.CreateElement("level");
        XmlAttribute xa = doc.CreateAttribute("levelNr");
        xa.Value = level.LevelNr.ToString();
        nodeNewLevel.Attributes.Append(xa);
        XmlElement moves = doc.CreateElement("moves");
        moves.InnerText = level.Moves.ToString();
        XmlElement pushes = doc.CreateElement("pushes");
        pushes.InnerText = level.Pushes.ToString();

        nodeNewLevel.AppendChild(moves);
        nodeNewLevel.AppendChild(pushes);
        setName.AppendChild(nodeNewLevel);
    }
    else
    {
        XmlElement moves = nodeLevel["moves"];
        XmlElement pushes = nodeLevel["pushes"];
        int nrOfMoves = int.Parse(moves.InnerText);
        int nrOfPushes = int.Parse(pushes.InnerText);

        if (level.Pushes < nrOfPushes)
        {
            pushes.InnerText = level.Pushes.ToString();
            moves.InnerText = level.Moves.ToString();
        }
        else if (level.Pushes == nrOfPushes && level.Moves < nrOfMoves)
            moves.InnerText = level.Moves.ToString();
    }

        doc.Save(filename);
}

Here's what happens:

We make use of XPath to select nodes in an XML file. This way, we don't have to read the XML line by line until we find the right element where we want to add a new element or update an existing one. The line:

XmlNode setName = doc.SelectSingleNode("/savegame/levelSets/" + 
    "levelSet[@title = \"" + level.LevelSetName + "\"]");

selects the level set node of the level we're currently playing. (Remember that Sokoban Pro supports multiple level sets, so there may be more than 1 level set in the XML). Then, the line:

XmlNode nodeLevel = setName.SelectSingleNode("level[@levelNr = " 
               + level.LevelNr + "]");

selects the level number we're playing in the current level set.

If we don't find the current level node, it means that we haven't finished the level before and we add a new level node. If we do find the level node, it means that we've played the level before and we check if our current score is better, and if yes, let's update the score.

The game makes heavy use of reading and writing XML files, and as you can see, using XPath is a very powerful tool.

Another thing I want to show here is how I load the level. As I've said, we store the level in the XML. A level consists of different items; a wall, a floor, a box, etc.. When we load a level, we read the lines in the XML that contain the level data. Each item in a level is represented by an ASCII character. When we read the level data, we store the items in a 2-dimensional array.

When we want to draw the level, we read the array and we can draw the level as follows:

// Draw the level

    for (int i = 0; i < width; i++)
    {
        for (int j = 0; j < height; j++)
        {
            Image image = GetLevelImage(levelMap[i, j], sokoDirection);

            g.DrawImage(image, ITEM_SIZE + i * ITEM_SIZE, ITEM_SIZE 
                        + j * ITEM_SIZE, ITEM_SIZE, ITEM_SIZE);

            // Set Sokoban's position

            if (levelMap[i, j] == ItemType.Sokoban 
                || levelMap[i, j] == ItemType.SokobanOnGoal)
            {
                sokoPosX = i;
                sokoPosY = j;
            }
        }
    }

We read the array line by line, character by character. Depending on what item we encounter, we get an image returned by the GetLevelImage method, which checks what item we have and returns the corresponding image. Lastly, we draw the image, given the position and the width and height of the items. The size is stored in the ITEM_SIZE variable. If we want to draw the level smaller (maybe for monitors with lower resolutions), we can decrease the value of ITEM_SIZE (default is 30).

Another interesting thing was checking if Sokoban is allowed to move. He can only move if the item in front of him is an empty space, or a box with an empty space behind it. When he moves, we store the new position of Sokoban and the box and the empty space Sokoban leaves behind in three separate Item objects, and we use these objects to only redraw these three items of the level instead of redrawing the entire level. Same goes for undoing the last push.

Enjoy!

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