fixed checkmate and completed implementation (more testing is still advised)

Mon, 07 Apr 2014 14:08:57 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 07 Apr 2014 14:08:57 +0200
changeset 29
c6a1ad6cf749
parent 28
0c1371488d87
child 30
a285ee393860

fixed checkmate and completed implementation (more testing is still advised)

src/chess/rules.c file | annotate | diff | comparison | revisions
src/chess/rules.h file | annotate | diff | comparison | revisions
src/main.c file | annotate | diff | comparison | revisions
--- a/src/chess/rules.c	Fri Apr 04 17:36:42 2014 +0200
+++ b/src/chess/rules.c	Mon Apr 07 14:08:57 2014 +0200
@@ -90,36 +90,6 @@
     }
 }
 
-_Bool get_any_threat_for(GameState *gamestate, uint8_t row, uint8_t file,
-        uint8_t color, Move *threat) {
-    Move threats[16];
-    int threatcount = 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) {
-                memset(&(threats[threatcount]), 0, sizeof(Move));
-                threats[threatcount].piece = gamestate->board[r][f];
-                threats[threatcount].fromrow = r;
-                threats[threatcount].fromfile = f;
-                threats[threatcount].torow = row;
-                threats[threatcount].tofile = file;
-                threatcount++;
-            }
-        }
-    }
-    
-    for (int i = 0 ; i < threatcount ; i++) {
-        if (validate_move(gamestate, &(threats[i]))) {
-            if (threat) {
-                *threat = threats[i];
-            }
-            return 1;
-        }
-    }
-    
-    return 0;
-}
-
 void apply_move(GameState *gamestate, Move *move) {
     uint8_t piece = move->piece & PIECE_MASK;
     uint8_t color = move->piece & COLOR_MASK;
@@ -167,9 +137,7 @@
     addmove(gamestate, move);
 }
 
-_Bool validate_move(GameState *gamestate, Move *move) {
-    _Bool result;
-    
+static _Bool validate_move_rules(GameState *gamestate, Move *move) {
     /* validate indices (don't trust opponent) */
     if (!chkidx(move)) {
         return 0;
@@ -181,50 +149,52 @@
     }
     
     /* does piece exist */
-    result = msrc(gamestate->board, move) == move->piece;
+    if (msrc(gamestate->board, move) != move->piece) {
+        return 0;
+    }
     
     /* can't capture own pieces */
-    uint8_t piececolor = (move->piece & COLOR_MASK);
-    if ((mdst(gamestate->board, move) & COLOR_MASK) == piececolor) {
+    if ((mdst(gamestate->board, move) & COLOR_MASK)
+        == (move->piece & COLOR_MASK)) {
         return 0;
     }
     
     /* validate individual rules */
     switch (move->piece & PIECE_MASK) {
     case PAWN:
-        result = result && pawn_chkrules(gamestate, move);
-        result = result && !pawn_isblocked(gamestate, move);
-        break;
+        return pawn_chkrules(gamestate, move) &&
+            !pawn_isblocked(gamestate, move);
     case ROOK:
-        result = result && rook_chkrules(move);
-        result = result && !rook_isblocked(gamestate, move);
-        break;
+        return rook_chkrules(move) &&
+            !rook_isblocked(gamestate, move);
     case KNIGHT:
-        result = result && knight_chkrules(move);
-        result = result && !knight_isblocked(gamestate, move);
-        break;
+        return knight_chkrules(move); /* knight is never blocked */
     case BISHOP:
-        result = result && bishop_chkrules(move);
-        result = result && !bishop_isblocked(gamestate, move);
-        break;
+        return bishop_chkrules(move) &&
+            !bishop_isblocked(gamestate, move);
     case QUEEN:
-        result = result && queen_chkrules(move);
-        result = result && !queen_isblocked(gamestate, move);
-        break;
+        return queen_chkrules(move) &&
+            !queen_isblocked(gamestate, move);
     case KING:
-        result = result && king_chkrules(gamestate, move);
-        result = result && !king_isblocked(gamestate, move);
-        break;
+        return king_chkrules(gamestate, move) &&
+            !king_isblocked(gamestate, move);
     default:
-        result = 0;
+        return 0;
     }
+}
+
+_Bool validate_move(GameState *gamestate, Move *move) {
     
-    /* cancel processing to avoid recursion overflow with is_covered() */
+    _Bool result = validate_move_rules(gamestate, move);
+    
+    /* cancel processing to save resources */
     if (!result) {
         return 0;
     }
     
     /* find kings for check validation */
+    uint8_t piececolor = (move->piece & COLOR_MASK);
+    
     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++) {
@@ -252,9 +222,10 @@
     }
     
     /* correct check and checkmate flags (move is still valid) */
-    Move threat;
-    move->check = get_any_threat_for(&simulation, opkingrow, opkingfile,
-        piececolor, &threat);
+    Move threats[16];
+    uint8_t threatcount;
+    move->check = get_threats(&simulation, opkingrow, opkingfile,
+        piececolor, threats, &threatcount);
     
     if (move->check) {
         /* determine possible escape fields */
@@ -273,17 +244,58 @@
                 }
             }
         }
-        /* can't escape, can we capture? */
-        if (!canescape) {
-            canescape = is_covered(&simulation, threat.fromrow,
-                threat.fromfile, opponent_color(piececolor));
+        /* can't escape, can he capture? */
+        if (!canescape && threatcount == 1) {
+            canescape = is_attacked(&simulation, threats[0].fromrow,
+                threats[0].fromfile, opponent_color(piececolor));
+        }
+        
+        /* can't capture, can he block? */
+        if (!canescape && threatcount == 1) {
+            Move *threat = &(threats[0]);
+            uint8_t threatpiece = threat->piece & PIECE_MASK;
+            
+            /* knight, pawns and the king cannot be blocked */
+            if (threatpiece == BISHOP || threatpiece == ROOK
+                || threatpiece == QUEEN) {
+                if (threat->fromrow == threat->torow) {
+                    /* rook aspect (on row) */
+                    int d = threat->tofile > threat->fromfile ? 1 : -1;
+                    uint8_t file = threat->fromfile;
+                    while (!canescape && file != threat->tofile - d) {
+                        file += d;
+                        canescape |= is_protected(&simulation,
+                            threat->torow, file, opponent_color(piececolor));
+                    }
+                } else if (threat->fromfile == threat->tofile) {
+                    /* rook aspect (on file) */
+                    int d = threat->torow > threat->fromrow ? 1 : -1;
+                    uint8_t row = threat->fromrow;
+                    while (!canescape && row != threat->torow - d) {
+                        row += d;
+                        canescape |= is_protected(&simulation,
+                            row, threat->tofile, opponent_color(piececolor));
+                    }
+                } else {
+                    /* bishop aspect */
+                    int dr = move->torow > move->fromrow ? 1 : -1;
+                    int df = move->tofile > move->fromfile ? 1 : -1;
 
-            /* can't capture, can we block? */
-            // TODO: make it so
+                    uint8_t row = move->fromrow;
+                    uint8_t file = move->fromfile;
+                    while (!canescape && file != move->tofile - df
+                        && row != move->torow - dr) {
+                        row += dr;
+                        file += df;
+                        canescape |= is_protected(&simulation, row, file,
+                            opponent_color(piececolor));
+                    }
+                }
+            }
+        }
             
-            if (!canescape) {
-                gamestate->checkmate = 1;
-            }
+        if (!canescape) {
+            gamestate->checkmate = 1;
         }
     }
     
@@ -418,3 +430,97 @@
         return INVALID_MOVE_SYNTAX;
     }
 }
+
+_Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
+        uint8_t color, Move *threats, uint8_t *threatcount) {
+    Move candidates[16];
+    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) {
+                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;
+                candidatecount++;
+            }
+        }
+    }
+
+    if (threatcount) {
+        *threatcount = 0;
+    }
+    
+    
+    _Bool result = 0;
+    
+    for (int i = 0 ; i < candidatecount ; i++) {
+        if (validate_move_rules(gamestate, &(candidates[i]))) {
+            result = 1;
+            if (threats && threatcount) {
+                threats[(*threatcount)++] = candidates[i];
+            }
+        }
+    }
+    
+    return result;
+}
+
+_Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
+        uint8_t color, Move *threats, uint8_t *threatcount) {
+
+    Move candidates[16];
+    uint8_t candidatecount;
+    if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
+        
+        if (threatcount) {
+            *threatcount = 0;
+        }
+        _Bool result = 0;
+        uint8_t kingfile = 0, kingrow = 0;
+        for (uint8_t row = 0 ; row < 8 ; row++) {
+            for (uint8_t file = 0 ; file < 8 ; file++) {
+                if ((gamestate->board[row][file] & COLOR_MASK) == color) {
+                    kingfile = file;
+                    kingrow = row;
+                }
+            }
+        }
+        
+        for (uint8_t i = 0 ; i < candidatecount ; i++) {
+            GameState simulation;
+            memcpy(&simulation, gamestate, sizeof(GameState));
+            apply_move(&simulation, &(candidates[i]));
+            if (!is_covered(&simulation, kingrow, kingfile,
+                    opponent_color(color))) {
+                result = 1;
+                if (threats && threatcount) {
+                    threats[(*threatcount)++] = candidates[i];
+                }
+            }
+        }
+        
+        return result;
+    } else {
+        return 0;
+    }
+}
+
+_Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
+        uint8_t color) {
+    
+    Move threats[16];
+    uint8_t threatcount;
+    if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
+        for (int i = 0 ; i < threatcount ; i++) {
+            if (threats[i].piece != (color|KING)) {
+                return 1;
+            }
+        }
+        return 0;
+    } else {
+        return 0;
+    }
+}
--- a/src/chess/rules.h	Fri Apr 04 17:36:42 2014 +0200
+++ b/src/chess/rules.h	Mon Apr 07 14:08:57 2014 +0200
@@ -145,20 +145,41 @@
 /**
  * Checks, if a specified field is covered by a piece of a certain color.
  * 
- * Note: when the field is covered by multiple pieces, this function returns
- * the first piece it can find.
+ * The out-parameters may both be NULL, but if any of them is set, the other
+ * must be set, too.
  * 
  * @param gamestate the current game state
  * @param row row of the field to check
  * @param file file of the field to check
  * @param color the color of the piece that should threaten the field
- * @param threat if not NULL: a pointer to the move structure where
- * the move that could be performed to capture the field should be stored
+ * @param threats the array where to store the threats (should be able to the
+ * rare maximum of 16 elements)
+ * @param threatcount a pointer to an uint8_t where to store the amount of threats
  * @return TRUE, if any piece of the specified color threatens the specified
  * field (i.e. could capture an opponent piece)
  */
-_Bool get_any_threat_for(GameState *gamestate, uint8_t row, uint8_t file,
-        uint8_t color, Move *threat);
+_Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
+        uint8_t color, Move* threats, uint8_t* threatcount);
+
+/**
+ * Checks, if a specified field is covered by a piece of a certain color AND
+ * if this piece is not pinned and therefore able to perform the move.
+ * 
+ * The out-parameters may both be NULL, but if any of them is set, the other
+ * must be set, too.
+ * 
+ * @param gamestate the current game state
+ * @param row row of the field to check
+ * @param file file of the field to check
+ * @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 the
+ * rare maximum of 16 elements)
+ * @param threatcount a pointer to an uint8_t where to store the amount of threats
+ * @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);
 
 /**
  * Checks, if a specified field is covered by a piece of a certain color.
@@ -168,10 +189,42 @@
  * @param file file of the field to check
  * @param color the color of the piece that should cover the field
  * @return TRUE, if any piece of the specified color threatens the specified
- * field (i.e. could capture an opponent piece)
+ * field
  */
 #define is_covered(gamestate, row, file, color) \
-    get_any_threat_for(gamestate, row, file, color, NULL)
+    get_threats(gamestate, row, file, color, NULL, NULL)
+
+/**
+ * Checks, if a specified field is attacked by a piece of a certain color.
+ * 
+ * I.e. the field is covered by a piece AND this piece is not pinned and
+ * therefore able to perform the move.
+ * 
+ * @param gamestate the current game state
+ * @param row row of the field to check
+ * @param file file of the field to check
+ * @param color the color of the piece that should cover the field
+ * @return TRUE, if any piece of the specified color threatens the specified
+ * field and could capture an opponent piece
+ */
+#define is_attacked(gamestate, row, file, color) \
+    get_threats(gamestate, row, file, color, NULL, NULL)
+
+/**
+ * Checks, if a specified field is protected by a piece of a certain color.
+ * 
+ * I.e. the field is covered by a piece that is NOT the king AND this piece is
+ * not pinned and therefore able to perform the move.
+ * 
+ * @param gamestate the current game state
+ * @param row row of the field to check
+ * @param file file of the field to check
+ * @param color the color of the piece that should cover the field
+ * @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);
 
 /**
  * Evaluates a move syntactically and stores the move data in the specified
--- a/src/main.c	Fri Apr 04 17:36:42 2014 +0200
+++ b/src/main.c	Mon Apr 07 14:08:57 2014 +0200
@@ -164,6 +164,7 @@
     }    
     initscr();
     cbreak();
+    keypad(stdscr, TRUE);
     if (has_colors()) {
         start_color();
         init_colorpairs();

mercurial