Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2efe03145f | |||
| 98d701b2fe |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
maze.png
|
|
||||||
76
README.md
Normal file
76
README.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# go-maze
|
||||||
|
|
||||||
|
Tiny Go maze library I built for fun.
|
||||||
|
|
||||||
|
It generates random mazes, can solve them, and can render PNGs.
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
- Generate a maze grid (`0 = wall`, `1 = walkable`)
|
||||||
|
- Solve a maze (top entrance -> bottom exit)
|
||||||
|
- Render mazes as images
|
||||||
|
- Optionally highlight the solution path
|
||||||
|
|
||||||
|
## Upcoming Features
|
||||||
|
|
||||||
|
- [ ] Add the posibiltiy for entrance/exit to be on the left/right walls
|
||||||
|
- [ ] Add the posiblity of choice when it comes to which position to put the entrance or the exit
|
||||||
|
- [ ] Add complex maze shapes, such as L, U, etc...
|
||||||
|
|
||||||
|
## Example images
|
||||||
|
|
||||||
|
Normal maze:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Maze with solution path:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get tea.chunkbyte.com/kato/go-maze@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
If this is private on your Gitea, set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go env -w GOPRIVATE=tea.chunkbyte.com # You need to do this
|
||||||
|
git config --global url."ssh://git@tea.chunkbyte.com:2422/".insteadOf "https://tea.chunkbyte.com/" # Optional
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use in code
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
maze "tea.chunkbyte.com/kato/go-maze/maze"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
grid, err := maze.GenerateWithSeed(41, 41, 42)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := maze.DefaultRenderOptions()
|
||||||
|
opts.Scale = 8
|
||||||
|
opts.HighlightPath = true
|
||||||
|
|
||||||
|
if err := maze.SavePNG(grid, "maze.png", opts); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run the web example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./examples/web
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open `http://localhost:8080`.
|
||||||
BIN
docs/images/maze.png
Normal file
BIN
docs/images/maze.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
docs/images/maze_solved.png
Normal file
BIN
docs/images/maze_solved.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
13
maze/maze.go
13
maze/maze.go
@@ -9,6 +9,8 @@ import (
|
|||||||
// A value of 0 is a wall and 1 is a walkable path.
|
// A value of 0 is a wall and 1 is a walkable path.
|
||||||
type Grid [][]int
|
type Grid [][]int
|
||||||
|
|
||||||
|
const GOLDEN_RATION_BIT_MIXER = 0x9e3779b97f4a7c15
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrInvalidDimensions is returned when width or height are not positive.
|
// ErrInvalidDimensions is returned when width or height are not positive.
|
||||||
ErrInvalidDimensions = errors.New("maze dimensions must be greater than zero")
|
ErrInvalidDimensions = errors.New("maze dimensions must be greater than zero")
|
||||||
@@ -33,7 +35,7 @@ func Generate(width, height int) (Grid, error) {
|
|||||||
//
|
//
|
||||||
// The same width, height, and seed always produce the same maze.
|
// The same width, height, and seed always produce the same maze.
|
||||||
func GenerateWithSeed(width, height int, seed uint64) (Grid, error) {
|
func GenerateWithSeed(width, height int, seed uint64) (Grid, error) {
|
||||||
rng := rand.New(rand.NewPCG(seed, seed^0x9e3779b97f4a7c15))
|
rng := rand.New(rand.NewPCG(seed, seed^GOLDEN_RATION_BIT_MIXER))
|
||||||
return generate(width, height, rng.IntN)
|
return generate(width, height, rng.IntN)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +62,9 @@ func generate(width, height int, intN func(int) int) (Grid, error) {
|
|||||||
stackY := make([]int, 1, width*height/2)
|
stackY := make([]int, 1, width*height/2)
|
||||||
stackX[0], stackY[0] = startX, startY
|
stackX[0], stackY[0] = startX, startY
|
||||||
|
|
||||||
|
// Depth-first backtracking carve:
|
||||||
|
// grow the maze from the current cell into a random unvisited neighbor,
|
||||||
|
// and backtrack when no further expansion is possible.
|
||||||
for len(stackX) > 0 {
|
for len(stackX) > 0 {
|
||||||
last := len(stackX) - 1
|
last := len(stackX) - 1
|
||||||
x, y := stackX[last], stackY[last]
|
x, y := stackX[last], stackY[last]
|
||||||
@@ -90,6 +95,7 @@ func generate(width, height int, intN func(int) int) (Grid, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Choose a top-border entrance that connects to an already carved cell.
|
||||||
topChoices := make([]int, 0, width/2)
|
topChoices := make([]int, 0, width/2)
|
||||||
for x := 1; x < width-1; x++ {
|
for x := 1; x < width-1; x++ {
|
||||||
if grid[1][x] == 1 {
|
if grid[1][x] == 1 {
|
||||||
@@ -101,6 +107,7 @@ func generate(width, height int, intN func(int) int) (Grid, error) {
|
|||||||
grid[0][entranceX] = 1
|
grid[0][entranceX] = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Choose a bottom-border exit that connects to an already carved cell.
|
||||||
bottomChoices := make([]int, 0, width/2)
|
bottomChoices := make([]int, 0, width/2)
|
||||||
for x := 1; x < width-1; x++ {
|
for x := 1; x < width-1; x++ {
|
||||||
if grid[height-2][x] == 1 {
|
if grid[height-2][x] == 1 {
|
||||||
@@ -158,6 +165,8 @@ func Solve(grid Grid) ([][]bool, error) {
|
|||||||
dx := [4]int{0, 1, 0, -1}
|
dx := [4]int{0, 1, 0, -1}
|
||||||
dy := [4]int{-1, 0, 1, 0}
|
dy := [4]int{-1, 0, 1, 0}
|
||||||
|
|
||||||
|
// Breadth-first search from entrance to exit while recording parent links
|
||||||
|
// so the shortest path can be reconstructed afterward.
|
||||||
found := false
|
found := false
|
||||||
for head := 0; head < len(queue); head++ {
|
for head := 0; head < len(queue); head++ {
|
||||||
idx := queue[head]
|
idx := queue[head]
|
||||||
@@ -192,6 +201,7 @@ func Solve(grid Grid) ([][]bool, error) {
|
|||||||
return nil, ErrNoPath
|
return nil, ErrNoPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reconstruct the path by following parent pointers backward from exit to entrance.
|
||||||
pathCells := make([]bool, width*height)
|
pathCells := make([]bool, width*height)
|
||||||
for idx := end; idx != -1; idx = parent[idx] {
|
for idx := end; idx != -1; idx = parent[idx] {
|
||||||
pathCells[idx] = true
|
pathCells[idx] = true
|
||||||
@@ -200,6 +210,7 @@ func Solve(grid Grid) ([][]bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expose the flat path buffer as a 2D matrix aligned with the maze grid.
|
||||||
path := make([][]bool, height)
|
path := make([][]bool, height)
|
||||||
for y := 0; y < height; y++ {
|
for y := 0; y < height; y++ {
|
||||||
rowStart := y * width
|
rowStart := y * width
|
||||||
|
|||||||
Reference in New Issue
Block a user