//! Transposition table for deduplicating identical states. // trans rights are human rights :D use mino::srs::{PieceType, Queue}; use mino::Mat; use crate::bot::node::{Node, RawNodePtr}; use crate::bot::raw::RawMatPtr; use crate::HashMap; pub struct TransTable { lookup: HashMap>, } impl TransTable { /// Constructs a new empty transposition table. pub fn new() -> Self { Self { lookup: HashMap::with_capacity(128), } } /// Looks up a node in the transposition table matching the given state. If one is /// found, returns `Err(existing_node_ptr)`. If not found, a node is created and /// inserted by calling the given closure, and returns `Ok(newly_inserted_node)`. pub fn get_or_insert<'a>( &mut self, matrix: &Mat, queue: Queue<'_>, mut alloc: impl FnMut() -> &'a Node, ) -> Result<&'a Node, RawNodePtr> { // two-phase lookup: first key off queue info, then matrix info let map = self .lookup .entry(QueueKey::new(queue)) .or_insert_with(HashMap::new); if let Some(&node_ptr) = map.get(&matrix) { return Err(node_ptr); } let node = alloc(); debug_assert_eq!(*node.matrix(), *matrix); debug_assert_eq!(node.queue(), queue); // IMPORTANT: it would be invalid to insert `matrix.into()` since we don't have // guaruntees about its lifetime. we have to use a pointer to the matrix in the // newly allocated `node`. map.insert(node.matrix().into(), node.into()); Ok(node) } } // Rather than storing the actual next pieces of each queue, only store the number of // next pieces. This is because the suffixes will always be the same if they have the // same number of pieces, so its more efficient to only store & compare the length. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] struct QueueKey(u16, Option); impl QueueKey { fn new(queue: Queue<'_>) -> Self { Self(queue.next.len() as u16, queue.hold) } }