Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MazeGenerator exercise and related tests #2355

Merged
merged 35 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c88783b
Add MazeGenerator exercise and related tests
Sep 17, 2023
1e1765a
Update exercises/practice/mazy-mice/src/main/java/MazeGenerator.java
Sep 18, 2023
0b9ef44
Update exercises/practice/mazy-mice/src/main/java/MazeGenerator.java
Sep 18, 2023
01bc92e
Update mazy-mice exercise metadata configuration
Sep 19, 2023
6cc8087
Merge remote-tracking branch 'origin/mazy-mice' into mazy-mice
Sep 19, 2023
7d7348e
Improve grammar in mazy-mice exercise instructions
Sep 19, 2023
ccbbcd7
Update placeholder in hints.md
Sep 19, 2023
26ecea4
Merge branch 'main' into mazy-mice
Sep 30, 2023
11fe1d0
Refactor test method name for clarity
Sep 30, 2023
2ef9ca6
Update test method name to reflect logical assertion
Sep 30, 2023
4071a73
Add 'mazy-mice' to settings.gradle
Sep 30, 2023
d2cfeed
Merge branch 'main' into mazy-mice
Oct 7, 2023
f33e267
Add new "mazy-mice" exercise to config.json
Oct 7, 2023
88106d6
Refactor maze generation and test methods for readability
Oct 28, 2023
4003c58
Merge branch 'main' into mazy-mice
Oct 28, 2023
1bd5acf
Add descriptions to auto-generated test cases
Oct 28, 2023
d8585eb
Change access modifier for test methods in MazeGeneratorTest
Oct 28, 2023
4416e9e
Add additional hints for exercise in hints.md
Oct 29, 2023
a25e6b3
Add a new test for seeded maze generator
Oct 29, 2023
473e735
Add tests for maze size validation
Oct 29, 2023
ca072f3
Add maze dimensions validation to the MazeGenerator class
Oct 29, 2023
5b9e2c5
Revise header formatting in instructions.md
Oct 31, 2023
54e1580
Remove redundant .meta/tests.toml file
Oct 31, 2023
07777d7
delete version file
Oct 31, 2023
7be25c0
Remove Dimensions.java as its functionality is redundant
Oct 31, 2023
44367ac
Update exercises/practice/mazy-mice/src/test/java/MazeGeneratorTest.java
Oct 31, 2023
0d446b8
Update exercises/practice/mazy-mice/src/test/java/MazeGeneratorTest.java
Oct 31, 2023
004b60d
Update config.json to include new practices
Oct 31, 2023
238f971
Merge branch 'main' into mazy-mice
Oct 31, 2023
2f77e5e
Add new game "Mazy Mice" in config.json
Oct 31, 2023
f1ca0a9
Merge remote-tracking branch 'origin/mazy-mice' into mazy-mice
Oct 31, 2023
4afc2f1
Add Dimensions.java to Mazy Mice example files
Oct 31, 2023
0db470a
Remove redundant mazeNotNull test in MazeGeneratorTest class
Oct 31, 2023
833a94d
Refactor MazeGeneratorTest for better test organization
Nov 1, 2023
69c39aa
Add status field to config.json
Nov 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions exercises/practice/mazy-mice/.docs/hints.md
rabestro marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# placeholder
rabestro marked this conversation as resolved.
Show resolved Hide resolved

## Maze generation

You can use any algorithm to generate a perfect maze. The [recursive backtracker][recursive-backtracker] is a good choice.

## Box drawing characters

| Character | Name | Unicode |
|:---------:|:--------------------------------------|:--------|
| ┌ | box drawings light down and right | U+250C |
| ─ | box drawings light horizontal | U+2500 |
| ┬ | box drawings light down and horizontal| U+252C |
| ┐ | box drawings light down and left | U+2510 |
| │ | box drawings light vertical | U+2502 |
| └ | box drawings light up and right | U+2514 |
| ┴ | box drawings light up and horizontal | U+2534 |
| ┘ | box drawings light up and left | U+2518 |
| ├ | box drawings light vertical and right | U+2520 |
| ⇨ | rightwards white arrow | U+21E8 |


[recursive-backtracker]: https://en.wikipedia.org/wiki/Maze_generation_algorithm
48 changes: 48 additions & 0 deletions exercises/practice/mazy-mice/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## Instructions
rabestro marked this conversation as resolved.
Show resolved Hide resolved

Your task is to generate the perfect mazes for Mickey and Minerva — those with only one solution and no isolated sections.
Here's what you need to know:

- The maze has a rectangular shape with an opening at the start and end.
- The maze has rooms and passages, which intersect at right angles.
- The program should accept two parameters: rows and columns. The maze should be between 5 and 100 cells in size.
rabestro marked this conversation as resolved.
Show resolved Hide resolved
- A maze which is `x` columns wide and `y` rows high should be `2x + 1` characters wide and `2y + 1` characters high.
- If no seed is provided, generate a random maze. If the same seed is provided multiple times, the resulting maze should be the same each time.
- Use [box-drawing][Box-drawing] characters to draw walls, and an arrow symbol (⇨) for the entrance on the left and exit on the right.

It's time to create some perfect mazes for these adventurous mice!

### Examples

1. The small square maze 5x5 cells (or 11x11 characters)
rabestro marked this conversation as resolved.
Show resolved Hide resolved
```text
┌───────┬─┐
│ │ │
│ ┌─┬── │ │
│ │ │ │ ⇨
│ │ │ ──┤ │
⇨ │ │ │ │
┌─┤ └── │ │
│ │ │ │
│ │ ────┘ │
│ │
└─────────┘
```
2. The rectangular maze 6x18 cells
rabestro marked this conversation as resolved.
Show resolved Hide resolved
```text
┌───────────┬─────────┬───────────┬─┐
│ │ │ │ │
│ ┌───────┐ │ ┌─┐ ──┐ └───┐ ┌───┐ │ │
│ │ │ │ │ │ │ │ │ │ ⇨
│ └─┐ ┌─┐ │ │ │ ├── ├───┐ │ │ ──┼── │
│ │ │ │ │ │ │ │ │ │ │ │
└── │ │ ├───┴───┤ ┌─┘ ┌─┘ │ ├── │ ──┤
⇨ │ │ │ │ │ │ │ │ │
┌─┬─┴─┐ └─┐ ┌─┐ │ └─┐ │ ┌─┘ │ ──┴─┐ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ └── │ │ │ └── │ ──┘ ┌─┘ ──┐ │ │
│ │ │ │ │ │ │
└───┴───────┴───────┴─────┴─────┴───┘
```

[Box-drawing]: https://en.wikipedia.org/wiki/Box-drawing_character
3 changes: 3 additions & 0 deletions exercises/practice/mazy-mice/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Introduction

Meet Mickey and Minerva, two clever mice who love to navigate their way through a maze to find cheese. They enjoy a good challenge, but with only their tiny mouse brains, they prefer if there is only one correct path to the cheese.
19 changes: 19 additions & 0 deletions exercises/practice/mazy-mice/.meta/config.json
rabestro marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"rabestro"
],
"files": {
"solution": [
"src/main/java/MazeGenerator.java"
],
"test": [
"src/test/java/MazeGeneratorTest.java"
],
"example": [
".meta/src/reference/java/MazeGenerator.java"
]
},
"blurb": "Meet Mickey and Minerva, two clever mice who love to navigate their way through a maze to find cheese. They enjoy a good challenge, but with only their tiny mouse brains, they prefer if there is only one correct path to the cheese.",
"source": "Inspired by the 'Maze Generator' created by Jan Boström at Alance AB.",
"source_url": "https://mazegenerator.net/"
}
rabestro marked this conversation as resolved.
Show resolved Hide resolved
rabestro marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Represents the dimensions of a maze.
* <p>
* Dimensions of a grid can be represented in cells or characters.
* Rows and columns are used for cells, while width and height are used for characters.
*/
public record Dimensions(int rows, int columns) {
/**
* Returns the width of the maze in characters.
*
* @return the width of the maze
*/
int width() {
return 2 * columns + 1;
}

/**
* Returns the height of the maze in characters.
*
* @return the height of the maze
*/
int height() {
return 2 * rows + 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import java.util.Random;
import java.util.random.RandomGenerator;
import java.util.BitSet;
import java.util.EnumSet;
import java.util.Set;

import static java.util.stream.IntStream.range;

public class MazeGenerator {

public char[][] generatePerfectMaze(Dimensions dimensions) {
return new Grid(dimensions, RandomGenerator.getDefault())
.generateMaze()
.placeDoors()
.print();
}

public char[][] generatePerfectMaze(Dimensions dimensions, int seed) {
return new Grid(dimensions, new Random(seed))
.generateMaze()
.placeDoors()
.print();
}
}

enum Direction {
NORTH(0, 1),
EAST(1, 0),
SOUTH(0, -1),
WEST(-1, 0);
private final int dx;
private final int dy;

Direction(int dx, int dy) {
this.dx = dx;
this.dy = dy;
}

public int dx() {
return dx;
}

public int dy() {
return dy;
}
}

final class Grid {
private final Dimensions dimensions;
private final BitSet grid;
private final RandomGenerator randomGenerator;

Grid(Dimensions dimensions, RandomGenerator randomGenerator) {
this.dimensions = dimensions;
this.grid = new BitSet(dimensions.width() * dimensions.height());
this.randomGenerator = randomGenerator;
}

Grid generateMaze() {
generate(new Cell(1, 1));
return this;
}

private int random(int bound) {
return randomGenerator.nextInt(bound);
}

private Direction pickRandomDirection(Set<Direction> directions) {
int size = directions.size();
int itemIndex = random(size);
var direction = directions.toArray(new Direction[size])[itemIndex];
directions.remove(direction);
return direction;
}

Grid placeDoors() {
new Cell(1 + 2 * random(dimensions.rows()), 0).erase();
new Cell(1 + 2 * random(dimensions.rows()), dimensions.width() - 1).erase();
return this;
}

private void generate(Cell cell) {
cell.erase();

var directions = EnumSet.allOf(Direction.class);
do {
var direction = pickRandomDirection(directions);
var wall = cell.move(direction);
var next = wall.move(direction);
if (next.isValid() && next.isNotEmpty()) {
wall.erase();
generate(next);
}
} while (!directions.isEmpty());
}

char[][] print() {
return range(0, dimensions.height())
.mapToObj(this::line)
.toArray(char[][]::new);
}

private char[] line(int x) {
return range(0, dimensions.width())
.map(y -> new Cell(x, y).symbol())
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString()
.toCharArray();
}

private final class Cell {
final int x;
final int y;

private Cell(int x, int y) {
this.x = x;
this.y = y;
}

boolean isValid() {
return x > 0 && x < dimensions.height() && y > 0 && y < dimensions.width();
}

void erase() {
grid.set(index());
}

boolean isNotEmpty() {
return !isEmpty();
}

boolean isEmpty() {
return grid.get(index());
}

int index() {
return x * dimensions.width() + y;
}

boolean isDoor() {
return isEmpty() && (y == 0 || y == dimensions.width() - 1);
}

Cell move(Direction direction) {
return new Cell(x + direction.dx(), y + direction.dy());
}

char symbol() {
if (isDoor()) {
return '⇨';
}
if (isEmpty()) {
return ' ';
}
var n = x > 0 && new Cell(x - 1, y).isNotEmpty() ? 1 : 0;
var e = y < dimensions.width() - 1 && new Cell(x, y + 1).isNotEmpty() ? 1 : 0;
var s = x < dimensions.height() - 1 && new Cell(x + 1, y).isNotEmpty() ? 1 : 0;
var w = y > 0 && new Cell(x, y - 1).isNotEmpty() ? 1 : 0;
var i = n + 2 * e + 4 * s + 8 * w;
return switch (i) {
case 0 -> ' ';
case 1, 5, 4 -> '│';
case 2, 8, 10 -> '─';
case 3 -> '└';
case 6 -> '┌';
case 7 -> '├';
case 9 -> '┘';
case 11 -> '┴';
case 12 -> '┐';
case 13 -> '┤';
case 14 -> '┬';
case 15 -> '┼';
default -> throw new IllegalStateException("Unexpected value: " + i);
};
}
}
}
1 change: 1 addition & 0 deletions exercises/practice/mazy-mice/.meta/version
rabestro marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
24 changes: 24 additions & 0 deletions exercises/practice/mazy-mice/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apply plugin: "java"
apply plugin: "eclipse"
apply plugin: "idea"

// set default encoding to UTF-8
compileJava.options.encoding = "UTF-8"
compileTestJava.options.encoding = "UTF-8"

repositories {
mavenCentral()
}

dependencies {
testImplementation "junit:junit:4.13"
testImplementation "org.assertj:assertj-core:3.15.0"
}

test {
testLogging {
exceptionFormat = 'full'
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
}
25 changes: 25 additions & 0 deletions exercises/practice/mazy-mice/src/main/java/Dimensions.java
rabestro marked this conversation as resolved.
Show resolved Hide resolved
rabestro marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Represents the dimensions of a maze.
* <p>
* Dimensions of a grid can be represented in cells or characters.
* Rows and columns are used for cells, while width and height are used for characters.
*/
public record Dimensions(int rows, int columns) {
/**
* Returns the width of the maze in characters.
*
* @return the width of the maze
*/
int width() {
return 2 * columns + 1;
}

/**
* Returns the height of the maze in characters.
*
* @return the height of the maze
*/
int height() {
return 2 * rows + 1;
}
}
10 changes: 10 additions & 0 deletions exercises/practice/mazy-mice/src/main/java/MazeGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
public class MazeGenerator {

public char[][] generatePerfectMaze(Dimensions dimensions) {
rabestro marked this conversation as resolved.
Show resolved Hide resolved
return null;
rabestro marked this conversation as resolved.
Show resolved Hide resolved
}

public char[][] generatePerfectMaze(Dimensions dimensions, int seed) {
return null;
rabestro marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading