summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore12
-rw-r--r--LICENSE15
-rw-r--r--Makefile64
-rw-r--r--basic-games.c.in30
-rw-r--r--common.h13
-rw-r--r--config.mk8
-rw-r--r--multicall-hardlinks.mk12
-rw-r--r--multicall-symlinks.mk10
-rw-r--r--singlecall.mk5
-rw-r--r--tic-tac-toe.c664
10 files changed, 833 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..903ff7d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+*\#*
+*~
+*.o
+*.a
+*.so
+*.so.*
+*.su
+*.lo
+*.bo
+/basic-games
+/basic-games.c
+/tic-tac-toe
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c44b2d9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2021 Mattias Andrée <maandree@kth.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..60fb777
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,64 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+
+CALLTYPE = multicall-hardlinks
+# multicall-hardlinks = multiple hardlinks of the same multicall binary is installed
+# multicall-symlinks = multiple links to a multicall binary named $(PREFIX)/bin/basic-games are installed
+# singlecall = separate binaries are install for each command
+
+
+BIN =\
+ tic-tac-toe
+
+HDR =\
+ common.h
+
+COMMON =
+
+OBJ = $(BIN:=.o) $(COMMON)
+BOBJ = $(OBJ:.o=.bo)
+MAN1 = $(BIN:=.1)
+
+
+include $(CONFIGFILE)
+include $(CALLTYPE).mk
+
+
+$(OBJ): $(@:.o=.c) $(HDR)
+$(BOBJ): $(@:.bo=.c) $(HDR)
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.bo:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -Dmain="$$(printf '%s\n' main__$* | tr - _)" -DMULTICALL_BINARY
+
+basic-games: basic-games.o $(BOBJ)
+ $(CC) -o $@ $@.o $(BOBJ) $(LDFLAGS)
+
+basic-games.c: basic-games.c.in Makefile
+ printf '#define LIST_COMMANDS' > $@
+ printf '\\\n\tX(%s)' $(BIN) | tr - _ >> $@
+ printf '\n\n' >> $@
+ cat basic-games.c.in >> $@
+
+tic-tac-toe: tic-tac-toe.o
+ $(CC) -o $@ $@.o $(LDFLAGS)
+
+install-common:
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1"
+ cp -- $(MAN1) "$(DESTDIR)$(MANPREFIX)/man1"
+
+uninstall:
+ -cd -- "$(DESTDIR)$(PREFIX)/bin" && rm -f -- $(BIN_)
+ -cd -- "$(DESTDIR)$(MANPREFIX)/man1" && rm -f -- $(MAN1)
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/basic-games"
+
+clean:
+ -rm -rf -- *.o *.a *.lo *.so *.bo *.su $(BIN) basic-games basic-games.c
+
+.SUFFIXES:
+.SUFFIXES: .o .c .bo
+
+.PHONY: all install install-common uninstall clean
diff --git a/basic-games.c.in b/basic-games.c.in
new file mode 100644
index 0000000..b297951
--- /dev/null
+++ b/basic-games.c.in
@@ -0,0 +1,30 @@
+/* See LICENSE file for copyright and license details. */
+#include <stdio.h>
+#include <string.h>
+
+#define X(NAM) int main__##NAM(int, char *[]);
+LIST_COMMANDS
+#undef X
+
+char *argv0 = NULL;
+
+int
+main(int argc, char *argv[])
+{
+ char *name, *p;
+
+ name = strrchr(*argv, '/');
+ if (!name++)
+ name = *argv;
+ for (p = name; (p = strchr(p, '-'));)
+ *p++ = '_';
+
+#define X(NAM)\
+ if (!strcmp(name, #NAM))\
+ return main__##NAM(argc, argv);
+ LIST_COMMANDS;
+#undef X
+
+ fprintf(stderr, "%s: unrecognised command for multicall binary\n", *argv);
+ return 127;
+}
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..8b56f54
--- /dev/null
+++ b/common.h
@@ -0,0 +1,13 @@
+/* See LICENSE file for copyright and license details. */
+#ifdef MULTICALL_BINARY
+# define LIBSIMPLY_CONFIG_MULTICALL_BINARY
+#endif
+
+#include <libsimple.h>
+#include <libsimple-arg.h>
+#include <libterminput.h>
+
+#include <sys/random.h>
+#include <sys/timerfd.h>
+#include <sys/timex.h>
+#include <termios.h>
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..0a4122e
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = cc
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS = -std=c99 -Wall -Wextra -Os
+LDFLAGS = -lsimple -lterminput -s
diff --git a/multicall-hardlinks.mk b/multicall-hardlinks.mk
new file mode 100644
index 0000000..aaec46d
--- /dev/null
+++ b/multicall-hardlinks.mk
@@ -0,0 +1,12 @@
+all: basic-games
+
+install: basic-games install-common
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ set -- $(BIN) &&\
+ cp -- "$$1" "$(DESTDIR)$(PREFIX)/bin/$$1" &&\
+ linkto="$$1" &&\
+ shift 1 &&\
+ cd -- "$(DESTDIR)$(PREFIX)/bin/" &&\
+ for f; do\
+ ln -- "$$linkto" "$$f" || exit 1;\
+ done
diff --git a/multicall-symlinks.mk b/multicall-symlinks.mk
new file mode 100644
index 0000000..042f94a
--- /dev/null
+++ b/multicall-symlinks.mk
@@ -0,0 +1,10 @@
+all: basic-games
+
+install: basic-games install-common
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ cp -- basic-games "$(DESTDIR)$(PREFIX)/lib/"
+ cd -- "$(DESTDIR)$(PREFIX)/bin/" &&\
+ for f in $(BIN); do\
+ ln -s -- ../lib/basic-games "$$f" || exit 1;\
+ done
diff --git a/singlecall.mk b/singlecall.mk
new file mode 100644
index 0000000..7dbb158
--- /dev/null
+++ b/singlecall.mk
@@ -0,0 +1,5 @@
+all: $(BIN)
+
+install: $(BIN) install-common
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ cp -- $(BIN) "$(DESTDIR)$(PREFIX)/bin/"
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;
+}