This week, I’m going to conclude our trio of articles on games and graphics programming with a touch of class. Not a new stylish interface, but a class module to run a high score system. This system will be adaptable, so that you can use it in your future games should you wish, and will read and write the registry, with no API calls in sight. It must be magic!
Before you read the next few pages, please download the complete Lander 3 project for reference.
Let’s just have a little think about what this system must do, before we get coding:
- Be able to save a name and score for each item
- A score system where either bigger is better, or smaller is better
- Insert a new score in the correct place, according to the sort order
- Persist scores in the registry
- Support unlimited numbers of high score tables
- Provide a variable length formatted display of current highest scores
A tough call? With a little classy coding, we’ll soon have it under control!
We’ll be using a User Defined Type (UDT) to hold the data. This needs to hold the name and the score, so it would seems sensible to have two fields, one for the name and one for the score:
Private Type Score strName As String intScore As String End Type Private Scores() As Score
You may be wondering why there are no dimensions defined for the array. This is because the array is dynamic. In other words, we can change the size of the array as the program is running. This enables us to have a dynamic length table that can be changed by a property of the class, rather than having it hard coded into the class. To change the length of the array, we can use the ReDim statement.
As I mentioned, we will be using a property to control the table length. If you don’t understand properties or classes, have a read of the ActiveX Tutorial and all will be revealed! Let’s take a look at the code first, then I can explain it:
Private numberplaces As Integer Public Property Get NumPlaces() As Integer NumPlaces = numberplaces End Property Public Property Let NumPlaces(ByVal vNewValue As Integer) numberplaces = vNewValue ReDim Preserve Scores(1 To numberplaces) As Score End Property
As with all properties, a private variable within the class holds the actual value of the property. The Get function is very simple, but the Let function is a little more tricky as the array must be resized. This is done using the ReDim statement. The Preserve keyword tells VB to try and keep all the data that is in the array intact. This works well when the array is made smaller, as VB can just get rid of the excess data, but when the array is enlarged, VB just fills the array with empty variables – not great, but it’ll do.
We also need properties for the table name, and for the ‘type’ of high score table. The type of high score table determines whether a higher score is better, or a lower score is better. This could be achieved using Enums, which allow us to take advantage of VB’s autocomplete features. Here’s the code:
Private scorename As String Private ScoreOrder As ScoreSystem Public Enum ScoreSystem BiggerIsBetter = 0 SmallerIsBetter = 1 End Enum Public Property Get ScoreType() As ScoreSystem ScoreType = ScoreOrder End Property Public Property Let ScoreType(ByVal vNewValue As ScoreSystem) ScoreOrder = vNewValue End Property Public Property Get TableName() As String TableName = scorename End Property Public Property Let TableName(ByVal vNewValue As String) scorename = vNewValue End Property
That’s most of the boring bits out of the way now. Let’s have some fun with the registry.
Since we will be using VB’s built in registry functions, we can’t go far wrong, but the normal caveats still apply. The registry is an integral part of windows, so make sure that you backup before you start fiddling!
The registry functions must be able to read and write the entire high score table in the registry, according to the application’s title, the name of the table and the number of rows in the table. The GetSetting and SaveSetting functions allow us to do all of this in only 4 lines of code! Check it out:
Public Sub ReadScores() Dim getscore As Integer For getscore = 1 To numberplaces Scores(getscore).strName = GetSetting(App.Title, "Scores" _ & scorename, getscore & "name", "No name") Scores(getscore).intScore = GetSetting(App.Title, "Scores" _ & scorename, getscore & "score", 1000) Next End Sub Public Sub WriteScores() Dim putscore As Integer For putscore = 1 To numberplaces SaveSetting App.Title, "Scores" & scorename, putscore & _ "name", Scores(putscore).strName SaveSetting App.Title, "Scores" & scorename, putscore & _ "score", Scores(putscore).intScore Next End Sub
How much explanation does this code need? The read function goes through all the positions in the scores array, getting a value from the registry, and defaulting to “No name” and 1000 if none exist. The write function again goes through every element in the array, saving the value. You don’t need any degrees or Microsoft Certifications to understand this!
(You can find out more about using the Registry in our topic area)
Where a certain score is placed in the table depends on the score itself and whether bigger is better, or smaller is better. At first we will look at a system using bigger is better, although we will not be using it for the Lander game, it is easier to understand.
The first thing is to check whether the score is good enough to get on the score board:
Public Function InsertScores(intScore As Integer) _ As Integer Dim insertscore As Integer, updatescore As Integer Dim strName As String If intScore < Scores(numberplaces).intScore Then InsertScores = 0 Exit Function End If
If it is not good enough, the function exits, returning 0 for the position where the score was inserted. Otherwise it just keeps going. The only difference between the 'bigger is better' system and the 'smaller is better' system is the operator (< or >) used to determine whether the score is elegible. The best way to evaluate this in just one line of code is to use the IIf function. If you have not come across it before, look it up in the help file, as it can be really useful. This is how we will use it:
If IIf(ScoreOrder = SmallerIsBetter, intScore > _ Scores(numberplaces).intScore, intScore < _ Scores(numberplaces).intScore) Then 'etc
Now that we have decided that the score can go into the table, we must first find where to put it. Once we have found it, we must go through, moving all the others down, then insert it into the new position. In the middle of all this, we must also get the user's name:
For insertscore = 1 To numberplaces If IIf(ScoreOrder = SmallerIsBetter, intScore < _ Scores(insertscore).intScore, intScore > _ Scores(insertscore).intScore) Then ' we've found the score's position, so move ' all the other scores down: For updatescore = numberplaces - 1 To insertscore + 1 Step -1 Scores(updatescore) = Scores(updatescore - 1) Next ' then get the user's name strName = InputBox("Please enter your name") ' set the new name Scores(insertscore).strName = IIf(strName = "", "Anonymous", _ strName) ' and the new score Scores(insertscore).intScore = intScore ' and return the new position InsertScores = insertscore Exit For End If Next End Function
And that's it! The score can now be inserted by using some code like this:
clsScores.InsertScores 180
Now that we can get scores into the table, lets figure out how to get scores out of the table.
This function returns a string containing the table, with the three columns, position, score and name separated by tabs. There is an optional parameter, BiggestScore, that allows only part of the table to be printed. If this is not specified, then the whole table is printed:
Public Function MakeScores(Optional BiggestScore As Integer) _ As String Dim makescore As Integer If BiggestScore > numberplaces Or IsEmpty(BiggestScore) = _ True Or BiggestScore = 0 Then BiggestScore = numberplaces MakeScores = IIf(ScoreOrder = BiggerIsBetter, "Bigger", _ "Smaller") & " is better" & vbNewLine & "Position" & vbTab _ & "Score" & vbTab & "Name" & vbNewLine & String(60, "-") For makescore = 1 To BiggestScore MakeScores = MakeScores & vbNewLine & makescore & ")" & _ vbTab & Scores(makescore).intScore & vbTab & _ Scores(makescore).strName Next End Function
The code is a lot easier to read all on one line, so load up VB, and all will be revealed.
Well, this is the end of my development of the Lander game. As usual, you can check out the demo to accompany this article. I hope you have gained some knowledge about programming a simple game in VB, and will continue to add features to the program.
If you have any questions or comments, you can post them in the feedback system below, or in the Q and A forum.