/* See LICENSE file for copyright and license details. */ #include "common.h" USAGE(""); static volatile sig_atomic_t caught_sigterm = 0; static volatile sig_atomic_t caught_sigwinch = 1; static struct termios saved_stty; static int cpu_begins = 0; static int rand_on_first = 0; static int level = 5; static void sigterm(int signo) { caught_sigterm = 1; (void) signo; } static void sigwinch(int signo) { caught_sigwinch = 1; (void) signo; } static void restore_terminal(void) { printf("\033[?25h\n\033[?1049;1003;1006l"); fflush(stdout); tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty); } static void get_win_combination(char board[3][3], char marks[3][3]) { int i; memset(marks, 0, sizeof(char[3][3])); for (i = 0; i < 3; i++) { if (board[i][0] != ' ' && board[i][0] == board[i][1] && board[i][1] == board[i][2]) marks[i][0] = marks[i][1] = marks[i][2] = 1; if (board[0][i] != ' ' && board[0][i] == board[1][i] && board[1][i] == board[2][i]) marks[0][i] = marks[1][i] = marks[2][i] = 1; } if (board[1][1] != ' ' && board[0][0] == board[1][1] && board[1][1] == board[2][2]) marks[0][0] = marks[1][1] = marks[2][2] = 1; if (board[1][1] != ' ' && board[0][2] == board[1][1] && board[1][1] == board[2][0]) marks[0][2] = marks[1][1] = marks[2][0] = 1; } static void get_winner(char board[3][3], char *winnerp) { int i; for (i = 0; i < 3; i++) { if (board[i][0] != ' ' && board[i][0] == board[i][1] && board[i][1] == board[i][2]) { *winnerp = board[i][0]; return; } if (board[0][i] != ' ' && board[0][i] == board[1][i] && board[1][i] == board[2][i]) { *winnerp = board[0][i]; return; } } if (board[1][1] != ' ' && board[0][0] == board[1][1] && board[1][1] == board[2][2]) { *winnerp = board[1][1]; return; } if (board[1][1] != ' ' && board[0][2] == board[1][1] && board[1][1] == board[2][0]) { *winnerp = board[1][1]; return; } *winnerp = ' '; } static int16_t cpu_think(char board[3][3], char player, char cpu_player, int empty, int depth, int maximize) { int16_t score, choices = 0, best = maximize ? -111 : 111; char winner; int x, y; if (empty == 0) return 0; if (!depth) { for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) if (board[y][x] == ' ') choices |= (int16_t)(1 << (y * 3 + x)); return (int16_t)(empty | choices); } for (y = 0; y < 3; y++) { for (x = 0; x < 3; x++) { if (board[y][x] != ' ') continue; board[y][x] = player; get_winner(board, &winner); if (winner == cpu_player) { score = (int16_t)(20 - empty); } else if (winner != ' ') { score = (int16_t)(empty - 20); } else { score = cpu_think(board, player ^ 'X' ^ 'O', cpu_player, empty - 1, depth - 1, maximize ^ 1); score >>= 9; } if (maximize ? (score >= best) : (score <= best)) { if (score != best) { best = score; choices = 0; } choices |= (int16_t)(1 << (y * 3 + x)); } board[y][x] = ' '; } } return (int16_t)((best << 9) | choices); } static void cpu_play(char board[3][3], char player, int empty, int rand_move) { int16_t packed_choices = cpu_think(board, player, player, empty, rand_move ? 0 : level, 1); int16_t bit; char choices[9], i; int nchoices = 0, y, x; long int r, mask; for (bit = 1, i = 0; i < 9; i++, bit <<= 1) if (packed_choices & bit) choices[nchoices++] = i; if (nchoices == 0) abort(); if (nchoices == 1) r = 0; else { if (nchoices <= 2) mask = 1; else if (nchoices <= 4) mask = 3; else if (nchoices <= 8) mask = 7; else mask = 15; do { r = random() & mask; } while (r >= nchoices); } i = choices[r]; y = i / 3; x = i % 3; board[y][x] = player; } int main(int argc, char *argv[]) { union libterminput_input input; struct libterminput_state termctx; struct termios stty; struct sigaction sigact; struct winsize winsize = {.ws_col = 80, .ws_row = 24}; int r, dualplayer = 0, redraw = 1, left = (winsize.ws_col - ((int)sizeof("> Single player") - 1)) / 2; char board[3][3], marks[3][3], winner, player; const char *highlight; int x, y, empty, rand_move = 0, confline = 0; ARGBEGIN { default: usage(); break; } ARGEND; if (argc) usage(); memset(&termctx, 0, sizeof(termctx)); memset(&sigact, 0, sizeof(sigact)); sigact.sa_handler = sigterm; sigaction(SIGTERM, &sigact, NULL); sigaction(SIGQUIT, &sigact, NULL); sigaction(SIGINT, &sigact, NULL); sigact.sa_handler = sigwinch; sigaction(SIGWINCH, &sigact, NULL); if (tcgetattr(STDIN_FILENO, &stty)) eprintf("tcgetattr :"); saved_stty = stty; stty.c_lflag &= (tcflag_t)~(ECHO | ICANON); if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty)) eprintf("tcsetattr TCSAFLUSH:"); printf("\033[?1049;1003;1006h\033[?25l"); libsimple_eprintf_preprint = restore_terminal; restart: for (;;) { if (caught_sigterm) goto out; if (caught_sigwinch) { if (ioctl(STDOUT_FILENO, (unsigned long)TIOCGWINSZ, &winsize) < 0) { if (errno == EINTR) continue; eprintf("ioctl TIOCGWINSZ:"); } caught_sigwinch = 0; left = (winsize.ws_col - ((int)sizeof("> Single player") - 1)) / 2; redraw = 1; } if (redraw) { printf("\033[H\033[m"); for (r = (winsize.ws_row - 2) / 2; r > 0; r--) printf("\033[K\n"); printf("%*.s%s %s\033[m\033[K\n", left, "", dualplayer == 0 ? "\033[1;31m>" : " ", "Single player"); printf("%*.s%s %s\033[m\033[J", left, "", dualplayer == 1 ? "\033[1;31m>" : " ", "Dual player"); fflush(stdout); redraw = 0; } r = libterminput_read(STDIN_FILENO, &input, &termctx); if (r <= 0) { if (r < 0) { if (errno == EINTR) continue; libsimple_eprintf("libterminput_read :"); } break; } if (input.type == LIBTERMINPUT_KEYPRESS) { if (input.keypress.key == LIBTERMINPUT_UP && dualplayer > 0) { dualplayer -= 1; redraw = 1; } else if (input.keypress.key == LIBTERMINPUT_DOWN && dualplayer < 1) { dualplayer += 1; redraw = 1; } else if (input.keypress.key == LIBTERMINPUT_ENTER) { break; } else if (input.keypress.key == LIBTERMINPUT_SYMBOL) { if (!strcmp(input.keypress.symbol, " ")) { break; } else if (!strcmp(input.keypress.symbol, "q") || !strcmp(input.keypress.symbol, "Q")) { goto out; } else if (!strcmp(input.keypress.symbol, "L") && (input.keypress.mods & LIBTERMINPUT_CTRL)) { redraw = 1; } } } else if (input.type == LIBTERMINPUT_MOUSEEVENT && input.mouseevent.event == LIBTERMINPUT_PRESS && input.mouseevent.button == LIBTERMINPUT_BUTTON1 && input.mouseevent.x > (left < 0 ? 0 : (size_t)left) && input.mouseevent.x - (left < 0 ? 0 : (size_t)left) <= (int)sizeof("> Single player") - 1) { input.mouseevent.y -= (size_t)((winsize.ws_row < 2 ? 2 : winsize.ws_row) - 2) / 2; switch (input.mouseevent.y) { case 1: dualplayer = 0; goto new_game; case 2: dualplayer = 1; goto new_game; default: break; } } } new_game: redraw = 1; x = y = 1; player = 'X'; empty = 9; winner = ' '; highlight = "\033[1;31m"; memset(board, ' ', sizeof(board)); if (!dualplayer) { left = (winsize.ws_col - ((int)sizeof("> [X] CPU's first move is random") - 1)) / 2; for (;;) { if (caught_sigterm) goto out; if (caught_sigwinch) { if (ioctl(STDOUT_FILENO, (unsigned long)TIOCGWINSZ, &winsize) < 0) { if (errno == EINTR) continue; eprintf("ioctl TIOCGWINSZ:"); } caught_sigwinch = 0; left = (winsize.ws_col - ((int)sizeof("> [X] CPU's first move is random") - 1)) / 2; redraw = 1; } if (redraw) { printf("\033[H\033[m"); for (r = (winsize.ws_row - 5) / 2; r > 0; r--) printf("\033[K\n"); printf("%*.s%s [%s] CPU begins\033[m\033[K\n", left, "", confline == 0 ? "\033[1;31m>" : " ", cpu_begins ? "X" : " "); printf("%*.s%s [%s] CPU's first move is random\033[m\033[K\n", left, "", confline == 1 ? "\033[1;31m>" : " ", rand_on_first ? "X" : " "); printf("%*.s%s Difficulty: %i\033[m\033[K\n", left, "", confline == 2 ? "\033[1;31m>" : " ", level); printf("%*.s%s [PLAY]\033[m\033[K\n", left, "", confline == 3 ? "\033[1;31m>" : " "); printf("%*.s%s [back]\033[m\033[J", left, "", confline == 4 ? "\033[1;31m>" : " "); fflush(stdout); redraw = 0; } r = libterminput_read(STDIN_FILENO, &input, &termctx); if (r <= 0) { if (r < 0) { if (errno == EINTR) continue; libsimple_eprintf("libterminput_read :"); } break; } if (input.type == LIBTERMINPUT_KEYPRESS) { if (input.keypress.key == LIBTERMINPUT_UP && confline > 0) { confline -= 1; redraw = 1; } else if (input.keypress.key == LIBTERMINPUT_DOWN && confline < 4) { confline += 1; redraw = 1; } else if (input.keypress.key == LIBTERMINPUT_LEFT && confline == 2 && level > 0) { level -= 1; redraw = 1; } else if (input.keypress.key == LIBTERMINPUT_RIGHT && confline == 2 && level < 9) { level += 1; redraw = 1; } else if (input.keypress.key == LIBTERMINPUT_ENTER) { trigger_conf: if (confline == 0) { cpu_begins ^= 1; redraw = 1; } else if (confline == 1) { rand_on_first ^= 1; redraw = 1; } else if (confline == 3) { break; } else if (confline == 4) { redraw = 1; goto restart; } } else if (input.keypress.key == LIBTERMINPUT_SYMBOL) { if (!strcmp(input.keypress.symbol, " ")) { goto trigger_conf; } else if (!strcmp(input.keypress.symbol, "q") || !strcmp(input.keypress.symbol, "Q")) { goto out; } else if (!strcmp(input.keypress.symbol, "L") && (input.keypress.mods & LIBTERMINPUT_CTRL)) { redraw = 1; } } } else if (input.type == LIBTERMINPUT_MOUSEEVENT && input.mouseevent.event == LIBTERMINPUT_PRESS && input.mouseevent.x > (left < 0 ? 0 : (size_t)left)) { input.mouseevent.x -= (left < 0 ? 0 : (size_t)left); input.mouseevent.y -= (size_t)((winsize.ws_row < 5 ? 5 : winsize.ws_row) - 5) / 2; if (input.mouseevent.y > 0 && input.mouseevent.y <= 5 && input.mouseevent.x <= (int)sizeof("> [X] CPU's first move is random") - 1) { if (input.mouseevent.button == LIBTERMINPUT_BUTTON1 || ((int)input.mouseevent.y - 1 == 2 && (input.mouseevent.button == LIBTERMINPUT_BUTTON3 || input.mouseevent.button == LIBTERMINPUT_XBUTTON1|| input.mouseevent.button == LIBTERMINPUT_XBUTTON2))) { confline = (int)input.mouseevent.y - 1; if (confline != 2) goto trigger_conf; if (input.mouseevent.button == LIBTERMINPUT_BUTTON1 || input.mouseevent.button == LIBTERMINPUT_XBUTTON2) { level += 1; level %= 10; } else if (level) { level -= 1; } else { level = 9; } redraw = 1; } } } } left = (winsize.ws_col - ((int)sizeof("| X | X | X |") - 1)) / 2; redraw = 1; rand_move = rand_on_first; if (cpu_begins) { player = 'O'; highlight = "\033[1;34m"; goto cpu_move; } } left = (winsize.ws_col - ((int)sizeof("| X | X | X |") - 1)) / 2; for (;;) { if (caught_sigterm) goto out; if (caught_sigwinch) { if (ioctl(STDOUT_FILENO, (unsigned long)TIOCGWINSZ, &winsize) < 0) { if (errno == EINTR) continue; eprintf("ioctl TIOCGWINSZ:"); } caught_sigwinch = 0; left = (winsize.ws_col - ((int)sizeof("| X | X | X |") - 1)) / 2; redraw = 1; } if (redraw) { printf("\033[H\033[m"); for (r = (winsize.ws_row - 8) / 2; r > 0; r--) printf("\033[K\n"); printf("%*.s%s┌───%s┬%s───%s┬%s───┐%s\033[K\n", left, "", (y == 0 && x == 0) ? highlight : "", (y == 0 && x == 1) ? highlight : "", (y == 0 && x == 0) ? "\033[0m" : "", (y == 0 && x == 2) ? highlight : "", (y == 0 && x == 1) ? "\033[0m" : "", (y == 0 && x == 2) ? "\033[0m" : ""); printf("%*.s%s│ %c %s│%s %c %s│%s %c │%s\033[K\n", left, "", (y == 0 && x == 0) ? highlight : "", (y == 0 && x == 0 && board[0][0] == ' ') ? '+' : board[0][0], (y == 0 && x == 1) ? highlight : "", (y == 0 && x == 0) ? "\033[0m" : "", (y == 0 && x == 1 && board[0][1] == ' ') ? '+' : board[0][1], (y == 0 && x == 2) ? highlight : "", (y == 0 && x == 1) ? "\033[0m" : "", (y == 0 && x == 2 && board[0][2] == ' ') ? '+' : board[0][2], (y == 0 && x == 2) ? "\033[0m" : ""); printf("%*.s%s├───%s┼%s───%s┼%s───┤%s\033[K\n", left, "", (y <= 1 && x == 0) ? highlight : "", (y <= 1 && x == 1) ? highlight : "", (y <= 1 && x == 0) ? "\033[0m" : "", (y <= 1 && x == 2) ? highlight : "", (y <= 1 && x == 1) ? "\033[0m" : "", (y <= 1 && x == 2) ? "\033[0m" : ""); printf("%*.s%s│ %c %s│%s %c %s│%s %c │%s\033[K\n", left, "", (y == 1 && x == 0) ? highlight : "", (y == 1 && x == 0 && board[1][0] == ' ') ? '+' : board[1][0], (y == 1 && x == 1) ? highlight : "", (y == 1 && x == 0) ? "\033[0m" : "", (y == 1 && x == 1 && board[1][1] == ' ') ? '+' : board[1][1], (y == 1 && x == 2) ? highlight : "", (y == 1 && x == 1) ? "\033[0m" : "", (y == 1 && x == 2 && board[1][2] == ' ') ? '+' : board[1][2], (y == 1 && x == 2) ? "\033[0m" : ""); printf("%*.s%s├───%s┼%s───%s┼%s───┤%s\033[K\n", left, "", (y >= 1 && x == 0) ? highlight : "", (y >= 1 && x == 1) ? highlight : "", (y >= 1 && x == 0) ? "\033[0m" : "", (y >= 1 && x == 2) ? highlight : "", (y >= 1 && x == 1) ? "\033[0m" : "", (y >= 1 && x == 2) ? "\033[0m" : ""); printf("%*.s%s│ %c %s│%s %c %s│%s %c │%s\033[K\n", left, "", (y == 2 && x == 0) ? highlight : "", (y == 2 && x == 0 && board[2][0] == ' ') ? '+' : board[2][0], (y == 2 && x == 1) ? highlight : "", (y == 2 && x == 0) ? "\033[0m" : "", (y == 2 && x == 1 && board[2][1] == ' ') ? '+' : board[2][1], (y == 2 && x == 2) ? highlight : "", (y == 2 && x == 1) ? "\033[0m" : "", (y == 2 && x == 2 && board[2][2] == ' ') ? '+' : board[2][2], (y == 2 && x == 2) ? "\033[0m" : ""); printf("%*.s%s└───%s┴%s───%s┴%s───┘%s\033[J", left, "", (y == 2 && x == 0) ? highlight : "", (y == 2 && x == 1) ? highlight : "", (y == 2 && x == 0) ? "\033[0m" : "", (y == 2 && x == 2) ? highlight : "", (y == 2 && x == 1) ? "\033[0m" : "", (y == 2 && x == 2) ? "\033[0m" : ""); fflush(stdout); redraw = 0; } r = libterminput_read(STDIN_FILENO, &input, &termctx); if (r <= 0) { if (r < 0) { if (errno == EINTR) continue; libsimple_eprintf("libterminput_read :"); } break; } if (input.type == LIBTERMINPUT_KEYPRESS) { if (input.keypress.key == LIBTERMINPUT_UP && y > 0) { y -= 1; redraw = 1; } else if (input.keypress.key == LIBTERMINPUT_DOWN && y < 2) { y += 1; redraw = 1; } else if (input.keypress.key == LIBTERMINPUT_LEFT && x > 0) { x -= 1; redraw = 1; } else if (input.keypress.key == LIBTERMINPUT_RIGHT && x < 2) { x += 1; redraw = 1; } else if (input.keypress.key == LIBTERMINPUT_ENTER) { select: if (board[y][x] == ' ') { board[y][x] = player; redraw = 1; get_winner(board, &winner); empty -= 1; if (!empty || winner != ' ') break; if (!dualplayer) { cpu_move: cpu_play(board, player ^ 'X' ^ 'O', empty, rand_move); rand_move = 0; get_winner(board, &winner); empty -= 1; if (!empty || winner != ' ') break; } else { player ^= 'X' ^ 'O'; highlight = player == 'X' ? "\033[1;31m" : "\033[1;34m"; } } } else if (input.keypress.key == LIBTERMINPUT_SYMBOL) { if (!strcmp(input.keypress.symbol, " ")) { goto select; } else if (!strcmp(input.keypress.symbol, "q") || !strcmp(input.keypress.symbol, "Q")) { goto out; } else if (!strcmp(input.keypress.symbol, "L") && (input.keypress.mods & LIBTERMINPUT_CTRL)) { redraw = 1; } else if (!strcmp(input.keypress.symbol, "7")) { y = 0, x = 0; goto select; } else if (!strcmp(input.keypress.symbol, "8")) { y = 0, x = 1; goto select; } else if (!strcmp(input.keypress.symbol, "9")) { y = 0, x = 2; goto select; } else if (!strcmp(input.keypress.symbol, "4")) { y = 1, x = 0; goto select; } else if (!strcmp(input.keypress.symbol, "5")) { y = 1, x = 1; goto select; } else if (!strcmp(input.keypress.symbol, "6")) { y = 1, x = 2; goto select; } else if (!strcmp(input.keypress.symbol, "1")) { y = 2, x = 0; goto select; } else if (!strcmp(input.keypress.symbol, "2")) { y = 2, x = 1; goto select; } else if (!strcmp(input.keypress.symbol, "3")) { y = 2, x = 2; goto select; } } } else if (input.type == LIBTERMINPUT_MOUSEEVENT && input.mouseevent.event == LIBTERMINPUT_PRESS && input.mouseevent.button == LIBTERMINPUT_BUTTON1) { input.mouseevent.x -= (left + 2) < 1 ? 1 : (size_t)(left + 2); input.mouseevent.y -= (size_t)(((winsize.ws_row < 8 ? 8 : winsize.ws_row) - 8) / 2 + 1); if (input.mouseevent.x < 4 * 3 && input.mouseevent.y < 7 && (input.mouseevent.x & 3) != 3 && (input.mouseevent.y & 1)) { y = (int)input.mouseevent.y / 2; x = (int)input.mouseevent.x / 4; goto select; } } } get_win_combination(board, marks); if (winner == 'X') highlight = "\033[1;31m"; else highlight = "\033[1;34m"; for (;;) { if (caught_sigterm) goto out; if (caught_sigwinch) { if (ioctl(STDOUT_FILENO, (unsigned long)TIOCGWINSZ, &winsize) < 0) { if (errno == EINTR) continue; eprintf("ioctl TIOCGWINSZ:"); } caught_sigwinch = 0; left = (winsize.ws_col - ((int)sizeof("| X | X | X |") - 1)) / 2; redraw = 1; } if (redraw) { printf("\033[H\033[m"); for (r = (winsize.ws_row - 8) / 2; r > 0; r--) printf("\033[K\n"); printf("%*.s┌───┬───┬───┐\033[K\n", left, ""); printf("%*.s│ %s%c%s │ %s%c%s │ %s%c%s │\033[K\n", left, "", marks[0][0] ? highlight : "", board[0][0], "\033[m", marks[0][1] ? highlight : "", board[0][1], "\033[m", marks[0][2] ? highlight : "", board[0][2], "\033[m"); printf("%*.s├───┼───┼───┤\033[K\n", left, ""); printf("%*.s│ %s%c%s │ %s%c%s │ %s%c%s │\033[K\n", left, "", marks[1][0] ? highlight : "", board[1][0], "\033[m", marks[1][1] ? highlight : "", board[1][1], "\033[m", marks[1][2] ? highlight : "", board[1][2], "\033[m"); printf("%*.s├───┼───┼───┤\033[K\n", left, ""); printf("%*.s│ %s%c%s │ %s%c%s │ %s%c%s │\033[K\n", left, "", marks[2][0] ? highlight : "", board[2][0], "\033[m", marks[2][1] ? highlight : "", board[2][1], "\033[m", marks[2][2] ? highlight : "", board[2][2], "\033[m"); printf("%*.s└───┴───┴───┘\033[K\n", left, ""); if (winner == ' ') printf("%*.s\033[1m Draw!\033[J", left, ""); else printf("%*.s\033[1;%sm %c won!!\033[J", left, "", winner == 'X' ? "31" : "34", winner); fflush(stdout); redraw = 0; redraw = 0; } r = libterminput_read(STDIN_FILENO, &input, &termctx); if (r <= 0) { if (r < 0) { if (errno == EINTR) continue; libsimple_eprintf("libterminput_read :"); } break; } if (input.type == LIBTERMINPUT_KEYPRESS) { if (input.keypress.key == LIBTERMINPUT_ENTER) { goto new_game; } else if (input.keypress.key == LIBTERMINPUT_SYMBOL) { if (!strcmp(input.keypress.symbol, " ")) goto new_game; else if (!strcmp(input.keypress.symbol, "q") || !strcmp(input.keypress.symbol, "Q")) goto out; else if (!strcmp(input.keypress.symbol, "L") && (input.keypress.mods & LIBTERMINPUT_CTRL)) redraw = 1; } } else if (input.type == LIBTERMINPUT_MOUSEEVENT && input.mouseevent.event == LIBTERMINPUT_PRESS && input.mouseevent.button == LIBTERMINPUT_BUTTON1) { goto new_game; } } out: restore_terminal(); return 0; }