diff --git a/src/solver/algorithm/strong/cyclic.rs b/src/solver/algorithm/strong/cyclic.rs index 670c758..06f4244 100644 --- a/src/solver/algorithm/strong/cyclic.rs +++ b/src/solver/algorithm/strong/cyclic.rs @@ -6,3 +6,232 @@ //! //! #### Authorship //! - Max Fierro, 12/3/2023 (maxfierro@berkeley.edu) +//! - Ishir Garg, 3/12/2024 (ishirgarg@berkeley.edu) + +use anyhow::{bail, Context, Result}; + +use std::collections::{HashMap, VecDeque}; + +use crate::database::KVStore; +use crate::game::{Bounded, Transition}; +use crate::interface::IOMode; +use crate::model::game::State; +use crate::model::solver::SUtility; +use crate::solver::error::SolverError; +use crate::solver::record::sur::RecordBuffer; +use crate::solver::{Sequential, SimpleUtility}; +use crate::util::Identify; + +/* SOLVER */ + +pub fn solver(game: &G, mode: IOMode) -> Result<()> +where + G: Transition + + Bounded + + SimpleUtility<2, B> + + Sequential<2, B> + + Identify, +{ + todo!() +} + +/* DATABASE INITIALIZATION */ + +/// TODO + +/* SOLVING ALGORITHM */ + +fn cyclic_solver(game: &G, db: &mut D) -> Result<()> +where + G: Transition + Bounded + SimpleUtility<2, B> + Sequential<2, B>, + D: KVStore, +{ + let mut winning_frontier = VecDeque::new(); + let mut tying_frontier = VecDeque::new(); + let mut losing_frontier = VecDeque::new(); + let mut child_counts = HashMap::new(); + enqueue_children( + &mut child_counts, + &mut winning_frontier, + &mut losing_frontier, + &mut tying_frontier, + game.start(), + game, + db, + )?; + + while !winning_frontier.is_empty() + && !losing_frontier.is_empty() + && !tying_frontier.is_empty() + { + let child = if !losing_frontier.is_empty() { + losing_frontier + .pop_front() + .unwrap() + } else if !winning_frontier.is_empty() { + winning_frontier + .pop_front() + .unwrap() + } else { + tying_frontier.pop_front().unwrap() + }; + + let db_entry = RecordBuffer::from(db.get(&child).unwrap()) + .context("Failed to create record for middle state.")?; + + let child_utility = db_entry + .get_utility(game.turn(child)) + .context("Failed to get utility from record.")?; + + let child_remoteness = db_entry.get_remoteness(); + let parents = game.retrograde(child); + + match child_utility { + SUtility::Lose => { + for parent in parents { + if *child_counts.get(&parent).unwrap() > 0 { + let mut buf = RecordBuffer::new(game.players()) + .context( + "Failed to create record for end state.", + )?; + + buf.set_utility([SUtility::Win, SUtility::Lose])?; + + buf.set_remoteness(child_remoteness + 1)?; + db.put(&parent, &buf); + + child_counts.insert(parent, 0); + winning_frontier.push_back(parent); + } + } + }, + SUtility::Tie => { + for parent in parents { + let child_count = *child_counts.get(&parent).unwrap(); + if child_count == 0 { + continue; + } + + let mut buf = RecordBuffer::new(game.players()) + .context("Failed to create record for end state.")?; + + buf.set_utility([SUtility::Tie, SUtility::Tie])?; + buf.set_remoteness(child_remoteness + 1)?; + db.put(&parent, &buf); + + tying_frontier.push_back(parent); + child_counts.insert(parent, 0); + } + }, + SUtility::Win => { + for parent in parents { + let child_count = *child_counts.get(&parent).unwrap(); + if child_count == 0 { + continue; + } + + if child_count == 1 { + let mut buf = RecordBuffer::new(game.players()) + .context( + "Failed to create record for end state.", + )?; + + buf.set_utility([SUtility::Lose, SUtility::Win])?; + + buf.set_remoteness(child_remoteness + 1)?; + db.put(&parent, &buf); + + losing_frontier.push_back(parent); + } + + child_counts.insert(parent, child_count - 1); + } + }, + SUtility::Draw => bail!(SolverError::SolverViolation { + name: todo!(), + hint: todo!(), + }), + } + } + + for (parent, child_count) in child_counts { + if child_count > 0 { + let mut buf = RecordBuffer::new(game.players()) + .context("Failed to create record for end state.")?; + + buf.set_utility([SUtility::Draw, SUtility::Draw])?; + db.put(&parent, &buf); + } + } + + Ok(()) +} + +fn enqueue_children( + child_counts: &mut HashMap, usize>, + winning_frontier: &mut VecDeque>, + losing_frontier: &mut VecDeque>, + tying_frontier: &mut VecDeque>, + curr_state: State, + game: &G, + db: &mut D, +) -> Result<()> +where + G: Transition + Bounded + SimpleUtility<2, B> + Sequential<2, B>, + D: KVStore, +{ + if game.end(curr_state) { + let mut buf = RecordBuffer::new(game.players()) + .context("Failed to create placeholder record.")?; + buf.set_utility(game.utility(curr_state)) + .context("Failed to copy utility values to record.")?; + buf.set_remoteness(0) + .context("Failed to set remoteness for end state.")?; + db.put(&curr_state, &buf); + + match game + .utility(curr_state) + .get(game.turn(curr_state)) + .unwrap() + { + SUtility::Win => winning_frontier.push_back(curr_state), + SUtility::Tie => tying_frontier.push_back(curr_state), + SUtility::Lose => losing_frontier.push_back(curr_state), + SUtility::Draw => bail!(SolverError::SolverViolation { + name: todo!(), + hint: todo!(), + }), + } + return Ok(()); + } + + let children = game.prograde(curr_state); + child_counts.insert(curr_state, children.len()); + + for child in children { + if child_counts.contains_key(&child) { + continue; + } + + /// TODO: No recursion allowed + enqueue_children( + child_counts, + winning_frontier, + losing_frontier, + tying_frontier, + child, + game, + db, + )?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + + use super::*; + + // TODO +} diff --git a/src/solver/algorithm/strong/puzzle.rs b/src/solver/algorithm/strong/puzzle.rs index d969b1b..3df25e9 100644 --- a/src/solver/algorithm/strong/puzzle.rs +++ b/src/solver/algorithm/strong/puzzle.rs @@ -7,112 +7,222 @@ use anyhow::{Context, Result}; -use crate::database::volatile; -use crate::database::{KVStore, Tabular}; -use crate::game::{Bounded, DTransition, GeneralSum, Playable, STransition}; +use std::collections::VecDeque; + +use crate::database::KVStore; +use crate::game::{Bounded, Transition}; use crate::interface::IOMode; -use crate::model::{PlayerCount, Remoteness, State, Utility}; -use crate::solver::record::mur::RecordBuffer; -use crate::solver::{RecordType, MAX_TRANSITIONS}; +use crate::model::game::State; +use crate::model::solver::{Remoteness, SUtility}; +use crate::solver::record::surcc::{ChildCount, RecordBuffer}; +use crate::solver::{ClassicPuzzle, SimpleUtility}; + +/* SOLVER */ -pub fn dynamic_solver(game: &G, mode: IOMode) -> Result<()> +pub fn dynamic_solver(game: &G, mode: IOMode) -> Result<()> where - G: DTransition + Bounded + Playable + GeneralSum, + G: Transition + Bounded + ClassicPuzzle, { - let mut db = volatile_database(game) - .context("Failed to initialize volatile database.")?; + todo!() +} - bfs(&mut db, game) - .context("Failed solving algorithm execution.")?; +/* DATABASE INITIALIZATION */ - Ok(()) -} +// TODO +/* SOLVING ALGORITHM */ -fn bfs(game: &G, db: &mut D) +/// Runs BFS starting from the ending primitive positions of a game, working its +/// way up the game tree in reverse. Assigns a remoteness and simple utiliity to +/// every winning and losing position. Draws (positions where winning is +/// impossible, but it is possible to play forever without losing) not assigned +/// a remoteness. This implementation uses the SURCC record to store child count +/// along with utility and remoteness. +fn reverse_bfs_solver(db: &mut D, game: &G) -> Result<()> where - G: DTransition + Bounded + SimpleSum, - D: KVStore, + G: Transition + Bounded + ClassicPuzzle, + D: KVStore, { - let end_states = discover_end_states_helper(db, game); + let end_states = discover_child_counts(db, game)?; + + let mut winning_queue: VecDeque> = VecDeque::new(); + let mut losing_queue: VecDeque> = VecDeque::new(); + for end_state in end_states { + let utility = game.utility(end_state); + match utility { + SUtility::Win => winning_queue.push_back(end_state), + SUtility::Lose => losing_queue.push_back(end_state), + SUtility::Tie => todo!(), + SUtility::Draw => todo!(), + }; + update_db_record(db, end_state, utility, 0, 0)?; + } + + reverse_bfs_winning_states(db, game, &mut winning_queue)?; + reverse_bfs_losing_states(db, game, &mut losing_queue)?; - for state in end_states { - let mut buf = RecordBuffer::new() - .context("Failed to create placeholder record.")?; - buf.set_remoteness(0) - .context("Failed to set remoteness for end state.")?; - db.put(state, &buf); + Ok(()) +} - bfs_state(db, game, state); +/// Performs BFS on winning states, marking visited states as a win +fn reverse_bfs_winning_states( + db: &mut D, + game: &G, + winning_queue: &mut VecDeque>, +) -> Result<()> +where + G: Transition + Bounded, + D: KVStore, +{ + while let Some(state) = winning_queue.pop_front() { + let buf = RecordBuffer::from(db.get(&state).unwrap())?; + let child_remoteness = buf.get_remoteness(); + + for parent in game.retrograde(state) { + let child_count = + RecordBuffer::from(db.get(&parent).unwrap())?.get_child_count(); + if child_count > 0 { + winning_queue.push_back(parent); + update_db_record( + db, + parent, + SUtility::Win, + 1 + child_remoteness, + 0, + )?; + } + } } + + Ok(()) } -fn bfs_state(db: &mut D, game: &G) +/// Performs BFS on losing states, marking visited states as a loss. Remoteness +/// is the shortest path to a primitive losing position. +fn reverse_bfs_losing_states( + db: &mut D, + game: &G, + losing_queue: &mut VecDeque>, +) -> Result<()> where - G: DTransition + Bounded + SimpleSum, - D: KVStore, + G: Transition + Bounded, + D: KVStore, { + while let Some(state) = losing_queue.pop_front() { + let parents = game.retrograde(state); + let child_remoteness = + RecordBuffer::from(db.get(&state).unwrap())?.get_remoteness(); + + for parent in parents { + let child_count = + RecordBuffer::from(db.get(&parent).unwrap())?.get_child_count(); + if child_count > 0 { + // Update child count + let mut buf = RecordBuffer::from(db.get(&parent).unwrap()) + .context("Failed to get record for middle state")?; + let new_child_count = buf.get_child_count() - 1; + buf.set_child_count(new_child_count)?; + db.put(&parent, &buf); + + // If all children have been solved, set this state as a losing + // state + if new_child_count == 0 { + losing_queue.push_back(parent); + update_db_record( + db, + parent, + SUtility::Lose, + 1 + child_remoteness, + 0, + )?; + } + } + } + } + Ok(()) } -fn discover_end_states(db: &mut D, game: &G) -> Vec +/// Updates the database record for a puzzle with given simple utility, +/// remoteness, and child count +fn update_db_record( + db: &mut D, + state: State, + utility: SUtility, + remoteness: Remoteness, + child_count: ChildCount, +) -> Result<()> where - G: DTransition + Bounded + SimpleSum, - D: KVStore, + D: KVStore, { - let visited = HashSet::new(); - let end_states = Vec::new(); + let mut buf = RecordBuffer::from(db.get(&state).unwrap()) + .context("Failed to create record for middle state")?; + buf.set_utility([utility]) + .context("Failed to set utility for state.")?; + buf.set_remoteness(remoteness) + .context("Failed to set remoteness for state.")?; + buf.set_child_count(child_count) + .context("Failed to set child count for state.")?; + db.put(&state, &buf); + + Ok(()) +} - discover_end_states(db, game, game.start(), visited, end_states); +fn discover_child_counts( + db: &mut D, + game: &G, +) -> Result>> +where + G: Transition + Bounded, + D: KVStore, +{ + let mut end_states = Vec::new(); + discover_child_counts_from_state(db, game, game.start(), &mut end_states)?; - end_states + Ok(end_states) } -fn discover_end_states_helper(db: &mut D, game: &G, state: State, visited: HashSet, end_states: Vec) +fn discover_child_counts_from_state( + db: &mut D, + game: &G, + state: State, + end_states: &mut Vec>, +) -> Result<()> where - G: DTransition + Bounded + SimpleSum, - D: KVStore, + G: Transition + Bounded, + D: KVStore, { - visited.insert(state); + let child_count = game.prograde(state).len() as ChildCount; - if game.end(state) { - end_states.insert(state); + if child_count == 0 { + end_states.push(state); } - for child in game.prograde(state) { - if !visted.contains(child) { - discover_end_states(db, game, child, visited, end_states); + let mut buf = + RecordBuffer::new(1).context("Failed to create record for state")?; + buf.set_utility([SUtility::Draw]) + .context("Failed to set remoteness for state")?; + buf.set_child_count(child_count) + .context("Failed to set child count for state.")?; + db.put(&state, &buf); + + for &child in game + .prograde(state) + .iter() + .chain(game.retrograde(state).iter()) + { + if db.get(&child).is_none() { + discover_child_counts_from_state(db, game, child, end_states)?; } } -} -/* DATABASE INITIALIZATION */ -/// Initializes a volatile database, creating a table schema according to the -/// solver record layout, initializing a table with that schema, and switching -/// to that table before returning the database handle. -fn volatile_database(game: &G) -> Result -where - G: Playable, -{ - let id = game.id(); - let db = volatile::Database::initialize(); - - let schema = RecordType::REMOTE(N) - .try_into() - .context("Failed to create table schema for solver records.")?; - db.create_table(&id, schema) - .context("Failed to create database table for solution set.")?; - db.select_table(&id) - .context("Failed to select solution set database table.")?; - - Ok(db) + Ok(()) } - #[cfg(test)] -mod test { - #[test] - fn test() { - assert!(false); - } +mod tests { + + use super::*; + + // TODO } diff --git a/src/solver/mod.rs b/src/solver/mod.rs index 7c9eedb..f3dbe08 100644 --- a/src/solver/mod.rs +++ b/src/solver/mod.rs @@ -27,6 +27,7 @@ pub mod record { pub mod mur; pub mod sur; pub mod rem; + pub mod surcc; } /// Implementations of algorithms that can consume game implementations and @@ -48,6 +49,7 @@ pub mod algorithm { pub mod strong { pub mod acyclic; pub mod cyclic; + pub mod puzzle; } /// Solving algorithms for deterministic complete-information games that @@ -78,6 +80,9 @@ pub enum RecordType { SUR(PlayerCount), /// Remoteness record (no utilities). REM, + /// Simple Utility Remoteness with Child Counts records for a specific + /// number of players + SURCC(PlayerCount), } /* STRUCTURAL INTERFACES */ diff --git a/src/solver/record/surcc.rs b/src/solver/record/surcc.rs new file mode 100644 index 0000000..d446109 --- /dev/null +++ b/src/solver/record/surcc.rs @@ -0,0 +1,380 @@ +//! # Simple-Utility Remoteness with Child Counts (SURCC) Record Module +//! +//! Implementation of a database record buffer for storing simple utilities +//! information of different amounts of players and the remoteness value +//! associated with a particular game state, along with the child count This is +//! mainly for internal solver use; some solving algorithms need to track child +//! counts along with each state +//! +//! #### Authorship +//! +//! - Ishir Garg, 4/1/2024 (ishirgarg@berkeley.edu) + +use anyhow::{Context, Result}; +use bitvec::field::BitField; +use bitvec::order::Msb0; +use bitvec::slice::BitSlice; +use bitvec::{bitarr, BitArr}; + +use crate::database::{Attribute, Datatype, Record, Schema, SchemaBuilder}; +use crate::model::game::{Player, PlayerCount}; +use crate::model::solver::{Remoteness, SUtility}; +use crate::solver::error::SolverError::RecordViolation; +use crate::solver::RecordType; +use crate::util; + +/* CONSTANTS */ + +/// The exact number of bits that are used to encode remoteness. +pub const REMOTENESS_SIZE: usize = 16; + +/// The maximum number of bits that can be used to encode a single record. +pub const BUFFER_SIZE: usize = 128; + +/// The exact number of bits that are used to encode utility for one player. +pub const UTILITY_SIZE: usize = 2; + +/// The exact number of bits that are used to encode the child counts for a +/// state/record entry. +pub const CHILD_COUNT_SIZE: usize = 32; + +/// Type for child count +pub type ChildCount = u64; + +/* SCHEMA GENERATOR */ + +/// Return the database table schema associated with a record instance with +/// a specific number of `players` under this record implementation. +pub fn schema(players: PlayerCount) -> Result { + if RecordBuffer::bit_size(players) > BUFFER_SIZE { + Err(RecordViolation { + name: RecordType::SURCC(players).to_string(), + hint: format!( + "This record can only hold utility values for up to {} \ + players, but there was an attempt to create a schema that \ + would represent one holding {} players.", + RecordBuffer::player_count(BUFFER_SIZE), + players + ), + })? + } else { + let mut schema = SchemaBuilder::new().of(RecordType::SURCC(players)); + + for i in 0..players { + let name = &format!("P{} utility", i); + let data = Datatype::ENUM; + let size = UTILITY_SIZE; + schema = schema + .add(Attribute::new(name, data, size)) + .context( + "Failed to add utility attribute to database schema.", + )?; + } + + let name = "State remoteness"; + let data = Datatype::UINT; + let size = REMOTENESS_SIZE; + schema = schema + .add(Attribute::new(name, data, size)) + .context( + "Failed to add remoteness attribute to database schema.", + )?; + + let name = "State child count"; + let data = Datatype::UINT; + let size = CHILD_COUNT_SIZE; + schema = schema + .add(Attribute::new(name, data, size)) + .context( + "Failed to add child count attribute to database schema.", + )?; + + Ok(schema.build()) + } +} + +/* RECORD IMPLEMENTATION */ + +/// Solver-specific record entry, meant to communicate the remoteness and each +/// player's utility at a corresponding game state. The layout is as follows: +/// +/// ```none +/// [UTILITY_SIZE bits: P0 utility] +/// ... +/// [UTILITY_SIZE bits: P(N-1) utility] +/// [REMOTENESS_SIZE bits: Remoteness] +/// [CHILD_COUNT_SIZE bits: Child count] +/// [0b0 until BUFFER_SIZE] +/// ``` +/// +/// The number of players `N` is limited by `BUFFER_SIZE`, because a statically +/// sized buffer is used for intermediary storage. The utility and remoteness +/// values are encoded in big-endian, with utility being a signed two's +/// complement integer and remoteness an unsigned integer. +pub struct RecordBuffer { + buf: BitArr!(for BUFFER_SIZE, in u8, Msb0), + players: PlayerCount, +} + +impl Record for RecordBuffer { + #[inline(always)] + fn raw(&self) -> &BitSlice { + &self.buf[..Self::bit_size(self.players)] + } +} + +impl RecordBuffer { + /// Returns a new instance of a bit-packed record buffer that is able to + /// store utility values for `players`. Fails if `players` is too high for + /// the underlying buffer's capacity. + #[inline(always)] + pub fn new(players: PlayerCount) -> Result { + if Self::bit_size(players) > BUFFER_SIZE { + Err(RecordViolation { + name: RecordType::SURCC(players).to_string(), + hint: format!( + "The record can only hold utility values for up to {} \ + players, but there was an attempt to instantiate one for \ + {} players.", + Self::player_count(BUFFER_SIZE), + players + ), + })? + } else { + Ok(Self { + buf: bitarr!(u8, Msb0; 0; BUFFER_SIZE), + players, + }) + } + } + + /// Return a new instance with `bits` as the underlying buffer. Fails in the + /// event that the size of `bits` is incoherent with the record. + #[inline(always)] + pub fn from(bits: &BitSlice) -> Result { + let len = bits.len(); + if len > BUFFER_SIZE { + Err(RecordViolation { + name: RecordType::SURCC(0).to_string(), + hint: format!( + "The record implementation operates on a buffer of {} \ + bits, but there was an attempt to instantiate one from a \ + buffer of {} bits.", + BUFFER_SIZE, len, + ), + })? + } else if len < Self::minimum_bit_size() { + Err(RecordViolation { + name: RecordType::SURCC(0).to_string(), + hint: format!( + "This record implementation stores utility values, but \ + there was an attempt to instantiate one with from a buffer \ + with {} bits, which is not enough to store a remoteness \ + and child count value (which takes {} bits).", + len, + Self::minimum_bit_size(), + ), + })? + } else { + let players = Self::player_count(len); + let mut buf = bitarr!(u8, Msb0; 0; BUFFER_SIZE); + buf[..len].copy_from_bitslice(bits); + Ok(Self { players, buf }) + } + } + + /* GET METHODS */ + + /// Parse and return the utility value corresponding to `player`. Fails if + /// the `player` index passed in is incoherent with player count. + #[inline(always)] + pub fn get_utility(&self, player: Player) -> Result { + if player >= self.players { + Err(RecordViolation { + name: RecordType::SURCC(self.players).to_string(), + hint: format!( + "A record was instantiated with {} utility entries, and \ + there was an attempt to fetch the utility of player {} \ + (0-indexed) from that record instance.", + self.players, player, + ), + })? + } else { + let start = Self::utility_index(player); + let end = start + UTILITY_SIZE; + let val = self.buf[start..end].load_be::(); + if let Ok(utility) = SUtility::try_from(val) { + Ok(utility) + } else { + Err(RecordViolation { + name: RecordType::SURCC(self.players).to_string(), + hint: format!( + "There was an attempt to deserialize a utility value \ + of '{}' into a simple utility type.", + val, + ), + })? + } + } + } + + /// Parse and return the remoteness value in the record encoding. Failure + /// here indicates corrupted state. + #[inline(always)] + pub fn get_remoteness(&self) -> Remoteness { + let start = Self::remoteness_index(self.players); + let end = start + REMOTENESS_SIZE; + self.buf[start..end].load_be::() + } + + /// Parse and return the child count value in the record encoding. Failure + /// here indicates corrupted state. + #[inline(always)] + pub fn get_child_count(&self) -> ChildCount { + let start = Self::child_count_index(self.players); + let end = start + CHILD_COUNT_SIZE; + self.buf[start..end].load_be::() + } + + /* SET METHODS */ + + /// Set this entry to have the utility values in `v` for each player. Fails + /// if any of the utility values are too high to fit in the space dedicated + /// for each player's utility, or if there is a mismatch between player + /// count and the number of utility values passed in. + #[inline(always)] + pub fn set_utility( + &mut self, + v: [SUtility; N], + ) -> Result<()> { + if N != self.players { + Err(RecordViolation { + name: RecordType::SURCC(self.players).to_string(), + hint: format!( + "A record was instantiated with {} utility entries, and \ + there was an attempt to use a {}-entry utility list to \ + update the record utility values.", + self.players, N, + ), + })? + } else { + for player in 0..self.players { + let utility = v[player] as u64; + let size = util::min_ubits(utility); + if size > UTILITY_SIZE { + Err(RecordViolation { + name: RecordType::SURCC(self.players).to_string(), + hint: format!( + "This record implementation uses {} bits to store \ + signed integers representing utility values, but \ + there was an attempt to store a utility of {}, \ + which requires at least {} bits to store.", + UTILITY_SIZE, utility, size, + ), + })? + } + + let start = Self::utility_index(player); + let end = start + UTILITY_SIZE; + self.buf[start..end].store_be(utility); + } + Ok(()) + } + } + + /// Set this entry to have `value` remoteness. Fails if `value` is too high + /// to fit in the space dedicated for remoteness within the record. + #[inline(always)] + pub fn set_remoteness(&mut self, value: Remoteness) -> Result<()> { + let size = util::min_ubits(value); + if size > REMOTENESS_SIZE { + Err(RecordViolation { + name: RecordType::SURCC(self.players).to_string(), + hint: format!( + "This record implementation uses {} bits to store unsigned \ + integers representing remoteness values, but there was an \ + attempt to store a remoteness value of {}, which requires \ + at least {} bits to store.", + REMOTENESS_SIZE, value, size, + ), + })? + } else { + let start = Self::remoteness_index(self.players); + let end = start + REMOTENESS_SIZE; + self.buf[start..end].store_be(value); + Ok(()) + } + } + + /// Set this entry to have `value` child count. Fails if `value` is too high + /// to fit in the space dedicated for child count within the record. + #[inline(always)] + pub fn set_child_count(&mut self, value: ChildCount) -> Result<()> { + let size = util::min_ubits(value); + if size > CHILD_COUNT_SIZE { + Err(RecordViolation { + name: RecordType::SURCC(self.players).to_string(), + hint: format!( + "This record implementation uses {} bits to store unsigned \ + integers representing child count values, but there was an \ + attempt to store a child count value of {}, which requires \ + at least {} bits to store.", + CHILD_COUNT_SIZE, value, size, + ), + })? + } else { + let start = Self::child_count_index(self.players); + let end = start + CHILD_COUNT_SIZE; + self.buf[start..end].store_be(value); + Ok(()) + } + } + + /* LAYOUT HELPER METHODS */ + + /// Return the number of bits that would be needed to store a record + /// containing utility information for `players` as well as remoteness. + #[inline(always)] + const fn bit_size(players: usize) -> usize { + (players * UTILITY_SIZE) + REMOTENESS_SIZE + CHILD_COUNT_SIZE + } + + /// Return the minimum number of bits needed for a valid record buffer. + #[inline(always)] + const fn minimum_bit_size() -> usize { + REMOTENESS_SIZE + CHILD_COUNT_SIZE + } + + /// Return the bit index of the remoteness entry start in the record buffer. + #[inline(always)] + const fn remoteness_index(players: usize) -> usize { + players * UTILITY_SIZE + } + + /// Return the bit index of the 'i'th player's utility entry start. + #[inline(always)] + const fn utility_index(player: Player) -> usize { + player * UTILITY_SIZE + } + + /// Return the bit index of the child count entry + #[inline(always)] + const fn child_count_index(players: usize) -> usize { + players * UTILITY_SIZE + REMOTENESS_SIZE + } + + /// Return the maximum number of utility entries supported by a dense record + /// (one that maximizes bit usage) with `length`. Ignores unused bits. + #[inline(always)] + const fn player_count(length: usize) -> usize { + (length - REMOTENESS_SIZE - CHILD_COUNT_SIZE) / UTILITY_SIZE + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + // TODO +} diff --git a/src/solver/util.rs b/src/solver/util.rs index 3d21534..611d21c 100644 --- a/src/solver/util.rs +++ b/src/solver/util.rs @@ -28,7 +28,16 @@ impl Display for RecordType { "Simple Utility Remoteness ({players} players)", ) }, - RecordType::REM => write!(f, "Remoteness (no utility)"), + RecordType::SURCC(players) => { + write!( + f, + "Simple Utility Remoteness with Child Count ({} players)", + players + ) + }, + RecordType::REM => { + write!(f, "Remoteness Only") + }, } } } @@ -38,6 +47,7 @@ impl TryInto for RecordType { fn try_into(self) -> Result { match self { + RecordType::SURCC(players) => record::surcc::schema(players), RecordType::MUR(players) => record::mur::schema(players), RecordType::SUR(players) => record::sur::schema(players), RecordType::REM => record::rem::schema(),