packages feed

haskell-in-space-0.1: Asteroids-aufgabe.hs

module Main (main) where

import Asteroids.Geometry

import Graphics.HGL.Run (runGraphics)
import Graphics.HGL.Window (Event(..), RedrawMode(..), Window(), closeWindow, getWindowTick, maybeGetWindowEvent, openWindowEx, setGraphic)
import Graphics.HGL.Units (Point())
import Graphics.HGL.Utils (Color(..), drawInWindow, overGraphics, withColor)
import Graphics.HGL.Draw.Monad (Graphic())
import Graphics.HGL.Draw.Text (text)
import Graphics.HGL.Key (isControlLKey, isDownKey, isLeftKey, isRightKey, isUpKey)

-- to make ghc happy -- delete for hugs
fromInt :: Num a=> Int-> a
fromInt n = fromInteger (toInteger n)

data State = State { ship  :: Ship, bullets :: [Bullet], asteroids :: [Asteroid], game :: Game }

data Ship =
   Ship { pos    :: Point,
          shp    :: Shape,
          vel    :: Point,
          ornt   :: Double,
          thrust :: Double,
          hAcc   :: Double,
          bbox   :: BBox}
         deriving (Eq)

data Bullet =
   Bullet { posB :: Point,
            shpB :: Shape,
            velB :: Point,
            time :: Int,
            bboxb :: BBox}
           deriving (Eq)

data Asteroid =
  Asteroid { posA :: Point,
             shpA :: Shape,
             velA :: Point,
             size :: Int,
             bboxa :: BBox}
            deriving (Eq)

data Game = Over | Continue
  deriving (Eq)

type BBox = (Point, Point)

spaceShip :: Figure
spaceShip = Polygon [(10, 4), (-10, 10), (-15, 0), (-10, -10), (10, -4), (0, -4) , (0, 4), (10, 4)]

spaceShipBox :: BBox
spaceShipBox = ((-15, -15), (15, 15))

asteroid :: [Point]
asteroid = [(8, 0), (2, 6), (-6, 4), (-6, -4), (2, -6), (8, 0)]

asteroidBox :: BBox
asteroidBox = ((-8, -8), (8, 8))

asteroidS :: Figure
asteroidS = Polygon asteroid

asteroidM :: Figure
asteroidM = Polygon (map (\(x, y) -> (2*x, 2*y)) asteroid)

asteroidL :: Figure
asteroidL = Polygon (map (\(x, y) -> (4*x, 4*y)) asteroid)

asteroidList :: [Figure]
asteroidList = asteroidS:asteroidM:asteroidL:[]

bulletBox :: BBox
bulletBox = ((-2, -2), (2, 2))

bullet :: Figure
bullet = Polygon [(2, 0), (-1, -1), (-1, 1), (2, 0)]

winSize :: (Int, Int)
winSize = (1000, 800)

aDelta :: Double
aDelta = 1

vMax :: Double
vMax = 20

hDelta :: Double
hDelta = 0.3

maxBTime :: Int
maxBTime = 48

maxAsteroids :: Int
maxAsteroids = 6

minAstDir :: Double
minAstDir = (- pi)

maxAstDir :: Double
maxAstDir = pi

-- asteroidNumber :: IO Int
-- asteroidNumber = randomRIO (1, maxAsteroids)
asteroidNumber :: Int
asteroidNumber = maxAsteroids

-- asteroidDirection :: IO Double
-- asteroidDirection = randomRIO (minAstDir, maxAstDir)
asteroidDirection :: Double
asteroidDirection = 1.0

boxAsteroid :: Point -> Int -> BBox
boxAsteroid p s = translate (resize asteroidBox sze) p
  where sze = s + 1

translate :: BBox -> Point -> BBox
translate (a, b) c = (addWinMod a c, addWinMod b c)

resize :: BBox -> Int -> BBox
resize ((xa, ya), (xb, yb)) s = ((s * xa, s * ya), (s * xb, s * yb))

initialState :: State
initialState =
  State {ship= setShp $
     Ship{pos= shipPos, vel= (0, 0), ornt= pi/2,
          thrust= 0, hAcc= 0, bbox = translate spaceShipBox shipPos},
          bullets = [], asteroids =
          Asteroid{posA = ast1, velA = (1, 1),
          bboxa = boxAsteroid ast1 2,
          shpA = shape (Translate ast1 asteroidL), size = 2}:
          Asteroid{posA = ast2, velA = (1, -1),
          bboxa = boxAsteroid ast2 2,
          shpA = shape (Translate ast2 asteroidL), size = 2}:
          Asteroid{posA = ast3, velA = (-1, 1),
          bboxa = boxAsteroid ast3 2,
          shpA = shape (Translate ast3 asteroidL), size = 2}:
          Asteroid{posA = ast4, velA = (-1, -1),
          bboxa = boxAsteroid ast4 2,
          shpA = shape (Translate ast4 asteroidL), size = 2}:[],
          game = Continue}
            where
              minXP = 100
              minYP = 100
              maxXP = fst winSize - 100
              maxYP = snd winSize - 100
              ast1 = (minXP, minYP)
              ast2 = (minXP, maxYP)
              ast3 = (maxXP, minYP)
              ast4 = (maxXP, maxYP)
              shipPos = (fst winSize `div` 2, snd winSize `div` 2)

moveShip :: Ship-> Ship
moveShip(Ship {pos= pos0, vel= vel0, hAcc= hAcc, thrust= t, ornt= o}) =
  setShp $
    Ship{pos= addWinMod pos0 vel1,
         vel= if l> vMax then smult (vMax/l) vel1 else vel1,
         thrust= t, ornt= o+ hAcc, hAcc= hAcc} where
           vel1= add (polar t o) vel0
           l   = len vel1

setShp :: Ship-> Ship
setShp s = s{shp= shape (Translate (pos s)
                     (Rotate (ornt s) spaceShip)),
                     bbox = translate spaceShipBox (pos s)}

moveAsteroids :: [Asteroid] -> [Asteroid]
moveAsteroids [] = []
moveAsteroids (Asteroid{posA = pa, velA = va, size = s}:as) =
  Asteroid{posA = position, shpA = shape (Translate position (asteroidList !! s)),
  velA = va, size = s, bboxa = boxAsteroid position s}:(moveAsteroids as)
    where position = addWinMod pa va

moveBullets :: [Bullet] -> [Bullet]
moveBullets [] = []
moveBullets (Bullet{posB = pb, velB = vb, time = tb}:bs)
  | tb >= maxBTime = moveBullets bs
  | otherwise = Bullet{posB = position, shpB = shape (Translate position bullet),
      velB = vb, time = tb + 1, bboxb = translate bulletBox position}:(moveBullets bs)
        where position = addWinMod pb vb

addWinMod :: (Int,Int) -> (Int,Int) -> (Int,Int)
addWinMod (a, b) (c, d)=
  ((a+ c) `mod` (fst winSize),
   (b+ d) `mod` (snd winSize))

drawState :: State-> Graphic
drawState s = overGraphics
                ( drawShip (ship s):
                 (map drawBullet (bullets s))++
                 (map drawAsteroid (asteroids s)) )

drawShip :: Ship-> Graphic
drawShip s =
   withColor (if thrust s> 0 then Blue else if thrust s< 0 then Yellow else Green)
             (drawShape (shp s))

drawAsteroid :: Asteroid -> Graphic
drawAsteroid a = withColor White (drawShape (shpA a))

drawBullet :: Bullet -> Graphic
drawBullet b = withColor Red (drawShape (shpB b))

lostState :: State -> State
lostState st = st{game = Over} -- nicht komplett

wonState :: State -> State
wonState st = st{game = Over} -- auch nicht komplett

bounding :: BBox -> BBox -> Bool
bounding ((xa, ya), (xb, yb)) ((xc, yc), (xd, yd))
  | yd < ya || yc > yb || xd < xa || xc > xb = False
  | otherwise = True

checkIntersections :: State -> State
checkIntersections st@State{ship = s, asteroids = a, bullets = b}
  | a == [] = wonState st
  | or [intersect (shp s) x | Asteroid{shpA = x, bboxa = xa} <- a, bounding xa (bbox s)] = lostState st
  | otherwise = st{asteroids = (interSecAst intersecting) ++ [x | x <- a, not (elem x intersecting)],
    bullets = [bul | bul@Bullet{shpB = x} <- b,
    not (or [intersect x y | Asteroid{shpA = y} <- intersecting])]}
      where intersecting = [x | x@Asteroid{shpA = xa, bboxa = ya} <- a,
                            or [intersect xa xb| Bullet{shpB = xb, bboxb = yb} <- b,
                            bounding ya yb]]

interSecAst :: [Asteroid] -> [Asteroid]
interSecAst [] = []
interSecAst (a:as)
  | s == 0 = interSecAst as
  | otherwise = [Asteroid{posA = p, size = (s - 1),
    shpA = shape (Translate p (asteroidList !! (s - 1))),
    velA = rot (asteroidDirection + (pi - (fromInt (mod n (maxAsteroids + 1))))) (3 - s, 3 - s),
    bboxa = translate (resize asteroidBox (s + 1)) p} | n <- [1..asteroidNumber]] ++
    interSecAst as
      where
      s = (size a)
      p = (posA a)

loop :: Window-> State-> IO ()
loop w s =
  do setGraphic w (drawState s)
     getWindowTick w
     evs<- getEvs
     t <- nextState evs s
     loop w t
      where

--        gameOver :: State -> IO ()
--        gameOver s
--          | (game s) == Over =
--             do drawInWindow w (text (fst winSize `div` 2,  snd winSize `div` 2) "You lost!!!!")
--          | [(0::Int)| x <- (asteroids s)] == [] =
--             do drawInWindow w (text (fst winSize `div` 2, snd winSize `div` 2) "You won!")
--          | otherwise = loop w s

       nextState :: [Event]-> State-> IO State
       nextState evs t =
         do return $ checkIntersections s1{ship= moveShip (ship s1),
                      asteroids = moveAsteroids (asteroids s1),
                      bullets = moveBullets (bullets s1)}
           where
            s1= foldl (flip procEv) t evs

       getEvs :: IO [Event]
       getEvs = do x<- maybeGetWindowEvent w
                   case x of
                       Nothing -> return []
                       Just e  -> do rest <- getEvs
                                     return (e : rest)

       procEv :: Event-> State-> State
       procEv (Key {keysym= k, isDown=down})
        | isLeftKey k && down      = sethAcc hDelta
        | isLeftKey k && not down  = sethAcc 0
        | isRightKey k && down     = sethAcc (- hDelta)
        | isRightKey k && not down = sethAcc 0
        | isUpKey k && down        = setThrust aDelta
        | isUpKey k && not down    = setThrust 0
        | isDownKey k && down      = setThrust (- aDelta)
        | isDownKey k && not down  = setThrust 0
        | isControlLKey k && down  = shoot
       procEv _ = id

       sethAcc :: Double->State-> State
       sethAcc a t = s{ship= (ship t){hAcc= a}}

       setThrust :: Double-> State-> State
       setThrust a t = s{ship= (ship t){thrust= a}}

       shoot :: State -> State
       shoot t@State{ship = Ship{pos = pos0, ornt = r}, bullets = b} =
         t{bullets= b ++ [Bullet{time = 0, velB = speed,
          posB = pos0, shpB = shape (Translate (addWinMod pos0 speed) bullet),
          bboxb = translate bulletBox pos0}]}
            where speed = (rot r (round (1.5 * vMax), 0))

main :: IO ()
main = runGraphics $
  do w<- openWindowEx "Starshiptroopers II: Bugwars (ft. Spaceman Spiff)"
                      Nothing winSize DoubleBuffered
                      (Just 30)
     loop w initialState
     closeWindow w