2

I am currently working on a project for one of my first programming classes. We are building a C program that reads a .txt file, which contains information about an Othello (aka Reversi) match, and determines whether the match is correct or not (if it followed the rules, the moves were correct, etc.). The style of the .txt received is the following:

FirstName,Letter1
FirstName,Letter2
Letter
Plays

Where Letter1 and Letter2 are either B or N (for white and black in spanish, respectively), then the following line is either B or N again, which determines who starts moving (in our version of Othello both colours may start) and then plays is a series of positions on the 8x8 grid, represented by , with letters from A to H and numbers from 1 to 8. There is a single play per line, so an example of an (incomplete) match could be

John,N
Martin,B
N
F5
D6
C3

Where the first move is John who plays black F5.

My problem arises with the possibility of a player skipping a turn. Players may (and must) skip their turn if and only if there is no possible play to be made. So, if a player skips a turn, an empty line will be in place of their play. Therefore, there might be a .txt file with the following style (this is an impossible move, just in case)

F5
D6

C3

Where D6 and C3 were both plays made by the same player, consecutively, since their opponent skipped their turn.

So, in a certain part of my program, I have a read_file function that goes through the .txt file and saves the information about the game on a struct I called "datos". In particular, one of its fields is plays, which is an array of strings. There, I am storing the strings of the format for normal plays and "XX" for skipped turns. So the array for the last game would be ["F5", "D6", "XX", "C3"].

I have spents 2 days working on this function and when it finally seemed to work I realized it was not saving the skipped turns at all. Instead, when a player skipped its turn, the function stopped saving the plays made and moved on with the program, leaving the match incomplete. This is my current code, it does not even try to store the "XX" plays because I have tried a thousand methods and they always fail.

char play[3];
    while (fscanf(file, "%s\n", play) == 1) {
       if (strlen(play) != 2 || jugada[0] < 'A' || jugada[0] > 'H' || jugada[1] < '1' || jugada[1] > '8') {
            fclose(file);
            free_datos(match);
            return 5;
        }

        //if (jugada[0] == '\n' && jugada[1] == '\0') strcpy(jugada, "XX");

        match->plays_counter++;
        match->plays = realloc(match->plays, match->plays_counter * sizeof(char *));
        match->plays[match->plays_counter - 1] = malloc(MAX_PLAY * sizeof(char));
        strcpy(match->plays[match->plays_counter - 1], play);
    } 

This current version just ends the while loop when it finds an empty line, which is simply not what I need it to do.

I tried multiple versions, using fscanf, fgets, chatGPT, searched stackOverflow and found no answers to my problem. I have read suggested posts but most deal with the removal of empty lines rather than actually wanting to take them into account.

About the other versions, sometimes they would add a "XX" after every play, doubling the size of the array (but correctly recognizing skipped turns, lol), more often than not they would just crash or end the while loop after finding a skipped turn, etc. I have asked chatGPT too many times and it keeps providing me with wrong answers.

Could someone help me out? I have tried with fgets but all of the versions I tried would also not work. For some reason I could not find any more info on youtube or anywhere, most of the times what to do when working with empty lines was overlooked.

Sorry if this was too long, I have been working on this for a surprisingly large amount of time considering it should really not be that hard. It is driving me crazy at this point. Thank you in advance!

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • 6
    Don’t use scanf. It skips all white space - including runs of new lines. You want fgets; consult the man page or online documentation to see examples. – nneonneo Jan 31 '23 at 00:59
  • What you need to understand is that what looks like an empty line in a file really isn't empty. As a minimum, there's a newline character, and there could be any number of spaces and/or tabs (unless you've deliberately stripped trailing white space). So after reading a line with `fgets`, the code needs to look at each character. If every character in the line is a whitespace character, then the line is blank. – user3386109 Jan 31 '23 at 01:15
  • 3
    I'd also get rid of all the dynamic memory nonsense (`malloc` and `realloc`). The maximum number of moves in a game of Othello/Reversi is 64. So a fixed-size array of moves will work just fine. – user3386109 Jan 31 '23 at 01:20
  • 1
    @user3386109: plus some number of skipped moves, although there are other ways of storing that. Personally, I doubt whether there is any point keeping an array of moves at all. Just process them one at a time as you read them. – rici Jan 31 '23 at 01:22
  • @rici Agreed. Just processing them as you read them is a good idea. – user3386109 Jan 31 '23 at 01:31
  • Thank you for your answers. Indeed now that you guys mention it, the dynamic arrays might be unnecessary. However, I first started using them because this is just part of a function inside main, and I couldn't find another way to return an array of moves without having issues with local adresses, but I am completely new to C so there is probably a way I just have not found yet. I considered just reading the moves on the go but I felt it was harder that way, and now I am far too invested in the reading-before method to change haha, but I will definitely take that into account for the future! – Below Average C Programmer Jan 31 '23 at 12:03
  • @Octavio: In your previous comment, you stated that you are having trouble returning an array from a function. You may find this helpful: [Returning an array using C](https://stackoverflow.com/q/11656532/12149471) – Andreas Wenzel Jan 31 '23 at 15:23
  • Thanks, Andreas! I have finished my project now, it works, so I will take a look into the tips you and the rest have mentioned in order to improve my work. Thanks once again! – Below Average C Programmer Feb 03 '23 at 02:40

1 Answers1

3

Your problem is that the function call

fscanf(file, "%s\n", play)

does not do what you want.

The %s specifier will first consume and discard all whitespace characters (which includes spaces and newline characters), and then when it encounters a non-whitespace character, it will read everything it encounters until the next whitespace character.

The \n character in the format string will instruct scanf to consume and discard all whitespace characters (not just a single one!), until it encounters a non-whitespace character.

This means that with the input

F5
D6

C3

the first call to fscanf will read F5\n, the second call to fscanf will read D6\n\n and the third function call will read C3\n. However, this is not what you want. You want the second call to only read one line instead of two lines, i.e. you want it to read only D6\n instead of D6\n\n.

I do not recommend that you attempt to solve this problem with scanf. Instead, I suggest that you use fgets, as that function behaves in a more intuitive manner. In particular, that function will always read a single line at once (unless the supplied memory buffer is not large enough to store the entire line).

Here is an example program:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_MOVES 64

int main( void )
{
    char moves[MAX_MOVES][3];
    int num_moves = 0;

    FILE *fp;
    char line[4];

    //attempt to open file
    fp = fopen( "input.txt", "r" );
    if ( fp == NULL )
    {
        fprintf( stderr, "Error opening file!\n" );
        exit( EXIT_FAILURE );
    }

    //read moves from stream
    while ( fgets( line, sizeof line, fp ) != NULL )
    {
        //remove newline character, if it exists
        line[strcspn(line,"\n")] = '\0';

        //make sure that we have room for another move
        if ( num_moves == MAX_MOVES )
        {
            fprintf( stderr, "Error: Not enough room for storing more moves!\n" );
            exit( EXIT_FAILURE );
        }

        switch ( strlen( line ) )
        {
            case 0:
                //this is a skipped move
                strcpy( moves[num_moves++], "XX" );
                break;

            case 2:
                //this is not a skipped move
                if (
                    line[0] < 'A' || line[0] > 'H' ||
                    line[1] < '1' || line[1] > '8'
                )
                {
                    fprintf( stderr, "Error: Invalid move!\n" );
                    exit( EXIT_FAILURE );
                }
                strcpy( moves[num_moves++], line );
                break;

            default:
                fprintf( stderr, "Error: Move has invalid length!\n" );
                exit( EXIT_FAILURE );
        }
    }

    //print all moves
    for ( int i = 0; i < num_moves; i++ )
    {
        printf( "%s\n", moves[i] );
    }

    //cleanup
    fclose( fp );
}

For the input

F5
D6

C3

this program has the following output:

F5
D6
XX
C3
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • Thank you, @AndreasWenzel, this worked perfectly. You used some functions I had not heard of so I am going to further investigate them, but I did my (poorer) adaptation of your code and it was of great help. You couldn't imagine my face when I finally ran this program and actually reached the final state of the board! lol. – Below Average C Programmer Jan 31 '23 at 12:14
  • @Octavio: I am pleased that I was able to help. If you have any questions, feel free to ask. In case you are having trouble understanding the line `line[strcspn(line,"\n")] = '\0';`, [here](https://en.cppreference.com/w/c/string/byte/strcspn) is a link to the documentation of that function. There are also other ways to remove the newline character read by `fgets`. See this question for further information: [Removing trailing newline character from fgets() input](https://stackoverflow.com/q/2693776/12149471) – Andreas Wenzel Jan 31 '23 at 15:12