summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-02-19 17:51:57 +0100
committerMattias Andrée <m@maandree.se>2026-02-19 17:51:57 +0100
commit7d7487fd43f1c4c2f3fdd671f0cd6c3f471892b1 (patch)
treeff6cbaf4fde226f703241c1e1111570bb1e107be
parentUpdate e-mail (diff)
downloadbasic-games-7d7487fd43f1c4c2f3fdd671f0cd6c3f471892b1.tar.gz
basic-games-7d7487fd43f1c4c2f3fdd671f0cd6c3f471892b1.tar.bz2
basic-games-7d7487fd43f1c4c2f3fdd671f0cd6c3f471892b1.tar.xz
Add rock-paper-scissor (WIP)HEADmaster
Signed-off-by: Mattias Andrée <m@maandree.se>
-rw-r--r--rock-paper-scissors.c369
1 files changed, 369 insertions, 0 deletions
diff --git a/rock-paper-scissors.c b/rock-paper-scissors.c
new file mode 100644
index 0000000..c18cdde
--- /dev/null
+++ b/rock-paper-scissors.c
@@ -0,0 +1,369 @@
+/* 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 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);
+}
+
+
+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;
+ const char *highlight;
+ int confline = 0, line = 0, player = 0, level = 0;
+ uintmax_t wins, loses;
+
+ 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;
+ player = 0;
+ highlight = "\033[1;31m";
+
+ if (!dualplayer) {
+ left = (winsize.ws_col - ((int)sizeof("> [PLAY] Select mode randomly") - 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("> [PLAY] Select mode randomly") - 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 Pattern depth: %i\033[m\033[K\n", left, "",
+ confline == 0 ? "\033[1;31m>" : " ", level);
+ printf("%*.s%s [PLAY] Select mode randomly\033[m\033[K\n", left, "",
+ confline == 1 ? "\033[1;31m>" : " ");
+ printf("%*.s%s [PLAY] Offensive mode\033[m\033[K\n", left, "",
+ confline == 2 ? "\033[1;31m>" : " ");
+ printf("%*.s%s [PLAY] Defensive mode\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 == 0 && level > 0) {
+ level -= 1;
+ redraw = 1;
+ } else if (input.keypress.key == LIBTERMINPUT_RIGHT && confline == 0 && level < 101) {
+ level += 1;
+ redraw = 1;
+ } else if (input.keypress.key == LIBTERMINPUT_ENTER) {
+ trigger_conf:
+ if (confline == 1) {
+ /* TODO select mode randomly */
+ break;
+ } else if (confline == 2) {
+ /* TODO offensive mode */
+ break;
+ } else if (confline == 3) {
+ /* TODO defensive mode */
+ 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 != 0)
+ goto trigger_conf;
+ if (input.mouseevent.button == LIBTERMINPUT_BUTTON1 ||
+ input.mouseevent.button == LIBTERMINPUT_XBUTTON2) {
+ level += 1;
+ level %= 101;
+ } else if (level) {
+ level -= 1;
+ } else {
+ level = 100;
+ }
+ redraw = 1;
+ }
+
+ }
+ }
+ }
+ redraw = 1;
+ }
+
+ left = (winsize.ws_col - ((int)sizeof("> Scissors") - 1)) / 2;
+ wins = 0;
+ loses = 0;
+
+ 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("> Scissors") - 1)) / 2;
+ redraw = 1;
+ }
+
+ if (redraw) {
+ printf("\033[H\033[m");
+ for (r = (winsize.ws_row - 7) / 2; r > 0; r--)
+ printf("\033[K\n");
+ if (!dualplayer) {
+ printf("%*.s\033[1;31m%ju (wins)\033[0m : \033[1;34m%ju (loses)\033[0m\033[K\n",
+ (winsize.ws_row - 3) / 2 - snprintf(NULL, 0, "%ju (wins)", wins),
+ "", wins, loses);
+ } else {
+ printf("%*.s\033[1;31m%ju (P1)\033[0m : \033[1;34m%ju (P2)\033[0m\033[K\n",
+ (winsize.ws_row - 3) / 2 - snprintf(NULL, 0, "%ju (P1)", wins),
+ "", wins, loses);
+ }
+ printf("%*.s%s%s%s\033[K\n\n", left, "",
+ line == 0 ? highlight : "", "Rock",
+ line == 0 ? "\033[0m" : "");
+ printf("%*.s%s%s%s\033[K\n\n", left, "",
+ line == 1 ? highlight : "", "Paper",
+ line == 1 ? "\033[0m" : "");
+ printf("%*.s%s%s%s\033[J", left, "",
+ line == 2 ? highlight : "", "Scissors",
+ line == 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 && line > 0) {
+ line -= 1;
+ redraw = 1;
+ } else if (input.keypress.key == LIBTERMINPUT_DOWN && line < 2) {
+ line += 1;
+ redraw = 1;
+ } else if (input.keypress.key == LIBTERMINPUT_ENTER) {
+ select:
+ ;/* TODO */
+ } 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, "r") ||
+ !strcmp(input.keypress.symbol, "R") ||
+ !strcmp(input.keypress.symbol, "1")) {
+ line = 0;
+ goto select;
+ } else if (!strcmp(input.keypress.symbol, "p") ||
+ !strcmp(input.keypress.symbol, "P") ||
+ !strcmp(input.keypress.symbol, "2")) {
+ line = 1;
+ goto select;
+ } else if (!strcmp(input.keypress.symbol, "s") ||
+ !strcmp(input.keypress.symbol, "S") ||
+ !strcmp(input.keypress.symbol, "3")) {
+ line = 2;
+ goto select;
+ }
+ }
+ } else if (input.type == LIBTERMINPUT_MOUSEEVENT &&
+ input.mouseevent.event == LIBTERMINPUT_PRESS &&
+ input.mouseevent.button == LIBTERMINPUT_BUTTON1) {
+ /* TODO */
+ }
+ }
+
+out:
+ restore_terminal();
+ return 0;
+}