2

I must do a game in university in C.

I'm having real problems with the read and write in binary files.

I already asked the monitors, read a chapter about binary files in the book, and tried to search in Google for anything that could help me understand better the problem that I'm having, but none of these worked properly.

There's a struct in the game with all the informations that we need to save to the player restart the game when he likes. We must save all the different games in a single one binary file, using the name for recongnition. So, I did a algorithm that read the binary file, if it exists, and if it find a game with the same name of the player, it overwrite it with the new game, if it does not exist, it will write in the end of the file.

We must also do the reading of the file for loading a previous game.

I did a little program just to test the idea with the struct having only the player's name, but I'm having a problem: when I save, it appears to be working propperly, but when I try to load it, it shows me twice or even three times the name of the player (i putted the command puts with the name in the case the program finds a game saved with the same name).

Another thing that is puzzling me is that when I save two different games with different names, when I try to load the first one, it shows only one time.

I would really aprecciate any help that you could give me in why this is happening.

My code:

int main()
{
GAME game;
GAME jogoTeste;
FILE *saved_game;
int i = 0;
int choice, saved = 0;

gets(game.player.name);

printf("Choose what you want to do:\n");
printf("1 to save, 2 to load\n");
scanf("%d", &choice);

if (choice == 1)
{
    i = 0;
    saved_game = fopen("saved_game.dat", "a+b");
    rewind(saved_game);
    while (!feof(saved_game))
    {
        fread(&testeGame, sizeof(GAME), 1, saved_game);
        if (!strcmp(testGame.player.name, game.player.name))
        {
            int control = fwrite(&game, sizeof(GAME), 1, saved_game);
            if (control != 1)
            {
                printf("An error happened in the wrintting\n");
                printf("Were written %d elements intead of 1.\n", control);
            }
            else
            {
                saved = 1;
                printf("Player updaded!\n");
            }
        }
    }

    if (saved == 0);
    {
        fwrite(&game, sizeof(GAME), 1, saved_game);
        printf("New player saved\n");
    }
    fclose(saved_game);
}

else if (escolha == 2)
{
    i = 0;
    saved_game = fopen("saved_game.dat", "rb");

    while (!feof(saved_game))
    {
        fread(&testGame, sizeof(GAME), 1, saved_game);

        if (strcmp(testGame.player.name, game.player.name) == 0)
        {
            puts(testGame.player.name);
        }
    }
    fclose(saved_game);
}

}

and the struct GAME is (the other ones are not that important here):

typedef struct
{
    PLAYER player;
    GUARD guards[5];
    KEY keys[5];
    COORDINATE walls[50];
    COORDINATE tower[50];
    COORDINATE ogre;
} GAME;

If there is anyhing in portuguese is because I didn't see it right now and just forget to translate it for here. I'm sorry

EDIT: I tried some alterations that you mentioned and the error that duplicated my loading game are gone, but there is something that is really puzzling my head right now, my code that saves the game right now is:

saved = 0;
    saved_game = fopen("saved_game.dat", "r+b");
    while (fread(&jogoTeste, sizeof(GAME), 1, saved_game))
    {
        if (strcmp(jogoTeste.player.name, game.player.name) == 0)
        {
            fseek(saved_game, currentOffSet, SEEK_SET);

            control = fwrite(&game, sizeof(GAME), 1, saved_game);

            if (control != 1)
            {
                printf("An error happened in the wrintting\n");
                printf("Were written %d elements intead of 1.\n", control);
            }
            else
            {
                saved = 1;
                printf("Player updated!\n");
            }

            fseek(saved_game, 0, SEEK_END);
        }
        currentOffSet = ftell(saved_game);
    }

    if (!saved);
    {
        fwrite(&game, sizeof(GAME), 1, saved_game);
        printf("New player saved\n");
    }
    fclose(saved_game);

But when I execute it, it gives me in the screen: "Player updated!" AND "New player saved", how can it be? When it updates the player, it also changes the variable saved to 1, and there is the if before the "New player saved" just for that. How can it be executing two parts that shouldn't be?

  • 3
    Please note that `gets` and `while (!feof(saved_game))` are both bad. the first is obsolete. – Weather Vane May 24 '18 at 18:47
  • 5
    Lots of cannonical problems here: 1) [gets is dangerous and should not be used](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-so-dangerous-that-it-should-not-be-used), 2) You're not checking the return values of scanf, 3) [while(!feof(fp)) is wrong](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-considered-wrong), 4) I don't understand why you are opening the file for append mode and then trying to rewind. – MFisherKDX May 24 '18 at 18:47
  • 2
    Both the question and the author would benefit from putting together a [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve). – user3386109 May 24 '18 at 18:54
  • 2) the scanf will not be in the final code, it was just for me to test if I could do the save/load part 3) Why (!feof(fp)) is wrong? I thought about changing when I read that it could not return false if there was no operation before, but I thought that the rewind counted 4) I don't understand either, I used first "r+b" mode, but the professor made me change that because it was to create a file if there wasn't one already – LucasCardoso910 May 24 '18 at 19:00
  • Re: " Why (!feof(fp)) is wrong?" --> If `fread(&testGame, sizeof(GAME), 1, saved_game);` fails to read data, what does your code do with the junk that is in `testGame`? `feof()` returning 0 does not mean the _next_ input operation has data to read. – chux - Reinstate Monica May 24 '18 at 19:10
  • 1
    BTW, who or what text suggested code like `while (!feof(saved_game))`? – chux - Reinstate Monica May 24 '18 at 19:11
  • @LucasCardoso910: `feof(fp)` makes sense when you are *reading* data, not writing it as in your case. `while(!feof(fp))` is wrong under all circumstances, this has to do with *concurrency* of io operations. So basically, your code is a classical examples of a race condition. Your loop schedules a write (through OS) and then checks the eof condition. It performs a *random* number of writes before *accidentally* exiting. – alexsh May 24 '18 at 19:13
  • So what should I use instead of while(!feof(fp))? I had seen another way with the return of fread, but that will not guarantee also that the next time will read something valuable, right? – LucasCardoso910 May 24 '18 at 19:15
  • @LucasCardoso910: why do you need a `while` loop at all? Just remove it and do a single write (or not, if `choice != 1`). Do check the return of `fopen` though. – alexsh May 24 '18 at 19:18
  • The loop is because the idea is to save different games of different people, so I needed to know the exactly place to overwrite the previously game or write it in the end – LucasCardoso910 May 24 '18 at 19:21
  • Your struct GAME has no variable for type PLAYER. – stark May 24 '18 at 19:22
  • I just realized that it is missing the line that I use the fread to read the file in the choice 1, I'm sorry, guys – LucasCardoso910 May 24 '18 at 19:24
  • It has no variable for type PLAYER because an error when I was translating to post here, the original code had. Just edited that too – LucasCardoso910 May 24 '18 at 19:26
  • In the second `while` loop check the return of `fread` itself. If you read in the first loop as well (a file that is opened for reading), use the return of that `fread`. – alexsh May 24 '18 at 19:27
  • 1
    @alexsh `feof` is not wrong in **all** circumstances, although the `while` loop is. Its good usage is **after** reading. Its purpose is to tell if the *previous* read failed, not if the *next* one will fail. – Weather Vane May 24 '18 at 19:50
  • @WeatherVane: I did not mean to say `feof` is uniformly bad, I meant he should use a different condition in the while loop (such as the return of `fread`) or use `feof` inside the loop itself. I also realized that he has a logical problem (reading and appending to the same file), which is even more problematic than the loop itself. – alexsh May 24 '18 at 21:36
  • The append mode allows me only to write in the end of the file? No matter if I change the point that I am reading or writing? But I don't know if that will solve it too, because when I was first having problems, I was using the w+b mode, but I am really confused with the modes right now – LucasCardoso910 May 24 '18 at 21:57
  • I will try the solutions that you all gave me right now and will be right back. Thanks for all the comments – LucasCardoso910 May 24 '18 at 21:58
  • I eddited my post to update my situation, I am having another problem right now, if it is not so well described in the post I will gladly reedit it to bigger clarity. – LucasCardoso910 May 24 '18 at 22:46

2 Answers2

2

In your code

    fread(&testeGame, sizeof(GAME), 1, saved_game);
    if (!strcmp(testGame.player.name, game.player.name))
    {
        int control = fwrite(&game, sizeof(GAME), 1, saved_game);

After you fread, the file pointer has moved forward, then you fwrite after where the fread was. You need to move the file pointer back to overwrite the old record i.e. use ftell to get where you are then go fseek to go back to that spot or alternatively use fseek to move relative to your current file position back sizeof(GAME) bytes.

e.g.

    currentOffset = ftell(saved_game);
    fread(&testeGame, sizeof(GAME), 1, saved_game);
    if (!strcmp(testGame.player.name, game.player.name))
    { 
        fseek(saved_game, currentOffset, SEEK_SET);
        int control = fwrite(&game, sizeof(GAME), 1, saved_game);
AndersK
  • 33,910
  • 6
  • 56
  • 81
1

The main problem is the while(!feof(...)){} construct. This performs a random number of appends due to a race condition. The simple fix is to remove the loop altogether. A number of other problems with the code probably will not affect its operation, although should be fixed as a matter of good practice (using getc, superfluous rewind, etc., see the comments).

EDIT: the OP changed the code after some discussion so removing the loop as suggested above is not going to work. The proper fix is to separate the reading the file to look for the name of the player, maybe setting a boolean flag, and then perform an append if the boolean is set.

alexsh
  • 1,071
  • 6
  • 10
  • But I had tried before with other ways instead of !feof and it resulted in the same error – LucasCardoso910 May 24 '18 at 19:31
  • How about using `while(fread(&testeGame, sizeof(GAME), 1, saved_game) != 1)` instead (and removing the `fread` inside the loop). Also, consider what happens when you *append* and then try to read what you have just appended? You will append again. And again. In other words you have to read the file first, and *then* append to it to avoid an infinite loop. – alexsh May 24 '18 at 19:36
  • When I open a binary file in "a+b" mode, no matter if I use the fseek or wathever to move the cursor, when I write I will always write in the end of the file? – LucasCardoso910 May 24 '18 at 21:54
  • It shouldn't be `while(fread(&testGame, sizeof(GAME), 1, saved_game == 1))`? Or just `while(fread(&testGame, sizeof(GAME), 1, saved_game))`? Because when it returns 1 is because it has readen one element, right? I had seen this form in a book, but I think that I tried and it didn't worked properly to me, but I will try it again. Thank you very much – LucasCardoso910 May 24 '18 at 22:05
  • I followed the both answers, actually, but yours was the one that helped more, but it really didn't worked properly until I used the variable currentOffSet that was in the answer of Anders K. It's currently working, so I must thank you guys a lot – LucasCardoso910 May 25 '18 at 15:05