From 74c0180e1cf955207fba7930dea66c94150bcebf Mon Sep 17 00:00:00 2001 From: ShenMian Date: Sat, 20 Jan 2024 18:41:35 +0800 Subject: [PATCH] feat(solver): add freeze deadlock detection --- src/solver/state.rs | 79 +++++++++++++++++++++++++++++++++------ src/systems/auto_solve.rs | 3 +- src/test.rs | 4 +- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/solver/state.rs b/src/solver/state.rs index 7133152..b3cc190 100644 --- a/src/solver/state.rs +++ b/src/solver/state.rs @@ -101,23 +101,20 @@ impl State { continue; } - // TODO: 检测是否会产生新的死锁 if self.can_block_crate(&new_crate_position, solver) { continue; } let mut new_movements = self.movements.clone(); - if let Some(path) = - find_path(&self.player_position, &next_player_position, |position| { - self.can_block_player(position, solver) - }) - { - new_movements.extend( - path.windows(2) - .map(|p| Direction::from_vector(p[1] - p[0]).unwrap()) - .map(Movement::with_move), - ) - } + let path = find_path(&self.player_position, &next_player_position, |position| { + self.can_block_player(position, solver) + }) + .unwrap(); + new_movements.extend( + path.windows(2) + .map(|p| Direction::from_vector(p[1] - p[0]).unwrap()) + .map(Movement::with_move), + ); new_movements.push(Movement::with_push(push_direction)); // skip tunnels @@ -138,6 +135,18 @@ impl State { new_crate_positions.remove(crate_position); new_crate_positions.insert(new_crate_position); + // skip deadlocks + if !solver.level.target_positions.contains(&new_crate_position) + && self.is_freeze_deadlock( + &new_crate_position, + &new_crate_positions, + solver, + &mut HashSet::new(), + ) + { + continue; + } + let new_player_position = new_crate_position - push_direction.to_vector(); let new_state = State::new( @@ -174,6 +183,52 @@ impl State { instance } + fn is_freeze_deadlock( + &self, + crate_position: &Vector2, + crate_positions: &HashSet>, + solver: &Solver, + visited: &mut HashSet>, + ) -> bool { + if !visited.insert(*crate_position) { + return true; + } + + for direction in [ + Direction::Up, + Direction::Down, + Direction::Left, + Direction::Right, + ] + .chunks(2) + { + let neighbors = [ + crate_position + direction[0].to_vector(), + crate_position + direction[1].to_vector(), + ]; + if solver + .level + .get_unchecked(&neighbors[0]) + .intersects(Tile::Wall) + || solver + .level + .get_unchecked(&neighbors[1]) + .intersects(Tile::Wall) + { + continue; + } + if (crate_positions.contains(&neighbors[0]) + && self.is_freeze_deadlock(&neighbors[0], crate_positions, solver, visited)) + || (crate_positions.contains(&neighbors[1]) + && self.is_freeze_deadlock(&neighbors[1], crate_positions, solver, visited)) + { + continue; + } + return false; + } + return true; + } + /// Minimum number of pushes required to complete the level. fn lower_bound(&self, solver: &Solver) -> usize { *self diff --git a/src/systems/auto_solve.rs b/src/systems/auto_solve.rs index eb3c300..668040d 100644 --- a/src/systems/auto_solve.rs +++ b/src/systems/auto_solve.rs @@ -12,8 +12,9 @@ pub fn spawn_lowerbound_marks( mut board: Query<&mut Board>, ) { let Board { board, tile_size } = &mut *board.single_mut(); + let solver = solver_state.solver.lock().unwrap(); - let lowerbounds = solver_state.solver.lock().unwrap().lower_bounds().clone(); + let lowerbounds = solver.lower_bounds().clone(); let max_lowerbound = lowerbounds .iter() .map(|(_, lowerbound)| *lowerbound) diff --git a/src/test.rs b/src/test.rs index 4858834..1fcf900 100644 --- a/src/test.rs +++ b/src/test.rs @@ -22,8 +22,8 @@ mod tests { let levels = Level::load_from_file(Path::new("assets/levels/box_world.xsb")).unwrap(); let mut failed = 0; for i in [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 19, 21, 24, 28, 29, 31, 34, - 36, 40, 43, 61, 62, 64, 65, 69, 74, 76, 90, 91, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 21, 24, 28, 30, 31, + 34, 36, 40, 43, 61, 62, 64, 65, 69, 74, 76, 90, 91, ] { let level = levels[i].clone(); let mut solver = Solver::new(level.clone());