diff options
| author | Mattias Andrée <maandree@kth.se> | 2021-04-22 20:49:48 +0200 |
|---|---|---|
| committer | Mattias Andrée <maandree@kth.se> | 2021-04-22 20:51:38 +0200 |
| commit | 77e516665f23b9cb7af864318c61582d91ab5870 (patch) | |
| tree | 50772907d8d9ce249e47e034455489e22185ac98 | |
| download | basic-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>
| -rw-r--r-- | .gitignore | 12 | ||||
| -rw-r--r-- | LICENSE | 15 | ||||
| -rw-r--r-- | Makefile | 64 | ||||
| -rw-r--r-- | basic-games.c.in | 30 | ||||
| -rw-r--r-- | common.h | 13 | ||||
| -rw-r--r-- | config.mk | 8 | ||||
| -rw-r--r-- | multicall-hardlinks.mk | 12 | ||||
| -rw-r--r-- | multicall-symlinks.mk | 10 | ||||
| -rw-r--r-- | singlecall.mk | 5 | ||||
| -rw-r--r-- | tic-tac-toe.c | 664 |
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 @@ -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; +} |
