--- a/src/game.c Tue May 19 17:59:24 2026 +0200 +++ b/src/game.c Tue May 19 18:03:06 2026 +0200 @@ -185,7 +185,6 @@ printw("\rFilename: "); clrtoeol(); - refresh(); char filename[64]; int y = getcury(stdscr); @@ -226,16 +225,10 @@ if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) { if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) { - gamestate->resign = 1; - printw("%s resigned!", curcolorstr); - clrtobot(); - refresh(); + gamestate->resign = true; return 1; } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) { - gamestate->remis = 1; - printw("Game ends remis."); - clrtobot(); - refresh(); + gamestate->remis = true; return 1; } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) { save_pgn(gamestate, gameinfo); @@ -247,12 +240,8 @@ if (result == VALID_MOVE_SEMANTICS) { apply_move(gamestate, &move); if (gamestate->checkmate) { - printw("Checkmate!"); - clrtoeol(); return 1; } else if (gamestate->stalemate) { - printw("Stalemate!"); - clrtoeol(); return 1; } else { return 0; @@ -323,13 +312,10 @@ resign_suggested = true; break; case NETCODE_RESIGN: - gamestate->resign = 1; - printw("\rYour opponent resigned!"); - clrtoeol(); + gamestate->resign = true; return 1; case NETCODE_CONNLOST: - printw("\rYour opponent has left the game."); - clrtoeol(); + gamestate->ragequit = true; return 1; case NETCODE_ERROR: printw("\rCannot perform asynchronous network IO"); @@ -347,10 +333,7 @@ bool was_premove = use_premove; use_premove = false; if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) { - gamestate->resign = 1; - printw("You resigned!"); - clrtoeol(); - refresh(); + gamestate->resign = true; net_send_code(opponent, NETCODE_RESIGN); return 1; } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) { @@ -358,10 +341,7 @@ } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) { if (remis_suggested) { net_send_code(opponent, NETCODE_REMIS); - gamestate->remis = 1; - printw("\rRemis accepted!"); - clrtoeol(); - refresh(); + gamestate->remis = true; return 1; } if (!remis_rejected) { net_send_code(opponent, NETCODE_REMIS); @@ -369,15 +349,10 @@ refresh(); code = net_recieve_code(opponent); if (code == NETCODE_ACCEPT) { - gamestate->remis = 1; - printw("\rRemis accepted!"); - clrtoeol(); - refresh(); + gamestate->remis = true; return 1; } else if (code == NETCODE_CONNLOST) { - printw("\rYour opponent left the game."); - clrtoeol(); - refresh(); + gamestate->ragequit = true; return 1; } else { remis_rejected = true; @@ -404,13 +379,7 @@ || code == NETCODE_CHECKMATE || code == NETCODE_STALEMATE) { apply_move(gamestate, &move); - if (gamestate->checkmate) { - printw("Checkmate!"); - clrtoeol(); - return 1; - } else if (gamestate->stalemate) { - printw("Stalemate!"); - clrtoeol(); + if (gamestate->checkmate || gamestate->stalemate) { return 1; } else { return 0; @@ -468,10 +437,7 @@ /* 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(); + gamestate->resign = true; net_send_code(opponent, NETCODE_RESIGN); return 1; } else if (strncmp(movestr, "taunt", MOVESTR_BUFLEN) == 0) { @@ -505,26 +471,19 @@ timecontrol(gamestate, gameinfo); return 1; case NETCODE_RESIGN: - gamestate->resign = 1; - printw("\rYour opponent resigned!"); - clrtoeol(); + gamestate->resign = true; return 1; case NETCODE_CONNLOST: - printw("\rYour opponent has left the game."); - clrtoeol(); + gamestate->ragequit = true; return 1; case NETCODE_REMIS: if (remis_suggested) { - gamestate->remis = 1; - printw("\rRemis accepted!"); - clrtoeol(); + gamestate->remis = true; return 1; } else { if (prompt_yesno( "\rYour opponent offers remis - do you accept")) { - gamestate->remis = 1; - printw("\rRemis accepted!"); - clrtoeol(); + gamestate->remis = true; net_send_code(opponent, NETCODE_ACCEPT); return 1; } else { @@ -540,13 +499,9 @@ 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); @@ -575,31 +530,82 @@ } void game_review(Settings* settings, GameState *gamestate) { + const unsigned page_moves = 10; GameInfo *gameinfo = &(settings->gameinfo); + GameState viewedstate = {0}; + unsigned viewedmove = gamestate->movecount; + bool redraw = true; - move(0,0); - draw_board(gamestate, WHITE, settings->unicode); - - mvaddstr(getmaxy(stdscr)-1, 0, - "Press 'q' to quit or 's' to save a PGN file..."); - refresh(); - flushinp(); - noecho(); int c; do { + if (redraw) { + gamestate_cleanup(&viewedstate); + gamestate_at_move(gamestate, viewedmove, &viewedstate); + + erase(); /* don't use clear() to avoid flickering */ + draw_board(&viewedstate, WHITE, settings->unicode); + timecontrol(&viewedstate, gameinfo); + + move(getmaxy(stdscr)-5, 0); + const char *curcolorstr = + gamestate->movecount % 2 == 0 ? "White" : "Black"; + if (gamestate->resign) { + printw("%s resigned.\n", curcolorstr); + } else if (gamestate->remis) { + addstr("The game ended remis.\n"); + } else if (gamestate->stalemate) { + addstr("The game ended in a stalemate.\n"); + } else if (gamestate->checkmate) { + printw("%s has lost the game.\n", curcolorstr); + } else if (gamestate->ragequit) { + printw("Your opponent disconnected.\n"); + } + addstr("\nPress 'q' to quit, 's' to save the position as PGN, or\n" + "arrow keys, home/end, page up/down to review the game.\n"); + flushinp(); + redraw = false; + } c = getch(); if (c == 's') { addch('\r'); echo(); - save_pgn(gamestate, gameinfo); - addstr(" Press 'q' to quit..."); + save_pgn(&viewedstate, gameinfo); noecho(); + redraw = true; + } else if (c == KEY_UP || c == KEY_LEFT) { + if (viewedmove > 0) { + viewedmove--; + redraw = true; + } + } else if (c == KEY_DOWN || c == KEY_RIGHT) { + if (viewedmove < gamestate->movecount) { + viewedmove++; + redraw = true; + } + } else if (c == KEY_HOME) { + viewedmove = 0; + redraw = true; + } else if (c == KEY_END) { + viewedmove = gamestate->movecount; + redraw = true; + } else if (c == KEY_PPAGE) { + if (viewedmove > page_moves) { + viewedmove -= page_moves; + } else { + viewedmove = 0; + } + redraw = true; + } else if (c == KEY_NPAGE) { + viewedmove += page_moves; + if (viewedmove > gamestate->movecount) { + viewedmove = gamestate->movecount; + } + redraw = true; } } while (c != 'q'); echo(); - - gamestate_cleanup(gamestate); + gamestate_cleanup(&viewedstate); } void game_play_singlemachine(Settings *settings) { @@ -642,6 +648,7 @@ } while (running); game_review(settings, &gamestate); + gamestate_cleanup(&gamestate); } void game_play(Settings *settings, GameState *gamestate, int opponent) {