Sat, 04 Apr 2026 13:25:47 +0200
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(¤ttstamp, 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 {