first commit

This commit is contained in:
srtk 2025-04-28 12:09:05 +05:30
commit a2ff70d4c1
17 changed files with 2004 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
build/
.vscode/

30
Makefile Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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