Task 15.3 - new approach: generators/maze/maze_generator (WiP)

This commit is contained in:
thomashaw
2018-08-17 17:16:30 +01:00
committed by ts
parent 5f3d4197e1
commit 66b01fb35b
78 changed files with 342 additions and 1825 deletions

View File

@@ -62,7 +62,7 @@ define secgen_functions::install_setgid_script (
owner => 'root',
group => $grp,
mode => '2775',
content => $script_data[0],
content => $script_data,
require => Group[$grp],
}

View File

@@ -6,7 +6,7 @@ class ruby_challenge_example::install {
source_module_name => $module_name,
challenge_name => $challenge_name,
script_name => 'test.rb',
script_data => $secgen_params['script_data'],
script_data => $secgen_params['script_data'][0],
group => $secgen_params['group'],
account => $secgen_params['account'],
flag => $secgen_params['flag'],

View File

@@ -6,7 +6,7 @@ class math_challenge::install {
source_module_name => $module_name,
challenge_name => $challenge_name,
script_name => "$challenge_name.rb",
script_data => $secgen_params['script_data'],
script_data => $secgen_params['script_data'][0],
group => $secgen_params['group'],
account => $secgen_params['account'],
flag => $secgen_params['flag'],

View File

@@ -1,4 +0,0 @@
/build
/goxz
.golint.txt
/vendor

View File

@@ -1,29 +0,0 @@
sudo: false
language: go
go:
- 1.9
env: PATH=/home/travis/gopath/bin:$PATH
script:
- make test
- make lint
- make cross
deploy:
provider: releases
skip_cleanup: true
api_key:
secure: El/qxSDFFxk0KCI4e7ed6Jyau6Lw4u8h9CejN1ke2WHez6Nyv9f41uEM+EQFxp/mfdVEAlkjAA/A+nknWP9gJeANqhCpmEaFQ5J3eaW2nECd8fglclqdVP9NZ7Fbl4qTKsQpD7xYfFUk+P++4dVKZXuutNXT9gPUjVbzOx/tCnl1CwktOEFPEpv9fy64vD00ywUg1SaJgSl5dqA2RGunNSh6hr+pELXYjQ0uQzjMG+Gpknga0EWQEZleOXspw0ClUTrMYuqTooM/8yFfmG5d/p/8wQCCvyIBEQYCSOoOL+li7CBO26GnBvuPxMTvEzQ+0jjOxaEEgc+hOLo1elB+NbxT9Wz5NkJYbkmSUSfk9OZ4vmcvRZbQR9aiqZTeLVB8x6ln/Xzk45zeORcjRQI0fXd5k/dYRLKxKc3DQcXpa3ASzM8VXjx4p3YjcmqMvt+KtKhFL+DbSGxsxjuR6ulk3QJtuM3hnnsRkz58VTZunFsknRr3b0s8pHBYpGw+wLwWJ+IOrk0GBl0MBV705upoi+8xJiqZ7LPvkAe3XRDSV2jPZUyvn5+gzTQ597zTk3zZrpzQ0809UdQRWWkQYF7udozQ4K9+qk34j8Z2IAykgn+ClRmgTdiH87DAIBsZmeSEMjvripBMs5Vkbe3ijnyKuGPREjZ4bgEKrNVl9Y5yNg0=
file:
- goxz/maze_darwin_386.zip
- goxz/maze_darwin_amd64.zip
- goxz/maze_freebsd_386.tar.gz
- goxz/maze_freebsd_amd64.tar.gz
- goxz/maze_linux_386.tar.gz
- goxz/maze_linux_amd64.tar.gz
- goxz/maze_netbsd_386.tar.gz
- goxz/maze_netbsd_amd64.tar.gz
- goxz/maze_windows_386.zip
- goxz/maze_windows_amd64.zip
on:
repo: itchyny/maze
tags: true
all_branches: false

View File

@@ -1,39 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/codegangsta/cli"
packages = ["."]
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
version = "v1.20.0"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
name = "github.com/mattn/go-runewidth"
packages = ["."]
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
version = "v0.0.2"
[[projects]]
branch = "master"
name = "github.com/nsf/termbox-go"
packages = ["."]
revision = "aa4a75b1c20a2b03751b1a9f7e41d58bd6f71c43"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "83801418e1b59fb1880e363299581ee543af32ca"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "7ae4b9c97302a35f895e8e4aeaa1bfef36b827a9099b7ab07af89162f5abdee3"
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -1,34 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/codegangsta/cli"
version = "1.20.0"
[[constraint]]
name = "github.com/mattn/go-isatty"
version = "0.0.3"
[[constraint]]
branch = "master"
name = "github.com/nsf/termbox-go"

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 itchyny
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,41 +0,0 @@
BIN = maze
DIR = ./cmd/maze
GOROOT ?= /usr/local/go
all: clean test build
build: deps
go build -o build/$(BIN) $(DIR)
install: deps
go install ./...
deps:
go get -u github.com/golang/dep/cmd/dep
dep ensure
cross: crossdeps
goxz -os=linux,darwin,freebsd,netbsd,windows -arch=386,amd64 -n $(BIN) $(DIR)
crossdeps: deps
go get github.com/Songmu/goxz/cmd/goxz
test: testdeps build
go test -v $(DIR)...
testdeps:
go get -d -v -t ./...
lint: lintdeps build
go vet
golint -set_exit_status $(go list ./... | grep -v /vendor/)
lintdeps:
go get -d -v -t ./...
go get -u github.com/golang/lint/golint
clean:
rm -rf build goxz
go clean
.PHONY: build install deps cross crossdeps test testdeps lint lintdeps clean

View File

@@ -1,60 +0,0 @@
# maze [![Travis Build Status](https://travis-ci.org/itchyny/maze.svg?branch=master)](https://travis-ci.org/itchyny/maze)
![maze](https://raw.githubusercontent.com/wiki/itchyny/maze/image/maze1.gif)
## Usage
The `maze` command without the arguments prints the random maze to the standard output.
```sh
maze
```
![maze](https://raw.githubusercontent.com/wiki/itchyny/maze/image/maze6.gif)
We can play the maze on the terminal with `--interactive`.
```sh
maze --interactive
```
![maze](https://raw.githubusercontent.com/wiki/itchyny/maze/image/maze2.gif)
The `--format color` is a good option to print the colored maze. Also we can specify the size of the maze with `--width` and `--height`.
```sh
maze --width 20 --height 10 --format color
```
![maze](https://raw.githubusercontent.com/wiki/itchyny/maze/image/maze3.gif)
We can toggle the solution with the `s` key.
![maze](https://raw.githubusercontent.com/wiki/itchyny/maze/image/maze4.gif)
If we change the font size of the terminal smaller, we get a large maze.
![maze](https://raw.githubusercontent.com/wiki/itchyny/maze/image/maze5.gif)
## Installation
### Homebrew
```bash
$ brew install itchyny/maze/maze
```
### Download binary from GitHub Releases
[Releases・itchyny/maze - GitHub](https://github.com/itchyny/maze/releases)
### Build from source
```bash
$ go get -u github.com/itchyny/maze/cmd/maze
```
## Bug Tracker
Report bug at [Issues・itchyny/maze - GitHub](https://github.com/itchyny/maze/issues).
## Author
itchyny (https://github.com/itchyny)
## License
This software is released under the MIT License, see LICENSE.
## Special thanks
Special thanks to the [termbox-go](https://github.com/nsf/termbox-go) library.
## References
- [Maze generation algorithm - Wikipedia, the free encyclopedia](https://en.wikipedia.org/wiki/Maze_generation_algorithm)
- [Maze solving algorithm - Wikipedia, the free encyclopedia](https://en.wikipedia.org/wiki/Maze_solving_algorithm)
- [lunixbochs/maze: Maze generation and salvation](https://github.com/lunixbochs/maze)
- [willfrew/maze-generation: Some maze generation algorithms written in Go](https://github.com/willfrew/maze-generation)

View File

@@ -1,62 +0,0 @@
package main
import (
"fmt"
"math/rand"
"os"
"github.com/codegangsta/cli"
"github.com/itchyny/maze"
"github.com/nsf/termbox-go"
)
func action(ctx *cli.Context) error {
err := termbox.Init()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
return nil
}
config, errors := makeConfig(ctx)
if errors != nil {
hasErr := false
termbox.Close()
for _, err := range errors {
if err.Error() != "" {
fmt.Fprintf(os.Stderr, err.Error()+"\n")
hasErr = true
}
}
if hasErr {
fmt.Fprintf(os.Stderr, "\n")
}
cli.ShowAppHelp(ctx)
return nil
}
maze := createMaze(config)
if config.Interactive {
defer termbox.Close()
interactive(maze, config.Format)
} else {
termbox.Close()
if config.Image {
maze.PrintImage(config.Output, config.Format, config.Scale)
} else {
maze.Print(config.Output, config.Format)
}
}
return nil
}
func createMaze(config *Config) *maze.Maze {
rand.Seed(config.Seed)
maze := maze.NewMaze(config.Height, config.Width)
maze.Start = config.Start
maze.Goal = config.Goal
maze.Cursor = config.Start
maze.Generate()
if config.Solution {
maze.Solve()
}
return maze
}

View File

@@ -1,137 +0,0 @@
package main
import (
"errors"
"io"
"os"
"strconv"
"strings"
"time"
"github.com/codegangsta/cli"
"github.com/itchyny/maze"
"github.com/mattn/go-isatty"
"github.com/nsf/termbox-go"
)
// Config is the command configuration
type Config struct {
Width int
Height int
Start *maze.Point
Goal *maze.Point
Interactive bool
Image bool
Scale int
Solution bool
Format *maze.Format
Seed int64
Output io.Writer
}
func makeConfig(ctx *cli.Context) (*Config, []error) {
var errs []error
if ctx.GlobalBool("help") {
errs = append(errs, errors.New(""))
return nil, errs
}
termWidth, termHeight := termbox.Size()
width := ctx.GlobalInt("width")
if width <= 0 {
width = (termWidth - 4) / 4
}
height := ctx.GlobalInt("height")
if height <= 0 {
height = (termHeight - 5) / 2
}
start := &maze.Point{0, 0}
starts := strings.Split(ctx.GlobalString("start"), ",")
if len(starts) > 0 {
if value, err := strconv.Atoi(starts[0]); err == nil {
if 0 <= value && value < height {
start.X = value
}
}
}
if len(starts) > 1 {
if value, err := strconv.Atoi(starts[1]); err == nil {
if 0 <= value && value < width {
start.Y = value
}
}
}
goal := &maze.Point{height - 1, width - 1}
goals := strings.Split(ctx.GlobalString("goal"), ",")
if len(goals) > 0 {
if value, err := strconv.Atoi(goals[0]); err == nil {
if 0 <= value && value < height {
goal.X = value
}
}
}
if len(goals) > 1 {
if value, err := strconv.Atoi(goals[1]); err == nil {
if 0 <= value && value < width {
goal.Y = value
}
}
}
interactive := ctx.GlobalBool("interactive")
solution := ctx.GlobalBool("solution")
format := maze.Default
if ctx.GlobalString("format") == "color" {
format = maze.Color
}
output := ctx.App.Writer
outfile := ctx.GlobalString("output")
if outfile != "" {
file, err := os.Create(outfile)
if err != nil {
errs = append(errs, errors.New("cannot create the output file: "+outfile))
} else {
output = file
}
}
image := ctx.GlobalBool("image")
if image {
if file, ok := output.(*os.File); ok && isatty.IsTerminal(file.Fd()) {
errs = append(errs, errors.New("cannot write binary data into the terminal\nuse -output flag"))
}
}
scale := ctx.GlobalInt("scale")
seed := int64(ctx.GlobalInt("seed"))
if !ctx.IsSet("seed") {
seed = time.Now().UnixNano()
}
if len(errs) > 0 {
return nil, errs
}
return &Config{
Width: width,
Height: height,
Start: start,
Goal: goal,
Interactive: interactive,
Image: image,
Scale: scale,
Solution: solution,
Format: format,
Seed: seed,
Output: output,
}, nil
}

View File

@@ -1,57 +0,0 @@
package main
import (
"github.com/codegangsta/cli"
)
var flags = []cli.Flag{
cli.StringFlag{
Name: "width",
Usage: "The width of the maze",
},
cli.StringFlag{
Name: "height",
Usage: "The height of the maze",
},
cli.StringFlag{
Name: "start",
Usage: "The start coordinate",
},
cli.StringFlag{
Name: "goal",
Usage: "The goal coordinate",
},
cli.BoolFlag{
Name: "interactive",
Usage: "Play the maze interactively",
},
cli.BoolFlag{
Name: "solution",
Usage: "Print the maze with the solution",
},
cli.StringFlag{
Name: "format",
Usage: "Output format, `default` or `color`",
},
cli.StringFlag{
Name: "output, o",
Usage: "Output file name",
},
cli.BoolFlag{
Name: "image",
Usage: "Generate image",
},
cli.IntFlag{
Name: "scale",
Usage: "Scale of the image",
Value: 1,
},
cli.StringFlag{
Name: "seed",
Usage: "The random seed",
},
cli.BoolFlag{
Name: "help, h",
Usage: "Shows the help of the command",
},
}

View File

@@ -1,153 +0,0 @@
package main
import (
"fmt"
"time"
"unicode"
"github.com/itchyny/maze"
"github.com/nsf/termbox-go"
)
type keyDir struct {
key termbox.Key
char rune
dir int
}
var keyDirs = []*keyDir{
{termbox.KeyArrowUp, 'k', maze.Up},
{termbox.KeyArrowDown, 'j', maze.Down},
{termbox.KeyArrowLeft, 'h', maze.Left},
{termbox.KeyArrowRight, 'l', maze.Right},
}
func interactive(maze *maze.Maze, format *maze.Format) {
events := make(chan termbox.Event)
go func() {
for {
events <- termbox.PollEvent()
}
}()
strwriter := make(chan string)
ticker := time.NewTicker(10 * time.Millisecond)
go printTermbox(maze, strwriter, time.Now())
maze.Started = true
maze.Write(strwriter, format)
loop:
for {
select {
case event := <-events:
if event.Type == termbox.EventKey {
if !maze.Finished {
for _, keydir := range keyDirs {
if event.Key == keydir.key || event.Ch == keydir.char {
maze.Move(keydir.dir)
if maze.Finished {
maze.Solve()
}
maze.Write(strwriter, format)
continue loop
}
}
if event.Key == termbox.KeyCtrlZ || event.Ch == 'u' {
maze.Undo()
maze.Write(strwriter, format)
} else if event.Ch == 's' {
if maze.Solved {
maze.Clear()
} else {
maze.Solve()
}
maze.Write(strwriter, format)
}
}
if event.Ch == 'q' || event.Ch == 'Q' || event.Key == termbox.KeyCtrlC || event.Key == termbox.KeyCtrlD {
break loop
}
}
case <-ticker.C:
if !maze.Finished {
strwriter <- "\u0000"
}
}
}
ticker.Stop()
}
func printTermbox(maze *maze.Maze, strwriter chan string, start time.Time) {
x, y := 1, 0
for {
str := <-strwriter
switch str {
case "\u0000":
printFinished(maze, time.Now().Sub(start))
termbox.Flush()
x, y = 1, 0
default:
printString(str, &x, &y)
}
}
}
func printString(str string, x *int, y *int) {
attr, skip, d0, d1, d := false, false, '0', '0', false
fg, bg := termbox.ColorDefault, termbox.ColorDefault
for _, c := range str {
if c == '\n' {
*x, *y = (*x)+1, 0
} else if c == '\x1b' || attr && c == '[' {
attr = true
} else if attr && unicode.IsDigit(c) {
if !skip {
if d {
d1 = c
} else {
d0, d = c, true
}
}
} else if attr && c == ';' {
skip = true
} else if attr && c == 'm' {
if d0 == '7' && d1 == '0' {
fg, bg = termbox.AttrReverse, termbox.AttrReverse
} else if d0 == '3' {
fg, bg = termbox.Attribute(uint64(d1-'0'+1)), termbox.ColorDefault
} else if d0 == '4' {
fg, bg = termbox.ColorDefault, termbox.Attribute(uint64(d1-'0'+1))
} else {
fg, bg = termbox.ColorDefault, termbox.ColorDefault
}
attr, skip, d0, d1, d = false, false, '0', '0', false
} else {
termbox.SetCell(*y, *x, c, fg, bg)
*y = *y + 1
}
}
}
func printFinished(maze *maze.Maze, duration time.Duration) {
str := fmt.Sprintf("%8d.%02ds ", int64(duration/time.Second), int64((duration%time.Second)/1e7))
fg, bg := termbox.ColorDefault, termbox.ColorDefault
if maze.Finished {
x, y := maze.Height, 2*maze.Width-6
if y < 0 {
y = 0
}
for j, s := range []string{
" ",
" Finished! ",
str,
" ",
" Press q to quit ",
" "} {
for i, c := range s {
termbox.SetCell(y+i, x+j, c, fg, bg)
}
}
} else {
for i, c := range str {
termbox.SetCell(4*maze.Width+i-8, 1, c, fg, bg)
}
}
}

View File

@@ -1,14 +0,0 @@
package main
import (
"os"
)
var name = "maze"
var version = "v0.0.2"
var description = "Maze generating and solving program"
var author = "itchyny"
func main() {
os.Exit(run(os.Args))
}

View File

@@ -1,39 +0,0 @@
package main
import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestMain(t *testing.T) {
build, _ := filepath.Abs("../build")
filepath.Walk("../test", func(path string, info os.FileInfo, err error) error {
if strings.HasSuffix(path, ".sh") {
cmd := exec.Command("bash", filepath.Base(path))
cmd.Dir = filepath.Dir(path)
cmd.Env = append(os.Environ(), "PATH="+build+":"+"/bin")
stderr := new(bytes.Buffer)
cmd.Stderr = stderr
output, err := cmd.Output()
if err != nil {
t.Errorf("FAIL: execution failed: " + path + ": " + err.Error() + " " + stderr.String())
} else {
outfile := strings.TrimSuffix(path, filepath.Ext(path)) + ".txt"
expected, err := ioutil.ReadFile(outfile)
if err != nil {
t.Errorf("FAIL: error on reading output file: " + outfile)
} else if strings.HasPrefix(string(output), strings.TrimSuffix(string(expected), "\n")) {
t.Logf("PASS: " + path + "\n")
} else {
t.Errorf("FAIL: output differs: " + path + "\n")
}
}
}
return nil
})
}

View File

@@ -1,26 +0,0 @@
package main
import (
"github.com/codegangsta/cli"
)
func run(args []string) int {
app := newApp()
if app.Run(args) != nil {
return 1
}
return 0
}
func newApp() *cli.App {
app := cli.NewApp()
app.Name = name
app.HelpName = name
app.Usage = description
app.Version = version
app.Author = author
app.Flags = flags
app.HideHelp = true
app.Action = action
return app
}

View File

@@ -1,488 +0,0 @@
package maze
import (
"bytes"
"fmt"
"image"
"image/color"
"image/png"
"io"
"math/rand"
"strings"
)
// Maze cell configurations
// The paths of the maze is represented in the binary representation.
const (
Up = 1 << iota
Down
Left
Right
)
// The solution path is represented by (Up|Down|Left|Right) << SolutionOffset.
// The user's path is represented by (Up|Down|Left|Right) << VisitedOffset.
const (
SolutionOffset = 4
VisitedOffset = 8
)
// Directions is the set of all the directions
var Directions = []int{Up, Down, Left, Right}
// The differences in the x-y coordinate
var dx = map[int]int{Up: -1, Down: 1, Left: 0, Right: 0}
var dy = map[int]int{Up: 0, Down: 0, Left: -1, Right: 1}
// Opposite directions
var Opposite = map[int]int{Up: Down, Down: Up, Left: Right, Right: Left}
// Point on the maze
type Point struct {
X, Y int
}
// Equal judges the equality of the two points
func (point *Point) Equal(target *Point) bool {
return point.X == target.X && point.Y == target.Y
}
// Advance the point forward by the argument direction
func (point *Point) Advance(direction int) *Point {
return &Point{point.X + dx[direction], point.Y + dy[direction]}
}
// Maze represents the configuration of a maze
type Maze struct {
Directions [][]int
Height int
Width int
Start *Point
Goal *Point
Cursor *Point
Solved bool
Started bool
Finished bool
}
// NewMaze creates a new maze
func NewMaze(height int, width int) *Maze {
var directions [][]int
for x := 0; x < height; x++ {
directions = append(directions, make([]int, width))
}
return &Maze{directions, height, width, &Point{0, 0}, &Point{height - 1, width - 1}, &Point{0, 0}, false, false, false}
}
// Contains judges whether the argument point is inside the maze or not
func (maze *Maze) Contains(point *Point) bool {
return 0 <= point.X && point.X < maze.Height && 0 <= point.Y && point.Y < maze.Width
}
// Neighbors gathers the nearest undecided points
func (maze *Maze) Neighbors(point *Point) (neighbors []int) {
for _, direction := range Directions {
next := point.Advance(direction)
if maze.Contains(next) && maze.Directions[next.X][next.Y] == 0 {
neighbors = append(neighbors, direction)
}
}
return neighbors
}
// Connected judges whether the two points is connected by a path on the maze
func (maze *Maze) Connected(point *Point, target *Point) bool {
dir := maze.Directions[point.X][point.Y]
for _, direction := range Directions {
if dir&direction != 0 {
next := point.Advance(direction)
if next.X == target.X && next.Y == target.Y {
return true
}
}
}
return false
}
// Next advances the maze path randomly and returns the new point
func (maze *Maze) Next(point *Point) *Point {
neighbors := maze.Neighbors(point)
if len(neighbors) == 0 {
return nil
}
direction := neighbors[rand.Int()%len(neighbors)]
maze.Directions[point.X][point.Y] |= direction
next := point.Advance(direction)
maze.Directions[next.X][next.Y] |= Opposite[direction]
return next
}
// Generate the maze
func (maze *Maze) Generate() {
point := maze.Start
stack := []*Point{point}
for len(stack) > 0 {
for {
point = maze.Next(point)
if point == nil {
break
}
stack = append(stack, point)
}
i := rand.Int() % ((len(stack) + 1) / 2)
point = stack[i]
stack = append(stack[:i], stack[i+1:]...)
}
}
// Solve the maze
func (maze *Maze) Solve() {
if maze.Solved {
return
}
point := maze.Start
stack := []*Point{point}
solution := []*Point{point}
visited := 1 << 12
// Repeat until we reach the goal
for !point.Equal(maze.Goal) {
maze.Directions[point.X][point.Y] |= visited
for _, direction := range Directions {
// Push the nearest points to the stack if not been visited yet
if maze.Directions[point.X][point.Y]&direction == direction {
next := point.Advance(direction)
if maze.Directions[next.X][next.Y]&visited == 0 {
stack = append(stack, next)
}
}
}
// Pop the stack
point = stack[len(stack)-1]
stack = stack[:len(stack)-1]
// We have reached to a dead end so we pop the solution
for last := solution[len(solution)-1]; !maze.Connected(point, last); {
solution = solution[:len(solution)-1]
last = solution[len(solution)-1]
}
solution = append(solution, point)
}
// Fill the solution path on the maze
for i, point := range solution {
if i < len(solution)-1 {
next := solution[i+1]
for _, direction := range Directions {
if maze.Directions[point.X][point.Y]&direction == direction {
temp := point.Advance(direction)
if next.X == temp.X && next.Y == temp.Y {
maze.Directions[point.X][point.Y] |= direction << SolutionOffset
maze.Directions[next.X][next.Y] |= Opposite[direction] << SolutionOffset
break
}
}
}
}
}
maze.Solved = true
}
// Clear the solution
func (maze *Maze) Clear() {
all := Up | Down | Left | Right
all |= all << VisitedOffset // Do not clear the user's path
for _, directions := range maze.Directions {
for j := range directions {
directions[j] &= all
}
}
maze.Solved = false
}
// Move the cursor
func (maze *Maze) Move(direction int) {
point := maze.Cursor
next := point.Advance(direction)
// If there's a path on the maze, we can move the cursor
if maze.Contains(next) && maze.Directions[point.X][point.Y]&direction == direction {
maze.Directions[point.X][point.Y] ^= direction << VisitedOffset
maze.Directions[next.X][next.Y] ^= Opposite[direction] << VisitedOffset
maze.Cursor = next
}
maze.Started = true
// Check if the cursor has reached the goal or not
maze.Finished = maze.Cursor.Equal(maze.Goal)
}
// Undo the visited path
func (maze *Maze) Undo() {
point := maze.Cursor
next := point
for {
// Find the previous point
for _, direction := range Directions {
if (maze.Directions[point.X][point.Y]>>VisitedOffset)&direction != 0 {
next = point.Advance(direction)
maze.Directions[point.X][point.Y] ^= direction << VisitedOffset
maze.Directions[next.X][next.Y] ^= Opposite[direction] << VisitedOffset
break
}
}
if point.Equal(next) {
// Previous point was not found (for example: the start point)
break
} else {
// Move backward
point = next
// If there's another path which has not been visited, stop the procedure
count := 0
for _, direction := range Directions {
if maze.Directions[next.X][next.Y]&direction != 0 {
count = count + 1
}
}
// The path we came from, we visited once and another
if count > 2 {
break
}
}
}
// Move back the cursor
maze.Cursor = point
maze.Finished = maze.Cursor.Equal(maze.Goal)
}
// Format is the printing format of the maze
type Format struct {
Wall string
Path string
StartLeft string
StartRight string
GoalLeft string
GoalRight string
Solution string
SolutionStartLeft string
SolutionStartRight string
SolutionGoalLeft string
SolutionGoalRight string
Visited string
VisitedStartLeft string
VisitedStartRight string
VisitedGoalLeft string
VisitedGoalRight string
Cursor string
}
// Default format
var Default = &Format{
Wall: "##",
Path: " ",
StartLeft: "S ",
StartRight: " S",
GoalLeft: "G ",
GoalRight: " G",
Solution: "::",
SolutionStartLeft: "S:",
SolutionStartRight: ":S",
SolutionGoalLeft: "G:",
SolutionGoalRight: ":G",
Visited: "..",
VisitedStartLeft: "S.",
VisitedStartRight: ".S",
VisitedGoalLeft: "G.",
VisitedGoalRight: ".G",
Cursor: "::",
}
// Color format
var Color = &Format{
Wall: "\x1b[7m \x1b[0m",
Path: " ",
StartLeft: "S ",
StartRight: " S",
GoalLeft: "G ",
GoalRight: " G",
Solution: "\x1b[44;1m \x1b[0m",
SolutionStartLeft: "\x1b[44;1mS \x1b[0m",
SolutionStartRight: "\x1b[44;1m S\x1b[0m",
SolutionGoalLeft: "\x1b[44;1mG \x1b[0m",
SolutionGoalRight: "\x1b[44;1m G\x1b[0m",
Visited: "\x1b[42;1m \x1b[0m",
VisitedStartLeft: "\x1b[42;1mS \x1b[0m",
VisitedStartRight: "\x1b[42;1m S\x1b[0m",
VisitedGoalLeft: "\x1b[42;1mG \x1b[0m",
VisitedGoalRight: "\x1b[42;1m G\x1b[0m",
Cursor: "\x1b[43;1m \x1b[0m",
}
func plot(img *image.RGBA, x, y, scale int, c color.Color) {
for dy := 0; dy < scale; dy++ {
for dx := 0; dx < scale; dx++ {
img.Set(x*scale+dx, y*scale+dy, c)
}
}
}
// PrintImage outputs the maze to the IO writer as PNG image
func (maze *Maze) PrintImage(writer io.Writer, format *Format, scale int) {
var buf bytes.Buffer
maze.Print(&buf, format)
lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
for i, line := range lines {
lines[i] = strings.TrimSpace(line)
}
width := len(lines[0]) / 2
height := len(lines)
img := image.NewRGBA(image.Rect(0, 0, width*scale, height*scale))
red, green, yellow :=
color.RGBA{255, 0, 0, 255},
color.RGBA{0, 255, 0, 255},
color.RGBA{255, 255, 0, 255}
for y := 0; y < height; y++ {
if y >= len(lines) {
continue
}
for x := 0; x < width; x++ {
if x*2 >= len(lines[y]) {
continue
}
switch lines[y][x*2 : x*2+2] {
case "##":
plot(img, x, y, scale, color.Black)
case "::":
plot(img, x, y, scale, yellow)
case "S ", " S", "S:", ":S":
plot(img, x, y, scale, red)
case "G ", " G", "G:", ":G":
plot(img, x, y, scale, green)
default:
plot(img, x, y, scale, color.White)
}
}
}
png.Encode(writer, img)
}
// Print out the maze to the IO writer
func (maze *Maze) Print(writer io.Writer, format *Format) {
strwriter := make(chan string)
go maze.Write(strwriter, format)
for {
str := <-strwriter
switch str {
case "\u0000":
return
default:
fmt.Fprint(writer, str)
}
}
}
// Write out the maze to the writer channel
func (maze *Maze) Write(writer chan string, format *Format) {
// If solved or started, it changes the appearance of the start and the goal
startLeft := format.StartLeft
if maze.Solved {
startLeft = format.SolutionStartLeft
} else if maze.Started {
startLeft = format.VisitedStartLeft
}
startRight := format.StartRight
if maze.Solved {
startRight = format.SolutionStartRight
} else if maze.Started {
startRight = format.VisitedStartRight
}
goalLeft := format.GoalLeft
if maze.Solved {
goalLeft = format.SolutionGoalLeft
} else if maze.Finished {
goalLeft = format.VisitedGoalLeft
}
goalRight := format.GoalRight
if maze.Solved {
goalRight = format.SolutionGoalRight
} else if maze.Finished {
goalRight = format.VisitedGoalRight
}
// We can use & to check if the direction is the solution path or the path user has visited
solved := (Up | Down | Left | Right) << SolutionOffset
visited := (Up | Down | Left | Right) << VisitedOffset
// Print out the maze
writer <- "\n"
for x, row := range maze.Directions {
// There are two lines printed for each maze lines
for _, direction := range []int{Up, Right} {
writer <- format.Path // The left margin
// The left wall
if maze.Start.X == x && maze.Start.Y == 0 && direction == Right {
writer <- startLeft
} else if maze.Goal.X == x && maze.Goal.Y == 0 && maze.Width > 1 && direction == Right {
writer <- goalLeft
} else {
writer <- format.Wall
}
for y, directions := range row {
// In the `direction == Right` line, we print the path cell
if direction == Right {
if directions&solved != 0 {
writer <- format.Solution
} else if directions&visited != 0 {
if maze.Cursor.X == x && maze.Cursor.Y == y {
writer <- format.Cursor
} else {
writer <- format.Visited
}
} else {
writer <- format.Path
}
}
// Print the start or goal point on the right hand side
if maze.Start.X == x && maze.Start.Y == y && y == maze.Width-1 && 0 < y && direction == Right {
writer <- startRight
} else if maze.Goal.X == x && maze.Goal.Y == y && y == maze.Width-1 && direction == Right {
writer <- goalRight
} else
// Print the start or goal point on the top wall of the maze
if maze.Start.X == x && maze.Start.Y == y && x == 0 && maze.Height > 1 && 0 < y && y < maze.Width-1 && direction == Up {
writer <- startLeft
} else if maze.Goal.X == x && maze.Goal.Y == y && x == 0 && maze.Height > 1 && 0 < y && y < maze.Width-1 && direction == Up {
writer <- goalLeft
} else
// If there is a path in the direction (Up or Right) on the maze
if directions&direction != 0 {
// Print the path cell, or the solution cell if solved or the visited cells if the user visited
if (directions>>SolutionOffset)&direction != 0 {
writer <- format.Solution
} else if (directions>>VisitedOffset)&direction != 0 {
writer <- format.Visited
} else {
writer <- format.Path
}
} else {
// Print the wall cell
writer <- format.Wall
}
// In the `direction == Up` line, we print the wall cell
if direction == Up {
writer <- format.Wall
}
}
writer <- "\n"
}
}
// Print the bottom wall of the maze
writer <- format.Path
writer <- format.Wall
for y := 0; y < maze.Width; y++ {
if maze.Start.X == maze.Height-1 && maze.Start.Y == y && maze.Height > 1 && 0 < y && y < maze.Width-1 {
writer <- startLeft
} else if maze.Goal.X == maze.Height-1 && maze.Goal.Y == y && 0 < y && y < maze.Width-1 {
writer <- goalRight
} else {
writer <- format.Wall
}
writer <- format.Wall
}
writer <- "\n\n"
// Inform that we finished printing the maze
writer <- "\u0000"
}

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --solution

View File

@@ -1,23 +0,0 @@
##########################################
S:::::::##::::::##::::::::::::::::::::::##
######::##::##::##::## ##############::##
##::::::##::##::::::## ## ## ##::##
##::######::########## ## ## ######::##
##::##::::::## ## ## ::##
##::##::###### ########## ## ######::##
##::::::## ## ##::::::##
########## ###### ##############::## ##
## ## ## ## ## ##::::::::::## ##
## ## ## ## ## ## ##::########## ##
## ## ## ## ##::::::::::## ##
## ########## ########## ######::######
## ## ## ##::::::##
###### ## ## ######################::##
## ## ## ##::::::::::##
## ############## ## ## ##::##########
## ## ## ## ##::::::::::##
###### ## ## ######################::##
## ## ## :::G
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --format color --width 10 --height 10

View File

@@ -1,23 +0,0 @@
                     
S      
                     
               
                     
           
                     
         
                     
               
                     
             
                     
         
                     
         
                     
           
                     
      G
                     

View File

@@ -1 +0,0 @@
maze --seed 0 --format color --width 10 --height 10 --solution

View File

@@ -1,23 +0,0 @@
                     
S                     
                     
                   
                     
              
                     
             
                     
                   
                     
                 
                     
           
                     
             
                     
               
                     
        G
                     

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10

View File

@@ -1,23 +0,0 @@
##########################################
S ## ## ##
###### ## ## ## ## ############## ##
## ## ## ## ## ## ## ##
## ###### ########## ## ## ###### ##
## ## ## ## ## ##
## ## ###### ########## ## ###### ##
## ## ## ## ##
########## ###### ############## ## ##
## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ########## ##
## ## ## ## ## ## ##
## ########## ########## ###### ######
## ## ## ## ##
###### ## ## ###################### ##
## ## ## ## ##
## ############## ## ## ## ##########
## ## ## ## ## ##
###### ## ## ###################### ##
## ## ## G
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --goal 9,5 --solution

View File

@@ -1,23 +0,0 @@
##########################################
S:::::::##::::::##::::::::::::::::::::::##
######::##::##::##::## ##############::##
##::::::##::##::::::## ## ## ##::##
##::######::########## ## ## ######::##
##::##::::::## ## ## ::##
##::##::###### ########## ## ######::##
##::::::## ## ##::::::##
########## ###### ##############::## ##
## ## ## ## ## ##::::::::::## ##
## ## ## ## ## ## ##::########## ##
## ## ## ## ##::::::::::## ##
## ########## ########## ######::######
## ## ## ##::::::##
###### ## ## ######################::##
## ## ## ##::::::::::##
## ############## ## ## ##::##########
## ## ## ## ##::::::::::##
###### ## ## ######################::##
## ## ## ::::::::::::::::::##
######################:G##################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --goal 9,0 --solution

View File

@@ -1,23 +0,0 @@
##########################################
S:::::::##::::::##::::::::::::::::::::::##
######::##::##::##::## ##############::##
##::::::##::##::::::## ## ## ##::##
##::######::########## ## ## ######::##
##::##::::::## ## ## ::##
##::##::###### ########## ## ######::##
##::::::## ## ##::::::##
########## ###### ##############::## ##
## ## ## ## ## ##::::::::::## ##
## ## ## ## ## ## ##::########## ##
## ## ## ## ##:: ## ##
## ########## ##########::###### ######
## ##::::::##:::::::::::::: ## ##
######::##::##::###################### ##
##::::::##:::::: ## ## ##
##::############## ## ## ## ##########
##::::::## ## ## ## ##
######::## ## ###################### ##
G:::::::## ## ##
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --goal 5,0 --solution

View File

@@ -1,23 +0,0 @@
##########################################
S:::::::##::::::##:::::: ##
######::##::##::##::##::############## ##
##::::::##::##::::::##::## ## ## ##
##::######::##########::## ## ###### ##
##::##::::::##::::::::::## ## ##
##::##::######::########## ## ###### ##
##::::::##::::::## ## ##
##########::###### ############## ## ##
##::::::##::## ## ## ## ## ##
##::##::##::## ## ## ## ########## ##
G:::##::::::## ## ## ## ##
## ########## ########## ###### ######
## ## ## ## ##
###### ## ## ###################### ##
## ## ## ## ##
## ############## ## ## ## ##########
## ## ## ## ## ##
###### ## ## ###################### ##
## ## ## ##
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --goal 0,5 --solution

View File

@@ -1,23 +0,0 @@
######################G:##################
S:::::::##::::::##:::::: ##
######::##::##::##::## ############## ##
##::::::##::##::::::## ## ## ## ##
##::######::########## ## ## ###### ##
##::##::::::## ## ## ##
##::##::###### ########## ## ###### ##
##::::::## ## ## ##
########## ###### ############## ## ##
## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ########## ##
## ## ## ## ## ## ##
## ########## ########## ###### ######
## ## ## ## ##
###### ## ## ###################### ##
## ## ## ## ##
## ############## ## ## ## ##########
## ## ## ## ## ##
###### ## ## ###################### ##
## ## ## ##
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --goal 0,9 --solution

View File

@@ -1,23 +0,0 @@
##########################################
S:::::::##::::::##:::::::::::::::::::::::G
######::##::##::##::## ############## ##
##::::::##::##::::::## ## ## ## ##
##::######::########## ## ## ###### ##
##::##::::::## ## ## ##
##::##::###### ########## ## ###### ##
##::::::## ## ## ##
########## ###### ############## ## ##
## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ########## ##
## ## ## ## ## ## ##
## ########## ########## ###### ######
## ## ## ## ##
###### ## ## ###################### ##
## ## ## ## ##
## ############## ## ## ## ##########
## ## ## ## ## ##
###### ## ## ###################### ##
## ## ## ##
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 1

View File

@@ -1,5 +0,0 @@
##########################################
S G
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 1 --solution

View File

@@ -1,5 +0,0 @@
##########################################
S::::::::::::::::::::::::::::::::::::::::G
##########################################

View File

@@ -1,7 +0,0 @@
NAME:
maze - Maze generating and solving program
USAGE:
maze [global options] [arguments...]
VERSION:

View File

@@ -1 +0,0 @@
maze --seed 0 --width 1 --height 1

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --output .tmp; cat .tmp; rm .tmp

View File

@@ -1,23 +0,0 @@
##########################################
S ## ## ##
###### ## ## ## ## ############## ##
## ## ## ## ## ## ## ##
## ###### ########## ## ## ###### ##
## ## ## ## ## ##
## ## ###### ########## ## ###### ##
## ## ## ## ##
########## ###### ############## ## ##
## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ########## ##
## ## ## ## ## ## ##
## ########## ########## ###### ######
## ## ## ## ##
###### ## ## ###################### ##
## ## ## ## ##
## ############## ## ## ## ##########
## ## ## ## ## ##
###### ## ## ###################### ##
## ## ## G
##########################################

View File

@@ -1 +0,0 @@
maze --seed 1 --width 10 --height 10

View File

@@ -1,23 +0,0 @@
##########################################
S ## ## ## ## ##
## ## ###### ## ## ## ###### ## ##
## ## ## ## ## ## ##
########## ## ########## ## ###### ##
## ## ## ## ##
## ############## ############## ## ##
## ## ## ##
########## ## ## ############## ######
## ## ## ## ## ## ##
## ## ###### ########## ## ###### ##
## ## ## ## ## ##
## ########## ## ############## ## ##
## ## ## ## ## ## ##
########## ###### ## ## ## ## ## ##
## ## ## ## ## ## ## ## ##
## ## ## ## ###### ## ## ## ## ##
## ## ## ## ## ## ## ## ##
## ################## ## ###### ## ##
## ## ## G
##########################################

View File

@@ -1 +0,0 @@
maze --seed 2 --width 10 --height 10

View File

@@ -1,23 +0,0 @@
##########################################
S ## ## ## ##
## ###### ## ## ## ########## ## ##
## ## ## ## ## ## ##
## ## ## ## ## ########## ##########
## ## ## ## ## ## ##
## ## ########## ## ## ###### ## ##
## ## ## ## ## ## ## ##
## ## ## ## ## ## ############## ##
## ## ## ## ## ## ## ##
## ###### ## ############## ## ######
## ## ## ## ##
###################### ## ########## ##
## ## ## ## ##
## ## ###### ## ################## ##
## ## ## ## ## ## ##
## ## ########## ## ########## ## ##
## ## ## ## ## ## ## ##
## ## ## ## ###### ## ########## ##
## ## ## G
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 3 --height 3

View File

@@ -1,9 +0,0 @@
##############
S ## ##
###### ## ##
## ## ##
## ###### ##
## G
##############

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --start 9,5 --solution

View File

@@ -1,23 +0,0 @@
##########################################
## ## ## ## ##
## ###### ## ###### ## ## ## ## ##
## ## ## ## ## ## ## ##
###### ########## ## ############## ##
## ## ## ## ##
## ###### ########## ###### ## ######
## ## ## ## ## ## ##
################## ## ## ## ###### ##
## ## ## ## ## ## ##
## ## ## ## ###### ## ## ## ######
## ## ## ## ## ## ## ## ##
## ########## ###### ## ## ## ## ##
## ## ## ## ## ## ## ##
## ############## ## ## ## ## ## ##
## ## ## ## ## ## ## ##
## ## ## ## ###### ########## ## ##
## ## ## ## ## ##
## ## ########## ###### ########## ##
## ## ##:::::::::::::::::::G
######################S:##################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --start 9,0 --solution

View File

@@ -1,23 +0,0 @@
##########################################
## ## ## ## ##
## ## ## ########## ###### ## ## ##
## ## ## ## ## ## ## ##
## ## ## ## ## ###### ########## ##
## ## ## ## ## ## ##
###### ########## ## ########## ######
## ## ## ## ## ##
## ## ###### ###### ## ########## ##
## ## ## ## ##
## ## ## ###### ######################
## ## ## ## ## ##
###### ## ########## ## ## ###### ##
## ## ## ##::::::::::## ##
## ################## ##::######::######
## ## ## ##::::::##::::::##
## ## ########## ##########::######::##
## ## ##::::::::::## ::##
###### ##############::########## ##::##
S:::::::::::::::::::::::## ##:::G
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --start 9,9 --goal 0,0 --solution

View File

@@ -1,23 +0,0 @@
##########################################
G:::##::::::##:::::::::::::: ##
##::##::##::##::##########::## ##########
##::::::##::##::## ##::## ## ##
##########::##::## ######::## ## ## ##
## ##::::::## ## ::## ## ##
## ## ########## ## ##::########## ##
## ## ## ## ##::## ## ##
## ###### ## ## ######::###### ## ##
## ## ## ## ::## ## ##
###### ## ########## ##::## ###### ##
## ## ## ##::## ## ##
## ########## ## ######::## ## ## ##
## ## ## ##::## ## ##
###### ## ## ##########::## ##########
## ## ## ## ::## ##
## ## ########## ######::########## ##
## ## ## ##::::::::::## ##
## ########## ###### ##########::######
## ## ##:::::::S
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --start 5,0 --solution

View File

@@ -1,23 +0,0 @@
##########################################
## ::::::## ## ##
## ##::##::## ## ############## ######
## ##::##::## ## ## ## ##
######::##::## ########## ## ###### ##
##::::::##::## ## ## ## ## ##
##::######::## ## ## ## ## ## ######
##:: ##::## ## ## ## ##
##::######::########## ## ## ###### ##
##::::::##:: ## ## ## ## ##
######::##::## ############## ###### ##
S:::::::##::## ## ## ##
## ## ##::############## ## ## ######
## ## ##::##::::::## ## ## ## ##
## ######::##::##::## ## ## ###### ##
## ##::##::##::## ## ## ##
###### ##::##::##::## ########## ######
## ##::##::##:: ##::::::::::## ##
## ######::##::##::######::######::## ##
## ##::::::##::::::::::## :::::::G
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --start 5,9 --solution

View File

@@ -1,23 +0,0 @@
##########################################
## ## ## ## ##
## ###### ## ###### ## ## ###### ##
## ## ## ## ## ## ##
## ## ########## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ##
## ## ## ########## ## ## ## ## ##
## ## ## ## ## ## ## ## ##
## ###### ## ## ## ## ########## ##
## ## ## ##::::::::::## ##
## ######################::######::######
## ## ## ::::::##:::::::S
## ## ########## ##########::##########
## ## ## ##:::::: ##
## ## ############## ##::########## ##
## ## ## ## ##::::::::::## ##
## ###### ## ###### ##########::######
## ## ## ## ## ##::::::##
###### ## ###### ## ## ## ######::##
## ## ## ## :::G
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --start 0,5 --solution

View File

@@ -1,23 +0,0 @@
######################S:##################
## ## ##::::::## ## ##
########## ## ##########::## ## ## ##
## ## ::::::## ## ##
## ##################::############## ##
## ## ## ##::::::::::## ##
## ## ## ## ## ## ######::## ######
## ## ## ## ##::::::## ##
## ############## ## ##::########## ##
## ## ## ## ##::## ##
## ## ## ###### ## ##::## ##########
## ## ## ## ##::## ## ##
## ########## ## ## ##::## ## ## ##
## ## ## ## ##::## ## ##
## ## ############## ##::########## ##
## ## ## ## ##::## ##::::::##
## ###### ###### ## ##::## ##::##::##
## ## ## ## ##::##::::::##::##
###### ## ## ###### ##::##::######::##
## ## ##::::::## :::G
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 10 --height 10 --start 0,9 --solution

View File

@@ -1,23 +0,0 @@
##########################################
## ## ## ## ## :::S
## ## ## ###### ## ## ###### ##::##
## ## ## ## ## ## ##::##
## ## ###### ## ##################::##
## ## ## ##::::::::::::::##::##
## ###### ##########::###### ##::##::##
## ## ##::::::::::## ##::##::##
## ## ######::## ##############::##::##
## ## ::## ## ## ::::::##
## ###### ##::## ###### ## ##########
## ##::## ## ## ##
##############::################## ## ##
## ##::::::::::##::::::## ##
## ###### ##########::##::##::##########
## ## ## ::::::##::::::::::##
## ## ###### ############## ######::##
## ## ## ## ## ## ::##
## ## ########## ## ## ###### ##::##
## ## ## ## ##:::G
##########################################

View File

@@ -1 +0,0 @@
maze --seed 0 --width 1 --height 10

View File

@@ -1,23 +0,0 @@
######
S ##
## ##
## ##
## ##
## ##
## ##
## ##
## ##
## ##
## ##
## ##
## ##
## ##
## ##
## ##
## ##
## ##
## ##
## G
######

View File

@@ -1 +0,0 @@
maze --seed 0 --width 1 --height 10 --solution

View File

@@ -1,23 +0,0 @@
######
S:::##
##::##
##::##
##::##
##::##
##::##
##::##
##::##
##::##
##::##
##::##
##::##
##::##
##::##
##::##
##::##
##::##
##::##
##:::G
######

View File

@@ -0,0 +1,229 @@
#!/usr/bin/env ruby
#
# A node is both a member of a directed graph, and a cell on an x,y
# plane of possibly-connected maze passages.
#
class Node
attr_accessor :row, :col, :visited, :neighbors, :on_path
def initialize(row, col)
@row = row
@col = col
@visited = false
@on_path = false
@neighbors = {north: nil, east: nil, south: nil, west: nil}
end
# Connect this node to another node. The other node can only fit
# in one of four cardinal directions from this node: North, South,
# East, or West.
def connect_to(other)
if other.row == self.row - 1
self.neighbors[:north] = other
other.neighbors[:south] = self
elsif other.row == self.row + 1
self.neighbors[:south] = other
other.neighbors[:north] = self
elsif other.col == self.col + 1
self.neighbors[:east] = other
other.neighbors[:west] = self
elsif other.col == self.col - 1
self.neighbors[:west] = other
other.neighbors[:east] = self
end
end
end
#
# A Maze is a container for Nodes, and is responsible for:
# - generating a maze by connecting unconnected nodes, and
# - finding a solution through an existing maze of connected nodes.
#
class Maze
DIRECTIONS = [[-1, 0], [1, 0], [0, -1], [0, 1]]
def initialize(rows, cols)
@rows = rows
@cols = cols
# Initialize the maze with a bunch of un-connected nodes
@maze = Array.new(@rows) do |r|
Array.new(@cols) do |c|
Node.new(r, c)
end
end
# Pick start and end nodes on opposite ends.
@start_node = @maze[rand(@rows)][0]
@end_node = @maze[rand(@rows)][@cols - 1]
end
# Generate the maze.
def generate
# Make a stack of nodes along our walk
stack = []
# Visit the first node
node = @start_node
node.visited = true
stack.push(node)
# Now keep walking and finding nodes.
while true
next_node = get_next_unvisited(node)
if next_node
next_node.visited = true
next_node.connect_to(node)
stack.push(next_node)
node = next_node
else
node = stack.pop
break if !node
end
end
end
def solve
# Reset visited state on all nodes.
@maze.each do |row|
row.each do |node|
node.visited = false
end
end
stack = []
# Visit the first node
node = @start_node
node.visited = true
node.on_path = true
while true
next_node = get_next_in_graph(node)
if next_node
next_node.visited = true
next_node.on_path = true
# If we're at the exit, we're done.
break if next_node == @end_node
# Otherwise, push onto the stack and keep going.
stack.push(node)
node = next_node
else
# This would only happen if there were no solution.
break if stack.size == 0
# 'node' has no un-visited neighbors, so it can't be on the
# path.
node.on_path = false
node = stack.pop
end
end
end
# When walking a completed maze, we are doing a random walk of a
# directed graph, so it's simpler than the random walk to create the
# maze.
def get_next_in_graph(node)
node.neighbors.values.compact.shuffle
.select {|neighbor| !neighbor.visited }.first
end
# When generating a new maze, we need to find the next un-visited
# neighboring node.
def get_next_unvisited(node)
neighbors = []
DIRECTIONS.each do |dir|
new_row = node.row + dir[0]
new_col = node.col + dir[1]
if new_row >= 0 && new_row < @rows &&
new_col >= 0 && new_col < @cols &&
!@maze[new_row][new_col].visited
neighbors << @maze[new_row][new_col]
end
end
neighbors[rand(neighbors.length)]
end
def to_s
buf = ""
@maze.each_with_index do |row, idx|
# Pass one.
row.each do |node|
buf << if node.neighbors[:north]
"+ "
else
"+---"
end
buf << if node.col == @cols - 1
"+"
else
""
end
end
buf << "\r\n"
# Pass two
row.each do |node|
buf << if node.neighbors[:west] || node == @start_node
" "
else
"|"
end
buf << if node.on_path
" @ "
else
" "
end
if node.col == @cols - 1
buf << if node == @end_node
" "
else
"|"
end
end
end
buf << "\r\n"
# Pass three, if last row
if idx == @rows - 1
row.each do |node|
buf << "+---"
end
buf << "+\r\n"
end
end
buf
end
end
rows = ARGV[0].to_i
cols = ARGV[1].to_i
if rows <= 0 || cols <= 0
puts "Usage: #{$0} <rows> <cols>"
exit -1
end
maze = Maze.new(rows, cols)
maze.generate
puts maze
puts "*********"
maze.solve
puts maze

View File

@@ -0,0 +1,9 @@
class maze::conf {
$secgen_params = secgen_functions::get_parameters($::base64_inputs_file)
# Make it generate 100 mazes + solutions
# Set permissions
}

View File

@@ -1,31 +1,46 @@
class maze::install {
$secgen_params = secgen_functions::get_parameters($::base64_inputs_file)
$challenge_name = $secgen_params['test'][0]
$maze_dir = '/vagrant/src/maze'
$challenge_name = $secgen_params['challenge_name'][0]
Exec { path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/go/bin:/vagrant/bin' }
::secgen_functions::create_directory { "create_$challenge_directory":
path => $maze_dir,
notify => File['copy maze dir'],
if ($secgen_params['account'] and $secgen_params['account'][0]) {
$acc = parsejson($secgen_params['account'][0])
$username = $acc['username']
$challenge_dir = "/home/$username/$challenge_name"
} elsif $secgen_params['storage_directory'] and $secgen_params['storage_directory'][0] {
$storage_dir = $secgen_params['storage_directory'][0]
$challenge_dir = "$storage_dir/$challenge_name"
} else {
$challenge_dir = "/root/$challenge_name"
}
file { 'copy maze dir':
path => $maze_dir,
ensure => directory,
recurse => true,
source => 'puppet:///modules/maze/maze-master',
notify => Exec['make maze'],
if $secgen_params['group'] and $secgen_params['group'][0] {
$group = $secgen_params['group'][0]
} else {
$group = $challenge_name
}
exec { 'make maze':
cwd => $maze_dir,
command => 'env DEPNOLOCK=1 make',
notify => Exec['install maze'],
# Move dependent maze generation script onto box
file { 'move maze.rb':
path => "$challenge_dir/maze.rb",
source => 'puppet:///modules/maze/maze.rb',
owner => 'root',
group => $group,
mode => '0440',
}
exec { 'install maze':
cwd => "$maze_dir/build",
command => 'install maze /usr/local/bin',
# Configure setgid wrapper script
::secgen_functions::install_setgid_script { $challenge_name:
source_module_name => $module_name,
challenge_name => $challenge_name,
script_name => 'test.rb',
script_data => template('maze/challenge_script.rb.erb'),
group => $secgen_params['group'],
account => $secgen_params['account'],
flag => $secgen_params['flag'],
port => $secgen_params['port'],
storage_directory => $secgen_params['storage_directory'],
strings_to_leak => $secgen_params['strings_to_leak'],
}
}

View File

@@ -1 +1,2 @@
include maze::install
include maze::conf

View File

@@ -4,8 +4,8 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.github/cliffe/SecGen/vulnerability">
<name>Maze Solving Challenge</name>
<author>itchyny</author>
<author>Thomas Shaw</author>
<author>itchyny</author>
<module_license>MIT</module_license>
<description>TODO</description>
@@ -18,71 +18,54 @@
<challenge_subtype>programming</challenge_subtype>
<difficulty>medium</difficulty>
<read_fact>test</read_fact>
<!-- script dropped in account's home directory by default with setuid configuration. -->
<read_fact>challenge_name</read_fact>
<read_fact>account</read_fact>
<read_fact>flag</read_fact>
<!-- group: Blank by default. Uses challenge name as group name unless explicitly provided. -->
<read_fact>group</read_fact>
<!-- storage_directory: Blank by default. If supplied, store the files here. e.g. NFS or SMB storage location -->
<read_fact>storage_directory</read_fact>
<!-- port: Blank by default. If supplied install script challenge as xinetd program running on given port -->
<read_fact>port</read_fact>
<default_input into="test">
<value>asdf</value>
<default_input into="challenge_name">
<value>solve_a_maze</value>
</default_input>
<default_input into="account">
<generator type="account">
<input into="username">
<value>challenges</value>
</input>
<input into="password">
<value>password</value>
</input>
</generator>
</default_input>
<default_input into="flag">
<generator type="flag_generator"/>
</default_input>
<reference>https://github.com/itchyny/maze</reference>
<requires>
<software_name>golang</software_name>
<module_path>utilities/unix/system/accounts</module_path>
</requires>
<requires>
<module_path>utilities/unix/system/binary_script_container</module_path>
</requires>
<requires>
<module_path>utilities/unix/languages/ruby</module_path>
</requires>
<requires>
<module_path>utilities/unix/system/xinetd</module_path>
</requires>
<requires>
<software_name>git</software_name>
</requires>
<!--&lt;!&ndash; script dropped in account's home directory by default with setuid configuration. &ndash;&gt;-->
<!--<read_fact>script_data</read_fact>-->
<!--<read_fact>account</read_fact>-->
<!--<read_fact>flag</read_fact>-->
<!--<read_fact>group</read_fact>-->
<!--&lt;!&ndash; storage_directory: Blank by default. If supplied, store the files here. e.g. NFS or SMB storage location &ndash;&gt;-->
<!--<read_fact>storage_directory</read_fact>-->
<!--&lt;!&ndash; port: Blank by default. If supplied install script challenge as xinetd program running on given port-->
<!--TODO: investigate why this doesnt work with nc &ndash;&gt;-->
<!--&lt;!&ndash;<read_fact>port</read_fact>&ndash;&gt;-->
<!--<default_input into="challenge_name">-->
<!--<value>echo_string</value>-->
<!--</default_input>-->
<!--<default_input into="script_data">-->
<!--<generator module_path=".*echo_string.*"/>-->
<!--</default_input>-->
<!--<default_input into="account">-->
<!--<generator type="account">-->
<!--<input into="username">-->
<!--<value>challenges</value>-->
<!--</input>-->
<!--<input into="password">-->
<!--<value>password</value>-->
<!--</input>-->
<!--</generator>-->
<!--</default_input>-->
<!--<default_input into="group">-->
<!--<value>echo_string</value>-->
<!--</default_input>-->
<!--<default_input into="flag">-->
<!--<generator type="flag_generator"/>-->
<!--</default_input>-->
<!--<requires>-->
<!--<module_path>utilities/unix/system/accounts</module_path>-->
<!--</requires>-->
<!--<requires>-->
<!--<module_path>utilities/unix/system/binary_script_container</module_path>-->
<!--</requires>-->
<!--<requires>-->
<!--<module_path>utilities/unix/languages/ruby</module_path>-->
<!--</requires>-->
<!--<requires>-->
<!--<module_path>utilities/unix/system/xinetd</module_path>-->
<!--</requires>-->
</vulnerability>

View File

@@ -0,0 +1,26 @@
#!/usr/local/bin/suid /usr/bin/ruby --
flag_path = ''
if ARGV[0] and File.directory? ARGV[0]
flag_path = ARGV.shift
if flag_path[-1] != '/'
flag_path += '/'
end
end
flag_path += 'flag'
puts "test"
puts "<%=@challenge_name-%>"
maze_output = `ruby maze.rb 10 10 <%= @challenge_dir -%>`
maze_arr = maze_output.split("*********")
maze = maze_arr.first
solution = maze_arr.last
puts 'maze: '
puts maze
puts 'solution: '
puts solution
# TODO: add instructions
# TODO: add comparison between solution and maze
# TODO: add functionality to require the solution of n>1 mazes
# TODO: add a time restriction