add possibility to specify the width for a PGN move block + function to generate only the move block default tip

Thu, 10 Sep 2026 16:48:33 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 10 Sep 2026 16:48:33 +0200
changeset 228
13b934b7d03e
parent 227
447d1e192807

add possibility to specify the width for a PGN move block + function to generate only the move block

somehow also relates to #924 and issue #970

src/chess/pgn.c file | annotate | diff | comparison | revisions
src/chess/pgn.h file | annotate | diff | comparison | revisions
--- a/src/chess/pgn.c	Thu Sep 10 09:35:41 2026 +0200
+++ b/src/chess/pgn.c	Thu Sep 10 16:48:33 2026 +0200
@@ -275,7 +275,7 @@
     return result;
 }
 
-static void pgn_insert_newlines(char *block) {
+static void pgn_insert_newlines(char *block, unsigned max_width) {
     size_t pos = 0;
     size_t last_space_pos = 0;
     size_t line_len = 0;
@@ -288,7 +288,7 @@
             last_space_pos = pos;
         }
         line_len++;
-        if (line_len > 80) {
+        if (line_len > max_width) {
             block[last_space_pos] = '\n';
             line_len = pos - last_space_pos;
         }
@@ -341,43 +341,12 @@
     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 */
-    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_game_result(gamestate);
-    resp += sprintf(resp, "[Result \"%s\"]\n\n", gameresult);
-
-    // TODO: add optional clock info
-    
-    /* moves */
-    char *moveblk = resp; /* remember where the move block starts */
-
+static size_t pgn_move_text_add(
+        char *buffer,
+        const GameState *gamestate,
+        unsigned max_width, bool export_comments) {
+    char *resp = buffer;
     for (unsigned i = 0 ; i < gamestate->movecount ; i++) {
-        /* reallocate buffer if needed */
-        {
-            size_t rsize = resp - result;
-            if (rsize + 128 > resultcap) {
-                resultcap *= 2;
-                char *newresult = realloc(result, resultcap);
-                if (newresult == NULL) {
-                    free(result);
-                    return NULL;
-                }
-                result = newresult;
-                resp = result + rsize;
-            }
-        }
-
         if (i % 2 == 0) {
             // TODO: add move_start offset
             resp += snprintf(resp, 16, "%d.\x1f%s",
@@ -401,7 +370,7 @@
             resp += print_clk(clk, resp, true);
             *(resp++) = ']';
             *(resp++) = '}';
-            
+
             /* elapsed move time */
             memcpy(resp, " {[%emt ", 8);
             resp += 8;
@@ -410,20 +379,63 @@
             *(resp++) = ']';
             *(resp++) = '}';
         }
-        
+
         *(resp++) = ' ';
     }
-    
+
+    const char *gameresult = pgn_game_result(gamestate);
     if (gameresult[0] != '*') {
         size_t rlen = strlen(gameresult);
         memcpy(resp, gameresult, rlen);
         resp += rlen;
     }
     *(resp++) = '\n';
-    *resp = 0;
+    *(resp++) = '\0';
 
     /* post-process move block and format it nicely */
-    pgn_insert_newlines(moveblk);
+    pgn_insert_newlines(buffer, max_width);
+
+    return resp-buffer;
+}
+
+char *pgn_move_text(const GameState *gamestate,
+            unsigned max_width, bool export_comments) {
+    size_t bufcap = 16;
+    bufcap += gamestate->movecount * 16;
+    if (export_comments) {
+        bufcap += gamestate->movecount * 40;
+    }
+    char *result = malloc(bufcap);
+    if (result == NULL) return NULL;
+    size_t n = pgn_move_text_add(result, gamestate, max_width, export_comments);
+    return realloc(result, n);
+}
 
-    return result;
+char *create_pgn(const GameState *gamestate, bool export_comments) {
+    /* calculate generous upper bound for memory usage */
+    size_t bufcap = 256;
+    bufcap += gamestate->movecount * 16;
+    if (export_comments) {
+        bufcap += gamestate->movecount * 40;
+    }
+    char *result = malloc(bufcap);
+    if (result == NULL) return NULL;
+    char *resp = result;
+
+    /* STR tag pairs */
+    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));
+    resp += sprintf(resp, "[Result \"%s\"]\n", pgn_game_result(gamestate));
+
+    // TODO: add optional clock info
+
+    *(resp++) = '\n';
+    resp += pgn_move_text_add(resp, gamestate, 80, export_comments);
+
+    /* shrink memory to fit */
+    return realloc(result, resp-result);
 }
--- a/src/chess/pgn.h	Thu Sep 10 09:35:41 2026 +0200
+++ b/src/chess/pgn.h	Thu Sep 10 16:48:33 2026 +0200
@@ -64,6 +64,9 @@
 pgn_result parse_pgn(const char *data, GameState *gamestate);
 char *create_pgn(const GameState *gamestate, bool export_comments);
 
+char *pgn_move_text(const GameState *gamestate,
+        unsigned max_width, bool export_comments);
+
 // TODO: this function looks a bit misplaced
 const char *pgn_player_name(const GameState *gamestate, Color color);
 

mercurial