# HG changeset patch # User Mike Becker # Date 1779969504 -7200 # Node ID 3fc6b1d6cbe9432907ce17810df7856b1f9673b2 # Parent 189c7c77aaabc48b2ef4ed7e9344f1ee354dc4fd implement optional delay - resolves #820 diff -r 189c7c77aaab -r 3fc6b1d6cbe9 src/chess/pgn.c --- a/src/chess/pgn.c Thu May 28 12:15:26 2026 +0200 +++ b/src/chess/pgn.c Thu May 28 13:58:24 2026 +0200 @@ -60,7 +60,7 @@ return pgn_error_strings[code]; } -int read_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) { +int read_pgn(FILE* stream, GameState *gamestate) { int c, i; char result[8]; @@ -255,8 +255,7 @@ return name[0] != '\0' ? name : "Anonymous"; } -void write_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo, - bool export_comments) { +void write_pgn(FILE* stream, GameState *gamestate, bool export_comments) { /* STR tag pairs */ fprintf(stream, "[Event \"%s\"]\n", "terminal-chess game"); fprintf(stream, "[Site \"%s\"]\n", "Somewhere on Earth"); @@ -327,11 +326,11 @@ } /* add clock times when the game was under time control */ - if (gameinfo->timecontrol) { + if (gamestate->info.timecontrol) { memcpy(moveblkptr, " {[%clk ", 8); moveblkptr += 8; unsigned clkmv = i + 2; /* time for the next move! */ - uint16_t clk = remaining_movetime2(gameinfo, gamestate, clkmv); + uint16_t clk = remaining_movetime2(gamestate, clkmv); snpr = print_clk(clk, moveblkptr, true); moveblkptr += snpr; *(moveblkptr++) = ']'; diff -r 189c7c77aaab -r 3fc6b1d6cbe9 src/chess/pgn.h --- a/src/chess/pgn.h Thu May 28 12:15:26 2026 +0200 +++ b/src/chess/pgn.h Thu May 28 13:58:24 2026 +0200 @@ -38,9 +38,8 @@ extern "C" { #endif -int read_pgn(FILE *stream, GameState *gamestate, GameInfo *gameinfo); -void write_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo, - bool export_comments); +int read_pgn(FILE *stream, GameState *gamestate); +void write_pgn(FILE* stream, GameState *gamestate, bool export_comments); void compute_fen(char *str, GameState *gamestate); const char *pgn_player_name(GameState *gamestate, uint8_t color); diff -r 189c7c77aaab -r 3fc6b1d6cbe9 src/chess/rules.c --- a/src/chess/rules.c Thu May 28 12:15:26 2026 +0200 +++ b/src/chess/rules.c Thu May 28 13:58:24 2026 +0200 @@ -170,7 +170,8 @@ gettimeofday(&curtimestamp, NULL); move->timestamp.tv_sec = curtimestamp.tv_sec; move->timestamp.tv_usec = (int32_t) curtimestamp.tv_usec; - + move->movetime.tv_usec = 0; + move->movetime.tv_sec = 0; if (gamestate->movecount > 1) { struct movetimeval lasttstamp = last_move(gamestate).timestamp; uint64_t sec = curtimestamp.tv_sec - lasttstamp.tv_sec; @@ -181,13 +182,16 @@ } else { micros = curtimestamp.tv_usec - lasttstamp.tv_usec; } + + while (micros >= 1000000) { + micros -= 1000000; + sec++; + } - move->movetime.tv_sec = sec; - move->movetime.tv_usec = (int32_t) micros; - } else { - /* no move time for the first move of both white and black */ - move->movetime.tv_usec = 0; - move->movetime.tv_sec = 0; + if (sec >= gamestate->info.delay) { + move->movetime.tv_sec = sec; + move->movetime.tv_usec = (int32_t) micros; + } } } @@ -316,6 +320,7 @@ void gamestate_at_move(GameState *gamestate, unsigned move_number, GameState *replay) { gamestate_init(replay); + memcpy(&replay->info, &gamestate->info, sizeof(GameInfo)); replay->review = true; if (move_number > gamestate->movecount) { move_number = gamestate->movecount; @@ -841,30 +846,28 @@ } } -uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate, - uint8_t color) { +uint16_t remaining_movetime(GameState *gamestate, uint8_t color) { unsigned move_number = gamestate->movecount; if (color == BLACK) { move_number |= 1; } else { move_number = (move_number + 1) & ~1; } - return remaining_movetime2(gameinfo, gamestate, move_number); + return remaining_movetime2(gamestate, move_number); } -uint16_t remaining_movetime2(GameInfo *gameinfo, GameState *gamestate, - unsigned move_number) { - if (!gameinfo->timecontrol) { +uint16_t remaining_movetime2(GameState *gamestate, unsigned move_number) { + if (!gamestate->info.timecontrol) { return 0; } - unsigned total_time = gameinfo->time; + unsigned total_time = gamestate->info.time; unsigned used_time = 0; suseconds_t micros = 0; /* when this is a 0+X game, the clock starts with the increment */ - if (gameinfo->time == 0) { - total_time += gameinfo->addtime; + if (gamestate->info.time == 0) { + total_time += gamestate->info.addtime; } /* go through all already played moves */ @@ -875,7 +878,13 @@ used_time += gamestate->moves[i].movetime.tv_sec; micros += gamestate->moves[i].movetime.tv_usec; /* add increments starting with move 2 */ - if (i > 1) total_time += gameinfo->addtime; + if (i > 1) total_time += gamestate->info.addtime; + } + + /* apply microseconds */ + while (micros >= 1000000) { + micros -= 1000000; + used_time++; } /* when the player is currently playing, count down the clock */ @@ -885,13 +894,21 @@ gamestate->moves[move_number - 1].timestamp; struct timeval currenttstamp; gettimeofday(¤ttstamp, NULL); - used_time += currenttstamp.tv_sec - lastmovetstamp.tv_sec; - micros += currenttstamp.tv_usec - lastmovetstamp.tv_usec; + + /* calculate current move time */ + unsigned cmsec = currenttstamp.tv_sec - lastmovetstamp.tv_sec; + suseconds_t cmusec = currenttstamp.tv_usec - lastmovetstamp.tv_usec; + + /* add microseconds carried over from last move and apply both */ + cmusec += micros; + cmsec += cmusec / 1000000; + + /* add the time and respect a possible dealy */ + if (cmsec >= gamestate->info.delay) { + used_time += cmsec - gamestate->info.delay; + } } - /* apply the microseconds */ - used_time += micros / 1000000; - return used_time >= total_time ? 0 : total_time - used_time; } diff -r 189c7c77aaab -r 3fc6b1d6cbe9 src/chess/rules.h --- a/src/chess/rules.h Thu May 28 12:15:26 2026 +0200 +++ b/src/chess/rules.h Thu May 28 13:58:24 2026 +0200 @@ -77,7 +77,7 @@ struct movetimeval { uint64_t tv_sec; - int32_t tv_usec; + int32_t tv_usec; // important that this is signed b/c potential carry }; typedef struct { @@ -96,15 +96,21 @@ typedef struct { uint8_t servercolor; + /** 1: play with timecontrol, 0: play without time control */ uint8_t timecontrol; + /** If timecontrol is 1, initial clock time in seconds */ uint16_t time; + /** If timecontrol is 1, time added per move in seconds */ uint16_t addtime; + /** If timecontrol is 1, delay before the clock starts ticking down */ + uint16_t delay; } GameInfo; -/** The buffer length for player names in GameInfo structures. */ +/** The buffer length for player names in GameState structures. */ #define PLAYER_NAME_BUFLEN 32 typedef struct { + GameInfo info; Board board; Move* moves; /** optional name of the white player */ @@ -363,26 +369,22 @@ * Returns the remaining time on the clock for the specified * half-move number. * - * @param gameinfo the information about the game * @param gamestate the current game state * @param move_number the half-move that is now going to be played * @return the remaining time - if time control is disabled, this function * always returns zero */ -uint16_t remaining_movetime2(GameInfo *gameinfo, GameState *gamestate, - unsigned move_number); +uint16_t remaining_movetime2(GameState *gamestate, unsigned move_number); /** * Returns the remaining time on the clock for the specified player. * - * @param gameinfo the information about the game * @param gamestate the current game state * @param color either BLACK or WHITE * @return the remaining time - if time control is disabled, this function * always returns zero */ -uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate, - uint8_t color); +uint16_t remaining_movetime(GameState *gamestate, uint8_t color); /** * Converts clock time to string. diff -r 189c7c77aaab -r 3fc6b1d6cbe9 src/main.c --- a/src/main.c Thu May 28 12:15:26 2026 +0200 +++ b/src/main.c Thu May 28 13:58:24 2026 +0200 @@ -44,7 +44,6 @@ #include typedef struct { - GameInfo gameinfo; /** * Server host address. * TCP: server address or \c NULL when we are the server @@ -52,7 +51,12 @@ */ char* serverhost; char* continuepgn; + unsigned short time; + unsigned short addtime; + unsigned short delay; short port; + uint8_t servercolor; + bool timecontrol; bool ishost; bool usedomainsocket; bool singlemachine; @@ -60,29 +64,53 @@ bool unicode; } Settings; -int get_settings(int argc, char **argv, Settings *settings) { +static Settings settings; + +static void init_settings(void) { + memset(&settings, 0, sizeof(settings)); + settings.servercolor = WHITE; + settings.port = 27015; + settings.unicode = !!setlocale(LC_CTYPE, "C.UTF-8"); +} + +static void cleanup() { + endwin(); + if (settings.usedomainsocket && settings.ishost) { + remove(settings.serverhost); + } +} + +static void settings_apply(GameState *gamestate) { + gamestate->info.servercolor = settings.servercolor; + gamestate->info.timecontrol = settings.timecontrol; + gamestate->info.time = settings.time; + gamestate->info.addtime = settings.addtime; + gamestate->info.delay = settings.delay; +} + +static int get_settings(int argc, char **argv) { char *valid; unsigned long int time, port; uint8_t timeunit = 60; size_t len; bool port_set = false; - for (int opt ; (opt = getopt(argc, argv, "a:bc:Fhp:rsS:t:uUv")) != -1 ;) { + for (int opt ; (opt = getopt(argc, argv, "a:bc:d:Fhp:rsS:t:uUv")) != -1 ;) { switch (opt) { case 'c': - settings->continuepgn = optarg; + settings.continuepgn = optarg; break; case 'b': - settings->gameinfo.servercolor = BLACK; + settings.servercolor = BLACK; break; case 'r': - settings->gameinfo.servercolor = rand() & 1 ? WHITE : BLACK; + settings.servercolor = rand() & 1 ? WHITE : BLACK; break; case 's': - settings->singlemachine = true; + settings.singlemachine = true; break; case 'F': - settings->disableflip = true; + settings.disableflip = true; break; case 'u': if (port_set) { @@ -90,13 +118,14 @@ "when a TCP port was specified.\n"); return 1; } - settings->usedomainsocket = true; + settings.usedomainsocket = true; break; case 'U': - settings->unicode = false; + settings.unicode = false; break; case 't': case 'a': + case 'd': len = strlen(optarg); if (optarg[len-1] == 's') { optarg[len-1] = '\0'; @@ -109,11 +138,13 @@ "- Maximum: 65535 seconds (1092 minutes)\n", optarg); return 1; } else { - settings->gameinfo.timecontrol = 1; + settings.timecontrol = 1; if (opt=='t') { - settings->gameinfo.time = timeunit * time; + settings.time = timeunit * time; + } else if (opt=='a') { + settings.addtime = time; } else { - settings->gameinfo.addtime = time; + settings.delay = time; } } break; @@ -122,7 +153,7 @@ fprintf(stderr, "Cannot use -p twice.\n"); return 1; } - if (settings->usedomainsocket) { + if (settings.usedomainsocket) { fprintf(stderr, "Cannot specify TCP port " "when using Unix domain sockets.\n"); return 1; @@ -135,7 +166,7 @@ optarg); return 1; } else { - settings->port = (short) port; + settings.port = (short) port; port_set = true; } break; @@ -155,18 +186,17 @@ " -u Use Unix domain socket instead of TCP\n" " -U Disables unicode pieces\n" " -v Print version information and exits\n" +"\nTime control (default: disabled)\n" +" -t