packages feed

venzone-1.0.0.0: src/Screen/Collision.hs

module Screen.Collision where

import Meeple.Operate
import Screen.Check
import Screen.Primitives
import Tile

import Lens.Micro.Platform
import Terminal.Game

import qualified Data.Ix    as I
import qualified Data.List  as L
import qualified Data.Tuple as T


-- Index of the meeple in [Meeples]
type Index = Int

-----------------------
-- MEEPLE COLLISIONS --
-----------------------

-- single you-on-everything collision
meepleCollision :: Index -> [Meeple] -> [Meeple]
meepleCollision i ms =
        let
            ei = length ms - 1
            pp  = [(i, j) | j <- [0..ei], j /= i] -- all possible pairs
            ppf = filter (areColliding ms) pp     -- only overlapping
        in
        L.foldl' collideMod ms ppf

-- Takes the position of two meeples and a list of meeples,
-- Sees if something has to happen, if so it can mark one or both the
-- indices ad 'Dead', or add to the list, but never shrink it or modify
-- it otherwise
collideMod :: [Meeple] -> (Index, Index) -> [Meeple]
collideMod ms (ia, ib) = ms & ix ia .~ na
                            & ix ib .~ nb
    where
          (na, nb) = collTable False (ms !! ia) (ms !! ib)


---------------------
-- COLLISION TABLE --
---------------------

type SecondRound = Bool
collTable :: Bool -> Meeple -> Meeple -> (Meeple, Meeple)

-- PLAYER --
collTable _ (MPlayer p) (MStar s) =
        (MPlayer p, MDead $ PlusStar (s ^. position))
collTable _ (MPlayer p) (MWin _) =
        (MPlayer p, MDead Won)
collTable _ (MPlayer _) m@(MDumb _) =
        (MDead None, m)
collTable _ (MPlayer _) m@(MSmart _) =
        (MDead None, m)
collTable _ (MPlayer _) m@(MBird _) =
        (MDead None, m)
collTable _ (MPlayer _) m@(MStone _) =
        (MDead None, m)
collTable _ (MPlayer _) m@(MKram _) =
        (MDead None, m)
collTable _ (MPlayer _) m@(MSickle _) =
        (MDead None, m)
collTable _ p@(MPlayer _) m@(MNettle n)
        | isStingy n = (MDead None, m)
        | otherwise  = (p, m)
collTable _ p@(MPlayer v) m@(MSave s)
        | v ^. isNew = (p, m)
        | otherwise  = (MPlayer $ v & amSaving .~ True,
                        MSave $ touchSave s)
collTable _ (MPlayer _) m@(MWitch _) =
        (MDead None, m)

-- DUMB --
collTable _ d@(MDumb _) (MStar _) =
        (d, MDead $ NeutralStar (d ^. position))
collTable _ (MDumb _) (MStone _) =
        (MDead None, MDead None)
collTable _ (MDumb _) (MSickle _) =
        (MDead None, MDead None)

-- SMART --
collTable _ s@(MSmart _) (MStar _) =
        (s, MDead $ NeutralStar (s ^. position))
collTable _ (MSmart _) (MStone _) =
        (MDead None, MDead None)
collTable _ (MSmart _) (MSickle _) =
        (MDead None, MDead None)

-- BIRD --
collTable _ b@(MBird _) (MStar _) =
        (b, MDead $ NeutralStar (b ^. position))
collTable _ (MBird _) (MSickle _) =
        (MDead None, MDead None)

-- KRAMPUS --
collTable _ k@(MKram _) (MStar _) =
        (k, MDead $ NeutralStar (k ^. position))


-- REPEAT? --
collTable False a b = T.swap $ collTable True b a -- inverted!
collTable True  a b = (a, b)   -- no effect


---------------------
-- ROOM COLLISIONS --
---------------------

roomCollision :: HasScreen s => s -> Meeple -> Meeple
roomCollision _ m@(MDead _) = m
roomCollision s m           = generalCollide s m

generalCollide :: HasScreen s => s -> Meeple -> Meeple
generalCollide _ m@(MDead _) = m
generalCollide s m@(MStone _)
        | checkSolid s (m ^. position) = MDead None
generalCollide s m
        | meepleTile s m (==Water) = MDead None
        | meepleTile s m (==Spike) = MDead None
        | meepleTile s m (==Slow)  = slowDown m
generalCollide _ m = m

slowDown :: Meeple -> Meeple
slowDown (MPlayer p) = MPlayer $ p & amSlow .~ True
slowDown m           = m

-----------------
-- ANCILLARIES --
-----------------

-- are two meeples in a collection (by index) colliding?
areColliding :: [Meeple] -> (Int, Int) -> Bool
areColliding ms (ia, ib) | isDead a ||
                           isDead b      = False
                                -- dead stuff can't collide
                         | pa == pb      = True
                         | isDiagonal &&
                           elem pb pas   = True
                         | otherwise     = False
    where
          a, b :: Meeple
          a = ms ^?! ix ia
          b = ms ^?! ix ib

          pa, pb :: Coords
          pa = a ^. position
          pb = b ^. position

          -- diagonal collision --


          isDiagonal :: Bool
          isDiagonal = let (px, py) = safePrevPos a
                           (cx, cy) = pa
                       in px /= cx && py /= cy

          pas :: [Coords]
          pas = diagonalArea pa (safePrevPos a)

-- rectangle between two points
diagonalArea :: Coords -> Coords -> [Coords]
diagonalArea (ra, ca) (rb, cb) =
        let p1 = (min ra rb, min ca cb)
            p2 = (max ra rb, max ca cb)
        in I.range (p1, p2)