Moved level state to soko package.
Renamed entity type character and egg to player and target respectively.
This commit is contained in:
parent
1e9d3e9089
commit
93be82bdbd
@ -14,9 +14,8 @@ type moveAnimation struct {
|
|||||||
pos geom.PointF32
|
pos geom.PointF32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMoveAnimation(e *entity, to geom.Point) *moveAnimation {
|
func newMoveAnimation(e *entity, from, to geom.Point) *moveAnimation {
|
||||||
ani := &moveAnimation{e: e, from: e.pos, to: to, pos: e.pos.ToF32()}
|
ani := &moveAnimation{e: e, from: from, to: to, pos: from.ToF32()}
|
||||||
ani.e.pos = to
|
|
||||||
return ani
|
return ani
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type entity struct {
|
type entity struct {
|
||||||
|
id int
|
||||||
typ soko.EntityType
|
typ soko.EntityType
|
||||||
pos geom.Point
|
|
||||||
scr entityLoc
|
scr entityLoc
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,6 +16,6 @@ type entityLoc struct {
|
|||||||
z float32
|
z float32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEntity(typ soko.EntityType, pos geom.Point) *entity {
|
func newEntity(e soko.Entity) *entity {
|
||||||
return &entity{typ, pos, entityLoc{pos.ToF32(), 0}}
|
return &entity{e.ID, e.Typ, entityLoc{e.Pos.ToF32(), 0}}
|
||||||
}
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "opslag.de/schobers/geom"
|
|
||||||
|
|
||||||
type entityList []*entity
|
|
||||||
|
|
||||||
func (l entityList) Add(e *entity) entityList {
|
|
||||||
return append(l, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l entityList) AddList(list entityList) entityList {
|
|
||||||
return append(l, list...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l entityList) Find(pos geom.Point) int {
|
|
||||||
for i, e := range l {
|
|
||||||
if e.pos == pos {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l entityList) FindEntity(pos geom.Point) *entity {
|
|
||||||
idx := l.Find(pos)
|
|
||||||
if idx == -1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return l[idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l entityList) Remove(pos geom.Point) entityList {
|
|
||||||
idx := l.Find(pos)
|
|
||||||
return append(l[:idx], l[idx+1:]...)
|
|
||||||
}
|
|
25
cmd/krampus19/entitymap.go
Normal file
25
cmd/krampus19/entitymap.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
type entityMap map[int]*entity
|
||||||
|
|
||||||
|
func (m entityMap) RenderOrder() []*entity {
|
||||||
|
entities := make([]*entity, 0, len(m))
|
||||||
|
for _, e := range m {
|
||||||
|
entities = append(entities, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(entities, func(i, j int) bool {
|
||||||
|
var posI, posJ = entities[i].scr.pos, entities[j].scr.pos
|
||||||
|
if posI.Y == posJ.Y {
|
||||||
|
if posI.X == posJ.X {
|
||||||
|
return entities[i].id < entities[j].id
|
||||||
|
}
|
||||||
|
return posI.X < posJ.X
|
||||||
|
}
|
||||||
|
return posI.Y < posJ.Y
|
||||||
|
})
|
||||||
|
|
||||||
|
return entities
|
||||||
|
}
|
@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"sort"
|
|
||||||
|
|
||||||
"opslag.de/schobers/allg5"
|
"opslag.de/schobers/allg5"
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
@ -204,7 +203,7 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
|||||||
}
|
}
|
||||||
case soko.TileTypeMagma:
|
case soko.TileTypeMagma:
|
||||||
l.drawSprite("magma", "magma", scr)
|
l.drawSprite("magma", "magma", scr)
|
||||||
brick := l.state.FindSunkenBrick(pos)
|
brick := l.state.FindSunkenBrickEntity(pos)
|
||||||
if brick != nil {
|
if brick != nil {
|
||||||
behind, front := splitParticles(scr.pos.Y, l.state.Particles(pos))
|
behind, front := splitParticles(scr.pos.Y, l.state.Particles(pos))
|
||||||
drawParticles(behind)
|
drawParticles(behind)
|
||||||
@ -215,21 +214,14 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entities := l.state.Entities()
|
entities := l.state.Entities().RenderOrder()
|
||||||
sort.Slice(entities, func(i, j int) bool {
|
|
||||||
if entities[i].scr.pos.Y == entities[j].scr.pos.Y {
|
|
||||||
return entities[i].scr.pos.X < entities[j].scr.pos.X
|
|
||||||
}
|
|
||||||
return entities[i].scr.pos.Y < entities[j].scr.pos.Y
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, e := range entities {
|
for _, e := range entities {
|
||||||
switch e.typ {
|
switch e.typ {
|
||||||
case soko.EntityTypeBrick:
|
case soko.EntityTypeBrick:
|
||||||
l.drawSprite("brick", "brick", e.scr)
|
l.drawSprite("brick", "brick", e.scr)
|
||||||
case soko.EntityTypeCharacter:
|
case soko.EntityTypePlayer:
|
||||||
l.drawSprite("dragon", "dragon", e.scr)
|
l.drawSprite("dragon", "dragon", e.scr)
|
||||||
case soko.EntityTypeEgg:
|
case soko.EntityTypeTarget:
|
||||||
l.drawSprite("egg", "egg", e.scr)
|
l.drawSprite("egg", "egg", e.scr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,11 @@ type playLevelState struct {
|
|||||||
|
|
||||||
pack levelPack
|
pack levelPack
|
||||||
level soko.Level
|
level soko.Level
|
||||||
|
state soko.State
|
||||||
player *entity
|
player *entity
|
||||||
egg *entity
|
egg *entity
|
||||||
bricks entityList
|
bricks entityMap
|
||||||
sunken entityList
|
sunken entityMap
|
||||||
splash map[geom.Point]*splashAnimation
|
splash map[geom.Point]*splashAnimation
|
||||||
|
|
||||||
steps int
|
steps int
|
||||||
@ -30,9 +31,14 @@ type playLevelState struct {
|
|||||||
keysDown keyPressedState
|
keysDown keyPressedState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) Entities() entityList {
|
func (s *playLevelState) Entities() entityMap {
|
||||||
var entities entityList
|
entities := entityMap{}
|
||||||
return entities.Add(s.player).Add(s.egg).AddList(s.bricks)
|
entities[s.player.id] = s.player
|
||||||
|
entities[s.egg.id] = s.egg
|
||||||
|
for id, e := range s.bricks {
|
||||||
|
entities[id] = e
|
||||||
|
}
|
||||||
|
return entities
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) Particles(at geom.Point) []particle {
|
func (s *playLevelState) Particles(at geom.Point) []particle {
|
||||||
@ -49,34 +55,34 @@ func (s *playLevelState) Particles(at geom.Point) []particle {
|
|||||||
return particles
|
return particles
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) FindSunkenBrick(pos geom.Point) *entity {
|
func (s *playLevelState) FindSunkenBrickEntity(pos geom.Point) *entity {
|
||||||
return s.sunken.FindEntity(pos)
|
id, ok := s.state.SunkenBricks[pos]
|
||||||
|
if ok {
|
||||||
|
return s.sunken[id]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) IsNextToMagma(pos geom.Point) bool {
|
func (s *playLevelState) IsNextToMagma(pos geom.Point) bool {
|
||||||
return s.checkTile(pos.Add2D(1, 0), s.isMagma) ||
|
return s.state.Any(soko.IsMagma, soko.Neighbours(pos)...)
|
||||||
s.checkTile(pos.Add2D(-1, 0), s.isMagma) ||
|
|
||||||
s.checkTile(pos.Add2D(0, -1), s.isMagma) ||
|
|
||||||
s.checkTile(pos.Add2D(0, 1), s.isMagma)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) Init(ctx *Context, pack, level string, onComplete func()) {
|
func (s *playLevelState) Init(ctx *Context, pack, level string, onComplete func()) {
|
||||||
s.ctx = ctx
|
s.ctx = ctx
|
||||||
s.pack = ctx.Levels.ByID(pack)
|
s.pack = ctx.Levels.ByID(pack)
|
||||||
s.level = s.pack.levels[level]
|
s.level = s.pack.levels[level]
|
||||||
|
s.state = s.level.State()
|
||||||
s.bricks = nil
|
s.bricks = nil
|
||||||
s.sunken = nil
|
s.sunken = nil
|
||||||
s.splash = map[geom.Point]*splashAnimation{}
|
s.splash = map[geom.Point]*splashAnimation{}
|
||||||
for i, e := range s.level.Entities {
|
|
||||||
switch e {
|
s.player = newEntity(s.state.Player)
|
||||||
case soko.EntityTypeBrick:
|
s.egg = newEntity(s.state.Target)
|
||||||
s.bricks = append(s.bricks, newEntity(e, s.level.IdxToPos(i)))
|
s.bricks = entityMap{}
|
||||||
case soko.EntityTypeCharacter:
|
for pos, id := range s.state.Bricks {
|
||||||
s.player = newEntity(e, s.level.IdxToPos(i))
|
s.bricks[id] = newEntity(soko.Entity{ID: id, Pos: pos, Typ: soko.EntityTypeBrick})
|
||||||
case soko.EntityTypeEgg:
|
|
||||||
s.egg = newEntity(e, s.level.IdxToPos(i))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
s.sunken = entityMap{}
|
||||||
s.keysDown = keyPressedState{}
|
s.keysDown = keyPressedState{}
|
||||||
s.onComplete = onComplete
|
s.onComplete = onComplete
|
||||||
}
|
}
|
||||||
@ -98,42 +104,45 @@ func (s *playLevelState) Tick(now time.Duration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) TryPlayerMove(key allg5.Key) {
|
func (s *playLevelState) TryPlayerMove(key allg5.Key) {
|
||||||
var dir geom.Point
|
var dir soko.Direction
|
||||||
switch key {
|
switch key {
|
||||||
case s.ctx.Settings.Controls.MoveUp:
|
case s.ctx.Settings.Controls.MoveUp:
|
||||||
dir = geom.Pt(0, -1)
|
dir = soko.DirectionUp
|
||||||
case s.ctx.Settings.Controls.MoveRight:
|
case s.ctx.Settings.Controls.MoveRight:
|
||||||
dir = geom.Pt(1, 0)
|
dir = soko.DirectionRight
|
||||||
case s.ctx.Settings.Controls.MoveDown:
|
case s.ctx.Settings.Controls.MoveDown:
|
||||||
dir = geom.Pt(0, 1)
|
dir = soko.DirectionDown
|
||||||
case s.ctx.Settings.Controls.MoveLeft:
|
case s.ctx.Settings.Controls.MoveLeft:
|
||||||
dir = geom.Pt(-1, 0)
|
dir = soko.DirectionLeft
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.tryPlayerMove(dir, key)
|
s.tryPlayerMove(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) tryPlayerMove(dir geom.Point, key allg5.Key) {
|
func (s *playLevelState) tryPlayerMove(dir soko.Direction) {
|
||||||
if s.player.scr.pos != s.player.pos.ToF32() {
|
if s.player.scr.pos != s.state.Player.Pos.ToF32() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
to := s.player.pos.Add(dir)
|
state, ok := s.state.MovePlayer(dir)
|
||||||
if !s.canMove(s.player.pos, dir) {
|
if !ok {
|
||||||
log.Printf("Move is not allowed (tried out move to %s after key '%s' was pressed)", to, gut.KeyToString(key))
|
log.Printf("Move is not allowed (tried out move %s)", dir.String())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if brick := s.bricks.FindEntity(to); brick != nil {
|
to := state.Player.Pos
|
||||||
log.Printf("Pushing brick at %s", to)
|
|
||||||
brickTo := to.Add(dir)
|
if brickID, ok := s.state.Bricks[to]; ok {
|
||||||
s.ani.StartFn(s.ctx.Tick, newMoveAnimation(brick, brickTo), func() {
|
log.Printf("Brick %d moved", brickID)
|
||||||
|
brick := s.bricks[brickID]
|
||||||
|
brickTo := state.Entities[brickID].Pos
|
||||||
|
s.ani.StartFn(s.ctx.Tick, newMoveAnimation(brick, to, brickTo), func() {
|
||||||
log.Printf("Brick movement finished")
|
log.Printf("Brick movement finished")
|
||||||
if s.checkTile(brickTo, s.wouldBrickSink) {
|
if sunkenBrickID, ok := state.SunkenBricks[brickTo]; ok && sunkenBrickID == brickID {
|
||||||
log.Printf("Sinking brick at %s", brickTo)
|
log.Printf("Sinking brick %d", brickID)
|
||||||
s.bricks = s.bricks.Remove(brickTo)
|
delete(s.bricks, brickID)
|
||||||
s.sunken = s.sunken.Add(brick)
|
s.sunken[brickID] = brick
|
||||||
s.ani.Start(s.ctx.Tick, newSinkAnimation(brick))
|
s.ani.Start(s.ctx.Tick, newSinkAnimation(brick))
|
||||||
|
|
||||||
splash := newSplashAnimation(brickTo)
|
splash := newSplashAnimation(brickTo)
|
||||||
@ -147,9 +156,9 @@ func (s *playLevelState) tryPlayerMove(dir geom.Point, key allg5.Key) {
|
|||||||
|
|
||||||
s.steps++
|
s.steps++
|
||||||
log.Printf("Moving player to %s", to)
|
log.Printf("Moving player to %s", to)
|
||||||
s.ani.StartFn(s.ctx.Tick, newMoveAnimation(s.player, to), func() {
|
s.ani.StartFn(s.ctx.Tick, newMoveAnimation(s.player, s.state.Player.Pos, to), func() {
|
||||||
log.Printf("Player movement finished")
|
log.Printf("Player movement finished")
|
||||||
if s.player.pos == s.egg.pos {
|
if to == state.Target.Pos {
|
||||||
s.complete = true
|
s.complete = true
|
||||||
if onComplete := s.onComplete; onComplete != nil {
|
if onComplete := s.onComplete; onComplete != nil {
|
||||||
onComplete()
|
onComplete()
|
||||||
@ -157,76 +166,11 @@ func (s *playLevelState) tryPlayerMove(dir geom.Point, key allg5.Key) {
|
|||||||
} else {
|
} else {
|
||||||
pressed := s.keysDown.ArePressed(s.ctx.Settings.Controls.MovementKeys()...)
|
pressed := s.keysDown.ArePressed(s.ctx.Settings.Controls.MovementKeys()...)
|
||||||
if len(pressed) == 1 {
|
if len(pressed) == 1 {
|
||||||
|
key := pressed[0]
|
||||||
log.Printf("Movement key %s is down, moving further", gut.KeyToString(key))
|
log.Printf("Movement key %s is down, moving further", gut.KeyToString(key))
|
||||||
s.TryPlayerMove(pressed[0])
|
s.TryPlayerMove(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
s.state = state
|
||||||
|
|
||||||
func (s *playLevelState) canMove(from, dir geom.Point) bool {
|
|
||||||
to := from.Add(dir)
|
|
||||||
if !s.checkTile(to, s.isSolidTile) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
brick := s.bricks.FindEntity(to)
|
|
||||||
if brick != nil {
|
|
||||||
brickTo := to.Add(dir)
|
|
||||||
return !s.checkTileNotFound(brickTo, s.isObstructed, true)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *playLevelState) checkTile(pos geom.Point, check func(pos geom.Point, idx int, t soko.TileType) bool) bool {
|
|
||||||
return s.checkTileNotFound(pos, check, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *playLevelState) checkTileNotFound(pos geom.Point, check func(pos geom.Point, idx int, t soko.TileType) bool, notFound bool) bool {
|
|
||||||
idx := s.level.PosToIdx(pos)
|
|
||||||
if idx == -1 {
|
|
||||||
return notFound
|
|
||||||
}
|
|
||||||
return check(pos, idx, s.level.Tiles[idx])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *playLevelState) findEntityAt(pos geom.Point) *entity {
|
|
||||||
if s.player.pos == pos {
|
|
||||||
return s.player
|
|
||||||
}
|
|
||||||
brick := s.bricks.FindEntity(pos)
|
|
||||||
if brick != nil {
|
|
||||||
return brick
|
|
||||||
}
|
|
||||||
return s.sunken.FindEntity(pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *playLevelState) isObstructed(pos geom.Point, idx int, t soko.TileType) bool {
|
|
||||||
if s.bricks.FindEntity(pos) != nil {
|
|
||||||
return true // brick
|
|
||||||
}
|
|
||||||
switch s.level.Tiles[idx] {
|
|
||||||
case soko.TileTypeMagma:
|
|
||||||
return false
|
|
||||||
case soko.TileTypeBasic:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *playLevelState) isMagma(pos geom.Point, idx int, t soko.TileType) bool {
|
|
||||||
return t == soko.TileTypeMagma
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *playLevelState) isSolidTile(pos geom.Point, idx int, t soko.TileType) bool {
|
|
||||||
switch t {
|
|
||||||
case soko.TileTypeBasic:
|
|
||||||
return true
|
|
||||||
case soko.TileTypeMagma:
|
|
||||||
return s.sunken.FindEntity(pos) != nil
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *playLevelState) wouldBrickSink(pos geom.Point, idx int, t soko.TileType) bool {
|
|
||||||
return t == soko.TileTypeMagma && s.sunken.FindEntity(pos) == nil
|
|
||||||
}
|
}
|
||||||
|
50
soko/direction.go
Normal file
50
soko/direction.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package soko
|
||||||
|
|
||||||
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
|
type Direction int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DirectionUp Direction = iota
|
||||||
|
DirectionRight
|
||||||
|
DirectionDown
|
||||||
|
DirectionLeft
|
||||||
|
)
|
||||||
|
|
||||||
|
var Directions = [4]Direction{DirectionUp, DirectionRight, DirectionDown, DirectionLeft}
|
||||||
|
|
||||||
|
func (d Direction) String() string {
|
||||||
|
switch d {
|
||||||
|
case DirectionUp:
|
||||||
|
return "up"
|
||||||
|
case DirectionRight:
|
||||||
|
return "right"
|
||||||
|
case DirectionDown:
|
||||||
|
return "down"
|
||||||
|
case DirectionLeft:
|
||||||
|
return "left"
|
||||||
|
}
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Direction) ToPoint() geom.Point {
|
||||||
|
switch d {
|
||||||
|
case DirectionUp:
|
||||||
|
return geom.Pt(0, -1)
|
||||||
|
case DirectionRight:
|
||||||
|
return geom.Pt(1, 0)
|
||||||
|
case DirectionDown:
|
||||||
|
return geom.Pt(0, 1)
|
||||||
|
case DirectionLeft:
|
||||||
|
return geom.Pt(-1, 0)
|
||||||
|
}
|
||||||
|
return geom.Point{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Neighbours(p geom.Point) []geom.Point {
|
||||||
|
neighbours := make([]geom.Point, 4)
|
||||||
|
for i, dir := range Directions {
|
||||||
|
neighbours[i] = p.Add(dir.ToPoint())
|
||||||
|
}
|
||||||
|
return neighbours
|
||||||
|
}
|
19
soko/entity.go
Normal file
19
soko/entity.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package soko
|
||||||
|
|
||||||
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
|
type Entity struct {
|
||||||
|
ID int
|
||||||
|
Pos geom.Point
|
||||||
|
Typ EntityType
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entities map[int]Entity
|
||||||
|
|
||||||
|
func (e Entities) Clone() Entities {
|
||||||
|
clone := Entities{}
|
||||||
|
for id, e := range e {
|
||||||
|
clone[id] = e
|
||||||
|
}
|
||||||
|
return clone
|
||||||
|
}
|
31
soko/entitylocations.go
Normal file
31
soko/entitylocations.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package soko
|
||||||
|
|
||||||
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
|
type EntityLocations map[geom.Point]int
|
||||||
|
|
||||||
|
func (l EntityLocations) Add(p geom.Point, id int) { l[p] = id }
|
||||||
|
|
||||||
|
func (l EntityLocations) Clone() EntityLocations {
|
||||||
|
clone := EntityLocations{}
|
||||||
|
for p, id := range l {
|
||||||
|
clone[p] = id
|
||||||
|
}
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l EntityLocations) Has(p geom.Point) bool {
|
||||||
|
_, ok := l[p]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l EntityLocations) Move(from, to geom.Point) {
|
||||||
|
id, ok := l[from]
|
||||||
|
if !ok {
|
||||||
|
panic("no entitiy at position")
|
||||||
|
}
|
||||||
|
l.Remove(from)
|
||||||
|
l.Add(to, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l EntityLocations) Remove(p geom.Point) { delete(l, p) }
|
@ -3,18 +3,18 @@ package soko
|
|||||||
type EntityType byte
|
type EntityType byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EntityTypeInvalid EntityType = EntityType(0)
|
EntityTypeInvalid EntityType = EntityType(0)
|
||||||
EntityTypeNone = '_'
|
EntityTypeNone = '_'
|
||||||
EntityTypeCharacter = '@'
|
EntityTypePlayer = '@'
|
||||||
EntityTypeEgg = 'X'
|
EntityTypeTarget = 'X'
|
||||||
EntityTypeBrick = 'B'
|
EntityTypeBrick = 'B'
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e EntityType) IsValid() bool {
|
func (e EntityType) IsValid() bool {
|
||||||
switch e {
|
switch e {
|
||||||
case EntityTypeNone:
|
case EntityTypeNone:
|
||||||
case EntityTypeCharacter:
|
case EntityTypePlayer:
|
||||||
case EntityTypeEgg:
|
case EntityTypeTarget:
|
||||||
case EntityTypeBrick:
|
case EntityTypeBrick:
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
@ -19,3 +19,29 @@ func (l Level) PosToIdx(p geom.Point) int {
|
|||||||
}
|
}
|
||||||
return p.Y*l.Width + p.X
|
return p.Y*l.Width + p.X
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l Level) State() State {
|
||||||
|
s := NewState()
|
||||||
|
for i, t := range l.Tiles {
|
||||||
|
pos := l.IdxToPos(i)
|
||||||
|
switch t {
|
||||||
|
case TileTypeBasic:
|
||||||
|
s.BasicTiles.Add(pos)
|
||||||
|
case TileTypeMagma:
|
||||||
|
s.MagmaTiles.Add(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, e := range l.Entities {
|
||||||
|
pos := l.IdxToPos(i)
|
||||||
|
switch e {
|
||||||
|
case EntityTypeBrick:
|
||||||
|
s.AddBrick(pos)
|
||||||
|
case EntityTypePlayer:
|
||||||
|
s.SetPlayer(pos)
|
||||||
|
case EntityTypeTarget:
|
||||||
|
s.SetTarget(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
17
soko/locations.go
Normal file
17
soko/locations.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package soko
|
||||||
|
|
||||||
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
|
type Locations map[geom.Point]bool
|
||||||
|
|
||||||
|
func (l Locations) Add(p geom.Point) { l[p] = true }
|
||||||
|
|
||||||
|
func (l Locations) Clone() Locations {
|
||||||
|
clone := Locations{}
|
||||||
|
for p := range l {
|
||||||
|
clone[p] = true
|
||||||
|
}
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Locations) Remove(p geom.Point) { delete(l, p) }
|
144
soko/state.go
Normal file
144
soko/state.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package soko
|
||||||
|
|
||||||
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
Player Entity
|
||||||
|
Target Entity
|
||||||
|
|
||||||
|
BasicTiles Locations
|
||||||
|
MagmaTiles Locations
|
||||||
|
|
||||||
|
Bricks EntityLocations
|
||||||
|
SunkenBricks EntityLocations
|
||||||
|
|
||||||
|
Entities Entities
|
||||||
|
|
||||||
|
nextEntityID int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewState() State {
|
||||||
|
return State{
|
||||||
|
BasicTiles: Locations{},
|
||||||
|
MagmaTiles: Locations{},
|
||||||
|
Bricks: EntityLocations{},
|
||||||
|
SunkenBricks: EntityLocations{},
|
||||||
|
Entities: Entities{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone creates and returns a clone of the state.
|
||||||
|
func (s State) Clone() State {
|
||||||
|
return State{
|
||||||
|
Player: s.Player,
|
||||||
|
Target: s.Target,
|
||||||
|
BasicTiles: s.BasicTiles.Clone(),
|
||||||
|
MagmaTiles: s.MagmaTiles.Clone(),
|
||||||
|
Bricks: s.Bricks.Clone(),
|
||||||
|
SunkenBricks: s.SunkenBricks.Clone(),
|
||||||
|
Entities: s.Entities.Clone(),
|
||||||
|
nextEntityID: s.nextEntityID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) addEntity(pos geom.Point, typ EntityType, add func(Entity)) {
|
||||||
|
id := s.nextEntityID
|
||||||
|
e := Entity{id, pos, typ}
|
||||||
|
add(e)
|
||||||
|
s.Entities[id] = e
|
||||||
|
s.nextEntityID++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) AddBrick(pos geom.Point) {
|
||||||
|
s.addEntity(pos, EntityTypeBrick, func(e Entity) {
|
||||||
|
s.Bricks.Add(pos, e.ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s State) All(pred func(State, geom.Point) bool, p ...geom.Point) bool {
|
||||||
|
for _, p := range p {
|
||||||
|
if !pred(s, p) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s State) Any(pred func(State, geom.Point) bool, p ...geom.Point) bool {
|
||||||
|
for _, p := range p {
|
||||||
|
if pred(s, p) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s State) IsOpenForBrick(p geom.Point) bool {
|
||||||
|
if s.IsWalkable(p) {
|
||||||
|
return !s.Bricks.Has(p)
|
||||||
|
}
|
||||||
|
return s.MagmaTiles[p]
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWalkable indicates that a player could walk over this tile regardless of any bricks that might be there.
|
||||||
|
func (s State) IsWalkable(p geom.Point) bool {
|
||||||
|
return s.BasicTiles[p] || (s.MagmaTiles[p] && s.SunkenBricks.Has(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MovePlayer tries to move the player in the specified direction. Returns the new state and true if the move is valid. Returns the current state and false is the move was invalid.
|
||||||
|
func (s State) MovePlayer(dir Direction) (State, bool) {
|
||||||
|
to := s.Player.Pos.Add(dir.ToPoint())
|
||||||
|
|
||||||
|
if !s.IsWalkable(to) {
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.Bricks.Has(to) {
|
||||||
|
return s.Mutate(func(s *State) {
|
||||||
|
s.movePlayer(to)
|
||||||
|
}), true
|
||||||
|
}
|
||||||
|
|
||||||
|
brickTo := to.Add(dir.ToPoint())
|
||||||
|
if !s.IsOpenForBrick(brickTo) {
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
|
brickID := s.Bricks[to]
|
||||||
|
return s.Mutate(func(s *State) {
|
||||||
|
s.movePlayer(to)
|
||||||
|
|
||||||
|
if s.MagmaTiles[brickTo] && !s.SunkenBricks.Has(brickTo) {
|
||||||
|
s.Bricks.Remove(to)
|
||||||
|
s.SunkenBricks.Add(brickTo, brickID)
|
||||||
|
} else {
|
||||||
|
s.Bricks.Move(to, brickTo)
|
||||||
|
}
|
||||||
|
s.Entities[brickID] = Entity{brickID, brickTo, EntityTypeBrick}
|
||||||
|
}), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutate clones the state, applies the mutation on the clone and returns the clone.
|
||||||
|
func (s State) Mutate(fn func(s *State)) State {
|
||||||
|
clone := s.Clone()
|
||||||
|
fn(&clone)
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) movePlayer(to geom.Point) {
|
||||||
|
s.Player.Pos = to
|
||||||
|
s.Entities[s.Player.ID] = Entity{s.Player.ID, to, EntityTypePlayer}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) SetPlayer(pos geom.Point) {
|
||||||
|
s.addEntity(pos, EntityTypePlayer, func(e Entity) {
|
||||||
|
s.Player = e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) SetTarget(pos geom.Point) {
|
||||||
|
s.addEntity(pos, EntityTypeTarget, func(e Entity) {
|
||||||
|
s.Target = e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsMagma(s State, p geom.Point) bool { return s.MagmaTiles[p] }
|
Loading…
Reference in New Issue
Block a user