2020-01-15 20:25:32 +00:00
package soko
import "opslag.de/schobers/geom"
type State struct {
Player Entity
Target Entity
Entities Entities
2020-01-16 06:33:04 +00:00
Level Level
IdxToPos [ ] geom . Point
Bricks Ints
SunkenBricks Ints
Walkable [ ] bool
2020-01-15 20:25:32 +00:00
nextEntityID int
}
2020-01-16 06:33:04 +00:00
func NewState ( l Level ) State {
size := l . Size ( )
s := State {
2020-01-15 20:25:32 +00:00
Entities : Entities { } ,
2020-01-16 06:33:04 +00:00
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 )
}
2020-01-15 20:25:32 +00:00
}
2020-01-16 06:33:04 +00:00
return s
2020-01-15 20:25:32 +00:00
}
// Clone creates and returns a clone of the state.
func ( s State ) Clone ( ) State {
return State {
Player : s . Player ,
Target : s . Target ,
2020-01-16 06:33:04 +00:00
Entities : s . Entities . Clone ( ) ,
Level : s . Level ,
IdxToPos : s . IdxToPos ,
2020-01-15 20:25:32 +00:00
Bricks : s . Bricks . Clone ( ) ,
SunkenBricks : s . SunkenBricks . Clone ( ) ,
2020-01-16 06:33:04 +00:00
Walkable : append ( s . Walkable [ : 0 : 0 ] , s . Walkable ... ) ,
2020-01-15 20:25:32 +00:00
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
}
2020-01-16 06:33:04 +00:00
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
}
2020-01-15 20:25:32 +00:00
func ( s State ) IsOpenForBrick ( p geom . Point ) bool {
2020-01-16 06:33:04 +00:00
idx := s . Level . PosToIdx ( p )
if idx == - 1 {
return false
2020-01-15 20:25:32 +00:00
}
2020-01-16 06:33:04 +00:00
return s . isOpenForBrick ( idx )
2020-01-15 20:25:32 +00:00
}
2020-01-16 06:33:04 +00:00
// IsWalkable indicates that a player could walk over this tile regardless of any Bricks that might be there.
2020-01-15 20:25:32 +00:00
func ( s State ) IsWalkable ( p geom . Point ) bool {
2020-01-16 06:33:04 +00:00
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
2020-01-15 20:25:32 +00:00
}
// 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 ) {
2020-01-16 06:33:04 +00:00
to := s . Level . MoveIdx ( s . Player . Pos , dir )
if to == - 1 {
return s , false
}
2020-01-15 20:25:32 +00:00
2020-01-16 06:33:04 +00:00
if ! s . Walkable [ to ] {
2020-01-15 20:25:32 +00:00
return s , false
}
2020-01-16 06:33:04 +00:00
brickID := s . Bricks [ to ]
if brickID == - 1 {
2020-01-15 20:25:32 +00:00
return s . Mutate ( func ( s * State ) {
s . movePlayer ( to )
} ) , true
}
2020-01-16 06:33:04 +00:00
brickTo := s . Level . MoveIdx ( to , dir )
if brickTo == - 1 {
return s , false
}
if ! s . isOpenForBrick ( brickTo ) {
2020-01-15 20:25:32 +00:00
return s , false
}
return s . Mutate ( func ( s * State ) {
s . movePlayer ( to )
2020-01-16 06:33:04 +00:00
if s . Level . Tiles [ brickTo ] == TileTypeMagma && s . SunkenBricks [ brickTo ] == - 1 {
s . SunkenBricks [ brickTo ] = brickID
s . Walkable [ brickTo ] = true
s . Bricks [ to ] = - 1
2020-01-15 20:25:32 +00:00
} else {
2020-01-16 06:33:04 +00:00
s . Bricks [ brickTo ] = brickID
s . Bricks [ to ] = - 1
2020-01-15 20:25:32 +00:00
}
2020-01-16 06:33:04 +00:00
s . updateEntity ( brickID , Entity { brickID , brickTo , EntityTypeBrick } )
2020-01-15 20:25:32 +00:00
} ) , true
}
2020-01-16 06:33:04 +00:00
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
}
2020-01-15 20:25:32 +00:00
// 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
}
2020-01-16 06:33:04 +00:00
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 ) {
2020-01-15 20:25:32 +00:00
s . addEntity ( pos , EntityTypePlayer , func ( e Entity ) {
s . Player = e
} )
}
2020-01-16 06:33:04 +00:00
func ( s * State ) initTarget ( pos geom . Point ) {
2020-01-15 20:25:32 +00:00
s . addEntity ( pos , EntityTypeTarget , func ( e Entity ) {
s . Target = e
} )
}
2020-01-16 06:33:04 +00:00
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
2020-01-15 20:49:01 +00:00
}
2020-01-16 06:33:04 +00:00
func ( s * State ) movePlayer ( to int ) {
2020-01-15 20:49:01 +00:00
s . Player . Pos = to
2020-01-16 06:33:04 +00:00
s . updateEntity ( s . Player . ID , Entity { s . Player . ID , to , EntityTypePlayer } )
2020-01-15 20:49:01 +00:00
}
2020-01-16 06:33:04 +00:00
func IsMagma ( s State , p geom . Point ) bool {
idx := s . Level . PosToIdx ( p )
if idx == - 1 {
return false
}
return s . Level . Tiles [ idx ] == TileTypeMagma
}