Mon, 07 Apr 2014 14:08:57 +0200
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