How to create a high score file

BlitzMax Forums/BlitzMax Beginners Area/How to create a high score file

abelian_grape(Posted 2010) [#1]
Hey all,

It's been a little while since I've been around these parts. Development of my game "Penguin Peril" has been halted due to college and what not, but now that I've graduated I've got a little extra time on my hands before grad school.

Anywho, I've finished the gameplay and menu system for my game and am interested in implementing a high scores file. The game is essentially an arcade-style side-scroller that keeps track of your score. What I'd like to do is write the player's score to a file once they get a "game over," allow the player to input a name or even initials with the score, and then have the game be able to read that file to display the high score list if the "High Scores" option is selected from the menu.

This is the first game I've worked on and I have very limited experience with BlitzMax and C++. I'm not sure how to utilize the commands built into BlitzMax to perform this task, so I was wondering if someone could help me out. I would really appreciate it!

muffins


ima747(Posted 2010) [#2]
Check the blitzmax documentation under Modules->System->File System for relevant functions. Specifically look into:
ReadFile()
WriteFile()
CloseStream()
ReadLine()
WriteLine()

Since you don't need to edit the score lines as such, you can cut a lot of corners and just store text as you wish it to be read.

Simple (untested) example to get you started:
' dummy variable for a score
local newScore:int = 9001 ' over 9000! I couldn't resist

' Write a high score to the file, this creates the scores file if it doesn't exist
local scoreFileStream:TStream = WriteFile("HighScores.pps") ' open the file for writing to. PPS extension for penguin panic scores... imaginative I know

WriteLine(scoreFileStream, "Player 1: " + newScore) ' this writes a string to the file and ends it with a new line, this makes it easy to read back out of the file using the ReadLine command

CloseStream(scoreFileStream) ' writing actually happens when the stream is closed (unless you force it) so make sure you close your stream! it's also polite for the file system.



' Read the high scores
scoreFileStream = ReadFile("HighScores.pps") ' Open the high scores file to read existing scores

while not eof(scoreFileStream) ' as long as there's lines in the file keep going
local aScore:string = ReadLine(scoreFileStream) ' read a line from the file, this advances our read position in the file by one line as well
' do something with aScore. Store it in a TList, an array, show it on the screen, whatever
wend

CloseStream(scoreFileStream) ' close the score file since we're done with it.


This technically just creates a text file (open it in textedit, notepad, whatever and take a peak) so if you need to secure your scores you'll have to do some more work etc. etc.


abelian_grape(Posted 2010) [#3]
Just what I was looking for. Thanks, ima747!

Also, is it fairly easy to create a window for the user to enter their name or initials to be paired with the score?


ima747(Posted 2010) [#4]
Fairly easy if you have MaxGUI, otherwise you're best bet is to create your own display in 2d, which can also be good visually since it won't break the user out of the game experience.


abelian_grape(Posted 2010) [#5]
How do I know if I have MaxGUI? haha

I tried using this code from GfK

http://www.blitzbasic.com/Community/posts.php?topic=64736

But the variable that CreateWindow is being stored in wants some kind of identifier.


abelian_grape(Posted 2010) [#6]
I see...MaxGUI is a separate product that I would have to purchase. Looks like I'll have to create my own display in 2D...though I still don't know how to take input from the user. My guess is I would have to design an image with a "text field" that comes up and prompts the user to type. When they type, the string is read into a variable and echoed back in my "text field" so the user can see what they are entering. How would I go about this?


Czar Flavius(Posted 2010) [#7]
You might find this helpful.

Function GetChar()

Description: Get next character

Returns: The character code of the next character.

Information: As the user hits keys on the keyboard, BlitzMax records the character codes of these keystrokes into an internal 'character queue'.

GetChar removes the next character code from this queue and returns it the application.

If the character queue is empty, 0 is returned.
From http://en.wikibooks.org/wiki/BlitzMax/Modules/User_input/Polled_input#GetChar


abelian_grape(Posted 2010) [#8]
I guess I'm not entirely sure how to utilize these functions. Here is what I am doing:

 
'BEGIN INPUT FROM USER
Local name = GetChar()
If KeyHit(KEY_ENTER)                                             ' If ENTER is hit, terminate input
      FlushKeys()
EndIf


'WRITE THE SCORE TO THE HIGH SCORES FILE
scoreFileStream = WriteFile("HighScores.txt")        ' Open file to write to
WriteLine(scoreFileStream, "Name: " + name)       ' Writes name to file
WriteLine(scoreFileStream, "Score: " + score)	       ' Writes score to file
CloseStream(scoreFileStream)				       ' Close the filestream
							



abelian_grape(Posted 2010) [#9]
By looking at the output, it looks like GetChar() is only getting the character code of the first character I enter if I enter an entire name. So I would like it to get more than one character, and convert those character codes to a string so it shows up as a name in my text file, not a number.

Also, is it possible to echo what the user is typing? I'm thinking maybe turning it into a loop and adding a line of code to get:




abelian_grape(Posted 2010) [#10]
After a little more reading, here's what I got going now:



As of right now I'm getting an error on this line:
DrawText(+ Chr(name), 375, 455)

saying it can't convert from String to Int. Fair enough, because I'm pretty sure I'm doing this completely wrong. Does anyone have a solution to this?


Czar Flavius(Posted 2010) [#11]
What are the + there for? You're not adding two things together.
Try replacing "Chr(name)" with just "name".

Chr takes only the number code of a single letter, so it doesn't understand a whole string. You're right to use a loop to build up the name string bit by bit from GetChar, but you don't need it once you've made the string.


abelian_grape(Posted 2010) [#12]
Awesome, got it working :) Thanks everybody for the continual help!

Now the next part...which I've dreaded. Right now when I save a high score, it just writes over the previous high score. I'd like the scores to be written to the file so as not to overwrite each other, and then sort them. I know nothing about sorting algorithms, other than they exist. How would I get started here?


Czar Flavius(Posted 2010) [#13]
1. Load the high scores from the file
2. Add the new high score
3. Sort the list of high scores in descending order (ie from highest to lowest)
4. Save only the first 10 or so entries.
(Consider what will happen if the file does not exist or has less than 10 entries)

TList has its own sort method for you to use. You just need to define the Compare method of an object and it's done automatically. The first two lines are for "house keeping" and you shouldn't concern yourself too much with them. The important parts are the if statement. It should return 1 if this object is greater or -1 if the object is smaller or 0 if the objects are equal. Or the other way around, I can never remember! So tinkering may be required.

I've written file operations in pseudo-code because I can never remember the commands.

Local high_scores:TList = New TList
While reading_from_file
	high_scores.AddLast(THighScore.Create(name_from_file, score_from_file))
Wend

'get high score from player and add to list

'sort the scores, ascending is set to False so it's high to low
high_scores.Sort(False)

'now we want to write the 10 highest results only to the file
Local i:Int = 10
For Local hs:THighScore = EachIn high_scores
	put_in_file(hs.name, hs.score)
	i :- 1
	If i = 0 Then Exit
Next

'we're done
high_scores = Null
close_file

Type THighScore
	Field name:String
	Field score:Int

	Method Compare:Int(other:Object)
		'as you can store anything in a TList, we need to check this is a high score object
		Local hs:THighScore = THighScore(other)
		'if this isn't a high score object, use a fail-safe compare method
		If Not hs Then Return Super.Compare(other)
		If hs.score < score Then Return -1
		If hs.score > score Then Return 1
		If hs.score = score Then Return 0
	End Method

	Function Create:THighScore(name:String, score:Int)
		Local high_score:THighScore = New THighScore
		high_score.name = name
		high_score.score = score
		Return high_score
	End Function
End Type



abelian_grape(Posted 2010) [#14]
Okay, that seems to make sense. However, you're passing the Create function a string and a score. The problem is, when I write to the file, and then read back from it, all it can read back is strings. So when I want to read out the score from the file, it comes out as a string, not an int. Is there a way to force it to be an int so I can pass it to the function as an int?


Zeke(Posted 2010) [#15]
how about use Write/ReadString to write/read name and then Write/ReadInt() to write/read scrore


abelian_grape(Posted 2010) [#16]
how about use Write/ReadString to write/read name and then Write/ReadInt() to write/read scrore


The problem I see with using ReadString is that ReadString has the string length as a parameter, and unless I restrict myself to using initials (where the user can only enter 3 letters each time), then names are all different lengths, and as far as I know, BlitzMax doesn't have a function that returns the length of a string (something similar to strlen in C++).


ima747(Posted 2010) [#17]
len(myString) will get you the length of the string. store that ahead of the string as an int and you're set. Read the int, now you know how far to read for the string. len is sort of a catch all for sizing things in blitzmax, it'll do arrays too.

Alternatively WriteLine() ReadLine() writes/reads a string with an ending cap of \r\n so it can be read back easily. Which is what I generally use to save time. Just use it in place of WriteString you don't need to worry about length as long as there's no \r\n in your text (which is highly unlikely as new lines in blitz are typically just treated with a \n (new line character) rather than the full \r\n (carrage return and new line characters)

The appropriate tecnique is dependent on your data. You could also parse out the \r's from your text to sanitize it for ReadLine() if you wanted to not have to handle the length and thought you might catch some \r's in your text (reading from a dos format ASCII text file for example)


Czar Flavius(Posted 2010) [#18]
Alternatively you can say myString.Length
Limit the names to a fixed size, such as 10 characters.


ima747(Posted 2010) [#19]
If the names are limited you need to pad the extra space.


Jesse(Posted 2010) [#20]
I don't know if this can help you figure things out but I have the community edition of pocket invaders that I added a score table. It allows name input, sorts and saves to a file. It's in OO format and can easily be extracted and modified to meet your needs, maybe. it includes the source code:
http://www.filefront.com/13632051/pocket-invaders-source/

look for TscoreTable and Ttext types in file.


abelian_grape(Posted 2010) [#21]
Okay so here's what I have:

The High Score Type



And the screen where I read the high scores file, and print out the names and scores (top ten).


Now you'll notice what I did was to change the score to a string. This created a problem in that now when the "sorting" method is applied, it only will sort by the first number in the score string. So in my top ten, a score of "30 points" will rank higher than a score of "200 points" because 3 > 2.

I tried using the suggestions posted here and there is no easy way with the code so far to keep the score as an int, because again, when I go to read it from a file, there's no way for me to go to the next line after reading the name. For example, I enter the scores in by:
WriteLine(name)
WriteLine(score)

Then when I go to read them back, I have to do
ReadLine(name)
ReadLine(score) <---- but that doesn't work since score is not a string.

So then I try to do:
ReadLine(name)
ReadInt(score) <---- but here I can't enter a new line to make the next name read out to be the next line down. Instead, it is searching on the same line as the score for the name.

So, is there a way to properly sort a string if the string is purely an integer, or is there a way to force the stream to go to the next line down after doing the ReadInt?


Jesse(Posted 2010) [#22]

ReadInt(score) <---- but here I can't enter a new line to make the next name read out to be the next line down. Instead, it is searching on the same line as the score for the name.


it should be
score = int(ReadLine(file))


alternatively you can do:
writeline(file, name$)
writeint(file,score%)

str$ = readline(file)
score% = readint(file)



abelian_grape(Posted 2010) [#23]
Perfect! Thank you!

Everything seems to be working exactly how I imagined it. Thank you all so much for the help! I hope this thread is able to help others with their high score programming projects.

So that last thing Jesse did...is that "casting?" I vaguely remember something similar from C++...the ability to take a variable of one type and force it to be another type.


Yahfree(Posted 2010) [#24]
I created a high score system for one of my games a while back. It's pretty basic but functional. I used it for another one of my games recently:




Jesse(Posted 2010) [#25]

is that "casting?"


yes. It works "between strings and numerical variables" and objects with a common base type(only from base to extended and extended to base of same extended type).


Czar Flavius(Posted 2010) [#26]
In the code I posted I mixed up "with" and "other". I've fixed that error in my post.

Local hs:THighScore = THighScore(other)

This is also casting. We are trying to cast other to a THighScore variable. If other isn't a THighScore, it will simply become Null. This is why we include that check in the Compare method.


abelian_grape(Posted 2010) [#27]
@ Czar Flavius: It still seemed to work even with the old code...not sure why that would be true but it did!