Skip to content

Commit

Permalink
First release
Browse files Browse the repository at this point in the history
  • Loading branch information
Fred committed May 13, 2023
1 parent a4d5ec6 commit b8d310f
Show file tree
Hide file tree
Showing 26 changed files with 1,187 additions and 0 deletions.
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work
.vscode
amaze
94 changes: 94 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# :door: amaze :door:

A program to create, solve and draw mazes in your terminal or in 2D/3D

## Gallery

2D Terminal view

<p align="center">
<img style="float: right;" src="https://github.com/fred1268/amaze/blob/development/readme/terminal_15x15_recursivebacktracker_solved.png" alt="Amaze in Action"/>
</p>
2D Graphical view
<p align="center">
<img style="float: right;" src="https://github.com/fred1268/amaze/blob/development/readme/2D_20x20_sidewinder_solved.png" alt="Amaze in Action"/>
</p>
3D Graphical view
<p align="center">
<img style="float: right;" src="https://github.com/fred1268/amaze/blob/development/readme/3D_50x50_growingtree_solved2.png" alt="Amaze in Action"/>
</p>

---

## Setup

This program uses G3n, so it has [the same prerequisites](https://github.com/g3n/engine#dependencies).
It is pretty straightforward, since most of the computers nowadays, have an OpenGL driver and a C
compiler. I tested both on Linux (manjaro) and mac M1.

> Also, note that Amaze uses [**clap :clap:, the Command Line Argument Parser**](https://github.com/fred1268/go-clap).
> clap is a non intrusive, lightweight command line parsing library you may want to try out in your
> own projects. Feel free to give it a try!
---

## Installation

```
git clone github.com/fred1268/amaze
cd amaze
go install
```

---

## Running the program

You can run the program with several flags, although it has decent defaults. Here are the available flags:

```
amaze: creates, solves and displays mazes
--width, -w <width>: set maze's width
--length, -l <length>: set maze's length
--algo, -a <name>: choose the algorithm used to create the maze:
binarytree
sidewinder
aldousbroder
wilson
huntandkill
recursivebacktracker
growingtree
use --choice, -c to chose sub algorithm:
random, last, or mix
--seed, -d <seed>: use seed to generate identical maze
--braid, -b <percent>: braid to remove deadends
--solve, -s: solve the maze
--print, -p: print maze in the terminal
```

Description of the command line parameters

- width and length allow you to chose the size of your map (defaults to 20).

- Amaze uses various algorithms to generate mazes, so you can chose the one you prefer (defaults to binarytree).

> Please note that **Growing Tree** requires the choice of a sub-algorithm.
> You can chose between `random`, `last` and `mix` (defaults to `random`)
- seed allows you to replay a maze you liked in the past. The current maze's seed is displayed in the console
when the program starts.

- braid allows you to remove none (0%), some (1-99%) or all (100%) of the deadends in the maze
(defaults to 0, keep deadends). Do **not** include the % sign like in `--braid 50`.

- solve allows you to solve the maze, and will display the maze with a color gradient.
The entrance (red dot) will be in lighter colors whereas the exit (green dot) will be in darker colors.
By default, mazes are unresolved, just in case you want to solve them :wink:.

> Please note: in the terminal, only the exit path is highlighted in a gradient of color.
- print allows you to display the maze in the terminal only (no 3D).

## Feedback

Feel free to send feedback, star, etc.
21 changes: 21 additions & 0 deletions algo/aldousbroder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package algo

import (
"github.com/fred1268/maze/maze"
)

func AldousBroder(m *maze.Maze) {
cell := m.GetCell(rnd.Int()%m.Width, rnd.Int()%m.Length)
m.Visit(cell)
for {
next := cell.Neighbors()[rnd.Int()%len(cell.Neighbors())]
if !m.Visited(next) {
cell.Link(next)
m.Visit(next)
}
cell = next
if m.IsFullyVisited() {
break
}
}
}
22 changes: 22 additions & 0 deletions algo/btree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package algo

import (
"github.com/fred1268/maze/maze"
)

func BinaryTree(m *maze.Maze) {
for y := m.Length - 1; y >= 0; y-- {
for x := 0; x < m.Width; x++ {
cell := m.GetCell(x, y)
if rnd.Int()%2 == 0 {
if !cell.LinkEast() {
cell.LinkNorth()
}
} else {
if !cell.LinkNorth() {
cell.LinkEast()
}
}
}
}
}
48 changes: 48 additions & 0 deletions algo/growingtree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package algo

import (
"github.com/fred1268/maze/maze"
"golang.org/x/exp/slices"
)

type choiceFunc func([]*maze.Cell) *maze.Cell

func GTRandom(list []*maze.Cell) *maze.Cell {
return list[rnd.Int()%len(list)]
}

func GTLast(list []*maze.Cell) *maze.Cell {
return list[len(list)-1]
}

func GTMix(list []*maze.Cell) *maze.Cell {
if rnd.Int()%2 == 0 {
return GTRandom(list)
}
return GTLast(list)
}

func GrowingTree(m *maze.Maze, fn choiceFunc) {
var active []*maze.Cell
cell := m.GetCell(rnd.Int()%m.Width, rnd.Int()%m.Length)
active = append(active, cell)
m.Visit(cell)
for {
cell := fn(active)
neighbors := cell.UnvisitedNeighbors()
if len(neighbors) != 0 {
target := neighbors[rnd.Int()%len(neighbors)]
cell.Link(target)
active = append(active, target)
m.Visit(target)
} else {
n := slices.Index(active, cell)
newActive := active[0:n]
newActive = append(newActive, active[n+1:]...)
active = newActive
if len(active) == 0 {
break
}
}
}
}
43 changes: 43 additions & 0 deletions algo/huntandkill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package algo

import (
"github.com/fred1268/maze/maze"
)

func huntAndKill_NextCell(m *maze.Maze) *maze.Cell {
var cell *maze.Cell
for y := m.Length - 1; y >= 0; y-- {
for x := 0; x < m.Width; x++ {
cell = m.GetCell(x, y)
if !m.Visited(cell) {
visitedNeighbors := cell.VisitedNeighbors()
if len(visitedNeighbors) != 0 {
c := visitedNeighbors[rnd.Int()%len(visitedNeighbors)]
cell.Link(c)
m.Visit(cell)
return cell
}
}
}
}
return nil
}

func HuntAndKill(m *maze.Maze) {
cell := m.GetCell(rnd.Int()%m.Width, rnd.Int()%m.Length)
m.Visit(cell)
for {
unvisitedNeighbors := cell.UnvisitedNeighbors()
if len(unvisitedNeighbors) != 0 {
next := unvisitedNeighbors[rnd.Int()%len(unvisitedNeighbors)]
cell.Link(next)
m.Visit(next)
cell = next
} else {
cell = huntAndKill_NextCell(m)
}
if m.IsFullyVisited() {
break
}
}
}
49 changes: 49 additions & 0 deletions algo/path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package algo

import (
"github.com/fred1268/maze/maze"
)

type path struct {
list []*maze.Cell
all map[*maze.Cell]struct{}
}

func newPath() *path {
return &path{all: make(map[*maze.Cell]struct{})}
}

func (p *path) visit(cell *maze.Cell) {
p.list = append(p.list, cell)
p.all[cell] = struct{}{}
}

func (p *path) backtrackTo(cell *maze.Cell) {
for i := len(p.list) - 1; i >= 0; i-- {
current := p.list[i]
if current == cell {
p.list = p.list[0 : i+1]
break
}
delete(p.all, current)
}
}

func (p *path) backtrack() *maze.Cell {
if len(p.list) == 0 {
return nil
}
cell := p.list[len(p.list)-1]
p.list = p.list[0 : len(p.list)-1]
delete(p.all, cell)
return cell
}

func (p *path) visited(cell *maze.Cell) bool {
_, ok := p.all[cell]
return ok
}

func (p *path) getPath() []*maze.Cell {
return p.list
}
9 changes: 9 additions & 0 deletions algo/rand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package algo

import "math/rand"

var rnd *rand.Rand

func SetSeed(seed int64) {
rnd = rand.New(rand.NewSource(seed))
}
49 changes: 49 additions & 0 deletions algo/recursivebacktracker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package algo

import (
"github.com/fred1268/maze/maze"
)

func recursiveBacktracker_NextCell(m *maze.Maze, path *path) *maze.Cell {
var prev *maze.Cell
var unvisitedNeighbors []*maze.Cell
for {
prev = path.backtrack()
if prev == nil {
break
}
unvisitedNeighbors = prev.UnvisitedNeighbors()
if len(unvisitedNeighbors) != 0 {
break
}
}
if len(unvisitedNeighbors) != 0 {
next := unvisitedNeighbors[rnd.Int()%len(unvisitedNeighbors)]
prev.Link(next)
return next
}
return nil
}

func RecursiveBacktracker(m *maze.Maze) {
path := newPath()
cell := m.GetCell(rnd.Int()%m.Width, rnd.Int()%m.Length)
m.Visit(cell)
path.visit(cell)
for {
var next *maze.Cell
unvisitedNeighbors := cell.UnvisitedNeighbors()
if len(unvisitedNeighbors) != 0 {
next = unvisitedNeighbors[rnd.Int()%len(unvisitedNeighbors)]
cell.Link(next)
} else {
next = recursiveBacktracker_NextCell(m, path)
}
m.Visit(next)
path.visit(next)
cell = next
if m.IsFullyVisited() {
break
}
}
}
Loading

0 comments on commit b8d310f

Please sign in to comment.