[A5] Writing Unicode to a file
Neil Roy

I have been working on a high score screen for my game (written in C using Allegro 5.1.7 and MinGW 4.7.0 with Code::Blocks 12.11 on Windows 7 64bit). It does the usual, displays high scores etc. If you make it on the high score screen it will prompt you to enter your name. Now I have been busily converting my old Allegro 4 code over to Allegro 5 Unicode and after much reading on these forums and documentation I have it working, only I had the game crash completely when it went to display unicode text with the following line...

      al_draw_ustr(font_verdana, fontcolor,
                   WIDTH/2, y, ALLEGRO_ALIGN_CENTRE,

This works fine with al_draw_text() instead and using a C string obviously.

Now I managed to track it down to loading/saving the high score data. When the game first starts, if there are no high scores, it writes new files with default values and it works fine, the line above displays fine as well. But once you get a new high score and it writes new values to the disk, it doesn't seem to either write or read them back properly.

I have the following struct:

typedef struct HISCORES {
   ALLEGRO_USTR *name;
   unsigned long  score;
   int            level;
   char           time[TIME_LEN];

this is written with...

      hiscore_file[i] = al_fopen(filename, "wb");
      if(hiscore_file[i]) {
         al_fwrite(hiscore_file[i], hiscore[i], NUM_SCORES * sizeof(HISCORES));

This probably isn't the best way to do things, I normally write each struct element separately, but this was code I already had laying around from my Allegro 4 days and it has always worked, and it works when the name is stored as a character array (C string) but not like this it seems? I have similar code for reading it in.

I am wondering, will reading and writing this as a C string, converting before writing with al_cstr() solve this problem or do you think there is something else wrong? Like I said, aside from the problems with it reading/writing, I have the Unicode name input working great (thanks to multiple posts in the forums).


The only way this could have worked before was if you had your names in a fixed size array (like char name[NAME_LEN]). What you're doing right now is writing the pointer value to file, which naturally won't work.

NiteHackr said:

I am wondering, will reading and writing this as a C string, converting before writing with al_cstr() solve this problem or do you think there is something else wrong?

Yes, this will work (not with al_cstr() by itself though, if you read its documentation). It's better (but more memory hungry) to do:

int l = al_ustr_size(name);
al_fwrite32l1(f, l);
al_fwrite(l, al_cstr(name), l);

There isn't really a clean way to read it back, unfortunately, at least not without allocating the string twice.

Neil Roy

Thanks, I almost hit myself in the head when I read your response about it being a pointer! DUH! Why that didn't occur to me I don't know. But you're right, I had a fixed size before just as you assumed.

Looks like you have a couple typos in here as well. ;)

SiegeLord said:

int l = al_ustr_size(name);
al_fwrite32l1(f, l);
al_fwrite(l, al_cstr(name), l);

Not sure what al_fwrite32l1(f, l); is, and where are you writing the name to in the last line?! ;)

Now that I have unicode working and using truetype fonts, the width varies on the names that are entered so the number of characters allowed for your name depends on the horizontal pixels your name takes up. I check the width of your name as it is entered in pixels now and only allow up to a certain number of pixels, which works beautifully. I had a fixed width before based on the number of characters. Anyhow, thanks a bunch.

I plan to read it back in as a C string and then assign that to the ustr with something like al_ustr_assign_cstr(hiscore[dif][i].name, cstrname);

EDIT: I just noticed al_fget_ustr() hmmm.... but no al_fput_ustr(), so I assume I still have to write it to file as a C string, but then I could read it with this? <confused>

Thanks a lot for your help with this anyhow. I guess I could have done it without asking a question in here, but I wanted to confirm I was doing it right as I am new to these unicode routines and it doesn't hurt for others to read this if they have the same questions. ;)

And just for completeness, anyone who may be curious, I display the name as it is being entered with a vertical black line at the end of the text, like you see when typing in a message in there forums. And as each character is typed, I get the total width of the name in pixels and test it against a defined width limit and remove the last character if it exceeds it, which works out really nice. Just posting part of my code for anyone that may be interested anyhow...

1// yourname is an ALLEGRO_USTR defined and initialized earlier 2// hiscore[difficulty][myscore].name is an ALLEGRO_USTR 3bool done = false; 4int key; 5int pos = (int)al_ustr_size(yourname); 6int x; 7 8while(!done) { 9 // copies a name, if one already exists to be displayed for editing 10 if(pos>0) al_ustr_assign(hiscore[difficulty][myscore].name, yourname); 11 12 al_wait_for_event(event_queue, &event); 13 switch(event.type) { 14 case ALLEGRO_EVENT_DISPLAY_CLOSE: 15 done = true; 16 break; 17 case ALLEGRO_EVENT_KEY_CHAR: 18 if(event.keyboard.unichar >= 32) { 19 pos += al_ustr_append_chr(yourname, event.keyboard.unichar); 20 // Get the width of the name in pixels (because TTF varies in width) 21 x = al_get_text_width(font_verdana, al_cstr(yourname)); 22 if(x > MAX_NAME_LEN) { // Check if the width in pixels is greater than the allowed size 23 if(al_ustr_prev(yourname, &pos)) al_ustr_truncate(yourname, pos); 24 } 25 } 26 else if(event.keyboard.keycode == ALLEGRO_KEY_BACKSPACE) { 27 if(al_ustr_prev(yourname, &pos)) al_ustr_truncate(yourname, pos); 28 } 29 else if(event.keyboard.keycode == ALLEGRO_KEY_ENTER|| 30 event.keyboard.keycode == ALLEGRO_KEY_PAD_ENTER) { 31 al_ustr_assign(hiscore[difficulty][myscore].name, yourname); 32 done = true; 33 } 34 break; 35 } 36}


Yeah, too many l's. Here's what I meant:

int sz = al_ustr_size(name);        // get the number of bytes in the name string
al_fwrite32le(file, sz);            // write the size to the file
al_fwrite(file, al_cstr(name), sz); // write the string to the file

I imagine you'd read the above using something like this:

int sz = al_fread32le(file);             // read the number of bytes in the name
char* buf = malloc(sz);                  // allocate a temporary buffer for the name
al_fread(file, buf, sz);                 // read the name into the buffer
name = al_ustr_new_from_buffer(buf, sz); // copy the buffer into the ustr
free(buf);                               // free the temporary buffer

I don't think there's a way to avoid the temporary buffer, which is a bit of a bummer from an api standpoint (what is missing is a function that takes ownership of the passed data pointer).

Neil Roy

Yay! Thanks for that! Wow, your timing couldn't have been better. I had been sitting here trying different ideas out and I had wrote the size of the string first then the string and it all seemed to be writing okay but wasn't reading them all back in properly. I tried what you suggested and it now reads and writes flawlessly, thanks a lot.

I think my problem was I wasn't writing the size properly and wasn't of course reading in back in properly as well as other stupid mistakes.

I am SO happy to finally get done this part of my game. Graphics all work fine, sound etc... never much of a problem with those, but this was a headache.

Peter Wang
SiegeLord said:

what is missing is a function that takes ownership of the passed data pointer

That kind of API makes me uneasy. For one, the buffer would have to be allocated with al_malloc, or else you would need to provide the corresponding free function to be called by al_ustr_free (adding a field to ALLEGRO_USTR).

An alternative would be to allow you to reserve space in the ALLEGRO_USTR, then you could read directly into it.

But for a high score list you might as well just read into a stack-allocated buffer of reasonable size (check!), then create the ALLEGRO_USTR from that.

Thread #613905. Printed from Allegro.cc