1
\$\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
  • \$\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 Mar 8 at 23:16
  • \$\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\$ – Toby Speight Mar 9 at 16:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.