refactor rules.h / rules.c default tip

Wed, 29 Jul 2026 13:45:51 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 29 Jul 2026 13:45:51 +0200
changeset 160
f87832cba8b8
parent 159
07c7be5661f4

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.

mercurial