Skip to content

Commit b8d310f

Browse files
author
Fred
committed
First release
1 parent a4d5ec6 commit b8d310f

26 files changed

+1187
-0
lines changed

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# If you prefer the allow list template instead of the deny list, see community template:
2+
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3+
#
4+
# Binaries for programs and plugins
5+
*.exe
6+
*.exe~
7+
*.dll
8+
*.so
9+
*.dylib
10+
11+
# Test binary, built with `go test -c`
12+
*.test
13+
14+
# Output of the go coverage tool, specifically when used with LiteIDE
15+
*.out
16+
17+
# Dependency directories (remove the comment below to include it)
18+
# vendor/
19+
20+
# Go workspace file
21+
go.work
22+
.vscode
23+
amaze

README.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# :door: amaze :door:
2+
3+
A program to create, solve and draw mazes in your terminal or in 2D/3D
4+
5+
## Gallery
6+
7+
2D Terminal view
8+
9+
<p align="center">
10+
<img style="float: right;" src="https://github.com/fred1268/amaze/blob/development/readme/terminal_15x15_recursivebacktracker_solved.png" alt="Amaze in Action"/>
11+
</p>
12+
2D Graphical view
13+
<p align="center">
14+
<img style="float: right;" src="https://github.com/fred1268/amaze/blob/development/readme/2D_20x20_sidewinder_solved.png" alt="Amaze in Action"/>
15+
</p>
16+
3D Graphical view
17+
<p align="center">
18+
<img style="float: right;" src="https://github.com/fred1268/amaze/blob/development/readme/3D_50x50_growingtree_solved2.png" alt="Amaze in Action"/>
19+
</p>
20+
21+
---
22+
23+
## Setup
24+
25+
This program uses G3n, so it has [the same prerequisites](https://github.com/g3n/engine#dependencies).
26+
It is pretty straightforward, since most of the computers nowadays, have an OpenGL driver and a C
27+
compiler. I tested both on Linux (manjaro) and mac M1.
28+
29+
> Also, note that Amaze uses [**clap :clap:, the Command Line Argument Parser**](https://github.com/fred1268/go-clap).
30+
> clap is a non intrusive, lightweight command line parsing library you may want to try out in your
31+
> own projects. Feel free to give it a try!
32+
33+
---
34+
35+
## Installation
36+
37+
```
38+
git clone github.com/fred1268/amaze
39+
cd amaze
40+
go install
41+
```
42+
43+
---
44+
45+
## Running the program
46+
47+
You can run the program with several flags, although it has decent defaults. Here are the available flags:
48+
49+
```
50+
amaze: creates, solves and displays mazes
51+
--width, -w <width>: set maze's width
52+
--length, -l <length>: set maze's length
53+
--algo, -a <name>: choose the algorithm used to create the maze:
54+
binarytree
55+
sidewinder
56+
aldousbroder
57+
wilson
58+
huntandkill
59+
recursivebacktracker
60+
growingtree
61+
use --choice, -c to chose sub algorithm:
62+
random, last, or mix
63+
--seed, -d <seed>: use seed to generate identical maze
64+
--braid, -b <percent>: braid to remove deadends
65+
--solve, -s: solve the maze
66+
--print, -p: print maze in the terminal
67+
```
68+
69+
Description of the command line parameters
70+
71+
- width and length allow you to chose the size of your map (defaults to 20).
72+
73+
- Amaze uses various algorithms to generate mazes, so you can chose the one you prefer (defaults to binarytree).
74+
75+
> Please note that **Growing Tree** requires the choice of a sub-algorithm.
76+
> You can chose between `random`, `last` and `mix` (defaults to `random`)
77+
78+
- seed allows you to replay a maze you liked in the past. The current maze's seed is displayed in the console
79+
when the program starts.
80+
81+
- braid allows you to remove none (0%), some (1-99%) or all (100%) of the deadends in the maze
82+
(defaults to 0, keep deadends). Do **not** include the % sign like in `--braid 50`.
83+
84+
- solve allows you to solve the maze, and will display the maze with a color gradient.
85+
The entrance (red dot) will be in lighter colors whereas the exit (green dot) will be in darker colors.
86+
By default, mazes are unresolved, just in case you want to solve them :wink:.
87+
88+
> Please note: in the terminal, only the exit path is highlighted in a gradient of color.
89+
90+
- print allows you to display the maze in the terminal only (no 3D).
91+
92+
## Feedback
93+
94+
Feel free to send feedback, star, etc.

algo/aldousbroder.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package algo
2+
3+
import (
4+
"github.com/fred1268/maze/maze"
5+
)
6+
7+
func AldousBroder(m *maze.Maze) {
8+
cell := m.GetCell(rnd.Int()%m.Width, rnd.Int()%m.Length)
9+
m.Visit(cell)
10+
for {
11+
next := cell.Neighbors()[rnd.Int()%len(cell.Neighbors())]
12+
if !m.Visited(next) {
13+
cell.Link(next)
14+
m.Visit(next)
15+
}
16+
cell = next
17+
if m.IsFullyVisited() {
18+
break
19+
}
20+
}
21+
}

algo/btree.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package algo
2+
3+
import (
4+
"github.com/fred1268/maze/maze"
5+
)
6+
7+
func BinaryTree(m *maze.Maze) {
8+
for y := m.Length - 1; y >= 0; y-- {
9+
for x := 0; x < m.Width; x++ {
10+
cell := m.GetCell(x, y)
11+
if rnd.Int()%2 == 0 {
12+
if !cell.LinkEast() {
13+
cell.LinkNorth()
14+
}
15+
} else {
16+
if !cell.LinkNorth() {
17+
cell.LinkEast()
18+
}
19+
}
20+
}
21+
}
22+
}

algo/growingtree.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package algo
2+
3+
import (
4+
"github.com/fred1268/maze/maze"
5+
"golang.org/x/exp/slices"
6+
)
7+
8+
type choiceFunc func([]*maze.Cell) *maze.Cell
9+
10+
func GTRandom(list []*maze.Cell) *maze.Cell {
11+
return list[rnd.Int()%len(list)]
12+
}
13+
14+
func GTLast(list []*maze.Cell) *maze.Cell {
15+
return list[len(list)-1]
16+
}
17+
18+
func GTMix(list []*maze.Cell) *maze.Cell {
19+
if rnd.Int()%2 == 0 {
20+
return GTRandom(list)
21+
}
22+
return GTLast(list)
23+
}
24+
25+
func GrowingTree(m *maze.Maze, fn choiceFunc) {
26+
var active []*maze.Cell
27+
cell := m.GetCell(rnd.Int()%m.Width, rnd.Int()%m.Length)
28+
active = append(active, cell)
29+
m.Visit(cell)
30+
for {
31+
cell := fn(active)
32+
neighbors := cell.UnvisitedNeighbors()
33+
if len(neighbors) != 0 {
34+
target := neighbors[rnd.Int()%len(neighbors)]
35+
cell.Link(target)
36+
active = append(active, target)
37+
m.Visit(target)
38+
} else {
39+
n := slices.Index(active, cell)
40+
newActive := active[0:n]
41+
newActive = append(newActive, active[n+1:]...)
42+
active = newActive
43+
if len(active) == 0 {
44+
break
45+
}
46+
}
47+
}
48+
}

algo/huntandkill.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package algo
2+
3+
import (
4+
"github.com/fred1268/maze/maze"
5+
)
6+
7+
func huntAndKill_NextCell(m *maze.Maze) *maze.Cell {
8+
var cell *maze.Cell
9+
for y := m.Length - 1; y >= 0; y-- {
10+
for x := 0; x < m.Width; x++ {
11+
cell = m.GetCell(x, y)
12+
if !m.Visited(cell) {
13+
visitedNeighbors := cell.VisitedNeighbors()
14+
if len(visitedNeighbors) != 0 {
15+
c := visitedNeighbors[rnd.Int()%len(visitedNeighbors)]
16+
cell.Link(c)
17+
m.Visit(cell)
18+
return cell
19+
}
20+
}
21+
}
22+
}
23+
return nil
24+
}
25+
26+
func HuntAndKill(m *maze.Maze) {
27+
cell := m.GetCell(rnd.Int()%m.Width, rnd.Int()%m.Length)
28+
m.Visit(cell)
29+
for {
30+
unvisitedNeighbors := cell.UnvisitedNeighbors()
31+
if len(unvisitedNeighbors) != 0 {
32+
next := unvisitedNeighbors[rnd.Int()%len(unvisitedNeighbors)]
33+
cell.Link(next)
34+
m.Visit(next)
35+
cell = next
36+
} else {
37+
cell = huntAndKill_NextCell(m)
38+
}
39+
if m.IsFullyVisited() {
40+
break
41+
}
42+
}
43+
}

algo/path.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package algo
2+
3+
import (
4+
"github.com/fred1268/maze/maze"
5+
)
6+
7+
type path struct {
8+
list []*maze.Cell
9+
all map[*maze.Cell]struct{}
10+
}
11+
12+
func newPath() *path {
13+
return &path{all: make(map[*maze.Cell]struct{})}
14+
}
15+
16+
func (p *path) visit(cell *maze.Cell) {
17+
p.list = append(p.list, cell)
18+
p.all[cell] = struct{}{}
19+
}
20+
21+
func (p *path) backtrackTo(cell *maze.Cell) {
22+
for i := len(p.list) - 1; i >= 0; i-- {
23+
current := p.list[i]
24+
if current == cell {
25+
p.list = p.list[0 : i+1]
26+
break
27+
}
28+
delete(p.all, current)
29+
}
30+
}
31+
32+
func (p *path) backtrack() *maze.Cell {
33+
if len(p.list) == 0 {
34+
return nil
35+
}
36+
cell := p.list[len(p.list)-1]
37+
p.list = p.list[0 : len(p.list)-1]
38+
delete(p.all, cell)
39+
return cell
40+
}
41+
42+
func (p *path) visited(cell *maze.Cell) bool {
43+
_, ok := p.all[cell]
44+
return ok
45+
}
46+
47+
func (p *path) getPath() []*maze.Cell {
48+
return p.list
49+
}

algo/rand.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package algo
2+
3+
import "math/rand"
4+
5+
var rnd *rand.Rand
6+
7+
func SetSeed(seed int64) {
8+
rnd = rand.New(rand.NewSource(seed))
9+
}

algo/recursivebacktracker.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package algo
2+
3+
import (
4+
"github.com/fred1268/maze/maze"
5+
)
6+
7+
func recursiveBacktracker_NextCell(m *maze.Maze, path *path) *maze.Cell {
8+
var prev *maze.Cell
9+
var unvisitedNeighbors []*maze.Cell
10+
for {
11+
prev = path.backtrack()
12+
if prev == nil {
13+
break
14+
}
15+
unvisitedNeighbors = prev.UnvisitedNeighbors()
16+
if len(unvisitedNeighbors) != 0 {
17+
break
18+
}
19+
}
20+
if len(unvisitedNeighbors) != 0 {
21+
next := unvisitedNeighbors[rnd.Int()%len(unvisitedNeighbors)]
22+
prev.Link(next)
23+
return next
24+
}
25+
return nil
26+
}
27+
28+
func RecursiveBacktracker(m *maze.Maze) {
29+
path := newPath()
30+
cell := m.GetCell(rnd.Int()%m.Width, rnd.Int()%m.Length)
31+
m.Visit(cell)
32+
path.visit(cell)
33+
for {
34+
var next *maze.Cell
35+
unvisitedNeighbors := cell.UnvisitedNeighbors()
36+
if len(unvisitedNeighbors) != 0 {
37+
next = unvisitedNeighbors[rnd.Int()%len(unvisitedNeighbors)]
38+
cell.Link(next)
39+
} else {
40+
next = recursiveBacktracker_NextCell(m, path)
41+
}
42+
m.Visit(next)
43+
path.visit(next)
44+
cell = next
45+
if m.IsFullyVisited() {
46+
break
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)