Skip to main content

State

Struct State 

Source
pub struct State {
    pub bb: Bitboard,
}
Expand description

Serialisable game state wrapping a Bitboard.

Fields§

§bb: Bitboard

Implementations§

Source§

impl State

Source

pub fn new(bb: Bitboard) -> Self

Examples found in repository?
examples/selfplay_export.rs (line 121)
71fn play_game(seed: u64, iterations: u32) -> (Vec<PendingRow>, WinStatus) {
72    let mut bb = Bitboard::EMPTY;
73    let mut rows = Vec::new();
74    let mut ply = 0u32;
75
76    loop {
77        if has_winning_line(&bb) {
78            return (rows, check_winner(&bb));
79        }
80        let legal = generate_legal_moves(&bb);
81        if legal.is_empty() {
82            // No legal moves: the side to move loses (see Global
83            // Constraints — this is a decisive result, never a draw).
84            let loser = current_player(&bb).unwrap();
85            let winner = if loser == 0 {
86                WinStatus::Player1Wins
87            } else {
88                WinStatus::Player0Wins
89            };
90            return (rows, winner);
91        }
92
93        let side_to_move = current_player(&bb).unwrap();
94        // use_transposition_table MUST be false here: with it on (the
95        // engine's actual default), root moves that canonicalize to the
96        // same child are merged onto one shared node and reported under a
97        // single arbitrary move, silently dropping every other legal move
98        // that led there — worst exactly at shallow plies (the empty
99        // board's 64 legal moves collapse to 3), which every self-play
100        // game passes through. Verified in mcts.rs's
101        // root_move_visits_default_config_collapses_symmetric_root_moves
102        // test — this is not a hypothetical concern, it was caught by
103        // Opus review of the PR that added root_move_visits and is
104        // exactly what this exporter must avoid to produce a faithful
105        // per-legal-move policy target.
106        let mut engine = MCTSEngine::new(MCTSConfig {
107            max_iterations: iterations,
108            seed: Some(seed.wrapping_add(ply as u64)),
109            use_transposition_table: false,
110            ..Default::default()
111        });
112        let (best_move, _) = engine.search(&bb).expect("legal moves exist");
113        let policy: Vec<(u8, u8, u32)> = engine
114            .root_move_visits()
115            .into_iter()
116            .map(|(mv, visits)| (mv.shape, mv.position, visits))
117            .collect();
118
119        rows.push(PendingRow {
120            ply,
121            qfen: State::new(bb).to_qfen(),
122            side_to_move,
123            policy,
124        });
125
126        bb = apply_move(&bb, &best_move);
127        ply += 1;
128    }
129}
More examples
Hide additional examples
examples/depth4_survey.rs (line 146)
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}
Source

pub fn empty() -> Self

Source

pub fn pack(&self, flags: u8) -> [u8; 18]

Source

pub fn unpack(data: &[u8]) -> Result<Self, String>

Source

pub fn to_qfen(&self) -> String

Examples found in repository?
examples/selfplay_export.rs (line 121)
71fn play_game(seed: u64, iterations: u32) -> (Vec<PendingRow>, WinStatus) {
72    let mut bb = Bitboard::EMPTY;
73    let mut rows = Vec::new();
74    let mut ply = 0u32;
75
76    loop {
77        if has_winning_line(&bb) {
78            return (rows, check_winner(&bb));
79        }
80        let legal = generate_legal_moves(&bb);
81        if legal.is_empty() {
82            // No legal moves: the side to move loses (see Global
83            // Constraints — this is a decisive result, never a draw).
84            let loser = current_player(&bb).unwrap();
85            let winner = if loser == 0 {
86                WinStatus::Player1Wins
87            } else {
88                WinStatus::Player0Wins
89            };
90            return (rows, winner);
91        }
92
93        let side_to_move = current_player(&bb).unwrap();
94        // use_transposition_table MUST be false here: with it on (the
95        // engine's actual default), root moves that canonicalize to the
96        // same child are merged onto one shared node and reported under a
97        // single arbitrary move, silently dropping every other legal move
98        // that led there — worst exactly at shallow plies (the empty
99        // board's 64 legal moves collapse to 3), which every self-play
100        // game passes through. Verified in mcts.rs's
101        // root_move_visits_default_config_collapses_symmetric_root_moves
102        // test — this is not a hypothetical concern, it was caught by
103        // Opus review of the PR that added root_move_visits and is
104        // exactly what this exporter must avoid to produce a faithful
105        // per-legal-move policy target.
106        let mut engine = MCTSEngine::new(MCTSConfig {
107            max_iterations: iterations,
108            seed: Some(seed.wrapping_add(ply as u64)),
109            use_transposition_table: false,
110            ..Default::default()
111        });
112        let (best_move, _) = engine.search(&bb).expect("legal moves exist");
113        let policy: Vec<(u8, u8, u32)> = engine
114            .root_move_visits()
115            .into_iter()
116            .map(|(mv, visits)| (mv.shape, mv.position, visits))
117            .collect();
118
119        rows.push(PendingRow {
120            ply,
121            qfen: State::new(bb).to_qfen(),
122            side_to_move,
123            policy,
124        });
125
126        bb = apply_move(&bb, &best_move);
127        ply += 1;
128    }
129}
More examples
Hide additional examples
examples/depth4_survey.rs (line 146)
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}
Source

pub fn from_qfen(qfen: &str) -> Result<Self, String>

Source

pub fn canonical_payload(&self) -> [u8; 16]

Source

pub fn canonical_key(&self) -> [u8; 18]

Source

pub fn symmetry_count(&self) -> usize

Trait Implementations§

Source§

impl Clone for State

Source§

fn clone(&self) -> State

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for State

Source§

impl Debug for State

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for State

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Eq for State

Source§

impl Hash for State

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for State

Source§

fn eq(&self, other: &State) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for State

Auto Trait Implementations§

§

impl Freeze for State

§

impl RefUnwindSafe for State

§

impl Send for State

§

impl Sync for State

§

impl Unpin for State

§

impl UnsafeUnpin for State

§

impl UnwindSafe for State

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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