#chess-engine #chess #move #lookup-tables #generator #generate-table #board-game

candidate

This is a fast chess move generator. It has a very good set of documentation, so you should take advantage of that. It (now) generates all lookup tables with a build.rs file, which means that very little pseudo-legal move generation requires branching. There are some convenience functions that are exposed to, for example, find all the squares between two squares. This uses a copy-on-make style structure, and the Board structure is as slimmed down as possible to reduce the cost of copying the board. There are places to improve perft-test performance further, but I instead opt to be more feature-complete to make it useful in real applications. For example, I generate both a hash of the board and a pawn-hash of the board for use in evaluation lookup tables (using Zobrist hashing). There are two ways to generate moves, one is faster, the other has more features that will be useful if making a chess engine. See the documentation for more details.

5个版本

0.0.5 2022年11月30日
0.0.4 2022年11月29日
0.0.3 2022年11月29日
0.0.2 2022年11月27日
0.0.1 2022年11月24日

#1042 in Game dev

44 每月下载量

MIT许可证

255KB
5K SLoC

A Fast Chess Library In Rust

Build Status docs.rs DeepSource crates.io

This library handles the process of move generation within a chess engine or chess program.

This is a library to manage chess game state and move generation.

需要Rust 1.31或更高版本

This library requires rust version 1.27 or greater in order to check for the BMI2 instruction-set at compile-time. Additionally, this build is compatible with rust 2018 which, I believe, requires rust 1.31.

注意:由于在AMD架构上的性能不佳,已禁用bmi2。我选择公开两个相关函数,如果CPU支持bmi2。

示例

增量式走法生成与捕获/非捕获排序

这里我们通过增量式走法生成遍历所有走法,这在不需要查看所有走法的情况下(例如在引擎搜索函数中)是非常理想的。

  use candidate::MoveGen;
  use candidate::Board;
  use candidate::EMPTY;

  // create a board with the initial position
  let board = Board::default();

  // create an iterable
  let mut iterable = MoveGen::new_legal(&board);

  // make sure .len() works.
  assert_eq!(iterable.len(), 20); // the .len() function does *not* consume the iterator

  // lets iterate over targets.
  let targets = board.color_combined(!board.side_to_move());
  iterable.set_iterator_mask(targets);

  // count the number of targets
  let mut count = 0;
  for _ in &mut iterable {
      count += 1;
      // This move captures one of my opponents pieces (with the exception of en passant)
  }

  // now, iterate over the rest of the moves
  iterable.set_iterator_mask(!EMPTY);
  for _ in &mut iterable {
      count += 1;
      // This move does not capture anything
  }

  // make sure it works
  assert_eq!(count, 20);

设置棋盘位置

Board结构尝试在所有时间点保持棋盘位置的合法性。这在通过用户输入设置棋盘时可能会很麻烦。

为了处理这个问题,在3.1.0版本中引入了BoardBuilder结构。该BoardBuilder结构遵循非消耗性构建器模式,可以通过Board::try_from(...)board_builder.try_into()将其转换为Result<Board, Error>

  use candidate::{Board, BoardBuilder, Piece, Square, Color};
  use std::convert::TryInto;

  let mut board_builder = BoardBuilder::new();
  board_builder.piece(Square::A1, Piece::King, Color::White)
               .piece(Square::A8, Piece::Rook, Color::Black)
               .piece(Square::D1, Piece::King, Color::Black);
  
  let board: Board = board_builder.try_into()?;

移动棋子

在这里,我们在棋盘上移动棋子。棋盘是一个在创建时复制的结构,这意味着每次移动棋子时,都会创建一个新的棋盘。你可以使用board.make_move()来更新当前位置,但不能撤销移动。棋盘结构为了减少复制时间而进行了大小优化。

  use candidate::{Board, ChessMove, Square, Color};

  let m = ChessMove::new(Square::D2, Square::D4, None);

  let board = Board::default();
  assert_eq!(board.make_move_new(m).side_to_move(), Color::Black);

表示完整游戏

下棋不仅仅是棋盘上的东西。游戏对象Game跟踪游戏的历史,以允许提出和棋、认输、50回合规则和棋、重复和棋以及需要游戏历史的任何一般事物。

  use candidate::{Game, Square, ChessMove};

  let b1c3 = ChessMove::new(Square::B1, Square::C3, None);
  let c3b1 = ChessMove::new(Square::C3, Square::B1, None);
  
  let b8c6 = ChessMove::new(Square::B8, Square::C6, None);
  let c6b8 = ChessMove::new(Square::C6, Square::B8, None);
  
  let mut game = Game::new();
  assert_eq!(game.can_declare_draw(), false);
  
  game.make_move(b1c3);
  game.make_move(b8c6);
  game.make_move(c3b1);
  game.make_move(c6b8);
  
  assert_eq!(game.can_declare_draw(), false); // position has shown up twice
  
  game.make_move(b1c3);
  game.make_move(b8c6);
  game.make_move(c3b1);
  game.make_move(c6b8);
  assert_eq!(game.can_declare_draw(), true); // position has shown up three times

FEN 字符串

BoardBuilderBoardGame都实现了FromStr,允许你将FEN字符串转换为对象。此外,BoardBuilderBoard实现了std::fmt::Display,可以将它们转换为FEN字符串。

  use candidate::Board;
  use std::str::FromStr;
  
  assert_eq!(
      Board::from_str("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
              .expect("Valid Position"),
    Board::default()
  );

编译时选项

在编译时,我强烈推荐使用RUSTFLAGS="-C target-cpu=native",特别是为了获取几乎所有现代CPU上可用的popcnt和ctzl指令。这被内部用来确定位图上有多少个棋子,以及棋子位于哪个方格。由于这里使用的类型系统,这些任务实际上只需要一条指令。此外,通过使用此标志,在具有这些指令的机器上启用了BMI2。

BMI2

BMI2指令集在支持它的机器上使用。这以两种方式加快了逻辑的速度

  • 它使用内置指令来完成与魔法位图相同的逻辑。
  • 它通过将移动存储在u16而不是u64中来减少缓存负载,可以通过单条指令将其解压缩为u64。

在没有BMI2的目标上,库回退到魔法位图。这是在编译时检查的。

Shakmaty

另一个Rust棋类库是'shakmaty'包。这是一个很棒的库,比这个库有更多的功能。它支持各种棋类变体,以及UCI协议。然而,这些功能是有代价的,并且在这个库的所有测试用例中,它的性能都比我这个库快。为了比较这两个库,我已经将'shakmaty'支持添加到'chess_perft'应用程序中,并将许多基准测试移动到该包中。你可以在这里查看结果:https://github.com/jordanbray/chess_perft

它做什么

这个库允许你从一个FEN格式的字符串创建棋盘,列出棋盘上所有合法的移动,并执行移动。

这个库还允许你查看各种棋盘状态信息,如王车易位权。

这个库有非常快的移动生成(其存在的首要目的),这将得到进一步优化。所有使棋子移动生成快速的技巧都被使用。

它不做什么

这不仅仅是一个棋类引擎,而是移动生成器。这不仅仅是一个棋类用户界面,而是移动生成器。这不仅仅是一个棋类PGN解析器,数据库,UCI通信器,XBOARD/WinBoard协议,网站或特级大师。只是一个谦逊的移动生成器。

更多即将到来

API 文档

https://jordanbray.github.io/chess/chess/.

历史

该项目是从 https://github.com/jordanbray/chess 分支出来的,版本为 91fe8e2。几个未合并的PR已添加到这个版本中 - 这些提交的作者已设置为反映实际作者。与chess crate保持向后兼容不是一个目标。在1.0之前,预计会有破坏性变化。请查看CHANGELOG.md以获取详细信息 - 所有破坏性变化都应标记为 BREAKING

从分支以来的一些改进

  • 构建时间有了显著提高。现在rust-analyzer实际上可以工作了(感谢KarelPeeters)
  • 对于完全填充的棋盘,检查status的速度提高了2-3倍(感谢AlexanderHarrison)
  • 使用新的cache_game_state功能,使用Game的速度提高了10-20倍,适用于合理大小的游戏(对于更大的游戏,提高更多)。如果您处于嵌入式环境且无法容忍额外的KB内存,您可以禁用此功能。
  • 检查未清理的走法的合法性提高了4-5倍
  • Game::make_move现在返回包含走法的SAN表示的Option<String>。为了避免在热路径上的开销,Board::make_move仍然返回bool。
  • Game添加了可选的仪器,使用tracing - 只需使用instrument_game功能。
  • 添加了便利方法Board::en_passant_targetBoard::has_checkers。目前,Board::en_passantBoard::en_passant_target稍快。
  • 添加了性能基准测试

这有没有什么好处?

是的

依赖关系

~140–280KB