Created
December 2, 2024 16:56
-
-
Save ernestohs/d5b0e1116ca99ab3b60597909a3c5d5b to your computer and use it in GitHub Desktop.
Ultimate Tic-Tac-Toe Bundle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdbool.h> | |
#include <termios.h> | |
#include <unistd.h> | |
#define BOARD_SIZE 3 | |
#define EMPTY ' ' | |
#define PLAYER_X 'X' | |
#define PLAYER_O 'O' | |
#define KEY_ESC 27 | |
#define KEY_W 119 | |
#define KEY_S 115 | |
#define KEY_A 97 | |
#define KEY_D 100 | |
#define KEY_ENTER 10 | |
typedef struct { | |
char small_boards[BOARD_SIZE][BOARD_SIZE][BOARD_SIZE][BOARD_SIZE]; | |
char big_board[BOARD_SIZE][BOARD_SIZE]; | |
int current_player; | |
int active_board_row; | |
int active_board_col; | |
int cursor_row; | |
int cursor_col; | |
bool first_move; | |
} GameState; | |
static struct termios orig_termios; | |
void enableRawMode() { | |
tcgetattr(STDIN_FILENO, &orig_termios); | |
struct termios raw = orig_termios; | |
raw.c_lflag &= ~(ECHO | ICANON); | |
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); | |
} | |
void disableRawMode() { | |
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); | |
} | |
char getChar() { | |
char c; | |
read(STDIN_FILENO, &c, 1); | |
return c; | |
} | |
void initializeGame(GameState* game) { | |
for (int i = 0; i < BOARD_SIZE; i++) { | |
for (int j = 0; j < BOARD_SIZE; j++) { | |
for (int k = 0; k < BOARD_SIZE; k++) { | |
for (int l = 0; l < BOARD_SIZE; l++) { | |
game->small_boards[i][j][k][l] = EMPTY; | |
} | |
} | |
game->big_board[i][j] = EMPTY; | |
} | |
} | |
game->current_player = 0; | |
game->active_board_row = 1; | |
game->active_board_col = 1; | |
game->cursor_row = 1; | |
game->cursor_col = 1; | |
game->first_move = true; | |
} | |
bool checkWin(const char board[BOARD_SIZE][BOARD_SIZE], char player) { | |
// Check rows | |
for (int i = 0; i < BOARD_SIZE; i++) { | |
if (board[i][0] == player && board[i][1] == player && board[i][2] == player) { | |
return true; | |
} | |
} | |
// Check columns | |
for (int i = 0; i < BOARD_SIZE; i++) { | |
if (board[0][i] == player && board[1][i] == player && board[2][i] == player) { | |
return true; | |
} | |
} | |
// Check diagonals | |
if (board[0][0] == player && board[1][1] == player && board[2][2] == player) { | |
return true; | |
} | |
if (board[0][2] == player && board[1][1] == player && board[2][0] == player) { | |
return true; | |
} | |
return false; | |
} | |
bool isBoardFull(const char board[BOARD_SIZE][BOARD_SIZE]) { | |
for (int i = 0; i < BOARD_SIZE; i++) { | |
for (int j = 0; j < BOARD_SIZE; j++) { | |
if (board[i][j] == EMPTY) { | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
char getCurrentPlayer(const GameState* game) { | |
return (game->current_player == 0) ? PLAYER_X : PLAYER_O; | |
} | |
void drawBoard(const GameState* game) { | |
printf("\033[2J\033[H"); | |
printf(" "); | |
for (int i = 0; i < BOARD_SIZE * 3; i++) { | |
printf("%d ", i); | |
} | |
printf("\n"); | |
for (int big_row = 0; big_row < BOARD_SIZE; big_row++) { | |
for (int small_row = 0; small_row < BOARD_SIZE; small_row++) { | |
printf("%2d ", big_row * 3 + small_row); | |
for (int big_col = 0; big_col < BOARD_SIZE; big_col++) { | |
for (int small_col = 0; small_col < BOARD_SIZE; small_col++) { | |
char symbol = game->small_boards[big_row][big_col][small_row][small_col]; | |
if (big_row == game->active_board_row && big_col == game->active_board_col && | |
small_row == game->cursor_row && small_col == game->cursor_col) { | |
printf("\033[7m%c\033[0m", symbol); | |
} else if (big_row == game->active_board_row && big_col == game->active_board_col) { | |
printf("\033[1m%c\033[0m", symbol); | |
} else { | |
printf("%c", symbol); | |
} | |
printf(" "); | |
} | |
if (big_col < BOARD_SIZE - 1) printf("│"); | |
} | |
printf("\n"); | |
} | |
if (big_row < BOARD_SIZE - 1) { | |
printf(" "); | |
for (int i = 0; i < BOARD_SIZE * 11 - 1; i++) printf("─"); | |
printf("\n"); | |
} | |
} | |
printf("\nCurrent Player: %c\n", getCurrentPlayer(game)); | |
if (!game->first_move) { | |
printf("Active Board: (%d,%d)\n", game->active_board_row, game->active_board_col); | |
} else { | |
printf("First move - play anywhere!\n"); | |
} | |
printf("\nControls: WASD to move, Enter to place, ESC to quit\n"); | |
} | |
bool isValidMove(const GameState* game) { | |
if (!game->first_move && | |
game->active_board_row != -1 && | |
(game->active_board_row != game->active_board_row || | |
game->active_board_col != game->active_board_col)) { | |
return false; | |
} | |
if (game->big_board[game->active_board_row][game->active_board_col] != EMPTY) { | |
return false; | |
} | |
return game->small_boards[game->active_board_row][game->active_board_col] | |
[game->cursor_row][game->cursor_col] == EMPTY; | |
} | |
void updateActiveBoard(GameState* game) { | |
int next_row = game->cursor_row; | |
int next_col = game->cursor_col; | |
if (game->big_board[next_row][next_col] != EMPTY) { | |
game->active_board_row = -1; | |
game->active_board_col = -1; | |
} else { | |
game->active_board_row = next_row; | |
game->active_board_col = next_col; | |
} | |
} | |
bool isGameOver(const GameState* game) { | |
if (checkWin(game->big_board, PLAYER_X) || checkWin(game->big_board, PLAYER_O)) { | |
return true; | |
} | |
for (int i = 0; i < BOARD_SIZE; i++) { | |
for (int j = 0; j < BOARD_SIZE; j++) { | |
if (game->big_board[i][j] == EMPTY) { | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
bool makeMove(GameState* game) { | |
char symbol = getCurrentPlayer(game); | |
game->small_boards[game->active_board_row][game->active_board_col] | |
[game->cursor_row][game->cursor_col] = symbol; | |
if (checkWin(game->small_boards[game->active_board_row][game->active_board_col], symbol)) { | |
game->big_board[game->active_board_row][game->active_board_col] = symbol; | |
} else if (isBoardFull(game->small_boards[game->active_board_row][game->active_board_col])) { | |
game->big_board[game->active_board_row][game->active_board_col] = 'T'; | |
} | |
updateActiveBoard(game); | |
game->current_player = 1 - game->current_player; | |
game->first_move = false; | |
if (isGameOver(game)) { | |
drawBoard(game); | |
if (checkWin(game->big_board, PLAYER_X)) { | |
printf("\nPlayer X wins!\n"); | |
} else if (checkWin(game->big_board, PLAYER_O)) { | |
printf("\nPlayer O wins!\n"); | |
} else { | |
printf("\nGame is a tie!\n"); | |
} | |
return false; | |
} | |
return true; | |
} | |
bool handleQuit() { | |
printf("\nDo you want to quit? (y/n): "); | |
char response = getChar(); | |
return (response == 'y' || response == 'Y'); | |
} | |
bool handleInput(GameState* game) { | |
char c = getChar(); | |
if (c == KEY_ESC) { | |
return handleQuit(); | |
} | |
switch (c) { | |
case KEY_W: | |
if (game->cursor_row > 0) game->cursor_row--; | |
break; | |
case KEY_S: | |
if (game->cursor_row < BOARD_SIZE - 1) game->cursor_row++; | |
break; | |
case KEY_A: | |
if (game->cursor_col > 0) game->cursor_col--; | |
break; | |
case KEY_D: | |
if (game->cursor_col < BOARD_SIZE - 1) game->cursor_col++; | |
break; | |
case KEY_ENTER: | |
if (isValidMove(game)) { | |
return makeMove(game); | |
} | |
break; | |
} | |
return true; | |
} | |
int main() { | |
GameState game; | |
initializeGame(&game); | |
enableRawMode(); | |
bool running = true; | |
while (running) { | |
drawBoard(&game); | |
running = handleInput(&game); | |
} | |
disableRawMode(); | |
printf("\nGame Over!\n"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment