Skip to content

Commit

Permalink
refactor: rename crates to boxes in soukoban game
Browse files Browse the repository at this point in the history
This commit renames all occurrences of 'crate' to 'box' in the soukoban game
implementation, as well as updates related graphics and documentation to
reflect this change.
  • Loading branch information
ShenMian committed Jun 14, 2024
1 parent b7d9668 commit cfce794
Show file tree
Hide file tree
Showing 18 changed files with 180 additions and 226 deletions.
File renamed without changes
20 changes: 10 additions & 10 deletions docs/auto_move.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
# Auto move

## Auto crate push
## Auto box push

<p align="center"><img src="assets/auto_crate_push.gif" width=70%></p>
<p align="center"><img src="assets/auto_box_push.gif" width=70%></p>

This feature can significantly assist users when playing larger levels.
At the same time, it also maintains the traditional control method.

Clicking on a crate will display all the points that the crate can reach (without moving other crates).
Clicking on a box will display all the points that the box can reach (without moving other boxes).

Take Microban #155 as an example:

<p align="center"><img src="assets/auto_crate_push_1.png" width=70%></p>
<p align="center"><img src="assets/auto_box_push_1.png" width=70%></p>

Clicking on one of the points will automatically push the selected crate to that position.
Clicking on one of the points will automatically push the selected box to that position.

In this case, the user can click on the target, and the character will automatically push the selected crate to the target to complete the level.
In this case, the user can click on the target, and the character will automatically push the selected box to the target to complete the level.

Some areas where the crates are reachable do not display points. This is because pushing the crate to those positions will lead to a deadlock and the player will be unable to continue completing the level.
Some areas where the boxes are reachable do not display points. This is because pushing the box to those positions will lead to a deadlock and the player will be unable to continue completing the level.

<p align="center"><img src="assets/auto_crate_push_2.png" width=70%></p>
<p align="center"><img src="assets/auto_box_push_2.png" width=70%></p>

## Auto player move

Expand All @@ -33,8 +33,8 @@ User can also directly click on the player's reachable area without selecting th

This feature is a bit controversial, with some users saying it's akin to cheating.

For simple levels, this does significantly reduce the level difficulty. An extreme example is a level with only a single crate and target, which means the player can complete it without having to do any reasoning.
For simple levels, this does significantly reduce the level difficulty. An extreme example is a level with only a single box and target, which means the player can complete it without having to do any reasoning.

But for challenging levels, the difficulty mainly lies in the intricate pushing relationship between multiple crates, rather than the pushing of a single crate. This feature allows players to focus on more complex reasoning instead of repeating the simple work of pushing a single crate.
But for challenging levels, the difficulty mainly lies in the intricate pushing relationship between multiple boxes, rather than the pushing of a single box. This feature allows players to focus on more complex reasoning instead of repeating the simple work of pushing a single box.

In addition, this feature is **optional**. Users can still use the traditional control methods.
18 changes: 9 additions & 9 deletions docs/level_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

## XSB format

| Tile | Symbol |
| ---------------- | ----------- |
| Wall | `#` |
| Player | `@` |
| Player on target | `+` |
| Crate | `$` |
| Crate on target | `*` |
| Target | `.` |
| Floor | ` `/`-`/`_` |
| Tile | Symbol |
| ---------------- | ------------ |
| Wall | `#` |
| Player | `@` |
| Player on target | `+` |
| Box | `$` |
| Box on target | `*` |
| Target | `.` |
| Floor | ``/`-`/`_` | |

## Import

Expand Down
4 changes: 2 additions & 2 deletions docs/solver.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ The solver can automatically solve levels of moderate complexity.
## Lower bound calculation method

- `MinimumPush`: Minimum push count to nearest target.
- `MinimumMove`: Minimum move count to nearest target. (This method is slow, especially on maps with many crates or large areas)
- `ManhattanDistance`: Manhattan distance to nearest target. (This method is fast, suitable for maps with many crates or large areas)
- `MinimumMove`: Minimum move count to nearest target. (This method is slow, especially on maps with many boxes or large areas)
- `ManhattanDistance`: Manhattan distance to nearest target. (This method is fast, suitable for maps with many boxes or large areas)

## Optimization

Expand Down
18 changes: 9 additions & 9 deletions src/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,27 @@ impl Board {
return false;
}
if self.level[player_next_position].intersects(Tiles::Box) {
let crate_next_position = player_next_position + &direction.into();
if self.level[crate_next_position].intersects(Tiles::Box | Tiles::Wall) {
let box_next_position = player_next_position + &direction.into();
if self.level[box_next_position].intersects(Tiles::Box | Tiles::Wall) {
return false;
}
}
true
}

/// Moves the player or pushes a crate in the specified direction.
/// Moves the player or pushes a box in the specified direction.
pub fn move_or_push(&mut self, direction: Direction) {
let direction_vector = &direction.into();
let player_next_position = self.level.player_position() + direction_vector;
if self.level[player_next_position].intersects(Tiles::Wall) {
return;
}
if self.level[player_next_position].intersects(Tiles::Box) {
let crate_next_position = player_next_position + direction_vector;
if self.level[crate_next_position].intersects(Tiles::Wall | Tiles::Box) {
let box_next_position = player_next_position + direction_vector;
if self.level[box_next_position].intersects(Tiles::Wall | Tiles::Box) {
return;
}
self.move_crate(player_next_position, crate_next_position);
self.move_box(player_next_position, box_next_position);

self.actions.push(Action::Push(direction));
} else {
Expand All @@ -72,8 +72,8 @@ impl Board {
let history = self.actions.pop().unwrap();
let direction = history.direction();
if history.is_push() {
let crate_position = self.level.player_position() + &direction.into();
self.move_crate(crate_position, self.level.player_position());
let box_position = self.level.player_position() + &direction.into();
self.move_box(box_position, self.level.player_position());
}
let player_prev_position = self.level.player_position() - &direction.into();
self.move_player(player_prev_position);
Expand Down Expand Up @@ -124,7 +124,7 @@ impl Board {
self.level.set_player_position(to);
}

fn move_crate(&mut self, from: Vector2<i32>, to: Vector2<i32>) {
fn move_box(&mut self, from: Vector2<i32>, to: Vector2<i32>) {
self.level[from].remove(Tiles::Box);
self.level[to].insert(Tiles::Box);
self.level.set_box_position(from, to);
Expand Down
4 changes: 2 additions & 2 deletions src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ pub struct Player;
pub struct AnimationState(benimator::State);

#[derive(Component)]
pub struct Crate;
pub struct Box;

#[derive(Component)]
pub struct PlayerMovableMark;

#[derive(Component)]
pub struct CratePushableMark;
pub struct BoxPushableMark;

#[derive(Component)]
pub struct LowerBoundMark;
4 changes: 2 additions & 2 deletions src/events.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use bevy::prelude::*;

#[derive(Event, Default)]
pub struct CrateEnterTarget;
pub struct BoxEnterTarget;

#[derive(Event, Default)]
pub struct CrateLeaveTarget;
pub struct BoxLeaveTarget;

#[derive(Event, Default)]
pub struct LevelSolved;
Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ fn main() {
app.init_resource::<ActionState<Action>>()
.insert_resource(input_map);

app.add_event::<CrateEnterTarget>()
.add_event::<CrateLeaveTarget>()
app.add_event::<BoxEnterTarget>()
.add_event::<BoxLeaveTarget>()
.add_event::<LevelSolved>()
.add_event::<UpdateGridPositionEvent>();

Expand Down
4 changes: 2 additions & 2 deletions src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ impl PlayerMovement {
pub enum AutoMoveState {
#[default]
Player,
Crate {
crate_position: Vector2<i32>,
Box {
position: Vector2<i32>,
paths: HashMap<PushState, Vec<Vector2<i32>>>,
},
}
Expand Down
33 changes: 14 additions & 19 deletions src/solve/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{
time::{Duration, Instant},
};

use crate::crate_pushable_paths_with_crate_positions;
use crate::box_pushable_paths_with_positions;
use crate::solve::state::*;
use soukoban::{direction::Direction, path_finding::reachable_area, Level};

Expand Down Expand Up @@ -210,11 +210,11 @@ impl Solver {
Direction::Down,
Direction::Left,
] {
let next_crate_position = target_position + &pull_direction.into();
let next_player_position = next_crate_position + &pull_direction.into();
let next_box_position = target_position + &pull_direction.into();
let next_player_position = next_box_position + &pull_direction.into();
if self.level.in_bounds(next_player_position)
&& !self.level[next_player_position].intersects(Tiles::Wall)
&& !self.level[next_crate_position].intersects(Tiles::Wall)
&& !self.level[next_box_position].intersects(Tiles::Wall)
{
player_position = Some(next_player_position);
break;
Expand Down Expand Up @@ -250,12 +250,12 @@ impl Solver {
Direction::Down,
Direction::Left,
] {
let next_crate_position = box_position + &pull_direction.into();
if self.level[next_crate_position].intersects(Tiles::Wall) {
let next_box_position = box_position + &pull_direction.into();
if self.level[next_box_position].intersects(Tiles::Wall) {
continue;
}

let next_player_position = next_crate_position + &pull_direction.into();
let next_player_position = next_box_position + &pull_direction.into();
if !self.level.in_bounds(next_player_position)
|| self.level[next_player_position].intersects(Tiles::Wall)
{
Expand All @@ -265,18 +265,16 @@ impl Solver {
continue;
}

let lower_bound = *lower_bounds
.get(&next_crate_position)
.unwrap_or(&usize::MAX);
let lower_bound = *lower_bounds.get(&next_box_position).unwrap_or(&usize::MAX);
let new_lower_bound = lower_bounds[&box_position] + 1;
if !visited.insert((next_crate_position, pull_direction)) {
if !visited.insert((next_box_position, pull_direction)) {
continue;
}
if new_lower_bound < lower_bound {
lower_bounds.insert(next_crate_position, new_lower_bound);
lower_bounds.insert(next_box_position, new_lower_bound);
}
self.minimum_push_to(
next_crate_position,
next_box_position,
next_player_position,
lower_bounds,
visited,
Expand All @@ -302,11 +300,8 @@ impl Solver {
continue;
}

let paths = crate_pushable_paths_with_crate_positions(
&self.level,
&position,
&HashSet::new(),
);
let paths =
box_pushable_paths_with_positions(&self.level, &position, &HashSet::new());
if let Some(lower_bound) = paths
.iter()
.filter(|path| self.level[path.0.box_position].intersects(Tiles::Goal))
Expand Down Expand Up @@ -341,7 +336,7 @@ impl Solver {
.level
.goal_positions()
.iter()
.map(|crate_pos| manhattan_distance(crate_pos, &position))
.map(|box_pos| manhattan_distance(box_pos, &position))
.min()
.unwrap() as usize;
lower_bounds.insert(position, lower_bound);
Expand Down
Loading

0 comments on commit cfce794

Please sign in to comment.