241 lines
4.9 KiB
Go
241 lines
4.9 KiB
Go
package soko
|
|
|
|
import "opslag.de/schobers/geom"
|
|
|
|
type State struct {
|
|
Player Entity
|
|
Target Entity
|
|
|
|
Entities Entities
|
|
|
|
Level Level
|
|
IdxToPos []geom.Point
|
|
Bricks Ints
|
|
SunkenBricks Ints
|
|
Walkable []bool
|
|
|
|
nextEntityID int
|
|
}
|
|
|
|
func NewState(l Level) State {
|
|
size := l.Size()
|
|
s := State{
|
|
Entities: Entities{},
|
|
Level: l,
|
|
IdxToPos: make([]geom.Point, size),
|
|
Bricks: NewInts(size),
|
|
SunkenBricks: NewInts(size),
|
|
Walkable: make([]bool, size),
|
|
}
|
|
|
|
for idx, t := range l.Tiles {
|
|
switch t {
|
|
case TileTypeBasic:
|
|
s.Walkable[idx] = true
|
|
}
|
|
s.IdxToPos[idx] = s.Level.IdxToPos(idx)
|
|
}
|
|
|
|
for idx, e := range l.Entities {
|
|
pos := s.IdxToPos[idx]
|
|
switch e {
|
|
case EntityTypeBrick:
|
|
s.addBrick(pos)
|
|
case EntityTypePlayer:
|
|
s.initPlayer(pos)
|
|
case EntityTypeTarget:
|
|
s.initTarget(pos)
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Clone creates and returns a clone of the state.
|
|
func (s State) Clone() State {
|
|
return State{
|
|
Player: s.Player,
|
|
Target: s.Target,
|
|
Entities: s.Entities.Clone(),
|
|
Level: s.Level,
|
|
IdxToPos: s.IdxToPos,
|
|
Bricks: s.Bricks.Clone(),
|
|
SunkenBricks: s.SunkenBricks.Clone(),
|
|
Walkable: append(s.Walkable[:0:0], s.Walkable...),
|
|
nextEntityID: s.nextEntityID,
|
|
}
|
|
}
|
|
|
|
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) Equal(other *State) bool {
|
|
for i := 0; i < len(s.Entities); i++ {
|
|
if other.Entities[i].Pos != s.Entities[i].Pos {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s State) IsOpenForBrick(p geom.Point) bool {
|
|
idx := s.Level.PosToIdx(p)
|
|
if idx == -1 {
|
|
return false
|
|
}
|
|
return s.isOpenForBrick(idx)
|
|
}
|
|
|
|
// 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 {
|
|
idx := s.Level.PosToIdx(p)
|
|
if idx == -1 {
|
|
return false
|
|
}
|
|
return s.Walkable[idx]
|
|
}
|
|
|
|
// IsWalkableAndFree indicates that a player can walk over this tile (no brick is at the tile).
|
|
func (s State) IsWalkableAndFree(p geom.Point) bool {
|
|
idx := s.Level.PosToIdx(p)
|
|
if idx == -1 {
|
|
return false
|
|
}
|
|
return s.Walkable[idx] && s.Bricks[idx] == -1
|
|
}
|
|
|
|
// 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.Level.MoveIdx(s.Player.Pos, dir)
|
|
if to == -1 {
|
|
return s, false
|
|
}
|
|
|
|
if !s.Walkable[to] {
|
|
return s, false
|
|
}
|
|
|
|
brickID := s.Bricks[to]
|
|
if brickID == -1 {
|
|
return s.Mutate(func(s *State) {
|
|
s.movePlayer(to)
|
|
}), true
|
|
}
|
|
|
|
brickTo := s.Level.MoveIdx(to, dir)
|
|
if brickTo == -1 {
|
|
return s, false
|
|
}
|
|
if !s.isOpenForBrick(brickTo) {
|
|
return s, false
|
|
}
|
|
return s.Mutate(func(s *State) {
|
|
s.movePlayer(to)
|
|
|
|
if s.Level.Tiles[brickTo] == TileTypeMagma && s.SunkenBricks[brickTo] == -1 {
|
|
s.SunkenBricks[brickTo] = brickID
|
|
s.Walkable[brickTo] = true
|
|
s.Bricks[to] = -1
|
|
} else {
|
|
s.Bricks[brickTo] = brickID
|
|
s.Bricks[to] = -1
|
|
}
|
|
s.updateEntity(brickID, Entity{brickID, brickTo, EntityTypeBrick})
|
|
}), true
|
|
}
|
|
|
|
func (s State) SetPlayer(to geom.Point) (State, bool) {
|
|
toIdx := s.Level.PosToIdx(to)
|
|
if toIdx == -1 {
|
|
return s, false
|
|
}
|
|
if !s.Walkable[toIdx] {
|
|
return s, false
|
|
}
|
|
if s.Bricks[toIdx] != -1 {
|
|
return s, false
|
|
}
|
|
return s.Mutate(func(s *State) {
|
|
s.movePlayer(toIdx)
|
|
}), 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) addBrick(pos geom.Point) {
|
|
s.addEntity(pos, EntityTypeBrick, func(e Entity) {
|
|
idx := s.Level.PosToIdx(pos)
|
|
if idx != -1 {
|
|
s.Bricks[idx] = e.ID
|
|
}
|
|
})
|
|
}
|
|
|
|
func (s *State) addEntity(pos geom.Point, typ EntityType, add func(Entity)) {
|
|
id := s.nextEntityID
|
|
e := Entity{id, s.Level.PosToIdx(pos), typ}
|
|
add(e)
|
|
s.Entities = append(s.Entities, e)
|
|
s.nextEntityID++
|
|
}
|
|
|
|
func (s *State) initPlayer(pos geom.Point) {
|
|
s.addEntity(pos, EntityTypePlayer, func(e Entity) {
|
|
s.Player = e
|
|
})
|
|
}
|
|
|
|
func (s *State) initTarget(pos geom.Point) {
|
|
s.addEntity(pos, EntityTypeTarget, func(e Entity) {
|
|
s.Target = e
|
|
})
|
|
}
|
|
|
|
func (s State) isOpenForBrick(idx int) bool {
|
|
if s.Walkable[idx] {
|
|
return s.Bricks[idx] == -1
|
|
}
|
|
return s.Level.Tiles[idx] == TileTypeMagma
|
|
}
|
|
|
|
func (s *State) updateEntity(id int, e Entity) {
|
|
idx := s.Entities.IdxById(id)
|
|
if idx == -1 {
|
|
return
|
|
}
|
|
s.Entities[idx] = e
|
|
}
|
|
|
|
func (s *State) movePlayer(to int) {
|
|
s.Player.Pos = to
|
|
s.updateEntity(s.Player.ID, Entity{s.Player.ID, to, EntityTypePlayer})
|
|
}
|
|
|
|
func IsMagma(s State, p geom.Point) bool {
|
|
idx := s.Level.PosToIdx(p)
|
|
if idx == -1 {
|
|
return false
|
|
}
|
|
return s.Level.Tiles[idx] == TileTypeMagma
|
|
}
|