diff options
| author | Aargh Rai <aargh.rai+git@gmail.com> | 2026-07-16 14:08:24 +0530 |
|---|---|---|
| committer | Aargh Rai <aargh.rai+git@gmail.com> | 2026-07-16 14:08:24 +0530 |
| commit | 1a52152852ff29c727673fc80e8bf8eca5ed6365 (patch) | |
| tree | e719771aa1b23f0b8774814ee907057f86f1dda1 | |
| parent | 6ac448727e1e26c78055eaaa361217f64393d808 (diff) | |
starting pos, move count, test pass
| -rw-r--r-- | Plan.md | 100 | ||||
| -rw-r--r-- | build.c | 2 | ||||
| -rw-r--r-- | include/engine/moves.h | 10 | ||||
| -rw-r--r-- | justfile | 4 | ||||
| -rw-r--r-- | resources/bitboards.html | 111 | ||||
| -rw-r--r-- | src/engine/moves.c | 250 | ||||
| -rw-r--r-- | src/engine/moves/attack.c | 154 | ||||
| -rw-r--r-- | src/engine/moves/king.c | 47 | ||||
| -rw-r--r-- | src/engine/moves/vec.c | 19 | ||||
| -rw-r--r-- | src/uci/command.c | 31 | ||||
| -rw-r--r-- | tests/generated.c | 23 | ||||
| -rw-r--r-- | tests/main.c | 2 |
12 files changed, 671 insertions, 82 deletions
@@ -0,0 +1,100 @@ +# Gacrux: Pseudo-Legal to Legal Move Conversion Plan + +## Current State +- Move generators produce **pseudo-legal** moves (follow piece movement rules but ignore checks/pins) +- `position_make_move()` has multiple bugs +- No attack detection or legal move filtering exists +- Perft test conditions are inverted + +## Phase 1: Fix Existing Bugs + +### `src/engine/moves.c` — `position_make_move()` +1. **Lines 94, 100**: Add missing semicolons after `assert(0)` +2. **Lines 54-55**: Black long castle modifies `WHITE_ROOK` → should be `BLACK_ROOK` +3. **Lines 71-73**: Black short castle modifies `WHITE_KING`/`WHITE_ROOK` → should be `BLACK_KING`/`BLACK_ROOK` +4. **Lines 96, 98**: `move.from` (u8) compared against `u64` bitboard constants → use square indices 63 and 56 +5. **Missing turn toggle**: `position->turn` never flips after a move + +### `test_perft_starting_position` +6. **Lines 169-176**: `==` → `!=` (test currently passes when counts are wrong) + +## Phase 2: Attack Detection + +Create `src/engine/moves/attack.c` with: +```c +bool square_attacked(position_t position, square_t square, u8 by_color); +``` + +Checks if any piece of `by_color` attacks `square`: +- **Pawn attacks**: Check diagonally forward for enemy pawns +- **Knight attacks**: Use existing `knight_moves[64]` lookup +- **King attacks**: Use existing `920078ULL` pattern +- **Sliding attacks**: Walk rays from target square (for now, ray-walking) + +## Phase 3: Legal Move Filtering + +Modify `get_moves()` (or add `get_legal_moves()`) to: +1. Generate all pseudo-legal moves +2. For each move, make it on a copy of the position +3. Find the friendly king in the resulting position +4. Check if the friendly king is attacked by the opponent +5. If attacked → remove the move (swap with last, decrement length) + +**Castling pre-checks** (in `king.c`): +- King not currently in check +- Squares king passes through are not attacked +- No pieces between king and rook + +## Phase 4: Magic Bitboards (Performance Optimization) + +Replace ray-walking with O(1) magic lookups for sliding piece attacks. + +### Data Structure +```c +typedef struct { + bitboard_t mask; // relevant occupancy bits (excludes edges) + bitboard_t *attacks; // pointer into attack table + bitboard_t magic; // the magic multiplier + int shift; // 64 - popcount(mask) +} magic_t; +``` + +### Attack Tables +- `rook_attacks[0x19000]` (~100KB) +- `bishop_attacks[0x1480]` (~5KB) + +### Index Computation +```c +unsigned index = ((occupied & magic.mask) * magic.magic) >> magic.shift; +return magic.attacks[index]; +``` + +### Initialization +At startup, for each square: +1. Compute mask (pseudo-attacks minus edges) +2. Enumerate all subsets of the mask +3. Compute true attacks via ray-walking (reference) +4. Find magic number via PRNG search + +### Usage +```c +// Move generation +bitboard_t attacks = get_rook_attacks(from_square, all_occupied); +attacks &= ~friendly_pieces; + +// Attack detection +bool square_attacked(...) { + if (get_rook_attacks(sq, occ) & (enemy_rooks | enemy_queens)) return true; + if (get_bishop_attacks(sq, occ) & (enemy_bishops | enemy_queens)) return true; + // ... +} +``` + +## Implementation Order +1. Fix bugs (Phase 1) +2. Add `square_attacked()` with ray-walking +3. Add legal move filtering +4. Fix castling legality +5. Verify perft d0-d7 +6. Uncomment + verify Kiwipete perft +7. Add magic bitboards (Phase 4) @@ -98,7 +98,7 @@ int test_mode(int run_mode) { execvp(args[0], args); } - RUN_CMD({CC, "generate/find_tests.c", "-o", "output/find_tests.o", NULL}); + RUN_CMD({CC, "tests/find_tests.c", "-o", "output/find_tests.o", NULL}); RUN_CMD({"./output/find_tests.o", NULL}); printf("\n"); RUN_CMD({CC, "tests/main.c", "-o", "output/test.o", NULL}); diff --git a/include/engine/moves.h b/include/engine/moves.h index 4032557..755a77a 100644 --- a/include/engine/moves.h +++ b/include/engine/moves.h @@ -28,7 +28,7 @@ typedef struct { square_t to; u16 flags; } move_t; -void position_make_move(position_t *position, move_t *move); +void position_make_move(position_t *position, move_t move); typedef struct { move_t *moves; @@ -36,8 +36,9 @@ typedef struct { u32 capacity; } moves_t; -moves_t moves_init(); -moves_t moves_init_wcapacity(u32 capacity); +void moves_init(moves_t *moves); +void moves_deinit(moves_t moves); +void moves_init_wcapacity(moves_t *moves, u32 capacity); moves_t moves_empty(); struct add_move_params { @@ -65,6 +66,9 @@ void get_queen_moves(moves_t *moves, position_t position); void get_moves(moves_t *moves, position_t position); +bool square_attacked(position_t position, square_t square, u8 by_color); +void get_legal_moves(moves_t *moves, position_t position); + #ifdef MOVES_INTERNAL void __forloop_rook_moves_gen( moves_t *moves, @@ -2,6 +2,10 @@ dev: ./build uci ./build uci run +test: + ./build test + ./build test run + analysis: python symbol_analyzer/main.py xdg-open symbol_analyzer/index.html diff --git a/resources/bitboards.html b/resources/bitboards.html new file mode 100644 index 0000000..8323dfe --- /dev/null +++ b/resources/bitboards.html @@ -0,0 +1,111 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Bitboard editor</title> + <style> + :root { + --square-side-length: 6rem; + --dark-color: #779556; + --light-color: #ebecd0; + --selected-color: #f37f6b; + --background-color: #0f0f0f; + --text-color: #fefefe; + } + body { + display: flex; + flex-direction: column; + align-items: center; + background-color: var(--background-color); + color: var(--text-color); + } + #board { + display: grid; + grid-template-columns: repeat(9, 1fr); + grid-template-rows: repeat(9, 1fr); + } + .dark { + background-color: var(--dark-color); + } + .light { + background-color: var(--light-color); + } + .selected { + background-color: var(--selected-color); + } + .square { + width: var(--square-side-length); + height: var(--square-side-length); + } + .label { + width: var(--square-side-length); + height: var(--square-side-length); + display: flex; + align-items: center; + justify-content: flex-end; + } + .horizontal_label { + width: var(--square-side-length); + height: var(--square-side-length); + display: flex; + justify-content: center; + } + </style> +</head> +<body> + <div id="board"> + </div> + <div id="output"> + </div> + <script> + const output = document.getElementById("output"); + + let bitboard = 0n; + function trackBitboard(i, selected) { + const mask = 1n << BigInt(i); + if (selected) { + bitboard |= mask; + } else { + bitboard &= ~mask; + } + output.textContent = bitboard.toString(); + } + + const selectedAction = trackBitboard; + + const board = document.getElementById("board"); + for (let i = 0; i < 64; i++) { + if (i % 8 == 0) { + const label = document.createElement("div"); + label.classList.add("label"); + label.textContent = 8 - Math.round(i / 8); + board.appendChild(label); + } + const square = document.createElement("div"); + square.classList.add("square"); + const shift = Math.floor(i / 8); + square.classList.add((i + shift) % 2 ? "dark" : "light"); + square.onclick = () => { + const selected = square.classList.contains("selected"); + selectedAction((i % 8) - 8 * Math.floor(i / 8) + 56, !selected); + if (selected) { + square.classList.remove("selected"); + } else { + square.classList.add("selected"); + } + }; + board.appendChild(square); + } + const label = document.createElement("div"); + label.classList.add("horizontal_label"); + board.appendChild(label); + for (let i = 0; i < 8; i++) { + const label = document.createElement("div"); + label.classList.add("horizontal_label"); + label.textContent = ["a", "b", "c", "d", "e", "f", "g", "h"][i]; + board.appendChild(label); + } + </script> +</body> +</html> diff --git a/src/engine/moves.c b/src/engine/moves.c index 05278a5..beaf8a6 100644 --- a/src/engine/moves.c +++ b/src/engine/moves.c @@ -2,12 +2,15 @@ #include "bitboard.h" #include "moves/vec.c" -#include "moves/king.c" #include "moves/knight.c" #include "moves/pawn.c" #include "moves/rook.c" #include "moves/bishop.c" #include "moves/queen.c" +#include "moves/attack.c" +#ifndef TEST_MOD +#include "moves/king.c" +#endif #include <assert.h> @@ -20,6 +23,45 @@ void get_moves(moves_t* moves, position_t position) { get_queen_moves(moves, position); } +void get_legal_moves(moves_t* moves, position_t position) { + get_moves(moves, position); + + u8 opponent = position.turn == WHITE_TURN ? BLACK_TURN : WHITE_TURN; + int write = 0; + for (int read = 0; read < moves->length; read++) { + move_t m = moves->moves[read]; + + if (m.flags & MOVE_SHORT_CASTLE) { + int king_sq = position.turn == WHITE_TURN ? 4 : 60; + int pass_sq = position.turn == WHITE_TURN ? 5 : 61; + if (square_attacked(position, king_sq, opponent)) continue; + if (square_attacked(position, pass_sq, opponent)) continue; + } + if (m.flags & MOVE_LONG_CASTLE) { + int king_sq = position.turn == WHITE_TURN ? 4 : 60; + int pass_sq = position.turn == WHITE_TURN ? 3 : 59; + if (square_attacked(position, king_sq, opponent)) continue; + if (square_attacked(position, pass_sq, opponent)) continue; + } + + position_t copy = position; + position_make_move(©, m); + + bitboard_t friendly_king; + if (position.turn == WHITE_TURN) { + friendly_king = copy.bitboards[WHITE_KING]; + } else { + friendly_king = copy.bitboards[BLACK_KING]; + } + int king_square = __builtin_ctzll(friendly_king); + + if (!square_attacked(copy, king_square, opponent)) { + moves->moves[write++] = m; + } + } + moves->length = write; +} + int find_piece_on_square(position_t* p, int square) { if ((p->bitboards[WHITE_KING] >> square) & 1) return WHITE_KING; if ((p->bitboards[WHITE_QUEEN] >> square) & 1) return WHITE_QUEEN; @@ -36,83 +78,193 @@ int find_piece_on_square(position_t* p, int square) { assert(0); } -void position_make_move(position_t* position, move_t* move) { - if (move->flags & MOVE_LONG_CASTLE) { +void position_make_move(position_t* position, move_t move) { + position->passantable_file = 0; + + if (move.flags & MOVE_LONG_CASTLE) { if (position->turn == WHITE_TURN) { - assert(position->bitboards[WHITE_KING] == 16); - assert((position->bitboards[WHITE_ROOK] >> 0) & 1); - - position->bitboards[WHITE_KING] = 2; - position->bitboards[WHITE_ROOK] += 3; - } else if (position->turn == BLACK_TURN) { - assert(position->bitboards[BLACK_KING] == 1152921504606846976ULL); - assert((position->bitboards[BLACK_ROOK] >> 56) & 1); - - position->bitboards[BLACK_KING] = 144115188075855872ULL; - position->bitboards[WHITE_ROOK] &= ~((bitboard_t)1 << 56); - position->bitboards[WHITE_ROOK] |= (bitboard_t)1 << 58; + position->bitboards[WHITE_KING] = (bitboard_t)1 << 2; + position->bitboards[WHITE_ROOK] &= ~((bitboard_t)1 << 0); + position->bitboards[WHITE_ROOK] |= (bitboard_t)1 << 3; + } else { + position->bitboards[BLACK_KING] = (bitboard_t)1 << 58; + position->bitboards[BLACK_ROOK] &= ~((bitboard_t)1 << 56); + position->bitboards[BLACK_ROOK] |= (bitboard_t)1 << 59; } + position->turn = !position->turn; return; } - if (move->flags & MOVE_SHORT_CASTLE) { + if (move.flags & MOVE_SHORT_CASTLE) { if (position->turn == WHITE_TURN) { - assert(position->bitboards[WHITE_KING] == 16); - assert((position->bitboards[WHITE_ROOK] >> 7) & 1); - - position->bitboards[WHITE_KING] = 64; + position->bitboards[WHITE_KING] = (bitboard_t)1 << 6; position->bitboards[WHITE_ROOK] &= ~((bitboard_t)1 << 7); position->bitboards[WHITE_ROOK] |= (bitboard_t)1 << 5; - } else if (position->turn == BLACK_TURN) { - assert(position->bitboards[BLACK_KING] == 1152921504606846976ULL); - assert((position->bitboards[BLACK_ROOK] >> 63) & 1); - - position->bitboards[WHITE_KING] = 4611686018427387904ULL; - position->bitboards[WHITE_ROOK] &= ~((bitboard_t)1 << 63); - position->bitboards[WHITE_ROOK] |= (bitboard_t)1 << 61; + } else { + position->bitboards[BLACK_KING] = (bitboard_t)1 << 62; + position->bitboards[BLACK_ROOK] &= ~((bitboard_t)1 << 63); + position->bitboards[BLACK_ROOK] |= (bitboard_t)1 << 61; } + position->turn = !position->turn; return; } - int piece_type = find_piece_on_square(position, move->from); - position->bitboards[piece_type] &= ~(1 << move->from); - if (move->flags & MOVE_PROMOTE_Q) { + int piece_type = find_piece_on_square(position, move.from); + position->bitboards[piece_type] &= ~((bitboard_t)1 << move.from); + + if (position->turn == BLACK_TURN) position->fullmove_clock++; + if (piece_type == WHITE_PAWN || piece_type == BLACK_PAWN) { + position->halfmove_clock = 0; + } else { + position->halfmove_clock++; + } + + if (position->castling > 0) { + if (piece_type == WHITE_ROOK) { + if (move.from == 7) { + position->castling &= ~WHITE_SHORT_CASTLE; + } else if (move.from == 0) { + position->castling &= ~WHITE_LONG_CASTLE; + } + } else if (piece_type == BLACK_ROOK) { + if (move.from == 63) { + position->castling &= ~BLACK_SHORT_CASTLE; + } else if (move.from == 56) { + position->castling &= ~BLACK_LONG_CASTLE; + } + } else if (piece_type == WHITE_KING) { + position->castling &= ~(WHITE_SHORT_CASTLE | WHITE_LONG_CASTLE); + } else if (piece_type == BLACK_KING) { + position->castling &= ~(BLACK_SHORT_CASTLE | BLACK_LONG_CASTLE); + } + } + + if (move.flags & MOVE_PROMOTE_Q) { int q_type = position->turn == WHITE_TURN ? WHITE_QUEEN : BLACK_QUEEN; - position->bitboards[q_type] |= 1 << move->to; + position->bitboards[q_type] |= (bitboard_t)1 << move.to; + position->turn = !position->turn; return; } - if (move->flags & MOVE_PROMOTE_R) { + if (move.flags & MOVE_PROMOTE_R) { int q_type = position->turn == WHITE_TURN ? WHITE_ROOK : BLACK_ROOK; - position->bitboards[q_type] |= 1 << move->to; + position->bitboards[q_type] |= (bitboard_t)1 << move.to; + position->turn = !position->turn; return; } - if (move->flags & MOVE_PROMOTE_B) { + if (move.flags & MOVE_PROMOTE_B) { int q_type = position->turn == WHITE_TURN ? WHITE_BISHOP : BLACK_BISHOP; - position->bitboards[q_type] |= 1 << move->to; + position->bitboards[q_type] |= (bitboard_t)1 << move.to; + position->turn = !position->turn; return; } - if (move->flags & MOVE_PROMOTE_N) { + if (move.flags & MOVE_PROMOTE_N) { int q_type = position->turn == WHITE_TURN ? WHITE_KNIGHT : BLACK_KNIGHT; - position->bitboards[q_type] |= 1 << move->to; + position->bitboards[q_type] |= (bitboard_t)1 << move.to; + position->turn = !position->turn; return; } - if (move->flags & MOVE_EN_PASSANT) { + if (move.flags & MOVE_EN_PASSANT) { int target_sqr; - if (position->turn == WHITE_TURN) target_sqr = move->to - 8; - else target_sqr = move->to + 8; + if (position->turn == WHITE_TURN) target_sqr = move.to - 8; + else target_sqr = move.to + 8; int to_remove_piece_type = find_piece_on_square(position, target_sqr); - position->bitboards[piece_type] |= 1 << move->to; - position->bitboards[to_remove_piece_type] &= ~(1 << target_sqr); + position->bitboards[piece_type] |= (bitboard_t)1 << move.to; + position->bitboards[to_remove_piece_type] &= ~((bitboard_t)1 << target_sqr); + position->turn = !position->turn; return; } - if (move->flags & MOVE_CAPTURE) { - int to_remove_piece_type = find_piece_on_square(position, move->to); - position->bitboards[to_remove_piece_type] &= ~(1 << move->to); + if (move.flags & MOVE_CAPTURE) { + int to_remove_piece_type = find_piece_on_square(position, move.to); + position->bitboards[to_remove_piece_type] &= ~((bitboard_t)1 << move.to); + + if (to_remove_piece_type == WHITE_ROOK) { + if (move.to == 7) position->castling &= ~WHITE_SHORT_CASTLE; + else if (move.to == 0) position->castling &= ~WHITE_LONG_CASTLE; + } else if (to_remove_piece_type == BLACK_ROOK) { + if (move.to == 63) position->castling &= ~BLACK_SHORT_CASTLE; + else if (move.to == 56) position->castling &= ~BLACK_LONG_CASTLE; + } } - // i can't put this above the find_piece_on_square function, because there - // is a possibility that piece_type would resolve to the piece that is being - // moved, which would mess with the ~(1 << to_sqr) - position->bitboards[piece_type] |= 1 << move->to; + if (piece_type == WHITE_PAWN && move.from / 8 == 1 && move.to / 8 == 3) { + position->passantable_file = (move.from % 8) + 1; + } else if (piece_type == BLACK_PAWN && move.from / 8 == 6 && move.to / 8 == 4) { + position->passantable_file = (move.from % 8) + 1; + } + + position->bitboards[piece_type] |= (bitboard_t)1 << move.to; + position->turn = !position->turn; +} + +#ifdef TEST_MOD +#include "fen.h" + +int count_positions(position_t position, int depth) { + if (depth == 0) return 1; + moves_t moves; + moves_init(&moves); + get_legal_moves(&moves, position); + int count = 0; + for (int i = 0; i < moves.length; i++) { + position_t copy = position; + position_make_move(©, moves.moves[i]); + count += count_positions(copy, depth - 1); + } + moves_deinit(moves); + return count; } + +void perft_divide(position_t position, int depth) { + if (depth == 0) return; + moves_t moves; + moves_init(&moves); + get_legal_moves(&moves, position); + for (int i = 0; i < moves.length; i++) { + position_t copy = position; + position_make_move(©, moves.moves[i]); + int c = count_positions(copy, depth - 1); + printf(" %c%d%c%d: %d\n", + 'a' + (moves.moves[i].from % 8), 1 + (moves.moves[i].from / 8), + 'a' + (moves.moves[i].to % 8), 1 + (moves.moves[i].to / 8), c); + } + moves_deinit(moves); +} + +// https://www.chessprogramming.org/Perft_Results#Initial_Position +bool test_perft_starting_position() { + position_t position = load_fen( + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" + ).position; + int d; + d = count_positions(position, 0); printf("d0: %d\n", d); if (d != 1) return false; + d = count_positions(position, 1); printf("d1: %d\n", d); if (d != 20) return false; + d = count_positions(position, 2); printf("d2: %d\n", d); if (d != 400) return false; + d = count_positions(position, 3); printf("d3: %d\n", d); if (d != 8902) return false; + d = count_positions(position, 4); printf("d4: %d\n", d); if (d != 197281) return false; + + position_t castle_pos = load_fen( + "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1" + ).position; + int cp = count_positions(castle_pos, 1); + printf("R3K2R d1: %d (expected 26)\n", cp); + + d = count_positions(position, 5); printf("d5: %d\n", d); if (d != 4865609) return false; + return true; +} + +bool test_perft_kiwipete_position() { + position_t position = load_fen( + "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1" + ).position; + int d; + d = count_positions(position, 1); printf("d0: %d\n", d); if (d != 48) return false; + d = count_positions(position, 2); printf("d1: %d\n", d); if (d != 2039) return false; + d = count_positions(position, 3); printf("d2: %d\n", d); if (d != 97862) return false; + d = count_positions(position, 4); printf("d3: %d\n", d); if (d != 4085603) return false; + d = count_positions(position, 5); printf("d4: %d\n", d); if (d != 193690690) return false; + d = count_positions(position, 6); printf("d5: %d\n", d); if (d != 8031647685) return false; + return true; +} + +#endif // TEST_MOD diff --git a/src/engine/moves/attack.c b/src/engine/moves/attack.c new file mode 100644 index 0000000..e4923ea --- /dev/null +++ b/src/engine/moves/attack.c @@ -0,0 +1,154 @@ +#define MOVES_INTERNAL +#include "engine/moves.h" + +static bool pawn_attacks_square(position_t position, square_t square, u8 by_color) { + bitboard_t enemy_pawns; + if (by_color == WHITE_TURN) { + enemy_pawns = position.bitboards[WHITE_PAWN]; + if (square < 8) return false; + bitboard_t nw = (square % 8 == 7) ? 0 : (enemy_pawns >> (square - 7)); + bitboard_t ne = (square % 8 == 0) ? 0 : (enemy_pawns >> (square - 9)); + return (nw | ne) & 1; + } else { + enemy_pawns = position.bitboards[BLACK_PAWN]; + if (square > 54) return false; + bitboard_t se = (square % 8 == 0) ? 0 : (enemy_pawns >> (square + 7)); + bitboard_t sw = (square % 8 == 7) ? 0 : (enemy_pawns >> (square + 9)); + return (se | sw) & 1; + } +} + +static bool knight_attacks_square(position_t position, square_t square, u8 by_color) { + bitboard_t enemy_knights = (by_color == WHITE_TURN) + ? position.bitboards[WHITE_KNIGHT] + : position.bitboards[BLACK_KNIGHT]; + return knight_moves[square] & enemy_knights; +} + +static bool king_attacks_square(position_t position, square_t square, u8 by_color) { + bitboard_t enemy_king = (by_color == WHITE_TURN) + ? position.bitboards[WHITE_KING] + : position.bitboards[BLACK_KING]; + bitboard_t movement; + if (square >= 10) { + movement = 920078ULL << (square - 10); + } else { + movement = 920078ULL >> -(square - 10); + } + if ((bitboard_t)1 << square & BITMASK_FILE_A) { + movement &= BITMASK_FILE_A | BITMASK_FILE_B; + } else if ((bitboard_t)1 << square & BITMASK_FILE_H) { + movement &= BITMASK_FILE_G | BITMASK_FILE_H; + } + return movement & enemy_king; +} + +static bool sliding_attacks_square( + position_t position, + square_t square, + u8 by_color, + bool check_rook, + bool check_bishop +) { + bitboard_t all_occupied = whites(position) | blacks(position); + int rank = square / 8; + int file = square % 8; + + bitboard_t enemy_rooks = (by_color == WHITE_TURN) ? position.bitboards[WHITE_ROOK] : position.bitboards[BLACK_ROOK]; + bitboard_t enemy_bishops = (by_color == WHITE_TURN) ? position.bitboards[WHITE_BISHOP] : position.bitboards[BLACK_BISHOP]; + bitboard_t enemy_queens = (by_color == WHITE_TURN) ? position.bitboards[WHITE_QUEEN] : position.bitboards[BLACK_QUEEN]; + + if (check_rook) { + bitboard_t rook_like = enemy_rooks | enemy_queens; + + int r, idx; + r = rank + 1; + while (r < 8) { + idx = r * 8 + file; + if ((all_occupied >> idx) & 1) { + if ((rook_like >> idx) & 1) return true; + break; + } + r++; + } + r = rank - 1; + while (r >= 0) { + idx = r * 8 + file; + if ((all_occupied >> idx) & 1) { + if ((rook_like >> idx) & 1) return true; + break; + } + r--; + } + int f = file + 1; + while (f < 8) { + idx = rank * 8 + f; + if ((all_occupied >> idx) & 1) { + if ((rook_like >> idx) & 1) return true; + break; + } + f++; + } + f = file - 1; + while (f >= 0) { + idx = rank * 8 + f; + if ((all_occupied >> idx) & 1) { + if ((rook_like >> idx) & 1) return true; + break; + } + f--; + } + } + + if (check_bishop) { + bitboard_t bishop_like = enemy_bishops | enemy_queens; + + int r, f, idx; + r = rank + 1; f = file + 1; + while (r < 8 && f < 8) { + idx = r * 8 + f; + if ((all_occupied >> idx) & 1) { + if ((bishop_like >> idx) & 1) return true; + break; + } + r++; f++; + } + r = rank - 1; f = file - 1; + while (r >= 0 && f >= 0) { + idx = r * 8 + f; + if ((all_occupied >> idx) & 1) { + if ((bishop_like >> idx) & 1) return true; + break; + } + r--; f--; + } + r = rank - 1; f = file + 1; + while (r >= 0 && f < 8) { + idx = r * 8 + f; + if ((all_occupied >> idx) & 1) { + if ((bishop_like >> idx) & 1) return true; + break; + } + r--; f++; + } + r = rank + 1; f = file - 1; + while (r < 8 && f >= 0) { + idx = r * 8 + f; + if ((all_occupied >> idx) & 1) { + if ((bishop_like >> idx) & 1) return true; + break; + } + r++; f--; + } + } + + return false; +} + +bool square_attacked(position_t position, square_t square, u8 by_color) { + if (pawn_attacks_square(position, square, by_color)) return true; + if (knight_attacks_square(position, square, by_color)) return true; + if (king_attacks_square(position, square, by_color)) return true; + if (sliding_attacks_square(position, square, by_color, true, true)) return true; + return false; +} diff --git a/src/engine/moves/king.c b/src/engine/moves/king.c index 706cf9c..98c6ab9 100644 --- a/src/engine/moves/king.c +++ b/src/engine/moves/king.c @@ -42,15 +42,51 @@ void get_king_moves(moves_t* moves, position_t position) { .flags = ((enemy_pieces >> to) & 1) ? MOVE_CAPTURE : 0, ); } + + bitboard_t all_occupied = friendly_pieces | enemy_pieces; + if (position.turn == WHITE_TURN) { + if ((position.castling & WHITE_SHORT_CASTLE) && + king_square == 4 && + !((all_occupied >> 5) & 1) && + !((all_occupied >> 6) & 1)) + { + add_move(moves, 4, 6, .flags = MOVE_SHORT_CASTLE); + } + if ((position.castling & WHITE_LONG_CASTLE) && + king_square == 4 && + !((all_occupied >> 3) & 1) && + !((all_occupied >> 2) & 1) && + !((all_occupied >> 1) & 1)) + { + add_move(moves, 4, 2, .flags = MOVE_LONG_CASTLE); + } + } else { + if ((position.castling & BLACK_SHORT_CASTLE) && + king_square == 60 && + !((all_occupied >> 61) & 1) && + !((all_occupied >> 62) & 1)) + { + add_move(moves, 60, 62, .flags = MOVE_SHORT_CASTLE); + } + if ((position.castling & BLACK_LONG_CASTLE) && + king_square == 60 && + !((all_occupied >> 59) & 1) && + !((all_occupied >> 58) & 1) && + !((all_occupied >> 57) & 1)) + { + add_move(moves, 60, 58, .flags = MOVE_LONG_CASTLE); + } + } } #ifdef TEST_MOD #include <stdbool.h> -#include "vec.c" bool test_white_king_corners() { - moves_t moves = moves_init(); + moves_t moves; + moves_init(&moves); position_t p = {0}; + p.bitboards[BLACK_KING] = 100; p.bitboards[WHITE_KING] = 1; p.turn = WHITE_TURN; get_king_moves(&moves, p); @@ -82,12 +118,16 @@ bool test_white_king_corners() { if (moves.moves[2].from != 56) return false; if (moves.moves[2].to != 57) return false; + moves_deinit(moves); return true; } bool test_black_king_corners() { - moves_t moves = moves_init(); + moves_t moves; + moves_init(&moves); + position_t p = {0}; + p.bitboards[WHITE_KING] = 100; p.bitboards[BLACK_KING] = 1; p.turn = BLACK_TURN; get_king_moves(&moves, p); @@ -119,6 +159,7 @@ bool test_black_king_corners() { if (moves.moves[2].from != 56) return false; if (moves.moves[2].to != 57) return false; + moves_deinit(moves); return true; } #endif diff --git a/src/engine/moves/vec.c b/src/engine/moves/vec.c index b5cbb6f..6da3eb9 100644 --- a/src/engine/moves/vec.c +++ b/src/engine/moves/vec.c @@ -3,17 +3,18 @@ #include <assert.h> #include <stdlib.h> -moves_t moves_init() { - return moves_init_wcapacity(16); +void moves_init(moves_t *moves) { + return moves_init_wcapacity(moves, 16); } -moves_t moves_init_wcapacity(u32 capacity) { - move_t* moves = malloc(capacity * sizeof(*moves)); - return (moves_t) { - .moves = moves, - .capacity = capacity, - .length = 0, - }; +void moves_deinit(moves_t moves) { + free(moves.moves); +} + +void moves_init_wcapacity(moves_t *moves, u32 capacity) { + moves->moves = malloc(capacity * sizeof(*moves)); + moves->length = 0; + moves->capacity = capacity; } moves_t moves_empty() { diff --git a/src/uci/command.c b/src/uci/command.c index c64fee2..583b887 100644 --- a/src/uci/command.c +++ b/src/uci/command.c @@ -101,25 +101,42 @@ bool test_uci_cmd_t() { uci_cmd_append_arg(&cmd, "arg2"); uci_cmd_append_arg(&cmd, "arg3"); - if (strncmp(cmd.root, "root", MAX_TOKEN_SIZE) != 0) return false; - if (strncmp(uci_cmd_get_arg(cmd, 0), "arg1", MAX_TOKEN_SIZE) != 0) + if (strncmp(cmd.root, "root", MAX_TOKEN_SIZE) != 0) { + uci_cmd_deinit(cmd); return false; - if (strncmp(uci_cmd_get_arg(cmd, 1), "arg2", MAX_TOKEN_SIZE) != 0) + } + if (strncmp(uci_cmd_get_arg(cmd, 0), "arg1", MAX_TOKEN_SIZE) != 0) { + uci_cmd_deinit(cmd); return false; - if (strncmp(uci_cmd_get_arg(cmd, 2), "arg3", MAX_TOKEN_SIZE) != 0) + } + if (strncmp(uci_cmd_get_arg(cmd, 1), "arg2", MAX_TOKEN_SIZE) != 0) { + uci_cmd_deinit(cmd); return false; + } + if (strncmp(uci_cmd_get_arg(cmd, 2), "arg3", MAX_TOKEN_SIZE) != 0) { + uci_cmd_deinit(cmd); + return false; + } + uci_cmd_deinit(cmd); return true; } bool test_invalid_cmd_uci_cmd_t() { - uci_cmd_t cmd = uci_cmd_t_init(); + uci_cmd_t cmd = uci_cmd_init(); + uci_cmd_add(&cmd, "john"); uci_cmd_add(&cmd, "debug"); uci_cmd_add(&cmd, "on"); - if (strncmp(cmd.root, "debug", MAX_TOKEN_SIZE) != 0) return false; - if (strncmp(uci_cmd_t_get_arg(cmd, 0), "on", MAX_TOKEN_SIZE) != 0) + if (strncmp(cmd.root, "debug", MAX_TOKEN_SIZE) != 0) { + uci_cmd_deinit(cmd); return false; + } + if (strncmp(uci_cmd_get_arg(cmd, 0), "on", MAX_TOKEN_SIZE) != 0) { + uci_cmd_deinit(cmd); + return false; + } + uci_cmd_deinit(cmd); return true; } #endif diff --git a/tests/generated.c b/tests/generated.c index 777f8b1..43145e0 100644 --- a/tests/generated.c +++ b/tests/generated.c @@ -1,25 +1,30 @@ -#include "../src/engine/fen.c" +#include "../src/fen.c" +#include "../src/engine/moves.c" #include "../src/engine/moves/king.c" #include "../src/uci/command.c" -int total_test_count = 7; -bool (*tests[7])(void) = { +int total_test_count = 9; +bool (*tests[9])(void) = { test_fen_no_passant, test_fen_passant, test_starting_position, + test_perft_starting_position, + test_perft_kiwipete_position, test_white_king_corners, test_black_king_corners, - test_ucicmd, - test_invalid_cmd_ucicmd, + test_uci_cmd_t, + test_invalid_cmd_uci_cmd_t, }; -int max_test_name_size = 18; -char test_names[7][100] = { +int max_test_name_size = 23; +char test_names[9][100] = { "fen_no_passant", "fen_passant", "starting_position", + "perft_starting_position", + "perft_kiwipete_position", "white_king_corners", "black_king_corners", - "ucicmd", - "invalid_cmd_ucicmd", + "uci_cmd_t", + "invalid_cmd_uci_cmd_t", }; diff --git a/tests/main.c b/tests/main.c index 191f0dd..ff7560d 100644 --- a/tests/main.c +++ b/tests/main.c @@ -22,7 +22,7 @@ int main() { padded_testname[j] = test_names[i][j]; j++; } - while (j < max_test_name_size + 2) { + while (j < max_test_name_size + 2 - 1) { padded_testname[j++] = ' '; } padded_testname[j] = 0; |
