chess_startpos_rs/lib.rs
1//! Generate, count, and sample chess back-rank arrangements under
2//! composable constraints (Chess960, Chess2880, custom presets).
3//!
4//! The crate is generic — parameterised over a user-defined piece
5//! kind, a user-defined colour set, and board size — and built around
6//! a small set of composable constraint primitives with `And` / `Or` /
7//! `Not` combinators. An opinionated [`chess`] module ships
8//! ready-to-use presets for the canonical chess shuffle variants
9//! (Chess960, Chess-2880, full shuffle).
10//!
11//! # Mental model
12//!
13//! - `pieces` is the **alphabet** — the *set* of distinct kinds
14//! available. Duplicate entries are silently deduplicated.
15//! - `num_squares` is the board size.
16//! - `square_colors` labels each square with a colour from the
17//! user-defined colour set. Generic; defaults to [`SquareColor`].
18//! - Constraints filter the arrangements you want. The solver
19//! enumerates length-`num_squares` sequences over the alphabet
20//! and keeps the ones satisfying the root constraint.
21//!
22//! The constraint vocabulary is `Count`, `CountOnColor`, `At`,
23//! `NotAt`, `Order`, and `Relative`, composable with `And` / `Or` /
24//! `Not`. See [`Constraint`] for each variant's semantics.
25//!
26//! # Quick start — chess presets
27//!
28//! ```
29//! use chess_startpos_rs::chess;
30//!
31//! assert_eq!(chess::shuffle().count(), 5040);
32//! assert_eq!(chess::chess_2880().count(), 2880);
33//! assert_eq!(chess::chess_960().count(), 960);
34//! ```
35//!
36//! # Custom piece kinds, boards, and colours
37//!
38//! ```
39//! use chess_startpos_rs::{alternating, Constraint, CountOp, Problem, SquareColor};
40//!
41//! #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
42//! enum Tile { A, B }
43//!
44//! let problem: Problem<Tile> = Problem {
45//! num_squares: 3,
46//! square_colors: alternating(3, SquareColor::Light, SquareColor::Dark),
47//! pieces: vec![Tile::A, Tile::B], // alphabet
48//! constraint: Constraint::And(vec![
49//! Constraint::Count { piece: Tile::A, op: CountOp::Eq, value: 2 },
50//! Constraint::Count { piece: Tile::B, op: CountOp::Eq, value: 1 },
51//! Constraint::At { piece: Tile::B, square: 1 },
52//! ]),
53//! };
54//! assert_eq!(problem.count(), 1);
55//! assert_eq!(problem.at(0), Some(vec![Tile::A, Tile::B, Tile::A]));
56//! ```
57
58#![warn(missing_docs)]
59
60use std::fmt::Debug;
61use std::hash::Hash;
62
63mod constraint;
64mod problem;
65
66pub mod chess;
67
68pub use constraint::{Constraint, CountOp, SquareColor};
69pub use problem::{Problem, ProblemBuilder, ValidationError};
70
71/// Marker trait for piece kinds usable as the type parameter of
72/// [`Problem`] / [`Constraint`].
73///
74/// Has a blanket impl: any type satisfying the supertrait bounds is
75/// automatically a `PieceKind`. The user's piece enum typically
76/// derives `Copy`, `Clone`, `Debug`, `PartialEq`, `Eq`,
77/// `PartialOrd`, `Ord`, `Hash`.
78pub trait PieceKind: Copy + Eq + Ord + Hash + Debug {}
79
80impl<T: Copy + Eq + Ord + Hash + Debug> PieceKind for T {}
81
82/// Marker trait for colour kinds usable as the type parameter of
83/// [`Problem`] / [`Constraint`].
84///
85/// Has a blanket impl: any type satisfying the supertrait bounds is
86/// automatically a `ColorKind`. The user's colour enum typically
87/// derives `Copy`, `Clone`, `Debug`, `PartialEq`, `Eq`, `Hash`.
88///
89/// The default colour type for `Problem` is [`SquareColor`] — the
90/// binary light/dark partition standard chess uses. Define your own
91/// for N-way partitions (halves, thirds, fairy zones, …).
92pub trait ColorKind: Copy + Eq + Hash + Debug {}
93
94impl<T: Copy + Eq + Hash + Debug> ColorKind for T {}
95
96/// Returns a `Vec<C>` of length `n` alternating between `first` and
97/// `second`, starting with `first`.
98///
99/// ```
100/// use chess_startpos_rs::{alternating, SquareColor};
101///
102/// let cs = alternating(4, SquareColor::Dark, SquareColor::Light);
103/// assert_eq!(
104/// cs,
105/// vec![
106/// SquareColor::Dark,
107/// SquareColor::Light,
108/// SquareColor::Dark,
109/// SquareColor::Light,
110/// ],
111/// );
112/// ```
113#[must_use]
114pub fn alternating<C: Copy>(n: usize, first: C, second: C) -> Vec<C> {
115 (0..n)
116 .map(|i| if i % 2 == 0 { first } else { second })
117 .collect()
118}
119
120/// Returns a `Vec<C>` of length `n` where every entry is `c`.
121///
122/// ```
123/// use chess_startpos_rs::{uniform, SquareColor};
124///
125/// assert_eq!(
126/// uniform(3, SquareColor::Light),
127/// vec![SquareColor::Light, SquareColor::Light, SquareColor::Light],
128/// );
129/// ```
130#[must_use]
131pub fn uniform<C: Copy>(n: usize, c: C) -> Vec<C> {
132 vec![c; n]
133}