From c5cad477620971c737b2493759754fe64424b339 Mon Sep 17 00:00:00 2001 From: kts of kettek Date: Fri, 2 Feb 2024 15:59:24 -0800 Subject: [PATCH] Initial stuff --- cmd/prog/main.go | 140 +++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 22 ++++++++ go.sum | 56 +++++++++++++++++++ gobl.go | 27 +++++++++ sauce/astar.go | 85 ++++++++++++++++++++++++++++ sauce/cell.go | 5 ++ sauce/chunk.go | 31 +++++++++++ sauce/world.go | 65 ++++++++++++++++++++++ 8 files changed, 431 insertions(+) create mode 100644 cmd/prog/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 gobl.go create mode 100644 sauce/astar.go create mode 100644 sauce/cell.go create mode 100644 sauce/chunk.go create mode 100644 sauce/world.go diff --git a/cmd/prog/main.go b/cmd/prog/main.go new file mode 100644 index 0000000..d58cd6d --- /dev/null +++ b/cmd/prog/main.go @@ -0,0 +1,140 @@ +package main + +import ( + "fmt" + "gridpathingtest/sauce" + "image/color" + "math" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/ebitenutil" + "github.com/hajimehoshi/ebiten/v2/inpututil" + "github.com/hajimehoshi/ebiten/v2/vector" + "github.com/tinne26/etxt" + "github.com/tinne26/fonts/liberation/lbrtserif" +) + +var ( + txt *etxt.Renderer +) + +const ( + CellSize = 20 +) + +type Game struct { + world *sauce.World + cellX, cellY int + fromX, fromY int + toX, toY int + whichFromTo bool +} + +func NewGame() *Game { + g := &Game{} + g.world = sauce.NewWorld(3, 3) + g.world.HueristicLine(10, 0, 10, 10, 1) + g.world.HueristicLine(10, 13, 10, 30, 1) + return g +} + +var ( + lastOutsideWidth int + lastOutsideHeight int + lastScreenWidth int + lastScreenHeight int +) + +func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { + if lastOutsideWidth == outsideWidth && lastOutsideHeight == outsideHeight { + return lastScreenWidth, lastScreenHeight + } + lastOutsideWidth = outsideWidth + lastOutsideHeight = outsideHeight + scale := ebiten.DeviceScaleFactor() + txt.SetScale(scale) + lastScreenWidth, lastScreenHeight = int(math.Ceil(float64(outsideWidth)*scale)), int(math.Ceil(float64(outsideHeight)*scale)) + return lastScreenWidth, lastScreenHeight +} + +func (g *Game) Update() error { + cursorX, cursorY := ebiten.CursorPosition() + g.cellX = cursorX / CellSize + g.cellY = cursorY / CellSize + if cell := g.world.At(g.cellX, g.cellY); cell != nil { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + cell.Hueristic++ + } else if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { + cell.Hueristic-- + } else if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonMiddle) { + if !g.whichFromTo { + g.fromX, g.fromY = g.cellX, g.cellY + } else { + g.toX, g.toY = g.cellX, g.cellY + } + g.whichFromTo = !g.whichFromTo + } + } + return nil +} + +func (g *Game) Draw(screen *ebiten.Image) { + for i, chunk := range g.world.Chunks { + cx, cy := float32(chunk.X*CellSize), float32(chunk.Y*CellSize) + c := color.NRGBA{255, 255, 255, 255} + c.G = 255 - uint8(i*100/len(g.world.Chunks)) + for x := 0; x < chunk.Width(); x++ { + for y := 0; y < chunk.Height(); y++ { + cell := chunk.At(x, y) + if cell == nil { + continue + } + px := cx + float32(x*CellSize) + py := cy + float32(y*CellSize) + c.A = 255 - cell.Hueristic*255/10 + + if g.cellX == chunk.X+x && g.cellY == chunk.Y+y { + c.R = 0 + c.B = 0 + } else { + c.R = 255 + c.B = 255 + c.G = 255 - uint8(i*100/len(g.world.Chunks)) + } + if g.fromX == chunk.X+x && g.fromY == chunk.Y+y { + c.R = 0 + c.G = 0 + } + if g.toX == chunk.X+x && g.toY == chunk.Y+y { + c.B = 0 + c.G = 0 + } + + vector.DrawFilledRect(screen, px, py, CellSize, CellSize, c, false) + vector.StrokeRect(screen, px, py, CellSize, CellSize, 1, color.Black, false) + s := fmt.Sprintf("%d", cell.Hueristic) + txt.Draw(screen, s, int(px+CellSize/2), int(py+CellSize/2)) + } + } + } + ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f", ebiten.ActualTPS(), ebiten.ActualFPS())) +} + +func main() { + g := NewGame() + + txt = etxt.NewRenderer() + txt.SetFont(lbrtserif.Font()) + txt.Utils().SetCache8MiB() + txt.SetColor(color.Black) + txt.SetAlign(etxt.Center) + txt.SetSize(12) + + ebiten.SetWindowSize(1280, 720) + + if err := ebiten.RunGameWithOptions(g, &ebiten.RunGameOptions{ + GraphicsLibrary: ebiten.GraphicsLibraryOpenGL, + }); err != nil { + panic(err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e1c10c8 --- /dev/null +++ b/go.mod @@ -0,0 +1,22 @@ +module gridpathingtest + +go 1.21.6 + +require ( + github.com/hajimehoshi/ebiten/v2 v2.6.5 + github.com/kettek/gobl v0.3.0 + github.com/tinne26/etxt v0.0.9-alpha.6.0.20231222075849-994f1af41032 + github.com/tinne26/fonts/liberation/lbrtserif v0.0.0-20230317183620-0b634734e4ec +) + +require ( + github.com/ebitengine/purego v0.5.0 // indirect + github.com/jezek/xgb v1.1.0 // indirect + github.com/radovskyb/watcher v1.0.7 // 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 + golang.org/x/text v0.13.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..594763f --- /dev/null +++ b/go.sum @@ -0,0 +1,56 @@ +github.com/ebitengine/purego v0.5.0 h1:JrMGKfRIAM4/QVKaesIIT7m/UVjTj5GYhRSQYwfVdpo= +github.com/ebitengine/purego v0.5.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/hajimehoshi/ebiten/v2 v2.6.5 h1:lALv+qhEK3CBWViyiGpz4YcR6slVJEjCiS7sExKZ9OE= +github.com/hajimehoshi/ebiten/v2 v2.6.5/go.mod h1:TZtorL713an00UW4LyvMeKD8uXWnuIuCPtlH11b0pgI= +github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk= +github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +github.com/kettek/gobl v0.3.0 h1:ZZ3/w8RKGni+OljEXnuEEKc+Xb+2WV5O9I9bjR+RKGI= +github.com/kettek/gobl v0.3.0/go.mod h1:x08Yfd9JU5Xqv69pggE1ieWICu+sZeMphOmhx1TSud4= +github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= +github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= +github.com/tinne26/etxt v0.0.9-alpha.6.0.20231222075849-994f1af41032 h1:ea+KUYcOiOAja3igwE4sqN+qaX4aEia2TSyazi6VvH4= +github.com/tinne26/etxt v0.0.9-alpha.6.0.20231222075849-994f1af41032/go.mod h1:Icbd4bDjrXag1oYIhB51CrkMYqRb7YMv0AsrOSfNKfU= +github.com/tinne26/fonts/liberation/lbrtserif v0.0.0-20230317183620-0b634734e4ec h1:CUSt85il4uQxLjlVhup44P7gpaZmkYooIHmCLjq85vg= +github.com/tinne26/fonts/liberation/lbrtserif v0.0.0-20230317183620-0b634734e4ec/go.mod h1:4BN4bFDBeF9+E97yjko9Pe7x8WgY4Ek6oiOYa1KDgpE= +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 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +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= diff --git a/gobl.go b/gobl.go new file mode 100644 index 0000000..195799f --- /dev/null +++ b/gobl.go @@ -0,0 +1,27 @@ +package main + +import ( + "runtime" + + . "github.com/kettek/gobl" +) + +func main() { + var exe string + if runtime.GOOS == "windows" { + exe = ".exe" + } + + runArgs := append([]interface{}{}, "./prog"+exe) + + Task("build"). + Exec("go", "build", "./cmd/prog") + Task("run"). + Exec(runArgs...) + Task("watch"). + Watch("cmd/prog/*", "sauce/*"). + Signaler(SigQuit). + Run("build"). + Run("run") + Go() +} diff --git a/sauce/astar.go b/sauce/astar.go new file mode 100644 index 0000000..a197fa9 --- /dev/null +++ b/sauce/astar.go @@ -0,0 +1,85 @@ +package sauce + +type Node struct { + parent *Node + x, y int + f, g, h int +} + +func FindPath(w *World, fromX, fromY int, toX, toY int) []Node { + openList := make([]*Node, 0) + closedList := make([]*Node, 0) + + startNode := &Node{x: fromX, y: fromY} + endNode := &Node{x: toX, y: toY} + + openList = append(openList, startNode) + + for len(openList) > 0 { + var cnode *Node + var cindex int + for i, node := range openList { + if cnode == nil || node.f < cnode.f { + cnode = node + cindex = i + } + } + openList = append(openList[:cindex], openList[cindex+1:]...) + closedList = append(closedList, cnode) + + if cnode.x == endNode.x && cnode.y == endNode.y { + path := make([]Node, 0) + for cnode != nil { + path = append(path, *cnode) + cnode = cnode.parent + } + return path + } + + var neighbors []Node + for x := -1; x <= 1; x++ { + for y := -1; y <= 1; y++ { + if x == 0 && y == 0 { + continue + } + if x != 0 && y != 0 { + continue + } + if cell := w.At(cnode.x+x, cnode.y+y); cell != nil { + neighbors = append(neighbors, Node{x: cnode.x + x, y: cnode.y + y, parent: cnode}) + } + } + } + + for _, neighbor := range neighbors { + exists := false + for _, node := range closedList { + if node.x == neighbor.x && node.y == neighbor.y { + exists = true + break + } + } + if exists { + continue + } + + exists = false + for _, node := range openList { + if node.x == neighbor.x && node.y == neighbor.y { + exists = true + break + } + } + if exists { + continue + } + + neighbor.g = cnode.g + 1 // this value potentially should be a float and modified if a diagonal. + neighbor.h = (toX - neighbor.x) + (toY - neighbor.y) // Maybe should be calculated differently. + neighbor.f = neighbor.g + neighbor.h + + openList = append(openList, &neighbor) + } + } + return nil +} diff --git a/sauce/cell.go b/sauce/cell.go new file mode 100644 index 0000000..fe6344f --- /dev/null +++ b/sauce/cell.go @@ -0,0 +1,5 @@ +package sauce + +type Cell struct { + Hueristic uint8 +} diff --git a/sauce/chunk.go b/sauce/chunk.go new file mode 100644 index 0000000..c2d4b15 --- /dev/null +++ b/sauce/chunk.go @@ -0,0 +1,31 @@ +package sauce + +type Chunk struct { + X int + Y int + Cells [][]Cell +} + +func NewChunk(width, height int) *Chunk { + c := &Chunk{} + c.Cells = make([][]Cell, width) + for i := range c.Cells { + c.Cells[i] = make([]Cell, height) + } + return c +} + +func (c *Chunk) Width() int { + return len(c.Cells) +} + +func (c *Chunk) Height() int { + return len(c.Cells[0]) +} + +func (c *Chunk) At(x, y int) *Cell { + if x < 0 || y < 0 || x >= c.Width() || y >= c.Height() { + return nil + } + return &c.Cells[x][y] +} diff --git a/sauce/world.go b/sauce/world.go new file mode 100644 index 0000000..786fd29 --- /dev/null +++ b/sauce/world.go @@ -0,0 +1,65 @@ +package sauce + +type World struct { + Chunks []*Chunk +} + +func NewWorld(width, height int) *World { + w := &World{} + + for x := 0; x < width; x++ { + for y := 0; y < height; y++ { + w.Chunks = append(w.Chunks, NewChunk(10, 10)) + w.Chunks[len(w.Chunks)-1].X = x * 10 + w.Chunks[len(w.Chunks)-1].Y = y * 10 + } + } + + return w +} + +func (w *World) At(x, y int) *Cell { + for _, chunk := range w.Chunks { + if x >= chunk.X && x < chunk.X+chunk.Width() && y >= chunk.Y && y < chunk.Y+chunk.Height() { + return chunk.At(x-chunk.X, y-chunk.Y) + } + } + return nil +} + +func (w *World) HueristicLine(x1, y1, x2, y2 int, h uint8) { + // Draw a line from x1, y1 to x2, y2 applying the hueristic value to each cell. + dx := x2 - x1 + dy := y2 - y1 + x := x1 + y := y1 + sx := 1 + sy := 1 + if dx < 0 { + sx = -1 + dx = -dx + } + if dy < 0 { + sy = -1 + dy = -dy + } + err := dx - dy + for { + cell := w.At(x, y) + if cell != nil { + cell.Hueristic = h + } + if x == x2 && y == y2 { + break + } + e2 := 2 * err + if e2 > -dy { + err -= dy + x += sx + } + if e2 < dx { + err += dx + y += sy + } + } +}