haskell-karel/Internal/World.hs

99 lines
3.3 KiB
Haskell
Raw Normal View History

{-# LANGUAGE LambdaCase #-}
module Internal.World
( World(..),
worldWithDimensions,
getBeepersAt,
setBeepersAt,
hasWallAt,
addWallAt,
removeWallAt,
positionWithinWorld,
)
where
import qualified Data.Map.Strict as M
import qualified Data.Set as S
import Internal.Types
data World = World
{ worldDimensions :: Vector,
worldWalls :: M.Map Vector (S.Set Direction),
worldBeepers :: M.Map Vector Int
}
deriving (Show, Eq)
worldWithDimensions :: Vector -> World
worldWithDimensions (width, height)
| width <= 0 || height <= 0 = error "Cannot have world with zero or negative dimension"
| otherwise =
World
{ worldDimensions = (width, height),
worldWalls = M.empty,
worldBeepers = M.empty
}
positionWithinWorld :: World -> Vector -> Bool
positionWithinWorld w (x, y) = valid_x && valid_y
where
(world_width, world_height) = worldDimensions w
valid_x = x > 0 && x <= world_width
valid_y = y > 0 && y <= world_height
setBeepersAt :: Vector -> Int -> World -> World
setBeepersAt pos count w
| not $ positionWithinWorld w pos = error "Invalid position"
| count < 0 = error "Invalid count of worldBeepers"
| count == 0 = w {worldBeepers = M.delete pos old_beepers}
| otherwise = w {worldBeepers = M.insert pos count old_beepers}
where
old_beepers = worldBeepers w
getBeepersAt :: Vector -> World -> Int
getBeepersAt pos w
| not $ positionWithinWorld w pos = error "Invalid position"
| otherwise = M.findWithDefault 0 pos (worldBeepers w)
switchDirectionToInternal :: Vector -> Direction -> (Vector, Direction)
switchDirectionToInternal (x, y) = \case
West -> ((x - 1, y), East)
South -> ((x, y - 1), North)
dir -> ((x, y), dir)
isOnEdge :: Vector -> Vector -> Direction -> Bool
isOnEdge (width, height) (x, y) dir = horizontally || vertically
where
horizontally = x == width && dir == East
vertically = y == height && dir == North
addWallAt :: Vector -> Direction -> World -> World
addWallAt pos dir w
| not $ positionWithinWorld w newPosition = error "Invalid position"
| isOnEdge (worldDimensions w) newPosition newDirection = w
| otherwise = w {worldWalls = M.insert newPosition newWalls (worldWalls w)}
where
(newPosition, newDirection) = switchDirectionToInternal pos dir
oldWalls = M.findWithDefault S.empty newPosition (worldWalls w)
newWalls = S.union oldWalls $ S.singleton newDirection
removeWallAt :: Vector -> Direction -> World -> World
removeWallAt pos dir w
| not $ positionWithinWorld w newPosition = error "Invalid position"
| isOnEdge (worldDimensions w) newPosition newDirection = w
| S.null newWalls = w {worldWalls = M.delete newPosition (worldWalls w)}
| otherwise = w {worldWalls = M.insert newPosition newWalls (worldWalls w)}
where
(newPosition, newDirection) = switchDirectionToInternal pos dir
oldWalls = M.findWithDefault S.empty newPosition (worldWalls w)
newWalls = oldWalls S.\\ S.singleton newDirection
hasWallAt :: Vector -> Direction -> World -> Bool
hasWallAt pos dir w
| not $ positionWithinWorld w newPosition = False
| isOnEdge (worldDimensions w) newPosition newDirection = True
| otherwise = case worldWalls w M.!? newPosition of
Just wallsInPlace -> S.member newDirection wallsInPlace
Nothing -> False
where
(newPosition, newDirection) = switchDirectionToInternal pos dir