From 9bbfb4c5fb5c15ea4c64ece59eb79178f9ab3e7c Mon Sep 17 00:00:00 2001 From: ShenMian Date: Wed, 24 Jan 2024 14:12:42 +0800 Subject: [PATCH] refactor: refactor some code --- src/board.rs | 9 +++++++++ src/database.rs | 22 ++++++++++++++-------- src/level.rs | 5 +++++ src/solver/solver.rs | 18 +++++++++++------- src/solver/state.rs | 42 +++++++++++++++++++++++++----------------- 5 files changed, 64 insertions(+), 32 deletions(-) diff --git a/src/board.rs b/src/board.rs index 287d3b3..7791d93 100644 --- a/src/board.rs +++ b/src/board.rs @@ -13,6 +13,7 @@ pub struct Board { } impl Board { + /// Creates a new board with the specified level. pub fn with_level(level: Level) -> Self { Self { level, @@ -21,6 +22,7 @@ impl Board { } } + /// Checks if the player can move or push in the specified direction. pub fn moveable(&self, direction: Direction) -> bool { let player_next_position = self.level.player_position + direction.to_vector(); if self @@ -47,6 +49,7 @@ impl Board { return true; } + /// Moves the player or pushes a crate in the specified direction. pub fn move_or_push(&mut self, direction: Direction) { let direction_vector = direction.to_vector(); let player_next_position = self.level.player_position + direction_vector; @@ -80,6 +83,7 @@ impl Board { self.undone_movements.clear(); } + /// Undoes the last push. pub fn undo_push(&mut self) { while let Some(history) = self.movements.last() { if history.is_push { @@ -90,6 +94,7 @@ impl Board { } } + /// Undoes the last move. pub fn undo_move(&mut self) { debug_assert!(!self.movements.is_empty()); let history = self.movements.pop().unwrap(); @@ -103,6 +108,7 @@ impl Board { self.undone_movements.push(history); } + /// Redoes the last push. pub fn redo_push(&mut self) { while let Some(history) = self.undone_movements.last() { if history.is_push { @@ -113,6 +119,7 @@ impl Board { } } + /// Redoes the last move. pub fn redo_move(&mut self) { debug_assert!(!self.undone_movements.is_empty()); let history = self.undone_movements.pop().unwrap(); @@ -121,10 +128,12 @@ impl Board { self.undone_movements = undone_movements; } + /// Checks if the level is solved. pub fn is_solved(&self) -> bool { self.level.crate_positions == self.level.target_positions } + /// Returns the player's current orientation. pub fn player_orientation(&self) -> Direction { self.movements .last() diff --git a/src/database.rs b/src/database.rs index 82e5ee1..14557be 100644 --- a/src/database.rs +++ b/src/database.rs @@ -14,16 +14,18 @@ pub struct Database { } impl Database { - #[allow(dead_code)] - pub fn from_memory() -> Self { + /// Creates a new Database instance with a connection to a file-based database. + pub fn from_file>(path: P) -> Self { Self { - connection: Connection::open_in_memory().expect("failed to open database"), + connection: Connection::open(path).expect("failed to open database"), } } - pub fn from_file>(path: P) -> Self { + /// Creates a new Database instance with an in-memory connection. + #[allow(dead_code)] + pub fn from_memory() -> Self { Self { - connection: Connection::open(path).expect("failed to open database"), + connection: Connection::open_in_memory().expect("failed to open database"), } } @@ -86,6 +88,7 @@ impl Database { ); } + /// Returns the level ID by the provided level. pub fn get_level_id(&self, level: &Level) -> Option { let hash = Database::normalized_hash(level); match self.connection.query_row( @@ -98,6 +101,7 @@ impl Database { } } + /// Returns a level based by ID. pub fn get_level_by_id(&self, id: u64) -> Option { let mut statement = self .connection @@ -124,6 +128,7 @@ impl Database { Some(level) } + /// Returns the ID of the next unsolved level after the provided ID. pub fn next_unsolved_level_id(&self, id: u64) -> Option { let mut statement = self.connection.prepare( "SELECT id FROM tb_level WHERE id > ? AND id NOT IN (SELECT level_id FROM tb_solution) ORDER BY id ASC LIMIT 1", @@ -205,27 +210,28 @@ impl Database { Some(best_push) } - /// Retrieves the maximum level ID. + /// Returns the maximum level ID. pub fn max_level_id(&self) -> Option { self.connection .query_row("SELECT MAX(id) FROM tb_level", [], |row| row.get(0)) .unwrap() } - /// Retrieves the minimum level ID. + /// Returns the minimum level ID. pub fn min_level_id(&self) -> Option { self.connection .query_row("SELECT MIN(id) FROM tb_level", [], |row| row.get(0)) .unwrap() } + /// Computes a normalized hash for the provided level. fn normalized_hash(level: &Level) -> String { let mut hasher = SipHasher24::new(); let mut normalized_level = level.clone(); normalized_level.normalize(); normalized_level.hash(&mut hasher); let hash = hasher.finish(); - // 必须先将 hash 转为字符串, 否则 rusqlite 可能报错 + // Must convert the hash to a string first, otherwise rusqlite may throw an error. hash.to_string() } } diff --git a/src/level.rs b/src/level.rs index a062cc6..45b0543 100644 --- a/src/level.rs +++ b/src/level.rs @@ -71,6 +71,7 @@ impl fmt::Display for ParseMapError { type Result = std::result::Result; impl Level { + /// Creates a new level. pub fn new( map: Vec, dimensions: Vector2, @@ -257,6 +258,7 @@ impl Level { &mut self.data[(position.y * self.dimensions.x + position.x) as usize] } + /// Exports the map layout as a XSB format string. pub fn export_map(&self) -> String { let mut result = String::new(); for y in 0..self.dimensions.y { @@ -283,6 +285,7 @@ impl Level { result } + /// Exports metadata as a XSB format string. pub fn export_metadata(&self) -> String { let mut result = String::new(); for (key, value) in self.metadata.iter() { @@ -291,6 +294,7 @@ impl Level { result } + /// Normalizes the level. pub fn normalize(&mut self) { assert!(self .get_unchecked(&self.player_position) @@ -616,6 +620,7 @@ impl Level { .collect(); } + /// Checks if a position is within the bounds of the level. pub fn in_bounds(&self, position: &Vector2) -> bool { 0 <= position.x && position.x < self.dimensions.x diff --git a/src/solver/solver.rs b/src/solver/solver.rs index a24c15e..6465a43 100644 --- a/src/solver/solver.rs +++ b/src/solver/solver.rs @@ -81,7 +81,7 @@ impl Solver { instance } - /// Search for solution using the A* algorithm. + /// Searches for solution using the A* algorithm. pub fn search(&mut self, timeout: Duration) -> Result { let timer = Instant::now(); self.visited @@ -116,10 +116,12 @@ impl Solver { self.heap.peek() } + /// Returns a reference to the set of tunnels. pub fn tunnels(&self) -> &HashSet<(Vector2, Direction)> { self.tunnels.get_or_init(|| self.calculate_tunnels()) } + /// Calculates and returns the set of tunnels in the level. fn calculate_tunnels(&self) -> HashSet<(Vector2, Direction)> { let mut tunnels = HashSet::new(); for x in 1..self.level.dimensions.x - 1 { @@ -225,11 +227,13 @@ impl Solver { tunnels } + /// Returns a reference to the set of lower bounds. pub fn lower_bounds(&self) -> &HashMap, usize> { self.lower_bounds .get_or_init(|| self.calculate_lower_bounds()) } + /// Calculates and returns the set of lower bounds. fn calculate_lower_bounds(&self) -> HashMap, usize> { match self.lower_bound_method { LowerBoundMethod::MinimumPush => self.minimum_push_lower_bounds(), @@ -238,6 +242,7 @@ impl Solver { } } + /// Calculates and returns the lower bounds using the minimum push method. fn minimum_push_lower_bounds(&self) -> HashMap, usize> { let mut lower_bounds = HashMap::new(); for target_position in &self.level.target_positions { @@ -287,6 +292,7 @@ impl Solver { } } + /// Calculates and returns the lower bounds using the minimum move method. fn minimum_move_lower_bounds(&self) -> HashMap, usize> { let mut lower_bounds = HashMap::new(); for x in 1..self.level.dimensions.x - 1 { @@ -325,6 +331,7 @@ impl Solver { lower_bounds } + /// Calculates and returns the lower bounds using the Manhattan distance method. fn manhattan_distance_lower_bounds(&self) -> HashMap, usize> { let mut lower_bounds = HashMap::new(); for x in 1..self.level.dimensions.x - 1 { @@ -351,24 +358,21 @@ impl Solver { lower_bounds } + /// Shrinks the heap by retaining only a subset of states based on heuristics. #[allow(dead_code)] fn shrink_heap(heap: &mut BinaryHeap) { let max_pressure = 200_000; if heap.len() > max_pressure { let mut heuristics: Vec<_> = heap.iter().map(|state| state.heuristic()).collect(); heuristics.sort_unstable(); - let mut costs: Vec<_> = heap.iter().map(|state| state.move_count()).collect(); - costs.sort_unstable(); let alpha = 0.8; let heuristic_median = heuristics[(heuristics.len() as f32 * alpha) as usize]; - let cost_median = costs[(costs.len() as f32 * alpha) as usize]; - heap.retain(|state| { - state.heuristic() <= heuristic_median && state.move_count() <= cost_median - }); + heap.retain(|state| state.heuristic() <= heuristic_median); } } + /// Prints the lower bounds for each position in the level. #[allow(dead_code)] pub fn print_lower_bounds(&self) { for y in 0..self.level.dimensions.y { diff --git a/src/solver/state.rs b/src/solver/state.rs index d6d43d2..cc5c5b7 100644 --- a/src/solver/state.rs +++ b/src/solver/state.rs @@ -61,20 +61,22 @@ impl State { heuristic: 0, lower_bound: OnceCell::new(), }; - debug_assert!(instance.move_count() < 10000); - debug_assert!(instance.push_count() < 10000); + debug_assert!(instance.movements.move_count() < 10000); + debug_assert!(instance.movements.push_count() < 10000); debug_assert!(instance.lower_bound(solver) < 10000); instance.heuristic = match solver.strategy() { - Strategy::Fast => instance.lower_bound(solver) * 10000 + instance.move_count(), - Strategy::Mixed => instance.lower_bound(solver) + instance.move_count(), + Strategy::Fast => { + instance.lower_bound(solver) * 10000 + instance.movements.move_count() + } + Strategy::Mixed => instance.lower_bound(solver) + instance.movements.move_count(), Strategy::OptimalMovePush => { - instance.move_count() * 10000_0000 - + instance.push_count() * 10000 + instance.movements.move_count() * 10000_0000 + + instance.movements.push_count() * 10000 + instance.lower_bound(solver) } Strategy::OptimalPushMove => { - instance.push_count() * 10000_0000 - + instance.move_count() * 10000 + instance.movements.push_count() * 10000_0000 + + instance.movements.move_count() * 10000 + instance.lower_bound(solver) } }; @@ -166,28 +168,24 @@ impl State { successors } + /// Checks if the current state represents a solved level. pub fn is_solved(&self, solver: &Solver) -> bool { self.lower_bound(solver) == 0 } + /// Returns the heuristic value of the current state. pub fn heuristic(&self) -> usize { self.heuristic } - pub fn move_count(&self) -> usize { - self.movements.len() - } - - pub fn push_count(&self) -> usize { - self.movements.iter().filter(|x| x.is_push).count() - } - + /// Returns a normalized clone of the current state. pub fn normalized(&self, solver: &Solver) -> Self { let mut instance = self.clone(); instance.player_position = self.normalized_player_position(solver); instance } + /// Checks if the new crate position leads to a freeze deadlock. fn is_freeze_deadlock( &self, crate_position: &Vector2, @@ -211,6 +209,8 @@ impl State { crate_position + direction[0].to_vector(), crate_position + direction[1].to_vector(), ]; + + // Checks if any immovable walls on the axis. if solver .level .get_unchecked(&neighbors[0]) @@ -222,6 +222,8 @@ impl State { { continue; } + + // Checks if any immovable crates on the axis. if (crate_positions.contains(&neighbors[0]) && self.is_freeze_deadlock(&neighbors[0], crate_positions, solver, visited)) || (crate_positions.contains(&neighbors[1]) @@ -229,18 +231,20 @@ impl State { { continue; } + return false; } return true; } - /// Minimum number of pushes required to complete the level. + /// Returns the lower bound value for the current state. fn lower_bound(&self, solver: &Solver) -> usize { *self .lower_bound .get_or_init(|| self.calculate_lower_bound(solver)) } + /// Calculates and returns the lower bound value for the current state. fn calculate_lower_bound(&self, solver: &Solver) -> usize { self.crate_positions .iter() @@ -248,11 +252,13 @@ impl State { .sum() } + /// Checks if a position can block the player's movement. fn can_block_player(&self, position: &Vector2, solver: &Solver) -> bool { solver.level.get_unchecked(position).intersects(Tile::Wall) || self.crate_positions.contains(position) } + /// Checks if a position can block a crate's movement. fn can_block_crate(&self, position: &Vector2, solver: &Solver) -> bool { solver .level @@ -262,10 +268,12 @@ impl State { || self.crate_positions.contains(position) } + /// Returns the normalized player position based on reachable area. fn normalized_player_position(&self, solver: &Solver) -> Vector2 { normalized_area(&self.player_reachable_area(solver)) } + /// Returns the reachable area for the player in the current state. fn player_reachable_area(&self, solver: &Solver) -> HashSet> { solver .level