Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
cheina97 committed Mar 24, 2024
1 parent d6eba3e commit 3135101
Show file tree
Hide file tree
Showing 13 changed files with 497 additions and 0 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# GoMaze

*GoMaze* is a simple maze generator and solver written in *Go*. It randomly generate the maze and uses *Dijkstra algorithm* to find the minimum path between 2 points.

The library used to render the maze and manage inputs is [ebitengine](https://ebitengine.org/), a simple 2D game engine written in Go.
The minimum path is calculated using the [graph](https://pkg.go.dev/github.com/dominikbraun/graph#readme-getting-started) library.

![](./doc/example.gif)

# How to use

## How to run

```bash
go run cmd/main.go
```

```bash
Flags:
-h int
height of the maze (cells number) (default 20)
-w int
width of the maze (cells number) (default 20)
```

## How to play

- *Click* on the maze with the *left mouse button* to set the *start* and the *ending* point.
- Press *R* on the keyboard to *generate* a new maze.

# How it works

To find the minimum path between 2 points, the program uses the *Dijkstra algorithm*. Every walkable cell of the matrix (representing the maze), have been added to a graph. The weight of the edges is the distance between 2 cells, which is always 1. The algorithm will find the shortest path between the 2 points by exploring the graph.

This is an example of the generated graph:

![](./doc/graph.png)




22 changes: 22 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"flag"
"log"

"github.com/cheina97/gomaze/pkg/game"
"github.com/hajimehoshi/ebiten/v2"
)

func main() {
w := flag.Int("w", 20, "width of the maze (cells number)")
h := flag.Int("h", 20, "height of the maze (cells number)")
flag.Parse()
game, err := game.NewGame(*w, *h)
if err != nil {
log.Fatal(err)
}
if err := ebiten.RunGame(game); err != nil {
log.Fatal(err)
}
}
Binary file added doc/example.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module github.com/cheina97/gomaze

go 1.22.1

require (
github.com/dominikbraun/graph v0.23.0
github.com/hajimehoshi/ebiten/v2 v2.6.7
)

require (
github.com/ebitengine/purego v0.6.0 // indirect
github.com/jezek/xgb v1.1.0 // indirect
golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 // indirect
golang.org/x/image v0.12.0 // indirect
golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.12.0 // indirect
)
49 changes: 49 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/ebitengine/purego v0.6.0 h1:Yo9uBc1x+ETQbfEaf6wcBsjrQfCEnh/gaGUg7lguEJY=
github.com/ebitengine/purego v0.6.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/hajimehoshi/ebiten/v2 v2.6.7 h1:rxlMxu487wZN/JteykmuGdO1qotOolL8vJDU85lPh7A=
github.com/hajimehoshi/ebiten/v2 v2.6.7/go.mod h1:gKgQI26zfoSb6j5QbrEz2L6nuHMbAYwrsXa5qsGrQKo=
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 h1:3AGKexOYqL+ztdWdkB1bDwXgPBuTS/S8A4WzuTvJ8Cg=
golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0=
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 h1:Q6NT8ckDYNcwmi/bmxe+XbiDMXqMRW1xFBtJ+bIpie4=
golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57/go.mod h1:wEyOn6VvNW7tcf+bW/wBz1sehi2s2BZ4TimyR7qZen4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Empty file added makefile
Empty file.
78 changes: 78 additions & 0 deletions mygraph.gv
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
strict graph {


"1/2" [ weight=0 ];

"1/2" -- "2/2" [ weight=0 ];

"1/2" -- "1/3" [ weight=0 ];

"1/2" -- "1/1" [ weight=0 ];

"2/5" [ weight=0 ];

"2/5" -- "2/4" [ weight=0 ];

"2/0" [ weight=0 ];

"2/4" [ weight=0 ];

"2/4" -- "2/5" [ weight=0 ];

"2/4" -- "1/4" [ weight=0 ];

"0/5" [ weight=0 ];

"0/5" -- "0/4" [ weight=0 ];

"1/1" [ weight=0 ];

"1/1" -- "0/1" [ weight=0 ];

"1/1" -- "1/2" [ weight=0 ];

"0/1" [ weight=0 ];

"0/1" -- "0/0" [ weight=0 ];

"0/1" -- "1/1" [ weight=0 ];

"0/3" [ weight=0 ];

"0/3" -- "0/4" [ weight=0 ];

"0/3" -- "1/3" [ weight=0 ];

"0/0" [ weight=0 ];

"0/0" -- "0/1" [ weight=0 ];

"0/4" [ weight=0 ];

"0/4" -- "1/4" [ weight=0 ];

"0/4" -- "0/5" [ weight=0 ];

"0/4" -- "0/3" [ weight=0 ];

"2/2" [ weight=0 ];

"2/2" -- "1/2" [ weight=0 ];

"1/3" [ weight=0 ];

"1/3" -- "0/3" [ weight=0 ];

"1/3" -- "1/2" [ weight=0 ];

"1/3" -- "1/4" [ weight=0 ];

"1/4" [ weight=0 ];

"1/4" -- "0/4" [ weight=0 ];

"1/4" -- "1/3" [ weight=0 ];

"1/4" -- "2/4" [ weight=0 ];

}
101 changes: 101 additions & 0 deletions pkg/game/game.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package game

import (
"os"

"github.com/cheina97/gomaze/pkg/grid"
"github.com/cheina97/gomaze/pkg/matrix"
"github.com/dominikbraun/graph/draw"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)

type lastPosSet string

const (
start lastPosSet = "start"
end lastPosSet = "end"
)

type Game struct {
w, h int
Matrix *matrix.Matrix
nextPosToSet lastPosSet
startFinding bool
}

func NewGame(w int, h int) (*Game, error) {
ebiten.SetWindowSize(w*grid.CellSize, h*grid.CellSize)
ebiten.SetWindowTitle("GoMaze")
m := matrix.NewMatrix(h, w)
if err := m.GenerateRandomWalls(); err != nil {
return nil, err
}
if err := m.InitEdges(); err != nil {
return nil, err
}
file, _ := os.Create("./mygraph.gv")
_ = draw.DOT(m.Graph, file)

return &Game{
w: w,
h: h,
Matrix: m,
nextPosToSet: start,
startFinding: false,
}, nil
}

func (g *Game) Update() error {
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
mx, my := ebiten.CursorPosition()
i, j := grid.GetGridCellFromCoords(mx, my)

p := g.Matrix.Get(j, i)
if p == matrix.Invalid || p == matrix.Wall {
return nil
}

switch g.nextPosToSet {
case end:
g.Matrix.CleanAllValues(matrix.End)
g.Matrix.Set(j, i, matrix.End)
g.Matrix.End = &matrix.Point{X: j, Y: i}
g.nextPosToSet = start
g.startFinding = true
case start:
g.Matrix.CleanAllValues(matrix.Start)
g.Matrix.Set(j, i, matrix.Start)
g.Matrix.Start = &matrix.Point{X: j, Y: i}
g.nextPosToSet = end
g.startFinding = true
}
}
if inpututil.IsKeyJustPressed(ebiten.KeyR) {
g.Matrix = matrix.NewMatrix(g.h, g.w)
if err := g.Matrix.GenerateRandomWalls(); err != nil {
return err
}
if err := g.Matrix.InitEdges(); err != nil {
return err
}
}

if g.Matrix.Start != nil && g.Matrix.End != nil && g.startFinding {
g.Matrix.CleanAllValues(matrix.Path)
if err := g.Matrix.FindMinimumPath(); err != nil {
return err
}
g.startFinding = false
}

return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
grid.DrawGrid(screen, g.Matrix)
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
return outsideWidth, outsideHeight
}
16 changes: 16 additions & 0 deletions pkg/grid/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package grid

import "image/color"

const (
CellSize = 30
BorderOffset = 20
)

var (
emptyColor = color.RGBA{255, 255, 255, 0}
wallColor = color.RGBA{0, 0, 0, 0}
startColor = color.RGBA{75, 162, 71, 0}
endColor = color.RGBA{255, 20, 87, 0}
pathColor = color.RGBA{255, 219, 87, 0}
)
36 changes: 36 additions & 0 deletions pkg/grid/grid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package grid

import (
"image/color"

"github.com/cheina97/gomaze/pkg/matrix"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
)

func DrawGrid(dst *ebiten.Image, m *matrix.Matrix) {
for x := 0; x < m.Cols; x++ {
for y := 0; y < m.Rows; y++ {
switch m.Get(x, y) {
case matrix.Empty:
DrawGridCell(dst, x, y, emptyColor)
case matrix.Wall:
DrawGridCell(dst, x, y, wallColor)
case matrix.Start:
DrawGridCell(dst, x, y, startColor)
case matrix.End:
DrawGridCell(dst, x, y, endColor)
case matrix.Path:
DrawGridCell(dst, x, y, pathColor)
}
}
}
}

func DrawGridCell(dst *ebiten.Image, x, y int, cl color.Color) {
vector.DrawFilledRect(dst, float32(x*CellSize), float32(y*CellSize), float32(CellSize), float32(CellSize), cl, false)
}

func GetGridCellFromCoords(x, y int) (int, int) {
return y / (CellSize), x / (CellSize)
}
10 changes: 10 additions & 0 deletions pkg/matrix/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package matrix

const (
Wall = 1
Empty = 0
Start = 2
End = 3
Path = 4
Invalid = -1
)
Loading

0 comments on commit 3135101

Please sign in to comment.