src/game.c

changeset 113
5f58df30d422
parent 110
e2bb03494d46
child 114
12df1b7c792f
--- a/src/game.c	Thu Apr 23 12:26:53 2026 +0200
+++ b/src/game.c	Mon Apr 27 16:55:28 2026 +0200
@@ -272,8 +272,18 @@
     
     size_t bufpos = 0;
     char movestr[MOVESTR_BUFLEN];
-    bool remisrejected = false;
+    bool remis_rejected = false;
+    bool remis_suggested = false;
+    bool use_premove = false;
     uint8_t code;
+
+    if (*gamestate->premove) {
+        use_premove = true;
+        const unsigned mlen = sizeof(gamestate->premove);
+        strncpy(movestr, gamestate->premove, mlen);
+        movestr[mlen] = '\0';
+        memset(gamestate->premove, 0, mlen);
+    }
     
     flushinp();
     while (1) {
@@ -283,20 +293,46 @@
         }
         
         move(inputy, 0);
-        if (remisrejected) {
-            printw(
-                "Use chess notation to enter your move.\n"
-                "Remis offer rejected                    \n\n"
-                "Type your move: ");
+        printw("Use chess notation to enter your move.\n");
+        if (remis_suggested) {
+            printw("The opponent offers remis. Also type remis to accept.\n\n");
+        } else if (remis_rejected) {
+            printw("Remis offer rejected.                                \n\n");
         } else {
-            printw(
-                "Use chess notation to enter your move.\n"
-                "Or use a command: remis, resign, savepgn\n\n"
-                "Type your move: ");
+            printw("Or use a command: remis, resign, savepgn             \n\n");
         }
+        printw("Type your move: ");
         clrtoeol();
-        
-        if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
+
+        /* check if the opponent sent us something */
+        code = net_recieve_code_async(opponent);
+        switch (code) {
+            case NETCODE_REMIS:
+                remis_suggested = true;
+                break;
+            case NETCODE_RESIGN:
+                gamestate->resign = 1;
+                printw("\rYour opponent resigned!");
+                clrtoeol();
+                return 1;
+            case NETCODE_CONNLOST:
+                printw("\rYour opponent has left the game.");
+                clrtoeol();
+                return 1;
+            case NETCODE_ERROR:
+                printw("\rCannot perform asynchronous network IO");
+                cbreak(); getch();
+                exit(EXIT_FAILURE);
+            case NETCODE_AGAIN:
+                /* try again */
+                break;
+            default:
+                printw("\nThe opponent sent an invalid network pacakge.");
+        }
+
+        /* read move */
+        if (use_premove || asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
+            use_premove = false;
             if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
                 gamestate->resign = 1;
                 printw("You resigned!");
@@ -307,7 +343,14 @@
             } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
                 save_pgn(gamestate, gameinfo);
             } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
-                if (!remisrejected) {
+                if (remis_suggested) {
+                    net_send_code(opponent, NETCODE_REMIS);
+                    gamestate->remis = 1;
+                    printw("\rRemis accepted!");
+                    clrtoeol();
+                    refresh();
+                    return 1;
+                } if (!remis_rejected) {
                     net_send_code(opponent, NETCODE_REMIS);
                     printw("Remis offer sent - waiting for acceptance...");
                     refresh();
@@ -324,7 +367,7 @@
                         refresh();
                         return 1;
                     } else {
-                        remisrejected = true;
+                        remis_rejected = true;
                     }
                 }
             } else {
@@ -375,51 +418,83 @@
     }
 }
 
-static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
-    
-    struct timeval timeout;
+static int recvmove(GameState *gamestate, GameInfo *gameinfo,
+        int opponent, uint8_t mycolor) {
+    memset(gamestate->premove, 0, sizeof(gamestate->premove));
+
+    size_t bufpos = 0;
+    char movestr[MOVESTR_BUFLEN];
+    bool remis_suggested = false;
     while (1) {
         timecontrol(gamestate, gameinfo);
         
         move(inputy, 0);
-        printw("Awaiting opponent move...");
+        printw("Awaiting opponent move. Use chess notation to prepare a move.\n");
+        if (*gamestate->premove) {
+            printw("Current pre-move: %s                    \n\n",
+                gamestate->premove);
+        } else if (remis_suggested) {
+            printw("Suggested remis.                        \n\n");
+        } else {
+            printw("Or use a command: remis, resign, savepgn\n\n");
+        }
+        printw("Prepare your next move: ");
         clrtoeol();
         refresh();
 
-        fd_set readfds;
-        
-        FD_ZERO(&readfds);
-        FD_SET(opponent, &readfds);
-        timeout.tv_sec = 0;
-        timeout.tv_usec = 100000;
-        
-        // TODO: allow commands while waiting (e.g. resign, offer draw)
-        
-        int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
-        if (result == -1) {
-            printw("\rCannot perform asynchronous network IO");
-            cbreak(); getch();
-            exit(EXIT_FAILURE);
+        /* allow the player to prepare a move */
+        if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
+            if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
+                gamestate->resign = 1;
+                printw("You resigned!");
+                clrtoeol();
+                refresh();
+                net_send_code(opponent, NETCODE_RESIGN);
+                return 1;
+            } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
+                remis_suggested = true;
+                net_send_code(opponent, NETCODE_REMIS);
+            } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
+                save_pgn(gamestate, gameinfo);
+            } else if (movestr[0] == 0) {
+                memset(gamestate->premove, 0, sizeof(gamestate->premove));
+            } else {
+                Move move;
+                int res = eval_move(gamestate, movestr, &move, mycolor);
+                if (res == VALID_MOVE_SYNTAX) {
+                    strncpy(gamestate->premove, movestr, 8);
+                    memset(movestr, 0, MOVESTR_BUFLEN);
+                    bufpos = 0;
+                    clrtobot();
+                } else {
+                    eval_move_failed_msg(res);
+                }
+            }
         }
-        if (result > 0) {
-            uint8_t code = net_recieve_code(opponent);
 
-            Move move;
-            switch (code) {
-            case NETCODE_TIMEOVER:
-                /* redraw the time control */
-                timecontrol(gamestate, gameinfo);
-                return 1;
-            case NETCODE_RESIGN:
-                gamestate->resign = 1;
-                printw("\rYour opponent resigned!");
+        /* read opponent's move */
+        uint8_t code = net_recieve_code_async(opponent);
+        switch (code) {
+        case NETCODE_TIMEOVER:
+            /* redraw the time control */
+            timecontrol(gamestate, gameinfo);
+            return 1;
+        case NETCODE_RESIGN:
+            gamestate->resign = 1;
+            printw("\rYour opponent resigned!");
+            clrtoeol();
+            return 1;
+        case NETCODE_CONNLOST:
+            printw("\rYour opponent has left the game.");
+            clrtoeol();
+            return 1;
+        case NETCODE_REMIS:
+            if (remis_suggested) {
+                gamestate->remis = 1;
+                printw("\rRemis accepted!");
                 clrtoeol();
                 return 1;
-            case NETCODE_CONNLOST:
-                printw("\rYour opponent has left the game.");
-                clrtoeol();
-                return 1;
-            case NETCODE_REMIS:
+            } else {
                 if (prompt_yesno(
                     "\rYour opponent offers remis - do you accept")) {
                     gamestate->remis = 1;
@@ -430,37 +505,46 @@
                 } else {
                     net_send_code(opponent, NETCODE_DECLINE);
                 }
-                break;
-            case NETCODE_MOVE:
-                net_recieve_data(opponent, &move, sizeof(Move));
-                code = validate_move(gamestate, &move);
-                if (code == VALID_MOVE_SEMANTICS) {
-                    apply_move(gamestate, &move);
-                    if (gamestate->checkmate) {
-                        net_send_code(opponent, NETCODE_CHECKMATE);
-                        printw("\rCheckmate!");
-                        clrtoeol();
-                        return 1;
-                    } else if (gamestate->stalemate) {
-                        net_send_code(opponent, NETCODE_STALEMATE);
-                        printw("\rStalemate!");
-                        clrtoeol();
-                        return 1;
-                    } else if (move.check) {
-                        net_send_code(opponent, NETCODE_CHECK);
-                    } else {
-                        net_send_code(opponent, NETCODE_ACCEPT);
-                    }
-                    return 0;
+            }
+            break;
+        case NETCODE_MOVE: {
+            Move move;
+            net_recieve_data(opponent, &move, sizeof(Move));
+            code = validate_move(gamestate, &move);
+            if (code == VALID_MOVE_SEMANTICS) {
+                apply_move(gamestate, &move);
+                if (gamestate->checkmate) {
+                    net_send_code(opponent, NETCODE_CHECKMATE);
+                    printw("\rCheckmate!");
+                    clrtoeol();
+                    return 1;
+                } else if (gamestate->stalemate) {
+                    net_send_code(opponent, NETCODE_STALEMATE);
+                    printw("\rStalemate!");
+                    clrtoeol();
+                    return 1;
+                } else if (move.check) {
+                    net_send_code(opponent, NETCODE_CHECK);
                 } else {
-                    uint32_t reason = htonl(code);
-                    net_send_data(opponent, NETCODE_DECLINE,
-                        &reason, sizeof(uint32_t));
+                    net_send_code(opponent, NETCODE_ACCEPT);
                 }
-                break;
-            default:
-                printw("\nInvalid network request.");
+                return 0;
+            } else {
+                uint32_t reason = htonl(code);
+                net_send_data(opponent, NETCODE_DECLINE,
+                    &reason, sizeof(uint32_t));
             }
+            break;
+        }
+        case NETCODE_ERROR:
+            printw("\rCannot perform asynchronous network IO");
+            cbreak(); getch();
+            exit(EXIT_FAILURE);
+        case NETCODE_AGAIN:
+            /* try again */
+            break;
+        default:
+            printw("\nInvalid network request.");
         }
     }
 }
@@ -554,7 +638,8 @@
             running = !sendmove(gamestate, &(settings->gameinfo),
                 opponent, mycolor);
         } else {
-            running = !recvmove(gamestate, &(settings->gameinfo), opponent);
+            running = !recvmove(gamestate, &(settings->gameinfo),
+                opponent, mycolor);
         }
         myturn ^= true;
     }  while (running);

mercurial