scroll-1.20150313: Player.hs
module Player where
import Control.Monad.State.Strict
import qualified Data.Vector.Mutable as MV
import Control.Applicative
import Data.Maybe
import Types
import Level.Border
modifyPlayer :: (Player -> Player) -> M ()
modifyPlayer f = modify $ \s -> s { player = f (player s) }
isSpellCaster :: M ()
isSpellCaster = modify $ \s -> s { player = (player s) { spellCaster = True } }
shiftBody :: Player -> Segment -> [Segment]
shiftBody p segment
| playerLen p == 0 = []
| length (playerBody p) >= playerLen p = segment : shrinkBody p
| otherwise = segment : playerBody p
shrinkBody :: Player -> [Segment]
shrinkBody = reverse . drop 1 . reverse . playerBody
-- Does not adjust the body.
setHeadPos :: Pos -> M ()
setHeadPos pos = modifyPlayer $ \p -> p { playerHead = pos }
-- Moves the player head and each segment up 1 space.
-- This is called when the scroll moves.
shiftPlayerUp :: M ()
shiftPlayerUp = modifyPlayer $ offsetPos (id, pred)
-- Find a starting position for the player that is
-- not occupied a letter, and from which the player can dive to the help
-- level.
--
-- The help level has a lot of whitespace in its first line, so the player
-- is put on the first line. In the unlikely event that there is no
-- suitable starting in the first line, one letter is destroyed to make
-- one.
startingPosition :: M ()
startingPosition = do
w <- gets world
l <- lift $ MV.read w 2
divable <- scanv [0..MV.length l - 1] checkCanDiveTo
usable <- scanv divable $ lift . checkBounded w
if null usable
then do
let x = midpoint divable
lift $ MV.write l x ' '
setHeadPos (x, 2)
else setHeadPos (midpoint usable, 2)
where
scanv range cond = do
ms <- forM range $ \x -> do
ok <- cond (x, 2)
return $ if ok then Just x else Nothing
return (catMaybes ms)
midpoint [x] = x
midpoint l = l !! pred (length l `div` 2)
-- In order to be able to dive through the scroll, there needs to be a
-- bordered area waiting on the flipSide.
--
-- This does not check if the player is in a position (empty stomach,
-- etc to use the spot), or check if the player will be able to dive
-- back from the flipSide to this side.
checkCanDive :: M Bool
checkCanDive = checkCanDiveTo =<< (playerHead <$> gets player)
checkCanDiveTo :: Pos -> M Bool
checkCanDiveTo pos = do
w <- gets flipSide
lift $ checkBounded w pos
removeSwallowing :: (Char -> Bool) -> Player -> Player
removeSwallowing match p = case playerSwallowing p of
Just c | match c -> p { playerSwallowing = Nothing }
_ -> p
-- Only positions on the current side of the scroll.
wormPositions :: M [Pos]
wormPositions = do
p <- gets player
ps <- wormTailPositions
return (playerHead p : ps)
wormTailPositions :: M [Pos]
wormTailPositions = do
p <- gets player
let segs = filter (\s -> segmentSide s == CurrentSide) (playerBody p)
return $ map getPos segs