Wed, 29 Jul 2026 13:45:51 +0200
refactor rules.h / rules.c
- add meaningful type aliases
- add more useful functions and macros
- increase safety when working with flags and nibbles
| src/chess/pawn.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/pawn.c Tue Jul 28 13:44:10 2026 +0200 +++ b/src/chess/pawn.c Wed Jul 29 13:45:51 2026 +0200 @@ -31,12 +31,12 @@ #include "rules.h" bool pawn_chkrules(GameState *gamestate, Move *move) { - int8_t d = ((move->piece & COLOR_MASK) == WHITE ? -1 : 1); + int8_t d = piece_color(move->piece) == WHITE ? -1 : 1; if (move->torow == (d < 0 ? 7 : 0)) { if (move->promotion) { - uint8_t promopiece = move->promotion & PIECE_MASK; - if (!promopiece || promopiece == PAWN || promopiece == KING) { + unsigned ppiecetype = piece_type(move->promotion); + if (!ppiecetype || ppiecetype == PAWN || ppiecetype == KING) { return false; } } else { @@ -53,9 +53,8 @@ move->fromfile == move->tofile + 1 || move->fromfile == move->tofile - 1)) { - return mdst(gamestate->board, move) || - (gamestate->board[move->fromrow][move->tofile] - & ENPASSANT_THREAT); + return piece_at(gamestate, mdst(move)) || + enpassant_threat_exists(gamestate, move->fromrow, move->tofile); } else { return false; } @@ -72,9 +71,9 @@ bool pawn_isblocked(GameState *gamestate, Move *move) { if (move->torow == move->fromrow + 1 || move->torow == move->fromrow - 1) { - return mdst(gamestate->board, move) && !move->capture; + return piece_at(gamestate, mdst(move)) && !move->capture; } else { - return mdst(gamestate->board, move) || + return piece_at(gamestate, mdst(move)) || gamestate->board[(move->fromrow+move->torow)/2][move->tofile]; } }
--- a/src/chess/rules.c Tue Jul 28 13:44:10 2026 +0200 +++ b/src/chess/rules.c Wed Jul 29 13:45:51 2026 +0200 @@ -40,6 +40,7 @@ #include <stdio.h> #include <stdlib.h> #include <sys/time.h> +#include <assert.h> void gamestate_init(GameState *gamestate) { memset(gamestate, 0, sizeof(GameState)); @@ -101,7 +102,7 @@ return simulation; } -uint8_t current_color(GameState *gamestate) { +Color current_color(GameState *gamestate) { return (gamestate->movecount % 2 == 0) ? WHITE : BLACK; } @@ -113,7 +114,7 @@ memset(string, 0, 8); unsigned int idx; - if ((move->piece&PIECE_MASK) == KING && + if (piece_type(move->piece) == KING && abs(move->tofile-move->fromfile) == 2) { /* special formats for castling */ if (move->tofile==fileidx('c')) { @@ -129,19 +130,18 @@ idx = string[0] ? 1 : 0; /* find out how many source information we do need */ - uint8_t piece = move->piece & PIECE_MASK; - if (piece == PAWN) { + if (piece_type(move->piece) == PAWN) { if (move->capture) { string[idx++] = filechr(move->fromfile); } - } else if (piece != KING) { + } else if (piece_type(move->piece) != KING) { /* resolve ambiguities, if any */ Move threats[16]; - uint8_t threatcount; + size_t threatcount; if (get_threats(gamestate, move->torow, move->tofile, - move->piece&COLOR_MASK, threats, &threatcount)) { + piece_color(move->piece), threats, &threatcount)) { unsigned int ambrows = 0, ambfiles = 0, ambpiece = 0; - for (uint8_t i = 0 ; i < threatcount ; i++) { + for (size_t i = 0 ; i < threatcount ; i++) { if (threats[i].piece == move->piece) { ambpiece++; if (threats[i].fromrow == move->fromrow) { @@ -222,8 +222,8 @@ } } -char getpiecechr(uint8_t piece) { - switch (piece & PIECE_MASK) { +char getpiecechr(Piece piece) { + switch (piece_type(piece)) { case ROOK: return 'R'; case KNIGHT: return 'N'; case BISHOP: return 'B'; @@ -233,9 +233,9 @@ } } -char* getpieceunicode(uint8_t piece) { - if ((piece & COLOR_MASK) == WHITE) { - switch (piece & PIECE_MASK) { +char* getpieceunicode(Piece piece) { + if (piece_color(piece) == WHITE) { + switch (piece_type(piece)) { case PAWN: return "\u2659"; case ROOK: return "\u2656"; case KNIGHT: return "\u2658"; @@ -245,7 +245,7 @@ default: return ""; } } else { - switch (piece & PIECE_MASK) { + switch (piece_type(piece)) { case PAWN: return "\u265f"; case ROOK: return "\u265c"; case KNIGHT: return "\u265e"; @@ -257,13 +257,13 @@ } } -uint8_t getpiece(char c) { +Piece getpiece(char c, Color color) { switch (c) { - case 'R': return ROOK; - case 'N': return KNIGHT; - case 'B': return BISHOP; - case 'Q': return QUEEN; - case 'K': return KING; + case 'R': return mkpiece(ROOK, color); + case 'N': return mkpiece(KNIGHT, color); + case 'B': return mkpiece(BISHOP, color); + case 'Q': return mkpiece(QUEEN, color); + case 'K': return mkpiece(KING, color); default: return 0; } } @@ -276,45 +276,43 @@ format_move(gamestate, move); } } - - uint8_t piece = move->piece & PIECE_MASK; - uint8_t color = move->piece & COLOR_MASK; - + /* en passant capture */ - if (move->capture && piece == PAWN && - mdst(gamestate->board, move) == 0) { - gamestate->board[move->fromrow][move->tofile] = 0; + if (move->capture && piece_type(move->piece) == PAWN && + piece_at(gamestate, mdst(move)) == 0) { + piece_remove(gamestate, move->fromrow, move->tofile); } /* remove old en passant threats */ - for (uint8_t file = 0 ; file < 8 ; file++) { - gamestate->board[3][file] &= ~ENPASSANT_THREAT; - gamestate->board[4][file] &= ~ENPASSANT_THREAT; - } - - /* add new en passant threat */ - if (piece == PAWN && ( - (move->fromrow == 1 && move->torow == 3) || - (move->fromrow == 6 && move->torow == 4))) { - move->piece |= ENPASSANT_THREAT; + for (File file = 0 ; file < 8 ; file++) { + enpassant_threat_remove(gamestate, 3, file); + enpassant_threat_remove(gamestate, 4, file); } /* move (and maybe capture or promote) */ - msrc(gamestate->board, move) = 0; + piece_remove(gamestate, msrc(move)); if (move->promotion) { - mdst(gamestate->board, move) = move->promotion; + piece_set(gamestate, mdst(move), move->promotion); } else { - mdst(gamestate->board, move) = move->piece; + piece_set(gamestate, mdst(move), move->piece); + } + + /* add new en passant threat */ + if (piece_type(move->piece) == PAWN && ( + (move->fromrow == 1 && move->torow == 3) || + (move->fromrow == 6 && move->torow == 4))) { + enpassant_threat_add(gamestate, move->torow, move->tofile); } /* castling */ - if (piece == KING && move->fromfile == fileidx('e')) { + if (piece_type(move->piece) == KING && move->fromfile == fileidx('e')) { + const Color color = piece_color(move->piece); if (move->tofile == fileidx('g')) { gamestate->board[move->torow][fileidx('h')] = 0; - gamestate->board[move->torow][fileidx('f')] = color|ROOK; + gamestate->board[move->torow][fileidx('f')] = mkpiece(ROOK, color); } else if (move->tofile == fileidx('c')) { gamestate->board[move->torow][fileidx('a')] = 0; - gamestate->board[move->torow][fileidx('d')] = color|ROOK; + gamestate->board[move->torow][fileidx('d')] = mkpiece(ROOK, color); } } @@ -363,6 +361,8 @@ } static int validate_move_rules(GameState *gamestate, Move *move) { + assert((move->piece & ~(PIECE_MASK|COLOR_MASK)) == 0); + /* validate indices (don't trust opponent) */ if (!isidx(move->fromrow) || !isidx(move->fromfile) || !isidx(move->torow) || !isidx(move->tofile)) { @@ -375,31 +375,31 @@ } /* does piece exist */ - if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK)) - != (move->piece&(PIECE_MASK|COLOR_MASK))) { + if (piece_at(gamestate, msrc(move)) != move->piece) { return PIECE_NOT_FOUND; } + + /* is there any piece at the destination? */ + Piece piece_at_dst = piece_at(gamestate, mdst(move)); /* can't capture own pieces */ - if ((mdst(gamestate->board, move) & COLOR_MASK) - == (move->piece & COLOR_MASK)) { + if (piece_color(piece_at_dst) == piece_color(move->piece)) { return RULES_VIOLATED; } - + /* must capture, if and only if destination is occupied... */ - if ((mdst(gamestate->board, move) == 0 && move->capture) || - (mdst(gamestate->board, move) != 0 && !move->capture)) { + if (!((piece_at_dst == 0) ^ move->capture)) { /* ... or the capture happens en passant */ - if (!move->capture || (move->piece & PIECE_MASK) != PAWN || - !(gamestate->board[move->fromrow][move->tofile] - & ENPASSANT_THREAT)) { + if (!move->capture || piece_type(move->piece) != PAWN || + !enpassant_threat_exists(gamestate, + move->fromrow, move->tofile)) { return INVALID_MOVE_SYNTAX; } } /* validate individual rules */ bool chkrules; - switch (move->piece & PIECE_MASK) { + switch (piece_type(move->piece)) { case PAWN: chkrules = pawn_chkrules(gamestate, move) && !pawn_isblocked(gamestate, move); @@ -445,18 +445,18 @@ apply_move_impl(&simulation, &simmove, true); /* find kings for check validation */ - uint8_t piececolor = (move->piece & COLOR_MASK); - uint8_t oppcolor = opponent_color(piececolor); + Color piececolor = piece_color(move->piece); + Color oppcolor = opponent_color(piececolor); - uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0; - for (uint8_t row = 0 ; row < 8 ; row++) { - for (uint8_t file = 0 ; file < 8 ; file++) { - if (simulation.board[row][file] == - (piececolor == WHITE?WKING:BKING)) { + File mykingfile = 0, opkingfile = 0; + Row mykingrow = 0, opkingrow = 0; + for (Row row = 0 ; row < 8 ; row++) { + for (File file = 0 ; file < 8 ; file++) { + Piece p = piece_at(&simulation, row, file); + if (p == mkpiece(KING, piececolor)) { mykingfile = file; mykingrow = row; - } else if (simulation.board[row][file] == - (piececolor == WHITE?BKING:WKING)) { + } else if (p == mkpiece(KING, oppcolor)) { opkingfile = file; opkingrow = row; } @@ -466,7 +466,7 @@ /* don't move into or stay in check position */ if (is_covered(&simulation, mykingrow, mykingfile, oppcolor)) { gamestate_cleanup(&simulation); - if ((move->piece & PIECE_MASK) == KING) { + if (piece_type(move->piece) == KING) { return KING_MOVES_INTO_CHECK; } else { /* last move is always not null in this case */ @@ -477,7 +477,7 @@ /* correct check and checkmate flags (move is still valid) */ Move threats[16]; - uint8_t threatcount; + size_t threatcount; move->check = get_threats(&simulation, opkingrow, opkingfile, piececolor, threats, &threatcount); @@ -489,23 +489,23 @@ if (dr == 0 && df == 0) continue; if (!isidx(opkingrow + dr)) continue; if (!isidx(opkingfile + df)) continue; - uint8_t er = opkingrow + dr; - uint8_t ef = opkingfile + df; + Row er = opkingrow + dr; + File ef = opkingfile + df; /* check if escape field is already covered (threatened) */ if (is_covered(&simulation, er, ef, piececolor)) continue; /* check if piece of the king's color blocks the field */ - if ((simulation.board[er][ef] & COLOR_MASK) == oppcolor) + if (piece_color(simulation.board[er][ef]) == oppcolor) continue; /* check if an attacking piece blocks the field */ - if ((simulation.board[er][ef] & COLOR_MASK) == piececolor) { + if (piece_color(simulation.board[er][ef]) == piececolor) { /* test if the king can fight back */ GameState sim_retaliate = gamestate_copy_sim(&simulation); Move move_retaliate = {0}; - move_retaliate.piece = (oppcolor|KING); + move_retaliate.piece = mkpiece(KING, oppcolor); move_retaliate.fromrow = opkingrow; move_retaliate.fromfile = opkingfile; move_retaliate.torow = er; @@ -530,15 +530,14 @@ /* can't capture, can he block? */ if (!canescape && threatcount == 1) { Move *threat = &(threats[0]); - uint8_t threatpiece = threat->piece & PIECE_MASK; + unsigned tptype = piece_type(threat->piece); /* knight, pawns and the king cannot be blocked */ - if (threatpiece == BISHOP || threatpiece == ROOK - || threatpiece == QUEEN) { + if (tptype == BISHOP || tptype == ROOK || tptype == QUEEN) { if (threat->fromrow == threat->torow) { /* rook aspect (on row) */ int d = threat->tofile > threat->fromfile ? 1 : -1; - uint8_t file = threat->fromfile; + File file = threat->fromfile; while (!canescape && file != threat->tofile - d) { file += d; canescape |= is_protected(&simulation, @@ -547,7 +546,7 @@ } else if (threat->fromfile == threat->tofile) { /* rook aspect (on file) */ int d = threat->torow > threat->fromrow ? 1 : -1; - uint8_t row = threat->fromrow; + Row row = threat->fromrow; while (!canescape && row != threat->torow - d) { row += d; canescape |= is_protected(&simulation, @@ -558,10 +557,10 @@ int dr = threat->torow > threat->fromrow ? 1 : -1; int df = threat->tofile > threat->fromfile ? 1 : -1; - uint8_t row = threat->fromrow; - uint8_t file = threat->fromfile; + Row row = threat->fromrow; + File file = threat->fromfile; while (!canescape && file != threat->tofile - df - && row != threat->torow - dr) { + && row != threat->torow - dr) { row += dr; file += df; canescape |= is_protected(&simulation, row, file, @@ -581,32 +580,40 @@ return VALID_MOVE_SEMANTICS; } -bool get_threats(GameState *gamestate, uint8_t row, uint8_t file, - uint8_t color, Move *threats, uint8_t *threatcount) { +Piece piece_at(const GameState *gamestate, Row row, File file) { + return gamestate->board[row][file] & (PIECE_MASK|COLOR_MASK); +} + +void piece_set(GameState *gamestate, Row row, File file, Piece piece) { + gamestate->board[row][file] = piece; +} + +bool get_threats(GameState *gamestate, Row row, File file, + Color color, Move *threats, size_t *threatcount) { Move candidates[32]; - int candidatecount = 0; - for (uint8_t r = 0 ; r < 8 ; r++) { - for (uint8_t f = 0 ; f < 8 ; f++) { - if ((gamestate->board[r][f] & COLOR_MASK) == color) { + size_t ccount = 0; + for (Row r = 0 ; r < 8 ; r++) { + for (File f = 0 ; f < 8 ; f++) { + if (piece_color(gamestate->board[r][f]) == color) { /* non-capturing move */ - memset(&(candidates[candidatecount]), 0, sizeof(Move)); - candidates[candidatecount].piece = gamestate->board[r][f]; - candidates[candidatecount].fromrow = r; - candidates[candidatecount].fromfile = f; - candidates[candidatecount].torow = row; - candidates[candidatecount].tofile = file; - if ((gamestate->board[r][f]&PIECE_MASK) == PAWN - && (row == 0 || row == 7)) { + memset(&(candidates[ccount]), 0, sizeof(Move)); + Piece p = piece_at(gamestate, r, f); + candidates[ccount].piece = p; + candidates[ccount].fromrow = r; + candidates[ccount].fromfile = f; + candidates[ccount].torow = row; + candidates[ccount].tofile = file; + if (piece_type(p) == PAWN && (row == 0 || row == 7)) { /* the exact piece for promotion does not matter */ - candidates[candidatecount].promotion = color|QUEEN; + candidates[ccount].promotion = mkpiece(QUEEN, color); } - candidatecount++; + ccount++; /* capturing move */ - memcpy(&(candidates[candidatecount]), - &(candidates[candidatecount-1]), sizeof(Move)); - candidates[candidatecount].capture = 1; - candidatecount++; + memcpy(&(candidates[ccount]), + &(candidates[ccount-1]), sizeof(Move)); + candidates[ccount].capture = 1; + ccount++; } } } @@ -618,7 +625,7 @@ bool result = false; - for (int i = 0 ; i < candidatecount ; i++) { + for (size_t i = 0 ; i < ccount ; i++) { if (validate_move_rules(gamestate, &(candidates[i])) == VALID_MOVE_SEMANTICS) { result = true; @@ -632,15 +639,16 @@ } bool is_pinned(GameState *gamestate, Move *move) { - uint8_t color = move->piece & COLOR_MASK; + Color color = piece_color(move->piece); GameState simulation = gamestate_copy_sim(gamestate); Move simmove = *move; apply_move(&simulation, &simmove); - uint8_t kingfile = 0, kingrow = 0; - for (uint8_t row = 0 ; row < 8 ; row++) { - for (uint8_t file = 0 ; file < 8 ; file++) { + File kingfile = 0; + Row kingrow = 0; + for (Row row = 0 ; row < 8 ; row++) { + for (File file = 0 ; file < 8 ; file++) { if (simulation.board[row][file] == (color|KING)) { kingfile = file; kingrow = row; @@ -655,21 +663,22 @@ return covered; } -bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file, - uint8_t color, Move *threats, uint8_t *threatcount) { +bool get_real_threats(GameState *gamestate, Row row, File file, + Color color, Move *threats, size_t *threatcount) { if (threatcount) { *threatcount = 0; } Move candidates[16]; - uint8_t candidatecount; + size_t candidatecount; if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) { bool result = false; - uint8_t kingfile = 0, kingrow = 0; - for (uint8_t r = 0 ; r < 8 ; r++) { - for (uint8_t f = 0 ; f < 8 ; f++) { + File kingfile = 0; + Row kingrow = 0; + for (Row r = 0 ; r < 8 ; r++) { + for (File f = 0 ; f < 8 ; f++) { if (gamestate->board[r][f] == (color|KING)) { kingfile = f; kingrow = r; @@ -677,7 +686,7 @@ } } - for (uint8_t i = 0 ; i < candidatecount ; i++) { + for (size_t i = 0 ; i < candidatecount ; i++) { // TODO: check if we really need full-blown simulations here GameState simulation = gamestate_copy_sim(gamestate); Move simmove = candidates[i]; @@ -700,12 +709,11 @@ static int getlocation(GameState *gamestate, Move *move) { - uint8_t color = move->piece & COLOR_MASK; - uint8_t piece = move->piece & PIECE_MASK; + Color color = piece_color(move->piece); bool incheck = gamestate->movecount > 0 ? last_move(gamestate).check:false; Move threats[16], *threat = NULL; - uint8_t threatcount; + size_t threatcount; /* determine all threats and sort out the unreal threats on our own */ if (get_threats(gamestate, move->torow, move->tofile, color, @@ -714,7 +722,7 @@ bool found = false; /* find threats for the specified position */ - for (uint8_t i = 0 ; i < threatcount ; i++) { + for (size_t i = 0 ; i < threatcount ; i++) { if (threats[i].piece == move->piece && (move->fromrow == POS_UNSPECIFIED || move->fromrow == threats[i].fromrow) && @@ -739,7 +747,7 @@ /* can't threaten specified position */ if (!threat) { if (found) { - if (piece == KING) { + if (piece_type(move->piece) == KING) { return KING_MOVES_INTO_CHECK; } else if (incheck) { return KING_IN_CHECK; @@ -760,7 +768,7 @@ } } -static int eval_move1(const char *pstr, Move *move, uint8_t color) { +static int eval_move1(const char *pstr, Move *move, Color color) { memset(move, 0, sizeof(Move)); move->fromfile = POS_UNSPECIFIED; move->fromrow = POS_UNSPECIFIED; @@ -780,11 +788,10 @@ /* evaluate promotion */ if (len > 3 && mstr[len-2] == '=') { - move->promotion = getpiece(mstr[len-1]); + move->promotion = getpiece(mstr[len-1], color); if (!move->promotion) { return INVALID_MOVE_SYNTAX; } else { - move->promotion |= color; len -= 2; mstr[len] = 0; } @@ -792,26 +799,26 @@ if (len == 2) { /* pawn move (e.g. "e4") */ - move->piece = PAWN; + move->piece = mkpiece(PAWN, color); move->tofile = fileidx(mstr[0]); move->torow = rowidx(mstr[1]); } else if (len == 3) { if (strcmp(mstr, "O-O") == 0) { /* king side castling */ - move->piece = KING; + move->piece = mkpiece(KING, color); move->fromfile = fileidx('e'); move->tofile = fileidx('g'); move->fromrow = move->torow = color == WHITE ? 0 : 7; } else { /* move (e.g. "Nf3") */ - move->piece = getpiece(mstr[0]); + move->piece = getpiece(mstr[0], color); move->tofile = fileidx(mstr[1]); move->torow = rowidx(mstr[2]); } } else if (len == 4) { - move->piece = getpiece(mstr[0]); - if (!move->piece) { - move->piece = PAWN; + move->piece = getpiece(mstr[0], color); + if (move->piece == 0) { + move->piece = mkpiece(PAWN, color); move->fromfile = fileidx(mstr[0]); } if (mstr[1] == 'x') { @@ -821,7 +828,9 @@ /* move (e.g. "Ndf3", "N2c3", "e2e4") */ if (isfile(mstr[1])) { move->fromfile = fileidx(mstr[1]); - if (move->piece == PAWN) { + /* when the piece is a pawn, second char cannot be a file */ + if (piece_type(move->piece) == PAWN) { + /* invalidate the result */ move->piece = 0; } } else { @@ -833,12 +842,12 @@ } else if (len == 5) { if (strcmp(mstr, "O-O-O") == 0) { /* queen side castling "O-O-O" */ - move->piece = KING; + move->piece = mkpiece(KING, color); move->fromfile = fileidx('e'); move->tofile = fileidx('c'); move->fromrow = move->torow = color == WHITE ? 0 : 7; } else { - move->piece = getpiece(mstr[0]); + move->piece = getpiece(mstr[0], color); if (mstr[2] == 'x') { move->capture = 1; if (move->piece) { @@ -852,7 +861,7 @@ } } else { /* long notation capture (e.g. "e5xf6") */ - move->piece = PAWN; + move->piece = mkpiece(PAWN, color); move->fromfile = fileidx(mstr[0]); move->fromrow = rowidx(mstr[1]); } @@ -868,7 +877,7 @@ /* long notation capture (e.g. "Nc5xf3") */ if (mstr[3] == 'x') { move->capture = 1; - move->piece = getpiece(mstr[0]); + move->piece = getpiece(mstr[0], color); move->fromfile = fileidx(mstr[1]); move->fromrow = rowidx(mstr[2]); move->tofile = fileidx(mstr[4]); @@ -881,14 +890,12 @@ return INVALID_MOVE_SYNTAX; } - if (move->piece == PAWN - && move->torow == (color==WHITE?7:0) - && !move->promotion) { + if (piece_type(move->piece) == PAWN + && move->torow == (color==WHITE?7:0) + && !move->promotion) { return NEED_PROMOTION; } - move->piece |= color; - /* up to this point * destination indices must be specified and valid * source indices must either be valid or unspecified @@ -902,7 +909,7 @@ } int eval_move(GameState *gamestate, const char *mstr, - Move *move, uint8_t color) { + Move *move, Color color) { int result = eval_move1(mstr, move, color); if (result == VALID_MOVE_SYNTAX) { if (move->fromfile == POS_UNSPECIFIED @@ -913,18 +920,16 @@ return result; } -int check_move(const char *mstr, uint8_t color) { +int check_move(const char *mstr, Color color) { Move move; return eval_move1(mstr, &move, color); } -bool is_protected(GameState *gamestate, uint8_t row, uint8_t file, - uint8_t color) { - +bool is_protected(GameState *gamestate, Row row, File file, Color color) { Move threats[16]; - uint8_t threatcount; + size_t threatcount; if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) { - for (int i = 0 ; i < threatcount ; i++) { + for (size_t i = 0 ; i < threatcount ; i++) { if (threats[i].piece != (color|KING)) { return true; } @@ -935,7 +940,7 @@ } } -uint16_t remaining_movetime(GameState *gamestate, uint8_t color) { +uint16_t remaining_movetime(GameState *gamestate, Color color) { unsigned move_number = gamestate->movecount; if (color == BLACK) { move_number |= 1;
--- a/src/chess/rules.h Tue Jul 28 13:44:10 2026 +0200 +++ b/src/chess/rules.h Wed Jul 29 13:45:51 2026 +0200 @@ -30,6 +30,7 @@ #ifndef RULES_H #define RULES_H +#include <stdlib.h> #include <stdint.h> #include <stdbool.h> @@ -44,21 +45,21 @@ #define KING_MOVES_INTO_CHECK 7 #define RULES_VIOLATED 10 -#define ENPASSANT_THREAT 0x40 +#define ENPASSANT_THREAT 0x40u -#define WHITE 0x10 -#define BLACK 0x20 +#define WHITE 0x10u +#define BLACK 0x20u #define opponent_color(color) ((color)==WHITE?BLACK:WHITE) -#define PIECE_MASK 0x0F -#define COLOR_MASK 0x30 +#define PIECE_MASK 0x0Fu +#define COLOR_MASK 0x30u -#define PAWN 0x01 -#define ROOK 0x02 -#define KNIGHT 0x03 -#define BISHOP 0x04 -#define QUEEN 0x05 -#define KING 0x06 +#define PAWN 0x01u +#define ROOK 0x02u +#define KNIGHT 0x03u +#define BISHOP 0x04u +#define QUEEN 0x05u +#define KING 0x06u #define WPAWN (WHITE|PAWN) #define WROOK (WHITE|ROOK) @@ -74,6 +75,11 @@ #define BKING (BLACK|KING) typedef uint8_t Board[8][8]; +typedef uint8_t BoardIndex; +typedef BoardIndex Row; +typedef BoardIndex File; +typedef uint8_t Piece; +typedef uint8_t Color; struct movetimeval { uint64_t tv_sec; @@ -81,11 +87,11 @@ }; typedef struct { - uint8_t piece; - uint8_t fromfile; - uint8_t fromrow; - uint8_t tofile; - uint8_t torow; + Piece piece; + File fromfile; + Row fromrow; + File tofile; + Row torow; uint8_t promotion; uint8_t check; uint8_t capture; @@ -140,6 +146,17 @@ bool review; } GameState; +#define piece_type(piece) ((piece)&PIECE_MASK) +#define piece_color(piece) ((piece)&COLOR_MASK) +#define mkpiece(type,color) ((type)|(color)) + +#define enpassant_threat_add(gamestate, row, file) \ + (gamestate->board[row][file] |= ENPASSANT_THREAT) +#define enpassant_threat_remove(gamestate, row, file) \ + (gamestate->board[row][file] &= ~ENPASSANT_THREAT) +#define enpassant_threat_exists(gamestate, row, file) \ + (gamestate->board[row][file] & ENPASSANT_THREAT) + #define is_game_running(gamestate) !((gamestate)->checkmate || \ (gamestate)->wresign || (gamestate)->bresign || (gamestate)->threefold || \ (gamestate)->stalemate || (gamestate)->remis || (gamestate)->review) @@ -148,13 +165,13 @@ ((gamestate)->moves[(gamestate)->movecount-1]) #define POS_UNSPECIFIED UINT8_MAX -#define mdst(b,m) b[(m)->torow][(m)->tofile] -#define msrc(b,m) b[(m)->fromrow][(m)->fromfile] +#define mdst(m) (m)->torow, (m)->tofile +#define msrc(m) (m)->fromrow, (m)->fromfile /** Checks if the index is specified and valid. */ -#define isidx(idx) ((uint8_t)(idx) < 8) +#define isidx(idx) ((BoardIndex)(idx) < 8) /** Checks if the index is unspecified or valid. */ -#define isidxr(idx) ((idx) == POS_UNSPECIFIED || (uint8_t)(idx) < 8) +#define isidxr(idx) ((idx) == POS_UNSPECIFIED || (BoardIndex)(idx) < 8) #define isfile(file) (file >= 'a' && file <= 'h') #define isrow(row) (row >= '1' && row <= '8') @@ -169,7 +186,6 @@ #define fileidx_s(c) (isfile(c)?fileidx(c):POS_UNSPECIFIED) #define rowidx_s(c) (isrow(c)?rowidx(c):POS_UNSPECIFIED) - /** * Initializes a game state and prepares the chess board. * @param gamestate the game state to initialize @@ -186,21 +202,22 @@ * Maps a character to a piece. * * Does not work for pawns, since they don't have a character. - * + * * @param c one of R,N,B,Q,K - * @return numeric value for the specified piece + * @param color the piece color + * @return the specified piece or zero when the character is invalid */ -uint8_t getpiece(char c); +Piece getpiece(char c, Color color); /** * Maps a piece to a character. * - * Does not work for pawns, scince they don't have a character. + * Does not work for pawns, since they don't have a character. * * @param piece may have color or additional flags * @return character value for the specified piece */ -char getpiecechr(uint8_t piece); +char getpiecechr(Piece piece); /** * Maps a piece to a unicode character sequence. @@ -208,10 +225,10 @@ * The returned unicode is for black pieces. * You may colorize the output by setting the terminal foreground color. * - * @param piece the piece to dispaly + * @param piece the piece to display * @return unicode character sequence for the specified piece */ -char* getpieceunicode(uint8_t piece); +char* getpieceunicode(Piece piece); /** * Returns the color of the player who is next to move. @@ -219,7 +236,38 @@ * @param gamestate the current game state * @return the color of the player who is next to move */ -uint8_t current_color(GameState *gamestate); +Color current_color(GameState *gamestate); + +/** + * Returns the piece at the specified position. + * + * @param gamestate the current game state + * @param row the row + * @param file the file + * @return the piece at the specified position + */ +Piece piece_at(const GameState *gamestate, Row row, File file); + +/** + * Places a piece at the specified position in the current game state. + * + * @param gamestate the current game state + * @param row the row + * @param file the file + * @param piece the piece to place at the specified position + */ +void piece_set(GameState *gamestate, Row row, File file, Piece piece); + +/** + * Removes the piece at the specified position in the current game state. + * + * @param gamestate the current game state + * @param row the row + * @param file the file + */ +static inline void piece_remove(GameState *gamestate, Row row, File file) { + piece_set(gamestate, row, file, 0); +} /** * Checks, if a specified field is covered by a piece of a certain color. @@ -233,13 +281,12 @@ * @param color the color of the piece that should threaten the field * @param threats the array where to store the threats (should be able to hold * the rare maximum of 16 elements) - * @param threatcount a pointer to an uint8_t where the count of threats is - * stored + * @param threatcount a pointer where the count of threats is stored * @return true, if any piece of the specified color threatens the specified * field (i.e. could capture an opponent piece) */ -bool get_threats(GameState *gamestate, uint8_t row, uint8_t file, - uint8_t color, Move* threats, uint8_t* threatcount); +bool get_threats(GameState *gamestate, Row row, File file, + Color color, Move* threats, size_t* threatcount); /** * Checks, if a specified field is covered by a piece of a certain color AND @@ -254,13 +301,12 @@ * @param color the color of the piece that should threaten the field * @param threats the array where to store the threats (should be able to hold * the rare maximum of 16 elements) - * @param threatcount a pointer to an uint8_t where the count of threats is - * stored + * @param threatcount a pointer where the count of threats is stored * @return true, if any piece of the specified color threatens the specified * field (i.e. could capture an opponent piece) */ -bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file, - uint8_t color, Move* threats, uint8_t* threatcount); +bool get_real_threats(GameState *gamestate, Row row, File file, + Color color, Move* threats, size_t* threatcount); /** * Checks, if a specified field is covered by a piece of a certain color. @@ -304,8 +350,7 @@ * @return true, if any piece (excluding the king) of the specified color * threatens the specified field and could capture an opponent piece */ -bool is_protected(GameState *gamestate, uint8_t row, uint8_t file, - uint8_t color); +bool is_protected(GameState *gamestate, Row row, File file, Color color); /** * Checks, if the specified move cannot be performed, because the piece is @@ -339,8 +384,7 @@ * @param color the color of the player to evaluate the move for * @return status code (see macros in this file for the list of codes) */ -int eval_move(GameState *gamestate, const char *mstr, - Move *move, uint8_t color); +int eval_move(GameState *gamestate, const char *mstr, Move *move, Color color); /** * Syntactically checks a move without verifying that a piece exists that is @@ -350,7 +394,7 @@ * @param color the color of the player to evaluate the move for * @return status code (see macros in this file for the list of codes) */ -int check_move(const char *mstr, uint8_t color); +int check_move(const char *mstr, Color color); /** * Validates move by applying chess rules. @@ -399,7 +443,7 @@ * @return the remaining time - if time control is disabled, this function * always returns zero */ -uint16_t remaining_movetime(GameState *gamestate, uint8_t color); +uint16_t remaining_movetime(GameState *gamestate, Color color); /** * Converts clock time to string.