Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
165 commits
Select commit Hold shift + click to select a range
870d760
Update to a more recent version of rand.
edre Jan 28, 2021
2e14f54
Fix all build warnings from stable rustc.
edre Jan 28, 2021
9195125
Remove Player from the API interface. Game::State must keep track of …
edre Feb 4, 2021
5529dd5
Increase maximum moves to 200.
edre Feb 4, 2021
fd8e4d9
Use alpha-beta pruning at the top level of negamax.
edre Feb 5, 2021
feb37b2
Convert Evaluation from an enum to an alias for i32.
edre Feb 13, 2021
a0557e9
Add a rustfmt config and apply it.
edre Feb 14, 2021
ef8060f
Migrate benchmark to bencher crate to use stable rust.
edre Feb 14, 2021
cb17957
Add connect four example game.
edre Feb 14, 2021
51bd581
Tweak winning evaluations to prolong defeat and accelerate victory.
edre Feb 14, 2021
42769e1
Add actual evaluator to connect four.
edre Feb 14, 2021
2a46eed
Add testing harness for strategies.
edre Feb 15, 2021
167539b
Add an iterative search strategy and Zobrist trait.
edre Feb 15, 2021
d258cad
Add benchmarks based on connect four comparing negamax and iterative …
edre Feb 15, 2021
dc34591
Simplify Negamax config by removing the Options struct.
edre Feb 16, 2021
339bab2
Simplify IterativeSearch options.
edre Feb 16, 2021
74edfcc
Encapsulate replacement strategy in TranspositionTable.
edre Feb 16, 2021
e895e84
Refactor connect4 to twiddle colors less.
edre Feb 16, 2021
fed0f39
Update documentation for 0.1.0 release.
edre Feb 16, 2021
3854eda
Tidy some doc comments.
edre Feb 17, 2021
77803cf
Hide pub testing functions.
edre Feb 17, 2021
d3744a4
Add configuration for transposition table replacement strategy.
edre Feb 18, 2021
2e649f3
Add null window search option.
edre Feb 18, 2021
aee2546
Add option for incrementing 2 depths at a time.
edre Feb 20, 2021
5a1553d
Implement optional quiescence search at the leaf nodes.
edre Feb 22, 2021
c41434c
Compute the principal variation at each iteration.
edre Feb 22, 2021
e4f135d
Refactor table bookkeeping out of giant negamax function body.
edre Feb 22, 2021
88192ef
Port value clamping to IterativeSearch.
edre Feb 23, 2021
379b1e2
integration test: ensure strategies actually pick one of the best moves
edre Feb 23, 2021
27adf8d
Narrow window after failed scout probe.
edre Feb 24, 2021
651cd62
Resolve the mystery of the non-exact PV nodes.
edre Feb 24, 2021
73086cf
Prevent infinitely long principal variations.
edre Feb 24, 2021
e9adb07
Update new option documentation.
edre Feb 24, 2021
01265c6
Update version to 0.1.1
edre Feb 24, 2021
c626e29
Pass a Vec in the generate_moves API.
edre Feb 25, 2021
f2f4a2f
Add a generic perft function for benchmarking move generation.
edre Feb 25, 2021
c12e303
Add mean and effective branching factors to stats.
edre Feb 25, 2021
9878128
Fix clippy lints.
edre Mar 2, 2021
ed13cad
Remove unused .project file.
edre Mar 2, 2021
5175527
Simplify default implementation signature for generate_noisy_moves.
edre Mar 3, 2021
47d7f58
Upgrade to rust 2018 edition.
edre Mar 3, 2021
712b551
Make Evaluators stateful so they can be configured like Strategies.
edre Mar 3, 2021
b20c524
Replace custom test hash function with DefaultHasher.
edre Mar 3, 2021
1a1af0d
Add parallel strategy, based on rayon and Young Brothers Wait.
edre Mar 6, 2021
d218c17
Add deeper correctness test.
edre Mar 7, 2021
b964f06
Use connect4 example as deterministic macrobenchmark.
edre Mar 8, 2021
5aff5e0
Switch to parking_lot::Mutex for slightly better perf and entry size.
edre Mar 9, 2021
a72f338
Update regular benchmark.
edre Mar 9, 2021
0da96f1
Refactor single-thread transposition table into trait.
edre Mar 9, 2021
415220d
Bring ConcurrentTable into the Table trait
edre Mar 9, 2021
d152dab
Dedup populate_pv into Table trait.
edre Mar 9, 2021
1a5dae3
Refactor table-based negamax into its own type.
edre Mar 9, 2021
8feb462
Add aspiration window option in IterativeSearch.
edre Mar 10, 2021
21e0e16
Add another parallel strategy based on LazySMP.
edre Mar 13, 2021
d50e004
Experimental unsafe table that doesn't bother to use atomics.
edre Mar 13, 2021
68b898e
Add less unsafe lock-free table with similar benchmarks.
edre Mar 13, 2021
3b0f2af
Shrink Evaluation to i16 to make transposition table entry smaller.
edre Apr 19, 2022
cc9bf23
Export Random strategy.
edre Apr 19, 2022
7078ea7
Add multi-threaded perft option.
edre Apr 19, 2022
06958a1
New MCTS strategy. Beats Random. Multiple threads coming.
edre Apr 22, 2022
2fc91da
Revert "Shrink Evaluation to i16 to make transposition table entry sm…
edre Apr 22, 2022
469769b
Merge lockfree table with MCTS.
edre Apr 22, 2022
33470fa
Ratchet down Entry size to 16 bytes for regular and concurrent tables.
edre Apr 22, 2022
cede551
Add MCTSOptions.
edre Apr 22, 2022
fc27941
Multi-threaded MCTS.
edre Apr 22, 2022
f0c5bd9
Make max_rollouts configurable.
edre Apr 23, 2022
707e58a
Fix build.
edre Apr 23, 2022
4e5ce08
Generate the right moves in rollout.
edre Apr 23, 2022
6ab1063
Add low-effort static move reordering based on Evaluators.
edre Apr 23, 2022
c66f5bd
Add stats to LazySMP.
edre May 3, 2022
33f506a
Consolidate duplicate options into reusing IterativeOptions.
edre May 3, 2022
66a496f
Add timeout option to MCTS.
edre May 3, 2022
e5fb611
Remove unused ShardedTable and its parking_lot dependency.
edre May 3, 2022
0f82429
Implement alternating depth searches on LazySmp helper threads.
edre May 4, 2022
76f4749
Release 0.2.1
edre May 4, 2022
8f336f1
Some basic verbosity setting.
edre May 4, 2022
8439f10
Encapsulate LazySmp helper thread communication into CommandSignal.
edre May 6, 2022
bbe1fe4
Parallelize aspiration search in LazySmp.
edre May 6, 2022
d4921b2
New iterative option based on MTD(f) algorithm.
edre May 7, 2022
a265e53
Add verbosity to IterativeSearch and clean it up a little.
edre May 8, 2022
0d9045d
Various improvements to verbose mode.
edre May 12, 2022
2715dee
Add human-readable move stats and pv to verbose mode.
edre May 14, 2022
4f5a677
Use depth the same way everywhere, and also change the depth API by 1.
edre May 18, 2022
0b8d915
Add null move pruning option.
edre May 21, 2022
0f1504d
Reorder top-level moves on every iteration.
edre May 22, 2022
069604f
Add verbosity to ParallelYbw.
edre May 24, 2022
c767892
Add num_threads option to ybw.
edre May 25, 2022
724a9ff
Add background pondering to ybw.
edre May 25, 2022
c432090
Fix crash in background threads.
edre May 25, 2022
07c847e
Copy null-move-depth into ybw.
edre May 25, 2022
9d712e5
Standardize all APIs to use u8 for depth.
edre May 25, 2022
0964398
num_threads should be usize everywhere
edre May 25, 2022
1b8bfea
Refactor noisy moves to be part of Evaluator instead of Game.
edre May 25, 2022
4581f76
Revert noisy moves to be a generation instead of a bool.
edre May 27, 2022
32ba31d
Copy the YBW verbosity style to the other iterative strategies.
edre Jun 8, 2022
0ba09fa
Add timeout, max_depth, and principal_variation to the Strategy trait.
edre Jun 8, 2022
e1fce7e
Return principal variation from YBW
edre Jun 8, 2022
1a918eb
Detect cycles manually and find PV to arbitrary depth.
edre Jun 8, 2022
3aedf21
Release v0.3.0
edre Jun 8, 2022
7e2eb85
Fix clippy lints.
edre Jun 8, 2022
54893ad
Template Game type to Random and MCTS.
edre Jun 14, 2022
ab796d5
Allow compilation under wasm32-unknown-unknown.
edre Oct 7, 2022
524df7b
Use a version of Instant that is wasm-compatible.
edre Oct 8, 2022
6b1dc19
Create timer mechanism for wasm.
edre Oct 8, 2022
05ce848
Tweak and refactor null move pruning
edre Oct 18, 2022
1b04a97
Add singular extension option
edre Oct 19, 2022
52b3210
Add countermove and history table to IterativeSearch.
edre Oct 19, 2022
af39d41
Make some config fields public.
edre Oct 26, 2022
3b0e76c
Exit early when guaranteed to win or lose.
edre Oct 31, 2022
f3c777b
New example for using minimax crate on third party chess library.
edre Oct 31, 2022
7c701e6
chess: Add zobrist and notation to use IterativeSearch.
edre Nov 1, 2022
c77a8f2
Add thread-local MovePool to YBW.
edre Nov 1, 2022
8473278
Add thread-local countermoves table to ybw.
edre Nov 2, 2022
df37325
Pull out Stats struct for future reuse.
edre Nov 2, 2022
2163a93
Add basic stats to YBW via threadlocals.
edre Nov 2, 2022
c978eac
Simplify ThreadLocal implementation
edre Nov 4, 2022
07c7e75
Reduce Evaluation to an i16.
edre Nov 4, 2022
19d51f1
Delete LazySMP.
edre Nov 4, 2022
b99f5e8
Rename Ybw stuff to Parallel.
edre Nov 4, 2022
ed68b48
Add a frontpage to rustdoc, and give it the simplest example.
edre Nov 5, 2022
8f0a4d3
Deflake mcts test.
edre Nov 5, 2022
ec4c9c6
Simplify wasm32 config checks.
edre Nov 5, 2022
40e5270
Upgrade to 2021 edition rust.
edre Nov 5, 2022
1efd316
Release 0.4.0
edre Nov 5, 2022
c62d488
Remove redundant license text from README.
edre Nov 5, 2022
b012820
Don't die if strategies are given a winning position.
edre Mar 14, 2023
22e78b0
Use fancier atomics orderings.
edre Mar 15, 2023
37e53b3
Fix clippy lints.
edre Mar 21, 2023
d5ba3c0
Fix null move pruning.
edre Mar 21, 2023
0b30ae6
Experimental unified Game trait.
edre Mar 19, 2023
9c75609
Full prototype of experimental unified Game trait.
edre Mar 19, 2023
10cca65
Convenience function for applying moves for either implementation.
edre Mar 21, 2023
de23fa5
Refactor everything to the new unified Game trait.
edre Mar 21, 2023
5b4f859
Remove unnecessary move reference in apply() and undo().
edre Mar 21, 2023
e436e1d
Remove move referencing in other Game trait APIs.
edre Mar 22, 2023
7510412
Release 0.5.0
edre Mar 22, 2023
a1acf12
Fix wasm build.
edre Mar 22, 2023
28bee1c
Release 0.5.1
edre Mar 22, 2023
7fb389f
Add mancala example.
edre Mar 22, 2023
fb410c5
Remove Move from docs
edre Mar 22, 2023
9faa5cc
Exponential algorithm for interpretting depth in mcts
edre May 9, 2023
45b1d33
Update rustfmt edition to avoid deprecation warning.
edre May 12, 2023
46a27d1
mcts: add verbose mode
edre May 12, 2023
fed4272
mcts: use scoped threads instead of cloning everything
edre May 16, 2023
e451ceb
mcts: add custom rollout policy
edre May 16, 2023
1a88855
mcts: implement virtual loss
edre May 16, 2023
94c5d94
mcts: implement endgame terminal-state propagation.
edre May 17, 2023
5ec9fb2
mcts: pick random best child more uniformly
edre May 17, 2023
e2ffde1
mcts: add principal variation
edre Jun 5, 2023
8e01887
Release 0.5.2
edre Jun 7, 2023
24ff09b
Remove verbose-mode dependency on zobrist_hash.
edre Nov 27, 2023
32d49bb
Release 0.5.3
edre Nov 27, 2023
87538e1
mcts: Factor out random_best into util library.
edre Dec 8, 2023
70dbd40
Removed Copy trait bound on Game::M
Lege19 Jan 14, 2024
aa290f4
Merge pull request #1 from Lege19/no-trait-bound-on-move
edre Jan 30, 2024
9c9964a
Consistent uses of semicolons in verbose logs.
edre Jan 2, 2025
3846d5b
Back out "Removed Copy trait bound on Game::M"
edre Jan 2, 2025
4b89a9c
Fix new clippy lints.
edre Jan 2, 2025
92031c2
Respect the move ordering in parallel search.
edre Jan 2, 2025
ff79d16
Rename util.rs in common.rs in src/strategies/.
edre Jan 2, 2025
5d030a0
Decay old countermoves values faster.
edre Jan 3, 2025
1c094fd
Release 0.5.4
edre Feb 19, 2025
bb04bc3
Update rand to 0.9
edre Feb 19, 2025
0032065
feat(stats): iterative deepening statistics table via parallel reads
rsarvar1a Oct 10, 2025
dad8275
Merge pull request #3 from rsarvar1a/feature-iterative-statistics
edre Oct 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
Cargo.lock
target

**/*~
17 changes: 0 additions & 17 deletions .project

This file was deleted.

3 changes: 3 additions & 0 deletions .rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
edition = "2021"
fn_params_layout = "Compressed"
use_small_heuristics = "Max"
26 changes: 21 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
[package]
name = "minimax"
version = "0.0.2"
authors = [ "Samuel Fredrickson <samfredrickson@gmail.com>" ]
version = "0.5.4"
authors = [ "Eric Roshan-Eisner <eric.d.eisner@gmail.com>", "Samuel Fredrickson <samfredrickson@gmail.com>" ]
description = "Generic implementations of Minimax."
documentation = "http://kinghajj.github.io/doc/minimax/"
repository = "http://github.com/kinghajj/minimax-rs.git"
documentation = "https://docs.rs/minimax"
edition = "2021"
repository = "https://github.com/edre/minimax-rs"
readme = "README.md"
keywords = ["ai", "game", "minimax", "negamax"]
license = "MIT"

[dependencies]
rand = "0.3.*"
instant = { version = "0.1", features = ["wasm-bindgen"] }
rand = "0.9"

[target.'cfg(not(target_arch="wasm32"))'.dependencies]
num_cpus = "1.0"
rayon = "^1.5"

[target.'cfg(target_arch="wasm32")'.dependencies]
getrandom = { version = "0.3", features = ["wasm_js"]}

[dev-dependencies]
bencher = "0.1.5"

[[bench]]
name = "negamax"
harness = false

[profile.test]
opt-level = 3
40 changes: 12 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# minimax-rs - Generic implementations of Minimax in Rust.

[![Build Status](https://travis-ci.org/kinghajj/minimax-rs.svg?branch=master)](https://travis-ci.org/kinghajj/minimax-rs) [![Crates.io](https://img.shields.io/crates/v/minimax.svg)](https://crates.io/crates/minimax)
[![Build Status](https://api.travis-ci.com/edre/minimax-rs.svg?branch=master)](https://travis-ci.com/github/edre/minimax-rs)
[![Crates.io](https://img.shields.io/crates/v/minimax.svg)](https://crates.io/crates/minimax)
[![Documentation](https://docs.rs/minimax/badge.svg)](https://docs.rs/minimax)

## About

Expand All @@ -10,33 +12,15 @@ This library provides interfaces that describe:
2. methods of evaluating particular game states for a player; and
3. strategies for choosing moves for a player.

The eventual goal is to have multiple proper strategies, so that any combination
of evaluators and strategies can be tested against each other. Currently, only
a basic alpha-beta pruning Negamax strategy is implemented.
This crate implements multiple different strategies, so that any combination of
custom evaluators and strategies can be tested against each other. These include
single- and multi-threaded algorithms using alpha-beta pruning, iterative
deepening, and transposition tables. There is also a basic implementation of
multi-threaded Monte Carlo Tree Search, which does not require writing an
evaluator.

## Example

The `ttt` module contains an implementation of Tic-Tac-Toe, demonstrating how to
use the game and evaluation interfaces. `test` shows how to use strategies.

## License

Copyright (c) 2015 Samuel Fredrickson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
The `ttt` and `connect4` modules contain implementations of Tic-Tac-Toe and
Connect Four, demonstrating how to use the game and evaluation interfaces.
`test` shows how to use strategies.
85 changes: 38 additions & 47 deletions benches/negamax.rs
Original file line number Diff line number Diff line change
@@ -1,56 +1,47 @@
#![feature(test)]
#[macro_use]
extern crate bencher;
extern crate minimax;
extern crate test;
use test::Bencher;
use minimax::*;

#[derive(Clone)]
pub struct Board;

#[derive(Copy, Clone)]
pub struct Place;
#[path = "../examples/connect4.rs"]
mod connect4;

pub struct Eval;

pub struct Noop;
use bencher::Bencher;
use minimax::*;

impl Move for Place {
type G = Noop;
fn apply(&self, _: &mut Board) {
}
fn undo(&self, _: &mut Board) {
}
fn bench_negamax(b: &mut Bencher) {
let board = connect4::Board::default();
b.iter(|| {
let mut s = Negamax::new(connect4::BasicEvaluator::default(), 5);
let m = s.choose_move(&board);
assert!(m.is_some());
});
}

impl Game for Noop {
type S = Board;
type M = Place;

fn generate_moves(_: &Board, _: Player, ms: &mut [Option<Place>]) -> usize {
const NUM_MOVES: usize = 4;
for m in ms.iter_mut().take(NUM_MOVES) {
*m = Some(Place);
}
ms[NUM_MOVES] = None;
NUM_MOVES
}

fn get_winner(_: &Board) -> Option<Winner> {
None
}
fn bench_iterative(b: &mut Bencher) {
let board = connect4::Board::default();
b.iter(|| {
let mut s = IterativeSearch::new(
connect4::BasicEvaluator::default(),
IterativeOptions::new().with_table_byte_size(32_000),
);
s.set_max_depth(5);
let m = s.choose_move(&board);
assert!(m.is_some());
});
}

impl Evaluator for Eval {
type G = Noop;

fn evaluate(_: &Board, _: Option<Winner>) -> Evaluation {
Evaluation::Score(0)
}
fn bench_parallel(b: &mut Bencher) {
let board = connect4::Board::default();
b.iter(|| {
let mut s = ParallelSearch::new(
connect4::BasicEvaluator::default(),
IterativeOptions::new().with_table_byte_size(32_000),
ParallelOptions::new(),
);
s.set_max_depth(5);
let m = s.choose_move(&board);
assert!(m.is_some());
});
}

#[bench]
fn bench_negamax(b: &mut Bencher) {
let board = Board;
let mut s = Negamax::<Eval>::new(Options { max_depth: 10 });
b.iter(|| s.choose_move(&board, Player::Computer));
}
benchmark_group!(benches, bench_negamax, bench_iterative, bench_parallel);
benchmark_main!(benches);
8 changes: 8 additions & 0 deletions examples/chess/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "chess-minimax-example"
version = "0.1.0"
edition = "2021"

[dependencies]
chess = "3.2"
minimax = { path = "../.." }
82 changes: 82 additions & 0 deletions examples/chess/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
extern crate chess;
extern crate minimax;

use chess::{Board, BoardStatus, ChessMove, MoveGen};
use minimax::{Game, Strategy};

struct Chess;

impl minimax::Game for Chess {
type S = Board;
type M = ChessMove;

fn generate_moves(b: &Board, moves: &mut Vec<ChessMove>) {
for m in MoveGen::new_legal(b) {
moves.push(m);
}
}

fn get_winner(b: &Board) -> Option<minimax::Winner> {
match b.status() {
BoardStatus::Ongoing => None,
BoardStatus::Stalemate => Some(minimax::Winner::Draw),
BoardStatus::Checkmate => Some(minimax::Winner::PlayerJustMoved),
}
}

fn apply(b: &mut Board, m: ChessMove) -> Option<Board> {
Some(b.make_move_new(m))
}

fn zobrist_hash(b: &Board) -> u64 {
b.get_hash()
}

fn notation(_b: &Board, m: ChessMove) -> Option<String> {
Some(format!("{}", m))
}
}

#[derive(Default)]
struct Evaluator;

impl minimax::Evaluator for Evaluator {
type G = Chess;
fn evaluate(&self, board: &Board) -> minimax::Evaluation {
let mut score = 0;
for sq in 0..64 {
let sq = unsafe { chess::Square::new(sq) };
if let Some(piece) = board.piece_on(sq) {
let value = match piece {
chess::Piece::Pawn => 1,
chess::Piece::Knight => 3,
chess::Piece::Bishop => 3,
chess::Piece::Rook => 5,
chess::Piece::Queen => 9,
chess::Piece::King => 0,
};
if board.color_on(sq).unwrap() == board.side_to_move() {
score += value;
} else {
score -= value;
}
}
}
score
}
}

fn main() {
let mut b = Board::default();
let opts = minimax::IterativeOptions::new().verbose();
let mut strategy = minimax::IterativeSearch::new(Evaluator::default(), opts);
strategy.set_timeout(std::time::Duration::from_secs(1));
while Chess::get_winner(&b).is_none() {
println!("{}", b);
match strategy.choose_move(&b) {
Some(m) => b = Chess::apply(&mut b, m).unwrap(),
None => break,
}
}
println!("Checkmate {:?}", b.side_to_move());
}
Loading