From 1e166f45f2760182eb6ee91619a4f8ae11754508 Mon Sep 17 00:00:00 2001 From: faiface Date: Tue, 30 May 2017 13:30:09 +0200 Subject: [PATCH] add maze generator community example --- community/maze/LICENSE | 21 ++ community/maze/README.md | 19 ++ community/maze/maze-generator.go | 317 +++++++++++++++++++++++++++++++ community/maze/screenshot.png | Bin 0 -> 14155 bytes community/maze/stack/stack.go | 86 +++++++++ 5 files changed, 443 insertions(+) create mode 100644 community/maze/LICENSE create mode 100644 community/maze/README.md create mode 100644 community/maze/maze-generator.go create mode 100644 community/maze/screenshot.png create mode 100644 community/maze/stack/stack.go diff --git a/community/maze/LICENSE b/community/maze/LICENSE new file mode 100644 index 0000000..1326e36 --- /dev/null +++ b/community/maze/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 stephen + +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. diff --git a/community/maze/README.md b/community/maze/README.md new file mode 100644 index 0000000..27b344a --- /dev/null +++ b/community/maze/README.md @@ -0,0 +1,19 @@ +# Maze generator in Go + +Created by [Stephen Chavez](https://github.com/redragonx) + +This uses the game engine: Pixel. Install it here: https://github.com/faiface/pixel + +I made this to improve my understanding of Go and some game concepts with some basic maze generating algorithms. + +Controls: Press 'R' to restart the maze. + +Optional command-line arguments: `go run ./maze-generator.go` + - `-w` sets the maze's width in pixels. + - `-h` sets the maze's height in pixels. + - `-c` sets the maze cell's size in pixels. + +Code based on the Recursive backtracker algorithm. +- https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker + +![Screenshot](screenshot.png) \ No newline at end of file diff --git a/community/maze/maze-generator.go b/community/maze/maze-generator.go new file mode 100644 index 0000000..a8b09f8 --- /dev/null +++ b/community/maze/maze-generator.go @@ -0,0 +1,317 @@ +package main + +// Code based on the Recursive backtracker algorithm. +// https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker +// See https://youtu.be/HyK_Q5rrcr4 as an example +// YouTube example ported to Go for the Pixel library. + +// Created by Stephen Chavez + +import ( + "crypto/rand" + "errors" + "flag" + "fmt" + "math/big" + "time" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/examples/community/maze/stack" + "github.com/faiface/pixel/imdraw" + "github.com/faiface/pixel/pixelgl" + + "github.com/pkg/profile" + "golang.org/x/image/colornames" +) + +var visitedColor = pixel.RGB(0.5, 0, 1).Mul(pixel.Alpha(0.35)) +var hightlightColor = pixel.RGB(0.3, 0, 0).Mul(pixel.Alpha(0.45)) +var debug = false + +type cell struct { + walls [4]bool // Wall order: top, right, bottom, left + + row int + col int + visited bool +} + +func (c *cell) Draw(imd *imdraw.IMDraw, wallSize int) { + drawCol := c.col * wallSize // x + drawRow := c.row * wallSize // y + + imd.Color = colornames.White + if c.walls[0] { + // top line + imd.Push(pixel.V(float64(drawCol), float64(drawRow)), pixel.V(float64(drawCol+wallSize), float64(drawRow))) + imd.Line(3) + } + if c.walls[1] { + // right Line + imd.Push(pixel.V(float64(drawCol+wallSize), float64(drawRow)), pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize))) + imd.Line(3) + } + if c.walls[2] { + // bottom line + imd.Push(pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize)), pixel.V(float64(drawCol), float64(drawRow+wallSize))) + imd.Line(3) + } + if c.walls[3] { + // left line + imd.Push(pixel.V(float64(drawCol), float64(drawRow+wallSize)), pixel.V(float64(drawCol), float64(drawRow))) + imd.Line(3) + } + imd.EndShape = imdraw.SharpEndShape + + if c.visited { + imd.Color = visitedColor + imd.Push(pixel.V(float64(drawCol), (float64(drawRow))), pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize))) + imd.Rectangle(0) + } +} + +func (c *cell) GetNeighbors(grid []*cell, cols int, rows int) ([]*cell, error) { + neighbors := []*cell{} + j := c.row + i := c.col + + top, _ := getCellAt(i, j-1, cols, rows, grid) + right, _ := getCellAt(i+1, j, cols, rows, grid) + bottom, _ := getCellAt(i, j+1, cols, rows, grid) + left, _ := getCellAt(i-1, j, cols, rows, grid) + + if top != nil && !top.visited { + neighbors = append(neighbors, top) + } + if right != nil && !right.visited { + neighbors = append(neighbors, right) + } + if bottom != nil && !bottom.visited { + neighbors = append(neighbors, bottom) + } + if left != nil && !left.visited { + neighbors = append(neighbors, left) + } + + if len(neighbors) == 0 { + return nil, errors.New("We checked all cells...") + } + return neighbors, nil +} + +func (c *cell) GetRandomNeighbor(grid []*cell, cols int, rows int) (*cell, error) { + neighbors, err := c.GetNeighbors(grid, cols, rows) + if neighbors == nil { + return nil, err + } + nBig, err := rand.Int(rand.Reader, big.NewInt(int64(len(neighbors)))) + if err != nil { + panic(err) + } + randomIndex := nBig.Int64() + return neighbors[randomIndex], nil +} + +func (c *cell) hightlight(imd *imdraw.IMDraw, wallSize int) { + x := c.col * wallSize + y := c.row * wallSize + + imd.Color = hightlightColor + imd.Push(pixel.V(float64(x), float64(y)), pixel.V(float64(x+wallSize), float64(y+wallSize))) + imd.Rectangle(0) +} + +func newCell(col int, row int) *cell { + newCell := new(cell) + newCell.row = row + newCell.col = col + + for i := range newCell.walls { + newCell.walls[i] = true + } + return newCell +} + +// Creates the inital maze slice for use. +func initGrid(cols, rows int) []*cell { + grid := []*cell{} + for j := 0; j < rows; j++ { + for i := 0; i < cols; i++ { + newCell := newCell(i, j) + grid = append(grid, newCell) + } + } + return grid +} + +func setupMaze(cols, rows int) ([]*cell, *stack.Stack, *cell) { + // Make an empty grid + grid := initGrid(cols, rows) + backTrackStack := stack.NewStack(len(grid)) + currentCell := grid[0] + + return grid, backTrackStack, currentCell +} + +func cellIndex(i, j, cols, rows int) int { + if i < 0 || j < 0 || i > cols-1 || j > rows-1 { + return -1 + } + return i + j*cols +} + +func getCellAt(i int, j int, cols int, rows int, grid []*cell) (*cell, error) { + possibleIndex := cellIndex(i, j, cols, rows) + + if possibleIndex == -1 { + return nil, fmt.Errorf("cellIndex: CellIndex is a negative number %d", possibleIndex) + } + return grid[possibleIndex], nil +} + +func removeWalls(a *cell, b *cell) { + x := a.col - b.col + + if x == 1 { + a.walls[3] = false + b.walls[1] = false + } else if x == -1 { + a.walls[1] = false + b.walls[3] = false + } + + y := a.row - b.row + + if y == 1 { + a.walls[0] = false + b.walls[2] = false + } else if y == -1 { + a.walls[2] = false + b.walls[0] = false + } +} + +func run() { + // unsiged integers, because easier parsing error checks. + // We must convert these to intergers, as done below... + uScreenWidth, uScreenHeight, uWallSize := parseArgs() + + var ( + // In pixels + // Defualt is 800x800x40 = 20x20 wallgrid + screenWidth = int(uScreenWidth) + screenHeight = int(uScreenHeight) + wallSize = int(uWallSize) + + frames = 0 + second = time.Tick(time.Second) + + grid = []*cell{} + cols = screenWidth / wallSize + rows = screenHeight / wallSize + currentCell = new(cell) + backTrackStack = stack.NewStack(1) + ) + + // Set game FPS manually + fps := time.Tick(time.Second / 60) + + cfg := pixelgl.WindowConfig{ + Title: "Pixel Rocks! - Maze example", + Bounds: pixel.R(0, 0, float64(screenHeight), float64(screenWidth)), + } + + win, err := pixelgl.NewWindow(cfg) + if err != nil { + panic(err) + } + + grid, backTrackStack, currentCell = setupMaze(cols, rows) + + gridIMDraw := imdraw.New(nil) + + for !win.Closed() { + if win.JustReleased(pixelgl.KeyR) { + fmt.Println("R pressed") + grid, backTrackStack, currentCell = setupMaze(cols, rows) + } + + win.Clear(colornames.Gray) + gridIMDraw.Clear() + + for i := range grid { + grid[i].Draw(gridIMDraw, wallSize) + } + + // step 1 + // Make the initial cell the current cell and mark it as visited + currentCell.visited = true + currentCell.hightlight(gridIMDraw, wallSize) + + // step 2.1 + // If the current cell has any neighbours which have not been visited + // Choose a random unvisited cell + nextCell, _ := currentCell.GetRandomNeighbor(grid, cols, rows) + if nextCell != nil && !nextCell.visited { + // step 2.2 + // Push the current cell to the stack + backTrackStack.Push(currentCell) + + // step 2.3 + // Remove the wall between the current cell and the chosen cell + + removeWalls(currentCell, nextCell) + + // step 2.4 + // Make the chosen cell the current cell and mark it as visited + nextCell.visited = true + currentCell = nextCell + } else if backTrackStack.Len() > 0 { + currentCell = backTrackStack.Pop().(*cell) + } + + gridIMDraw.Draw(win) + win.Update() + <-fps + updateFPSDisplay(win, &cfg, &frames, grid, second) + } +} + +// Parses the maze arguments, all of them are optional. +// Uses uint as implicit error checking :) +func parseArgs() (uint, uint, uint) { + var mazeWidthPtr = flag.Uint("w", 800, "w sets the maze's width in pixels.") + var mazeHeightPtr = flag.Uint("h", 800, "h sets the maze's height in pixels.") + var wallSizePtr = flag.Uint("c", 40, "c sets the maze cell's size in pixels.") + + flag.Parse() + + // If these aren't default values AND if they're not the same values. + // We should warn the user that the maze will look funny. + if *mazeWidthPtr != 800 || *mazeHeightPtr != 800 { + if *mazeWidthPtr != *mazeHeightPtr { + fmt.Printf("WARNING: maze width: %d and maze height: %d don't match. \n", *mazeWidthPtr, *mazeHeightPtr) + fmt.Println("Maze will look funny because the maze size is bond to the window size!") + } + } + + return *mazeWidthPtr, *mazeHeightPtr, *wallSizePtr +} + +func updateFPSDisplay(win *pixelgl.Window, cfg *pixelgl.WindowConfig, frames *int, grid []*cell, second <-chan time.Time) { + *frames++ + select { + case <-second: + win.SetTitle(fmt.Sprintf("%s | FPS: %d with %d Cells", cfg.Title, *frames, len(grid))) + *frames = 0 + default: + } + +} + +func main() { + if debug { + defer profile.Start().Stop() + } + pixelgl.Run(run) +} diff --git a/community/maze/screenshot.png b/community/maze/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ce5bfd2f89336e571b304e1dc224af9f2e231013 GIT binary patch literal 14155 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 s.max { + if last := s.PopLast(); last == nil { + panic("Unexpected nil in stack") + } + } + s.top = &Element{value, s.top} + s.size++ +} + +// Remove the top element from the stack and return it's value +// If the stack is empty, return nil +func (s *Stack) Pop() (value interface{}) { + if s.size > 0 { + value, s.top = s.top.value, s.top.next + s.size-- + return + } + return nil +} + +func (s *Stack) PopLast() (value interface{}) { + if lastElem := s.popLast(s.top); lastElem != nil { + return lastElem.value + } + return nil +} + +//Peek returns a top without removing it from list +func (s *Stack) Peek() (value interface{}, exists bool) { + exists = false + if s.size > 0 { + value = s.top.value + exists = true + } + + return +} + +func (s *Stack) popLast(elem *Element) *Element { + if elem == nil { + return nil + } + // not last because it has next and a grandchild + if elem.next != nil && elem.next.next != nil { + return s.popLast(elem.next) + } + + // current elem is second from bottom, as next elem has no child + if elem.next != nil && elem.next.next == nil { + last := elem.next + // make current elem bottom of stack by removing its next element + elem.next = nil + s.size-- + return last + } + return nil +}