3
\$\begingroup\$

Would you be so kind as to review my TicTacToe implementation I wrote in C? I wrote this blind (not looking up other implementations). You can find it on Github here.

Any input and/or suggestions would be appreciated. Any pointers on style would also be appreciated.

I have used fflush(stdin); to clear input buffer between prompts (it was suggested in a C book I'm studying) but have also heard it was bad practice. Seems to work great though for when the user inputs more characters than he should. Clean Code guy suggests putting reused variables as global (i, j, counterX, counterY) but I've also been told globals should be avoided like the plague, so should I really?

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

typedef enum{
    Playing     = 0,
    Player1Wins = 1,
    Player2Wins = 2,
    Draw        = 3
} gameState;

const char emptyboard[8][8] = {
        {'-','-','-','-','-','-','-',' '},
        {'|',' ','|',' ','|',' ','|','3'},
        {'-','-','-','-','-','-','-',' '},
        {'|',' ','|',' ','|',' ','|','2'},
        {'-','-','-','-','-','-','-',' '},
        {'|',' ','|',' ','|',' ','|','1'},
        {'-','-','-','-','-','-','-',' '},
        {' ','a',' ','b',' ','c',' ',' '}
};

void clearScreen();
void resetBoard(char* board[8][8], int* turn);
void drawBoard(char* board[8][8]);
void addToBoard(char* board[8][8], int* player, int* turn, char* posX, char* posY);
void declareWinner(gameState currentGameState);
int getPlayerInput(int player, char* posX, char* posY); //Returns 1 if valid
int checkWin(char* (board)[8][8], int* turn);
int checkVerticalVictory(char* board[8][8]);
int checkHorizontalVictory(char* board[8][8]);
int checkDiagonalVictory(char* board[8][8]);
int checkDraw(int* turn);
int promptReplay();



int main()
{
    char board[8][8] = {
    };
    char posX, posY;
    int player = 1;     //Can be 1 or 2
    int turn = 0;       //Draw at 9
    gameState currentGameState  = Playing;
    int restart = 0;

    do{
        resetBoard(board, &turn);
        do{
            clearScreen();
            drawBoard(board);
            while(!getPlayerInput(player, &posX, &posY));       //Keep prompting until input is valid
            addToBoard(board, &player, &turn, &posX, &posY);
            currentGameState = checkWin(board, &turn);
        }while(currentGameState == Playing);

        clearScreen();
        drawBoard(board);
        declareWinner(currentGameState);

        restart = promptReplay();

    }while(restart);

    return 0;
}

void drawBoard(char* board[8][8])
{
    for(int i=0; i<8; i++)
    {
        for(int j=0; j<8; j++)
        {
            putchar(board[i][j]);
        }
        putchar('\n');
    }
}

int getPlayerInput(int player, char* posX, char* posY)
{
    char _posX;
    char _posY;
    char input[3];

    printf("\nPlayer %d, What position do you want to play? (ex. a3) ", player);

    fgets(input, 3, stdin);
    _posX = input[0];
    _posY = input[1];
    fflush(stdin);

    //check if input is valid
    switch(_posX)
    {
        case 'a':
        case 'A':
        case 'b':
        case 'B':
        case 'c':
        case 'C': *posX = _posX; break;
        default:
            printf("\nWrong horizontal input (%c), try again!\n", _posX);
            return 0;
    }
    switch(_posY)
    {
        case '1':
        case '2':
        case '3': *posY = _posY; break;
        default:
            printf("\nWrong vertical input (%c), try again!\n", _posX);
            return 0;
    }

    return 1;
}

void addToBoard(char* board[8][8], int* player, int* turn, char* posX, char* posY)
{
    int arrayRow, arrayCol;

    switch(*posX)
    {
        case 'a':
        case 'A': arrayCol = 1; break;
        case 'b':
        case 'B': arrayCol = 3; break;
        case 'c':
        case 'C': arrayCol = 5; break;
    }
    switch(*posY)
    {
        case '1': arrayRow = 5; break;
        case '2': arrayRow = 3; break;
        case '3': arrayRow = 1; break;
    }

    if(board[arrayRow][arrayCol] == ' ')
    {
        if(*player == 1)
        {
            board[arrayRow][arrayCol] = 'X';
            *player = 2;
        }
        else
        {
            board[arrayRow][arrayCol] = 'O';
            *player = 1;
        }
        (*turn)++;
    }
}

int checkWin(char* board[8][8], int* turn)
{
    /*  Returns 0, 1, 2, 3:
        0 - No victory
        1 - Player 1 Victory
        2 - Player 2 Victory
        3 - Draw
    */

    /*
    Valid positions:
        [1][1], [1][3], [1][5],
        [3][1], [3][3], [3][5],
        [5][1], [5][3], [5][5],
    */

    int winCondition = 0;

    winCondition = checkHorizontalVictory(board);
    if(winCondition) return winCondition;

    winCondition = checkVerticalVictory(board);
    if(winCondition) return winCondition;

    winCondition = checkDiagonalVictory(board);
    if(winCondition) return winCondition;

    winCondition = checkDraw(turn);
    if(winCondition) return winCondition;

    return winCondition;
}

int checkHorizontalVictory(char* board[8][8])
{
    int counterX;
    int counterO;

    for(int i=1; i<=5; i+=2)
    {
        counterX = 0;
        counterO = 0;

        for(int j=1; j<=5; j+=2)
        {
            if(board[i][j] == ' ')
            {
                break;
            }
            else if(board[i][j] == 'X')
            {
                counterX++;
            }
            else if(board[i][j] == 'O')
            {
                counterO++;
            }
        }

        if      (counterX == 3) return 1;
        else if (counterO == 3) return 2;
    }

    return 0;
}

int checkVerticalVictory(char* board[8][8])
{
    int counterX;
    int counterO;

    for(int j=1; j<=5; j+=2)
    {
        counterX = 0;
        counterO = 0;

        for(int i=1; i<=5; i+=2)
        {
            if(board[i][j] == ' ')
            {
                break;
            }
            else if(board[i][j] == 'X')
            {
                counterX++;
            }
            else if(board[i][j] == 'O')
            {
                counterO++;
            }
        }

        if      (counterX == 3) return 1;
        else if (counterO == 3) return 2;
    }

    return 0;
}

int checkDiagonalVictory(char* board[8][8])
{
    int counterX = 0;
    int counterO = 0;

    //Left to Right
    for(int i=1; i<=5; i+=2)
    {
        if(board[i][i] == ' ')
        {
            break;
        }
        else if(board[i][i] == 'X')
        {
            counterX++;
        }
        else if(board[i][i] == 'O')
        {
            counterO++;
        }
    }

    if      (counterX == 3) return 1;
    else if (counterO == 3) return 2;

    //Right to Left
    counterX = 0;
    counterO = 0;

    for(int k=0, i=1, j=5; k<3; k++, i+=2, j-=2)
    {
        if(board[i][j] == ' ')
        {
            break;
        }
        else if(board[i][j] == 'X')
        {
            counterX++;
        }
        else if(board[i][j] == 'O')
        {
            counterO++;
        }
    }

    if      (counterX == 3) return 1;
    else if (counterO == 3) return 2;

    else return 0;
}

checkDraw(int* turn)
{
    if(*turn == 9)
    {
        return 3;
    }

    return 0;
}

int promptReplay()
{
    char choice;
    puts("\n\nPlay Again? (y/n): ");
    choice = getchar();

    if(choice == 'y')
    {
        fflush(stdin);
        return 1;
    }
    else if(choice == 'n')
    {
        fflush(stdin);
        return 0;
    }
    else
    {
        fflush(stdin);
        puts("\nWrong input.\n");
        promptReplay();
    }
}

void resetBoard(char* board[8][8], int* turn)
{
    *turn = 0;

    for(int i=0; i<8; i++)
    {
        for(int j=0; j<8; j++)
        {
            board[i][j] = emptyboard[i][j];
        }
    }
}

void declareWinner(gameState currentGameState)
{
    switch (currentGameState)
    {
        case Player1Wins:
                puts("Player 1 wins!"); break;
        case Player2Wins:
                puts("Player 2 wins!"); break;
        case Draw:
                puts("Player 2 wins!"); break;
    }
}

void clearScreen()
{
    #ifdef _WIN32
    system("cls");

    #elif __linux__
    system("clear");

    #endif
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Global variables creates chaos when project gets bigger. It's like throwing your clothes middle of room, instead of putting them in wardrobe. \$\endgroup\$
    – Kao
    Commented Mar 8, 2021 at 23:16
  • 1
    \$\begingroup\$ That's a worthwhile answer @Kao, so shouldn't be a comment. You would earn reputation if you turn that into an answer, too. \$\endgroup\$ Commented Mar 9, 2021 at 16:04

2 Answers 2

3
\$\begingroup\$

How I Review Code

I have performed Code Reviews for over 30 years both on the Code Review Community and professionally. I have also had to maintain code written by others and this generally requires a thorough code inspection before starting.

The criteria I use for code reviews is:

  1. Does the code compile without errors and warnings?
  2. Does the code work as expected?
  3. Is the code easy to maintain, especially by others?
  4. Is the code portable (will it compile and run on multiple systems, Window, Linux and Unix)?
  5. Does the code follow best practices? Usually this goes hand in hand with number 3.
  6. How complex is the code? (Really also part of number 3)
  7. Is the code extendable?

General Observations

The best thing I can pick out is that the code does seem to be portable, the one case where there might be a portability issue has #ifdef statements that allow it to run on both Windows and Linux.

The code is pretty easy to read, it is not all that easy to modify or maintain. The code doesn't follow best practices and clearly ignores the C programming standard in at least one case.

I am building and running this on Window 10 using Visual Studio 2019 professional with fairly strict warning checking equivalent to -wall.

Does the code compile without errors and warnings?

No. There is one error message and multiple warning messages. The one error message has to do with this line in main().

    char board[8][8] = {};

The braces { and } require a value.

There are also multiple warning messages about the use of char* board[8][8] in the function prototypes, see below.

Does the code work as expected?

No, the code runs, but there are issues in the function getPlayerInput().

I'm not sure why you mention this since you only have one global variable

Clean Code guy suggests putting reused variables as global (i, j, counterX, counterY) but I've also been told globals should be avoided like the plague, so should I really?

I think your code is better the way it is, you only have one global variable and that is just used to start or restart the game. It would be better if you declared that variable static so that if this program ever gets broken into multiple source files then there won't be any linking errors.

BUG, Buffer Overflow

I have used fflush(stdin);

The problem with the input does not need fflush(stdin); You need to make the input array bigger. Currently the code

char input[3];
fgets(input, 3, stdin);

Has an array of 3 characters and is only getting 3 characters, however, fgets() should really be returning 4 characters, the column, the row, end of line and a termination character. What input actually contains is column, row, end of line, it is not terminated with a '\0'. This is possibly causing all kinds of unknown behavior in the program.

The input array should be at least 4 characters. When creating arrays of chars it is always best to use a size of some power of two, and generally use some multiple of the word size of the compiler.

Suggested Corrections

Fix All the Compiler Warning Messages, Compile with -wall

There are a lot of compiler warning messages, these should all be addressed before the code is posted on Code Review.

1>C2TicTacTo07012021.c(48,25): warning C4047: 'function': 'char ()[8]' differs in levels of indirection from 'char [8][8]'
1>C2TicTacTo07012021.c(48,20): warning C4024: 'resetBoard': different types for formal and actual parameter 1
1>C2TicTacTo07012021.c(51,28): warning C4047: 'function': 'char ()[8]' differs in levels of indirection from 'char [8][8]'
1>C2TicTacTo07012021.c(51,23): warning C4024: 'drawBoard': different types for formal and actual parameter 1
1>C2TicTacTo07012021.c(53,29): warning C4047: 'function': 'char ()[8]' differs in levels of indirection from 'char [8][8]'
1>C2TicTacTo07012021.c(53,24): warning C4024: 'addToBoard': different types for formal and actual parameter 1
1>C2TicTacTo07012021.c(54,46): warning C4047: 'function': 'char ()[8]' differs in levels of indirection from 'char [8][8]'
1>C2TicTacTo07012021.c(54,41): warning C4024: 'checkWin': different types for formal and actual parameter 1
1>C2TicTacTo07012021.c(58,24): warning C4047: 'function': 'char ()[8]' differs in levels of indirection from 'char [8][8]'
1>C2TicTacTo07012021.c(58,19): warning C4024: 'drawBoard': different types for formal and actual parameter 1
1>C2TicTacTo07012021.c(74,32): warning C4047: 'function': 'int' differs in levels of indirection from 'char *'
1>C2TicTacTo07012021.c(74,29): warning C4024: 'putchar': different types for formal and actual parameter 1
1>C2TicTacTo07012021.c(138,41): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(142,44): warning C4047: '=': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(147,44): warning C4047: '=': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(199,35): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(203,40): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(207,40): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(232,35): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(236,40): warning C4047: '==': 'char *' differs in levels of indirection from 'int' 1>C2TicTacTo07012021.c(240,40): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(261,31): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(265,36): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(269,36): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(284,31): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(288,36): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(292,36): warning C4047: '==': 'char *' differs in levels of indirection from 'int'
1>C2TicTacTo07012021.c(343,43): warning C4047: '=': 'char *' differs in levels of indirection from 'const char'
1>C2TicTacTo07012021.c(333): warning C4715: 'promptReplay': not all control paths return a value

Follow the C Coding Standard

The function getPlayerInput() has 2 variables that start with underscores.

    char _posX;
    char _posY;

Variables that start with underscore may be reserved.

These temporary variables aren't really needed since the input array already exists.

    switch (input[0])
    {
    case 'a':
    case 'A':
    case 'b':
    case 'B':
    case 'c':
    case 'C': *posX = input[0]; break;
    default:
        printf("\nWrong horizontal input (%c), try again!\n", _posX);
        return 0;
    }
    switch (input[1])
    {
    case '1':
    case '2':
    case '3': *posY = input[1]; break;
    default:
        printf("\nWrong vertical input (%c), try again!\n", _posX);
        return 0;
    }

Code Organization

Function prototypes are very useful in large programs that contain multiple source files, and that in case they will be in header files. In a single file program like this it is better to put the main() function at the bottom of the file and all the functions that get used in the proper order above main(). Keep in mind that every line of code written is another line of code where a bug can crawl into the code.

In the case of this program the function prototypes cause warning statements during compilation.

Separate Data From Display

It is generally better to separate the data from the display. This would make the check functions that check for a win easier to write and maintain, the for loops could increment by one rather than two. It would also make the resetBoard() function easier to write and maintain. It would make the drawboard() function a little more difficult, but that is 5 functions simplified and 1 function made more complex.

\$\endgroup\$
2
  • \$\begingroup\$ fflush(stdin) is not only unnecessary - it's UB! N1570 para 7.21.5.2.2 says "If stream points to an output stream or an update stream in which the most recent operation was not input, …; otherwise, the behavior is undefined." \$\endgroup\$ Commented Jul 2, 2021 at 5:24
  • \$\begingroup\$ Oh, I thought I had upvoted; fixed that now. \$\endgroup\$ Commented Jul 2, 2021 at 12:05
2
\$\begingroup\$

What's <conio.h>? It's not mentioned in the C standard, and if I remove it, the code kind of compiles, so I guess it's not required.

I say "kind of" because of the warnings it generates, which shouldn't be ignored:

gcc -std=c17 -fPIC -g -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds  -Wstrict-prototypes    -Wconversion    256860.c    -o 256860
256860.c:23:1: warning: function declaration isn’t a prototype [-Wstrict-prototypes]
   23 | void clearScreen();
      | ^~~~
256860.c:34:1: warning: function declaration isn’t a prototype [-Wstrict-prototypes]
   34 | int promptReplay();
      | ^~~
256860.c:38:5: warning: function declaration isn’t a prototype [-Wstrict-prototypes]
   38 | int main()
      |     ^~~~
256860.c: In function ‘main’:
256860.c:40:24: warning: ISO C forbids empty initializer braces [-Wpedantic]
   40 |     char board[8][8] = {
      |                        ^
256860.c:49:20: warning: passing argument 1 of ‘resetBoard’ from incompatible pointer type [-Wincompatible-pointer-types]
   49 |         resetBoard(board, &turn);
      |                    ^~~~~
      |                    |
      |                    char (*)[8]
256860.c:24:23: note: expected ‘char * (*)[8]’ but argument is of type ‘char (*)[8]’
   24 | void resetBoard(char* board[8][8], int* turn);
      |                 ~~~~~~^~~~~~~~~~~
256860.c:52:23: warning: passing argument 1 of ‘drawBoard’ from incompatible pointer type [-Wincompatible-pointer-types]
   52 |             drawBoard(board);
      |                       ^~~~~
      |                       |
      |                       char (*)[8]
256860.c:25:22: note: expected ‘char * (*)[8]’ but argument is of type ‘char (*)[8]’
   25 | void drawBoard(char* board[8][8]);
      |                ~~~~~~^~~~~~~~~~~
256860.c:54:24: warning: passing argument 1 of ‘addToBoard’ from incompatible pointer type [-Wincompatible-pointer-types]
   54 |             addToBoard(board, &player, &turn, &posX, &posY);
      |                        ^~~~~
      |                        |
      |                        char (*)[8]
256860.c:26:23: note: expected ‘char * (*)[8]’ but argument is of type ‘char (*)[8]’
   26 | void addToBoard(char* board[8][8], int* player, int* turn, char* posX, char* posY);
      |                 ~~~~~~^~~~~~~~~~~
256860.c:55:41: warning: passing argument 1 of ‘checkWin’ from incompatible pointer type [-Wincompatible-pointer-types]
   55 |             currentGameState = checkWin(board, &turn);
      |                                         ^~~~~
      |                                         |
      |                                         char (*)[8]
256860.c:29:21: note: expected ‘char * (*)[8]’ but argument is of type ‘char (*)[8]’
   29 | int checkWin(char* (board)[8][8], int* turn);
      |              ~~~~~~~^~~~~~~~~~~~
256860.c:59:19: warning: passing argument 1 of ‘drawBoard’ from incompatible pointer type [-Wincompatible-pointer-types]
   59 |         drawBoard(board);
      |                   ^~~~~
      |                   |
      |                   char (*)[8]
256860.c:25:22: note: expected ‘char * (*)[8]’ but argument is of type ‘char (*)[8]’
   25 | void drawBoard(char* board[8][8]);
      |                ~~~~~~^~~~~~~~~~~
256860.c: In function ‘drawBoard’:
256860.c:75:29: warning: passing argument 1 of ‘putchar’ makes integer from pointer without a cast [-Wint-conversion]
   75 |             putchar(board[i][j]);
      |                     ~~~~~~~~^~~
      |                             |
      |                             char *
In file included from 256860.c:2:
/usr/include/stdio.h:528:25: note: expected ‘int’ but argument is of type ‘char *’
  528 | extern int putchar (int __c);
      |                     ~~~~^~~
256860.c: In function ‘addToBoard’:
256860.c:140:34: warning: comparison between pointer and integer
  140 |     if(board[arrayRow][arrayCol] == ' ')
      |                                  ^~
256860.c:144:39: warning: assignment to ‘char *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
  144 |             board[arrayRow][arrayCol] = 'X';
      |                                       ^
256860.c:149:39: warning: assignment to ‘char *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
  149 |             board[arrayRow][arrayCol] = 'O';
      |                                       ^
256860.c: In function ‘checkHorizontalVictory’:
256860.c:201:28: warning: comparison between pointer and integer
  201 |             if(board[i][j] == ' ')
      |                            ^~
256860.c:205:33: warning: comparison between pointer and integer
  205 |             else if(board[i][j] == 'X')
      |                                 ^~
256860.c:209:33: warning: comparison between pointer and integer
  209 |             else if(board[i][j] == 'O')
      |                                 ^~
256860.c: In function ‘checkVerticalVictory’:
256860.c:234:28: warning: comparison between pointer and integer
  234 |             if(board[i][j] == ' ')
      |                            ^~
256860.c:238:33: warning: comparison between pointer and integer
  238 |             else if(board[i][j] == 'X')
      |                                 ^~
256860.c:242:33: warning: comparison between pointer and integer
  242 |             else if(board[i][j] == 'O')
      |                                 ^~
256860.c: In function ‘checkDiagonalVictory’:
256860.c:263:24: warning: comparison between pointer and integer
  263 |         if(board[i][i] == ' ')
      |                        ^~
256860.c:267:29: warning: comparison between pointer and integer
  267 |         else if(board[i][i] == 'X')
      |                             ^~
256860.c:271:29: warning: comparison between pointer and integer
  271 |         else if(board[i][i] == 'O')
      |                             ^~
256860.c:286:24: warning: comparison between pointer and integer
  286 |         if(board[i][j] == ' ')
      |                        ^~
256860.c:290:29: warning: comparison between pointer and integer
  290 |         else if(board[i][j] == 'X')
      |                             ^~
256860.c:294:29: warning: comparison between pointer and integer
  294 |         else if(board[i][j] == 'O')
      |                             ^~
256860.c: At top level:
256860.c:306:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
  306 | checkDraw(int* turn)
      | ^~~~~~~~~
256860.c:316:5: warning: function declaration isn’t a prototype [-Wstrict-prototypes]
  316 | int promptReplay()
      |     ^~~~~~~~~~~~
256860.c: In function ‘promptReplay’:
256860.c:320:14: warning: conversion from ‘int’ to ‘char’ may change value [-Wconversion]
  320 |     choice = getchar();
      |              ^~~~~~~
256860.c: In function ‘resetBoard’:
256860.c:348:25: warning: assignment to ‘char *’ from ‘char’ makes pointer from integer without a cast [-Wint-conversion]
  348 |             board[i][j] = emptyboard[i][j];
      |                         ^
256860.c: In function ‘declareWinner’:
256860.c:355:5: warning: enumeration value ‘Playing’ not handled in switch [-Wswitch]
  355 |     switch (currentGameState)
      |     ^~~~~~
256860.c: At top level:
256860.c:366:6: warning: function declaration isn’t a prototype [-Wstrict-prototypes]
  366 | void clearScreen()
      |      ^~~~~~~~~~~
256860.c: In function ‘promptReplay’:
256860.c:338:1: warning: control reaches end of non-void function [-Wreturn-type]
  338 | }
      | ^

There are some serious problems there, in particular where we pass a matrix of char to functions expecting a matrix of char*.

I suggest creating a typedef for the frequently-used matrix:

#define BOARD_WIDTH 8
#define BOARD_HEIGHT 8
typedef char game_board[BOARD_HEIGHT][BOARD_WIDTH];

That makes our declarations much easier to keep consistent.

const game_board emptyboard = {
        {'-','-','-','-','-','-','-',' '},
        {'|',' ','|',' ','|',' ','|','3'},
        {'-','-','-','-','-','-','-',' '},
        {'|',' ','|',' ','|',' ','|','2'},
        {'-','-','-','-','-','-','-',' '},
        {'|',' ','|',' ','|',' ','|','1'},
        {'-','-','-','-','-','-','-',' '},
        {' ','a',' ','b',' ','c',' ',' '}
};

void clearScreen(void);
void resetBoard(game_board board, int* turn);
void drawBoard(game_board board);
void addToBoard(game_board board, int* player, int* turn, char* posX, char* posY);
void declareWinner(gameState currentGameState);
int getPlayerInput(int player, char* posX, char* posY); //Returns 1 if valid
int checkWin(game_board board, int* turn);
int checkVerticalVictory(game_board board);
int checkHorizontalVictory(game_board board);
int checkDiagonalVictory(game_board board);
int checkDraw(int* turn);
int promptReplay(void);

Normally, I'd make the board 3✕3, since the other entries in the matrix are for presentation rather than actual state.


char choice;
puts("\n\nPlay Again? (y/n): ");
choice = getchar();

Here, we not only fail to check for EOF, but we can no longer do so, as we converted the input to char. Use int, or turn to scanf():

for (;;) {
    puts("Play Again? (y/n): ");
    char choice;
    if (scanf(" %c%*[^\n]", &choice) < 1) {
        /* EOF */
        return 0;
    }
    switch (choice) {
    case 'y': case 'Y':
        return 1;
    case 'n': case 'N':
        return 0;
    }
    puts("\nWrong input.\n");
    /* loop; ask again */
}

#ifdef _WIN32
system("cls");

#elif __linux__
system("clear");

#endif

Use system() with care. system("/usr/bin/clear") would be better, since you can't rely on users having a sane PATH. And on other platforms, is this function really supposed to do nothing? Perhaps:

#else
#error Please add support for clearing screen
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.