#[repr(C, align(16))]pub struct Bitboard {
pub planes: [u16; 8],
}Expand description
128-bit bitboard: 8 planes of u16 for a 4×4 Quantik board.
Layout: [C0S0, C0S1, C0S2, C0S3, C1S0, C1S1, C1S2, C1S3]
where C = color (0 = player 0, 1 = player 1) and S = shape (0..3 → A..D).
Fields§
§planes: [u16; 8]Implementations§
Source§impl Bitboard
impl Bitboard
pub const EMPTY: Self
pub fn new(planes: [u16; 8]) -> Self
pub fn plane_index(player: u8, shape: u8) -> usize
pub fn occupied(&self) -> u16
pub fn is_position_occupied(&self, pos: u8) -> bool
Sourcepub fn with_move(&self, player: u8, shape: u8, position: u8) -> Self
pub fn with_move(&self, player: u8, shape: u8, position: u8) -> Self
Set one bit and return a new bitboard (functional style).
Sourcepub fn player_piece_count(&self, player: u8) -> u32
pub fn player_piece_count(&self, player: u8) -> u32
Total number of pieces for a given player.
Sourcepub fn shape_piece_count(&self, player: u8, shape: u8) -> u32
pub fn shape_piece_count(&self, player: u8, shape: u8) -> u32
Piece count for a specific (player, shape) pair.
Sourcepub fn to_le_bytes(&self) -> [u8; 16]
pub fn to_le_bytes(&self) -> [u8; 16]
Pack to 16 little-endian bytes.
Examples found in repository?
examples/depth4_survey.rs (line 73)
71fn enumerate_depth4() -> Vec<(Bitboard, u64)> {
72 let mut frontier: HashMap<[u8; 16], (Bitboard, u64)> = HashMap::new();
73 frontier.insert(Bitboard::EMPTY.to_le_bytes(), (Bitboard::EMPTY, 1));
74
75 for _ply in 0..4 {
76 let mut next: HashMap<[u8; 16], (Bitboard, u64)> = HashMap::new();
77 for (bb, mult) in frontier.values() {
78 for mv in generate_legal_moves(bb) {
79 let child = apply_move(bb, &mv);
80 if has_winning_line(&child) {
81 continue;
82 }
83 let canon = SymmetryHandler::find_canonical(&child);
84 let key = canon.to_le_bytes();
85 let entry = next.entry(key).or_insert((canon, 0));
86 entry.1 += mult;
87 }
88 }
89 frontier = next;
90 }
91
92 let mut states: Vec<(Bitboard, u64)> = frontier
93 .into_values()
94 .filter(|(bb, _)| !generate_legal_moves(bb).is_empty())
95 .collect();
96 states.sort_by_key(|(bb, _)| bb.to_le_bytes());
97 states
98}
99
100fn main() {
101 let args = parse_args();
102
103 let started = Instant::now();
104 let mut states = enumerate_depth4();
105 println!(
106 "enumerated {} canonical nonterminal depth-4 states in {:.2}s",
107 states.len(),
108 started.elapsed().as_secs_f64()
109 );
110 if args.sample > 0 && args.sample < states.len() {
111 use rand::prelude::*;
112 let mut rng = StdRng::seed_from_u64(args.seed);
113 states.shuffle(&mut rng);
114 states.truncate(args.sample);
115 states.sort_by_key(|(bb, _)| bb.to_le_bytes());
116 println!(
117 "sampled {} of {} states (seed={})",
118 states.len(),
119 10946,
120 args.seed
121 );
122 } else if args.limit > 0 {
123 states.truncate(args.limit);
124 println!("limited to {} states for this run", states.len());
125 }
126
127 let book = OpeningBookDatabase::open(&OpeningBookConfig {
128 database_path: args.db.clone(),
129 ..Default::default()
130 })
131 .expect("open book");
132
133 let mut rows: Vec<serde_json::Value> = Vec::with_capacity(states.len());
134 let mut solved = 0usize;
135 let mut cutoff = 0usize;
136 let solve_started = Instant::now();
137
138 for (i, (bb, mult)) in states.iter().enumerate() {
139 let t0 = Instant::now();
140 let reference = solve_position_with_book(bb, args.budget_s, Some(&book));
141 let elapsed = t0.elapsed().as_secs_f64();
142 println!(
143 " [{}/{}] {} solved={} elapsed={:.3}s",
144 i + 1,
145 states.len(),
146 State::new(*bb).to_qfen(),
147 reference.is_some(),
148 elapsed
149 );
150
151 match &reference {
152 Some(r) => {
153 solved += 1;
154 rows.push(serde_json::json!({
155 "qfen": State::new(*bb).to_qfen(),
156 "multiplicity": mult,
157 "value": r["value"],
158 "nodes": r["nodes"],
159 "solve_time_s": elapsed,
160 "solver": r["solver"],
161 }));
162 }
163 None => {
164 cutoff += 1;
165 rows.push(serde_json::json!({
166 "qfen": State::new(*bb).to_qfen(),
167 "multiplicity": mult,
168 "value": null,
169 "nodes": null,
170 "solve_time_s": elapsed,
171 "solver": null,
172 }));
173 }
174 }
175
176 if (i + 1) % 25 == 0 || i + 1 == states.len() {
177 println!(
178 "progress: {}/{} solved={} cutoff={} elapsed_total={:.1}s",
179 i + 1,
180 states.len(),
181 solved,
182 cutoff,
183 solve_started.elapsed().as_secs_f64()
184 );
185 // Flush partial results after every progress line so a killed
186 // run still leaves a readable, if incomplete, artifact — the
187 // SQLite book itself is already durable per-row regardless.
188 let partial = serde_json::json!({
189 "budget_s": args.budget_s,
190 "total_canonical_depth4_states": states.len(),
191 "processed": i + 1,
192 "solved": solved,
193 "cutoff": cutoff,
194 "positions": rows,
195 });
196 std::fs::write(&args.out, serde_json::to_string_pretty(&partial).unwrap())
197 .expect("write partial output");
198 }
199 }
200
201 println!(
202 "done: {} solved, {} cutoff, total solve wall time {:.1}s -> {}",
203 solved,
204 cutoff,
205 solve_started.elapsed().as_secs_f64(),
206 args.out
207 );
208}More examples
examples/depth_sweep.rs (line 23)
21fn main() {
22 let mut frontier: HashMap<[u8; 16], (Bitboard, u64)> = HashMap::new();
23 frontier.insert(Bitboard::EMPTY.to_le_bytes(), (Bitboard::EMPTY, 1));
24
25 println!(
26 "depth,canonical_states,raw_boards,p0_wins_raw,p1_wins_raw,with_legal_moves,mean_orbit,min_orbit,max_orbit,elapsed_s"
27 );
28
29 for depth in 1..=16u32 {
30 let started = Instant::now();
31 let mut next: HashMap<[u8; 16], (Bitboard, u64)> = HashMap::new();
32 let mut p0_wins_raw: u64 = 0;
33 let mut p1_wins_raw: u64 = 0;
34
35 for (bb, mult) in frontier.values() {
36 for mv in generate_legal_moves(bb) {
37 let child = apply_move(bb, &mv);
38 if has_winning_line(&child) {
39 match check_winner(&child) {
40 WinStatus::Player0Wins => p0_wins_raw += mult,
41 WinStatus::Player1Wins => p1_wins_raw += mult,
42 WinStatus::NoWin => unreachable!(),
43 }
44 continue;
45 }
46 let canon = SymmetryHandler::find_canonical(&child);
47 let key = canon.to_le_bytes();
48 let entry = next.entry(key).or_insert((canon, 0));
49 entry.1 += mult;
50 }
51 }
52 frontier = next;
53
54 let canonical_states = frontier.len();
55 let raw_boards: u64 = frontier.values().map(|(_, m)| *m).sum();
56 let with_moves = frontier
57 .values()
58 .filter(|(bb, _)| !generate_legal_moves(bb).is_empty())
59 .count();
60
61 let mut orbit_sizes: Vec<usize> = frontier
62 .values()
63 .map(|(bb, _)| SymmetryHandler::orbit_size(bb))
64 .collect();
65 orbit_sizes.sort_unstable();
66 let mean = if orbit_sizes.is_empty() {
67 0.0
68 } else {
69 orbit_sizes.iter().sum::<usize>() as f64 / orbit_sizes.len() as f64
70 };
71 let min = orbit_sizes.first().copied().unwrap_or(0);
72 let max = orbit_sizes.last().copied().unwrap_or(0);
73
74 // Per-depth orbit-size histogram, written alongside the summary line.
75 let mut hist: BTreeMap<usize, u64> = BTreeMap::new();
76 for bb in frontier.values() {
77 *hist.entry(SymmetryHandler::orbit_size(&bb.0)).or_insert(0) += 1;
78 }
79 let hist_str: Vec<String> = hist.iter().map(|(s, c)| format!("{s}:{c}")).collect();
80
81 println!(
82 "{depth},{canonical_states},{raw_boards},{p0_wins_raw},{p1_wins_raw},{with_moves},{mean:.2},{min},{max},{:.3}",
83 started.elapsed().as_secs_f64()
84 );
85 eprintln!(" depth {depth} orbit histogram: {}", hist_str.join(" "));
86
87 if canonical_states == 0 {
88 eprintln!("frontier empty at depth {depth} — every line of play has ended by here");
89 break;
90 }
91 if with_moves == 0 {
92 eprintln!("all depth-{depth} states are dead ends (no legal moves) — game always over by depth {}", depth + 1);
93 }
94 }
95}Sourcepub fn from_le_bytes(buf: &[u8; 16]) -> Self
pub fn from_le_bytes(buf: &[u8; 16]) -> Self
Unpack from 16 little-endian bytes.
Trait Implementations§
impl Copy for Bitboard
impl Eq for Bitboard
impl StructuralPartialEq for Bitboard
Auto Trait Implementations§
impl Freeze for Bitboard
impl RefUnwindSafe for Bitboard
impl Send for Bitboard
impl Sync for Bitboard
impl Unpin for Bitboard
impl UnsafeUnpin for Bitboard
impl UnwindSafe for Bitboard
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
Converts
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
Converts
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more