summaryrefslogtreecommitdiffstats
path: root/tic-tac-toe.c
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2021-04-22 20:49:48 +0200
committerMattias Andrée <maandree@kth.se>2021-04-22 20:51:38 +0200
commit77e516665f23b9cb7af864318c61582d91ab5870 (patch)
tree50772907d8d9ce249e47e034455489e22185ac98 /tic-tac-toe.c
downloadbasic-games-77e516665f23b9cb7af864318c61582d91ab5870.tar.gz
basic-games-77e516665f23b9cb7af864318c61582d91ab5870.tar.bz2
basic-games-77e516665f23b9cb7af864318c61582d91ab5870.tar.xz
First commit, with implementation of tic-tac-toe
Signed-off-by: Mattias Andrée <maandree@kth.se>
Diffstat (limited to 'tic-tac-toe.c')
-rw-r--r--tic-tac-toe.c664
1 files changed, 664 insertions, 0 deletions
diff --git a/tic-tac-toe.c b/tic-tac-toe.c
new file mode 100644
index 0000000..907ff94
--- /dev/null
+++ b/tic-tac-toe.c
@@ -0,0 +1,664 @@
+/* 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 <stdin>:");
+ saved_stty = stty;
+ stty.c_lflag &= (tcflag_t)~(ECHO | ICANON);
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty))
+ eprintf("tcsetattr <stdin> 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 <stdout> 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 <stdin>:");
+ }
+ 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 <stdout> 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 <stdin>:");
+ }
+ 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 <stdout> 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 <stdin>:");
+ }
+ 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 <stdout> 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 <stdin>:");
+ }
+ 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;
+}