Skip to content

Commit

Permalink
feat,infra: Clean up the project and continue impl
Browse files Browse the repository at this point in the history
- Add Makefile for OpenBench (#55)
- Move all lints from src/lib.rs to Cargo.toml
- Continue implementing the engine's UCI handler
- Add engine integration test using assert_cmd
- Extend the score to support checkmates
- Add `pabi bench` command for OpenBench and impl stub
- Make minor progress towards Transposition Table impl
  • Loading branch information
kirillbobyrev committed Jun 22, 2024
1 parent 3d29d12 commit 1991d3b
Show file tree
Hide file tree
Showing 31 changed files with 731 additions and 613 deletions.
427 changes: 139 additions & 288 deletions Cargo.lock

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,63 @@ include = [
"README.md",
] # Reduce the package size by only including things necessary for building it.

[lints.rust]
# Warn
absolute_paths_not_starting_with_crate = "warn"
let_underscore_drop = "warn"
macro_use_extern_crate = "warn"
missing_docs = "warn"
unused_extern_crates = "warn"
unused_import_braces = "warn"
unused_lifetimes = "warn"
unused_qualifications = "warn"
variant_size_differences = "warn"
# Deny
keyword_idents = "deny"
keyword_idents_2018 = "deny"
keyword_idents_2024 = "deny"
single_use_lifetimes = "deny"
trivial_casts = "deny"
trivial_numeric_casts = "deny"
unreachable_pub = "deny"
unused_results = "deny"

[lints.clippy]
# Warn
cargo = "warn"
cognitive_complexity = "warn"
complexity = "warn"
correctness = "warn"
dbg_macro = "warn"
nursery = "warn"
pedantic = "warn"
style = "warn"
suspicious = "warn"
decimal_literal_representation = "warn"
# Deny
cargo_common_metadata = "deny"
doc_markdown = "deny"
equatable_if_let = "deny"
float_cmp = "deny"
float_cmp_const = "deny"
get_unwrap = "deny"
implicit_clone = "deny"
imprecise_flops = "deny"
inefficient_to_string = "deny"
linkedlist = "deny"
manual_assert = "deny"
needless_collect = "deny"
perf = "deny"
redundant_clone = "deny"
suboptimal_flops = "deny"
trivial_regex = "deny"
use_self = "deny"

[lints.rustdoc]
broken_intra_doc_links = "deny"
invalid_rust_codeblocks = "deny"
unescaped_backticks = "deny"

[workspace]
members = ["tools"]

Expand All @@ -37,6 +94,7 @@ shadow-rs = "0.28.0"
rand = "0.8.5"

[dev-dependencies]
assert_cmd = "2.0.14"
criterion = { version = "0.5.1", features = [
"cargo_bench_support",
"csv_output",
Expand All @@ -46,6 +104,7 @@ iai = "0.1.1"
pretty_assertions = "1.1.0"
# Used for testing and comparing against a reasonable baseline for correctness.
shakmaty = "0.27.0"
predicates = "3.1.0"

[[bench]]
harness = false
Expand Down
20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Supporting builds through `make` command is a requirement for OpenBench:
# https://github.com/AndyGrant/OpenBench/wiki/Requirements-For-Public-Engines#basic-requirements

# Variable for the output binary name, defaults to 'pabi' if not provided.
EXE ?= pabi

ifeq ($(OS),Windows_NT)
EXE_SUFFIX := .exe
else
EXE_SUFFIX :=
endif

# Compile flags for the fastest possible build.
COMPILE_FLAGS := RUSTFLAGS='-C target-feature=+avx2,+fma,+bmi1,+bmi2'

# Compile the target and add a link to the binary for OpenBench to pick up.
openbench:
$(COMPILE_FLAGS) cargo rustc --profile=fast --bin=pabi -- --emit link=$(EXE)$(EXE_SUFFIX)

.PHONY: openbench
17 changes: 8 additions & 9 deletions benches/cycles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,37 @@ fn parse_stockfish_book_positions() {

// Low depths of known perft results (https://www.chessprogramming.org/Perft_Results).
fn perft() {
for (position, depth, nodes) in [
for (position, depth, nodes) in &[
// Position 1 (starting).
(Position::starting(), 5, 4865609),
(Position::starting(), 5, 4_865_609),
// Position 2.
(
Position::from_fen(
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1",
)
.unwrap(),
4,
4085603,
4_085_603,
),
// Position 3.
(
Position::from_fen("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1").unwrap(),
5,
674624,
674_624,
),
// Position 4.
(
Position::from_fen("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1")
.unwrap(),
4,
422333,
422_333,
),
// Position 5.
(
Position::from_fen("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8")
.unwrap(),
4,
2103487,
2_103_487,
),
// Position 6.
(
Expand All @@ -67,16 +67,15 @@ fn perft() {
)
.unwrap(),
4,
3894594,
3_894_594,
),
(
Position::from_fen("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1")
.unwrap(),
4,
422333,
422_333,
),
]
.iter()
{
assert_eq!(pabi::chess::position::perft(position, *depth), *nodes);
}
Expand Down
26 changes: 10 additions & 16 deletions benches/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fn parse(c: &mut Criterion) {
let positions = fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/data/positions.fen"))
.unwrap()
.lines()
.map(|line| line.to_string())
.map(ToString::to_string)
.collect::<Vec<_>>();
c.bench_with_input(
BenchmarkId::new(
Expand Down Expand Up @@ -109,25 +109,23 @@ criterion_group! {
// This acts both as performance and correctness test.
fn perft_bench(c: &mut Criterion) {
let mut group = c.benchmark_group("perft");
// TODO: Abstract this out and have a single array/dataset of perft positions to
// check. Inlining these is quite unappealing.
// TODO: Add Throughput - it should be the number of nodes.
for (position, depth, nodes) in [
for (position, depth, nodes) in &[
// Position 1.
(Position::starting(), 5, 4865609),
(Position::starting(), 6, 119060324),
(Position::starting(), 5, 4_865_609),
(Position::starting(), 6, 119_060_324),
// Position 3.
(
Position::from_fen("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1").unwrap(),
6,
11030083,
11_030_083,
),
// Position 4.
(
Position::from_fen("r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1")
.unwrap(),
6,
706045033,
706_045_033,
),
// Position 6.
(
Expand All @@ -136,33 +134,29 @@ fn perft_bench(c: &mut Criterion) {
)
.unwrap(),
5,
164075551,
164_075_551,
),
// Other positions.
(
Position::from_fen("r1bqkbnr/pppppppp/2n5/8/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 1 2")
.unwrap(),
6,
336655487,
336_655_487,
),
(
Position::from_fen("rnbqkbnr/pppppppp/8/8/8/N7/PPPPPPPP/R1BQKBNR b KQkq - 1 1")
.unwrap(),
6,
120142144,
120_142_144,
),
]
.iter()
{
group.throughput(criterion::Throughput::Elements(*nodes));
group.bench_with_input(
BenchmarkId::new(
"perft",
format!(
"position {}, depth {}, nodes {}",
position.to_string(),
depth,
nodes
"position {position}, depth {depth}, nodes {nodes}"
),
),
depth,
Expand Down
24 changes: 11 additions & 13 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,16 @@ fn generate_zobrist_keys() {
let mut rng = rand::thread_rng();

let piece_keys: [ZobristKey; NUM_COLORS * NUM_PIECES * NUM_SQUARES] =
std::array::from_fn(|_| rand::Rng::gen(&mut rng));
generate_file(
&format!("pieces_zobrist_keys"),
&format!("{:?}", piece_keys),
);
std::array::from_fn(|_| rand::Rng::r#gen(&mut rng));
generate_file("pieces_zobrist_keys", &format!("{piece_keys:?}"));

let en_passant_keys: [ZobristKey; 8] = std::array::from_fn(|_| rand::Rng::gen(&mut rng));
generate_file("en_passant_zobrist_keys", &format!("{:?}", en_passant_keys));
let en_passant_keys: [ZobristKey; 8] = std::array::from_fn(|_| rand::Rng::r#gen(&mut rng));
generate_file("en_passant_zobrist_keys", &format!("{en_passant_keys:?}"));
}

// PeSTO tables with modified encoding for easier serialization.
// Piece indices match the order of PieceKind, the planes match the order of Piece.
// Piece indices match the order of PieceKind, the planes match the order of
// Piece.

const MIDDLEGAME_VALUE: [i32; 6] = [0, 1025, 477, 365, 337, 82];
const ENDGAME_VALUE: [i32; 6] = [0, 936, 512, 297, 281, 94];
Expand Down Expand Up @@ -185,7 +183,7 @@ const ENDGAME_KING_TABLE: [i32; 64] = [
-53, -34, -21, -11, -28, -14, -24, -43
];

fn flip(square: usize) -> usize {
const fn flip(square: usize) -> usize {
square ^ 56
}

Expand Down Expand Up @@ -218,20 +216,20 @@ fn generate_pesto_tables() {
{
populate_piece_values(
piece_index,
&middlegame_piece_table,
middlegame_piece_table,
&MIDDLEGAME_VALUE,
&mut middlegame_table,
);
populate_piece_values(
piece_index,
&endgame_piece_table,
endgame_piece_table,
&ENDGAME_VALUE,
&mut endgame_table,
);
}

generate_file("pesto_middlegame_table", &format!("{:?}", middlegame_table));
generate_file("pesto_endgame_table", &format!("{:?}", endgame_table));
generate_file("pesto_middlegame_table", &format!("{middlegame_table:?}"));
generate_file("pesto_endgame_table", &format!("{endgame_table:?}"));
}

fn main() -> shadow_rs::SdResult<()> {
Expand Down
4 changes: 2 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ fix:
cargo +nightly clippy --all-features --fix --allow-staged

# Run most tests that are fast and are run by default.
test_basic:
test:
{{ compile_flags }} cargo test --profile=fast

# Run tests that are slow and are not run by default.
test_slow:
{{ compile_flags }} cargo test --profile=fast -- --ignored

# Run all tests.
test: test_basic test_slow
test_all: test test_slow

bench:
{{ compile_flags }} cargo bench --profile=fast
Expand Down
3 changes: 2 additions & 1 deletion src/chess/bitboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,14 +428,15 @@ impl Pieces {

#[cfg(test)]
mod tests {
use mem::size_of;
use pretty_assertions::assert_eq;

use super::*;
use crate::chess::core::{Rank, Square, BOARD_WIDTH};

#[test]
fn basics() {
assert_eq!(mem::size_of::<Bitboard>(), 8);
assert_eq!(size_of::<Bitboard>(), 8);
assert_eq!(Bitboard::full().bits, u64::MAX);
assert_eq!(Bitboard::empty().bits, u64::MIN);

Expand Down
33 changes: 32 additions & 1 deletion src/chess/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,21 @@ impl Square {
Err(_) => None,
}
}

fn next(self) -> Option<Self> {
if self as u8 == 63 {
None
} else {
Some(unsafe { mem::transmute(self as u8 + 1) })
}
}

/// Creates an iterator over all squares, starting from A1 (0) to H8 (63).
#[must_use] pub fn iter() -> SquareIterator {
SquareIterator {
current: Some(Self::A1),
}
}
}

impl TryFrom<u8> for Square {
Expand Down Expand Up @@ -217,6 +232,22 @@ impl TryFrom<&str> for Square {
}
}

/// Iterates over squares in the order from A1 to H8, from left to right, from
/// bottom to the top.
pub struct SquareIterator {
current: Option<Square>,
}

impl Iterator for SquareIterator {
type Item = Square;

fn next(&mut self) -> Option<Self::Item> {
let result = self.current;
self.current = self.current.and_then(Square::next);
result
}
}

impl fmt::Display for Square {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.file(), self.rank())
Expand Down Expand Up @@ -444,7 +475,7 @@ pub struct Piece {
}

impl Piece {
pub const fn plane(&self) -> usize {
#[must_use] pub const fn plane(&self) -> usize {
self.owner as usize * 6 + self.kind as usize
}
}
Expand Down
Loading

0 comments on commit 1991d3b

Please sign in to comment.