change move list from linked list to array (prepares game replays) default tip

Sat, 04 Apr 2026 13:25:47 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 04 Apr 2026 13:25:47 +0200
changeset 98
9cb41383540f
parent 97
f87cad9445b4

change move list from linked list to array (prepares game replays)

also fixes extra whitespaces in PGN output

src/chess/game-info.c file | annotate | diff | comparison | revisions
src/chess/game-info.h file | annotate | diff | comparison | revisions
src/chess/king.c file | annotate | diff | comparison | revisions
src/chess/pgn.c file | annotate | diff | comparison | revisions
src/chess/rules.c file | annotate | diff | comparison | revisions
src/game.c file | annotate | diff | comparison | revisions
src/server.c file | annotate | diff | comparison | revisions
--- a/src/chess/game-info.c	Sat Apr 04 12:35:59 2026 +0200
+++ b/src/chess/game-info.c	Sat Apr 04 13:25:47 2026 +0200
@@ -48,10 +48,6 @@
 }
 
 void gamestate_cleanup(GameState *gamestate) {
-    MoveList *elem = gamestate->movelist;
-    while (elem) {
-        MoveList *cur = elem;
-        elem = elem->next;
-        free(cur);
-    }
+    free(gamestate->moves);
+    gamestate->movecount = gamestate->movecapacity = 0;
 }
\ No newline at end of file
--- a/src/chess/game-info.h	Sat Apr 04 12:35:59 2026 +0200
+++ b/src/chess/game-info.h	Sat Apr 04 13:25:47 2026 +0200
@@ -81,13 +81,6 @@
     char string[8];
 } Move;
 
-typedef struct MoveList MoveList;
-
-struct MoveList {
-    Move move;
-    MoveList* next;
-};
-
 typedef struct {
     uint8_t servercolor;
     uint8_t timecontrol;
@@ -97,9 +90,11 @@
 
 typedef struct {
     Board board;
-    MoveList* movelist;
-    MoveList* lastmove;
-    unsigned int movecount; /* number of (half-)moves (counting BOTH colors) */
+    Move* moves;
+    /** capacity of the move array */
+    unsigned movecapacity;
+    /** number of (half-)moves (counting BOTH colors) */
+    unsigned int movecount;
     bool checkmate;
     bool stalemate;
     bool remis;
@@ -110,6 +105,9 @@
 #define is_game_running(gamestate) !((gamestate)->checkmate || \
     (gamestate)->resign || (gamestate)->stalemate || (gamestate)->remis)
 
+#define last_move(gamestate) \
+    ((gamestate)->moves[(gamestate)->movecount-1])
+
 /**
  * Initializes a game state and prepares the chess board.
  * @param gamestate the game state to initialize
--- a/src/chess/king.c	Sat Apr 04 12:35:59 2026 +0200
+++ b/src/chess/king.c	Sat Apr 04 13:25:47 2026 +0200
@@ -33,13 +33,12 @@
 
 static bool king_castling_chkmoved(GameState *gamestate,
     uint8_t row, uint8_t file) {
-    
-    MoveList *ml = gamestate->movelist;
-    while (ml) {
-        if (ml->move.fromfile == file && ml->move.fromrow == row) {
+
+    for (unsigned i = 0; i < gamestate->movecount; i++) {
+        if (gamestate->moves[i].fromfile == file
+            && gamestate->moves[i].fromrow == row) {
             return true;
         }
-        ml = ml->next;
     }
     
     return false;
@@ -79,7 +78,7 @@
             blocked |= gamestate->board[move->torow][fileidx('b')];
         }
         uint8_t midfile = (move->tofile+move->fromfile)/2;
-        blocked |= gamestate->lastmove->move.check ||
+        blocked |= last_move(gamestate).check ||
             gamestate->board[move->torow][midfile] ||
             is_covered(gamestate, move->torow, midfile, opponent_color);
     }
--- a/src/chess/pgn.c	Sat Apr 04 12:35:59 2026 +0200
+++ b/src/chess/pgn.c	Sat Apr 04 13:25:47 2026 +0200
@@ -186,8 +186,8 @@
     if (gamestate->stalemate || gamestate->remis) {
         result = "1/2-1/2";
     } else if (gamestate->checkmate || gamestate->resign) {
-        if (gamestate->lastmove) {
-            result = (gamestate->lastmove->move.piece & COLOR_MASK) == WHITE ?
+        if (gamestate->movecount > 0) {
+            result = (last_move(gamestate).piece & COLOR_MASK) == WHITE ?
                 "1-0" : "0-1";
         } else {
             result = "0-1";
@@ -198,20 +198,18 @@
     fprintf(stream, "[Result \"%s\"]\n\n", result);
     
     /* moves */
-    int i = 1;
-    for (MoveList *movelist = gamestate->movelist ;
-        movelist ; movelist = movelist->next) {
+    for (unsigned i = 0 ; i < gamestate->movecount ; i++) {
         
-        if (++i % 2 == 0) {
-            fprintf(stream, "%d. %s", i/2, movelist->move.string);
+        if (i % 2 == 0) {
+            fprintf(stream, "%d. %s", 1+i/2, gamestate->moves[i].string);
         } else {
-            fprintf(stream, " %s", movelist->move.string);
+            fprintf(stream, "%s", gamestate->moves[i].string);
         }
         
         // TODO: move time and maybe other comments
         
-        /* line break every 10 moves */
-        if ((i-1) % 20)  {
+        /* line break every 10 half-moves */
+        if ((i+1) % 10)  {
             fputc(' ', stream);
         } else {
             fputc('\n', stream);
@@ -268,8 +266,8 @@
 }
 
 static size_t fen_color(char *str, GameState *gamestate) {
-    uint8_t color = opponent_color(gamestate->lastmove ?
-        (gamestate->lastmove->move.piece & COLOR_MASK) : BLACK);
+    uint8_t color = opponent_color(gamestate->movecount > 0 ?
+        (last_move(gamestate).piece & COLOR_MASK) : BLACK);
     
     str[0] = color == WHITE ? 'w' : 'b';
     
@@ -278,13 +276,12 @@
 
 static bool fen_castling_chkmoved(GameState *gamestate,
     uint8_t row, uint8_t file) {
-    
-    MoveList *ml = gamestate->movelist;
-    while (ml) {
-        if (ml->move.fromfile == file && ml->move.fromrow == row) {
+
+    for (unsigned i = 0 ; i < gamestate->movecount ; i++) {
+        if (gamestate->moves[i].fromfile == file
+            && gamestate->moves[i].fromrow == row) {
             return true;
         }
-        ml = ml->next;
     }
     
     return false;
@@ -335,37 +332,21 @@
 }
 
 static size_t fen_halfmove(char *str, GameState *gamestate) {
-    
-    unsigned int i = 0;
-    for (MoveList *move = gamestate->movelist ; move ; move = move->next) {
-        if (move->move.capture || (move->move.piece & PIECE_MASK) == PAWN) {
-            i = 0;
+    unsigned int hm = 0;
+    for (unsigned int i = 0; i < gamestate->movecount; i++) {
+        if (gamestate->moves[i].capture
+            || (gamestate->moves[i].piece & PIECE_MASK) == PAWN) {
+            hm = 0;
         } else {
-            i++;
+            hm++;
         }
     }
-    
-    char m[8];
-    size_t len = sprintf(m, "%u", i);
-    memcpy(str, m, len);
-    
-    return len;
+
+    return sprintf(str, "%u", hm);
 }
 
 static size_t fen_movenr(char *str, GameState *gamestate) {
-    
-    MoveList *move = gamestate->movelist;
-    unsigned int i = 1;
-    while (move) {
-        i++;
-        move = move->next;
-    }
-    
-    char m[8];
-    size_t len = sprintf(m, "%u", i);
-    memcpy(str, m, len);
-    
-    return len;
+    return sprintf(str, "%u", gamestate->movecount);
 }
 
 void compute_fen(char *str, GameState *gamestate) {
--- a/src/chess/rules.c	Sat Apr 04 12:35:59 2026 +0200
+++ b/src/chess/rules.c	Sat Apr 04 13:25:47 2026 +0200
@@ -41,11 +41,16 @@
 
 static GameState gamestate_copy_sim(GameState *gamestate) {
     GameState simulation = *gamestate;
-    simulation.movecount = 0; /* simulations do not count moves */
-    if (simulation.lastmove) {
-        MoveList *lastmovecopy = malloc(sizeof(MoveList));
-        *lastmovecopy = *(simulation.lastmove);
-        simulation.movelist = simulation.lastmove = lastmovecopy;
+
+    // create a new move list for the simulation
+    simulation.movecapacity = 4;
+    simulation.movecount = 0;
+    simulation.moves = malloc(4 * sizeof(Move));
+
+    // copy the most recent move if a move was played
+    if (gamestate->movecount > 0) {
+        simulation.moves[0] = last_move(gamestate);
+        simulation.movecount++;
     }
 
     return simulation;
@@ -138,39 +143,39 @@
     }
 }
 
-static void addmove(GameState* gamestate, Move *move) {
-    MoveList *elem = malloc(sizeof(MoveList));
-    elem->next = NULL;
-    elem->move = *move;
-    
+static void addmove(GameState* gamestate, Move *data) {
+    if (gamestate->movecount == gamestate->movecapacity) {
+        gamestate->movecapacity += 64; /* 32 more full moves */
+        gamestate->moves = realloc(gamestate->moves,
+            gamestate->movecapacity * sizeof(Move));
+    }
+
+    Move *move = &gamestate->moves[gamestate->movecount];
+    *move = *data;
+
     struct timeval curtimestamp;
     gettimeofday(&curtimestamp, NULL);
-    elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
-    elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
+    move->timestamp.tv_sec = curtimestamp.tv_sec;
+    move->timestamp.tv_usec = (int32_t) curtimestamp.tv_usec;
     
-    if (gamestate->lastmove) {
-        struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
-        uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
+    if (gamestate->movecount > 0) {
+        struct movetimeval lasttstamp = last_move(gamestate).timestamp;
+        uint64_t sec = curtimestamp.tv_sec - lasttstamp.tv_sec;
         suseconds_t micros;
-        if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
-            micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
+        if (curtimestamp.tv_usec < lasttstamp.tv_usec) {
+            micros = 1000000-(lasttstamp.tv_usec - curtimestamp.tv_usec);
             sec--;
         } else {
-            micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
+            micros = curtimestamp.tv_usec - lasttstamp.tv_usec;
         }
         
-        elem->move.movetime.tv_sec = sec;
-        elem->move.movetime.tv_usec = micros;
-        
-        gamestate->lastmove->next = elem;
-        gamestate->lastmove = elem;
-        gamestate->movecount++;
+        move->movetime.tv_sec = sec;
+        move->movetime.tv_usec = (int32_t) micros;
     } else {
-        elem->move.movetime.tv_usec = 0;
-        elem->move.movetime.tv_sec = 0;
-        gamestate->movelist = gamestate->lastmove = elem;
-        gamestate->movecount = 1;
+        move->movetime.tv_usec = 0;
+        move->movetime.tv_sec = 0;
     }
+    gamestate->movecount++;
 }
 
 char getpiecechr(uint8_t piece) {
@@ -367,7 +372,7 @@
             return KING_MOVES_INTO_CHECK;
         } else {
             /* last move is always not null in this case */
-            return gamestate->lastmove->move.check ?
+            return last_move(gamestate).check ?
                 KING_IN_CHECK : PIECE_PINNED;
         }
     }
@@ -569,7 +574,7 @@
 
     uint8_t color = move->piece & COLOR_MASK;
     uint8_t piece = move->piece & PIECE_MASK;
-    bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:false;
+    bool incheck = gamestate->movecount > 0 ? last_move(gamestate).check:false;
     
     Move threats[16], *threat = NULL;
     uint8_t threatcount;
@@ -768,35 +773,30 @@
         return 0;
     }
     
-    if (gamestate->movelist) {
+    if (gamestate->movecount > 0) {
         uint16_t time = gameinfo->time;
         suseconds_t micros = 0;
-        
-        MoveList *movelist = color == WHITE ?
-            gamestate->movelist : gamestate->movelist->next;
-        
-        while (movelist) {
+
+        for (unsigned i = color == WHITE ? 0 : 1
+                ; i < gamestate->movecount ; i += 2) {
             time += gameinfo->addtime;
             
-            struct movetimeval *movetime = &(movelist->move.movetime);
-            if (movetime->tv_sec >= time) {
+            if (gamestate->moves[i].movetime.tv_sec >= time) {
                 return 0;
             }
             
-            time -= movetime->tv_sec;
-            micros += movetime->tv_usec;
-            
-            movelist = movelist->next ? movelist->next->next : NULL;
+            time -= gamestate->moves[i].movetime.tv_sec;
+            micros += gamestate->moves[i].movetime.tv_usec;
         }
         
         time_t sec;
-        movelist = gamestate->lastmove;
-        if ((movelist->move.piece & COLOR_MASK) != color) {
-            struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
+        Move *lastmove = &last_move(gamestate);
+        if ((lastmove->piece & COLOR_MASK) != color) {
+            struct movetimeval lastmovetstamp = lastmove->timestamp;
             struct timeval currenttstamp;
             gettimeofday(&currenttstamp, NULL);
-            micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
-            sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
+            micros += currenttstamp.tv_usec - lastmovetstamp.tv_usec;
+            sec = currenttstamp.tv_sec - lastmovetstamp.tv_sec;
             if (sec >= time) {
                 return 0;
             }
@@ -804,7 +804,7 @@
             time -= sec;
         }
         
-        sec = micros / 1e6L;
+        sec = micros / 1000000;
         
         if (sec >= time) {
             return 0;
--- a/src/game.c	Sat Apr 04 12:35:59 2026 +0200
+++ b/src/game.c	Sat Apr 04 13:25:47 2026 +0200
@@ -124,31 +124,28 @@
     uint8_t logy = 2;
     const uint8_t logx = boardx + 28;
     move(logy, logx);
-    
+
+    /* count full moves */
     unsigned int logi = 0;
-    MoveList *logelem = gamestate->movelist;
-    
+
     /* wrap log after 45 moves */
     while (gamestate->movecount/6-logi/3 >= 15) {
-        logelem = logelem->next->next;
         logi++;
     }
     
-    while (logelem) {
-        bool iswhite = (logelem->move.piece & COLOR_MASK) == WHITE;
+    for (unsigned mi = logi*2 ; mi < gamestate->movecount ; mi++) {
+        bool iswhite = mi % 2 == 0;
         if (iswhite) {
             logi++;
             printw("%d. ", logi);
         }
 
-        addstr(logelem->move.string);
+        addstr(gamestate->moves[mi].string);
         if (!iswhite && logi%3 == 0) {
             move(++logy, logx);
         } else {
             addch(' ');
         }
-
-        logelem = logelem->next;
     }
 }
 
@@ -518,7 +515,7 @@
                 addstr("Game has ended. Use -S to analyze it.\n");
                 return;
             }
-            curcol = opponent_color(gamestate.lastmove->move.piece&COLOR_MASK);
+            curcol = opponent_color(last_move(&gamestate).piece&COLOR_MASK);
         } else {
             printw("Can't read PGN file (%s)\n", strerror(errno));
             return;
@@ -546,8 +543,8 @@
         mycolor = opponent_color(mycolor);
     }
     
-    bool myturn = (gamestate->lastmove ?
-        (gamestate->lastmove->move.piece & COLOR_MASK) : BLACK) != mycolor;
+    bool myturn = (gamestate->movecount > 0 ?
+        (last_move(gamestate).piece & COLOR_MASK) : BLACK) != mycolor;
     
     bool running;
     do {
@@ -586,19 +583,18 @@
 }
 
 void dump_moveinfo(GameState *gamestate) {
-    int i = 1;
-    for (MoveList *movelist = gamestate->movelist ;
-        movelist ; movelist = movelist->next) {
-        if (++i % 2 == 0) {
-            printw("%d. %s", i/2, movelist->move.string);
+    for (unsigned i = 0 ; i < gamestate->movecount ; i++) {
+        if (i % 2 == 0) {
+            printw("%d. %s", 1+i/2, gamestate->moves[i].string);
         } else {
-            printw(" %s", movelist->move.string);
+            printw("%s", gamestate->moves[i].string);
         }
-        if (i % 20)  {
+        // only five moves reliably fit into one screen row
+        if ((i+1) % 10)  {
             addch(' ');
         } else {
             addch('\n');
         }
-        }
+    }
     refresh();
 }
--- a/src/server.c	Sat Apr 04 12:35:59 2026 +0200
+++ b/src/server.c	Sat Apr 04 13:25:47 2026 +0200
@@ -129,29 +129,14 @@
     int fd = server.client->fd;
     if (settings->continuepgn) {
         /* Continue game, send PGN data */
-        uint16_t mc = 0;
-        MoveList *movelist = gamestate.movelist;
-        while (movelist) {
-            mc++;
-            movelist = movelist->next;
-        }
-        
-        Move* moves = calloc(mc, sizeof(Move));
-        
-        movelist = gamestate.movelist;
-        mc = 0;
-        while (movelist) {
-            moves[mc] = movelist->move;
-            mc++;
-            movelist = movelist->next;
-        }
-        
+        uint16_t mc = gamestate.movecount;
         size_t pgndata_size = sizeof(GameInfo)+sizeof(mc)+mc*sizeof(Move);
         char *pgndata = malloc(pgndata_size);
         memcpy(pgndata, &(settings->gameinfo), sizeof(GameInfo));
-        memcpy(pgndata+sizeof(GameInfo), &mc, sizeof(mc));
-        memcpy(pgndata+sizeof(GameInfo)+sizeof(mc), moves, mc*sizeof(Move));
-        free(moves);
+        unsigned offset = sizeof(GameInfo);
+        memcpy(pgndata+offset, &mc, sizeof(mc));
+        offset += sizeof(mc);
+        memcpy(pgndata+offset, gamestate.moves, mc*sizeof(Move));
         net_send_data(fd, NETCODE_PGNDATA, pgndata, pgndata_size);
         free(pgndata);
     } else {

mercurial