--- 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);