packages feed

SpaceInvaders-0.4.4: src/Object.hs

{- $Id: Object.hs,v 1.2 2003/11/10 21:28:58 antony Exp $
******************************************************************************
*                              I N V A D E R S                               *
*                                                                            *
*       Module:         Object                                               *
*       Purpose:        Definition of objects in the world and their static  *
*                       properties.                                          *
*       Author:         Henrik Nilsson                                       *
*                                                                            *
*             Copyright (c) Yale University, 2003                            *
*                                                                            *
******************************************************************************
-}

module Object (
    Object,
    ObjInput(..),
    ObjOutput(..),
    ObsObjState(..),
    oosGun,             -- :: Position2 -> Velocity2 -> ObsObjState
    oosMissile,         -- :: Position2 -> Velocity2 -> ObsObjState
    oosAlien,           -- :: Position2 -> Heading -> Velocity2 -> ObsObjState
    isGun,              -- :: ObsObjState -> Bool
    isMissile,          -- :: ObsObjState -> Bool
    isAlien,            -- :: ObsObjState -> Bool
    touches,            -- :: ObsObjState -> ObsObjState -> Bool
    approaches,         -- :: ObsObjState -> ObsObjState -> Bool
    colliding,          -- :: ObsObjState -> ObsObjState -> Bool
    gunRadius,          -- :: Length
    gunBase,            -- :: Length
    gunHeight,          -- :: Length
    gunSpeedMax,        -- :: Speed
    gunAccMax,          -- :: Acceleration
    missileRadius,      -- :: Length
    missileInitialSpeed,-- :: Speed
    missileLifeSpan,    -- :: Time
    alienRadius,        -- :: Length
    alienWingRadius,    -- :: Length
    alienAccMax         -- :: Acceleration
) where

import FRP.Yampa (SF, Event)
import FRP.Yampa.Geometry
import FRP.Yampa.Forceable

import Parser (GameInput)
import PhysicalDimensions
import WorldGeometry


------------------------------------------------------------------------------
-- Object and related types
------------------------------------------------------------------------------

-- Objects are represented by signal functions, i.e. they are reactive and
-- can carry internal state.

type Object = SF ObjInput ObjOutput

data ObjInput = ObjInput {
    oiHit       :: Event (),
    oiGameInput :: GameInput
}

data ObjOutput = ObjOutput {
    ooObsObjState :: !ObsObjState,
    ooKillReq     :: Event (),
    ooSpawnReq    :: Event [Object]
}

-- Note: ObsObjState (Observable Object State) should only be constructed/
-- updated through the provided constructors/update functions since some of
-- the fields (e.g. if a bounding box field were added) might be dependent on
-- others. The reason ObsObjState is not exported abstractly is that it is
-- convenient to inspect it by pattern matching.
-- 
-- To avoid space leaks, all fields (except possibly dependent ones) are
-- strict.

data ObsObjState =
      OOSGun {
          oosPos    :: !Position2,
          oosVel    :: !Velocity2,
          oosRadius :: !Length,
          oosAmLvl  :: !Int
      }
    | OOSMissile {
          oosPos    :: !Position2,
          oosVel    :: !Velocity2,
          oosRadius :: !Length
      }
    | OOSAlien {
          oosPos    :: !Position2,
          oosHdng   :: !Heading,
          oosVel    :: !Velocity2,
          oosRadius :: !Length
      }


------------------------------------------------------------------------------
-- Instances
------------------------------------------------------------------------------

instance Forceable ObsObjState where
    -- If non-strict fields: oosNonStrict1 obj `seq` ... `seq` obj 
    force obj = obj


------------------------------------------------------------------------------
-- Smart constructors
------------------------------------------------------------------------------

-- If dependent fields (such as a bounding box) are added, these should also
-- be computed here.

oosGun :: Position2 -> Velocity2 -> Int -> ObsObjState
oosGun p v l = OOSGun {
                   oosPos    = p,
                   oosVel    = v,
                   oosRadius = gunRadius,
                   oosAmLvl  = l
               }


oosMissile :: Position2 -> Velocity2 -> ObsObjState
oosMissile p v = OOSMissile {
                     oosPos    = p,
                     oosVel    = v,
                     oosRadius = missileRadius
                 }


oosAlien :: Position2 -> Heading -> Velocity2 -> ObsObjState
oosAlien p h v = OOSAlien {
                     oosPos    = p,
                     oosHdng   = normalizeHeading h,
                     oosVel    = v,
                     oosRadius = alienRadius
                 }


------------------------------------------------------------------------------
-- Recognizers
------------------------------------------------------------------------------

isGun :: ObsObjState -> Bool
isGun (OOSGun {}) = True
isGun _           = False


isMissile :: ObsObjState -> Bool
isMissile (OOSMissile {}) = True
isMissile _               = False


isAlien :: ObsObjState -> Bool
isAlien (OOSAlien {}) = True
isAlien _             = False


------------------------------------------------------------------------------
-- Object and bounding box geometrical predicates
------------------------------------------------------------------------------


-- Check if two objects touch each other.
-- Currently only a radius-based intersection test.
touches :: ObsObjState -> ObsObjState -> Bool
oos1 `touches` oos2 =
    norm ((oosPos oos2) .-. (oosPos oos1)) < (oosRadius oos2 + oosRadius oos1)


-- Check if two objects are approaching each other.
approaches :: ObsObjState -> ObsObjState -> Bool
oos1 `approaches` oos2 =
    (oosVel oos2 ^-^ oosVel oos1) `dot` (oosPos oos2 .-. oosPos oos1) < 0.0


-- Check if two objects are colliding.
colliding :: ObsObjState -> ObsObjState -> Bool
oos1 `colliding` oos2 = oos1 `touches` oos2 && oos1 `approaches` oos2


------------------------------------------------------------------------------
-- Constants defining various object properties
------------------------------------------------------------------------------

-- All objects are treated as round as far as intersection tests etc. are
-- concerned, but they may be drawn using a different shape. It is assumed
-- that that shape is centered around the position of the object, and that
-- its size roughly corresponds to the size implied by the object radius.

-- Constants for gun.

-- Gun: drawn triangular
gunRadius, gunBase, gunHeight :: Length
gunRadius = 25
gunBase   = 2 * gunRadius
gunHeight = gunBase

gunSpeedMax :: Speed
gunSpeedMax = 500

gunAccMax :: Acceleration
gunAccMax = 500


-- Constants for missile.

-- Missiles: drawn circular.
missileRadius :: Length
missileRadius = 5.0

missileInitialSpeed :: Speed
missileInitialSpeed = 200

missileLifeSpan :: Time
missileLifeSpan = 5.0


-- Constants for alien.

-- Alien: drawn as a sphere with equatorial wing plane ("saturn-shaped")
alienRadius, alienWingRadius :: Length
alienRadius = 25
alienWingRadius = 2 * alienRadius

alienAccMax :: Acceleration
alienAccMax = 100


------------------------------------------------------------------------------
-- Support functions
------------------------------------------------------------------------------

{-
-- Old stuff

-- Bounding box is calculated once and cached inside object.

computeObjBBox :: Object -> BBox
computeObjBBox (ObjBlock {objPos = p}) = BBox (p .-^ d) (p .+^ d)
    where
        d = vector2 (blockSide / 2) (blockSide / 2)
computeObjBBox (ObjNSWall {objPos = p}) = BBox (p .-^ d) (p .+^ d)
    where
        d = vector2 (nsWallXSide / 2) (nsWallYSide / 2)
computeObjBBox (ObjEWWall {objPos = p}) = BBox (p .-^ d) (p .+^ d)
    where
        d = vector2 (ewWallXSide / 2) (ewWallYSide / 2)
computeObjBBox (ObjSimbotA {objPos = p}) = BBox (p .-^ d) (p .+^ d)
    where
        d = vector2 simbotARadius simbotARadius
computeObjBBox (ObjSimbotB {objPos = p, objHdng = d}) = BBox p1 p2
    where
        Point2 x1 y1 = p .+^ (vector2Polar simbotBRadius d) -- Nose
        Point2 x2 y2 = p .+^ (vector2Polar simbotBRadius (d + 2*pi/3))
        Point2 x3 y3 = p .+^ (vector2Polar simbotBRadius (d - 2*pi/3))

        p1 = Point2 (minimum [x1,x2,x3]) (minimum [y1,y2,y3])
        p2 = Point2 (maximum [x1,x2,x3]) (maximum [y1,y2,y3])
computeObjBBox (ObjBall {objPos = p}) = BBox (p .-^ d) (p .+^ d)
    where
        d = vector2 ballRadius ballRadius
-}