diff --git a/libriichi/benches/bench.rs b/libriichi/benches/bench.rs index f194d01..507e11f 100644 --- a/libriichi/benches/bench.rs +++ b/libriichi/benches/bench.rs @@ -79,15 +79,8 @@ fn sp(c: &mut Criterion) { c.bench_function(&format!("sp {cur_shanten} shanten"), |b| { b.iter(|| { let state = black_box(init_state.clone()); - let mut shanten_cache = Default::default(); let candidates = black_box(&calc) - .calc( - state, - can_discard, - tsumos_left, - cur_shanten, - &mut shanten_cache, - ) + .calc(state, can_discard, tsumos_left, cur_shanten) .unwrap(); black_box(candidates); }); @@ -131,15 +124,8 @@ fn sp(c: &mut Criterion) { c.bench_function(&format!("sp {cur_shanten} shanten"), |b| { b.iter(|| { let state = black_box(init_state.clone()); - let mut shanten_cache = Default::default(); let candidates = black_box(&calc) - .calc( - state, - can_discard, - tsumos_left, - cur_shanten, - &mut shanten_cache, - ) + .calc(state, can_discard, tsumos_left, cur_shanten) .unwrap(); black_box(candidates); }); diff --git a/libriichi/src/algo/sp/calc.rs b/libriichi/src/algo/sp/calc.rs index 7ce2cb8..b903490 100644 --- a/libriichi/src/algo/sp/calc.rs +++ b/libriichi/src/algo/sp/calc.rs @@ -1,7 +1,7 @@ use super::candidate::RawCandidate; use super::state::{InitState, State}; use super::tile::{DiscardTile, DrawTile}; -use super::{Candidate, CandidateColumn, ShantenCache, MAX_TSUMOS_LEFT}; +use super::{Candidate, CandidateColumn, MAX_TSUMOS_LEFT}; use crate::algo::agari::{Agari, AgariCalculator}; use crate::tile::Tile; use crate::{must_tile, t, tu8}; @@ -69,7 +69,6 @@ struct SPCalculatorState<'a, const MAX_TSUMO: usize> { discard_cache: StateCache, draw_cache: StateCache, - shanten_cache: &'a mut ShantenCache, #[cfg(feature = "sp_reproduce_cpp_ver")] real_max_tsumo: usize, @@ -88,7 +87,6 @@ impl SPCalculator<'_> { can_discard: bool, tsumos_left: u8, cur_shanten: i8, - shanten_cache: &mut ShantenCache, ) -> Result> { ensure!(cur_shanten >= 0, "can't calculate an agari hand"); ensure!(tsumos_left >= 1, "need at least one more tsumo"); @@ -118,7 +116,6 @@ impl SPCalculator<'_> { not_tsumo_prob_table: ¬_tsumo_prob_table, discard_cache: Default::default(), draw_cache: Default::default(), - shanten_cache, #[cfg(feature = "sp_reproduce_cpp_ver")] real_max_tsumo: tsumos_left as usize, }; @@ -205,17 +202,15 @@ impl SPCalculatorState<'_, MAX_TSUMO> { fn analyze_discard(&mut self, shanten: i8) -> Vec { // 打牌候補を取得する。 - let discard_tiles = - self.state - .get_discard_tiles(shanten, self.sup.tehai_len_div3, self.shanten_cache); + let discard_tiles = self + .state + .get_discard_tiles(shanten, self.sup.tehai_len_div3); let mut candidates = Vec::with_capacity(discard_tiles.len()); for DiscardTile { tile, shanten_diff } in discard_tiles { if shanten_diff == 0 { self.state.discard(tile); - let required_tiles = self - .state - .get_required_tiles(self.sup.tehai_len_div3, self.shanten_cache); + let required_tiles = self.state.get_required_tiles(self.sup.tehai_len_div3); let values = self.draw(shanten); self.state.undo_discard(tile); @@ -238,9 +233,7 @@ impl SPCalculatorState<'_, MAX_TSUMO> { candidates.push(candidate); } else if self.sup.calc_shanten_down && shanten_diff == 1 && shanten < SHANTEN_THRES { self.state.discard(tile); - let required_tiles = self - .state - .get_required_tiles(self.sup.tehai_len_div3, self.shanten_cache); + let required_tiles = self.state.get_required_tiles(self.sup.tehai_len_div3); self.state.n_extra_tsumo += 1; let values = self.draw(shanten + 1); self.state.n_extra_tsumo -= 1; @@ -263,9 +256,7 @@ impl SPCalculatorState<'_, MAX_TSUMO> { } fn analyze_draw(&mut self, shanten: i8) -> Vec { - let required_tiles = self - .state - .get_required_tiles(self.sup.tehai_len_div3, self.shanten_cache); + let required_tiles = self.state.get_required_tiles(self.sup.tehai_len_div3); let values = self.draw(shanten); let mut tenpai_probs = values.tenpai_probs; @@ -289,16 +280,14 @@ impl SPCalculatorState<'_, MAX_TSUMO> { fn analyze_discard_simple(&mut self, shanten: i8) -> Vec { // 打牌候補を取得する。 - let discard_tiles = - self.state - .get_discard_tiles(shanten, self.sup.tehai_len_div3, self.shanten_cache); + let discard_tiles = self + .state + .get_discard_tiles(shanten, self.sup.tehai_len_div3); discard_tiles .into_iter() .map(|DiscardTile { tile, shanten_diff }| { self.state.discard(tile); - let required_tiles = self - .state - .get_required_tiles(self.sup.tehai_len_div3, self.shanten_cache); + let required_tiles = self.state.get_required_tiles(self.sup.tehai_len_div3); self.state.undo_discard(tile); Candidate::from(RawCandidate { @@ -312,9 +301,7 @@ impl SPCalculatorState<'_, MAX_TSUMO> { } fn analyze_draw_simple(&mut self) -> Vec { - let required_tiles = self - .state - .get_required_tiles(self.sup.tehai_len_div3, self.shanten_cache); + let required_tiles = self.state.get_required_tiles(self.sup.tehai_len_div3); let candidate = Candidate::from(RawCandidate { tile: t!(?), required_tiles, @@ -345,9 +332,7 @@ impl SPCalculatorState<'_, MAX_TSUMO> { let mut exp_values = [0.; MAX_TSUMO]; // 自摸候補を取得する。 - let draw_tiles = - self.state - .get_draw_tiles(shanten, self.sup.tehai_len_div3, self.shanten_cache); + let draw_tiles = self.state.get_draw_tiles(shanten, self.sup.tehai_len_div3); // 有効牌の合計枚数を計算する。【暫定対応】 let sum_left_tiles = self.state.sum_left_tiles(); @@ -472,9 +457,7 @@ impl SPCalculatorState<'_, MAX_TSUMO> { let mut exp_values = [0.; MAX_TSUMO]; // 自摸候補を取得する。 - let draw_tiles = - self.state - .get_draw_tiles(shanten, self.sup.tehai_len_div3, self.shanten_cache); + let draw_tiles = self.state.get_draw_tiles(shanten, self.sup.tehai_len_div3); // 有効牌の合計枚数を計算する。 let sum_required_tiles: u8 = draw_tiles @@ -586,9 +569,9 @@ impl SPCalculatorState<'_, MAX_TSUMO> { fn discard_slow(&mut self, shanten: i8) -> Rc> { // 打牌候補を取得する。 - let discard_tiles = - self.state - .get_discard_tiles(shanten, self.sup.tehai_len_div3, self.shanten_cache); + let discard_tiles = self + .state + .get_discard_tiles(shanten, self.sup.tehai_len_div3); // 期待値が最大となる打牌を選択する。 let mut max_tenpai_probs = [f32::MIN; MAX_TSUMO]; @@ -788,8 +771,6 @@ mod test { #[test] fn nanikiru() { - let mut shanten_cache = Default::default(); - let mut calc = SPCalculator { tehai_len_div3: 4, chis: &[], @@ -825,13 +806,7 @@ mod test { let tsumos_left = 8; let cur_shanten = CALC_SHANTEN_FN(&tehai, calc.tehai_len_div3); let candidates = calc - .calc( - state, - can_discard, - tsumos_left, - cur_shanten, - &mut shanten_cache, - ) + .calc(state, can_discard, tsumos_left, cur_shanten) .unwrap(); assert_eq!(candidates[0].tile, t!(N)); assert_eq!(candidates[1].tile, t!(W)); @@ -853,28 +828,15 @@ mod test { let can_discard = true; let tsumos_left = 15; let cur_shanten = CALC_SHANTEN_FN(&tehai, calc.tehai_len_div3); - let mut shanten_cache = Default::default(); let candidates = calc - .calc( - state.clone(), - can_discard, - tsumos_left, - cur_shanten, - &mut shanten_cache, - ) + .calc(state.clone(), can_discard, tsumos_left, cur_shanten) .unwrap(); assert_eq!(candidates[0].tile, t!(9p)); assert!(candidates[0].shanten_down); calc.maximize_win_prob = true; let candidates = calc - .calc( - state, - can_discard, - tsumos_left, - cur_shanten, - &mut shanten_cache, - ) + .calc(state, can_discard, tsumos_left, cur_shanten) .unwrap(); assert_eq!(candidates[0].tile, t!(3m)); assert!(!candidates[0].shanten_down); @@ -915,15 +877,8 @@ mod test { let can_discard = true; let tsumos_left = 15; let cur_shanten = CALC_SHANTEN_FN(&tehai, calc.tehai_len_div3); - let mut shanten_cache = Default::default(); let candidates = calc - .calc( - state, - can_discard, - tsumos_left, - cur_shanten, - &mut shanten_cache, - ) + .calc(state, can_discard, tsumos_left, cur_shanten) .unwrap(); let c = if cfg!(feature = "sp_reproduce_cpp_ver") { &candidates[2] @@ -980,13 +935,7 @@ mod test { let can_discard = true; let tsumos_left = 5; let candidates = calc - .calc( - state, - can_discard, - tsumos_left, - cur_shanten, - &mut shanten_cache, - ) + .calc(state, can_discard, tsumos_left, cur_shanten) .unwrap(); assert_eq!(candidates.len(), 7); @@ -1002,8 +951,6 @@ mod test { #[test] fn tsumo_only() { - let mut shanten_cache = Default::default(); - let calc = SPCalculator { tehai_len_div3: 4, chis: &[], @@ -1041,13 +988,7 @@ mod test { let can_discard = false; let tsumos_left = 5; let candidates = calc - .calc( - state, - can_discard, - tsumos_left, - cur_shanten, - &mut shanten_cache, - ) + .calc(state, can_discard, tsumos_left, cur_shanten) .unwrap(); assert_eq!(candidates.len(), 1); let c = &candidates[0]; diff --git a/libriichi/src/algo/sp/mod.rs b/libriichi/src/algo/sp/mod.rs index d76d387..4c57db0 100644 --- a/libriichi/src/algo/sp/mod.rs +++ b/libriichi/src/algo/sp/mod.rs @@ -29,13 +29,11 @@ mod calc; mod candidate; -mod shanten_cache; mod state; mod tile; pub use calc::SPCalculator; pub use candidate::{Candidate, CandidateColumn}; -pub use shanten_cache::ShantenCache; pub use state::InitState; pub use tile::RequiredTile; diff --git a/libriichi/src/algo/sp/shanten_cache.rs b/libriichi/src/algo/sp/shanten_cache.rs deleted file mode 100644 index b152f75..0000000 --- a/libriichi/src/algo/sp/shanten_cache.rs +++ /dev/null @@ -1,25 +0,0 @@ -use ahash::AHashMap; - -/// Just to mention here, you can't reuse the cache for `Values` like this, -/// because `Values` depends on `tiles_seen` which changes rapidly in a game, -/// but shanten is only determined by the tehai. And very often, the shanten -/// cache will have a high hit rate throughout a kyoku since all hands are -/// either slightly derived from or the same as its previous one. -#[derive(Default)] -pub struct ShantenCache { - pub at_3n1: AHashMap<[u8; 34], i8>, - pub at_3n2: AHashMap<[u8; 34], i8>, -} - -impl ShantenCache { - #[inline] - pub fn new() -> Self { - Default::default() - } - - #[inline] - pub fn clear(&mut self) { - self.at_3n1.clear(); - self.at_3n2.clear(); - } -} diff --git a/libriichi/src/algo/sp/state.rs b/libriichi/src/algo/sp/state.rs index 13ecde3..f10f55a 100644 --- a/libriichi/src/algo/sp/state.rs +++ b/libriichi/src/algo/sp/state.rs @@ -1,4 +1,3 @@ -use super::shanten_cache::ShantenCache; use super::tile::{DiscardTile, DrawTile, RequiredTile}; use super::CALC_SHANTEN_FN; use crate::tile::Tile; @@ -101,7 +100,6 @@ impl State { &self, shanten: i8, tehai_len_div3: u8, - shanten_cache: &mut ShantenCache, ) -> ArrayVec<[DiscardTile; 14]> { let mut discard_tiles = ArrayVec::default(); @@ -112,10 +110,7 @@ impl State { } tehai[tid] -= 1; - let shanten_after = *shanten_cache - .at_3n1 - .entry(tehai) - .or_insert_with_key(|tehai| CALC_SHANTEN_FN(tehai, tehai_len_div3)); + let shanten_after = CALC_SHANTEN_FN(&tehai, tehai_len_div3); tehai[tid] += 1; let shanten_diff = shanten_after - shanten; @@ -137,7 +132,6 @@ impl State { &self, shanten: i8, tehai_len_div3: u8, - shanten_cache: &mut ShantenCache, ) -> ArrayVec<[DrawTile; 37]> { let mut draw_tiles = ArrayVec::default(); @@ -148,10 +142,7 @@ impl State { } tehai[tid] += 1; - let shanten_after = *shanten_cache - .at_3n2 - .entry(tehai) - .or_insert_with_key(|tehai| CALC_SHANTEN_FN(tehai, tehai_len_div3)); + let shanten_after = CALC_SHANTEN_FN(&tehai, tehai_len_div3); tehai[tid] -= 1; let shanten_diff = shanten_after - shanten; @@ -183,17 +174,10 @@ impl State { draw_tiles } - pub(super) fn get_required_tiles( - &self, - tehai_len_div3: u8, - shanten_cache: &mut ShantenCache, - ) -> ArrayVec<[RequiredTile; 34]> { + pub(super) fn get_required_tiles(&self, tehai_len_div3: u8) -> ArrayVec<[RequiredTile; 34]> { let mut tehai = self.tehai; - let shanten = *shanten_cache - .at_3n1 - .entry(tehai) - .or_insert_with_key(|tehai| CALC_SHANTEN_FN(tehai, tehai_len_div3)); + let shanten = CALC_SHANTEN_FN(&tehai, tehai_len_div3); let mut required_tiles = ArrayVec::default(); for (tid, &count) in self.tiles_in_wall.iter().enumerate() { @@ -202,10 +186,7 @@ impl State { } tehai[tid] += 1; - let shanten_after = *shanten_cache - .at_3n2 - .entry(tehai) - .or_insert_with_key(|tehai| CALC_SHANTEN_FN(tehai, tehai_len_div3)); + let shanten_after = CALC_SHANTEN_FN(&tehai, tehai_len_div3); tehai[tid] -= 1; if shanten_after < shanten { diff --git a/libriichi/src/state/agent_helper.rs b/libriichi/src/state/agent_helper.rs index a296fd5..001cb24 100644 --- a/libriichi/src/state/agent_helper.rs +++ b/libriichi/src/state/agent_helper.rs @@ -584,13 +584,7 @@ impl PlayerState { calc_shanten_down: false, }; - let mut max_ev_table = sp_calc.calc( - init_state, - can_discard, - tsumos_left, - cur_shanten, - &mut self.shanten_cache.lock(), - )?; + let mut max_ev_table = sp_calc.calc(init_state, can_discard, tsumos_left, cur_shanten)?; if is_discard_after_riichi { max_ev_table[0].tile = self.last_self_tsumo.unwrap(); } diff --git a/libriichi/src/state/player_state.rs b/libriichi/src/state/player_state.rs index 625272c..68efd51 100644 --- a/libriichi/src/state/player_state.rs +++ b/libriichi/src/state/player_state.rs @@ -1,15 +1,13 @@ use super::action::ActionCandidate; use super::item::{ChiPon, KawaItem, Sutehai}; -use crate::algo::sp::{Candidate, ShantenCache}; +use crate::algo::sp::Candidate; use crate::hand::tiles_to_string; use crate::must_tile; use crate::tile::Tile; use std::iter; -use std::sync::Arc; use anyhow::Result; use derivative::Derivative; -use parking_lot::Mutex; use pyo3::prelude::*; use serde_json as json; use tinyvec::{ArrayVec, TinyVec}; @@ -139,15 +137,6 @@ pub struct PlayerState { /// Used in can_riichi, also in single-player features to get the shanten /// for 3n+2. pub(super) has_next_shanten_discard: bool, - - /// Despite of its name, it is for single-player calculations only. - /// - /// The cache will be cleared at a StartKyoku event and also every time - /// `tehai_len_div3` changes. - /// - /// Arc Mutex is used here because we want interior mutability and also make - /// it trivial to Clone. - pub(super) shanten_cache: Arc>, } #[pymethods] diff --git a/libriichi/src/state/update.rs b/libriichi/src/state/update.rs index 1bbfc9f..ab2ee3e 100644 --- a/libriichi/src/state/update.rs +++ b/libriichi/src/state/update.rs @@ -110,7 +110,6 @@ impl PlayerState { self.kans_on_board = 0; self.tehai_len_div3 = 4; - self.shanten_cache.lock().clear(); self.has_next_shanten_discard = false; self.tiles_left = 70; self.at_turn = 0; @@ -381,7 +380,6 @@ impl PlayerState { self.last_cans.can_discard = true; self.is_menzen = false; self.tehai_len_div3 -= 1; - self.shanten_cache.lock().clear(); // Marked explicitly as `None` to let `Agent` impls set // `tsumogiri` to false in the Dahai after Chi self.last_self_tsumo = None; @@ -454,7 +452,6 @@ impl PlayerState { self.last_cans.can_discard = true; self.is_menzen = false; self.tehai_len_div3 -= 1; - self.shanten_cache.lock().clear(); // Marked explicitly as `None` to let `Agent` impls set // `tsumogiri` to false in the Dahai after Pon self.last_self_tsumo = None; @@ -503,7 +500,6 @@ impl PlayerState { self.at_rinshan = true; self.is_menzen = false; self.tehai_len_div3 -= 1; - self.shanten_cache.lock().clear(); self.update_doras_owned(0, pai); consumed @@ -583,7 +579,6 @@ impl PlayerState { self.at_rinshan = true; self.tehai_len_div3 -= 1; - self.shanten_cache.lock().clear(); consumed .into_iter() .for_each(|t| self.move_tile(t, MoveType::FuuroConsume));