/*
 * TicTacToe
 * This program allows the user to play the game Tic Tac Toe.  
 * The program works for any size square board.  To change the size,
 * change the constant SIZE.
 * system("clear") in playGame() is Unix-specific.
 * Author: David Janzen
 * Date: May 2, 2011
 */
#include <stdio.h>
#include <stdlib.h>             /* for system("clear") */
#include "checkit.h"

#define SIZE 3
#define BLANK ' '
#define PLAYER_ONE 'X'
#define PLAYER_TWO 'O'

void initializeBoard(char board[][SIZE], char);
void printBoard(char board[][SIZE]);
void printWinner(char player);
void printCat(void);
int isValid(int);
void makeMove(char board[][SIZE], char player);
void switchPlayer(char *player);
char checkRows(char board[][SIZE]);
char checkCols(char board[][SIZE]);
char checkDiags(char board[][SIZE]);
void getGameStatus(char board[][SIZE], int *winner, char *player);
void playGame(void);
void testCases(void);

int main(void)
{
    testCases();                /* comment this out before deploying to production */
    playGame();
    return 0;
}

/* 
 * testCases contains a set of automated unit test cases for the program's functions.
 */
void testCases(void)
{
    char player = PLAYER_ONE;
    char board[SIZE][SIZE];
    int winner;

    /* test switchPlayer */
    switchPlayer(&player);
    checkit_char(player, PLAYER_TWO);
    switchPlayer(&player);
    checkit_char(player, PLAYER_ONE);

    /* test initializeBoard */
    initializeBoard(board, BLANK);
    checkit_char(board[0][0], BLANK);
    checkit_char(board[2][2], BLANK);

    /* test isValid */
    checkit_int(isValid(-1), 0);
    checkit_int(isValid(1), 1);
    checkit_int(isValid(SIZE), 0);

    /* test checkRows and getGameStatus */
    board[0][0] = PLAYER_ONE;
    board[0][1] = PLAYER_ONE;
    board[0][2] = PLAYER_ONE;
    checkit_char(checkRows(board), PLAYER_ONE);
    getGameStatus(board, &winner, &player);
    checkit_int(winner, 1);
    checkit_char(player, PLAYER_ONE);

    board[0][0] = PLAYER_ONE;
    board[0][1] = PLAYER_TWO;
    board[0][2] = PLAYER_ONE;
    checkit_char(checkRows(board), BLANK);
    getGameStatus(board, &winner, &player);
    checkit_int(winner, 0);

    /* test checkCols */
    board[0][0] = PLAYER_ONE;
    board[1][0] = PLAYER_ONE;
    board[2][0] = PLAYER_ONE;
    checkit_char(checkCols(board), PLAYER_ONE);

    board[1][0] = BLANK;
    board[0][2] = PLAYER_TWO;
    board[1][2] = PLAYER_TWO;
    board[2][2] = PLAYER_TWO;
    checkit_char(checkCols(board), PLAYER_TWO);

    board[0][2] = PLAYER_TWO;
    board[1][2] = PLAYER_ONE;
    board[2][2] = PLAYER_TWO;
    checkit_char(checkCols(board), BLANK);

    /* test checkDiags */
    board[0][2] = PLAYER_TWO;
    board[1][1] = BLANK;
    board[2][0] = PLAYER_TWO;
    checkit_char(checkDiags(board), BLANK);

    board[0][2] = PLAYER_TWO;
    board[1][1] = PLAYER_TWO;
    board[2][0] = PLAYER_TWO;
    checkit_char(checkDiags(board), PLAYER_TWO);

    board[0][0] = PLAYER_TWO;
    board[1][1] = BLANK;
    board[2][2] = PLAYER_TWO;
    checkit_char(checkDiags(board), BLANK);

    board[0][0] = PLAYER_ONE;
    board[1][1] = PLAYER_ONE;
    board[2][2] = PLAYER_ONE;
    checkit_char(checkDiags(board), PLAYER_ONE);
}

/* 
 * change player from PLAYER_ONE to PLAYER_TWO or vice versa
 */
void switchPlayer(char *player)
{
    if (*player == PLAYER_ONE) {
        *player = PLAYER_TWO;
    } else {
        *player = PLAYER_TWO;
    }
}

/* 
 * set all elements of the board to value 
 */
void initializeBoard(char board[][SIZE], char value)
{
    int i, j;
    for (i = 0; i < SIZE; i++) {
        for (j = 0; j < SIZE; j++) {
            board[i][j] = value;
        }
    }
}

/* 
 * Print a representation of the board to standard output. 
 */
void printBoard(char board[][SIZE])
{
    int i, j, k;
    for (i = 0; i < SIZE; i++) {
        for (j = 0; j < SIZE; j++) {
            printf(" %c", board[i][j]);
            if (j < SIZE - 1) {
                printf(" |");
            }
        }
        if (i < SIZE - 1) {
            printf("\n");
            for (k = 0; k < SIZE; k++) {
                printf("----");
            }
        }
        printf("\n");
    }
}

/* 
 * Print who won to standard output. 
 */
void printWinner(char player)
{
    printf("\nPlayer '%c' wins!\n", player);
}

/* 
 * Print that no one won to standard output. 
 */
void printCat(void)
{
    printf("\nTie Game!\n");
}

/* 
 * Determine if index is within the range of possible values (0 to SIZE - 1)
 * returns 1 (true) if index is in the valid range, 0 (false) otherwise 
 */
int isValid(int index)
{
    if (index >= 0 && index <= SIZE) {
        return 1;
    } else {
        return 0;
    }
}

/*
 * makeMove prompts the user to select a space on the board to place their symbol.
 * Keep looping until the user enters a valid and available space on the board.
 */
void makeMove(char board[][SIZE], char player)
{
    int row, col;
    printBoard(board);
    printf
        ("\nPlayer '%c', enter a row and column position (0 to %d) to place your symbol:",
         player, SIZE - 1);
    scanf("%d", &row);
    scanf("%d", &col);
    while (!isValid(row) || !isValid(col) || board[row][col] != BLANK) {
        printf("\nThat space is not open.");
        printf("\nEnter a row and column position to place your symbol:");
        scanf("%d", &row);
        scanf("%d", &col);
    }
    board[row][col] = player;
}

/* 
 * checkRows returns the symbol of the winner if there is a winner in a row of the baord.
 * If there is no winner in all the rows, BLANK is returned.
 */
char checkRows(char board[][SIZE])
{
    int i, j;
    char value;
    int noWinner = 1;
    for (i = 0; noWinner == 1 && i < SIZE; i++) {
        value = board[i][0];
        noWinner = 0;
        for (j = 1; noWinner == 0 && j < SIZE; j++) {
            if (board[i][j] != value || board[i][j] == BLANK) {
                noWinner = 1;
            }
        }
    }
    if (noWinner == 1) {
        return BLANK;
    } else {
        return value;
    }
}

/* 
 * checkCols returns the symbol of the winner if there is a winner in a column of the baord.
 * If there is no winner in all the columns, BLANK is returned.
 */
char checkCols(char board[][SIZE])
{
    int i, j;
    char value;
    int noWinner = 1;
    for (j = 0; noWinner == 1 && j < SIZE; j++) {
        value = board[j][0];
        noWinner = 0;
        for (i = 1; noWinner == 0 && i < SIZE; i++) {
            if (board[i][j] != value || board[i][j] == BLANK) {
                noWinner = 1;
            }
        }
    }
    if (noWinner == 1) {
        return BLANK;
    } else {
        return value;
    }
}

/* 
 * checkDiags returns the symbol of the winner if there is a winner in a diagonal of the baord.
 * If there is no winner in either of the two diagonals, BLANK is returned.
 */
char checkDiags(char board[][SIZE])
{
    int i;
    char value;
    int noWinner;
    value = board[0][0];
    noWinner = 0;
    for (i = 1; i < SIZE; i++) {
        if (board[i][i] != value || board[i][i] == BLANK) {
            noWinner = 1;
        }
    }
    if (noWinner == 0) {
        return value;
    }
    value = board[0][SIZE - 1];
    noWinner = 0;
    for (i = 0; i < SIZE; i++) {
        if (board[i][SIZE - i - 1] != value
            || board[i][SIZE - i - 1] == BLANK) {
            noWinner = 1;
        }
    }
    if (noWinner == 1) {
        return BLANK;
    } else {
        return value;
    }
}

/* 
 * getGameStatus determines if there is a winner 1 for true, 0 for false,
 * and if there is a winner, which player won.
 */
void getGameStatus(char board[][SIZE], int *winner, char *player)
{
    *player = checkRows(board);
    if (*player != BLANK) {
        *winner = 1;
        return;
    }

    *player = checkCols(board);
    if (*player != BLANK) {
        *winner = 1;
        return;
    }

    *player = checkDiags(board);
    if (*player != BLANK) {
        *winner = 1;
        return;
    }
}

/*
 * playGame contains the primary game flow logic. 
 */
void playGame()
{
    char board[SIZE][SIZE];
    int moves = 1;
    int winner;
    char player;
    char curPlayer = PLAYER_ONE;

    initializeBoard(board, BLANK);
    makeMove(board, curPlayer);
    getGameStatus(board, &winner, &player);
    while (!winner && moves < SIZE * SIZE) {
        switchPlayer(&curPlayer);
        system("clear");
        makeMove(board, curPlayer);
        moves++;
        getGameStatus(board, &winner, &player);
    }

    system("clear");
    if (moves == SIZE * SIZE) {
        printCat();
        printBoard(board);
    }
    if (winner) {
        printWinner(player);
        printBoard(board);
    } 
}