venzone-1.0.0.0: src/Screen/Move.hs
{-# Language FlexibleContexts #-}
module Screen.Move where
import Terminal.Game
import Lens.Micro.Platform
import Meeple.Operate
import Room
import Screen.Check
import Screen.Collision
import Screen.Primitives
import qualified Data.List as L
-- moves, check for room and meeples collision <= one by one
moveCollideGeneral :: HasScreen s => s -> s
moveCollideGeneral s =
let -- motion (non step) action
s' = s & meeples . each %~ applyGravity (s ^. room)
& meeples . each %~ limitSpeed
-- steps
s'' = exhaustSteps (generateAllSteps s')
-- dead cleaning will be done in story,
-- since messages are collected there
in s''
where
-- step actions
exhaustSteps :: HasScreen s => s -> s
exhaustSteps sw =
if any hasSteps (sw ^. meeples)
then exhaustSteps (oneStep sw)
else sw
oneStep :: HasScreen s => s -> s
oneStep sw = let ms = sw ^. meeples
ei = length ms - 1
in
L.foldl' stepCollide sw [0..ei]
------------
-- MOTION --
------------
-- *non* stepped stuff
gravity :: Integer
gravity = 1
-- room:: jus for oob check
-- todo [refactor] refactor
applyGravity :: Room -> Meeple -> Meeple
applyGravity _ m@(MDead _) = m
applyGravity _ m@(MBird _) = m
applyGravity _ m@(MDumb _) = m & speed . _1 +~ gravity
applyGravity _ m@(MKram _) = m & speed . _1 +~ gravity
applyGravity _ m@(MLock _) = m
applyGravity _ m@(MNettle _) = m
applyGravity r m@(MPlayer p)
| p ^. isClimbing = m
| isOOBRoom r
(p ^. position) = m -- do not apply in between room or we can't
-- high jump between rooms
| otherwise = m & speed . _1 +~ gravity
applyGravity _ m@(MSave _) = m
applyGravity _ m@(MSmart _) = m & speed . _1 +~ gravity
applyGravity _ m@(MStar _) = m
applyGravity _ m@(MSickle _) = m & speed . _1 +~ gravity
applyGravity _ m@(MStone _) = m & speed . _1 +~ gravity
applyGravity _ m@(MWin _) = m
applyGravity _ m@(MWitch _) = m
limitSpeed :: Meeple -> Meeple
limitSpeed m@(MDead _) = m
limitSpeed m = m & speed . _1 %~ im (>=) 1
& speed . _2 %~ im (>=) 1
& speed . _2 %~ im (<=) (-1)
where
im :: (Integer -> Integer -> Bool) ->
Integer -> Integer -> Integer
im o t x = if x `o` t then t else x
-----------
-- STEPS --
-----------
generateAllSteps :: HasScreen s => s -> s
generateAllSteps s = s & meeples . each %~ generateSteps
-- single meeple, *one* step
stepCollide :: HasScreen s => s -> Index -> s
stepCollide s i = s & meeples . ix i %~ savePosition
& meeples . ix i %~ step s
& meeples . ix i %~ roomCollision s
& meeples %~ meepleCollision i
savePosition :: Meeple -> Meeple
savePosition m@(MDead _) = m
savePosition m = m & prevPos .~ m ^. position
-- this is a single step!
step :: HasScreen s => s -> Meeple -> Meeple
step _ m@(MDead _) = m
step s m@(MBird _) = simpleMove s m
step s m@(MDumb _) = simpleMove s m
step s m@(MKram _) = simpleMove s m
step _ m@(MLock _) = m
step _ m@(MNettle _) = m
step s m@(MPlayer _) = simpleMove s m
step _ m@(MSave _) = m
step s m@(MSickle _) = simpleMove s m
step s m@(MSmart _) = simpleMove s m
step _ m@(MStar _) = m
step s m@(MStone _) = simpleMove s m
step s m@(MWin _) = simpleMove s m
step s m@(MWitch _) = everyMove s m
-----------------
-- ANCILLARIES --
-----------------
-- move everywhere, regardless of boundaries (spirit)
everyMove :: HasScreen s => s -> Meeple -> Meeple
everyMove _ m
| not (hasSteps m) = m
| otherwise =
let (sr, sc) = m ^. speed
in m & position . _1 %~ (+sr)
& position . _2 %~ (+sc)
& id %~ dropStep
-- try to move in a direction fallback to other solutions if possible
simpleMove :: (HasScreen s) => s -> Meeple -> Meeple
simpleMove s a
| not (hasSteps a) = a
| (fp:_) <- fps = dropStep $ snd fp
| otherwise = dropStep $ a' & speed .~ (0, 0)
-- stuck? Just don't move
where
ps :: [(Coords, Meeple)]
ps = map (uncurry $ makePosa a)
-- we first try the expected, the no vert, etc.
[(False, False),
(True, False),
(False, True),
(True, True)]
fps :: [(Coords, Meeple)]
fps = filter (\(c, _) -> canGoThere s a c) ps
-- stop agi mov
a' | (MPlayer p) <- a =
if p ^. amSlow . to not ||
(p ^. delay . to (ticks 2) . to fetchFrame)
-- todo [refactor] no idea why ticks 3
then MPlayer $ p & action .~ ANoAction
else MPlayer p
| otherwise = a
-- given Meeple we modify slightly its speed and obtain projected landing
-- places. Uper functions can check these are sensible (against walls etc.)
-- and pick the right move.
type VerticalOffset = Bool
type HorizontalOffset = Bool
makePosa :: Meeple -> VerticalOffset -> VerticalOffset -> (Coords, Meeple)
makePosa m vo ho = (cs, m & vm & hm & position .~ cs)
where
pr = m ^. position . _1
pc = m ^. position . _2
stepSpeed = steps . to head
sr | vo = 0
| otherwise = m ^. stepSpeed . _1
sc | ho = 0
| otherwise = m ^. stepSpeed . _2
cs = (pr + sr, pc + sc)
vm | vo = speed . _1 .~ 0
| otherwise = id
hm | ho = \bm -> bm & speed . _2 .~ 0
& id %~ pActStop
| otherwise = id
pActStop (MPlayer p) = MPlayer $ p & action .~ ANoAction
pActStop wm = wm