Skip to content

Commit

Permalink
merge branch develop
Browse files Browse the repository at this point in the history
  • Loading branch information
MattEstHaut committed Apr 9, 2024
2 parents 2ee665e + d3d9aa5 commit 5f52eba
Show file tree
Hide file tree
Showing 13 changed files with 2,154 additions and 3 deletions.
63 changes: 61 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,62 @@
# Victoire
# Victoire ⚔️

Victoire est un moteur d'échecs développé en [Zig](https://ziglang.org/).
**Victoire** is a [UCI](https://github.com/nomemory/uci-protocol-specification) chess engine written in [Zig](https://ziglang.org/). It uses **pruning** and **reduction** techniques to reduce the search space of the **principal variation search** (**PVS**). And several other methods to accelerate the search or enhance evaluation, such as the use of **transposition tables**, **quiescence search**, **tapered evaluation**...

## ⚡️ Quick start

First, download and install **Zig** from the [official website](https://ziglang.org/download/).

> **⚠️ Note**: version **`0.12.0`** is recommended.
Next, clone this repository and build the project using the command:

```bash
zig build -Doptimize=ReleaseFast
```

You can now connect **Victoire** to a **GUI** such as [Cute Chess](https://github.com/cutechess/cutechess) or directly run the engine with the command:

```bash
zig-out/bin/Victoire
```

## 🌟 Features

**Victoire** uses the [UCI](https://github.com/nomemory/uci-protocol-specification) protocol to communicate.

### 🎮 Available commands

The commands interpreted by the engine are listed below, more **UCI** protocol commands will be added in the future.

| **Commands** | **Description** | **Comments** |
|------------------|-----------------------|---------------------------------------------------|
| **`uci`** | | |
| **`ucinewgame`** | Initialize a new game | Reset transposition table |
| **`isready`** | | |
| **`position`** | Set the position | Support `fen`, `startpos` and `moves` |
| **`go`** | Start the search | Support `movetime` (in ms) and `depth` (in plies) |
| **`stop`** | Stop the search | Stop ponder thread |
| **`setoption`** | Set an option | |
| **`quit`** | Quit the engine | Can be used at any time |

### ⚙️ Available options

Here is the list of currently available options. Use the command `setoption name <option name> value <value>` to change them.

| **Option** | **Type** | **Description** | **Comments** |
|--------------|----------|----------------------------------|------------------------------------------|
| **`Hash`** | `spin` | Transposition table size (in MB) | Will only take effect after `ucinewgame` |
| **`Ponder`** | `check` | Enable pondering | Take effect after next `go` command |

### 📝 Example

```bash
uci # Prints available options and default values
setoption name Hash value 128
ucinewgame # Initialize a new game

position startpos moves e2e4 e7e5
# Or position fen rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR b KQkq -

go movetime 5000 depth 50 # Search for 5 seconds or 50 plies
```
271 changes: 271 additions & 0 deletions src/chess.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
//! This module contains the representation of a chessboard and related features.

const squares = @import("squares.zig");

pub const Piece = enum {
pawn,
knight,
bishop,
rook,
queen,
king,
};

const Positions = struct {
pawns: u64 = 0,
knights: u64 = 0,
bishops: u64 = 0,
rooks: u64 = 0,
queens: u64 = 0,
king: u64 = 0,

occupied: u64 = 0,

/// Returns the mask of a given piece.
pub inline fn get(self: *Positions, piece: Piece) *u64 {
return switch (piece) {
.pawn => &self.pawns,
.knight => &self.knights,
.bishop => &self.bishops,
.rook => &self.rooks,
.queen => &self.queens,
.king => &self.king,
};
}

/// Returns the piece wich collides with the mask if any.
pub inline fn collision(self: Positions, mask: u64) ?Piece {
if (self.occupied & mask == 0) return null;
if (self.pawns & mask > 0) return .pawn;
if (self.knights & mask > 0) return .knight;
if (self.bishops & mask > 0) return .bishop;
if (self.rooks & mask > 0) return .rook;
if (self.queens & mask > 0) return .queen;
if (self.king & mask > 0) return .king;
return null;
}
};

pub const Color = enum {
white,
black,

pub inline fn other(self: Color) Color {
return switch (self) {
.white => .black,
.black => .white,
};
}
};

const Castling = enum {
K, // White kingside
Q, // White queenside
k, // Black kingside
q, // Black queenside
};

const CastlingRights = struct {
K: bool = true,
Q: bool = true,
k: bool = true,
q: bool = true,

inline fn removeWhiteRights(self: *CastlingRights) void {
self.K = false;
self.Q = false;
}

inline fn removeBlackRights(self: *CastlingRights) void {
self.k = false;
self.q = false;
}
};

const Promotion = enum {
knight,
bishop,
rook,
queen,

pub inline fn piece(self: Promotion) Piece {
return switch (self) {
.knight => .knight,
.bishop => .bishop,
.rook => .rook,
.queen => .queen,
};
}
};

/// Representation of a move.
pub const Move = struct {
src: u64 = 0,
dest: u64 = 0,
side: Color = .white,
piece: Piece = .pawn,
en_passant: bool = false,
capture: ?Piece = null,
promotion: ?Promotion = null,
castling: ?Castling = null, // src and dest must be specified for king
null_move: bool = false, // Only change side
is_check_evasion: bool = false, // Was in check before this move ?

/// Compares two moves.
pub inline fn sameAs(self: Move, other: Move) bool {
if (self.src != other.src) return false;
if (self.dest != other.dest) return false;
if (self.promotion != other.promotion) return false;
return true;
}

pub inline fn nullMove() Move {
return .{ .null_move = true };
}
};

/// Representation of a partial move (only source square, destination square and promotion if any).
pub const PartialMove = struct {
src: u64 = 0,
dest: u64 = 0,
promotion: ?Promotion = null,
};

/// Representation of a chess board.
pub const Board = struct {
white: Positions = .{},
black: Positions = .{},

side: Color = .white,
castling_rights: CastlingRights = .{},
en_passant: u64 = 0,

halfmove_clock: u8 = 0, // Not implemented
fullmove_number: u16 = 1, // Not implemented

pub inline fn empty() Board {
var board = Board{};
board.white.king = squares.e1;
board.black.king = squares.e8;
return board;
}

pub inline fn allies(self: *Board) *Positions {
return switch (self.side) {
.white => &self.white,
.black => &self.black,
};
}

pub inline fn enemies(self: *Board) *Positions {
return switch (self.side) {
.white => &self.black,
.black => &self.white,
};
}

/// Makes a move, doesn't check legality.
pub inline fn make(self: *Board, move: Move) void {
self.en_passant = 0;

if (!move.null_move) {
blk: {
if (move.castling != null) {
const diff = switch (move.castling.?) {
.K => squares.h1 | squares.f1,
.Q => squares.a1 | squares.d1,
.k => squares.h8 | squares.f8,
.q => squares.a8 | squares.d8,
};
self.allies().rooks ^= diff;
self.allies().occupied ^= diff;
break :blk;
}

if (move.promotion != null) {
self.allies().pawns ^= move.dest;

break :blk switch (move.promotion.?) {
.knight => self.allies().knights ^= move.dest,
.bishop => self.allies().bishops ^= move.dest,
.rook => self.allies().rooks ^= move.dest,
.queen => self.allies().queens ^= move.dest,
};
}

if (move.piece == .pawn) {
if (move.src & squares.row_2 > 0 and move.dest & squares.row_4 > 0) {
self.en_passant = move.dest << 8;
} else if (move.src & squares.row_7 > 0 and move.dest & squares.row_5 > 0) {
self.en_passant = move.dest >> 8;
}
}
}

const diff = move.src | move.dest;
self.allies().get(move.piece).* ^= diff;
self.allies().occupied ^= diff;

if (move.capture != null) {
const capture = blk: {
if (move.en_passant) break :blk if (move.side == .white) move.dest << 8 else move.dest >> 8;
break :blk move.dest;
};

switch (capture) {
squares.h1 => self.castling_rights.K = false,
squares.a1 => self.castling_rights.Q = false,
squares.h8 => self.castling_rights.k = false,
squares.a8 => self.castling_rights.q = false,
else => {},
}

self.enemies().occupied ^= capture;
self.enemies().get(move.capture.?).* ^= capture;
}

switch (move.src) {
squares.e1 => self.castling_rights.removeWhiteRights(),
squares.e8 => self.castling_rights.removeBlackRights(),
squares.a1 => self.castling_rights.Q = false,
squares.h1 => self.castling_rights.K = false,
squares.a8 => self.castling_rights.q = false,
squares.h8 => self.castling_rights.k = false,
else => {},
}
}

self.side = self.side.other();
}

pub inline fn copyAndMake(self: Board, move: Move) Board {
var child = self;
child.make(move);
return child;
}

/// Determines a Move from the Board and the PartialMove
pub inline fn completeMove(self: Board, partial: PartialMove) !Move {
var board = self;

var move = Move{
.src = partial.src,
.dest = partial.dest,
.side = board.side,
.promotion = partial.promotion,
.piece = board.allies().collision(partial.src) orelse return error.no_source_piece,
.capture = board.enemies().collision(partial.dest),
};

if (move.dest == board.en_passant and move.piece == .pawn) move.en_passant = true;

if (move.piece == .king) {
if (move.src == squares.e1 and move.dest == squares.g1) move.castling = .K;
if (move.src == squares.e1 and move.dest == squares.c1) move.castling = .Q;
if (move.src == squares.e8 and move.dest == squares.g8) move.castling = .k;
if (move.src == squares.e8 and move.dest == squares.c8) move.castling = .q;
}

return move;
}
};
Loading

0 comments on commit 5f52eba

Please sign in to comment.