Thu, 06 Aug 2026 14:45:55 +0200
add functions to list the possible moves for each piece
resolves #952
| src/chess/bishop.c | file | annotate | diff | comparison | revisions | |
| src/chess/bishop.h | file | annotate | diff | comparison | revisions | |
| src/chess/king.c | file | annotate | diff | comparison | revisions | |
| src/chess/king.h | file | annotate | diff | comparison | revisions | |
| src/chess/knight.c | file | annotate | diff | comparison | revisions | |
| src/chess/knight.h | file | annotate | diff | comparison | revisions | |
| src/chess/pawn.c | file | annotate | diff | comparison | revisions | |
| src/chess/pawn.h | file | annotate | diff | comparison | revisions | |
| src/chess/queen.c | file | annotate | diff | comparison | revisions | |
| src/chess/queen.h | file | annotate | diff | comparison | revisions | |
| src/chess/rook.c | file | annotate | diff | comparison | revisions | |
| src/chess/rook.h | 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/bishop.c Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/bishop.c Thu Aug 06 14:45:55 2026 +0200 @@ -52,3 +52,35 @@ return false; } + +size_t bishop_moves(GameState *gamestate, Color c, Row r, File f, Move *moves) { + + size_t count = 0; + const int directions[4][2] = { + { 1, 1}, + { 1, -1}, + {-1, 1}, + {-1, -1} + }; + + for (size_t i = 0 ; i < 4 ; i++) { + int row = r + directions[i][0]; + int file = f + directions[i][1]; + + while (isidx(row) && isidx(file)) { + moves[count] = (Move){0}; + moves[count].piece = mkpiece(BISHOP, c); + moves[count].fromrow = r; + moves[count].fromfile = f; + moves[count].torow = row; + moves[count].tofile = file; + moves[count].capture = piece_at(gamestate, row, file) != 0; + count++; + + row += directions[i][0]; + file += directions[i][1]; + } + } + + return count; +}
--- a/src/chess/bishop.h Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/bishop.h Thu Aug 06 14:45:55 2026 +0200 @@ -39,6 +39,11 @@ bool bishop_chkrules(const Move *move); bool bishop_isblocked(const GameState *gamestate, const Move *move); +#define BISHOP_MOVES_MAX 13 +size_t bishop_moves(GameState *gamestate, Color c, Row r, File f, Move *moves); +#define bishop_moves_allowed(gamestate, color, row, file, moves) \ + filter_moves_allowed(gamestate, color, row, file, moves, bishop_moves) + #ifdef __cplusplus } #endif
--- a/src/chess/king.c Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/king.c Thu Aug 06 14:45:55 2026 +0200 @@ -30,8 +30,6 @@ #include "rules.h" #include "king.h" -#include <string.h> - static bool king_castling_chkmoved( const GameState *gamestate, Row row, File file) { @@ -89,3 +87,54 @@ return blocked; } + +size_t king_moves(GameState *gamestate, Color c, Row r, File f, Move *moves) { + + size_t count = 0; + Piece king = mkpiece(KING, c); + + for (int dr = -1 ; dr <= 1 ; dr++) { + for (int df = -1 ; df <= 1 ; df++) { + if (dr == 0 && df == 0) { + continue; + } + + Row torow = r + dr; + File tofile = f + df; + + if (!isidx(torow) || !isidx(tofile)) { + continue; + } + + moves[count] = (Move){0}; + moves[count].piece = king; + moves[count].fromrow = r; + moves[count].fromfile = f; + moves[count].torow = torow; + moves[count].tofile = tofile; + moves[count].capture = piece_at(gamestate, torow, tofile) != 0; + count++; + } + } + + Row homerow = c == WHITE ? 0 : 7; + if (r == homerow && f == fileidx('e')) { + moves[count] = (Move){0}; + moves[count].piece = king; + moves[count].fromrow = r; + moves[count].fromfile = f; + moves[count].torow = r; + moves[count].tofile = fileidx('c'); + count++; + + moves[count] = (Move){0}; + moves[count].piece = king; + moves[count].fromrow = r; + moves[count].fromfile = f; + moves[count].torow = r; + moves[count].tofile = fileidx('g'); + count++; + } + + return count; +}
--- a/src/chess/king.h Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/king.h Thu Aug 06 14:45:55 2026 +0200 @@ -40,6 +40,11 @@ bool king_chkrules(const GameState *gamestate, const Move *move); bool king_isblocked(const GameState *gamestate, const Move *move); +#define KING_MOVES_MAX 8 +size_t king_moves(GameState *gamestate, Color c, Row r, File f, Move *moves); +#define king_moves_allowed(gamestate, color, row, file, moves) \ + filter_moves_allowed(gamestate, color, row, file, moves, king_moves) + #ifdef __cplusplus } #endif
--- a/src/chess/knight.c Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/knight.c Thu Aug 06 14:45:55 2026 +0200 @@ -37,3 +37,36 @@ return (dx == 2 && dy == 1) || (dx == 1 && dy == 2); } + +size_t knight_moves(GameState *gamestate, Color c, Row r, File f, Move *moves) { + + size_t count = 0; + const int offsets[8][2] = { + { 2, 1}, + { 2, -1}, + {-2, 1}, + {-2, -1}, + { 1, 2}, + { 1, -2}, + {-1, 2}, + {-1, -2} + }; + + for (size_t i = 0 ; i < 8 ; i++) { + int row = r + offsets[i][0]; + int file = f + offsets[i][1]; + + if (isidx(row) && isidx(file)) { + moves[count] = (Move){0}; + moves[count].piece = mkpiece(KNIGHT, c); + moves[count].fromrow = r; + moves[count].fromfile = f; + moves[count].torow = row; + moves[count].tofile = file; + moves[count].capture = piece_at(gamestate, row, file) != 0; + count++; + } + } + + return count; +}
--- a/src/chess/knight.h Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/knight.h Thu Aug 06 14:45:55 2026 +0200 @@ -39,6 +39,11 @@ bool knight_chkrules(const Move *move); #define knight_isblocked(gs,m) false +#define KNIGHT_MOVES_MAX 8 +size_t knight_moves(GameState *gamestate, Color c, Row r, File f, Move *moves); +#define knight_moves_allowed(gamestate, color, row, file, moves) \ + filter_moves_allowed(gamestate, color, row, file, moves, knight_moves) + #ifdef __cplusplus } #endif
--- a/src/chess/pawn.c Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/pawn.c Thu Aug 06 14:45:55 2026 +0200 @@ -77,3 +77,43 @@ gamestate->board[(move->fromrow+move->torow)/2][move->tofile]; } } + +size_t pawn_moves(GameState *gamestate, Color c, Row r, File f, Move *moves) { + size_t count = 0; + int rowdelta = c == WHITE ? 1 : -1; + int promotionrow = c == WHITE ? 7 : 0; + int startrow = c == WHITE ? 1 : 6; + const int targets[4][3] = { + {r + rowdelta, f, 0}, + {r + rowdelta, f - 1, 1}, + {r + rowdelta, f + 1, 1}, + {r + rowdelta * 2, f, 0} + }; + + for (size_t i = 0 ; i < 4 ; i++) { + int row = targets[i][0]; + int file = targets[i][1]; + + if (i == 3 && r != startrow) { + continue; + } + + if (isidx(row) && isidx(file)) { + moves[count] = (Move){0}; + moves[count].piece = mkpiece(PAWN, c); + moves[count].fromrow = r; + moves[count].fromfile = f; + moves[count].torow = row; + moves[count].tofile = file; + moves[count].capture = targets[i][2]; + + if (row == promotionrow) { + moves[count].promotion = mkpiece(QUEEN, c); + } + + count++; + } + } + + return count; +}
--- a/src/chess/pawn.h Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/pawn.h Thu Aug 06 14:45:55 2026 +0200 @@ -39,6 +39,11 @@ bool pawn_chkrules(const GameState *gamestate, const Move *move); bool pawn_isblocked(const GameState *gamestate, const Move *move); +#define PAWN_MOVES_MAX 4 +size_t pawn_moves(GameState *gamestate, Color c, Row r, File f, Move *moves); +#define pawn_moves_allowed(gamestate, color, row, file, moves) \ + filter_moves_allowed(gamestate, color, row, file, moves, pawn_moves) + #ifdef __cplusplus } #endif
--- a/src/chess/queen.c Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/queen.c Thu Aug 06 14:45:55 2026 +0200 @@ -43,3 +43,38 @@ return bishop_isblocked(gamestate, move); } } + +size_t queen_moves(GameState *gamestate, Color c, Row r, File f, Move *moves) { + size_t count = 0; + const int directions[8][2] = { + { 1, 0}, + {-1, 0}, + { 0, 1}, + { 0, -1}, + { 1, 1}, + { 1, -1}, + {-1, 1}, + {-1, -1} + }; + + for (size_t i = 0 ; i < 8 ; i++) { + int row = r + directions[i][0]; + int file = f + directions[i][1]; + + while (isidx(row) && isidx(file)) { + moves[count] = (Move){0}; + moves[count].piece = mkpiece(QUEEN, c); + moves[count].fromrow = r; + moves[count].fromfile = f; + moves[count].torow = row; + moves[count].tofile = file; + moves[count].capture = piece_at(gamestate, row, file) != 0; + count++; + + row += directions[i][0]; + file += directions[i][1]; + } + } + + return count; +}
--- a/src/chess/queen.h Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/queen.h Thu Aug 06 14:45:55 2026 +0200 @@ -39,6 +39,11 @@ bool queen_chkrules(const Move *move); bool queen_isblocked(const GameState *gamestate, const Move *move); +#define QUEEN_MOVES_MAX 27 +size_t queen_moves(GameState *gamestate, Color c, Row r, File f, Move *moves); +#define queen_moves_allowed(gamestate, color, row, file, moves) \ + filter_moves_allowed(gamestate, color, row, file, moves, queen_moves) + #ifdef __cplusplus } #endif
--- a/src/chess/rook.c Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/rook.c Thu Aug 06 14:45:55 2026 +0200 @@ -58,3 +58,34 @@ return false; } + +size_t rook_moves(GameState *gamestate, Color c, Row r, File f, Move *moves) { + size_t count = 0; + const int directions[4][2] = { + { 1, 0}, + {-1, 0}, + { 0, 1}, + { 0, -1} + }; + + for (size_t i = 0 ; i < 4 ; i++) { + int row = r + directions[i][0]; + int file = f + directions[i][1]; + + while (isidx(row) && isidx(file)) { + moves[count] = (Move){0}; + moves[count].piece = mkpiece(ROOK, c); + moves[count].fromrow = r; + moves[count].fromfile = f; + moves[count].torow = row; + moves[count].tofile = file; + moves[count].capture = piece_at(gamestate, row, file) != 0; + count++; + + row += directions[i][0]; + file += directions[i][1]; + } + } + + return count; +}
--- a/src/chess/rook.h Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/rook.h Thu Aug 06 14:45:55 2026 +0200 @@ -39,6 +39,11 @@ bool rook_chkrules(const Move *move); bool rook_isblocked(const GameState *gamestate, const Move *move); +#define ROOK_MOVES_MAX 14 +size_t rook_moves(GameState *gamestate, Color c, Row r, File f, Move *moves); +#define rook_moves_allowed(gamestate, color, row, file, moves) \ + filter_moves_allowed(gamestate, color, row, file, moves, rook_moves) + #ifdef __cplusplus } #endif
--- a/src/chess/rules.c Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/rules.c Thu Aug 06 14:45:55 2026 +0200 @@ -1135,3 +1135,20 @@ // TODO: implement threefold repetition detection return false; } + +size_t filter_moves_allowed(GameState *gamestate, + Color c, Row r, File f, Move *moves, moves_generator_func func) { + + /* worst case: the queen has the most moves */ + Move candidates[QUEEN_MOVES_MAX]; + size_t candidatecount = func(gamestate, c, r, f, candidates); + size_t count = 0; + + for (size_t i = 0 ; i < candidatecount ; i++) { + if (validate_move(gamestate, &candidates[i]) == VALID_MOVE_SEMANTICS) { + moves[count++] = candidates[i]; + } + } + + return count; +} \ No newline at end of file
--- a/src/chess/rules.h Sun Aug 02 15:19:47 2026 +0200 +++ b/src/chess/rules.h Thu Aug 06 14:45:55 2026 +0200 @@ -287,6 +287,25 @@ piece_set(gamestate, row, file, 0); } +typedef size_t(*moves_generator_func)(GameState *gamestate, + Color c, Row r, File f, Move *moves); + +/** + * Internal function used to filter out illegal moves. + * + * Use the macros for the specific pieces instead. + * + * @param gamestate the current gamestate + * @param c color of the piece + * @param r the row of the piece + * @param f the file of the piece + * @param moves target array for the list of moves + * @param func a function that unconditionally generates the moves + * @return the number of moves stored in the @p moves array + */ +size_t filter_moves_allowed(GameState *gamestate, + Color c, Row r, File f, Move *moves, moves_generator_func func); + /** * Determines a list of theoretically possible moves to the specified field. *