--- a/src/chess/pgn.c Mon Aug 17 12:22:08 2026 +0200 +++ b/src/chess/pgn.c Fri Aug 21 16:08:50 2026 +0200 @@ -58,7 +58,7 @@ return pgn_error_pos; } -static const char *pgn_result(GameState *gamestate) { +static const char *pgn_result(const GameState *gamestate) { if (is_game_drawn(gamestate)) { return "1/2-1/2"; } else if (gamestate->wresign) { @@ -269,7 +269,7 @@ } } -static const char *pgn_date(GameState *gamestate) { +static const char *pgn_date(const GameState *gamestate) { static char date[16]; time_t dateseconds; if (gamestate->movecount == 0) { @@ -301,98 +301,101 @@ return date; } -const char *pgn_player_name(GameState *gamestate, uint8_t color) { +const char *pgn_player_name(const GameState *gamestate, uint8_t color) { const char *name = color == WHITE ? gamestate->wname : gamestate->bname; return name[0] != '\0' ? name : "Anonymous"; } -void write_pgn(FILE* stream, GameState *gamestate, bool export_comments) { +int write_pgn(FILE* stream, const GameState *gamestate, bool export_comments) { + char *pgn = create_pgn(gamestate, export_comments); + if (pgn == NULL) return 1; + fputs(pgn, stream); + free(pgn); + return 0; +} + +char *create_pgn(const GameState *gamestate, bool export_comments) { + size_t resultcap = 4096; + char *result = malloc(resultcap); + if (result == NULL) return NULL; + char *resp = result; + /* STR tag pairs */ - fprintf(stream, "[Event \"%s\"]\n", "terminal-chess game"); - fprintf(stream, "[Site \"%s\"]\n", "Somewhere on Earth"); - fprintf(stream, "[Date \"%s\"]\n", pgn_date(gamestate)); - fprintf(stream, "[Round \"%s\"]\n", "-"); - fprintf(stream, "[White \"%s\"]\n", pgn_player_name(gamestate, WHITE)); - fprintf(stream, "[Black \"%s\"]\n", pgn_player_name(gamestate, BLACK)); - const char *result = pgn_result(gamestate); - fprintf(stream, "[Result \"%s\"]\n\n", result); + resp += sprintf(resp, "[Event \"%s\"]\n", "terminal-chess game"); + resp += sprintf(resp, "[Site \"%s\"]\n", "Somewhere on Earth"); + resp += sprintf(resp, "[Date \"%s\"]\n", pgn_date(gamestate)); + resp += sprintf(resp, "[Round \"%s\"]\n", "-"); + resp += sprintf(resp, "[White \"%s\"]\n", pgn_player_name(gamestate, WHITE)); + resp += sprintf(resp, "[Black \"%s\"]\n", pgn_player_name(gamestate, BLACK)); + const char *gameresult = pgn_result(gamestate); + resp += sprintf(resp, "[Result \"%s\"]\n\n", gameresult); // TODO: add optional clock info /* moves */ - size_t moveblkcap = 4096; - char *moveblk = malloc(moveblkcap); - char *moveblkptr = moveblk; - if (moveblk == NULL) { - // TODO: error handling (for the entire function actually) - abort(); - } + char *moveblk = resp; /* remember where the move block starts */ + for (unsigned i = 0 ; i < gamestate->movecount ; i++) { - /* reallocate move block buffer if needed */ + /* reallocate buffer if needed */ { - size_t moveblksize = moveblkptr - moveblk; - if (moveblksize + 128 > moveblkcap) { - moveblkcap *= 2; - char *newmoveblk = realloc(moveblk, moveblkcap); - if (newmoveblk == NULL) { - free(moveblk); - abort(); + size_t rsize = resp - result; + if (rsize + 128 > resultcap) { + resultcap *= 2; + char *newresult = realloc(result, resultcap); + if (newresult == NULL) { + free(result); + return NULL; } - moveblk = newmoveblk; - moveblkptr = moveblk + moveblksize; + result = newresult; + resp = result + rsize; } } - int snpr; /* return value of printf calls */ - if (i % 2 == 0) { - snpr = snprintf(moveblkptr, 16, "%d.\x1f%s", + resp += snprintf(resp, 16, "%d.\x1f%s", 1+i/2, gamestate->moves[i].string); } else { - snpr = snprintf(moveblkptr, 16, "%s", + resp += snprintf(resp, 16, "%s", gamestate->moves[i].string); } - moveblkptr += snpr; if (!export_comments) { - *(moveblkptr++) = ' '; + *(resp++) = ' '; continue; } /* add clock times when the game was under time control */ if (gamestate->info.timecontrol) { - memcpy(moveblkptr, " {[%clk ", 8); - moveblkptr += 8; + memcpy(resp, " {[%clk ", 8); + resp += 8; unsigned clkmv = i + 2; /* time for the next move! */ uint16_t clk = remaining_movetime2(gamestate, clkmv); - snpr = print_clk(clk, moveblkptr, true); - moveblkptr += snpr; - *(moveblkptr++) = ']'; - *(moveblkptr++) = '}'; + resp += print_clk(clk, resp, true); + *(resp++) = ']'; + *(resp++) = '}'; /* elapsed move time */ - memcpy(moveblkptr, " {[%emt ", 8); - moveblkptr += 8; + memcpy(resp, " {[%emt ", 8); + resp += 8; uint16_t emt = gamestate->moves[i].movetime.tv_sec; - snpr = print_clk(emt, moveblkptr, true); - moveblkptr += snpr; - *(moveblkptr++) = ']'; - *(moveblkptr++) = '}'; + resp += print_clk(emt, resp, true); + *(resp++) = ']'; + *(resp++) = '}'; } - *(moveblkptr++) = ' '; + *(resp++) = ' '; } - if (result[0] != '*') { - size_t rlen = strlen(result); - memcpy(moveblkptr, result, rlen); - moveblkptr += rlen; + if (gameresult[0] != '*') { + size_t rlen = strlen(gameresult); + memcpy(resp, gameresult, rlen); + resp += rlen; } - *(moveblkptr++) = '\n'; - *moveblkptr = 0; + *(resp++) = '\n'; + *resp = 0; + /* post-process move block and format it nicely */ pgn_insert_newlines(moveblk); - fputs(moveblk, stream); - free(moveblk); + return result; }