Skip to content

Commit

Permalink
feat: support visualization of automatic solution process
Browse files Browse the repository at this point in the history
  • Loading branch information
ShenMian committed Jan 17, 2024
1 parent 3946ade commit d5c84cb
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 79 deletions.
32 changes: 21 additions & 11 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ mod level;
use level::*;

mod systems;
use systems::auto_crate_push::*;
use systems::auto_solve::*;
use systems::input::*;
use systems::level::*;
use systems::render::*;
use systems::solver::*;
use systems::ui::*;

mod plugins;
Expand Down Expand Up @@ -94,14 +95,7 @@ fn main() {
.chain(),
),
)
.add_systems(
FixedUpdate,
(
animate_tiles_movement,
animate_player_movement,
animate_camera_zoom,
),
);
.add_systems(FixedUpdate, animate_camera_zoom);

app.add_systems(
Update,
Expand All @@ -118,6 +112,10 @@ fn main() {
file_drag_and_drop,
)
.run_if(in_state(AppState::Main)),
)
.add_systems(
FixedUpdate,
(animate_player_movement, animate_tiles_movement).run_if(in_state(AppState::Main)),
);

app.add_systems(
Expand All @@ -127,8 +125,20 @@ fn main() {
clear_action_state,
),
)
.add_systems(Update, update_solver.run_if(in_state(AppState::AutoSolve)))
.add_systems(OnExit(AppState::AutoSolve), despawn_lowerbound_marks)
.add_systems(
Update,
(update_solver, update_grid_position, move_tiles).run_if(in_state(AppState::AutoSolve)),
)
.add_systems(
OnExit(AppState::AutoSolve),
(
reset_board,
update_grid_position,
move_tiles,
despawn_lowerbound_marks,
)
.chain(),
)
.insert_resource(SolverState::default());

app.add_systems(OnEnter(AppState::AutoCratePush), spawn_crate_pushable_marks)
Expand Down
4 changes: 3 additions & 1 deletion src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl Default for Settings {
fn default() -> Self {
Self {
instant_move: false,
player_move_speed: 0.05,
player_move_speed: 0.1,
solver: SolverSettings::default(),
}
}
Expand Down Expand Up @@ -73,13 +73,15 @@ impl Default for AutoCratePushState {
#[derive(Resource)]
pub struct SolverState {
pub solver: Mutex<Solver>,
pub level: Level,
pub stopwatch: Stopwatch,
}

impl Default for SolverState {
fn default() -> Self {
Self {
solver: Mutex::new(Solver::new(Level::empty())),
level: Level::empty(),
stopwatch: Stopwatch::new(),
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/solver/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl Solver {
}

// Solver::shrink_heap(&mut self.heap);
Solver::print_info(&self.visited, &self.heap, &state);
// Solver::print_info(&self.visited, &self.heap, &state);

for successor in state.successors(&self) {
if self.visited.contains(&successor.normalized(&self)) {
Expand All @@ -115,6 +115,10 @@ impl Solver {
.get_or_init(|| self.calculate_lower_bounds())
}

pub fn best_state(&self) -> Option<&State> {
self.heap.peek()
}

fn minimum_push_count_to_nearest_target(&self, position: &Vector2<i32>) -> Option<usize> {
if self.level.target_positions.contains(position) {
return Some(0);
Expand Down
52 changes: 52 additions & 0 deletions src/systems/auto_crate_push.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use bevy::prelude::*;
use itertools::Itertools;

use crate::components::*;
use crate::resources::*;
use crate::AppState;

pub fn spawn_crate_pushable_marks(
mut commands: Commands,
mut auto_crate_push_state: ResMut<AutoCratePushState>,
board: Query<&Board>,
mut next_state: ResMut<NextState<AppState>>,
) {
let crate_position = &auto_crate_push_state.selected_crate;
let Board { board, tile_size } = board.single();

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

// spawn crate pushable marks
for crate_position in paths.keys().map(|state| state.crate_position).unique() {
commands.spawn((
SpriteBundle {
sprite: Sprite {
color: Color::GREEN.with_a(0.8),
custom_size: Some(Vec2::new(tile_size.x / 4.0, tile_size.y / 4.0)),
..default()
},
transform: Transform::from_xyz(
crate_position.x as f32 * tile_size.x,
(board.level.dimensions.y - crate_position.y) as f32 * tile_size.y,
10.0,
),
..default()
},
CratePushableMark,
));
}

if paths.is_empty() {
next_state.set(AppState::Main);
return;
}

auto_crate_push_state.paths = paths;
}

pub fn despawn_crate_pushable_marks(
mut commands: Commands,
marks: Query<Entity, With<CratePushableMark>>,
) {
marks.for_each(|entity| commands.entity(entity).despawn());
}
63 changes: 59 additions & 4 deletions src/systems/solver.rs → src/systems/auto_solve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,32 @@ pub fn setup_solver(
settings: Res<Settings>,
) {
let board = &board.single().board;
let SolverState { solver, stopwatch } = &mut *solver_state;
let SolverState {
solver,
level,
stopwatch,
} = &mut *solver_state;
*level = board.level.clone();
let mut solver = solver.lock().unwrap();
*solver = Solver::new(board.level.clone());
*solver = Solver::new(level.clone());
solver.initial(settings.solver.strategy, settings.solver.lower_bound_method);
stopwatch.reset();
}

pub fn reset_board(mut board: Query<&mut Board>, solver_state: Res<SolverState>) {
let board = &mut board.single_mut().board;
*board = crate::board::Board::with_level(solver_state.level.clone());
}

pub fn move_tiles(mut tiles: Query<(&mut Transform, &GridPosition)>, board: Query<&Board>) {
let Board { board, tile_size } = &board.single();
for (mut transform, grid_position) in tiles.iter_mut() {
transform.translation.x = grid_position.x as f32 * tile_size.x;
transform.translation.y =
board.level.dimensions.y as f32 * tile_size.y - grid_position.y as f32 * tile_size.y;
}
}

pub fn update_solver(
mut solver_state: ResMut<SolverState>,
mut board: Query<&mut Board>,
Expand All @@ -69,7 +88,13 @@ pub fn update_solver(
mut next_state: ResMut<NextState<AppState>>,
) {
let board = &mut board.single_mut().board;
let SolverState { solver, stopwatch } = &mut *solver_state;
let SolverState {
solver,
level,
stopwatch,
} = &mut *solver_state;

*board = crate::board::Board::with_level(level.clone());

let mut solver = solver.lock().unwrap();
let timeout = std::time::Duration::from_millis(50);
Expand All @@ -95,9 +120,10 @@ pub fn update_solver(
info!(" Solution: {}", solution.lurd());

for movement in &*solution {
player_move_or_push(movement.direction, board, &mut player_movement);
player_move_or_push(movement.direction, &mut player_movement);
}
next_state.set(AppState::Main);
return;
}
Err(SolveError::NoSolution) => {
stopwatch.tick(timer.elapsed());
Expand All @@ -106,9 +132,38 @@ pub fn update_solver(
stopwatch.elapsed().as_millis() as f32 / 1000.0
);
next_state.set(AppState::Main);
return;
}
Err(SolveError::Timeout) => {
let _ = stopwatch.tick(timer.elapsed());
}
}
if let Some(best_state) = solver.best_state() {
// println!(
// "lower bound: {:3}, moves: {:3}, pushes: {:3}",
// best_state.lower_bound(&solver),
// best_state.movements.move_count(),
// best_state.movements.push_count()
// );
for movement in &*best_state.movements {
board.move_or_push(movement.direction);
}
}
}

pub fn update_grid_position(
mut player_grid_positions: Query<&mut GridPosition, With<Player>>,
mut crate_grid_positions: Query<&mut GridPosition, (With<Crate>, Without<Player>)>,
board: Query<&Board>,
) {
let board = &board.single().board;
let mut player_grid_positions = player_grid_positions.single_mut();
**player_grid_positions = board.level.player_position;

for (mut crate_grid_position, crate_position) in crate_grid_positions
.iter_mut()
.zip(board.level.crate_positions.iter())
{
**crate_grid_position = *crate_position;
}
}
Loading

0 comments on commit d5c84cb

Please sign in to comment.