0

The program is a Tic-Tac-Toe game, and it hangs after taking the X and Y values from the user.

This is the main file:

#include <stdio.h>
#include <string.h>
#include "input.cpp"

int makeMove(char** board, char* moveX, char* moveY, char playerMove) {
    printf("Make your x move: ");
    int answerX = (int)getNum(moveX, 2);

    printf("Make your y move: ");
    int answerY = (int)getNum(moveY, 2);

    answerX--;
    answerY--;

    if ((answerX < 0) || (answerX > 2)) {
        return -1;
    }

    if ((answerY < 0) || (answerY > 2)) {
        return -1;
    }

    board[answerY][answerX] = playerMove;
    return 0;
}

int main()
{
    int turns = 0;
    const int MAX_TURNS = 9;
    char playerOneChar = 'X';
    char playerTwoChar = 'O';
    char currentChar = playerOneChar;

    while (turns <= MAX_TURNS) {
        char board[3][3];
        memset(board, ' ', 9);

        char moveX[2];
        memset(moveX, ' ', 2);

        char moveY[2];
        memset(moveY, ' ', 2);

        int result = makeMove(board, moveX, moveY, currentChar);
        
        if (result == 0) {
            if (currentChar == playerOneChar) {
                currentChar = playerTwoChar;
            }

            else {
                currentChar = playerOneChar;
            }

            turns++;
        }

        else {
            printf("Player move was out of bounds.");
        }
    }
}

This is input.cpp, and it is designed to safely get a number from the user, without allowing for buffer overruns or other problems:

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

long getNum(char* buffer, int bufferSize)
{
    long answer;
    int success = 0;

    while (!success)
    {
        // This gets the input from the user
        if (!fgets(buffer, bufferSize, stdin))
        {
            //If fgets fails, return with exit code 1.
            return 1;
        }

        char* endptr;

        errno = 0;
        answer = strtol(buffer, &endptr, 10);

        // If an error occurs, the user is told the number is too small or large.
        if (errno == ERANGE)
        {
            printf("Sorry, this number is too small or too large.\n");
            success = 0;
        }

        else if (endptr == buffer)
        {
            success = 0;
        }

        else if (*endptr && *endptr != '\n')
        {
            success = 0;
        }

        else
        {
            success = 1;
        }
    }

    return answer;
}

I attempted to input 2 numbers, but the program hanged afterwards. I attempted to research on the internet, but there was a lack of any explanation for why a function was not able to write to a 2D array properly. I am assuming that it hangs because char** is a pointer to char*, rather than being a pointer to a 2D array of chars.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 1
    The best questions have only the smallest amount of code that has the problem, rather than your full program. See if you can reduce your code to a single file and make it the smallest possible example that still has the problem. This is called a "minimum reproducible example." This makes it easier for people to read and focus on the problem area. Also, show your output. Copy and paste it as plain text into your question. – Gabriel Staples May 06 '23 at 14:58
  • 1
    @GabrielStaples When writing an nice comment like that try typing `[mre]`. – Yunnosch May 06 '23 at 15:03
  • 1
    `char moveX[2];` This string needs to hold (at least) 1 digit char, the newline that `fgets` adds and an end-of-string (`'\0'`) char. You are only allowing room for 2 of these. – 001 May 06 '23 at 15:04
  • Thanks Yunnosch. ProgrammingSolver, here's a link to a [mre] explanation. – Gabriel Staples May 06 '23 at 15:07
  • Multidimensional arrays are tricky. [Here are my notes on them](https://stackoverflow.com/a/67814330/4561887) that I have to reference each time I use them. – Gabriel Staples May 07 '23 at 06:22

2 Answers2

1

This function declaration:

int makeMove(char** board, char* moveX, char* moveY, char playerMove) {

is invalid.

You are passing to the function:

int result = makeMove(board, moveX, moveY, currentChar);

the two-dimensional array board declared like

char board[3][3];

Used as an argument expression it is implicitly converted to pointer to its first element of the type char ( * )[3]. But the function parameter has the type char **. There is no implicit conversion between objects of the pointer types char ( * )[3] and char **. So already this function call invokes undefined behavior.

You should declare the function like:

int makeMove(char board[][3], char* moveX, char* moveY, char playerMove) {

Another problem is that call of fgets:

if (!fgets(buffer, bufferSize, stdin))

As the bufferSize is equal to 2 then the new line character '\n' will not be stored in the input buffer (It will be stored only if the user at once will press the Enter key. In this case the array will look like { '\n', '\0' }). As a result a subsequent call of fgets will read an empty string that will contain only the new line character.

Pay attention to that it seems due to the extension of the file:

#include "input.cpp"

you are compiling your program as a C++ program. C and C++ are two different languages. But in any case it is a bad idea to include a module as a header. You need to compile it separately.

halfer
  • 19,824
  • 17
  • 99
  • 186
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • The file name of an include can be anything. The name provides no direction on how to compile it. It is not being compiled as C++. Yes, giving that file the suffix `.cpp` is bad idea. It should be `.h`. Or better yet `.c` and not included, instead compiled and linked, with a definition in an associated small `.h` file. – Mark Adler May 06 '23 at 16:49
  • @MarkAdler It seems he did not himself set this file extension. It is evident that it is the compiler used in an IDE assigned such a file extension. And it can do that in case if the compiler is a C++ compiler. – Vlad from Moscow May 06 '23 at 16:58
1

The first argument of makeMove() needs to be char board[][3], which is a fixed 2-D array with rows of length 3, which matches what you declared in main(). char** board is a rather different thing, which is a 1-D array of pointers, each pointing to a 1-D array of chars. Either one can be accessed with board[i][j], but how that is implemented for those two declarations is different.

Mark Adler
  • 101,978
  • 13
  • 118
  • 158