fix that PGN (with comments) can exceed 80 chars default tip

Fri, 17 Apr 2026 14:08:10 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 17 Apr 2026 14:08:10 +0200
changeset 107
36dd94278142
parent 106
bdc9528d3e2b

fix that PGN (with comments) can exceed 80 chars

resolves #824

src/chess/pgn.c file | annotate | diff | comparison | revisions
src/chess/rules.c file | annotate | diff | comparison | revisions
src/chess/rules.h file | annotate | diff | comparison | revisions
--- a/src/chess/pgn.c	Fri Apr 17 13:17:34 2026 +0200
+++ b/src/chess/pgn.c	Fri Apr 17 14:08:10 2026 +0200
@@ -28,6 +28,8 @@
  */
 
 #include "pgn.h"
+
+#include <stdlib.h>
 #include <ctype.h>
 #include <string.h>
 
@@ -177,6 +179,23 @@
     return 0;
 }
 
+static void pgn_insert_newlines(char *block) {
+    size_t pos = 0;
+    size_t last_space_pos = 0;
+    size_t line_len = 0;
+    while (block[pos] != '\0') {
+        if (block[pos] == ' ') {
+            last_space_pos = pos;
+        }
+        line_len++;
+        if (line_len > 80) {
+            block[last_space_pos] = '\n';
+            line_len = pos - last_space_pos;
+        }
+        pos++;
+    }
+}
+
 void write_pgn(FILE* stream, GameState *gamestate, GameInfo *gameinfo) {
     // TODO: tag pairs
 
@@ -197,40 +216,76 @@
     fprintf(stream, "[Result \"%s\"]\n\n", result);
     
     /* moves */
+    size_t moveblkcap = 4096;
+    char *moveblk = malloc(moveblkcap);
+    char *moveblkptr = moveblk;
+    if (moveblk == NULL) {
+        // TODO: error handling (for the entire function actually)
+        abort();
+    }
     for (unsigned i = 0 ; i < gamestate->movecount ; i++) {
-        
+        /* reallocate move block 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();
+                }
+                moveblk = newmoveblk;
+                moveblkptr = moveblk + moveblksize;
+            }
+        }
+
+        int snpr; /* return value of printf calls */
+
         if (i % 2 == 0) {
-            fprintf(stream, "%d. %s", 1+i/2, gamestate->moves[i].string);
+            snpr = snprintf(moveblkptr, 16, "%d. %s",
+                1+i/2, gamestate->moves[i].string);
         } else {
-            fprintf(stream, "%s", gamestate->moves[i].string);
+            snpr = snprintf(moveblkptr, 16, "%s",
+                gamestate->moves[i].string);
         }
+        moveblkptr += snpr;
 
         /* add clock times when the game was under time control */
         if (gameinfo->timecontrol) {
-            char clkstr[16];
+            memcpy(moveblkptr, " {[%clk ", 8);
+            moveblkptr += 8;
             unsigned clkmv = i + 2; /* time for the next move! */
             uint16_t clk = remaining_movetime2(gameinfo, gamestate, clkmv);
-            print_clk(clk, clkstr, true);
-            fprintf(stream, " {[%%clk %s]}", clkstr);
+            snpr = print_clk(clk, moveblkptr, true);
+            moveblkptr += snpr;
+            *(moveblkptr++) = ']';
+            *(moveblkptr++) = '}';
             
             /* elapsed move time */
-            print_clk(gamestate->moves[i].movetime.tv_sec, clkstr, true);
-            fprintf(stream, " {[%%emt %s]}", clkstr);
+            memcpy(moveblkptr, " {[%emt ", 8);
+            moveblkptr += 8;
+            uint16_t emt = gamestate->moves[i].movetime.tv_sec;
+            snpr = print_clk(emt, moveblkptr, true);
+            moveblkptr += snpr;
+            *(moveblkptr++) = ']';
+            *(moveblkptr++) = '}';
         }
         
-        /* line break every 10 half-moves */
-        if ((i+1) % 10)  {
-            fputc(' ', stream);
-        } else {
-            fputc('\n', stream);
-        }
+        *(moveblkptr++) = ' ';
     }
     
-    if (result[0] == '*') {
-        fputc('\n', stream);
-    } else {
-        fprintf(stream, "%s\n", result);
+    if (result[0] != '*') {
+        size_t rlen = strlen(result);
+        memcpy(moveblkptr, result, rlen);
+        moveblkptr += rlen;
     }
+    *(moveblkptr++) = '\n';
+    *moveblkptr = 0;
+
+    pgn_insert_newlines(moveblk);
+    fputs(moveblk, stream);
+
+    free(moveblk);
 }
 
 static size_t fen_pieces(char *str, GameState *gamestate) {
--- a/src/chess/rules.c	Fri Apr 17 13:17:34 2026 +0200
+++ b/src/chess/rules.c	Fri Apr 17 14:08:10 2026 +0200
@@ -823,13 +823,13 @@
     return used_time >= total_time ? 0 : total_time - used_time;
 }
 
-void print_clk(uint16_t time, char *str, bool always_hours) {
+int print_clk(uint16_t time, char *str, bool always_hours) {
     unsigned hours = time / 3600;
     unsigned minutes = (time % 3600) / 60;
     unsigned seconds = time % 60;
     if (hours > 0 || always_hours) {
-        snprintf(str, 9, "%u:%02u:%02u", hours, minutes, seconds);
+        return snprintf(str, 9, "%u:%02u:%02u", hours, minutes, seconds);
     } else {
-        snprintf(str, 6, "%02u:%02u", minutes, seconds);
+        return snprintf(str, 6, "%02u:%02u", minutes, seconds);
     }
 }
--- a/src/chess/rules.h	Fri Apr 17 13:17:34 2026 +0200
+++ b/src/chess/rules.h	Fri Apr 17 14:08:10 2026 +0200
@@ -263,7 +263,7 @@
  * @param str the target buffer (should be at least 10 chars large)
  * @param always_hours if hours should always be printed
  */
-void print_clk(uint16_t time, char *str, bool always_hours);
+int print_clk(uint16_t time, char *str, bool always_hours);
 
 #endif	/* RULES_H */
 

mercurial