Introduction
This application/game was designed as an experiment really! I remember playing this game on my graphing calculator many years ago. Never really played it since. And even with this, it's not so much as the game as it was making the game.
Credits to go out to: J. van den Bergh - (see About form in application for email address).
Without his work on the original game, this would have taken me a lot longer to get done. His graphics and graphics logic have been used. Originally, the game was in C# and was pretty simple. (No offence at all to the original author whatsoever - was an awesome app, I just wanted more!). I think the only code that exists of his is the Level and LevelSet code (now VB.NET - was C#).
Sorry about sticking the source and binaries on CodePlex - the downloads are reasonably large.
NOTE
The screenshots are of the DevExpress version. The version without DevExpress doesn't look as great, but functionality is still the same in most regards. Performance is slightly faster with data binding due to standard .NET controls having to rebind when a collection changes.
Background
What I added:
- Rewrote the game from ground up
- A cooler interface
- Put in sounds
- Added all options
- Added high scores (well, it's more just a score history)
- Added save game ability
- Added zoom in/out (very awesome)
- And heaps of other stuff
The quotes:
- They're in a file in the Data directory.
- File is called BoxWorld.Quotes.dat.
- It's a simple CSV (pipe delimited - the | character).
- Change it around, put your own quotes in.
- When playing a game, quote changes by itself (about 20 seconds).
- Click on the quote to get a new one.
To other developers:
- You can create more graphics libraries.
- Just copy the existing directory and go nuts.
- Change Tada.wav to another sound (call it the same).
- All data storage is done via XML.
- Scores have been tested for performance to 100k records.
- Zoom levels are per level. Very cool.
With the level files:
- Look in the Data\Levels folder for the levels.
- You can add your own levels.
- The level files are XML.
- Pretty easy to understand files.
- Just drop the file in that directory.
- Keep the level set title unique.
Important Note:
The included source code includes two projects. One project called LITE does not have DevExpress references and is built with standard .NET controls only. The DevExpress version looks a lot better, but not everyone has DevExpress.
The version without DevExpress is more or less the same as the one with the controls, but some of the binding is different. I've found the .NET controls (ListBox and ComboBox) tend to tend the datasource set to null before binding. Only seems to apply with lists of data.
Above all, enjoy playing it!
Acknowledgements
The Different Controls and Code
Controls
- HighScores
This control stores the high score data. Each player has their own settings file. A settings file stores data such as player ID, player name, high scores, zoom levels and general program options. When displaying high scores, all the player files are read and high scores are collated and presented.
- Levels
Really the main start screen. Shows level sets (a collection of levels) and levels. Also shows a preview of the level.
- Options
Lets you player configure options. Stores options per player.
- PlayBoard
This is the main control used to display the level.
- Players
The start screen. Used to select a player and maintain players. By default there is no option, but you can flick in the App.Config the option to turn passwords on. Passwords are saved in players XML file in encrypted form. Key is hardcoded in program.
- SaveGames
Shows a listing of saved games.
- Welcome
Simple control to display welcome text and provides a link to About form.
Controls
- frmAbout
Shows about text, level count and level set count.
- frmMain
Main screen of application.
- frmPlayBoard
Main play board control.
- frmSplash
Just the splash screen.
- frmStart
Start screen of application, lets user select and maintain users.
XML Serialization
All of the settings are saved in XML, all players have their individual XML files. Saving the XML looks like:
Public Shared Sub SavePlayer(MySettings As Models.Player)
Dim xSerial As Serialization.XmlSerializer
Dim objStreamWriter As IO.StreamWriter = Nothing
Dim Location As String
Try
Location = String.Format("{0}{1}.xml", _
My.Application.PlayersDirectory, MySettings.PlayerID)
objStreamWriter = New IO.StreamWriter(Location)
xSerial = New Serialization.XmlSerializer(MySettings.GetType)
xSerial.Serialize(objStreamWriter, MySettings)
Catch ex As Exception
Finally
If (objStreamWriter IsNot Nothing) Then
objStreamWriter.Close()
End If
End Try
End Sub
Essentially this code is building up the save location, creates a serializer with the Player model, then serializes the class to the XML file. So quick and easy.
And loading it:
Public Shared Function LoadPlayerSettings() As List(Of Models.Player)
Dim fileList As String()
Dim playerList As New List(Of Models.Player)
Dim createPlayer As Boolean = False
Try
fileList = System.IO.Directory.GetFiles(My.Application.PlayersDirectory)
If (fileList Is Nothing) Then
createPlayer = True
Else
If (fileList.Length = 0) Then
createPlayer = True
End If
End If
If (createPlayer) Then
Dim newPlayer As Models.Player
newPlayer = GeneralHelper.CreatePlayer("Guest", String.Empty)
playerList.Add(newPlayer)
GeneralHelper.SavePlayer(newPlayer)
Else
For i As Integer = 0 To fileList.Length - 1
Dim xSerial As Serialization.XmlSerializer
Dim objStreamReader As StreamReader = Nothing
Try
Dim newPlayer As New Models.Player
objStreamReader = New StreamReader(fileList(i))
xSerial = New Serialization.XmlSerializer(newPlayer.GetType)
newPlayer = CType(xSerial.Deserialize(objStreamReader), Models.Player)
playerList.Add(newPlayer)
Catch ex As Exception
Finally
If (objStreamReader IsNot Nothing) Then
objStreamReader.Close()
End If
End Try
Next
End If
Catch ex As Exception
End Try
Return playerList
End Function
Loading is a bit different as we get all the player XML files, if we don't have any, we always create a default player.
Skinning
You can create your own level graphics - just copy the Original directory instead graphics to the same directory (Graphics). Then just edit the files.
Running Timer
I actually found this code somewhere else, basically the play board has a TimeSpan on it (set to 0). I also have a Timer control on the board, and every 100 milliseconds, the TimeSpan gets 100 milliseconds added to it. When you save a game, the ticks of the TimeSpan is saved and when you resume, the ticks is loaded into the TimeSpan.
Serialize a Multidimensional Array to Base64 (i.e. Save Game)
Because some of the levels are just massive, I needed to implement the ability to save a game. The biggest thing was the array of level pieces.
Private _LevelData As Models.ItemType(,)
<Xml.Serialization.XmlIgnore()>
Public Property LevelData As Models.ItemType(,)
Get
Return Me._LevelData
End Get
Set(value As Models.ItemType(,))
Me._LevelData = value
End Set
End Property
Public Property LevelData64 As String
Get
Dim format As Binary.BinaryFormatter
Dim mStream As MemoryStream = Nothing
Dim str_b64 As String = String.Empty
Dim byArr As Byte()
Try
format = New Binary.BinaryFormatter()
mStream = New MemoryStream()
format.Serialize(mStream, Me.LevelData)
byArr = mStream.ToArray()
str_b64 = Convert.ToBase64String(byArr)
Catch ex As Exception
Finally
mStream.Close()
mStream.Dispose()
End Try
Return str_b64
End Get
Set(value As String)
Dim format As Binary.BinaryFormatter
Dim mStream As MemoryStream = Nothing
Try
format = New Binary.BinaryFormatter()
mStream = New MemoryStream(Convert.FromBase64String(value))
Me.LevelData = CType(format.Deserialize(mStream), ItemType(,))
Catch ex As Exception
Finally
mStream.Close()
mStream.Dispose()
End Try
End Set
End Property
The code above is modified a bit, but hopefully that makes sense. Much the same as general serialization, but using a BinaryFormatter. Note the XmlIgnore bit - I actually want to ignore that specific property for serializing and have my own property for that.
Whenever you're playing a level or resuming a save game, make sure you copy the array and not just set the array because of references. With arrays, you're manipulating the actual source. Something like:
Array.Copy(LevelMap, Me._DefaultMap, LevelMap.Length)
That bit of code copies the LevelMap (position of all tiles) into a default level map we can use later when we reset levels.
Points of Interest
- XML serialization is very cool.
- Learnt how to use a TimeSpan to keep a running timer.
- Learnt how to serialize a multi-dimensional array to base 64.
- Learnt how to save the state of a game and resume.
- There is some exception management, but it's not the best right now, may be improved upon later.
History
- Version 8.0.1110.30 - Initial release