Skip to content

Commit

Permalink
Merge pull request #4 from mkouhia/release_0.2
Browse files Browse the repository at this point in the history
Release 0.2
  • Loading branch information
mkouhia authored May 29, 2024
2 parents 53bdcae + 921df79 commit 392a290
Show file tree
Hide file tree
Showing 8 changed files with 561 additions and 221 deletions.
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.2.0] - 2024-05-29

### Added
- Read problems also from standard input.
- Add feature `mapgen`, which allows map generation with binary `generate-maze`.

### Changed
- Rename main binary executable to `solve-maze`
- Maze `hero_start`, `dragon_start` and `goal` are now usize indexes.
- Errors are separated for cases: no viable paths to end, or no paths without meeting the dragon.
- If there is no viable path from the dragon position to the hero position, the problem can be solved but the dragon does not move.

## [0.1.2] - 2024-05-28

### Changed
Expand All @@ -29,7 +41,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dragon routing based on shortest path to current hero position.
- Playback solution.

[unreleased]: https://github.com/mkouhia/wundernut-vol13/compare/v0.1.2...HEAD
[unreleased]: https://github.com/mkouhia/wundernut-vol13/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/mkouhia/wundernut-vol13/compare/v0.1.2...v0.2.0
[0.1.2]: https://github.com/mkouhia/wundernut-vol13/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/mkouhia/wundernut-vol13/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/mkouhia/wundernut-vol13/tree/v0.1.0
76 changes: 71 additions & 5 deletions Cargo.lock

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

15 changes: 14 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
[package]
name = "wundernut-vol13"
version = "0.1.2"
version = "0.2.0"
authors = ["Mikko Kouhia <[email protected]>"]
edition = "2021"
description = "Solution to Wundernut vol. 13"
readme = "README.md"
license = "MIT"
repository = "https://github.com/mkouhia/wundernut-vol13"
default-run = "solve-maze"

[dependencies]
anyhow = "1.0.86"
clap = { version = "4.5.4", features = ["derive"] }
itertools = "0.13.0"
rand = { version = "0.8.5", optional = true }

[features]
mapgen = ["dep:rand"]

[[bin]]
name = "solve-maze"
path = "src/main.rs"

[[bin]]
name = "generate-maze"
required-features = ["mapgen"]
52 changes: 44 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,36 @@ The problem is to find a way out of a maze while avoiding a moving dragon.

cargo build --release

You will now find the executable in `./target/release/wundernut-vol13`, or if on Windows, `./target/release/wundernut-vol13.exe`. In following examples, replace program name with the correct path to the built executable, or copy the executable to a convenient location.
You will now find the executable in `./target/release/solve-maze`, or if on Windows, `./target/release/solve-maze.exe`. In following examples, replace program name with the correct path to the built executable, or copy the executable to a convenient location.

- With the maze in some local file, solve the maze with

./target/release/wundernut-vol13 <FILE>
solve-maze [OPTIONS] <FILE>

- _Fun factor:_ solve and display the hero's journey in the maze:

./target/release/wundernut-vol13 --playback <FILE>
solve-maze --playback <FILE>

[solve-maze-example1.webm](https://github.com/mkouhia/wundernut-vol13/assets/1469093/23a9fed2-088a-4c8b-b3c7-5357f388b910)

If necessary, consult the program help in

./target/release/wundernut-vol13 --help
solve-maze --help

### Extras (additional fun factor)
Additional feature `mapgen` will can generate more maps for an increased fun factor. Build with feature `mapgen` to create another binary `generate-maze`:

cargo build --features mapgen --release

Then you can generate additional mazes with

generate-maze [OPTIONS]

To generate a maze and play back results, perform

generate-maze | solve-maze -p -

[generate-and-solve-maze.webm](https://github.com/mkouhia/wundernut-vol13/assets/1469093/cfd23422-a921-4882-8bae-b038b80e65e9)


## Implementation
Expand All @@ -53,6 +70,18 @@ The search for the optimal path starts at the hero location.
7. All acceptable resulting states are pushed to the heap for evaluation.
8. We loop over the possible states, taking more steps one by one until the goal is found.

Hero back-steps are allowed, to enable solving of such situations, where the hero cannot always go forward:

> ```
> 🟫🟫🟫🟫🟫🟫
> 🟫🏃🟩🟩🟫🟫
> 🟫🟩🟫🟩🟫🟫
> 🟫🟩🟩🐉🟩❎
> 🟫🟫🟫🟫🟫🟫
> ```
> Optimal solution: hero takes either step to the right or down, dragon comes to meet, hero steps back and then continues around the circle in the opposite direction to the first step.
This is implemented in the Dijkstra's algorithm, by having current best distance `dist` and best previous step to the node `prev` to be indexed by the hero position _and_ the dragon position. This increases the number of possible solutions quite heavily, but is required for these edge cases.
### Dragon movement
Expand All @@ -61,10 +90,17 @@ Once the maze has been set up, the shortest paths from one square to another doe
Thus, we can pre-calculate the shortest paths from any square `u` to any other square `v` with [Floyd-Warshall algorithm](https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm#Path_reconstruction).
Upon calculating the shortest paths, the previous steps on the path `u->v`are saved, and queried upon dragon movement.
### General implementation notices
**Special case**: If there is no viable path from the dragon position to the hero position, the dragon does not awake. It will remain sleeping in its current position.
### General implementation notes
- The algorithm is designed for small to medium sized, quite closed maps. Since back-steps for the hero are allowed, the number of possible solutions increases rapidly with the map size. For open maps, where back-steps for the hero are not required, reqular Dijkstra `dist` and `prev` with only hero position would be better.
- Rust is selected as the implementation language to provide solutions efficiently. Current implementation is still single-threaded. Multi-threading can be implemented to improve solution times.
- If the maze would have a square type with single access direction, that could be handled with directional edges.
- Different terrain types could be handled by introducing edge weights.
- For the problem solving algorithm, meeting a dragon and path to the goal not existing are equivalent: there is no feasible path to the end.
- The underlying graph may be simplified by converting long passageways to just two nodes with longer edge in between. This would reduce solution time for the optimal path, but converting to hero/dragon steps requires more attention.
- Non-rectangular grids, or other tile shapes (hexagonal, mixed shapes) can be implemented using the same graph-based approach. New map parsing methods would need to be introduced, and the `Point` struct revised.
- For the problem solving algorithm, meeting a dragon and path to the goal not existing are equivalent: there is no feasible path to the end. The cases are differentiated by running the same algorithm without the dragon constraint. If a solution is found, then the dragon was guilty.
- Because this is a family friendly implementation, no playback is available for the cases that the dragon would slay the hero.
## Development
Expand All @@ -82,7 +118,7 @@ Build release version of the program with
After this, you may run the compiled binary program with
./target/release/wundernut-vol13
./target/release/solve-maze
When developing, the program may also be run with
Expand All @@ -96,4 +132,4 @@ You can build the developer documentation with
## License
This project is licensed under the terms of the MIT license.
This project is licensed under the terms of the MIT license.
32 changes: 32 additions & 0 deletions src/bin/generate-maze.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//! CLI for maze solving
use clap::Parser;
use itertools::Itertools;
use wundernut_vol13::maze_generator::MazeGenerator;

/// Map generator for Wundernut vol. 13
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// Generated field height
#[arg(long, default_value_t = 20)]
height: usize,

/// Generated field width
#[arg(long, default_value_t = 15)]
width: usize,

/// Random seed
#[arg(long)]
seed: Option<u64>,
}

/// Read maze from file, print output
fn main() -> anyhow::Result<()> {
let args = Args::parse();

let mut gen = MazeGenerator::new(args.seed);
let res: Vec<Vec<char>> = gen.generate_maze(args.height, args.width);
println!("{}", res.iter().map(|row| row.iter().join("")).join("\n"));
Ok(())
}
Loading

0 comments on commit 392a290

Please sign in to comment.