Click here to Skip to main content
Click here to Skip to main content

Sokoban Pro

By , 6 Jan 2005
 

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

About the Author

JasperB
Web Developer
Netherlands Netherlands
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralPost newest version on here!!!memberJason Barry18 Jul '08 - 10:57 
I noticed that you have a newer version than what is on here. The link to your website does not work anymore so it would be awesome if you could post the newest version.
 
By the way, this is a very addictive game. I am stuck on level 14 right now... it's killing me!
GeneralHimemberMarquesTM25 May '08 - 23:52 
I have one project in my school were i need to make the game sokoban to pda. Your game is a great example to me. But i have one doubt. You use one file XML but i have to load the levels directly in one class. the levels stay in one Array.
 
Do you have any idea?
 
Thank's
NewsSokoban Pro under LinuxmemberS. Becker7 Dec '06 - 8:26 
Dear JasperB,
 
if you remove the ressource manager in line 273 of file Board.cs and the calls to resource it compiles very well under Ubuntu Linux with Mono 1.2.0 and MonoDevelop 0.12.
You also have to remove the space of the project folder name (Sokoban Pro -> SokobanPro) to make it run. From that point on you can play your game under Linux Smile | :)
 
Regards
 
Sascha Big Grin | :-D
 


GeneralAdd an in-game menumembermrsnipey26 May '05 - 12:30 
Adding an in-game menu to let you re-do a level that you've
already finished would be good.
 
Great game by the way. It was always a favourite of mine.
GeneralRe: Add an in-game menumembermrsnipey26 May '05 - 12:38 
Or even if you get stuck in a level, add a 'Restart' option instead
of having to restart the game. Smile | :)
GeneralGreat stuffmemberWillemM11 Jan '05 - 20:24 
I am working on a tankwars like game myself. It's a project for school.
Your game is a great example of how you can draw tiles and stuff. We need some ideas on how tile should look and this is just what I was looking for.
(I am working on this game in a team of 8 software engineers Smile | :) )
 
Do you mind if I use the tiles from your game?
 
"Every rule in a world of bits and bytes can be bend or eventually be broken"
GeneralRe: Great stuffmemberJasperB11 Jan '05 - 20:56 
Sure, go ahead!
GeneralLevel 3 crashsussgabis27 Dec '04 - 0:24 
On level 3 if you push the down arrow as you first move it will crash.
Second, I do not think you can pass this level.
GeneralRe: Level 3 crashmemberJasperB27 Dec '04 - 2:06 
Do you get an exception? Because it works just fine here. If you can post a stacktrace then maybe I can see what the problem is.
 
BTW. ALL levels are solvable (I've finished them all myself), although some of them (especially the later ones) a very difficult.
GeneralRe: Level 3 crashmembergabis28 Dec '04 - 2:17 
Indeed all levels are solvable.
I am way past level 3.
As I said, the first move has to be into the wall.
If you move legaly then it won't happen.
 
Here is the exception:
See the end of this message for details on invoking
just-in-time (JIT) debugging instead of this dialog box.
 
************** Exception Text **************
System.NullReferenceException: Object reference not set to an instance of an object.
at SokobanPro.Level.DrawChanges() in c:\Projects\Sokoban Pro\Level.cs:line 225
at SokobanPro.Board.DrawChanges() in c:\Projects\Sokoban Pro\Board.cs:line 154
at SokobanPro.Board.MoveSokoban(MoveDirection direction) in c:\Projects\Sokoban Pro\Board.cs:line 230
at SokobanPro.Board.AKeyDown(Object sender, KeyEventArgs e) in c:\Projects\Sokoban Pro\Board.cs:line 194
at System.Windows.Forms.Control.OnKeyDown(KeyEventArgs e)
at System.Windows.Forms.Control.ProcessKeyEventArgs(Message& m)
at System.Windows.Forms.Control.ProcessKeyMessage(Message& m)
at System.Windows.Forms.Control.WmKeyChar(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
at System.Windows.Forms.ContainerControl.WndProc(Message& m)
at System.Windows.Forms.Form.WndProc(Message& m)
at System.Windows.Forms.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
 

************** Loaded Assemblies **************
mscorlib
Assembly Version: 1.0.5000.0
Win32 Version: 1.1.4322.2032
CodeBase: file:///c:/windows/microsoft.net/framework/v1.1.4322/mscorlib.dll
----------------------------------------
Sokoban Pro
Assembly Version: 1.0.1822.28456
Win32 Version: 1.0.1822.28456
CodeBase: file:///c:/Projects/Sokoban%20Pro/Sokoban%20Pro.exe
----------------------------------------
System.Windows.Forms
Assembly Version: 1.0.5000.0
Win32 Version: 1.1.4322.2032
CodeBase: file:///c:/windows/assembly/gac/system.windows.forms/1.0.5000.0__b77a5c561934e089/system.windows.forms.dll
----------------------------------------
System
Assembly Version: 1.0.5000.0
Win32 Version: 1.1.4322.2032
CodeBase: file:///c:/windows/assembly/gac/system/1.0.5000.0__b77a5c561934e089/system.dll
----------------------------------------
System.Drawing
Assembly Version: 1.0.5000.0
Win32 Version: 1.1.4322.2032
CodeBase: file:///c:/windows/assembly/gac/system.drawing/1.0.5000.0__b03f5f7f11d50a3a/system.drawing.dll
----------------------------------------
System.Xml
Assembly Version: 1.0.5000.0
Win32 Version: 1.1.4322.2032
CodeBase: file:///c:/windows/assembly/gac/system.xml/1.0.5000.0__b77a5c561934e089/system.xml.dll
----------------------------------------
 
************** JIT Debugging **************
To enable just in time (JIT) debugging, the config file for this
application or machine (machine.config) must have the
jitDebugging value set in the system.windows.forms section.
The application must also be compiled with debugging
enabled.
 
For example:
 



 
When JIT debugging is enabled, any unhandled exception
will be sent to the JIT debugger registered on the machine
rather than being handled by this dialog.
 


General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 6 Jan 2005
Article Copyright 2004 by JasperB
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid