168 lines
4.5 KiB
Haskell
168 lines
4.5 KiB
Haskell
-- | This module defines the state of a running Karel program.
|
|
module KarelState where
|
|
|
|
import Prelude hiding (Either(..))
|
|
import KarelSyntax
|
|
|
|
|
|
--
|
|
-- * Positions and directions
|
|
--
|
|
|
|
-- | A cartesian coordinate, representing a position in the world.
|
|
type Pos = (Int,Int)
|
|
|
|
-- | Get the position next to the given position, offset by one square
|
|
-- in the indicated *cardinal* direction.
|
|
neighbor :: Card -> Pos -> Pos
|
|
neighbor North (x,y) = (x,y+1)
|
|
neighbor South (x,y) = (x,y-1)
|
|
neighbor East (x,y) = (x+1,y)
|
|
neighbor West (x,y) = (x-1,y)
|
|
|
|
-- | Get a cardinal direction relative to the current one.
|
|
cardTurn :: Dir -> Card -> Card
|
|
cardTurn Front c = c
|
|
cardTurn Back North = South
|
|
cardTurn Back South = North
|
|
cardTurn Back East = West
|
|
cardTurn Back West = East
|
|
cardTurn Left North = West
|
|
cardTurn Left South = East
|
|
cardTurn Left East = North
|
|
cardTurn Left West = South
|
|
cardTurn Right North = East
|
|
cardTurn Right South = West
|
|
cardTurn Right East = South
|
|
cardTurn Right West = North
|
|
|
|
|
|
--
|
|
-- * World state
|
|
--
|
|
|
|
-- | The state of the world is represented by a function that returns
|
|
-- for each position:
|
|
-- * Nothing, if the position is a wall
|
|
-- * Just k, if the position is clear and has k beepers
|
|
-- You can assume k is always >= 0.
|
|
type World = Pos -> Maybe Int
|
|
|
|
-- | Is the given position clear?
|
|
isClear :: Pos -> World -> Bool
|
|
isClear p w = w p /= Nothing
|
|
|
|
-- | Is there a beeper at the given position?
|
|
hasBeeper :: Pos -> World -> Bool
|
|
hasBeeper p w = maybe False (>0) (w p)
|
|
|
|
-- | Increment the number of beepers at the given position.
|
|
incBeeper :: Pos -> World -> World
|
|
incBeeper p w = \q -> if p == q
|
|
then case w q of
|
|
Nothing -> Just 1
|
|
Just i -> Just (i+1)
|
|
else w q
|
|
|
|
-- | Decrement the number of beepers at the given position. Note that this
|
|
-- function can yield a world with negative beepers, so you should make
|
|
-- sure to only decrement the beepers at a position after first checking
|
|
-- to make sure there is at least one beeper there (using `hasBeeper`).
|
|
decBeeper :: Pos -> World -> World
|
|
decBeeper p w = \q -> if p == q then fmap (subtract 1) (w q) else w q
|
|
|
|
|
|
--
|
|
-- * Robot state
|
|
--
|
|
|
|
-- | The state of the robot is represented by a triple containing:
|
|
-- * the current position
|
|
-- * the current facing (cardinal direction)
|
|
-- * the number of beepers in the beeper bag
|
|
type Robot = (Pos,Card,Int)
|
|
|
|
|
|
-- ** Robot position
|
|
|
|
-- | The robot's position.
|
|
getPos :: Robot -> Pos
|
|
getPos (p,_,_) = p
|
|
|
|
-- | Get a position relative to the robot's current facing and position.
|
|
relativePos :: Dir -> Robot -> Pos
|
|
relativePos d (p,c,_) = neighbor (cardTurn d c) p
|
|
|
|
-- | Set the robot's position.
|
|
setPos :: Pos -> Robot -> Robot
|
|
setPos p (_,c,b) = (p,c,b)
|
|
|
|
-- | Update the robot's position using an update function.
|
|
updatePos :: (Pos -> Pos) -> Robot -> Robot
|
|
updatePos f (p,c,b) = (f p,c,b)
|
|
|
|
|
|
-- ** Robot facing
|
|
|
|
-- | The robot's facing (cardinal direction).
|
|
getFacing :: Robot -> Card
|
|
getFacing (_,c,_) = c
|
|
|
|
-- | Set the robot's facing.
|
|
setFacing :: Card -> Robot -> Robot
|
|
setFacing c (p,_,b) = (p,c,b)
|
|
|
|
-- | Update the robot's facing using an update function.
|
|
updateFacing :: (Card -> Card) -> Robot -> Robot
|
|
updateFacing f (p,c,b) = (p,f c,b)
|
|
|
|
|
|
-- ** Beeper bag
|
|
|
|
-- | The number of beepers in the beeper bag.
|
|
getBag :: Robot -> Int
|
|
getBag (_,_,b) = b
|
|
|
|
-- | Is the beeper bag empty?
|
|
isEmpty :: Robot -> Bool
|
|
isEmpty (_,_,b) = b <= 0
|
|
|
|
-- | Increment the number of beepers in the bag.
|
|
incBag :: Robot -> Robot
|
|
incBag (p,c,b) = (p,c,b+1)
|
|
|
|
-- | Decrement the number of beepers in the bag.
|
|
decBag :: Robot -> Robot
|
|
decBag (p,c,b) = (p,c,b-1)
|
|
|
|
|
|
--
|
|
-- * Statement results
|
|
--
|
|
|
|
-- | The result of executing a statement.
|
|
--
|
|
-- * OK: The statement executed successfully, so return the updated state
|
|
-- of the world and the robot. OK to execute the next statement.
|
|
--
|
|
-- * Done: Produced only by the Shutdown statement. This returns the final
|
|
-- state of the robot. No further statements should be executed.
|
|
--
|
|
-- * Error: An error occurred. Includes a string for reporting error messages.
|
|
-- No further statements should be executed.
|
|
--
|
|
data Result = OK World Robot
|
|
| Done Robot
|
|
| Error String
|
|
|
|
instance Show Result where
|
|
show (OK _ r) = "OK: " ++ show r
|
|
show (Done r) = "Done: " ++ show r
|
|
show (Error s) = "Error: " ++ s
|
|
|
|
-- | Applies a function to the result if its an OK, otherwise returns
|
|
-- the result unchanged.
|
|
onOK :: (World -> Robot -> Result) -> Result -> Result
|
|
onOK f (OK w r) = f w r
|
|
onOK _ result = result
|