How to create a high score file
BlitzMax Forums/BlitzMax Beginners Area/How to create a high score file
| ||
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 |
| ||
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. |
| ||
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? |
| ||
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. |
| ||
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. |
| ||
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? |
| ||
You might find this helpful. Function GetChar() From http://en.wikibooks.org/wiki/BlitzMax/Modules/User_input/Polled_input#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. |
| ||
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 |
| ||
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: |
| ||
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? |
| ||
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. |
| ||
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? |
| ||
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 |
| ||
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? |
| ||
how about use Write/ReadString to write/read name and then Write/ReadInt() to write/read scrore |
| ||
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++). |
| ||
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) |
| ||
Alternatively you can say myString.Length Limit the names to a fixed size, such as 10 characters. |
| ||
If the names are limited you need to pad the extra space. |
| ||
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. |
| ||
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? |
| ||
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) |
| ||
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. |
| ||
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: |
| ||
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). |
| ||
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. |
| ||
@ Czar Flavius: It still seemed to work even with the old code...not sure why that would be true but it did! |