pub struct State {
pub bb: Bitboard,
}Expand description
Serialisable game state wrapping a Bitboard.
Fields§
§bb: BitboardImplementations§
Source§impl State
impl State
Sourcepub fn new(bb: Bitboard) -> Self
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
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}pub fn empty() -> Self
pub fn pack(&self, flags: u8) -> [u8; 18]
pub fn unpack(data: &[u8]) -> Result<Self, String>
Sourcepub fn to_qfen(&self) -> String
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
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}pub fn from_qfen(qfen: &str) -> Result<Self, String>
pub fn canonical_payload(&self) -> [u8; 16]
pub fn canonical_key(&self) -> [u8; 18]
pub fn symmetry_count(&self) -> usize
Trait Implementations§
impl Copy for State
impl Eq for State
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> 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