Skip to content

Commit

Permalink
perf(solver): improve lower bound calculation method
Browse files Browse the repository at this point in the history
  • Loading branch information
ShenMian committed Jan 14, 2024
1 parent bde0a86 commit 325a550
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 61 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ siphasher = "1.0"
arboard = "3.3" # 系统剪切板
image = "0.24"
winit = "0.28" # 版本需要与 bevy 中使用的保持一致
itertools = "0.12.0"

[profile.dev.package."*"]
opt-level = 3
29 changes: 18 additions & 11 deletions src/level.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,19 +444,18 @@ impl Level {
reachable
}

pub fn crate_pushable_path(
pub fn crate_pushable_paths_with_crate_positions(
&self,
crate_position: &Vector2<i32>,
initial_crate_positions: &HashSet<Vector2<i32>>,
) -> HashMap<PushState, Vec<Vector2<i32>>> {
debug_assert!(self.crate_positions.contains(crate_position));

let mut path = HashMap::<PushState, Vec<Vector2<i32>>>::new();
let mut paths = HashMap::<PushState, Vec<Vector2<i32>>>::new();
let mut visited = HashSet::new();
let mut queue = VecDeque::new();

let player_reachable_area = self.reachable_area(&self.player_position, |position| {
self.get_unchecked(position).intersects(Tile::Wall)
|| self.crate_positions.contains(position)
|| initial_crate_positions.contains(position)
});
for &push_direction in [
Direction::Up,
Expand All @@ -476,12 +475,12 @@ impl Level {
push_direction,
crate_position: *crate_position,
};
path.insert(new_state.clone(), vec![*crate_position]);
paths.insert(new_state.clone(), vec![*crate_position]);
queue.push_front(new_state);
}

while let Some(state) = queue.pop_back() {
let mut crate_positions = self.crate_positions.clone();
let mut crate_positions = initial_crate_positions.clone();
crate_positions.remove(crate_position);
crate_positions.insert(state.crate_position);

Expand Down Expand Up @@ -525,16 +524,24 @@ impl Level {
continue;
}

let mut new_path = path[&state].clone();
let mut new_path = paths[&state].clone();
new_path.push(new_crate_position);
path.insert(new_state.clone(), new_path);
paths.insert(new_state.clone(), new_path);

queue.push_front(new_state);
}
}

path.retain(|state, _| state.crate_position != *crate_position);
path
paths.retain(|state, _| state.crate_position != *crate_position);
paths
}

pub fn crate_pushable_paths(
&self,
crate_position: &Vector2<i32>,
) -> HashMap<PushState, Vec<Vector2<i32>>> {
debug_assert!(self.crate_positions.contains(crate_position));
self.crate_pushable_paths_with_crate_positions(crate_position, &self.crate_positions)
}

fn set_player_position(&mut self, position: &Vector2<i32>) {
Expand Down
2 changes: 1 addition & 1 deletion src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub enum CrateReachable {
None,
Some {
selected_crate: Vector2<i32>,
path: HashMap<PushState, Vec<Vector2<i32>>>,
paths: HashMap<PushState, Vec<Vector2<i32>>>,
},
}

Expand Down
78 changes: 50 additions & 28 deletions src/solver/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,49 @@ impl Solver {
.get_or_init(|| self.calculate_lower_bounds())
}

fn minimum_push_count_to_nearest_target(&self, position: &Vector2<i32>) -> Option<usize> {
if self.level.target_positions.contains(position) {
return Some(0);
}

let paths = self
.level
.crate_pushable_paths_with_crate_positions(position, &HashSet::new());

paths
.iter()
.filter(|path| self.level.target_positions.contains(&path.0.crate_position))
.map(|path| path.1.len() - 1)
.min()
}

fn shortest_path_length_to_nearest_target(&self, position: &Vector2<i32>) -> usize {
let nearest_target_position = self
.level
.target_positions
.iter()
.min_by_key(|crate_pos| manhattan_distance(crate_pos, &position))
.unwrap();
let movements = find_path(&position, &nearest_target_position, |position| {
self.level.get_unchecked(&position).intersects(Tile::Wall)
})
.unwrap();
movements.len() - 1
}

fn manhattan_distance_to_nearest_target(&self, position: &Vector2<i32>) -> usize {
self.level
.target_positions
.iter()
.map(|crate_pos| manhattan_distance(crate_pos, &position))
.min()
.unwrap() as usize
}

fn calculate_lower_bounds(&self) -> HashMap<Vector2<i32>, usize> {
let mut lower_bounds = HashMap::new();
for x in 1..self.level.dimensions.x - 1 {
for y in 1..self.level.dimensions.y - 1 {
// 到最近目标点的最短路径长度
let position = Vector2::new(x, y);
if !self.level.get_unchecked(&position).intersects(Tile::Floor)
|| self
Expand All @@ -121,33 +159,17 @@ impl Solver {
{
continue;
}
let closest_target_position = self
.level
.target_positions
.iter()
.min_by_key(|crate_pos| manhattan_distance(crate_pos, &position))
.unwrap();
let movements = find_path(&position, &closest_target_position, |position| {
self.level.get_unchecked(&position).intersects(Tile::Wall)
})
.unwrap();
lower_bounds.insert(position, movements.len() - 1);

// 到最近目标点的曼哈顿距离
// let position = Vector2::new(x, y);
// if !self.level.at(&position).intersects(Tile::Floor)
// || self.dead_positions.contains(&position)
// {
// continue;
// }
// let closest_target_distance = self
// .level
// .target_positions
// .iter()
// .map(|crate_pos| manhattan_distance(crate_pos, &position))
// .min()
// .unwrap();
// self.lower_bounds.insert(position, closest_target_distance);
if let Some(lower_bound) = self.minimum_push_count_to_nearest_target(&position) {
lower_bounds.insert(position, lower_bound);
}
// lower_bounds.insert(
// position,
// self.shortest_path_length_to_nearest_target(&position),
// );
// lower_bounds.insert(
// position,
// self.manhattan_distance_to_nearest_target(&position),
// );
}
}
lower_bounds
Expand Down
3 changes: 2 additions & 1 deletion src/solver/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ impl State {
fn calculate_lower_bound(&self, solver: &Solver) -> usize {
self.crate_positions
.iter()
.map(|crate_pos| solver.lower_bounds()[&crate_pos])
.map(|crate_position| solver.lower_bounds()[&crate_position])
.sum()
}

Expand All @@ -209,6 +209,7 @@ impl State {
.level
.get_unchecked(position)
.intersects(Tile::Wall | Tile::Deadlock)
|| !solver.lower_bounds().contains_key(position)
|| self.crate_positions.contains(position)
}

Expand Down
16 changes: 6 additions & 10 deletions src/systems/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,21 +247,17 @@ fn solve_level(board: &mut crate::board::Board, player_movement: &mut ResMut<Pla
}
assert!(verify_board.is_solved());

let lurd = solution
.iter()
.map(|x| Into::<char>::into(x.clone()))
.collect::<String>();
info!(
"Solved ({:?}): {} sec, ",
strategy,
timer.elapsed().as_millis() as f32 / 1000.0
);
info!(
" Moves: {}, pushes: {}",
solution.len(),
lurd.chars().filter(|x| x.is_uppercase()).count()
solution.move_count(),
solution.push_count()
);
info!(" Solution: {}", lurd);
info!(" Solution: {}", solution.lurd());

for movement in &*solution {
player_move_or_push(movement.direction, board, player_movement);
Expand Down Expand Up @@ -414,7 +410,7 @@ pub fn mouse_input(
}
CrateReachable::Some {
selected_crate,
path,
paths,
} => {
let mut crate_paths = Vec::new();
for &push_direction in [
Expand All @@ -425,15 +421,15 @@ pub fn mouse_input(
]
.iter()
{
if path.contains_key(&PushState {
if paths.contains_key(&PushState {
push_direction,
crate_position: grid_position,
}) {
if *selected_crate == grid_position {
*crate_reachable = CrateReachable::None;
return;
}
let crate_path = path[&PushState {
let crate_path = paths[&PushState {
push_direction,
crate_position: grid_position,
}]
Expand Down
13 changes: 5 additions & 8 deletions src/systems/render.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bevy::prelude::*;
use bevy::winit::WinitWindows;
use itertools::Itertools;

use crate::components::*;
use crate::direction::Direction;
Expand Down Expand Up @@ -229,14 +230,10 @@ pub fn select_crate(
let crate_position = &event.0;
let Board { board, tile_size } = board.single();

let path = board.level.crate_pushable_path(crate_position);
let paths = board.level.crate_pushable_paths(crate_position);

// spawn crate reachable marks
for &PushState {
push_direction: _,
crate_position,
} in path.keys()
{
for crate_position in paths.keys().map(|state| state.crate_position).unique() {
commands.spawn((
SpriteBundle {
sprite: Sprite {
Expand All @@ -255,12 +252,12 @@ pub fn select_crate(
));
}

if path.is_empty() {
if paths.is_empty() {
*crate_reachable = CrateReachable::None;
} else {
*crate_reachable = CrateReachable::Some {
selected_crate: *crate_position,
path,
paths,
};
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ mod tests {
fn solver() {
let levels = Level::load_from_file(Path::new("assets/levels/box_world.xsb")).unwrap();
for level in [
0, 1, 2, 3, 4, 6, 7, 9, 10, 11, 12, 13, 14, 16, 18, 19, 21, 24, 30, 31, 34, 36, 40, 43,
61, 62, 64, 74, 76, 90, 91,
0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 19, 21, 24, 28, 29, 30, 31, 34,
36, 40, 43, 61, 62, 63, 64, 65, 67, 69, 74, 76, 90, 91,
]
.map(|x| levels[x].clone())
{
Expand Down

0 comments on commit 325a550

Please sign in to comment.