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] }