pub struct SymmetryHandler;Implementations§
Source§impl SymmetryHandler
impl SymmetryHandler
Sourcepub fn find_canonical(bb: &Bitboard) -> Bitboard
pub fn find_canonical(bb: &Bitboard) -> Bitboard
Find the canonical (lexicographically smallest) bitboard under the 192-element symmetry group (8 D4 × 24 shape permutations, no color swap).
Examples found in repository?
examples/depth4_survey.rs (line 83)
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}More examples
examples/depth_sweep.rs (line 46)
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 canonical_payload(bb: &Bitboard) -> [u8; 16]
pub fn canonical_payload(bb: &Bitboard) -> [u8; 16]
16-byte canonical payload (LE-packed planes of the canonical form).
Sourcepub fn orbit_size(bb: &Bitboard) -> usize
pub fn orbit_size(bb: &Bitboard) -> usize
How many distinct boards in this orbit (1..192).
Examples found in repository?
examples/depth_sweep.rs (line 63)
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}Auto Trait Implementations§
impl Freeze for SymmetryHandler
impl RefUnwindSafe for SymmetryHandler
impl Send for SymmetryHandler
impl Sync for SymmetryHandler
impl Unpin for SymmetryHandler
impl UnsafeUnpin for SymmetryHandler
impl UnwindSafe for SymmetryHandler
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> 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