Skip to content

Commit

Permalink
more
Browse files Browse the repository at this point in the history
  • Loading branch information
tom-on-the-internet committed Nov 2, 2023
1 parent 1f1def2 commit 799379f
Show file tree
Hide file tree
Showing 7 changed files with 348 additions and 189 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Cemetery Escape

Can you get out?
Cemetery Escape is a game that you can play in your terminal.

You must escape the cemetery.
Search tombstones to find the key.
Then head for the door,
but watch out for ghosts.

## Requirements

- A [Nerd Font](https://www.nerdfonts.com/)
- A dark color scheme terminal
10 changes: 5 additions & 5 deletions ghost.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ func wanderPath(p position, m model) []position {
}

func playerNearby(p position, m model, dist int) bool {
return abs(p.x-m.playerX) <= dist && abs(p.y-m.playerY) <= dist
return abs(p.x-m.playerPos.x) <= dist && abs(p.y-m.playerPos.y) <= dist
}

func followPath(p position, m model) []position {
xTarget := m.playerX
yTarget := m.playerY
xTarget := m.playerPos.x
yTarget := m.playerPos.y
path := []position{}

for xTarget != p.x || yTarget != p.y {
Expand Down Expand Up @@ -136,7 +136,7 @@ func followPath(p position, m model) []position {
}

func huntPath(start position, m model) []position {
goal := position{x: m.playerX, y: m.playerY}
goal := m.playerPos

frontier := priorityQueue{}
frontier.put(start, 0)
Expand All @@ -157,7 +157,7 @@ func huntPath(start position, m model) []position {
neighbors := []position{}

for _, pos := range getNeighbors(current) {
if pos.x < 1 || pos.y < 1 || pos.x > m.level().width-2 || pos.y > m.level().width-2 {
if pos.x < 1 || pos.y < 1 || pos.x > m.level().width-2 || pos.y > m.level().height-2 {
continue
}

Expand Down
238 changes: 174 additions & 64 deletions levels.go

Large diffs are not rendered by default.

180 changes: 92 additions & 88 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,34 @@ type position struct {
y int
}

// model is the game model use with bubbletea.
type model struct {
username string
levels []level
levelIdx int
playerX int
playerY int
termHeight int
termWidth int
tombstonesChecked int
playerCoolDown int
windowTooSmall bool
playerHasKey bool
gameWon bool
isGameOver bool
isPaused bool
hasStarted bool
username string
levels []level
levelIdx int
playerPos position
termHeight int
termWidth int
playerCoolDown int
windowTooSmall bool
playerHasKey bool
gameWon bool
isGameOver bool
isPaused bool
hasStarted bool
}

func (m model) level() level {
return m.levels[m.levelIdx]
}

type level struct {
ghostMap map[position]*ghost
tombstoneMap map[position]*tombstone
width int
height int
doorX int
doorY int
playerStartX int
playerStartY int
ghostMap map[position]*ghost
tombstoneMap map[position]*tombstone
width int
height int
door position
playerStartPos position
}

type tombstone struct {
Expand All @@ -58,19 +55,24 @@ type nothing struct{}
type tickMsg nothing

func main() {
// flags? no.
// any args, show help and quit
if len(os.Args) > 1 {
help()
return
}

username := strings.TrimSpace(os.Getenv("USER"))
if username == "" {
username = "You"
}

// levels := []level{makeLevel(level6())}
levels := makeLevels()
initialModel := model{
username: username,
levels: levels,
levelIdx: 0,
playerX: levels[0].playerStartX,
playerY: levels[0].playerStartY,
username: username,
levels: makeLevels(),
levelIdx: 0,
playerPos: levels[0].playerStartPos,
}

p := tea.NewProgram(initialModel, tea.WithAltScreen())
Expand All @@ -79,7 +81,7 @@ func main() {
}

insanelyCleverFarewellMessage := "Ghoul-bye!"
fmt.Println(insanelyCleverFarewellMessage)
fmt.Print("\n" + insanelyCleverFarewellMessage + "\n")
}

func doTick() tea.Cmd {
Expand Down Expand Up @@ -135,28 +137,28 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Quit
case "up":
if playerCanMove("up", m) {
m.playerY--
m.playerPos.y--
m = afterPlayerMove(m)
}

return m, nil
case "down":
if playerCanMove("down", m) {
m.playerY++
m.playerPos.y++
m = afterPlayerMove(m)
}

return m, nil
case "left":
if playerCanMove("left", m) {
m.playerX--
m.playerPos.x--
m = afterPlayerMove(m)
}

return m, nil
case "right":
if playerCanMove("right", m) {
m.playerX++
m.playerPos.x++
m = afterPlayerMove(m)
}

Expand Down Expand Up @@ -198,10 +200,8 @@ func (m model) restartLevel() model {
m.isGameOver = false
m.isPaused = false
m.hasStarted = true
m.tombstonesChecked = 0
m.levels = makeLevels()
m.playerX = m.level().playerStartX
m.playerY = m.level().playerStartY
m.playerPos = m.level().playerStartPos

return m
}
Expand All @@ -213,51 +213,48 @@ func playerCanMove(direction string, m model) bool {

switch direction {
case "up":
m.playerY--
m.playerPos.y--
case "down":
m.playerY++
m.playerPos.y++
case "left":
m.playerX--
m.playerPos.x--
case "right":
m.playerX++
m.playerPos.x++
}

p := position{x: m.playerX, y: m.playerY}

if t := m.level().tombstoneMap[p]; t != nil {
if t := m.level().tombstoneMap[m.playerPos]; t != nil {
return false
}

playerInBounds := m.playerY > 0 && m.playerY < m.level().height-1 && m.playerX > 0 &&
m.playerX < m.level().width-1
playerInBounds := m.playerPos.y > 0 && m.playerPos.y < m.level().height-1 &&
m.playerPos.x > 0 &&
m.playerPos.x < m.level().width-1

return playerInBounds
}

func afterPlayerMove(m model) model {
m.playerCoolDown = 5

if m.playerHasKey && isAdjacent(m.level().doorX, m.level().doorY, m.playerX, m.playerY) {
isNextToDoor := isAdjacent(m.level().door.x, m.level().door.y, m.playerPos.x, m.playerPos.y)

if m.playerHasKey && isNextToDoor {
m.levelIdx++

if m.levelIdx == len(m.levels) {
m.gameWon = true
} else {
m.playerX = m.level().playerStartX
m.playerY = m.level().playerStartY
m.playerHasKey = false
m.tombstonesChecked = 0
return m
}

m.playerPos = m.level().playerStartPos
m.playerHasKey = false

return m
}

// could make an adjacency map
for p, stone := range m.level().tombstoneMap {
if isAdjacent(p.x, p.y, m.playerX, m.playerY) {
if !stone.checked {
stone.checked = true
m.tombstonesChecked++
}
for stonePos, stone := range m.level().tombstoneMap {
if isAdjacent(stonePos.x, stonePos.y, m.playerPos.x, m.playerPos.y) {
stone.checked = true

if stone.hasKey {
m.playerHasKey = true
Expand All @@ -269,47 +266,54 @@ func afterPlayerMove(m model) model {
}

func onTick(m model) model {
// create a new map so we don't wipe out an existing ghost
// when moving another ghost
newGhostMap := map[position]*ghost{}

for currPoint, g := range m.level().ghostMap {
pointToUse := currPoint
if g.cooldown <= 0 {
if len(g.path) > 0 {
nextPoint := g.path[len(g.path)-1]
g.path = g.path[:len(g.path)-1]

if ghostCanMove(nextPoint, g, newGhostMap, m) {
pointToUse = nextPoint
g.cooldown = 17

if g.kind == "follow" {
g.cooldown = 12
}
if g.kind == "hunt" {
g.cooldown = 12
}
} else {
g.path = []position{}
}
} else {
g.path = findPathForGhost(currPoint, *g, m)
g.cooldown = 50
if g.kind == "hunt" {
g.cooldown = 0
}
}
} else {
if g.cooldown > 0 {
g.cooldown--
newGhostMap[currPoint] = g

continue
}

if len(g.path) == 0 {
g.path = findPathForGhost(currPoint, *g, m)
g.cooldown = 50

if g.kind == "hunt" {
g.cooldown = 0
}

newGhostMap[currPoint] = g

continue
}

nextPoint := g.path[len(g.path)-1]
g.path = g.path[:len(g.path)-1]

if !ghostCanMove(nextPoint, g, newGhostMap, m) {
g.path = []position{}
newGhostMap[currPoint] = g

continue
}

g.cooldown = 12
if g.kind == "hunt" {
g.cooldown = 8
}

newGhostMap[pointToUse] = g
newGhostMap[nextPoint] = g
}

m.playerCoolDown--

m.levels[m.levelIdx].ghostMap = newGhostMap

playerOnGhost := m.level().ghostMap[position{x: m.playerX, y: m.playerY}] != nil
playerOnGhost := m.level().ghostMap[m.playerPos] != nil
if playerOnGhost {
m.isGameOver = true
}
Expand Down
9 changes: 8 additions & 1 deletion priority-queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import (
"sort"
)

// priorityQueue is a data structure used for storing
// positions and always fetching the item with the lowest priority number.
type priorityQueue struct {
// TODO: Don't use an array for this
// Use a binary tree
items []pqItem
}

Expand All @@ -20,9 +24,12 @@ func (pq *priorityQueue) put(pos position, priority int) {
})
}

// get returns the item with the lowest priority number.
// call empty() first to ensure this queue is not empty, or
// it will panic.
func (pq *priorityQueue) get() position {
if len(pq.items) == 0 {
return position{-1, -1}
panic("Cannot get from empty priorityQueue")
}

pos := pq.items[0].pos
Expand Down
Loading

0 comments on commit 799379f

Please sign in to comment.