first commit
This commit is contained in:
commit
a2ff70d4c1
17 changed files with 2004 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
build/
|
||||||
|
.vscode/
|
||||||
30
Makefile
Normal file
30
Makefile
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
CC = gcc
|
||||||
|
CFLAGS = -Wall -Wextra -O3 -pthread
|
||||||
|
LDFLAGS = -pthread
|
||||||
|
SRC_DIR = src
|
||||||
|
BUILD_DIR = build
|
||||||
|
OBJ_DIR = $(BUILD_DIR)/obj
|
||||||
|
BIN_DIR = $(BUILD_DIR)/bin
|
||||||
|
|
||||||
|
SRCS = $(wildcard $(SRC_DIR)/*.c)
|
||||||
|
OBJS = $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRCS))
|
||||||
|
TARGET = $(BIN_DIR)/bitchess
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJS) | $(BIN_DIR)
|
||||||
|
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(OBJ_DIR):
|
||||||
|
mkdir -p $(OBJ_DIR)
|
||||||
|
|
||||||
|
$(BIN_DIR):
|
||||||
|
mkdir -p $(BIN_DIR)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR)
|
||||||
131
README.md
Normal file
131
README.md
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
# BitChess Engine
|
||||||
|
## A Pure Algorithmic Chess Engine
|
||||||
|
|
||||||
|
### Chess Engine Overview
|
||||||
|
|
||||||
|
BitChess is a high-performance chess engine developed as a hobby project to explore low-level optimization techniques and algorithmic game theory. The engine uses traditional, non-AI approaches to chess programming, focusing on efficient data structures, multithreaded search, and careful optimization of critical paths.
|
||||||
|
|
||||||
|
This project demonstrates how far a pure algorithmic approach to chess can go without relying on neural networks or machine learning techniques that dominate modern engines like Stockfish, Leela Chess Zero, or AlphaZero.
|
||||||
|
|
||||||
|
### Motivation
|
||||||
|
|
||||||
|
The primary goals of this project are:
|
||||||
|
- Deepen understanding of bitboard representations and bit manipulation
|
||||||
|
- Master low-level optimization techniques in C
|
||||||
|
- Explore multithreaded search algorithms
|
||||||
|
- Build an efficient, pure algorithmic chess engine from scratch
|
||||||
|
- Benchmark algorithmic approaches against more modern techniques
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
- Bitboard-based representation for maximum performance
|
||||||
|
- Alpha-beta pruning with move ordering for efficient search
|
||||||
|
- Multithreaded search to utilize multi-core processors
|
||||||
|
- Position evaluation using piece-square tables and material value
|
||||||
|
- Clean terminal interface that updates in real-time
|
||||||
|
- Standard Algebraic Notation (SAN) support for move representation
|
||||||
|
|
||||||
|
### Technical Approach
|
||||||
|
|
||||||
|
#### Board Representation
|
||||||
|
|
||||||
|
The engine uses bitboards (64-bit unsigned integers) to represent the chess position, where each bit corresponds to a square on the board. This approach allows for extremely fast position updates and move generation using bitwise operations.
|
||||||
|
|
||||||
|
Bitboard for White Pawns (P): `0x000000000000FF00`
|
||||||
|
|
||||||
|
----------------
|
||||||
|
8 | . . . . . . . . |
|
||||||
|
7 | . . . . . . . . |
|
||||||
|
6 | . . . . . . . . |
|
||||||
|
5 | . . . . . . . . |
|
||||||
|
4 | . . . . . . . . |
|
||||||
|
3 | . . . . . . . . |
|
||||||
|
2 | P P P P P P P P |
|
||||||
|
1 | . . . . . . . . |
|
||||||
|
----------------
|
||||||
|
a b c d e f g h
|
||||||
|
|
||||||
|
#### Search Algorithm
|
||||||
|
|
||||||
|
BitChess employs a classic Minimax algorithm with Alpha-Beta pruning to search the game tree efficiently. The engine implements:
|
||||||
|
- Move ordering to maximize alpha-beta cutoffs
|
||||||
|
- Multithreaded search at the root node to parallelize evaluation
|
||||||
|
- Iterative deepening to find good moves quickly
|
||||||
|
- MVV-LVA (Most Valuable Victim - Least Valuable Aggressor) for capture ordering
|
||||||
|
|
||||||
|
#### Evaluation Function
|
||||||
|
|
||||||
|
The evaluation combines several components:
|
||||||
|
- **Material balance** - basic piece values (pawn=100, knight=320, etc.)
|
||||||
|
- **Piece-square tables** - bonuses/penalties for piece positioning
|
||||||
|
- **Mobility** - counting legal moves as a proxy for piece activity
|
||||||
|
- **King safety** - evaluating the king's defensive structure
|
||||||
|
|
||||||
|
#### Move Generation
|
||||||
|
|
||||||
|
The engine generates moves using precomputed attack tables:
|
||||||
|
- **Sliding pieces** (bishops, rooks, queens) - using ray attacks
|
||||||
|
- **Knights and kings** - using precomputed attack patterns
|
||||||
|
- **Pawns** - handling special moves like promotions, en passant, and double pushes
|
||||||
|
|
||||||
|
### Implementation Details
|
||||||
|
|
||||||
|
BitChess is written in C and designed with modularity in mind:
|
||||||
|
- **Bitboard module** - manages attack tables and bit manipulation
|
||||||
|
- **Board module** - handles game state and move application
|
||||||
|
- **Movegen module** - generates legal moves
|
||||||
|
- **Evaluation module** - assesses positions
|
||||||
|
- **Search module** - finds the best move
|
||||||
|
- **Notation module** - converts between internal move representation and SAN
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
|
||||||
|
Several optimizations are implemented:
|
||||||
|
- **Precomputed attack tables** - eliminate expensive calculations during search
|
||||||
|
- **Move ordering** - dramatically improves alpha-beta pruning efficiency
|
||||||
|
- **Multithreading** - utilizes all available CPU cores
|
||||||
|
- **Bit manipulation** - uses built-in popcount and bit-scan operations
|
||||||
|
- **Structure organization** - designed to maximize cache efficiency
|
||||||
|
|
||||||
|
### Current Capabilities
|
||||||
|
|
||||||
|
The engine currently:
|
||||||
|
- Implements all chess rules correctly, including castling, en passant, and promotions
|
||||||
|
- Searches at configurable depths with reasonable performance
|
||||||
|
- Uses multiple threads to speed up search
|
||||||
|
- Displays evaluation in centipawns
|
||||||
|
- Provides a clean terminal interface
|
||||||
|
|
||||||
|
### Future Improvements
|
||||||
|
|
||||||
|
Planned enhancements include:
|
||||||
|
- Transposition tables to avoid re-searching identical positions
|
||||||
|
- Quiescence search to resolve tactical sequences
|
||||||
|
- Opening book for stronger play in the early game
|
||||||
|
- Endgame tablebases for perfect play in simplified positions
|
||||||
|
- UCI protocol support for compatibility with chess GUIs
|
||||||
|
- Performance profiling and further optimization
|
||||||
|
|
||||||
|
### Building and Running
|
||||||
|
|
||||||
|
|
||||||
|
#### Clone the repository
|
||||||
|
```bash
|
||||||
|
git clone https://git.srtk.in/sarthak/bitchess.git
|
||||||
|
cd bitchess
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Build the project
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Run the engine
|
||||||
|
```bash
|
||||||
|
./build/bin/chess_engine
|
||||||
|
```
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- GCC or Clang compiler with C99 support
|
||||||
|
- POSIX-compliant operating system (Linux, macOS, or Windows with WSL)
|
||||||
|
- pthread library
|
||||||
147
src/bitboard.c
Normal file
147
src/bitboard.c
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
#include <string.h>
|
||||||
|
#include "bitboard.h"
|
||||||
|
|
||||||
|
// Precomputed attack tables
|
||||||
|
U64 knight_attacks[64];
|
||||||
|
U64 king_attacks[64];
|
||||||
|
U64 pawn_attacks[2][64]; // [color][square]
|
||||||
|
|
||||||
|
// Lookup tables for bishop and rook moves
|
||||||
|
U64 bishop_masks[64];
|
||||||
|
U64 rook_masks[64];
|
||||||
|
|
||||||
|
// Magic bitboard tables
|
||||||
|
U64 bishop_attacks[64][512];
|
||||||
|
U64 rook_attacks[64][4096];
|
||||||
|
|
||||||
|
// Initialize attack tables
|
||||||
|
void init_attack_tables() {
|
||||||
|
// Knight attacks
|
||||||
|
for (int sq = 0; sq < 64; ++sq) {
|
||||||
|
U64 mask = 0ULL;
|
||||||
|
int r = sq / 8, f = sq % 8;
|
||||||
|
int dr[8] = {2, 2, 1, 1, -1, -1, -2, -2};
|
||||||
|
int df[8] = {1, -1, 2, -2, 2, -2, 1, -1};
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
int nr = r + dr[i], nf = f + df[i];
|
||||||
|
if (nr >= 0 && nr < 8 && nf >= 0 && nf < 8) {
|
||||||
|
SET_BIT(mask, SQUARE(nr, nf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
knight_attacks[sq] = mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// King attacks
|
||||||
|
for (int sq = 0; sq < 64; ++sq) {
|
||||||
|
U64 mask = 0ULL;
|
||||||
|
int r = sq / 8, f = sq % 8;
|
||||||
|
|
||||||
|
for (int dr = -1; dr <= 1; ++dr) {
|
||||||
|
for (int df = -1; df <= 1; ++df) {
|
||||||
|
if (dr == 0 && df == 0) continue;
|
||||||
|
int nr = r + dr, nf = f + df;
|
||||||
|
if (nr >= 0 && nr < 8 && nf >= 0 && nf < 8) {
|
||||||
|
SET_BIT(mask, SQUARE(nr, nf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
king_attacks[sq] = mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pawn attacks
|
||||||
|
for (int sq = 0; sq < 64; ++sq) {
|
||||||
|
U64 white_mask = 0ULL, black_mask = 0ULL;
|
||||||
|
int r = sq / 8, f = sq % 8;
|
||||||
|
|
||||||
|
// White pawns attack up-left and up-right
|
||||||
|
if (r < 7) {
|
||||||
|
if (f > 0) SET_BIT(white_mask, SQUARE(r+1, f-1)); // Up-left
|
||||||
|
if (f < 7) SET_BIT(white_mask, SQUARE(r+1, f+1)); // Up-right
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black pawns attack down-left and down-right
|
||||||
|
if (r > 0) {
|
||||||
|
if (f > 0) SET_BIT(black_mask, SQUARE(r-1, f-1)); // Down-left
|
||||||
|
if (f < 7) SET_BIT(black_mask, SQUARE(r-1, f+1)); // Down-right
|
||||||
|
}
|
||||||
|
|
||||||
|
pawn_attacks[0][sq] = white_mask;
|
||||||
|
pawn_attacks[1][sq] = black_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize sliding piece attacks (simplified approach without magic bitboards)
|
||||||
|
for (int sq = 0; sq < 64; ++sq) {
|
||||||
|
int r = sq / 8, f = sq % 8;
|
||||||
|
|
||||||
|
// Initialize bishop and rook masks
|
||||||
|
bishop_masks[sq] = 0ULL;
|
||||||
|
rook_masks[sq] = 0ULL;
|
||||||
|
|
||||||
|
// North ray
|
||||||
|
for (int nr = r + 1; nr < 7; ++nr)
|
||||||
|
SET_BIT(rook_masks[sq], SQUARE(nr, f));
|
||||||
|
|
||||||
|
// South ray
|
||||||
|
for (int nr = r - 1; nr > 0; --nr)
|
||||||
|
SET_BIT(rook_masks[sq], SQUARE(nr, f));
|
||||||
|
|
||||||
|
// East ray
|
||||||
|
for (int nf = f + 1; nf < 7; ++nf)
|
||||||
|
SET_BIT(rook_masks[sq], SQUARE(r, nf));
|
||||||
|
|
||||||
|
// West ray
|
||||||
|
for (int nf = f - 1; nf > 0; --nf)
|
||||||
|
SET_BIT(rook_masks[sq], SQUARE(r, nf));
|
||||||
|
|
||||||
|
// North-East ray
|
||||||
|
for (int nr = r + 1, nf = f + 1; nr < 7 && nf < 7; ++nr, ++nf)
|
||||||
|
SET_BIT(bishop_masks[sq], SQUARE(nr, nf));
|
||||||
|
|
||||||
|
// South-East ray
|
||||||
|
for (int nr = r - 1, nf = f + 1; nr > 0 && nf < 7; --nr, ++nf)
|
||||||
|
SET_BIT(bishop_masks[sq], SQUARE(nr, nf));
|
||||||
|
|
||||||
|
// South-West ray
|
||||||
|
for (int nr = r - 1, nf = f - 1; nr > 0 && nf > 0; --nr, --nf)
|
||||||
|
SET_BIT(bishop_masks[sq], SQUARE(nr, nf));
|
||||||
|
|
||||||
|
// North-West ray
|
||||||
|
for (int nr = r + 1, nf = f - 1; nr < 7 && nf > 0; ++nr, --nf)
|
||||||
|
SET_BIT(bishop_masks[sq], SQUARE(nr, nf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get sliding piece attacks (bishop/rook)
|
||||||
|
U64 get_sliding_attacks(int sq, U64 blockers, int is_bishop) {
|
||||||
|
U64 attacks = 0ULL;
|
||||||
|
int r = sq / 8, f = sq % 8;
|
||||||
|
|
||||||
|
const int bishop_dirs[4][2] = {{1,1}, {1,-1}, {-1,1}, {-1,-1}};
|
||||||
|
const int rook_dirs[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}};
|
||||||
|
|
||||||
|
const int (*dirs)[2] = is_bishop ? bishop_dirs : rook_dirs;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int dr = dirs[i][0], df = dirs[i][1];
|
||||||
|
int nr = r, nf = f;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
nr += dr;
|
||||||
|
nf += df;
|
||||||
|
|
||||||
|
// Check if we're still on the board
|
||||||
|
if (nr < 0 || nr > 7 || nf < 0 || nf > 7)
|
||||||
|
break;
|
||||||
|
|
||||||
|
int target_sq = SQUARE(nr, nf);
|
||||||
|
SET_BIT(attacks, target_sq);
|
||||||
|
|
||||||
|
// Stop if we hit a blocker
|
||||||
|
if (GET_BIT(blockers, target_sq))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attacks;
|
||||||
|
}
|
||||||
25
src/bitboard.h
Normal file
25
src/bitboard.h
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef BITBOARD_H
|
||||||
|
#define BITBOARD_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// Precomputed attack tables
|
||||||
|
extern U64 knight_attacks[64];
|
||||||
|
extern U64 king_attacks[64];
|
||||||
|
extern U64 pawn_attacks[2][64]; // [color][square]
|
||||||
|
|
||||||
|
// Lookup tables for bishop and rook moves
|
||||||
|
extern U64 bishop_masks[64];
|
||||||
|
extern U64 rook_masks[64];
|
||||||
|
|
||||||
|
// Magic bitboard tables
|
||||||
|
extern U64 bishop_attacks[64][512];
|
||||||
|
extern U64 rook_attacks[64][4096];
|
||||||
|
|
||||||
|
// Initialize attack tables
|
||||||
|
void init_attack_tables();
|
||||||
|
|
||||||
|
// Get sliding piece attacks (bishop/rook)
|
||||||
|
U64 get_sliding_attacks(int sq, U64 blockers, int is_bishop);
|
||||||
|
|
||||||
|
#endif // BITBOARD_H
|
||||||
299
src/board.c
Normal file
299
src/board.c
Normal file
|
|
@ -0,0 +1,299 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "board.h"
|
||||||
|
#include "bitboard.h"
|
||||||
|
#include "movegen.h"
|
||||||
|
|
||||||
|
// Update occupancy bitboards
|
||||||
|
void update_occupancy(GameState *state) {
|
||||||
|
state->occ_white = 0ULL;
|
||||||
|
state->occ_black = 0ULL;
|
||||||
|
|
||||||
|
for (int pc = P; pc <= K; ++pc)
|
||||||
|
state->occ_white |= state->bitboards[pc];
|
||||||
|
|
||||||
|
for (int pc = p; pc <= k; ++pc)
|
||||||
|
state->occ_black |= state->bitboards[pc];
|
||||||
|
|
||||||
|
state->occ_all = state->occ_white | state->occ_black;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a standard chess starting position
|
||||||
|
void init_startpos(GameState *state) {
|
||||||
|
// Clear all bitboards
|
||||||
|
memset(state, 0, sizeof(GameState));
|
||||||
|
|
||||||
|
// Set up white pieces
|
||||||
|
state->bitboards[P] = 0x000000000000FF00ULL; // Pawns on rank 2
|
||||||
|
state->bitboards[N] = 0x0000000000000042ULL; // Knights on b1, g1
|
||||||
|
state->bitboards[B] = 0x0000000000000024ULL; // Bishops on c1, f1
|
||||||
|
state->bitboards[R] = 0x0000000000000081ULL; // Rooks on a1, h1
|
||||||
|
state->bitboards[Q] = 0x0000000000000008ULL; // Queen on d1
|
||||||
|
state->bitboards[K] = 0x0000000000000010ULL; // King on e1
|
||||||
|
|
||||||
|
// Set up black pieces
|
||||||
|
state->bitboards[p] = 0x00FF000000000000ULL; // Pawns on rank 7
|
||||||
|
state->bitboards[n] = 0x4200000000000000ULL; // Knights on b8, g8
|
||||||
|
state->bitboards[b] = 0x2400000000000000ULL; // Bishops on c8, f8
|
||||||
|
state->bitboards[r] = 0x8100000000000000ULL; // Rooks on a8, h8
|
||||||
|
state->bitboards[q] = 0x0800000000000000ULL; // Queen on d8
|
||||||
|
state->bitboards[k] = 0x1000000000000000ULL; // King on e8
|
||||||
|
|
||||||
|
// Initialize game state
|
||||||
|
state->side_to_move = 0; // White to move
|
||||||
|
state->castling = 15; // All castling rights available (1111 binary)
|
||||||
|
state->ep_square = -1; // No en passant
|
||||||
|
state->halfmove_clock = 0;
|
||||||
|
state->fullmove_number = 1;
|
||||||
|
|
||||||
|
update_occupancy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a move
|
||||||
|
void make_move(GameState *state, Move move, int *captured_pc, int *old_castling, int *old_ep) {
|
||||||
|
*old_castling = state->castling;
|
||||||
|
*old_ep = state->ep_square;
|
||||||
|
*captured_pc = NO_PIECE;
|
||||||
|
|
||||||
|
int side = state->side_to_move;
|
||||||
|
int from = move.from;
|
||||||
|
int to = move.to;
|
||||||
|
|
||||||
|
// Find the piece that is moving
|
||||||
|
int pc = NO_PIECE;
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (GET_BIT(state->bitboards[i], from)) {
|
||||||
|
pc = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pc == NO_PIECE) return; // Invalid move
|
||||||
|
|
||||||
|
// Handle en passant capture
|
||||||
|
if ((pc == P || pc == p) && to == state->ep_square) {
|
||||||
|
int cap_sq = to + (side == 0 ? -8 : 8);
|
||||||
|
int cap_pc = side == 0 ? p : P;
|
||||||
|
|
||||||
|
CLEAR_BIT(state->bitboards[cap_pc], cap_sq);
|
||||||
|
*captured_pc = cap_pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for regular capture
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (i != pc && GET_BIT(state->bitboards[i], to)) {
|
||||||
|
CLEAR_BIT(state->bitboards[i], to);
|
||||||
|
*captured_pc = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the piece
|
||||||
|
CLEAR_BIT(state->bitboards[pc], from);
|
||||||
|
|
||||||
|
// Handle promotion
|
||||||
|
if (move.promo && (pc == P || pc == p)) {
|
||||||
|
int promo_pc;
|
||||||
|
switch (move.promo) {
|
||||||
|
case 1: promo_pc = side == 0 ? N : n; break;
|
||||||
|
case 2: promo_pc = side == 0 ? B : b; break;
|
||||||
|
case 3: promo_pc = side == 0 ? R : r; break;
|
||||||
|
case 4: promo_pc = side == 0 ? Q : q; break;
|
||||||
|
default: promo_pc = side == 0 ? Q : q; break;
|
||||||
|
}
|
||||||
|
SET_BIT(state->bitboards[promo_pc], to);
|
||||||
|
} else {
|
||||||
|
SET_BIT(state->bitboards[pc], to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle castling - move the rook too
|
||||||
|
if (pc == K && from == e1) {
|
||||||
|
if (to == g1) { // Kingside castle
|
||||||
|
CLEAR_BIT(state->bitboards[R], h1);
|
||||||
|
SET_BIT(state->bitboards[R], f1);
|
||||||
|
} else if (to == c1) { // Queenside castle
|
||||||
|
CLEAR_BIT(state->bitboards[R], a1);
|
||||||
|
SET_BIT(state->bitboards[R], d1);
|
||||||
|
}
|
||||||
|
} else if (pc == k && from == e8) {
|
||||||
|
if (to == g8) { // Kingside castle
|
||||||
|
CLEAR_BIT(state->bitboards[r], h8);
|
||||||
|
SET_BIT(state->bitboards[r], f8);
|
||||||
|
} else if (to == c8) { // Queenside castle
|
||||||
|
CLEAR_BIT(state->bitboards[r], a8);
|
||||||
|
SET_BIT(state->bitboards[r], d8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update castling rights
|
||||||
|
if (pc == K) state->castling &= ~3; // Clear white castling rights
|
||||||
|
if (pc == k) state->castling &= ~12; // Clear black castling rights
|
||||||
|
if (from == a1 || to == a1) state->castling &= ~2; // White queenside
|
||||||
|
if (from == h1 || to == h1) state->castling &= ~1; // White kingside
|
||||||
|
if (from == a8 || to == a8) state->castling &= ~8; // Black queenside
|
||||||
|
if (from == h8 || to == h8) state->castling &= ~4; // Black kingside
|
||||||
|
|
||||||
|
// Set new en passant square
|
||||||
|
state->ep_square = -1;
|
||||||
|
if ((pc == P || pc == p) && abs(to - from) == 16) {
|
||||||
|
state->ep_square = (from + to) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update halfmove clock
|
||||||
|
if (pc == P || pc == p || *captured_pc != NO_PIECE) {
|
||||||
|
state->halfmove_clock = 0;
|
||||||
|
} else {
|
||||||
|
state->halfmove_clock++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update fullmove number
|
||||||
|
if (side == 1) {
|
||||||
|
state->fullmove_number++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch sides
|
||||||
|
state->side_to_move = 1 - side;
|
||||||
|
|
||||||
|
// Update occupancy
|
||||||
|
update_occupancy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undo a move
|
||||||
|
void undo_move(GameState *state, Move move, int captured_pc, int old_castling, int old_ep) {
|
||||||
|
int side = 1 - state->side_to_move; // Side that made the move
|
||||||
|
int from = move.from;
|
||||||
|
int to = move.to;
|
||||||
|
|
||||||
|
// Find the piece that moved
|
||||||
|
int pc = NO_PIECE;
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (GET_BIT(state->bitboards[i], to) && (
|
||||||
|
(side == 0 && i <= K) || (side == 1 && i >= p)
|
||||||
|
)) {
|
||||||
|
pc = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pc == NO_PIECE && !move.promo) return; // Invalid move
|
||||||
|
|
||||||
|
// Handle promotion (find the pawn)
|
||||||
|
if (move.promo) {
|
||||||
|
pc = side == 0 ? P : p;
|
||||||
|
int promo_pc;
|
||||||
|
switch (move.promo) {
|
||||||
|
case 1: promo_pc = side == 0 ? N : n; break;
|
||||||
|
case 2: promo_pc = side == 0 ? B : b; break;
|
||||||
|
case 3: promo_pc = side == 0 ? R : r; break;
|
||||||
|
case 4: promo_pc = side == 0 ? Q : q; break;
|
||||||
|
default: promo_pc = side == 0 ? Q : q; break;
|
||||||
|
}
|
||||||
|
CLEAR_BIT(state->bitboards[promo_pc], to);
|
||||||
|
} else {
|
||||||
|
CLEAR_BIT(state->bitboards[pc], to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the piece back
|
||||||
|
SET_BIT(state->bitboards[pc], from);
|
||||||
|
|
||||||
|
// Handle en passant capture
|
||||||
|
if ((pc == P || pc == p) && to == state->ep_square) {
|
||||||
|
int cap_sq = to + (side == 0 ? -8 : 8);
|
||||||
|
SET_BIT(state->bitboards[captured_pc], cap_sq);
|
||||||
|
}
|
||||||
|
// Restore captured piece
|
||||||
|
else if (captured_pc != NO_PIECE) {
|
||||||
|
SET_BIT(state->bitboards[captured_pc], to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle castling - move the rook back
|
||||||
|
if (pc == K && from == e1) {
|
||||||
|
if (to == g1) { // Kingside castle
|
||||||
|
CLEAR_BIT(state->bitboards[R], f1);
|
||||||
|
SET_BIT(state->bitboards[R], h1);
|
||||||
|
} else if (to == c1) { // Queenside castle
|
||||||
|
CLEAR_BIT(state->bitboards[R], d1);
|
||||||
|
SET_BIT(state->bitboards[R], a1);
|
||||||
|
}
|
||||||
|
} else if (pc == k && from == e8) {
|
||||||
|
if (to == g8) { // Kingside castle
|
||||||
|
CLEAR_BIT(state->bitboards[r], f8);
|
||||||
|
SET_BIT(state->bitboards[r], h8);
|
||||||
|
} else if (to == c8) { // Queenside castle
|
||||||
|
CLEAR_BIT(state->bitboards[r], d8);
|
||||||
|
SET_BIT(state->bitboards[r], a8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore castling rights
|
||||||
|
state->castling = old_castling;
|
||||||
|
|
||||||
|
// Restore en passant square
|
||||||
|
state->ep_square = old_ep;
|
||||||
|
|
||||||
|
// Adjust fullmove number
|
||||||
|
if (side == 1) {
|
||||||
|
state->fullmove_number--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch sides back
|
||||||
|
state->side_to_move = side;
|
||||||
|
|
||||||
|
// Update occupancy
|
||||||
|
update_occupancy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if king is in check
|
||||||
|
int is_king_in_check(const GameState *state, int side) {
|
||||||
|
int king_sq;
|
||||||
|
U64 king_bb = side == 0 ? state->bitboards[K] : state->bitboards[k];
|
||||||
|
|
||||||
|
if (!king_bb) return 0; // No king (shouldn't happen in a real game)
|
||||||
|
|
||||||
|
king_sq = __builtin_ctzll(king_bb);
|
||||||
|
return is_square_attacked(state, king_sq, 1 - side); // Is king attacked by opponent?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print board representation
|
||||||
|
void print_board(const GameState *state) {
|
||||||
|
char board[64];
|
||||||
|
memset(board, '.', 64);
|
||||||
|
|
||||||
|
for (int pc = 0; pc < 12; pc++) {
|
||||||
|
U64 bb = state->bitboards[pc];
|
||||||
|
while (bb) {
|
||||||
|
int sq = __builtin_ctzll(bb);
|
||||||
|
board[sq] = "PNBRQKpnbrqk"[pc];
|
||||||
|
CLEAR_BIT(bb, sq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n +---+---+---+---+---+---+---+---+\n");
|
||||||
|
for (int rank = 7; rank >= 0; rank--) {
|
||||||
|
printf("%d |", rank + 1);
|
||||||
|
for (int file = 0; file < 8; file++) {
|
||||||
|
printf(" %c |", board[rank * 8 + file]);
|
||||||
|
}
|
||||||
|
printf("\n +---+---+---+---+---+---+---+---+\n");
|
||||||
|
}
|
||||||
|
printf(" a b c d e f g h\n\n");
|
||||||
|
|
||||||
|
printf("Side to move: %s\n", state->side_to_move == 0 ? "White" : "Black");
|
||||||
|
printf("Fullmove number: %d\n", state->fullmove_number);
|
||||||
|
printf("Castling rights: %c%c%c%c\n",
|
||||||
|
(state->castling & 1) ? 'K' : '-',
|
||||||
|
(state->castling & 2) ? 'Q' : '-',
|
||||||
|
(state->castling & 4) ? 'k' : '-',
|
||||||
|
(state->castling & 8) ? 'q' : '-');
|
||||||
|
|
||||||
|
if (state->ep_square != -1) {
|
||||||
|
int file = state->ep_square % 8;
|
||||||
|
int rank = state->ep_square / 8;
|
||||||
|
printf("En passant: %c%d\n", 'a' + file, rank + 1);
|
||||||
|
} else {
|
||||||
|
printf("En passant: -\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
24
src/board.h
Normal file
24
src/board.h
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#ifndef BOARD_H
|
||||||
|
#define BOARD_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// Update occupancy bitboards
|
||||||
|
void update_occupancy(GameState *state);
|
||||||
|
|
||||||
|
// Initialize a standard chess starting position
|
||||||
|
void init_startpos(GameState *state);
|
||||||
|
|
||||||
|
// Make a move
|
||||||
|
void make_move(GameState *state, Move move, int *captured_pc, int *old_castling, int *old_ep);
|
||||||
|
|
||||||
|
// Undo a move
|
||||||
|
void undo_move(GameState *state, Move move, int captured_pc, int old_castling, int old_ep);
|
||||||
|
|
||||||
|
// Check if king is in check
|
||||||
|
int is_king_in_check(const GameState *state, int side);
|
||||||
|
|
||||||
|
// Print board representation
|
||||||
|
void print_board(const GameState *state);
|
||||||
|
|
||||||
|
#endif // BOARD_H
|
||||||
220
src/evaluation.c
Normal file
220
src/evaluation.c
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
#include "evaluation.h"
|
||||||
|
#include "movegen.h"
|
||||||
|
|
||||||
|
// Material values
|
||||||
|
const int piece_value[12] = {100, 320, 330, 500, 900, 20000,
|
||||||
|
-100, -320, -330, -500, -900, -20000};
|
||||||
|
|
||||||
|
// Piece-square tables for evaluation
|
||||||
|
const int pst_pawn[64] = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
50, 50, 50, 50, 50, 50, 50, 50,
|
||||||
|
10, 10, 20, 30, 30, 20, 10, 10,
|
||||||
|
5, 5, 10, 25, 25, 10, 5, 5,
|
||||||
|
0, 0, 0, 20, 20, 0, 0, 0,
|
||||||
|
5, -5,-10, 0, 0,-10, -5, 5,
|
||||||
|
5, 10, 10,-20,-20, 10, 10, 5,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const int pst_knight[64] = {
|
||||||
|
-50,-40,-30,-30,-30,-30,-40,-50,
|
||||||
|
-40,-20, 0, 0, 0, 0,-20,-40,
|
||||||
|
-30, 0, 10, 15, 15, 10, 0,-30,
|
||||||
|
-30, 5, 15, 20, 20, 15, 5,-30,
|
||||||
|
-30, 0, 15, 20, 20, 15, 0,-30,
|
||||||
|
-30, 5, 10, 15, 15, 10, 5,-30,
|
||||||
|
-40,-20, 0, 5, 5, 0,-20,-40,
|
||||||
|
-50,-40,-30,-30,-30,-30,-40,-50
|
||||||
|
};
|
||||||
|
|
||||||
|
const int pst_bishop[64] = {
|
||||||
|
-20,-10,-10,-10,-10,-10,-10,-20,
|
||||||
|
-10, 0, 0, 0, 0, 0, 0,-10,
|
||||||
|
-10, 0, 10, 10, 10, 10, 0,-10,
|
||||||
|
-10, 5, 5, 10, 10, 5, 5,-10,
|
||||||
|
-10, 0, 5, 10, 10, 5, 0,-10,
|
||||||
|
-10, 5, 5, 5, 5, 5, 5,-10,
|
||||||
|
-10, 0, 5, 0, 0, 5, 0,-10,
|
||||||
|
-20,-10,-10,-10,-10,-10,-10,-20
|
||||||
|
};
|
||||||
|
|
||||||
|
const int pst_rook[64] = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
5, 10, 10, 10, 10, 10, 10, 5,
|
||||||
|
-5, 0, 0, 0, 0, 0, 0, -5,
|
||||||
|
-5, 0, 0, 0, 0, 0, 0, -5,
|
||||||
|
-5, 0, 0, 0, 0, 0, 0, -5,
|
||||||
|
-5, 0, 0, 0, 0, 0, 0, -5,
|
||||||
|
-5, 0, 0, 0, 0, 0, 0, -5,
|
||||||
|
0, 0, 0, 5, 5, 0, 0, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const int pst_queen[64] = {
|
||||||
|
-20,-10,-10, -5, -5,-10,-10,-20,
|
||||||
|
-10, 0, 0, 0, 0, 0, 0,-10,
|
||||||
|
-10, 0, 5, 5, 5, 5, 0,-10,
|
||||||
|
-5, 0, 5, 5, 5, 5, 0, -5,
|
||||||
|
0, 0, 5, 5, 5, 5, 0, -5,
|
||||||
|
-10, 5, 5, 5, 5, 5, 0,-10,
|
||||||
|
-10, 0, 5, 0, 0, 0, 0,-10,
|
||||||
|
-20,-10,-10, -5, -5,-10,-10,-20
|
||||||
|
};
|
||||||
|
|
||||||
|
const int pst_king_mid[64] = {
|
||||||
|
-30,-40,-40,-50,-50,-40,-40,-30,
|
||||||
|
-30,-40,-40,-50,-50,-40,-40,-30,
|
||||||
|
-30,-40,-40,-50,-50,-40,-40,-30,
|
||||||
|
-30,-40,-40,-50,-50,-40,-40,-30,
|
||||||
|
-20,-30,-30,-40,-40,-30,-30,-20,
|
||||||
|
-10,-20,-20,-20,-20,-20,-20,-10,
|
||||||
|
20, 20, 0, 0, 0, 0, 20, 20,
|
||||||
|
20, 30, 10, 0, 0, 10, 30, 20
|
||||||
|
};
|
||||||
|
|
||||||
|
const int pst_king_end[64] = {
|
||||||
|
-50,-40,-30,-20,-20,-30,-40,-50,
|
||||||
|
-30,-20,-10, 0, 0,-10,-20,-30,
|
||||||
|
-30,-10, 20, 30, 30, 20,-10,-30,
|
||||||
|
-30,-10, 30, 40, 40, 30,-10,-30,
|
||||||
|
-30,-10, 30, 40, 40, 30,-10,-30,
|
||||||
|
-30,-10, 20, 30, 30, 20,-10,-30,
|
||||||
|
-30,-30, 0, 0, 0, 0,-30,-30,
|
||||||
|
-50,-30,-30,-30,-30,-30,-30,-50
|
||||||
|
};
|
||||||
|
|
||||||
|
// Evaluate material balance
|
||||||
|
int evaluate_material(const GameState *state) {
|
||||||
|
int score = 0;
|
||||||
|
|
||||||
|
// Material balance
|
||||||
|
for (int pc = P; pc <= K; pc++) {
|
||||||
|
score += piece_value[pc] * __builtin_popcountll(state->bitboards[pc]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int pc = p; pc <= k; pc++) {
|
||||||
|
score += piece_value[pc] * __builtin_popcountll(state->bitboards[pc]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate positional factors
|
||||||
|
int evaluate_position(const GameState *state) {
|
||||||
|
int score = 0;
|
||||||
|
int sq;
|
||||||
|
|
||||||
|
// Piece-square tables for positional evaluation
|
||||||
|
|
||||||
|
// White pawns
|
||||||
|
U64 wp = state->bitboards[P];
|
||||||
|
while (wp) {
|
||||||
|
sq = __builtin_ctzll(wp);
|
||||||
|
score += pst_pawn[sq];
|
||||||
|
CLEAR_BIT(wp, sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black pawns
|
||||||
|
U64 bp = state->bitboards[p];
|
||||||
|
while (bp) {
|
||||||
|
sq = __builtin_ctzll(bp);
|
||||||
|
score -= pst_pawn[63 - sq]; // Flip for black
|
||||||
|
CLEAR_BIT(bp, sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// White knights
|
||||||
|
U64 wn = state->bitboards[N];
|
||||||
|
while (wn) {
|
||||||
|
sq = __builtin_ctzll(wn);
|
||||||
|
score += pst_knight[sq];
|
||||||
|
CLEAR_BIT(wn, sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black knights
|
||||||
|
U64 bn = state->bitboards[n];
|
||||||
|
while (bn) {
|
||||||
|
sq = __builtin_ctzll(bn);
|
||||||
|
score -= pst_knight[63 - sq];
|
||||||
|
CLEAR_BIT(bn, sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// White bishops
|
||||||
|
U64 wb = state->bitboards[B];
|
||||||
|
while (wb) {
|
||||||
|
sq = __builtin_ctzll(wb);
|
||||||
|
score += pst_bishop[sq];
|
||||||
|
CLEAR_BIT(wb, sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black bishops
|
||||||
|
U64 bb = state->bitboards[b];
|
||||||
|
while (bb) {
|
||||||
|
sq = __builtin_ctzll(bb);
|
||||||
|
score -= pst_bishop[63 - sq];
|
||||||
|
CLEAR_BIT(bb, sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// White rooks
|
||||||
|
U64 wr = state->bitboards[R];
|
||||||
|
while (wr) {
|
||||||
|
sq = __builtin_ctzll(wr);
|
||||||
|
score += pst_rook[sq];
|
||||||
|
CLEAR_BIT(wr, sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black rooks
|
||||||
|
U64 br = state->bitboards[r];
|
||||||
|
while (br) {
|
||||||
|
sq = __builtin_ctzll(br);
|
||||||
|
score -= pst_rook[63 - sq];
|
||||||
|
CLEAR_BIT(br, sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// White queens
|
||||||
|
U64 wq = state->bitboards[Q];
|
||||||
|
while (wq) {
|
||||||
|
sq = __builtin_ctzll(wq);
|
||||||
|
score += pst_queen[sq];
|
||||||
|
CLEAR_BIT(wq, sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black queens
|
||||||
|
U64 bq = state->bitboards[q];
|
||||||
|
while (bq) {
|
||||||
|
sq = __builtin_ctzll(bq);
|
||||||
|
score -= pst_queen[63 - sq];
|
||||||
|
CLEAR_BIT(bq, sq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kings
|
||||||
|
if (state->bitboards[K]) {
|
||||||
|
sq = __builtin_ctzll(state->bitboards[K]);
|
||||||
|
score += pst_king_mid[sq];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->bitboards[k]) {
|
||||||
|
sq = __builtin_ctzll(state->bitboards[k]);
|
||||||
|
score -= pst_king_mid[63 - sq];
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete evaluation function
|
||||||
|
int evaluate(const GameState *state) {
|
||||||
|
int score = evaluate_material(state) + evaluate_position(state);
|
||||||
|
|
||||||
|
// Mobility bonus (simplified)
|
||||||
|
GameState temp_state = *state;
|
||||||
|
Move moves[256];
|
||||||
|
temp_state.side_to_move = 0; // White
|
||||||
|
int white_mobility = generate_moves(&temp_state, moves);
|
||||||
|
|
||||||
|
temp_state.side_to_move = 1; // Black
|
||||||
|
int black_mobility = generate_moves(&temp_state, moves);
|
||||||
|
|
||||||
|
score += (white_mobility - black_mobility) * 5; // 5 points per move advantage
|
||||||
|
|
||||||
|
// Adjust for side to move
|
||||||
|
return state->side_to_move == 0 ? score : -score;
|
||||||
|
}
|
||||||
27
src/evaluation.h
Normal file
27
src/evaluation.h
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef EVALUATION_H
|
||||||
|
#define EVALUATION_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// Material values
|
||||||
|
extern const int piece_value[12];
|
||||||
|
|
||||||
|
// Piece-square tables for evaluation
|
||||||
|
extern const int pst_pawn[64];
|
||||||
|
extern const int pst_knight[64];
|
||||||
|
extern const int pst_bishop[64];
|
||||||
|
extern const int pst_rook[64];
|
||||||
|
extern const int pst_queen[64];
|
||||||
|
extern const int pst_king_mid[64];
|
||||||
|
extern const int pst_king_end[64];
|
||||||
|
|
||||||
|
// Evaluate material balance
|
||||||
|
int evaluate_material(const GameState *state);
|
||||||
|
|
||||||
|
// Evaluate positional factors
|
||||||
|
int evaluate_position(const GameState *state);
|
||||||
|
|
||||||
|
// Complete evaluation function
|
||||||
|
int evaluate(const GameState *state);
|
||||||
|
|
||||||
|
#endif // EVALUATION_H
|
||||||
109
src/main.c
Normal file
109
src/main.c
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include "types.h"
|
||||||
|
#include "bitboard.h"
|
||||||
|
#include "board.h"
|
||||||
|
#include "movegen.h"
|
||||||
|
#include "evaluation.h"
|
||||||
|
#include "search.h"
|
||||||
|
#include "notation.h"
|
||||||
|
|
||||||
|
// Function to clear the terminal screen
|
||||||
|
void clear_screen() {
|
||||||
|
printf("\033[2J\033[H");
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to clear from cursor to end of screen
|
||||||
|
void clear_to_end_of_screen() {
|
||||||
|
printf("\033[J");
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to move cursor to the top-left corner
|
||||||
|
void move_cursor_to_top() {
|
||||||
|
printf("\033[H");
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to print the game state
|
||||||
|
void print_game_state(GameState *state, int depth, int move_number, char* move_san, int pre_eval) {
|
||||||
|
print_board(state);
|
||||||
|
|
||||||
|
if (move_number > 0) {
|
||||||
|
printf("Last move: %d%s: %s\nEvaluation: %+.2f\n",
|
||||||
|
move_number,
|
||||||
|
state->side_to_move == 1 ? "w" : "b", // opposite of current side
|
||||||
|
move_san,
|
||||||
|
pre_eval / 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Search depth: %d\n", depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main game loop
|
||||||
|
int main() {
|
||||||
|
// Initialize attack tables
|
||||||
|
init_attack_tables();
|
||||||
|
|
||||||
|
// Initialize game state
|
||||||
|
GameState state;
|
||||||
|
init_startpos(&state);
|
||||||
|
|
||||||
|
// Search depth (can be adjusted)
|
||||||
|
int depth = 5;
|
||||||
|
char last_move_san[10] = "";
|
||||||
|
|
||||||
|
// Clear the screen at the beginning
|
||||||
|
clear_screen();
|
||||||
|
print_game_state(&state, depth, 0, "", 0);
|
||||||
|
|
||||||
|
// Game loop
|
||||||
|
while (1) {
|
||||||
|
// Generate legal moves
|
||||||
|
Move moves[256];
|
||||||
|
int num_moves = generate_legal_moves(&state, moves);
|
||||||
|
|
||||||
|
// Check for checkmate or stalemate
|
||||||
|
if (num_moves == 0) {
|
||||||
|
move_cursor_to_top();
|
||||||
|
clear_to_end_of_screen();
|
||||||
|
print_game_state(&state, depth, state.fullmove_number, last_move_san, 0);
|
||||||
|
|
||||||
|
if (is_king_in_check(&state, state.side_to_move)) {
|
||||||
|
printf("Checkmate! %s wins.\n", state.side_to_move == 0 ? "Black" : "White");
|
||||||
|
} else {
|
||||||
|
printf("Stalemate! The game is a draw.\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Engine finds the best move
|
||||||
|
fflush(stdout);
|
||||||
|
Move best_move = find_best_move(&state, depth);
|
||||||
|
|
||||||
|
// Get SAN notation of the move
|
||||||
|
strcpy(last_move_san, move_to_san(&state, best_move));
|
||||||
|
|
||||||
|
// Get evaluation before making the move
|
||||||
|
int pre_eval = evaluate(&state);
|
||||||
|
int move_number = state.fullmove_number;
|
||||||
|
|
||||||
|
// Make the best move
|
||||||
|
int captured, old_castling, old_ep;
|
||||||
|
make_move(&state, best_move, &captured, &old_castling, &old_ep);
|
||||||
|
|
||||||
|
// Redraw the entire screen
|
||||||
|
move_cursor_to_top();
|
||||||
|
clear_to_end_of_screen();
|
||||||
|
print_game_state(&state, depth, move_number, last_move_san, pre_eval);
|
||||||
|
|
||||||
|
// Small delay between moves
|
||||||
|
usleep(500000); // 0.5 second
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
379
src/movegen.c
Normal file
379
src/movegen.c
Normal file
|
|
@ -0,0 +1,379 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "movegen.h"
|
||||||
|
#include "board.h"
|
||||||
|
#include "bitboard.h"
|
||||||
|
|
||||||
|
// Check if a square is attacked by a given side
|
||||||
|
int is_square_attacked(const GameState *state, int sq, int side) {
|
||||||
|
if (side == 0) { // Attacked by white
|
||||||
|
// Pawns
|
||||||
|
if (pawn_attacks[1][sq] & state->bitboards[P]) return 1;
|
||||||
|
|
||||||
|
// Knights
|
||||||
|
if (knight_attacks[sq] & state->bitboards[N]) return 1;
|
||||||
|
|
||||||
|
// King
|
||||||
|
if (king_attacks[sq] & state->bitboards[K]) return 1;
|
||||||
|
|
||||||
|
// Bishops & Queens (diagonal)
|
||||||
|
U64 bishops_queens = state->bitboards[B] | state->bitboards[Q];
|
||||||
|
if (bishops_queens) {
|
||||||
|
U64 bishop_attack = get_sliding_attacks(sq, state->occ_all, 1);
|
||||||
|
if (bishop_attack & bishops_queens) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rooks & Queens (orthogonal)
|
||||||
|
U64 rooks_queens = state->bitboards[R] | state->bitboards[Q];
|
||||||
|
if (rooks_queens) {
|
||||||
|
U64 rook_attack = get_sliding_attacks(sq, state->occ_all, 0);
|
||||||
|
if (rook_attack & rooks_queens) return 1;
|
||||||
|
}
|
||||||
|
} else { // Attacked by black
|
||||||
|
// Pawns
|
||||||
|
if (pawn_attacks[0][sq] & state->bitboards[p]) return 1;
|
||||||
|
|
||||||
|
// Knights
|
||||||
|
if (knight_attacks[sq] & state->bitboards[n]) return 1;
|
||||||
|
|
||||||
|
// King
|
||||||
|
if (king_attacks[sq] & state->bitboards[k]) return 1;
|
||||||
|
|
||||||
|
// Bishops & Queens (diagonal)
|
||||||
|
U64 bishops_queens = state->bitboards[b] | state->bitboards[q];
|
||||||
|
if (bishops_queens) {
|
||||||
|
U64 bishop_attack = get_sliding_attacks(sq, state->occ_all, 1);
|
||||||
|
if (bishop_attack & bishops_queens) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rooks & Queens (orthogonal)
|
||||||
|
U64 rooks_queens = state->bitboards[r] | state->bitboards[q];
|
||||||
|
if (rooks_queens) {
|
||||||
|
U64 rook_attack = get_sliding_attacks(sq, state->occ_all, 0);
|
||||||
|
if (rook_attack & rooks_queens) return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate pawn moves
|
||||||
|
int generate_pawn_moves(const GameState *state, Move *moves, int index) {
|
||||||
|
int count = index;
|
||||||
|
int side = state->side_to_move;
|
||||||
|
U64 pawns = side == 0 ? state->bitboards[P] : state->bitboards[p];
|
||||||
|
U64 enemy_occ = side == 0 ? state->occ_black : state->occ_white;
|
||||||
|
|
||||||
|
int direction = side == 0 ? 8 : -8; // Up for white, down for black
|
||||||
|
U64 promotion_rank = side == 0 ? 0xFF00000000000000ULL : 0x00000000000000FFULL;
|
||||||
|
U64 single_push_targets, double_push_targets;
|
||||||
|
|
||||||
|
if (side == 0) {
|
||||||
|
// White pawns move up (higher indices)
|
||||||
|
single_push_targets = (pawns << 8) & ~state->occ_all;
|
||||||
|
double_push_targets = ((single_push_targets & 0x0000000000FF0000ULL) << 8) & ~state->occ_all;
|
||||||
|
} else {
|
||||||
|
// Black pawns move down (lower indices)
|
||||||
|
single_push_targets = (pawns >> 8) & ~state->occ_all;
|
||||||
|
double_push_targets = ((single_push_targets & 0x0000FF0000000000ULL) >> 8) & ~state->occ_all;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single pushes and promotions
|
||||||
|
U64 temp = single_push_targets;
|
||||||
|
while (temp) {
|
||||||
|
int to = __builtin_ctzll(temp);
|
||||||
|
int from = to - direction;
|
||||||
|
|
||||||
|
// Check for promotion
|
||||||
|
if ((1ULL << to) & promotion_rank) {
|
||||||
|
// Add promotions to knight, bishop, rook, queen
|
||||||
|
moves[count++] = (Move){from, to, 1, 0}; // Knight
|
||||||
|
moves[count++] = (Move){from, to, 2, 0}; // Bishop
|
||||||
|
moves[count++] = (Move){from, to, 3, 0}; // Rook
|
||||||
|
moves[count++] = (Move){from, to, 4, 0}; // Queen
|
||||||
|
} else {
|
||||||
|
moves[count++] = (Move){from, to, 0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
CLEAR_BIT(temp, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double pushes
|
||||||
|
temp = double_push_targets;
|
||||||
|
while (temp) {
|
||||||
|
int to = __builtin_ctzll(temp);
|
||||||
|
int from = to - 2 * direction;
|
||||||
|
moves[count++] = (Move){from, to, 0, 0};
|
||||||
|
CLEAR_BIT(temp, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Captures to the left
|
||||||
|
U64 left_captures;
|
||||||
|
if (side == 0) {
|
||||||
|
// White pawns capture up-left
|
||||||
|
left_captures = ((pawns & ~0x0101010101010101ULL) << 7) & enemy_occ;
|
||||||
|
} else {
|
||||||
|
// Black pawns capture down-left
|
||||||
|
left_captures = ((pawns & ~0x0101010101010101ULL) >> 9) & enemy_occ;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp = left_captures;
|
||||||
|
while (temp) {
|
||||||
|
int to = __builtin_ctzll(temp);
|
||||||
|
int from = side == 0 ? to - 7 : to + 9;
|
||||||
|
|
||||||
|
// Check for promotion
|
||||||
|
if ((1ULL << to) & promotion_rank) {
|
||||||
|
moves[count++] = (Move){from, to, 1, 0}; // Knight
|
||||||
|
moves[count++] = (Move){from, to, 2, 0}; // Bishop
|
||||||
|
moves[count++] = (Move){from, to, 3, 0}; // Rook
|
||||||
|
moves[count++] = (Move){from, to, 4, 0}; // Queen
|
||||||
|
} else {
|
||||||
|
moves[count++] = (Move){from, to, 0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
CLEAR_BIT(temp, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Captures to the right
|
||||||
|
U64 right_captures;
|
||||||
|
if (side == 0) {
|
||||||
|
// White pawns capture up-right
|
||||||
|
right_captures = ((pawns & ~0x8080808080808080ULL) << 9) & enemy_occ;
|
||||||
|
} else {
|
||||||
|
// Black pawns capture down-right
|
||||||
|
right_captures = ((pawns & ~0x8080808080808080ULL) >> 7) & enemy_occ;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp = right_captures;
|
||||||
|
while (temp) {
|
||||||
|
int to = __builtin_ctzll(temp);
|
||||||
|
int from = side == 0 ? to - 9 : to + 7;
|
||||||
|
|
||||||
|
// Check for promotion
|
||||||
|
if ((1ULL << to) & promotion_rank) {
|
||||||
|
moves[count++] = (Move){from, to, 1, 0}; // Knight
|
||||||
|
moves[count++] = (Move){from, to, 2, 0}; // Bishop
|
||||||
|
moves[count++] = (Move){from, to, 3, 0}; // Rook
|
||||||
|
moves[count++] = (Move){from, to, 4, 0}; // Queen
|
||||||
|
} else {
|
||||||
|
moves[count++] = (Move){from, to, 0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
CLEAR_BIT(temp, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// En passant captures
|
||||||
|
if (state->ep_square != -1) {
|
||||||
|
U64 ep_pawns = pawns & pawn_attacks[1 - side][state->ep_square];
|
||||||
|
|
||||||
|
while (ep_pawns) {
|
||||||
|
int from = __builtin_ctzll(ep_pawns);
|
||||||
|
moves[count++] = (Move){from, state->ep_square, 0, 0};
|
||||||
|
CLEAR_BIT(ep_pawns, from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate knight moves
|
||||||
|
int generate_knight_moves(const GameState *state, Move *moves, int index) {
|
||||||
|
int count = index;
|
||||||
|
int side = state->side_to_move;
|
||||||
|
U64 knights = side == 0 ? state->bitboards[N] : state->bitboards[n];
|
||||||
|
U64 own_pieces = side == 0 ? state->occ_white : state->occ_black;
|
||||||
|
|
||||||
|
while (knights) {
|
||||||
|
int from = __builtin_ctzll(knights);
|
||||||
|
U64 targets = knight_attacks[from] & ~own_pieces;
|
||||||
|
|
||||||
|
while (targets) {
|
||||||
|
int to = __builtin_ctzll(targets);
|
||||||
|
moves[count++] = (Move){from, to, 0, 0};
|
||||||
|
CLEAR_BIT(targets, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
CLEAR_BIT(knights, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate bishop moves
|
||||||
|
int generate_bishop_moves(const GameState *state, Move *moves, int index) {
|
||||||
|
int count = index;
|
||||||
|
int side = state->side_to_move;
|
||||||
|
U64 bishops = side == 0 ? state->bitboards[B] : state->bitboards[b];
|
||||||
|
U64 own_pieces = side == 0 ? state->occ_white : state->occ_black;
|
||||||
|
|
||||||
|
while (bishops) {
|
||||||
|
int from = __builtin_ctzll(bishops);
|
||||||
|
U64 targets = get_sliding_attacks(from, state->occ_all, 1) & ~own_pieces;
|
||||||
|
|
||||||
|
while (targets) {
|
||||||
|
int to = __builtin_ctzll(targets);
|
||||||
|
moves[count++] = (Move){from, to, 0, 0};
|
||||||
|
CLEAR_BIT(targets, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
CLEAR_BIT(bishops, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate rook moves
|
||||||
|
int generate_rook_moves(const GameState *state, Move *moves, int index) {
|
||||||
|
int count = index;
|
||||||
|
int side = state->side_to_move;
|
||||||
|
U64 rooks = side == 0 ? state->bitboards[R] : state->bitboards[r];
|
||||||
|
U64 own_pieces = side == 0 ? state->occ_white : state->occ_black;
|
||||||
|
|
||||||
|
while (rooks) {
|
||||||
|
int from = __builtin_ctzll(rooks);
|
||||||
|
U64 targets = get_sliding_attacks(from, state->occ_all, 0) & ~own_pieces;
|
||||||
|
|
||||||
|
while (targets) {
|
||||||
|
int to = __builtin_ctzll(targets);
|
||||||
|
moves[count++] = (Move){from, to, 0, 0};
|
||||||
|
CLEAR_BIT(targets, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
CLEAR_BIT(rooks, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate queen moves
|
||||||
|
int generate_queen_moves(const GameState *state, Move *moves, int index) {
|
||||||
|
int count = index;
|
||||||
|
int side = state->side_to_move;
|
||||||
|
U64 queens = side == 0 ? state->bitboards[Q] : state->bitboards[q];
|
||||||
|
U64 own_pieces = side == 0 ? state->occ_white : state->occ_black;
|
||||||
|
|
||||||
|
while (queens) {
|
||||||
|
int from = __builtin_ctzll(queens);
|
||||||
|
|
||||||
|
// Combine bishop and rook moves for queen
|
||||||
|
U64 bishop_targets = get_sliding_attacks(from, state->occ_all, 1);
|
||||||
|
U64 rook_targets = get_sliding_attacks(from, state->occ_all, 0);
|
||||||
|
U64 targets = (bishop_targets | rook_targets) & ~own_pieces;
|
||||||
|
|
||||||
|
while (targets) {
|
||||||
|
int to = __builtin_ctzll(targets);
|
||||||
|
moves[count++] = (Move){from, to, 0, 0};
|
||||||
|
CLEAR_BIT(targets, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
CLEAR_BIT(queens, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate king moves
|
||||||
|
int generate_king_moves(const GameState *state, Move *moves, int index) {
|
||||||
|
int count = index;
|
||||||
|
int side = state->side_to_move;
|
||||||
|
U64 king = side == 0 ? state->bitboards[K] : state->bitboards[k];
|
||||||
|
U64 own_pieces = side == 0 ? state->occ_white : state->occ_black;
|
||||||
|
|
||||||
|
if (king == 0) return count; // No king (shouldn't happen)
|
||||||
|
|
||||||
|
int from = __builtin_ctzll(king);
|
||||||
|
U64 targets = king_attacks[from] & ~own_pieces;
|
||||||
|
|
||||||
|
while (targets) {
|
||||||
|
int to = __builtin_ctzll(targets);
|
||||||
|
moves[count++] = (Move){from, to, 0, 0};
|
||||||
|
CLEAR_BIT(targets, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Castling
|
||||||
|
if (side == 0) {
|
||||||
|
// White kingside castling
|
||||||
|
if ((state->castling & 1) &&
|
||||||
|
!(state->occ_all & 0x0000000000000060ULL) &&
|
||||||
|
!is_square_attacked(state, e1, 1) &&
|
||||||
|
!is_square_attacked(state, f1, 1) &&
|
||||||
|
!is_square_attacked(state, g1, 1)) {
|
||||||
|
moves[count++] = (Move){e1, g1, 0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// White queenside castling
|
||||||
|
if ((state->castling & 2) &&
|
||||||
|
!(state->occ_all & 0x000000000000000EULL) &&
|
||||||
|
!is_square_attacked(state, e1, 1) &&
|
||||||
|
!is_square_attacked(state, d1, 1) &&
|
||||||
|
!is_square_attacked(state, c1, 1)) {
|
||||||
|
moves[count++] = (Move){e1, c1, 0, 0};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Black kingside castling
|
||||||
|
if ((state->castling & 4) &&
|
||||||
|
!(state->occ_all & 0x6000000000000000ULL) &&
|
||||||
|
!is_square_attacked(state, e8, 0) &&
|
||||||
|
!is_square_attacked(state, f8, 0) &&
|
||||||
|
!is_square_attacked(state, g8, 0)) {
|
||||||
|
moves[count++] = (Move){e8, g8, 0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black queenside castling
|
||||||
|
if ((state->castling & 8) &&
|
||||||
|
!(state->occ_all & 0x0E00000000000000ULL) &&
|
||||||
|
!is_square_attacked(state, e8, 0) &&
|
||||||
|
!is_square_attacked(state, d8, 0) &&
|
||||||
|
!is_square_attacked(state, c8, 0)) {
|
||||||
|
moves[count++] = (Move){e8, c8, 0, 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate all pseudo-legal moves
|
||||||
|
int generate_moves(const GameState *state, Move *moves) {
|
||||||
|
int count = 0;
|
||||||
|
count = generate_pawn_moves(state, moves, count);
|
||||||
|
count = generate_knight_moves(state, moves, count);
|
||||||
|
count = generate_bishop_moves(state, moves, count);
|
||||||
|
count = generate_rook_moves(state, moves, count);
|
||||||
|
count = generate_queen_moves(state, moves, count);
|
||||||
|
count = generate_king_moves(state, moves, count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a move is legal (doesn't leave king in check)
|
||||||
|
int is_move_legal(GameState *state, Move move) {
|
||||||
|
int side = state->side_to_move;
|
||||||
|
int captured_pc = NO_PIECE;
|
||||||
|
int old_castling = state->castling;
|
||||||
|
int old_ep = state->ep_square;
|
||||||
|
|
||||||
|
// Make the move
|
||||||
|
make_move(state, move, &captured_pc, &old_castling, &old_ep);
|
||||||
|
|
||||||
|
// Check if king is in check after the move
|
||||||
|
int in_check = is_king_in_check(state, side);
|
||||||
|
|
||||||
|
// Undo the move
|
||||||
|
undo_move(state, move, captured_pc, old_castling, old_ep);
|
||||||
|
|
||||||
|
return !in_check;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate all legal moves
|
||||||
|
int generate_legal_moves(GameState *state, Move *moves) {
|
||||||
|
Move pseudo_moves[256];
|
||||||
|
int count = generate_moves(state, pseudo_moves);
|
||||||
|
int legal_count = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (is_move_legal(state, pseudo_moves[i])) {
|
||||||
|
moves[legal_count++] = pseudo_moves[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return legal_count;
|
||||||
|
}
|
||||||
36
src/movegen.h
Normal file
36
src/movegen.h
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#ifndef MOVEGEN_H
|
||||||
|
#define MOVEGEN_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// Check if a square is attacked by a given side
|
||||||
|
int is_square_attacked(const GameState *state, int sq, int side);
|
||||||
|
|
||||||
|
// Generate pawn moves
|
||||||
|
int generate_pawn_moves(const GameState *state, Move *moves, int index);
|
||||||
|
|
||||||
|
// Generate knight moves
|
||||||
|
int generate_knight_moves(const GameState *state, Move *moves, int index);
|
||||||
|
|
||||||
|
// Generate bishop moves
|
||||||
|
int generate_bishop_moves(const GameState *state, Move *moves, int index);
|
||||||
|
|
||||||
|
// Generate rook moves
|
||||||
|
int generate_rook_moves(const GameState *state, Move *moves, int index);
|
||||||
|
|
||||||
|
// Generate queen moves
|
||||||
|
int generate_queen_moves(const GameState *state, Move *moves, int index);
|
||||||
|
|
||||||
|
// Generate king moves
|
||||||
|
int generate_king_moves(const GameState *state, Move *moves, int index);
|
||||||
|
|
||||||
|
// Generate all pseudo-legal moves
|
||||||
|
int generate_moves(const GameState *state, Move *moves);
|
||||||
|
|
||||||
|
// Check if a move is legal (doesn't leave king in check)
|
||||||
|
int is_move_legal(GameState *state, Move move);
|
||||||
|
|
||||||
|
// Generate all legal moves
|
||||||
|
int generate_legal_moves(GameState *state, Move *moves);
|
||||||
|
|
||||||
|
#endif // MOVEGEN_H
|
||||||
273
src/notation.c
Normal file
273
src/notation.c
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "notation.h"
|
||||||
|
#include "board.h"
|
||||||
|
#include "movegen.h"
|
||||||
|
|
||||||
|
// Convert a move to SAN notation
|
||||||
|
char* move_to_san(const GameState *state, Move move) {
|
||||||
|
static char san[10];
|
||||||
|
int from_file = move.from % 8;
|
||||||
|
int from_rank = move.from / 8;
|
||||||
|
int to_file = move.to % 8;
|
||||||
|
int to_rank = move.to / 8;
|
||||||
|
|
||||||
|
// Find the moving piece
|
||||||
|
int pc = NO_PIECE;
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (GET_BIT(state->bitboards[i], move.from)) {
|
||||||
|
pc = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pc == NO_PIECE) return "error";
|
||||||
|
|
||||||
|
// Handle castling
|
||||||
|
if ((pc == K || pc == k) && abs(move.to - move.from) == 2) {
|
||||||
|
if (to_file > from_file) {
|
||||||
|
strcpy(san, "O-O"); // Kingside castle
|
||||||
|
} else {
|
||||||
|
strcpy(san, "O-O-O"); // Queenside castle
|
||||||
|
}
|
||||||
|
return san;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal moves
|
||||||
|
char piece_char = ' ';
|
||||||
|
if (pc != P && pc != p) {
|
||||||
|
piece_char = "NBRQK"[pc % 6];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if capture
|
||||||
|
int is_capture = 0;
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (i != pc && GET_BIT(state->bitboards[i], move.to)) {
|
||||||
|
is_capture = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// En passant capture
|
||||||
|
if ((pc == P || pc == p) && move.to == state->ep_square) {
|
||||||
|
is_capture = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we need file/rank disambiguation
|
||||||
|
int need_file = 0, need_rank = 0;
|
||||||
|
if (pc != P && pc != p) {
|
||||||
|
// Find if other pieces of same type can move to same square
|
||||||
|
GameState test_state = *state;
|
||||||
|
Move test_moves[256];
|
||||||
|
int num_moves = generate_legal_moves(&test_state, test_moves);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_moves; i++) {
|
||||||
|
if (test_moves[i].to == move.to && test_moves[i].from != move.from) {
|
||||||
|
// See if it's the same piece type
|
||||||
|
for (int p = 0; p < 12; p++) {
|
||||||
|
if (GET_BIT(state->bitboards[p], test_moves[i].from) && p == pc) {
|
||||||
|
int other_file = test_moves[i].from % 8;
|
||||||
|
int other_rank = test_moves[i].from / 8;
|
||||||
|
|
||||||
|
if (other_file == from_file) {
|
||||||
|
need_rank = 1;
|
||||||
|
} else if (other_rank == from_rank) {
|
||||||
|
need_file = 1;
|
||||||
|
} else {
|
||||||
|
need_file = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the SAN string
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
// Add piece letter except for pawns
|
||||||
|
if (piece_char != ' ') {
|
||||||
|
san[idx++] = piece_char;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add disambiguation if needed
|
||||||
|
if (need_file) {
|
||||||
|
san[idx++] = 'a' + from_file;
|
||||||
|
}
|
||||||
|
if (need_rank) {
|
||||||
|
san[idx++] = '1' + from_rank;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For pawns capturing, need to specify the file
|
||||||
|
if ((pc == P || pc == p) && is_capture) {
|
||||||
|
san[idx++] = 'a' + from_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add capture symbol
|
||||||
|
if (is_capture) {
|
||||||
|
san[idx++] = 'x';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add destination square
|
||||||
|
san[idx++] = 'a' + to_file;
|
||||||
|
san[idx++] = '1' + to_rank;
|
||||||
|
|
||||||
|
// Add promotion
|
||||||
|
if (move.promo) {
|
||||||
|
san[idx++] = '=';
|
||||||
|
san[idx++] = "NBRQ"[move.promo - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for check or checkmate
|
||||||
|
GameState after_state = *state;
|
||||||
|
int captured, old_castling, old_ep;
|
||||||
|
make_move(&after_state, move, &captured, &old_castling, &old_ep);
|
||||||
|
|
||||||
|
if (is_king_in_check(&after_state, after_state.side_to_move)) {
|
||||||
|
// Check if it's checkmate
|
||||||
|
Move next_moves[256];
|
||||||
|
int num_next_moves = generate_legal_moves(&after_state, next_moves);
|
||||||
|
|
||||||
|
if (num_next_moves == 0) {
|
||||||
|
san[idx++] = '#'; // Checkmate
|
||||||
|
} else {
|
||||||
|
san[idx++] = '+'; // Check
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
san[idx] = '\0';
|
||||||
|
return san;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse SAN notation into a Move
|
||||||
|
Move parse_san(const GameState *state, const char *san) {
|
||||||
|
Move result = {0, 0, 0, 0};
|
||||||
|
int side = state->side_to_move;
|
||||||
|
|
||||||
|
// Handle castling
|
||||||
|
if (strcmp(san, "O-O") == 0 || strcmp(san, "0-0") == 0) {
|
||||||
|
if (side == 0) {
|
||||||
|
result.from = e1;
|
||||||
|
result.to = g1;
|
||||||
|
} else {
|
||||||
|
result.from = e8;
|
||||||
|
result.to = g8;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(san, "O-O-O") == 0 || strcmp(san, "0-0-0") == 0) {
|
||||||
|
if (side == 0) {
|
||||||
|
result.from = e1;
|
||||||
|
result.to = c1;
|
||||||
|
} else {
|
||||||
|
result.from = e8;
|
||||||
|
result.to = c8;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the move
|
||||||
|
int len = strlen(san);
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
// Piece type
|
||||||
|
int piece_type = P; // Default to pawn
|
||||||
|
if (san[idx] >= 'A' && san[idx] <= 'Z') {
|
||||||
|
switch (san[idx]) {
|
||||||
|
case 'N': piece_type = N; break;
|
||||||
|
case 'B': piece_type = B; break;
|
||||||
|
case 'R': piece_type = R; break;
|
||||||
|
case 'Q': piece_type = Q; break;
|
||||||
|
case 'K': piece_type = K; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
if (side == 1) piece_type += 6; // Adjust for black pieces
|
||||||
|
|
||||||
|
// Source file and rank disambiguation
|
||||||
|
int source_file = -1, source_rank = -1;
|
||||||
|
|
||||||
|
// Look for disambiguating info before the target or capture symbol
|
||||||
|
while (idx < len) {
|
||||||
|
char c = san[idx];
|
||||||
|
|
||||||
|
if (c >= 'a' && c <= 'h') {
|
||||||
|
source_file = c - 'a';
|
||||||
|
idx++;
|
||||||
|
} else if (c >= '1' && c <= '8') {
|
||||||
|
source_rank = c - '1';
|
||||||
|
idx++;
|
||||||
|
} else if (c == 'x') {
|
||||||
|
// Skip capture symbol
|
||||||
|
idx++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target square
|
||||||
|
if (idx + 1 >= len) return result; // Invalid SAN
|
||||||
|
|
||||||
|
int target_file = san[idx] - 'a';
|
||||||
|
int target_rank = san[idx + 1] - '1';
|
||||||
|
idx += 2;
|
||||||
|
|
||||||
|
if (target_file < 0 || target_file > 7 || target_rank < 0 || target_rank > 7) {
|
||||||
|
return result; // Invalid target square
|
||||||
|
}
|
||||||
|
|
||||||
|
int target_square = target_rank * 8 + target_file;
|
||||||
|
result.to = target_square;
|
||||||
|
|
||||||
|
// Promotion
|
||||||
|
if (idx < len && san[idx] == '=') {
|
||||||
|
idx++;
|
||||||
|
if (idx < len) {
|
||||||
|
switch (san[idx]) {
|
||||||
|
case 'N': result.promo = 1; break;
|
||||||
|
case 'B': result.promo = 2; break;
|
||||||
|
case 'R': result.promo = 3; break;
|
||||||
|
case 'Q': result.promo = 4; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the source square
|
||||||
|
Move legal_moves[256];
|
||||||
|
int num_legal_moves = generate_legal_moves((GameState*)state, legal_moves);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_legal_moves; i++) {
|
||||||
|
Move m = legal_moves[i];
|
||||||
|
|
||||||
|
if (m.to == result.to && m.promo == result.promo) {
|
||||||
|
// Check if this move matches our piece type and any source constraints
|
||||||
|
int from_square = m.from;
|
||||||
|
int from_file = from_square % 8;
|
||||||
|
int from_rank = from_square / 8;
|
||||||
|
|
||||||
|
for (int p = 0; p < 12; p++) {
|
||||||
|
if (GET_BIT(state->bitboards[p], from_square)) {
|
||||||
|
if (p == piece_type &&
|
||||||
|
(source_file == -1 || source_file == from_file) &&
|
||||||
|
(source_rank == -1 || source_rank == from_rank)) {
|
||||||
|
result.from = from_square;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result; // If no matching move was found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print move in algebraic notation
|
||||||
|
void print_move(const GameState *state, Move move) {
|
||||||
|
printf("%s", move_to_san(state, move));
|
||||||
|
}
|
||||||
15
src/notation.h
Normal file
15
src/notation.h
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef NOTATION_H
|
||||||
|
#define NOTATION_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// Convert a move to SAN notation
|
||||||
|
char* move_to_san(const GameState *state, Move move);
|
||||||
|
|
||||||
|
// Parse SAN notation into a Move
|
||||||
|
Move parse_san(const GameState *state, const char *san);
|
||||||
|
|
||||||
|
// Print move in algebraic notation
|
||||||
|
void print_move(const GameState *state, Move move);
|
||||||
|
|
||||||
|
#endif // NOTATION_H
|
||||||
202
src/search.c
Normal file
202
src/search.c
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "search.h"
|
||||||
|
#include "evaluation.h"
|
||||||
|
#include "movegen.h"
|
||||||
|
#include "board.h"
|
||||||
|
|
||||||
|
// Order moves to improve alpha-beta pruning
|
||||||
|
void order_moves(Move *moves, int num_moves, const GameState *state) {
|
||||||
|
// Simple move ordering:
|
||||||
|
// 1. Captures (with MVV-LVA: Most Valuable Victim - Least Valuable Aggressor)
|
||||||
|
// 2. Promotions
|
||||||
|
// 3. Checks
|
||||||
|
// 4. Other moves
|
||||||
|
|
||||||
|
int scores[256] = {0};
|
||||||
|
|
||||||
|
for (int i = 0; i < num_moves; i++) {
|
||||||
|
int from = moves[i].from;
|
||||||
|
int to = moves[i].to;
|
||||||
|
int moving_piece = NO_PIECE;
|
||||||
|
int captured_piece = NO_PIECE;
|
||||||
|
|
||||||
|
// Find the moving piece
|
||||||
|
for (int p = 0; p < 12; p++) {
|
||||||
|
if (GET_BIT(state->bitboards[p], from)) {
|
||||||
|
moving_piece = p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find captured piece
|
||||||
|
for (int p = 0; p < 12; p++) {
|
||||||
|
if (GET_BIT(state->bitboards[p], to)) {
|
||||||
|
captured_piece = p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// En passant capture
|
||||||
|
if ((moving_piece == P || moving_piece == p) &&
|
||||||
|
to == state->ep_square) {
|
||||||
|
captured_piece = state->side_to_move == 0 ? p : P;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base score
|
||||||
|
if (captured_piece != NO_PIECE) {
|
||||||
|
// MVV-LVA: score = 10 * victim_value - attacker_value
|
||||||
|
int victim_value = abs(piece_value[captured_piece]) / 100;
|
||||||
|
int attacker_value = abs(piece_value[moving_piece]) / 100;
|
||||||
|
scores[i] = 10 * victim_value - attacker_value + 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Promotion bonus
|
||||||
|
if (moves[i].promo) {
|
||||||
|
scores[i] += 800 + moves[i].promo * 10; // Queen promo gets highest score
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if move gives check (simplified, could be more efficient)
|
||||||
|
GameState check_state = *state;
|
||||||
|
int captured, old_castling, old_ep;
|
||||||
|
make_move(&check_state, moves[i], &captured, &old_castling, &old_ep);
|
||||||
|
if (is_king_in_check(&check_state, check_state.side_to_move)) {
|
||||||
|
scores[i] += 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort moves based on scores (simple insertion sort)
|
||||||
|
for (int i = 1; i < num_moves; i++) {
|
||||||
|
Move temp_move = moves[i];
|
||||||
|
int temp_score = scores[i];
|
||||||
|
int j = i - 1;
|
||||||
|
|
||||||
|
while (j >= 0 && scores[j] < temp_score) {
|
||||||
|
moves[j + 1] = moves[j];
|
||||||
|
scores[j + 1] = scores[j];
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
|
||||||
|
moves[j + 1] = temp_move;
|
||||||
|
scores[j + 1] = temp_score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negamax with alpha-beta pruning
|
||||||
|
int minimax(GameState *state, int depth, int alpha, int beta) {
|
||||||
|
// Check if we're at a leaf node
|
||||||
|
if (depth == 0) {
|
||||||
|
return evaluate(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate and count legal moves
|
||||||
|
Move moves[256];
|
||||||
|
int num_moves = generate_legal_moves(state, moves);
|
||||||
|
|
||||||
|
// Check for checkmate or stalemate
|
||||||
|
if (num_moves == 0) {
|
||||||
|
if (is_king_in_check(state, state->side_to_move)) {
|
||||||
|
return -30000 + (10 - depth); // Checkmate (prefer faster checkmate)
|
||||||
|
} else {
|
||||||
|
return 0; // Stalemate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order moves to improve alpha-beta pruning
|
||||||
|
order_moves(moves, num_moves, state);
|
||||||
|
|
||||||
|
int best_score = -32000;
|
||||||
|
|
||||||
|
// Try each move
|
||||||
|
for (int i = 0; i < num_moves; i++) {
|
||||||
|
int captured, old_castling, old_ep;
|
||||||
|
|
||||||
|
make_move(state, moves[i], &captured, &old_castling, &old_ep);
|
||||||
|
int score = -minimax(state, depth - 1, -beta, -alpha);
|
||||||
|
undo_move(state, moves[i], captured, old_castling, old_ep);
|
||||||
|
|
||||||
|
if (score > best_score) {
|
||||||
|
best_score = score;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best_score > alpha) {
|
||||||
|
alpha = best_score;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alpha >= beta) {
|
||||||
|
break; // Beta cutoff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best_score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread function for search
|
||||||
|
void *thread_search(void *arg) {
|
||||||
|
ThreadArg *thread_arg = (ThreadArg *)arg;
|
||||||
|
GameState *state = &thread_arg->state;
|
||||||
|
int captured, old_castling, old_ep;
|
||||||
|
|
||||||
|
// Make the move
|
||||||
|
make_move(state, thread_arg->move, &captured, &old_castling, &old_ep);
|
||||||
|
|
||||||
|
// Search from this position
|
||||||
|
thread_arg->eval = -minimax(state, thread_arg->depth - 1, -30000, 30000);
|
||||||
|
|
||||||
|
// Lock mutex before checking if this is the best move
|
||||||
|
pthread_mutex_lock(thread_arg->mutex);
|
||||||
|
|
||||||
|
if (thread_arg->eval > *(thread_arg->best_eval)) {
|
||||||
|
*(thread_arg->best_eval) = thread_arg->eval;
|
||||||
|
*(thread_arg->best_move) = thread_arg->move;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(thread_arg->mutex);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find best move using multithreaded search
|
||||||
|
Move find_best_move(GameState *state, int depth) {
|
||||||
|
Move moves[256];
|
||||||
|
int num_moves = generate_legal_moves(state, moves);
|
||||||
|
|
||||||
|
if (num_moves == 0) {
|
||||||
|
// No legal moves
|
||||||
|
return (Move){0, 0, 0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order moves to search promising moves first
|
||||||
|
order_moves(moves, num_moves, state);
|
||||||
|
|
||||||
|
// Set up threads
|
||||||
|
pthread_t threads[256];
|
||||||
|
ThreadArg thread_args[256];
|
||||||
|
|
||||||
|
// Shared variables for best move
|
||||||
|
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
int best_eval = -32000;
|
||||||
|
Move best_move = moves[0]; // Default to first move
|
||||||
|
|
||||||
|
// Start a thread for each move
|
||||||
|
for (int i = 0; i < num_moves; i++) {
|
||||||
|
// Make a copy of the game state for each thread
|
||||||
|
thread_args[i].state = *state;
|
||||||
|
thread_args[i].move = moves[i];
|
||||||
|
thread_args[i].depth = depth;
|
||||||
|
thread_args[i].mutex = &mutex;
|
||||||
|
thread_args[i].best_eval = &best_eval;
|
||||||
|
thread_args[i].best_move = &best_move;
|
||||||
|
|
||||||
|
pthread_create(&threads[i], NULL, thread_search, &thread_args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all threads to finish
|
||||||
|
for (int i = 0; i < num_moves; i++) {
|
||||||
|
pthread_join(threads[i], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up mutex
|
||||||
|
pthread_mutex_destroy(&mutex);
|
||||||
|
|
||||||
|
return best_move;
|
||||||
|
}
|
||||||
19
src/search.h
Normal file
19
src/search.h
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef SEARCH_H
|
||||||
|
#define SEARCH_H
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
// Order moves to improve alpha-beta pruning
|
||||||
|
void order_moves(Move *moves, int num_moves, const GameState *state);
|
||||||
|
|
||||||
|
// Negamax with alpha-beta pruning
|
||||||
|
int minimax(GameState *state, int depth, int alpha, int beta);
|
||||||
|
|
||||||
|
// Thread function for search
|
||||||
|
void *thread_search(void *arg);
|
||||||
|
|
||||||
|
// Find best move using multithreaded search
|
||||||
|
Move find_best_move(GameState *state, int depth);
|
||||||
|
|
||||||
|
#endif // SEARCH_H
|
||||||
66
src/types.h
Normal file
66
src/types.h
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
#ifndef TYPES_H
|
||||||
|
#define TYPES_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
// Type for bitboard
|
||||||
|
typedef uint64_t U64;
|
||||||
|
|
||||||
|
// Piece indices
|
||||||
|
enum Piece {
|
||||||
|
P, N, B, R, Q, K, // White: pawn, knight, bishop, rook, queen, king
|
||||||
|
p, n, b, r, q, k, // Black
|
||||||
|
NO_PIECE
|
||||||
|
};
|
||||||
|
|
||||||
|
// Squares
|
||||||
|
enum Square {
|
||||||
|
a1, b1, c1, d1, e1, f1, g1, h1,
|
||||||
|
a2, b2, c2, d2, e2, f2, g2, h2,
|
||||||
|
a3, b3, c3, d3, e3, f3, g3, h3,
|
||||||
|
a4, b4, c4, d4, e4, f4, g4, h4,
|
||||||
|
a5, b5, c5, d5, e5, f5, g5, h5,
|
||||||
|
a6, b6, c6, d6, e6, f6, g6, h6,
|
||||||
|
a7, b7, c7, d7, e7, f7, g7, h7,
|
||||||
|
a8, b8, c8, d8, e8, f8, g8, h8,
|
||||||
|
NO_SQ
|
||||||
|
};
|
||||||
|
|
||||||
|
// Move structure
|
||||||
|
typedef struct {
|
||||||
|
int from;
|
||||||
|
int to;
|
||||||
|
int promo; // promotion piece or 0
|
||||||
|
int score; // for move ordering
|
||||||
|
} Move;
|
||||||
|
|
||||||
|
// Game state
|
||||||
|
typedef struct {
|
||||||
|
U64 bitboards[12];
|
||||||
|
U64 occ_white, occ_black, occ_all;
|
||||||
|
int side_to_move; // 0 white, 1 black
|
||||||
|
int castling; // KQkq permissions (1-8)
|
||||||
|
int ep_square; // en passant target square
|
||||||
|
int halfmove_clock; // for 50-move rule
|
||||||
|
int fullmove_number; // starts at 1, increments after black's move
|
||||||
|
} GameState;
|
||||||
|
|
||||||
|
// Thread argument structure
|
||||||
|
typedef struct {
|
||||||
|
GameState state;
|
||||||
|
Move move;
|
||||||
|
int depth;
|
||||||
|
int eval;
|
||||||
|
pthread_mutex_t *mutex;
|
||||||
|
int *best_eval;
|
||||||
|
Move *best_move;
|
||||||
|
} ThreadArg;
|
||||||
|
|
||||||
|
// Helper macros
|
||||||
|
#define GET_BIT(bb, sq) (((bb) >> (sq)) & 1ULL)
|
||||||
|
#define SET_BIT(bb, sq) ((bb) |= (1ULL << (sq)))
|
||||||
|
#define CLEAR_BIT(bb, sq) ((bb) &= ~(1ULL << (sq)))
|
||||||
|
#define SQUARE(rank, file) ((rank) * 8 + (file))
|
||||||
|
|
||||||
|
#endif // TYPES_H
|
||||||
Loading…
Add table
Add a link
Reference in a new issue