Skip to main content

SymmetryHandler

Struct SymmetryHandler 

Source
pub struct SymmetryHandler;

Implementations§

Source§

impl SymmetryHandler

Source

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
Hide additional 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}
Source

pub fn canonical_payload(bb: &Bitboard) -> [u8; 16]

16-byte canonical payload (LE-packed planes of the canonical form).

Source

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§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V