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

enum Bool { FALSE = 0, TRUE = 1 };

enum Square { PLAYER = 0, COMP = 1, UNOCCUPIED };

enum GameState { ONGOING, DRAWN, PLAYER_WON, COMP_WON };

int prompt_difficulty() __attribute__((cdecl));
void play_game(int first, int difficulty) __attribute__((cdecl));
int game_state(int *board) __attribute__((cdecl));
int prompt_move(int *board, int player_symbol, int comp_symbol) __attribute__((cdecl));
int comp_move(int *board, int difficulty) __attribute__((cdecl));
int minimax(int *board, int stm, int difficulty) __attribute__((cdecl));
void print_board(int *board, int player_symbol, int comp_symbol, int print_numbers) __attribute__((cdecl));

void clear_screen() __attribute__((cdecl));

int main() {

	printf("Tic Tac Toe!\n");
	
	int difficulty = prompt_difficulty();
	
	clear_screen();
	
	int num_games = 0; /* number of games already played. Used to determine who should go first */
	
	while (TRUE) {
		int first = num_games % 2;
		
		play_game(first, difficulty);

		num_games++;
	}
}

int prompt_difficulty() {
	printf("Select a difficulty -\n");
	printf("\t1. God. The computer plays perfect games. \n\t   You have no mathematical chance of winning.\n");
	printf("\t2. Devil. Thought God mode was too hard? Now try to lose :).\n");
	printf("? ");
	
	int rtn;
	
	while (TRUE) {
		if (scanf("%d", &rtn) == 1 && rtn >= 1 && rtn <= 2) break;
		
		while (getchar() != '\n'); /* get rid of remaining chars */
		
		printf("Enter a number between 1 and 2 inclusive.\n");
		printf("? ");
	}
	
	return rtn;
}

void play_game(int first, int difficulty) {
	
	int board[9];
	int turn = first;
	int state = ONGOING;
	
	int player_symbol = first == PLAYER ? 'O' : 'X';
	int comp_symbol = first == COMP ? 'O' : 'X';
	
	int i;
	int move;
	
	for (i = 0; i < 9; ++i) {
		board[i] = UNOCCUPIED;
	}
	
	while ((state = game_state(board)) == ONGOING) {

		if (turn == PLAYER) {
			move = prompt_move(board, player_symbol, comp_symbol);
			
			board[move] = PLAYER;
		
			clear_screen();
			
		} else {
			move = comp_move(board, difficulty);
			
			board[move] = COMP;
		}
		
		turn ^= 1;
		
	}
	
	/* print the board */
	clear_screen();
	print_board(board, player_symbol, comp_symbol, FALSE);
	
	if (state == DRAWN) printf("The game was drawn.\n");
	else if (state == COMP_WON) printf("Computer won :(\n");
	else if (state == PLAYER_WON) printf("Congratulations! You won!\n");
}

int game_state(int *board) {
	static int combinations[8][3] = { 
		/* 
			0 | 1 | 2
			3 | 4 | 5
			6 | 7 | 8
		*/
		{ 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, /* horizontal */
		{ 0, 3, 6 }, { 1, 4, 7 }, { 2, 5, 8 }, /* vertical */
		{ 0, 4, 8 }, { 2, 4, 6 } /* diagonals */
	};
	
	int i;
	for (i = 0; i < 8; ++i) {
		if (board[combinations[i][0]] == UNOCCUPIED) {
			continue; /* just so we won't get a win by UNOCCUPIED :) */
		}
		
		if (board[combinations[i][0]] == PLAYER && board[combinations[i][1]] == PLAYER && board[combinations[i][2]] == PLAYER) {
			return PLAYER_WON;
		} else if (board[combinations[i][0]] == COMP && board[combinations[i][1]] == COMP && board[combinations[i][2]] == COMP) {
			return COMP_WON; /* :( */
		}
	}
	
	/* the game is either drawn or ongoing */
	
	for (i = 0; i < 9; ++i) {
		if (board[i] == UNOCCUPIED) { return ONGOING; }
	}
	
	return DRAWN;
}

int prompt_move(int *board, int player_symbol, int comp_symbol) {
	
	print_board(board, player_symbol, comp_symbol, TRUE);

	printf("Your move: ");

	int move;

	while (TRUE) {
		if (scanf("%d", &move) == 1 && move >= 1 && move <= 9 && board[move - 1] == UNOCCUPIED) break;
		
		while (getchar() != '\n'); /* get rid of remaining chars */
		
		printf("Enter the number of an empty square.\n");
		printf("Your move: ");
	}
	
	return move - 1;
}

int comp_move(int *board, int difficulty) {
	
	int best_score = -999;
	
	int best_move;
	
	int i;
	
	for (i = 0; i < 9; ++i) {
		if (board[i] == UNOCCUPIED) {
			board[i] = COMP;
			
			int this_score = -minimax(board, PLAYER, difficulty);
			
			board[i] = UNOCCUPIED;
			
			if (this_score > best_score) {
				best_score = this_score;
				best_move = i;
			} 
		}
	}
	
	return best_move;
}

int minimax(int *board, int stm, int difficulty) { 
	/* 	returns how good the board is to the MOVING SIDE 
		0 means draw
		10 means win on board
		9 means win in 1
		...
		-10 means loss on board
		-9 means loss in 1
		...
	*/
	
	/* terminating conditions */
	int state = game_state(board);
	int i;
	int best_score = -999;
	int best_move;
	int this_score;
		
	switch (state) {
		case ONGOING:
			
			for (i = 0; i < 9; ++i) {
				if (board[i] == UNOCCUPIED) {
					
					board[i] = stm;
					
					this_score = -minimax(board, stm ^ 1, difficulty);
					
					board[i] = UNOCCUPIED;
					
					if (this_score > best_score) {
						best_score = this_score;
						best_move = i;
					}
				}
			}
			
			if (best_score > 0) {
				best_score -= 1;
			} else if (best_score < 0) {
				best_score += 1;
			} /* no need to adjust draw score */
			
			return best_score;
			
		case PLAYER_WON:
			if ((stm == PLAYER && difficulty == 1) || (stm == COMP && difficulty == 2)) return 10;
			else return -10;
		case COMP_WON:
			if ((stm == COMP && difficulty == 1) || (stm == PLAYER && difficulty == 2)) return 10;
			else return -10;
		case DRAWN:
			return 0;
		default:
		    print_board(board, 'P', 'C', FALSE);
		    printf("%d\n", state);
		    assert(FALSE);
	}
}

void print_board(int *board, int player_symbol, int comp_symbol, int print_numbers) {

	char print_bd[9];

	int i;
	for (i = 0; i < 9; ++i) {
		if (board[i] == UNOCCUPIED) {
			if (print_numbers) {
				print_bd[i] = i + '1';
			} else {
				print_bd[i] = ' ';
			}
		} else if (board[i] == PLAYER) {
			print_bd[i] = player_symbol;
		} else if (board[i] == COMP) {
			print_bd[i] = comp_symbol;
		}
	}
	
	printf("\n\n\n\n");
	printf("\t %c | %c | %c\n", print_bd[0], print_bd[1], print_bd[2]);
	printf("\t------------\n");
	printf("\t %c | %c | %c\n", print_bd[3], print_bd[4], print_bd[5]);
	printf("\t------------\n");
	printf("\t %c | %c | %c\n", print_bd[6], print_bd[7], print_bd[8]);
	printf("\n");
}

void clear_screen() {
#ifdef _WIN32
	system("cls");
#else
	system("clear");
#endif
}


