Help with file I/O
gary_ramsgate

I'm really struggling in loading a high score table.
I can create a blank hi-score table and save it to disk with no problems. When I try to load the data, the program runs ok and the name is displayed ok, but the score is loaded as some high number. I'm getting confused so any help would be gratefully received.

int blank_hiscores(void)
{
// Save a new blank hi-score table
//
ofstream myfile ("hiscores.txt");
for (int n=10; n>0;n--)
{
myfile << "xxxxxx"; //name
myfile << "\n";
myfile << n; //score
myfile << "\n";
}
myfile.close();
textprintf_ex(screen, font, 10, 50, makecol(255, 255, 255),-1, "New Hi-score table created");
rest(1000);
clear_bitmap(screen);
}

int load_hiscores(void)
{
string dummy;
int dummy1;

// Load Hi-score table
//
ifstream myfile ("hiscores.txt");
for (int n=0; n<10;n++)
{
getline (myfile,dummy);
hi_scores[n].name=dummy;
textprintf_ex(screen, font, 60, 30+(n*10), makecol(255, 255, 255),-1, "%s", hi_scores[n].name.c_str());

getline (myfile,dummy);
hi_scores[n].score=dummy1;
textprintf_ex(screen, font, 200, 30+(n*10), makecol(255, 255, 255),-1, "%i", hi_scores[n].score);
}
myfile.close();
textprintf_ex(screen, font, 10, 300, makecol(255, 255, 255),-1, "Hi-Score table loaded");
rest(2000);
clear_bitmap(screen);
}

X-G

You do realize you never actually set dummy1 to anything, right?

bamccaig
X-G said:

You do realize you never actually set dummy1 to anything, right?

Since he's asking us why he's outputting garbage he probably doesn't. ::):-[ It's an honest mistake with identifiers dummy and dummy1. ;)

gary_ramsgate said:

getline (myfile,dummy);
hi_scores[n].score=dummy1;

In other words, you mistakenly loaded the score into the string object instead of the integer. :)

I haven't done C/C++, file I/O, nor Allegro programming in a while so I couldn't say if that's the only problem...

Also, use [code]mycodehere[/code] tags and use descriptive variable identifiers. dummy and dummy1 are worse than a and b. ;D

For other BBCode syntax on Allegro.cc, check out the Help section (the Help button on the Post Reply toolbar).

Timorg

I use this code for my games that need a high score table, it can load and save and sort names, its doesn't deal with getting names from user, and showing the high-score table, it just manages the table itself. Even if the code isn't much use, it will stick you on the right track.

http://www.timorg.net/high_score.zip

If you need an example of use, I will whack something together.

-Tim

spellcaster

I wrote a small "game template" for allegro games some time ago. It also contains high score routines. You might want to take a look at it to get some ideas.

Please note that the main() routine is basically a showcase of the functions.

#SelectExpand
1#include <allegro.h> 2#include <stdio.h> 3#include <stdlib.h> 4 5 6enum { 7 HI_COLOR = -3, 8 TRUE_COLOR, 9 INDEXED_COLOR 10}; 11 12enum { 13 MODE_FAILED, 14 WINDOWED, 15 FULLSCREEN, 16 WINDOWED_OR_FULLSCREEN, 17 FULLSCREEN_OR_WINDOWED 18}; 19 20typedef struct { 21 int w, h, bpp; 22 int windowed; 23 int preferred_bpp; 24 25 BITMAP* buffer; 26} ScreenInfo; 27 28enum { HISCORE_COUNT = 10 }; 29enum { HISCORE_NAME_LENGTH = 16 }; 30 31 32typedef struct { 33 int score; 34 char name[HISCORE_NAME_LENGTH]; 35 char new_entry; 36} HiscoreEntry; 37 38/* We need one element more than actually used by the game */ 39HiscoreEntry hiscores[HISCORE_COUNT +1]; 40ScreenInfo screen_info = { 0, 0, 0, 0, 0, NULL }; 41 42 43/* 44 * Sets all hiscore table entries to a given name and value. 45 * name - the name to use. If longer than HISCORE_NAME_LENGTH, the name will be truncated. 46 * score - the score 47 */ 48void hiscore_init(char *name, int score) { 49 int a; 50 for (a = 0; a < HISCORE_COUNT; ++a) { 51 snprintf(hiscores[a].name, HISCORE_NAME_LENGTH, name); 52 hiscores[a].score = score; 53 hiscores[a].new_entry = 0; 54 } 55} 56 57/* 58 * Compares two hiscore entries. Used by quicksort in the hiscore_add() method 59 */ 60int hiscore_compare(const void *lhs, const void *rhs) { 61 return ((HiscoreEntry*) rhs)->score - ((HiscoreEntry*) lhs)->score; 62} 63 64 65/* 66 * Adds a new score to the table. After the call to this function 67 * you can recognize the new element in the table by the state of the 68 * new_entry flag - so if you want to display the latest entry to the table 69 * differently, just check the newly added flag. 70 */ 71void hiscore_add(char *name, int score) { 72 int a; 73 for (a=0; a < HISCORE_COUNT; ++a) { 74 hiscores[a].new_entry = 0; 75 } 76 77 /* the table contains one element more than HISCORE_COUNT, 78 * allowing us to add new scores easily 79 */ 80 snprintf(hiscores[HISCORE_COUNT].name, HISCORE_NAME_LENGTH, name); 81 hiscores[HISCORE_COUNT].score = score; 82 hiscores[a].new_entry = 1; 83 84 qsort(hiscores, HISCORE_COUNT+1, sizeof(HiscoreEntry), hiscore_compare); 85} 86 87int hiscore_save(const char* filename) { 88 int a; 89 PACKFILE *f = pack_fopen(filename, "w"); 90 91 if (f != NULL) { 92 for (a = 0; a < HISCORE_COUNT; ++a) { 93 pack_fwrite(hiscores[a].name, HISCORE_NAME_LENGTH, f); 94 pack_iputw(hiscores[a].score, f); 95 } 96 pack_fclose(f); 97 98 return TRUE; 99 } 100 return FALSE; 101} 102 103int hiscore_load(const char* filename) { 104 int a; 105 PACKFILE *f = pack_fopen(filename, "r"); 106 107 if (f != NULL) { 108 for (a = 0; a < HISCORE_COUNT; ++a) { 109 pack_fread(hiscores[a].name, HISCORE_NAME_LENGTH, f); 110 hiscores[a].score = pack_igetw(f); 111 } 112 pack_fclose(f); 113 114 return TRUE; 115 } 116 return FALSE; 117} 118 119 120/* 121 * Sets a gfx mode. 122 * If you pass a literal value for the colordepth (say 16) this 123 * routine will try to set the colordepth and return a failure if 124 * this depth could not be set. If you pass a mode constant the 125 * routine will try several fitting color depths if the preferred 126 * one could not be set. 127 * If you want to force a windowed mode, pass WINDOWED as windowed parameter 128 * If you want to force a fullscreen mode, pass FULLSCREEN as windowed. 129 * If you prefer windowed, but can live with fullscreen use WINDOWE_OR_FULLSCREEN. 130 * If you prefer fullscreen, but windowed is ok as well, pass FULLSCREEN_OR_WINDOWED 131 * 132 * width - screen width 133 * height - screen height 134 * colordepth - either HI_COLOR, TRUE_COLOR, INDEXED_COLOR or the bpp 135 * value you want to use (8, 16, etc..) 136 * windowed - either WINDOWED, FULLSCREEN, WINDOWED_OR_FULLSCREEN or 137 * FULLSCREEN_OR_WINDOWED 138 * 139 * returns TRUE if the mode was set, FALSE otherwise 140 */ 141int init_graphics(int width, int height, int colordepth, int windowed) { 142 143 /* 144 * Not all cards support 16 bit color, some require 15 145 * even if they are listed as "HiColor". 146 * In order to set a fitting graphics mode, some 147 * alternatives are defined. 148 * The 24 bit color mode is always tried last, since 149 * the 3 byte alignement makes it a terribly slow mode to 150 * use. 151 */ 152 static int hicolor[] = { 16, 15, 32, 24, 0 }; 153 static int truecolor[] = { 32, 16, 15, 24, 0 }; 154 int *depths = NULL; 155 int bpp = 0; 156 int a; 157 int mode = 0; 158 int result = -1; 159 160 /* 161 * depths is the array containing the bpp values. 162 * We'll iterate over that array (if it is != NULL) 163 * below and set the corresponding mode. If the 164 * array is NULL we'll try to set the colordepth 165 * stored in bpp (if bpp != 0) 166 */ 167 switch (colordepth) { 168 case HI_COLOR: 169 depths = hicolor; 170 break; 171 case TRUE_COLOR: 172 depths = truecolor; 173 break; 174 case INDEXED_COLOR: 175 bpp = 8; 176 break; 177 default: 178 bpp = colordepth; 179 } 180 181 /* Set the first mode (windows or fullscreen) to try */ 182 if (windowed == WINDOWED || windowed == WINDOWED_OR_FULLSCREEN) { 183 mode = GFX_AUTODETECT_WINDOWED; 184 } else { 185 mode = GFX_AUTODETECT_FULLSCREEN; 186 } 187 188 if (depths) { 189 /* While no mode is set and we have alternative modes */ 190 while (result < 0 && windowed != MODE_FAILED) { 191 a = 0; 192 while (depths[a] != 0 && result < 0) { 193 set_color_depth(depths[a]); 194 result = set_gfx_mode(mode, width, height, 0, 0); 195 if (result >= 0) { 196 bpp = depths[a]; 197 } 198 ++a; 199 } 200 201 if (result < 0) { 202 if (windowed == WINDOWED_OR_FULLSCREEN && mode == GFX_AUTODETECT_WINDOWED) { 203 mode = GFX_AUTODETECT_FULLSCREEN; 204 } else if (windowed == FULLSCREEN_OR_WINDOWED && mode == GFX_AUTODETECT_FULLSCREEN) { 205 mode = GFX_AUTODETECT_WINDOWED; 206 } else { 207 windowed = MODE_FAILED; 208 } 209 } 210 } 211 } else if (bpp != 0) { 212 /* User has requested a very specific color depth */ 213 set_color_depth(bpp); 214 result = set_gfx_mode(mode, width, height, 0, 0); 215 216 if (result < 0) { 217 if (windowed == WINDOWED_OR_FULLSCREEN && mode == GFX_AUTODETECT_WINDOWED) { 218 mode = GFX_AUTODETECT_FULLSCREEN; 219 } else if (windowed == FULLSCREEN_OR_WINDOWED && mode == GFX_AUTODETECT_FULLSCREEN) { 220 mode = GFX_AUTODETECT_WINDOWED; 221 } else { 222 windowed = MODE_FAILED; 223 } 224 225 /* if there is a 2nd mode we can try, try it: */ 226 if (windowed !=MODE_FAILED) { 227 result = set_gfx_mode(mode, width, height, 0, 0); 228 } 229 } 230 } 231 232 /* If a mode was set, result is equal to (or greater than) 0. 233 */ 234 if (result >= 0) { 235 236 /* If this method was called to change the graphics mode, 237 * we'll try to maintain the contents of the screen 238 */ 239 if (screen_info.w == width && screen_info.h == height) { 240 /* screensize was not changed, blit doublebuffer to screen */ 241 blit(screen_info.buffer, screen, 0, 0, 0, 0, width, height); 242 243 if (screen_info.bpp != bpp) { 244 /* colordepth has changed, we need a new buffer. 245 * If we destroy the buffer here and set it to NULL, 246 * a new bufer will be created below 247 */ 248 destroy_bitmap(screen_info.buffer); 249 screen_info.buffer = NULL; 250 } 251 } 252 screen_info.w = width; 253 screen_info.h = height; 254 screen_info.bpp = bpp; 255 screen_info.preferred_bpp = colordepth; 256 screen_info.windowed = (mode == GFX_AUTODETECT_WINDOWED); 257 258 /* create a doublebuffer if needed */ 259 if (screen_info.buffer == NULL) { 260 screen_info.buffer = create_bitmap(width, height); 261 } 262 263 return TRUE; 264 } else { 265 return FALSE; 266 } 267} 268 269/* 270 * Allows you to toggle the fullscreen / windowed aspect of your game. 271 * windowed - set to TRUE if you want to switch to windowed mode, 272 * set to FALSE if you want to switch to fullscreen mode. 273 * 274 * returns TRUE if the switch worked, FALSE otherwise 275 */ 276int set_window_mode(int windowed) { 277 if (screen_info.windowed != windowed) { 278 /* if your game uses video BITMAPs make sure to release them here, 279 * and recreate them after the call to init_graphics, kinda like this: 280 * 281 * int result = 0; 282 * unload_all_video_bitmaps(); 283 * result = init_graphics(screen_info.w, screen_info.h, screen_info.preferred_bpp, windowed ? WINDOWED : FULLSCREEN); 284 * load_all_video_bitmaps(); 285 * return result; 286 */ 287 return init_graphics(screen_info.w, screen_info.h, screen_info.preferred_bpp, windowed ? WINDOWED : FULLSCREEN); 288 } 289 return TRUE; 290} 291 292 293void update_screen() { 294 blit(screen_info.buffer, screen, 0, 0, 0, 0, screen_info.w, screen_info.h); 295} 296 297 298int main(int argc, char* argv) { 299 int lastkey = 0; 300 int a; 301 302 FONT *colorFont; 303 304 allegro_init(); 305 if (init_graphics(800, 600, HI_COLOR, WINDOWED_OR_FULLSCREEN)) { 306 install_keyboard(); 307 308 text_mode(-1); 309 310 clear_keybuf(); 311 clear(screen_info.buffer); 312 textprintf(screen_info.buffer, font, 10, 10, makecol(255,255,255), "%ix%ix%i", screen_info.w, screen_info.h, screen_info.bpp); 313 314 update_screen(); 315 316 while (!key[KEY_ESC]) { 317 318 if (key[KEY_W] && lastkey != KEY_W) { 319 lastkey = KEY_W; 320 set_window_mode(TRUE); 321 } else if (key[KEY_F] && lastkey != KEY_F) { 322 lastkey = KEY_F; 323 set_window_mode(FALSE); 324 } else { 325 lastkey = 0; 326 } 327 328 rest(1); 329 } 330 } 331 332 hiscore_init("This string is way too long. Way too long", 10); 333 hiscore_add("foo", 20); 334 hiscore_add("bar", 30); 335 hiscore_add("baz", 15); 336 for (a=0; a < HISCORE_COUNT; ++a) { 337 printf("%2i %16s %i\n", (a+1), hiscores[a].name, hiscores[a].score); 338 } 339 hiscore_save("hiscores.bin"); 340 hiscore_load("hiscores.bin"); 341 for (a=0; a < HISCORE_COUNT; ++a) { 342 printf("%2i %16s %i\n", (a+1), hiscores[a].name, hiscores[a].score); 343 } 344} 345END_OF_MAIN()

gary_ramsgate

Many thanks for the code. :)

Dustin Dettmer

Thats a clever trick of using enums for named constants.

X-G

Quote:

Since he's asking us why he's outputting garbage he probably doesn't.

It's a rhetorical device, you dimwit.

CGamesPlay
Quote:

Thats a clever trick of using enums for named constants.

It's pretty standard for C++. It allows you to put constants in a namespace.

spellcaster
Quote:

> Thats a clever trick of using enums for named constants.
It's pretty standard for C++. It allows you to put constants in a namespace.

It's also pretty standard for C ;)

GullRaDriel

It was / is pretty standard for C before even C++ exists ;)

CGamesPlay

Hmm, interesting. Why doesn't the grabber emit that sort of header then?

GullRaDriel

Heh, because grabber is a secret build from allegro big 5 ;-)

spellcaster

Sometimes using the preprocessor can be the better option. Esp. if you use some of the more powerful features of the pp.

Another point is that using the PP is IMO the better solution for the grabber headers. You're not working with actual constants. You are actually accessing an index. You just prefer to give that index a speaking name. So the mapping is value -> name.

In the case above the mapping is vice versa. I have name and map it to a value.

bamccaig
spellcaster said:

enum {
    HI_COLOR   = -3,
    TRUE_COLOR,
    INDEXED_COLOR
};

I'm not sure I understand the point of using enums instead of the preprocessor. It would help if I understood namespaces... :-/ And if you're not defining a namespace, spellcaster, wouldn't it be better to use the preprocessor? :-/

???

X-G said:

It's a rhetorical device, you dimwit.

X_G.PostCount+=2;:P

CGamesPlay
Quote:

And if you're not defining a namespace, spellcaster, wouldn't it be better to use the preprocessor? :-/

It's never better to use the preprocessor.

enum { NamedConstant = 2 };

void foo() {
    int NamedConstant = 3;
    NamedConstant = 4;
}

Obviously, this is an obvious example, since no one would create a variable named NamedConstant. However, this is totally valid code that compiles. But, had you used the preprocessor, you get this error message:

test.cpp: In function 'void foo()':
test.cpp:4: error: expected unqualified-id before numeric constant
test.cpp:5: error: invalid lvalue in assignment

Quite cryptic.

For a more likely example, try this on for size:

typedef struct BITMAP {
    int w, h;
} BITMAP;

#define BITMAP 3

BITMAP* buffer;

This sort of thing happens all the time.

bamccaig

Name collision is just a fact of programming though. I don't think that's a valid flaw in #define'ing constants. I was told that by #define'ing your global constants you save memory (even though it wouldn't be much).

Besides, considering how quickly the problem was detected in those threads returned by the search I would say that it's relatively easy to track down. You also probably don't want a local variable named the same as your constant to avoid logical errors (perhaps breaking somebody else's code) so I would still lean towards the preprocessor when possible.

It's also easy to come up with a naming convention to separate preprocessor definitions from other entities.

/*
 *  For example, and this is not to be taken as real code...
 */

#define NAMED_CONSTANT 4

class NamedConstant
{
private:
    int mintNamedConstant;
};

Anybody else have an opinion? ???

CGamesPlay
Quote:

Name collision is just a fact of programming though.

Sure, if you code like that. Using constants inside specific scopes completely eliminates the collision. You can select either constant:

#include <iostream>

using namespace std;

enum { Bitmap = 3 };

int main(int argc, char* argv[])
{
        int Bitmap;
        Bitmap = ::Bitmap;

        cout << "Bitmap constant is " << ::Bitmap << endl;
        cout << "Bitmap variable is " << Bitmap << endl;

        return 0;
}

Quote:

I was told that by #define'ing your global constants you save memory (even though it wouldn't be much).

Well, that's wrong, as the compiler will inline the constants anyways.

[append]
In addition, even if creating an enumeration caused any extra space to be allocated in the executable (which it doesn't), it would have to be enough data to cause the program to spill over into the next allocation chunk, otherwise it's insignificant. Allocation chunks are typically (or always, on a Pentium, IIRC) 4kb.
[/append]

The problem is that you're using the preprocessor to do things the compiler should be doing. It's like running a sed script on your code before you compile it. The sed script doesn't know whether or not you mean the variable or the constant, it just does string replacement. The compiler can accomplish the same task, but more intelligently.

Libraries that use the preprocessor have to prefix all of their symbols to avoid collision. Instead of doing that, use namespaces. Compare:

// GfxLib.h
#define MODE_SOLID 0x01

That uses the preprocessor to represent a flag passed to a function that draws shapes in a graphics library.

// ColDetek.h
#define MODE_SOLID 0x03

That uses the preprocessor to represent a flag passed to a function in a physics library. Now one can't use both libraries in the same file. To fix the problem, both libraries need to be made compliant.

// GfxLib.h
#define GFXLIB_MODE_SOLID 0x01

// ColDetek.h
#define COLDETEK_MODE_SOLID 0x03

Now to use them we have to use GFXLIB_MODE_SOLID or COLDETEK_MODE_SOLID. How annoying. Instead, use enumerations and namespaces:

// GfxLib.h
namespace GfxLib
{

enum { MODE_SOLID = 1 };

}
// ColDetek.h
namespace ColDetek
{

enum { MODE_SOLID = 3 };

}

Now, when you want to use ColDetek's constants, use ColDetek::MODE_SOLID. How does that help? using statements!

void foo() {
    using namespace ColDetek;

    ColDetekFunction(data, MODE_SOLID);

    // Lots of other calls to ColDetek's functions

    // But we can still use GfxLib in this function
    GfxLib::DrawPoly(points, GfxLib::MODE_SOLID);
}

Namespace collisions are not a product of programming, they are a product of poor programming.

bamccaig

Thanks. That's all very interesting.

CGamesPlay said:

Well, that's wrong, as the compiler will inline the constants anyways.

What do you mean by inline the constants? And if there's no difference in memory usage between using the preprocessor instruction (#define) versus an enum or const definition then why do large commercial games #define constants? :-/

CGamesPlay said:

// But we can still use GfxLib in this function

However, wouldn't using the prefix make it more clear which namespace each constant belongs to?

void foo()
{
    ColDetekFunction(data, ColDetek::MODE_SOLID);

    // Lots of other calls to ColDetek's functions

    GfxLib::DrawPoly(points, GfxLib::MODE_SOLID);
}

CGamesPlay
Quote:

I'm curious how the std namespace is defined if it contains entities from different header files... :-/ Or am I misunderstanding the structure of the std namespace? Does anybody know where it's defined? :-/

Namespaces don't have to be declared all at once. This code is valid:

namespace foo {

enum { constant };

}

namespace foo {

void func();

}

Quote:

Also, is there a way to switch between namespaces in the same scope (for example, use one, stop using it, use another one)?

No, because you can't "stop" using a namespace.

Quote:

However, wouldn't using the prefix make it more clear which namespace each constant belongs to?

Well, yeah, but it can also make it more convoluted. It gets to a point where you know you are passing ColDetek::MODE_SOLID to ColDetek::ColDetekFunction.

[edit]
Changed example to be clearer.

bamccaig
CGamesPlay said:

Namespaces don't have to be declared all at once.

CGamesPlay said:

No, because you can't "stop" using a namespace.

Good to know. Thanks!

I deleted the question in his quote convinced that it was a stupid qustion... Apparently it wasn't... :-/

CGamesPlay said:

Well, yeah, but it can also make it more convoluted. It gets to a point where you know you are passing ColDetek::MODE_SOLID to ColDetek::ColDetekFunction.

True... :)

spellcaster
Quote:

I'm not sure I understand the point of using enums instead of the preprocessor. It would help if I understood namespaces... :-/ And if you're not defining a namespace, spellcaster, wouldn't it be better to use the preprocessor?

Define "better". Main reason I use enums is that the enum is known to the compiler, so it'll appear in compile errors, during debug sessions, etc.

Besides that, it's mainly a question of style and lazyness. If I have a couple of constants with increasing numbers, using an enum is simply less typing.

And adding a new constant in the middle is way less work.
I also think that the enum version is slightly more readable.

You can also do something like this:

enum {
   MODE_1 = 0
   MODE_2,
   MODE_3,
   MODE_GUARD        
};
int countModes = MODE_GUARD - MODE_1;
// or
for (int a=MODE_1; a < MODE_GUARD; ++a) {
}

That's nothing fancy. It's just practical and provides a bit of comfort. Oh, have I mentioned that it saves a couple of keystrokes as well? This will not make a big difference, but if you start using it, and you need to insert that extra constant in the middle, you'll just smile, do it and smile even broader.

Guess that's why I'm using it. I like that warm feeling :)

CGamesPlay
Quote:

What do you mean by inline the constants?

const int NamedConstant = 3;
enum { NamedConstant = 3 };

Both of those create constant values. When you compile the program, the compiler doesn't actually add that value to the code as a variable. Instead, wherever you use the variable, the compiler places the literal value there directly.

Quote:

And if there's no difference in memory usage between using the preprocessor instruction (#define) versus an enum or const definition then why do large commercial games #define constants? :-/

In C there isn't much advantage to using the enums since you can't select the scoping you want anyways (in other words the function scope will always override the named constant and you can't change that behavior), and #defines are easier to type, I suppose (there might be an argument for "easier to read" also). The tradition probably carried over into C++, before people popularly realized the concrete benefits of enumerations.

Jonatan Hedborg

In C++, this is also quite nice:

enum WHATEVER {
ENUM1,
ENUM2,
ENUM3
}

void doSomething(WHATEVER w) {
...
}

Kitty Cat
typedef enum WHATEVER {
ENUM1,
ENUM2,
ENUM3
} WHATEVER;

void doSomething(WHATEVER w) {
...
}

Works in C, too. :)

Another thing I like about enums:

1enum eFoo {
2 VAL1,
3 VAL2,
4 VAL3
5};
6 
7void someFunc(eFoo foo)
8{
9 switch(foo)
10 {
11 case VAL1:
12 ...
13 break;
14 case VAL2:
15 ...
16 break;
17 }
18}

will generate a warning in GCC that 'VAL3' isn't handled in the switch statement.

GullRaDriel

KittyCat: that's pretty interesting !

Thread #591546. Printed from Allegro.cc