game saves in allegro?
relay01

Now this is WAY ahead in my scope of programming and even if this is possible I don't believe I'd be able to do it yet but I figured I'd throw it out there just because... I'm bored and seek conversation...
Does allegro offer anything about game saves? Does this have to be done in something else in order to do game saves? Has anyone wrote a program in allegro that uses game saves? I just figure it would be very lame (especially with an expansive game) if your progress couldn't be saved. I know with the game I'm working on that if I didn't offer a game save that most wouldn't be able to beat it. ???

Inphernic

No, Allegro doesn't offer game saving (although one could use config files for it, that would be exceedingly ghetto). That doesn't mean it's not possible.

spellcaster

Allegro packfile functions are exactly what you want. So, define your game data, and then save it. It's as easy as that.

Inphernic
Quote:

Allegro packfile functions are exactly what you want.

More like "Allegro packfile functions are exactly what you need", considering that if he would know anything about file saving/loading in Whatever++^2, he wouldn't be asking it like this (leading one to assume that what he basically wants are save_game() and load_game()). ;)

relay01

I'm not entirely familiar with packfiles yet. I'm working in C++ (which i'm new to) and allegro (which i'm also new to) I've been learning as much allegro and c++ as I could at every moment of free time... Which has left me with headaches and sleepless nights for a fortnight. I actually welcome going to calculus class because it's less confusing.

Thomas Fjellstrom

allegro's PACKFILE is almost identical to C's FILE.

spellcaster

See, we'd really like to help you, but right now what you're saying is basically:

"I want to do stuff. Can allegro do stuff?"

So, giving you an answer that actually helps you is kinda hard. But, let's say you're using something like this:

typedef struct {
     int level;
     int score;
     int lifes;
} GameData;

for your game data, what you'd do to save the data is to simply open a file, store the data and then close the file. if you're only targeting a single platform, something like:

GameData data;

FILE* f = fopen("game.sav", "wb");
fwrite(&data, sizeof(GameData), 1, f);
fclose(f);

will do the trick.
If you want to target several platforms, you should save each component on it's own:

GameData data;

FILE* f = fopen("game.sav", "wb");
fwrite(& (data.level) , sizeof(int), 1, f);
fwrite(& (data.score) , sizeof(int), 1, f);
fwrite(& (data.lifes) , sizeof(int), 1, f);
fclose(f);

if you don't know the fopen / fwrite / fclose methods - google for them. They are the basic C file IO functions, and you'll see them a lot. Also, allegro's packfile functions are modelled after them, so once you know how to use the std c routines, the allegros fuznctsions will be less intimidating.

Jonny Cook

Is there any reason why you would want to use Allegro's file IO routines instead of the standard C++ ones?

BAF

It's supposed to be more portable, endian safe, supports compression and encryption, and you can chunk it (pack_fopen_chunk).

relay01

Well that is what I asked... exactly... I wanted to know if allegro could do such saving features and apparently they can with packfiles... now if somebody could explain to me what a packfile is and does and how to use them... that would be awesome squared...

Matthew Leverton

Have you read the manual yet and tried? If you cannot do that, you cannot do anything at all.

miran
Quote:

now if somebody could explain to me what a packfile is and does and how to use them

Spellcaster already did that. Read his post again, but replace FILE with PACKFILE and all the fsomething() functions with pack_fsomething(). Everything else is the same or at least very similar. The manual explains most things that need explaining in detail.

m c

I'm away from my dev pc now (and will be for hours yet) but packfiles != grabber datafiles do they?

miran

packfiles != datafiles

Tobias Dammers

datafiles == subset_of(packfiles)

Shawn Hargreaves

At the risk of confusing things further, I have to say that fwrite (or pack_fwrite) are horrible API's. Totally untypesafe, error prone, and not portable across different endianess or processor word sizes.

I'd strongly recommend using individual component write functions (putw, putf, etc) instead.

Tobias Dammers

pack_mputXXX() is what you're looking for.

relay01

Tobias... you changed your avatar so for a second I couldn't recognize you (I'm a visual learner) but luckily you have a funny name so I could quickly catch on...
My game is so far from having the ablility to save anything so all this doesn't really mean anything to me yet... but what can I say... I like to spark conversation...
And I'm bored at work...

Jonny Cook
Quote:

At the risk of confusing things further, I have to say that fwrite (or pack_fwrite) are horrible API's. Totally untypesafe, error prone, and not portable across different endianess or processor word sizes.

I'd strongly recommend using individual component write functions (putw, putf, etc) instead.

But isn't pack_frwite just essentially pack_putc, except for an array of characters instead of an individual character?

I thought pack_fwrite would look similar to this:

void pack_fwrite(void *data, int size, PACKFILE *pf) {
    for (int i = 0; i < size; ++ i) {
        pack_putc(data<i>, pf);
    }
}

So what's so unsafe about that? I'm sorry, but my knowledge endianess is pretty limited.

Is this something I need to consider whenever I read/write to/from files?

Shawn Hargreaves

Yes, that's exactly how pack_fwrite is implemented.

The problem is, this is taking the address of some other type (eg. an integer), treating that as a void *, then casting the void * to a byte pointer and reading the data as individual bytes.

This is not a safe thing to do.

For one thing, it is very error prone. How does it know how many bytes to process? You have to tell it. If you tell it wrong, it does the wrong thing.

And also, how many bytes there are in an int can be different on other machines. And which order those bytes are in can be different (that is called endianess). So a file you write on one computer will not be able to be read on a different one.

Solution: don't use API's that take void * parameters (or at least, not unless you absolutely have to). They're evil and not typesafe.

In this case you can easily just call the strongly typed methods for bytes, ints, floats, etc. Your code becomes portable, less error prone, and also shorter and more readable: what's not to like about that?

Tobias Dammers
Quote:

but luckily you have a funny name so I could quickly catch on...

I take this as an offence.

Quote:

So what's so unsafe about that? I'm sorry, but my knowledge endianess is pretty limited.

What Shawn said. Or, to rephrase:
Suppose you have a 16-bit short. Since a byte is only 8 bits wide, you have to split you number up into two 8-bit parts, a "high" byte (the MSB, Most Significant Byte) and a "low" byte (the LSB or Least Significant Byte). When storing these two bytes in a byte-sequence (e.g. RAM, a hard disk, a network stream), you have 2 choices: MSB first or LSB first. Which one you choose is a matter of convention, and differs between platforms. MSB-first is referred to as "little-endian", LSB-first as "big-endian".

Quote:

Is this something I need to consider whenever I read/write to/from files?

Yes, unless you store everything as single bytes or strings (which are arrays of bytes).

CursedTyrant

Something like this perhaps?

1#include <iostream>
2#include <fstream>
3using namespace std;
4 
5typedef struct PLAYER
6{
7 int x, y;
8} PLAYER;
9 
10int main()
11{
12 PLAYER *p1 = new PLAYER();
13 p1->x = 0; p1->y = 5;
14
15 ofstream os("file.dat", ios::binary);
16 os.write((char *)p1, sizeof(struct PLAYER));
17 os.close();
18 std::cout << "X1: " << p1->x << "\t" << "Y1: " << p1->y << endl;
19
20 PLAYER *p2 = new PLAYER();
21 ifstream is("file.dat", ios::binary);
22 is.read((char *)p2, sizeof(struct PLAYER));
23 is.close();
24 std::cout << "X2: " << p2->x << "\t" << "Y2: " << p2->y << endl;
25
26 std::cout << "\n\n";
27 system("pause");
28 return 0;
29}

Shawn Hargreaves

That's the bad way to save things. It is just blindly dumping out whatever order of bytes happen to be in your PLAYER struct, which depends on what processor you happen to be running on today, and also how the compiler happened to lay out the struct in memory. The resulting files are fragile, non portable, and not future proof.

The only safe way to do this is to write your fields one at a time through some kind of interface that has well defined endianess rules:

output.WriteInt(p1->x);
output.WriteInt(p1->y);

Yes, it's a bit more typing for structures with lots of fields, but there are no shortcuts that don't lose safety.

And this extra typing is often actually a good thing because it provides a clear reference about exactly what the format of your file is: very handy if you need to extend it in the future, or if your types grow to include some fields that shouldn't be saved to disk, or if you add things that can't be saved at all by a simple binary dump (for instance pointers to other objects).

Actually I lied when I said there are no shortcuts: in languages with dynamic reflection capabilities (Python, C#, Java) you can use an automatic serializer to locate all the members for you and figure out the appropriate types. That's not possible in C++ though.

Tobias Dammers

...and in fact, automatic serialization has its downsides.
For example, in my current project, I use chunk files to store my object data. Sometimes I want to extend the functionality of an object, which means I have to add more fields to the class. The loader, however, checks for end-of-chunk, and if it can't read any more, it fills the rest of the class with default values. This way, I can still open older files without any data loss. This is pretty hard to achieve with an automatic serialization process.
And finally, often you don't need to store all data, but just a few, while the rest of the fields is calculated at run-time. In fact, if your objects reference other objects (for example, a pointer representing the current target of a space ship), then you need to save these references as something other than pointers (since pointers vary from one run to the next).

Shawn Hargreaves

It's true, the simple "just dump every single field" type serialization is pretty limited.

But there are smarter ways, especially if your language allows you to add reflection metadata on a per-field basis. For instance in C# you could write a serializer that understood things like:

class MyType
{
    public int ThisWillBeSerialized;
    public OtherType AndSoWillThis;

    [NoSerialize]
    public int ButThisOneWillNot;

    [SerializeOptional(Default="Hello World")]
    public string ThisMayNotBeThereInOldFileVersions;
}

ie. most stuff just works automatically, but you can decorate the type to indicate where you want other things to happen.

I'm a huge fan of reflection and attributes. It's a real shame that C++ doesn't support anything like that!

Thread #578370. Printed from Allegro.cc