packages feed

Ninjas-0.1.0.0: src/Simulation.hs

{-# LANGUAGE RecordWildCards #-}

module Simulation where

import Data.List  (findIndex)
import Graphics.Gloss.Data.Point
import Graphics.Gloss.Data.Vector
import Graphics.Gloss.Geometry.Angle
import System.Random (randomRIO)

import Character
import Parameters
import VectorUtils

data Player   = Player
  { playerCharacter :: Character
  , playerUsername :: String
  , playerScore    :: Int
  , playerVisited  :: [Int]
  , playerSmokes   :: Int
  }
  deriving (Show, Read, Eq)

data ServerMode = Playing | Starting Float | Stopped
  deriving (Eq, Read, Show)

-- | Lift a function on Characters to one on Players
mapPlayerCharacter :: (Character -> Character) -> Player -> Player
mapPlayerCharacter f p = p { playerCharacter = f (playerCharacter p) }

canHitPoint :: Character -> Point -> Bool
canHitPoint char pt = inRange && inFront
  where
  inRange = attackLen <= attackDistance

  -- u·v = ❘v❘ ❘u❘ cos Θ
  inFront = cos attackAngle * attackLen
         <= charFacing char `dotV` attackVector

  attackVector   = subPt pt (charPos char)
  attackLen      = magV attackVector

-- | Compute a random point inside a box.
randomPoint :: Point -> Point -> IO Point
randomPoint (minX,minY) (maxX,maxY) =
  do x <- randomRIO (minX,maxX)
     y <- randomRIO (minY,maxY)
     return (x,y)

-- | Compute a random point inside the board.
randomBoardPoint :: IO Point
randomBoardPoint = randomPoint boardMin boardMax

-- | Compute a random unit vector.
randomUnitVector :: IO Vector
randomUnitVector =
  do degrees <- randomRIO (0,359)
     let rads = degToRad $ fromInteger degrees
     return $ unitVectorAtAngle rads

-- | Construct a new character given a name, a position,
-- and a facing unit vector. When think is True,
-- the character will be scheduled to begin walking
-- after a random duration.
initServerCharacter :: Bool -> IO Character
initServerCharacter think =
  do charPos     <- randomBoardPoint
     charFacing  <- randomUnitVector
     waitWaiting <- pickWaitTime think
     let waitStunned = False
         charState = Waiting Wait { .. }
     return Character { .. }

-- | Construct a new player given an initial number
-- of smokebombs, an identifier, a username, and a
-- starting score.
initPlayer :: Int -> String -> Int -> IO Player
initPlayer smokes playerUsername playerScore =
  do playerCharacter <- initServerCharacter False
     let playerVisited = []
         playerSmokes  = smokes
     return Player { .. }

-- | When True, compute a new random wait time value.
pickWaitTime ::
  Bool {- ^ compute random delay -} ->
  IO (Maybe Float)
pickWaitTime False = return Nothing
pickWaitTime True  = fmap Just $ randomRIO (0, restTime)

-- | Determine if a point lies within a given pillar.
isInPillar ::
  Point {- ^ location to test -} ->
  Point {- ^ center of pillar -} ->
  Bool
isInPillar p (x,y) = pointInBox p (x-halfside, y-halfside)
                                  (x+halfside, y+halfside)
  where
  halfside = pillarSize / 2

-- | Determine the index, if any, of the pillar with contains a point.
whichPillar :: Point -> Maybe Int
whichPillar p = findIndex (isInPillar p) pillars

-- | Return true if the given player can use smokebombs.
hasSmokebombs :: Player -> Bool
hasSmokebombs p = playerSmokes p > 0

-- | Update a player to have one fewer smokebombs.
consumeSmokebomb :: Player -> Player
consumeSmokebomb p = p { playerSmokes = playerSmokes p - 1 }