diff --git a/examples/file_io.rs b/examples/file_io.rs index e483de1..a265444 100644 --- a/examples/file_io.rs +++ b/examples/file_io.rs @@ -71,6 +71,36 @@ fn main() { // game tree constructed from this file cannot access information after the river deal save_data_to_file(&game2, "memo string", "filename.bin", None).unwrap(); + println!("Reloading and Resolving..."); + let mut game3 = PostFlopGame::hacky_reload_and_resolve(&game2, 100, 0.01, true).unwrap(); + println!("game2[0]: {}", game2.strategy()[0]); + println!("game3[0]: {}", game3.strategy()[0]); + for (i, (a, b)) in game2.strategy().iter().zip(game3.strategy()).enumerate() { + if (a - b).abs() > 0.001 { + println!("{i}: Oh no"); + } + } + + // game3.back_to_root(); + // game3.cache_normalized_weights(); + // for (a, b) in game2.equity(0).iter().zip(game3.equity(0)) { + // if (a - b).abs() > 0.001 { + // println!("Oh no"); + // } + // } + // game2.back_to_root(); + // game2.cache_normalized_weights(); + + // for (a, b) in game2 + // .expected_values(0) + // .iter() + // .zip(game3.expected_values(0)) + // { + // if (a - b).abs() > 0.001 { + // println!("{} {} {}", a, b, (a - b).abs()); + // } + // } + // delete the file std::fs::remove_file("filename.bin").unwrap(); } diff --git a/examples/node_locking.rs b/examples/node_locking.rs index 74ff464..143ecb9 100644 --- a/examples/node_locking.rs +++ b/examples/node_locking.rs @@ -27,7 +27,7 @@ fn normal_node_locking() { // node locking must be performed after allocating memory and before solving game.play(1); // OOP all-in - game.lock_current_strategy(&[0.25, 0.75]); // lock IP's strategy: 25% fold, 75% call + game.lock_current_node(&[0.25, 0.75]); // lock IP's strategy: 25% fold, 75% call game.back_to_root(); solve(&mut game, 1000, 0.001, false); @@ -42,7 +42,7 @@ fn normal_node_locking() { game.allocate_memory(false); game.play(1); - game.lock_current_strategy(&[0.5, 0.5]); // lock IP's strategy: 50% fold, 50% call + game.lock_current_node(&[0.5, 0.5]); // lock IP's strategy: 50% fold, 50% call game.back_to_root(); solve(&mut game, 1000, 0.001, false); @@ -77,7 +77,7 @@ fn partial_node_locking() { game.allocate_memory(false); // lock OOP's strategy: only JJ is locked and the rest is not - game.lock_current_strategy(&[0.8, 0.0, 0.0, 0.2, 0.0, 0.0]); // JJ: 80% check, 20% all-in + game.lock_current_node(&[0.8, 0.0, 0.0, 0.2, 0.0, 0.0]); // JJ: 80% check, 20% all-in solve(&mut game, 1000, 0.001, false); game.cache_normalized_weights(); diff --git a/src/file.rs b/src/file.rs index 9380256..2497bef 100644 --- a/src/file.rs +++ b/src/file.rs @@ -329,6 +329,40 @@ mod tests { assert!((root_ev_ip - 15.0).abs() < 1e-4); } + #[test] + fn hacky_reload_and_resolve() { + let card_config = CardConfig { + range: [Range::ones(); 2], + flop: flop_from_str("Td9d6h").unwrap(), + ..Default::default() + }; + + let tree_config = TreeConfig { + starting_pot: 60, + effective_stack: 970, + flop_bet_sizes: [("50%", "").try_into().unwrap(), Default::default()], + turn_bet_sizes: [("50%", "").try_into().unwrap(), Default::default()], + ..Default::default() + }; + + let action_tree = ActionTree::new(tree_config).unwrap(); + let mut game = PostFlopGame::with_config(card_config, action_tree).unwrap(); + + game.allocate_memory(false); + + crate::solve(&mut game, 10, 0.01, false); + + // save (turn) + game.set_target_storage_mode(BoardState::Turn).unwrap(); + save_data_to_file(&game, "", "tmpfile.flop", None).unwrap(); + + // load (turn) + let mut turn_game: PostFlopGame = load_data_from_file("tmpfile.flop", None).unwrap().0; + + let mut reloaded = + PostFlopGame::hacky_reload_and_resolve(&turn_game, 10, 0.01, false).unwrap(); + } + #[test] #[cfg(feature = "zstd")] fn save_and_load_file_compressed() { diff --git a/src/game/base.rs b/src/game/base.rs index bab4b6b..3aea07f 100644 --- a/src/game/base.rs +++ b/src/game/base.rs @@ -810,6 +810,60 @@ impl PostFlopGame { info.num_storage_ip += node.num_elements_ip as u64; } + fn reload_and_resolve(game: &mut PostFlopGame) { + todo!("Not Implemented!") + } + + pub fn hacky_reload_and_resolve( + game: &PostFlopGame, + max_iterations: u32, + target_exploitability: f32, + print_progress: bool, + ) -> Result { + println!("Start hacky_reload_and_resolve"); + let card_config = game.card_config.clone(); + let action_tree = ActionTree::new(game.tree_config.clone())?; + print!("Building new game..."); + let mut new_game = PostFlopGame::with_config(card_config, action_tree)?; + println!("Done!"); + + print!("Allocating memory..."); + new_game.allocate_memory(game.is_compression_enabled()); + println!("Done!"); + + // Copy data into new game + print!("Copying memory..."); + for (dst, src) in new_game.storage1.iter_mut().zip(&game.storage1) { + *dst = *src; + } + for (dst, src) in new_game.storage2.iter_mut().zip(&game.storage2) { + *dst = *src; + } + for (dst, src) in new_game.storage_chance.iter_mut().zip(&game.storage_chance) { + *dst = *src; + } + for (dst, src) in new_game.storage_ip.iter_mut().zip(&game.storage_ip) { + *dst = *src; + } + // Nodelock and resolve + // for node_index in 0..game.node_arena.len() { + // let _ = new_game.lock_node_at_index(node_index); + // } + + let s1 = game.strategy(); + let s2 = new_game.strategy(); + println!("game2[0]: {}", s1[0]); + println!("game3[0]: {}", s2[0]); + // crate::solve( + // &mut new_game, + // max_iterations, + // target_exploitability, + // print_progress, + // ); + + Ok(new_game) + } + /// Sets the bunching effect. fn set_bunching_effect_internal(&mut self, bunching_data: &BunchingData) -> Result<(), String> { self.bunching_num_dead_cards = bunching_data.fold_ranges().len() * 2; diff --git a/src/game/interpreter.rs b/src/game/interpreter.rs index 7a9c8f5..02c22af 100644 --- a/src/game/interpreter.rs +++ b/src/game/interpreter.rs @@ -852,6 +852,40 @@ impl PostFlopGame { self.total_bet_amount } + /// Locks the strategy of the current node to the current strategy already in memory. + pub fn lock_current_strategy(&mut self) { + if self.is_terminal_node() { + panic!("Terminal node is not allowed"); + } + + if self.is_chance_node() { + panic!("Chance node is not allowed"); + } + + let mut node = self.node(); + + node.is_locked = true; + let index = self.node_index(&node); + self.locking_strategy + .insert(index, node.strategy().to_vec()); + } + + pub fn lock_node_at_index(&mut self, index: usize) -> Result<(), String> { + let mut node = self.node_arena[index].lock(); + if node.is_terminal() { + return Err("Cannot lock terminal node".to_string()); + } + + if node.is_chance() { + return Err("Cannot lock chance node".to_string()); + } + + node.is_locked = true; + self.locking_strategy + .insert(index, node.strategy().to_vec()); + Ok(()) + } + /// Locks the strategy of the current node. /// /// The `strategy` argument must be a slice of the length of `#(actions) * #(private hands)`. @@ -867,7 +901,7 @@ impl PostFlopGame { /// This method must be called after allocating memory and before solving the game. /// Panics if the memory is not yet allocated or the game is already solved. /// Also, panics if the current node is a terminal node or a chance node. - pub fn lock_current_strategy(&mut self, strategy: &[f32]) { + pub fn lock_current_node(&mut self, strategy: &[f32]) { if self.state < State::MemoryAllocated { panic!("Memory is not allocated"); } diff --git a/src/game/tests.rs b/src/game/tests.rs index c00c947..527cd42 100644 --- a/src/game/tests.rs +++ b/src/game/tests.rs @@ -869,7 +869,7 @@ fn node_locking() { game.allocate_memory(false); game.play(1); // all-in - game.lock_current_strategy(&[0.25, 0.75]); // 25% fold, 75% call + game.lock_current_node(&[0.25, 0.75]); // 25% fold, 75% call game.back_to_root(); solve(&mut game, 1000, 0.0, false); @@ -889,7 +889,7 @@ fn node_locking() { game.allocate_memory(false); game.play(1); // all-in - game.lock_current_strategy(&[0.5, 0.5]); // 50% fold, 50% call + game.lock_current_node(&[0.5, 0.5]); // 50% fold, 50% call game.back_to_root(); solve(&mut game, 1000, 0.0, false); @@ -929,7 +929,7 @@ fn node_locking_partial() { let mut game = PostFlopGame::with_config(card_config, action_tree).unwrap(); game.allocate_memory(false); - game.lock_current_strategy(&[0.8, 0.0, 0.0, 0.2, 0.0, 0.0]); // JJ -> 80% check, 20% all-in + game.lock_current_node(&[0.8, 0.0, 0.0, 0.2, 0.0, 0.0]); // JJ -> 80% check, 20% all-in solve(&mut game, 1000, 0.0, false); game.cache_normalized_weights(); @@ -970,7 +970,7 @@ fn node_locking_isomorphism() { game.allocate_memory(false); game.apply_history(&[0, 0, 15, 0, 0, 14]); // Turn: Spades, River: Hearts - game.lock_current_strategy(&[0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]); // AhKh -> check + game.lock_current_node(&[0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0]); // AhKh -> check finalize(&mut game);