Next phase worked out, but had to move some stuff around in the final result as well.

master
Zed A. Shaw 11 months ago
parent 00647a20e1
commit f7fd04e8e0
  1. 39
      02_mazes_and_enemies/data.go
  2. 14
      02_mazes_and_enemies/debug.go
  3. 44
      02_mazes_and_enemies/game.go
  4. 367
      02_mazes_and_enemies/main.go
  5. 67
      02_mazes_and_enemies/map.go
  6. 83
      02_mazes_and_enemies/maze.go
  7. 13
      02_mazes_and_enemies/movement.go
  8. 118
      02_mazes_and_enemies/ui.go
  9. 13
      04_combat_multifile/map.go
  10. 15
      04_combat_multifile/maze.go
  11. 27
      04_combat_multifile/pathing.go

@ -0,0 +1,39 @@
package main
import (
"github.com/gdamore/tcell/v2"
)
const (
WALL = '#'
SPACE = '.'
PATH_LIMIT = 1000
RENDER = true
SHOW_RENDER = false
SHOW_PATHS = false
HEARING_DISTANCE = 6
)
type Map [][]rune
type Paths [][]int
type Position struct {
X int
Y int
}
type Enemy struct {
HP int
Pos Position
Damage int
}
type Game struct {
Screen tcell.Screen
Level Map
Player Enemy
Status string
Width int
Height int
Enemies map[Position]*Enemy
}

@ -0,0 +1,14 @@
package main
import (
"log"
"os"
)
var dbg *log.Logger
func DebugInit() {
out, err := os.Create("debug.log")
if err != nil { log.Fatal(err) }
dbg = log.New(out, "", log.LstdFlags)
}

@ -0,0 +1,44 @@
package main
import (
"os"
"math/rand"
)
func NewGame(width int, height int) (*Game) {
var game Game
game.Width = width
game.Height = height
game.Enemies = make(map[Position]*Enemy)
game.Level = make(Map, height, height)
game.Player = Enemy{20, Position{1,1}, 4}
return &game
}
func (game *Game) Exit() {
if RENDER {
game.Screen.Fini()
}
os.Exit(0)
}
func (game *Game) PlaceEnemies(places []Position) {
for _, pos := range places {
if rand.Int() % 2 == 0 {
game.Enemies[pos] = &Enemy{10, pos, 4}
}
}
}
func (game *Game) Restart() {
game.SetStatus("YOU DIED! Try again.")
game.Player.HP = 20
game.Player.Pos = Position{1,1}
clear(game.Enemies)
game.FillMap(game.Level, '#')
game.Render()
}

@ -1,376 +1,17 @@
package main package main
import (
"os"
"slices"
"log"
"fmt"
"math/rand"
"time"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/encoding"
)
var dbg *log.Logger
const (
WALL = '#'
SPACE = '.'
PATH_LIMIT = 1000
RENDER = true
SHOW_RENDER = false
)
type Map [][]rune
type Paths [][]int
type Position struct {
x int
y int
}
type Enemy struct {
hp int
pos Position
damage int
}
type Game struct {
screen tcell.Screen
level Map
player Enemy
status string
width int
height int
enemies map[Position]*Enemy
}
func (game *Game) DrawText(x int, y int, text string) {
for i, cell := range text {
game.screen.SetContent(x+i, y, cell, nil, tcell.StyleDefault)
}
}
func (game *Game) DrawStatus() {
game.DrawText(0, game.height, game.status)
hp := fmt.Sprintf("HP: %d", game.player.hp)
game.DrawText(game.width - len(hp), game.height, hp)
}
func (game *Game) Status(msg string) {
game.status = msg
}
func (game *Game) DrawEntity(symbol rune, pos Position, color tcell.Color) {
style := tcell.StyleDefault.Bold(true).Foreground(color)
game.screen.SetContent(pos.x, pos.y, symbol, nil, style)
}
func (game *Game) DrawMap() {
gray := tcell.StyleDefault.Foreground(tcell.ColorGray)
for y, line := range game.level {
for x, cell := range line {
if cell == SPACE {
game.screen.SetContent(x, y, cell, nil, gray)
} else {
game.screen.SetContent(x, y, cell, nil, tcell.StyleDefault)
}
}
}
}
func (game *Game) Render() {
if !RENDER { return }
game.screen.Clear()
game.DrawMap()
game.DrawEntity('@', game.player.pos, tcell.ColorYellow)
for pos, _ := range game.enemies {
game.DrawEntity('G', pos, tcell.ColorRed)
}
game.DrawStatus()
game.screen.Show()
}
func (game *Game) Exit() {
if RENDER {
game.screen.Fini()
}
os.Exit(0)
}
func (game *Game) Occupied(x int, y int) bool {
pos := Position{x, y}
_, is_enemy := game.enemies[pos]
is_player := pos == game.player.pos
// Inbounds comes first to prevent accessing level with bad x,y
return !game.Inbounds(pos, 1) ||
game.level[y][x] == WALL ||
is_enemy ||
is_player
}
func (game *Game) MovePlayer(x_delta int, y_delta int) {
target := Position{
game.player.pos.x + x_delta,
game.player.pos.y + y_delta,
}
if !game.Occupied(target.x, target.y) {
game.player.pos = target
}
}
func (game *Game) HandleKeys(ev *tcell.EventKey) bool {
switch ev.Key() {
case tcell.KeyEscape:
return false
case tcell.KeyUp:
game.MovePlayer(0, -1)
case tcell.KeyDown:
game.MovePlayer(0, 1)
case tcell.KeyRight:
game.MovePlayer(1, 0)
case tcell.KeyLeft:
game.MovePlayer(-1, 0)
}
switch ev.Rune() {
case 'q':
return false
}
return true
}
func (game *Game) HandleEvents() bool {
if !RENDER { return false }
switch ev := game.screen.PollEvent().(type) {
case *tcell.EventResize:
game.screen.Sync()
case *tcell.EventKey:
return game.HandleKeys(ev)
}
return true
}
func (game *Game) InitScreen() {
encoding.Register()
var err error
game.screen, err = tcell.NewScreen()
if err != nil { log.Fatal(err) }
err = game.screen.Init()
if err != nil { log.Fatal(err) }
}
func NewGame(width int, height int) (*Game) {
var game Game
game.width = width
game.height = height
game.enemies = make(map[Position]*Enemy)
game.level = make(Map, height, height)
game.player = Enemy{20, Position{1,1}, 4}
return &game
}
func compass(x int, y int, offset int) []Position {
return []Position{
Position{x, y - offset},
Position{x, y + offset},
Position{x + offset, y},
Position{ x - offset, y},
}
}
func (game *Game) Inbounds(pos Position, offset int) bool {
return pos.x >= offset &&
pos.x < game.width - offset &&
pos.y >= offset &&
pos.y < game.height - offset
}
func (game *Game) Neighbors(near Position) []Position {
result := make([]Position, 0, 4)
points := compass(near.x, near.y, 2)
for _, pos := range points {
if game.Inbounds(pos, 0) {
result = append(result, pos)
}
}
return result
}
func (game *Game) NeighborWalls(pos Position) []Position {
neighbors := game.Neighbors(pos)
result := make([]Position, 0)
for _, at := range neighbors {
cell := game.level[at.y][at.x]
if cell == WALL {
result = append(result, at)
}
}
return result
}
func (game *Game) FindCoord(on *Position, found *Position) bool {
for y := 1; y < game.height ; y += 2 {
for x := 1; x < game.width ; x += 2 {
if game.level[y][x] != WALL {
continue
}
neighbors := game.Neighbors(Position{x, y})
for _, pos := range neighbors {
if game.level[pos.y][pos.x] == SPACE {
*on = Position{x, y}
*found = pos
return true
}
}
}
}
return false
}
func (game *Game) HAKStep(from Position, to Position) {
game.level[from.y][from.x] = SPACE
row := (from.y + to.y) / 2
col := (from.x + to.x) / 2
game.level[row][col] = SPACE
}
func (game *Game) HuntAndKill() []Position {
on := Position{1, 1}
found := Position{1,1}
dead_ends := make([]Position, 0)
for {
neighbors := game.NeighborWalls(on)
if len(neighbors) == 0 {
dead_ends = append(dead_ends, on)
if !game.FindCoord(&on, &found) {
break
}
game.HAKStep(on, found)
} else {
rand_neighbor := rand.Int() % len(neighbors)
nb := neighbors[rand_neighbor]
game.HAKStep(nb, on)
on = nb
}
if SHOW_RENDER {
game.Render()
time.Sleep(50 * time.Millisecond)
}
}
return dead_ends
}
func (game *Game) FillMap(target Map, setting rune) {
for y := 0 ; y < game.height; y++ {
target[y] = slices.Repeat([]rune{setting}, game.width)
}
}
func (game *Game) CarveRoom(pos Position, size int) {
// only use ones far enough inside
for y := pos.y - size; y < pos.y + size; y++ {
for x := pos.x - size; x < pos.x + size; x++ {
if game.Inbounds(Position{x, y}, 1) {
game.level[y][x] = SPACE
}
}
}
}
func (game *Game) CloneMap() Map {
// this is a shallow copy though
new_map := slices.Clone(game.level)
for i, row := range new_map {
// this makes sure the row is an actual copy
new_map[i] = slices.Clone(row)
}
return new_map
}
func (game *Game) AddRooms(dead_ends []Position, size int) {
rand.Shuffle(len(dead_ends), func(i, j int) {
dead_ends[i], dead_ends[j] = dead_ends[j], dead_ends[i]
})
for _, pos := range dead_ends[0:4] {
rs := rand.Int() % size + 1
game.CarveRoom(pos, rs)
}
}
func (game *Game) NewMap() []Position {
game.FillMap(game.level, '#')
dead_ends := game.HuntAndKill()
game.FillMap(game.level, '#')
game.AddRooms(dead_ends, game.height / 8)
dead_ends = game.HuntAndKill()
return dead_ends
}
func (game *Game) PlaceEnemies(places []Position) {
for _, pos := range places {
if rand.Int() % 2 == 0 {
game.enemies[pos] = &Enemy{10, pos, 4}
}
}
}
func main() { func main() {
out, err := os.Create("debug.log") DebugInit()
if err != nil { log.Fatal(err) }
dbg = log.New(out, "", log.LstdFlags)
game := NewGame(27, 17) game := NewGame(27, 17)
game.InitScreen() game.InitScreen()
dead_ends := game.NewMap() game.NewMap()
dead_ends := game.NewMaze()
game.PlaceEnemies(dead_ends) game.PlaceEnemies(dead_ends)
game.Render() game.Render()
for game.HandleEvents() && game.player.hp > 0 { for game.HandleEvents() && game.Player.HP > 0 {
game.Render() game.Render()
} }

@ -0,0 +1,67 @@
package main
import (
"slices"
)
func compass(near Position, offset int) []Position {
return []Position{
Position{near.X, near.Y - offset},
Position{near.X, near.Y + offset},
Position{near.X + offset, near.Y},
Position{near.X - offset, near.Y},
}
}
func (game *Game) CloneMap() Map {
// this is a shallow copy though
new_map := slices.Clone(game.Level)
for i, row := range new_map {
// this makes sure the row is an actual copy
new_map[i] = slices.Clone(row)
}
return new_map
}
func (game *Game) Inbounds(pos Position, offset int) bool {
return pos.X >= offset &&
pos.X < game.Width - offset &&
pos.Y >= offset &&
pos.Y < game.Height - offset
}
func (game *Game) Occupied(pos Position) bool {
_, is_enemy := game.Enemies[pos]
is_player := pos == game.Player.Pos
// Inbounds comes first to prevent accessing level with bad x,y
return !game.Inbounds(pos, 1) ||
game.Level[pos.Y][pos.X] == WALL ||
is_enemy ||
is_player
}
func (game *Game) FillMap(target Map, setting rune) {
for y := 0 ; y < game.Height; y++ {
target[y] = slices.Repeat([]rune{setting}, game.Width)
}
}
func (game *Game) NewMap() {
game.FillMap(game.Level, '#')
}
func (game *Game) Neighbors(near Position) []Position {
result := make([]Position, 0, 4)
points := compass(near, 2)
for _, pos := range points {
if game.Inbounds(pos, 0) {
result = append(result, pos)
}
}
return result
}

@ -0,0 +1,83 @@
package main
import (
"math/rand"
"time"
)
func (game *Game) NeighborWalls(pos Position) []Position {
neighbors := game.Neighbors(pos)
result := make([]Position, 0)
for _, at := range neighbors {
cell := game.Level[at.Y][at.X]
if cell == WALL {
result = append(result, at)
}
}
return result
}
func (game *Game) HuntNext(on *Position, found *Position) bool {
for y := 1; y < game.Height ; y += 2 {
for x := 1; x < game.Width ; x += 2 {
if game.Level[y][x] != WALL {
continue
}
neighbors := game.Neighbors(Position{x, y})
for _, pos := range neighbors {
if game.Level[pos.Y][pos.X] == SPACE {
*on = Position{x, y}
*found = pos
return true
}
}
}
}
return false
}
func (game *Game) HAKStep(from Position, to Position) {
game.Level[from.Y][from.X] = SPACE
row := (from.Y + to.Y) / 2
col := (from.X + to.X) / 2
game.Level[row][col] = SPACE
}
func (game *Game) NewMaze() []Position {
on := Position{1, 1}
found := Position{1,1}
dead_ends := make([]Position, 0)
for {
neighbors := game.NeighborWalls(on)
if len(neighbors) == 0 {
dead_ends = append(dead_ends, on)
if !game.HuntNext(&on, &found) {
break
}
game.HAKStep(on, found)
} else {
rand_neighbor := rand.Int() % len(neighbors)
nb := neighbors[rand_neighbor]
game.HAKStep(nb, on)
on = nb
}
if SHOW_RENDER {
game.Render()
time.Sleep(50 * time.Millisecond)
}
}
return dead_ends
}

@ -0,0 +1,13 @@
package main
func (game *Game) MovePlayer(x_delta int, y_delta int) {
target := Position{
game.Player.Pos.X + x_delta,
game.Player.Pos.Y + y_delta,
}
if !game.Occupied(target) {
game.Player.Pos = target
}
}

@ -0,0 +1,118 @@
package main
import (
"log"
"fmt"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/encoding"
)
//// DRAWING
func (game *Game) DrawText(x int, y int, text string) {
for i, cell := range text {
game.Screen.SetContent(x+i, y, cell, nil, tcell.StyleDefault)
}
}
func (game *Game) DrawStatus() {
game.DrawText(0, game.Height, game.Status)
hp := fmt.Sprintf("HP: %d", game.Player.HP)
game.DrawText(game.Width - len(hp), game.Height, hp)
}
func (game *Game) SetStatus(msg string) {
game.Status = msg
}
func (game *Game) DrawEntity(symbol rune, pos Position, color tcell.Color) {
style := tcell.StyleDefault.Bold(true).Foreground(color)
game.Screen.SetContent(pos.X, pos.Y, symbol, nil, style)
}
func (game *Game) DrawMap() {
gray := tcell.StyleDefault.Foreground(tcell.ColorGray)
for y, line := range game.Level {
for x, cell := range line {
if cell == SPACE {
game.Screen.SetContent(x, y, cell, nil, gray)
} else {
game.Screen.SetContent(x, y, cell, nil, tcell.StyleDefault)
}
}
}
}
///// RENDERING
func (game *Game) InitScreen() {
var err error
encoding.Register()
game.Screen, err = tcell.NewScreen()
// using log.Fatal instead of dbg.Fatal
// because the screen isn't setup yet
if err != nil { log.Fatal(err) }
err = game.Screen.Init()
if err != nil { log.Fatal(err) }
}
func (game *Game) Render() {
if !RENDER { return }
game.Screen.Clear()
game.DrawMap()
game.DrawEntity('@', game.Player.Pos, tcell.ColorYellow)
for pos, _ := range game.Enemies {
game.DrawEntity('G', pos, tcell.ColorRed)
}
game.DrawStatus()
game.Screen.Show()
}
//// EVENTS
func (game *Game) HandleKeys(ev *tcell.EventKey) bool {
switch ev.Key() {
case tcell.KeyEscape:
return false
case tcell.KeyUp:
game.MovePlayer(0, -1)
case tcell.KeyDown:
game.MovePlayer(0, 1)
case tcell.KeyRight:
game.MovePlayer(1, 0)
case tcell.KeyLeft:
game.MovePlayer(-1, 0)
}
switch ev.Rune() {
case 'q':
return false
}
return true
}
func (game *Game) HandleEvents() bool {
if !RENDER { return false }
switch ev := game.Screen.PollEvent().(type) {
case *tcell.EventResize:
game.Screen.Sync()
case *tcell.EventKey:
return game.HandleKeys(ev)
}
return true
}

@ -52,3 +52,16 @@ func (game *Game) FillMap(target Map, setting rune) {
func (game *Game) NewMap() { func (game *Game) NewMap() {
game.FillMap(game.Level, '#') game.FillMap(game.Level, '#')
} }
func (game *Game) Neighbors(near Position) []Position {
result := make([]Position, 0, 4)
points := compass(near, 2)
for _, pos := range points {
if game.Inbounds(pos, 0) {
result = append(result, pos)
}
}
return result
}

@ -5,6 +5,21 @@ import (
"time" "time"
) )
func (game *Game) NeighborWalls(pos Position) []Position {
neighbors := game.Neighbors(pos)
result := make([]Position, 0)
for _, at := range neighbors {
cell := game.Level[at.Y][at.X]
if cell == WALL {
result = append(result, at)
}
}
return result
}
func (game *Game) HuntNext(on *Position, found *Position) bool { func (game *Game) HuntNext(on *Position, found *Position) bool {
for y := 1; y < game.Height ; y += 2 { for y := 1; y < game.Height ; y += 2 {
for x := 1; x < game.Width ; x += 2 { for x := 1; x < game.Width ; x += 2 {

@ -10,33 +10,6 @@ func (game *Game) FillPaths(target Paths, setting int) {
} }
} }
func (game *Game) Neighbors(near Position) []Position {
result := make([]Position, 0, 4)
points := compass(near, 2)
for _, pos := range points {
if game.Inbounds(pos, 0) {
result = append(result, pos)
}
}
return result
}
func (game *Game) NeighborWalls(pos Position) []Position {
neighbors := game.Neighbors(pos)
result := make([]Position, 0)
for _, at := range neighbors {
cell := game.Level[at.Y][at.X]
if cell == WALL {
result = append(result, at)
}
}
return result
}
func (game *Game) PathAddNeighbors(neighbors []Position, closed Map, near Position) []Position { func (game *Game) PathAddNeighbors(neighbors []Position, closed Map, near Position) []Position {
points := compass(near, 1) points := compass(near, 1)

Loading…
Cancel
Save