Home > Mobile >  Reading empty-lines from a .txt in C
Reading empty-lines from a .txt in C

Time:01-31

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!

CodePudding user response:

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
  • Related