{-# OPTIONS_GHC -fbang-patterns #-} module Main (main) where import Control.Exception (Exception(ExitException), catch, throwIO) import Control.Monad (zipWithM_) import Data.IORef (IORef, modifyIORef, newIORef, readIORef, writeIORef) import Data.List (nub) import Data.Maybe (isJust, fromJust) import Graphics.UI.GLUT (DisplayMode(RGBMode, DoubleBuffered), getArgsAndInitialize, initialDisplayMode, initialWindowSize, mainLoop, createWindow, destroyWindow, swapBuffers, Menu(..), MenuItem(MenuEntry), attachMenu, Font(renderString), StrokeFont(Roman), Flavour(Wireframe), Object(Tetrahedron, Teapot, Octahedron, Icosahedron, Dodecahedron), renderObject, MouseButton(LeftButton,RightButton), Key(SpecialKey, Char), KeyState(..), SpecialKey(KeyUp, KeyRight, KeyLeft, KeyDown), displayCallback, keyboardMouseCallback, TimerCallback, addTimerCallback, MatrixComponent(..), MatrixMode(Projection, Modelview), Position(..), Size(..), Vector3(..), loadIdentity, matrixMode, preservingMatrix, viewport, ClearBuffer(DepthBuffer, ColorBuffer), clear, HasSetter(..), lookAt, perspective, Color(color), Color3(..), Vertex3(..)) import Prelude hiding (catch) import System.Exit (ExitCode(ExitSuccess)) main :: IO () main = do keystate <- newIORef [] cp <- newIORef $ openingProc keystate initialWindowSize $= Size 1200 800 initialDisplayMode $= [RGBMode,DoubleBuffered] getArgsAndInitialize wnd <- createWindow "Shu-thing" displayCallback $= dispProc cp keyboardMouseCallback $= Just (keyProc keystate) addTimerCallback 24 $ timerProc $ dispProc cp attachMenu LeftButton (Menu [ MenuEntry "&Exit" exitLoop]) attachMenu RightButton (Menu [ MenuEntry "&Exit" exitLoop]) initMatrix mainLoop destroyWindow wnd `catch` (\_ -> return ()) exitLoop :: IO a exitLoop = throwIO $ ExitException ExitSuccess initMatrix :: IO () initMatrix = do viewport $= (Position 0 0,Size 1200 800) matrixMode $= Projection loadIdentity perspective 30.0 (4/3) 600 1400 lookAt (Vertex3 0 0 (927 :: Double)) (Vertex3 0 0 (0 :: Double)) (Vector3 0 1 (0 :: Double)) dispProc :: IORef (IO Scene) -> IO () dispProc !cp = do m <- readIORef cp Scene next <- m writeIORef cp next data Scene = Scene (IO Scene) openingProc :: IORef [Key] -> IO Scene openingProc !ks = do keystate <- readIORef ks clear [ColorBuffer,DepthBuffer] matrixMode $= Modelview 0 loadIdentity color $ Color3 (1.0 :: Double) 1.0 1.0 preservingMatrix $ do translate $ Vector3 (-250 :: Double) 0 0 scale (0.8 :: Double) 0.8 0.8 renderString Roman "shu-thing" preservingMatrix $ do translate $ Vector3 (-180 :: Double) (-100) 0 scale (0.4 :: Double) 0.4 0.4 renderString Roman "Press Z key" swapBuffers if Char 'z' `elem` keystate then do gs <- newIORef initialGameState return $ Scene $ mainProc gs ks else return $ Scene $ openingProc ks endingProc :: IORef [Key] -> IORef Double -> IO Scene endingProc !ks !ctr= do keystate <- readIORef ks counter <- readIORef ctr modifyIORef ctr $ min 2420 . (+1.5) clear [ColorBuffer,DepthBuffer] matrixMode $= Modelview 0 loadIdentity color $ Color3 (1.0 :: Double) 1.0 1.0 zipWithM_ (\str pos -> preservingMatrix $ do translate $ Vector3 (-180 :: Double) (-240+counter-pos) 0 scale (0.3 :: Double) 0.3 0.3 renderString Roman str) stuffRoll [0,60..] swapBuffers if Char 'x' `elem` keystate then do return $ Scene $ openingProc ks else return $ Scene $ endingProc ks ctr where stuffRoll = [ "", "Game Design", " T. Muranushi", "", "Main Programmer", " H. Tanaka", "", "Enemy Algorithm", " M. Takayuki", "", "Graphics Designer", " Euclid", "", "Monad Designer", " tanakh", "", "Lazy Evaluator", " GHC 6.8", "", "Cast", " Player Dodecahedron", " Bullet Tetrahedron", " Enemy Octahedron", " Boss Teapot", "", "Special thanks to", " Simon Marlow", " Haskell B. Curry", "", "Presented by", " team combat", "", "WE LOVE HASKELL!", "", " press x key"] mainProc :: IORef GameState -> IORef [Key] -> IO Scene mainProc !gs !ks = do keystate <- readIORef ks modifyIORef gs $ updateGameState keystate gamestate <- readIORef gs clear [ColorBuffer,DepthBuffer] matrixMode $= Modelview 0 loadIdentity renderGameState gamestate swapBuffers if (isGameover gamestate) then return $ Scene $ openingProc ks else if (isClear gamestate) then do counter <- newIORef (0.0 :: Double) return $ Scene $ endingProc ks counter else return $ Scene $ mainProc gs ks timerProc :: IO a -> TimerCallback timerProc m = m >> addTimerCallback 16 (timerProc m) keyProc :: IORef [Key] -> Key -> KeyState -> t -> t1 -> IO () keyProc keystate !key !ks _ _ = case (key,ks) of (Char 'q',_) -> exitLoop (Char 'c',_) -> exitLoop (_,Down) -> modifyIORef keystate $ nub . (++[key]) (_,Up) -> modifyIORef keystate $ filter (/=key) bosstime, bosstime2 :: Int bosstime = 6600 bosstime2 = 7200 data GameObject = Player {position :: !Point,shotEnergy :: !Double,hp :: !Double}| Bullet {position :: !Point} | EnemyMaker {timer :: !Int,deathtimer :: !Int}| Enemy {position :: !Point,hp :: !Double,anime :: !Int,enemySpec :: !EnemySpec} | Explosion {position :: !Point,hp :: !Double,size :: !Double}| EnemyBullet {position :: !Point,velocity :: !Point} | GameoverSignal | ClearSignal deriving (Eq) data EnemySpec = EnemySpec {ways :: !Int,spread :: !Double,speed :: !Double,freq :: !Int,endurance :: !Double,boss :: !Bool} deriving (Eq) updateObject :: GameState -> [Key] -> GameObject -> [GameObject] updateObject _ !ks (!Player{position=pos,shotEnergy=sen,hp=oldhp}) = [(Player{position=newPos,shotEnergy=nsen,hp=newhp})] ++ shots where newPos :: Point newPos = if (oldhp > 0) then (nx,ny) +++ v else (nx,ny) newhp = oldhp (x,y) = pos nx = if (x < (-310)) then -310 else if (x > 310) then 310 else x ny = if (y < (-230)) then -230 else if (y > 200) then 200 else y v = (vx,vy) *++ (5.0 :: Double) shots = replicate shotn $ Bullet pos nsen = if (shotn /= 0) then (-1.0) else if (shotmode == 1 && shotn == 0) then (sen+0.25) else if(shotmode == 0) then 0.0 else sen vx :: Double vx = if ((SpecialKey KeyLeft) `elem` ks) then -1 else 0 + if ((SpecialKey KeyRight) `elem` ks) then 1 else 0 vy = if ((SpecialKey KeyUp) `elem` ks) then 1 else 0 + if ((SpecialKey KeyDown) `elem` ks) then -1 else 0 shotmode :: Int shotmode = if ((Char 'z') `elem` ks) then 1 else 0 shotn :: Int shotn = if (oldhp <= 0) then 0 else if (shotmode == 0) then 0 else if (sen >= 0) then 1 else 0 updateObject _ _ (!Bullet{position=pos}) = replicate n (Bullet newpos) where newpos = pos +++ (0.0,15.0) n = if( (\(_,y) -> y > 250) pos)then 0 else 1 updateObject !gs _ (!EnemyMaker{timer=t,deathtimer=dtime}) = [EnemyMaker{timer=t+1,deathtimer=newdtime}] ++ enemies ++ deatheffects where enemies = replicate n $ Enemy{position = (320*sin(dt*dt),240),hp=1.0,anime=0,enemySpec = spec} dt :: Double dt = fromIntegral t newdtime = dtime + if (hp p<=0 || (bossExist&&hp b<=0)) then 1 else 0 n = if((t`mod`120==0 && t<=bosstime) || t==bosstime2) then 1 else 0 deatheffects = if(dtime==0) then [] else if(dtime==120) then [Explosion{position=position deadone,hp=1.0,size=3.0*deathradius}] else if(dtime==130) then [Explosion{position=position deadone,hp=1.0,size=3.0*deathradius}] else if(dtime==140) then [Explosion{position=position deadone,hp=1.0,size=3.0*deathradius}] else if(dtime==240) then [if(hp p<=0) then GameoverSignal else ClearSignal] else if(dtime>120) then [] else if(dtime`mod`15/=0)then [] else [Explosion{position=position deadone +++ ((sin(dt),cos(dt))*++ (16*deathradius)),hp=1.0,size=0.3*deathradius}] p = findplayer gs b = fromJust mayb deadone :: GameObject deadone = if(hp p<=0) then p else b deathradius = if(hp p<=0) then 1 else 3 bossExist = isJust mayb mayb = findBoss gs spec = if(t==bosstime2) then (EnemySpec{ways=0,spread=0.1,speed=3.0,freq=10,endurance=300.0,boss=True}) else speclist !! (t `div` 600) speclist = [ EnemySpec {ways=0,spread=0.1,speed=3.0,freq=30,endurance=2.0,boss=False}, EnemySpec {ways=1,spread=0.3,speed=5.0,freq=60,endurance=4.0,boss=False}, EnemySpec {ways=3,spread=0.7,speed=0.2,freq=90,endurance=8.0,boss=False}, EnemySpec {ways=45,spread=0.069,speed=8.0,freq=450,endurance=1.0,boss=False}, EnemySpec {ways=0,spread=0.1,speed=1.0,freq=10,endurance=10.0,boss=False}, EnemySpec {ways=0,spread=0.1,speed=1.0,freq=10,endurance=10.0,boss=False}, EnemySpec {ways=3,spread=0.1,speed=3.0,freq=60,endurance=6.0,boss=False}, EnemySpec {ways=1,spread=0.5,speed=7.0,freq=45,endurance=3.0,boss=False}, EnemySpec {ways=(10),spread=0.3,speed=15.0,freq=115,endurance=5.0,boss=False} ] ++ map (\o -> EnemySpec {ways=o,spread=0.1,speed=4.0,freq=20,endurance=3.0,boss=False}) [0,1 ..] updateObject !gs _ !oldenemy@(Enemy{position=pos,hp=oldhp,anime=oldanime,enemySpec=spec}) = replicate n (oldenemy{position=newpos,hp=newhp,anime=newanime,enemySpec=newspec}) ++ shots ++ explosions where newpos = if isBoss then (200 * sin(danime/100),200 + 40 * cos(danime/80)) else pos +++ (0.0,-1.0) newhp = oldhp newanime = oldanime + 1 newspec = if(not isBoss) then spec else if (oldhp>0.75) then EnemySpec{ways=0,spread=0.1,speed=5.0,freq=10,endurance=300.0,boss=True} else if (oldhp>0.50) then EnemySpec{ways=8,spread=0.15,speed=3.0,freq=30,endurance=300.0,boss=True} else if (oldhp>0.25) then EnemySpec{ways=2,spread=1.2,speed=15.0,freq=10,endurance=300.0,boss=True} else if (oldhp>0.05) then EnemySpec{ways=40,spread=0.075,speed=3.0,freq=60,endurance=400.0,boss=True} else if (oldhp>0.00) then EnemySpec{ways=15,spread=0.2,speed=16.0,freq=20,endurance=900.0,boss=True} else EnemySpec{ways=(-1),spread=0.1,speed=3.0,freq=10,endurance=300.0,boss=True} danime :: Double danime = fromIntegral oldanime explosions = if(oldhp<=0 && not isBoss) then [Explosion{position=pos,hp=1.0,size=1.0}] else [] shots = if(oldanime`mod` frq /=(frq-1)) then [] else map (\v -> EnemyBullet{position=pos,velocity=v}) vs vs = (take (wa+1) $ iterate (vdistr***) centerv) ++ (take wa $ tail $ iterate (vdistrc***) centerv) centerv = (pp -+- pos) *++ (spd / (distance pp pos)) vdistr :: Point vdistr = (cos(sprd),sin(sprd)) vdistrc :: Point vdistrc = (cos(sprd),-sin(sprd)) pp = playerpos gs n = if( (\(_,y) -> y<(-250)) pos || (not isBoss && oldhp<=0))then 0 else 1 wa = ways spec spd= speed spec frq = freq spec sprd= spread spec isBoss = boss spec updateObject _ _ !e@(Explosion{}) = if(hp e>0) then [e{hp=hp e - (0.024/(size e))}] else [] updateObject _ _ !eb@(EnemyBullet{}) = if(outofmap (position eb)) then [] else [eb{position=position eb+++velocity eb}] updateObject _ _ go = [go] watcher :: [GameObject] -> [GameObject] watcher os = np ++ ne ++ nb ++ neb ++ others where ne = foldr ($) enemies $ map enemyDamager bullets np = foldr ($) players $ map playerDamager ebullets nb = foldr ($) bullets $ map bulletEraser enemies neb = foldr ($) ebullets $ map ebEraser players bulletEraser :: GameObject -> [GameObject] -> [GameObject] bulletEraser e = filter (\b -> (distance2 (position b) (position e)) > hitr(e)) enemyDamager :: GameObject -> [GameObject] -> [GameObject] enemyDamager b = map (\e -> if ((distance2 (position b) (position e)) > hitr(e)) then e else (\d -> d{hp=hp d-(1.0 / (endurance (enemySpec d)))}) e) hitr e = if(boss $ enemySpec e) then sq 100 else sq 32 playerDamager :: GameObject -> [GameObject] -> [GameObject] playerDamager eb = map (\p -> if((distance2 (position p) (position eb)) > 70) then p else (\q -> q{hp=hp q-0.3}) p) ebEraser p = filter (\eb -> (distance2 (position eb) (position p)) > 70) (enemies,bullets,ebullets,players,others) = foldl f ([],[],[],[],[]) os f (e,b,eb,p,x) o = case o of Enemy{} -> (o:e,b,eb,p,x) Bullet{} -> (e,o:b,eb,p,x) EnemyBullet{} -> (e,b,o:eb,p,x) Player{} -> (e,b,eb,o:p,x) _ -> (e,b,eb,p,o:x) renderGameObject :: GameObject -> IO () renderGameObject !Player{position=pos,hp=h} = preservingMatrix $ do let (x,y) = pos color (Color3 (1.0 :: Double) h h) translate (Vector3 x y 0) scale (10 :: Double) 10 10 rotate (x) (Vector3 0 1 0) rotate (30 :: Double) (Vector3 0 0 1) renderObject Wireframe Dodecahedron renderGameObject !Bullet{position=pos} = preservingMatrix $ do let (x,y) = pos color (Color3 (0.6 :: Double) 0.6 1.0) translate (Vector3 x y 0) scale (4 :: Double) 18 8 rotate (45 :: Double) (Vector3 0 1 0) rotate (90 :: Double) (Vector3 1 0 0) renderObject Wireframe Tetrahedron renderGameObject !Enemy{position=pos,anime=a,hp=h,enemySpec=EnemySpec{boss=False}} = preservingMatrix $ do let (x,y) = pos color (Color3 (cos rho) (sin rho) (0.0 :: Double)) translate (Vector3 x y 0) rotate (2*(theta :: Double)) (Vector3 0 1.0 0) scale (32 :: Double) 32 8 renderObject Wireframe Octahedron where theta = fromIntegral a rho = h * 3.14 / 2 renderGameObject !Enemy{position=pos,anime=a,hp=h,enemySpec=EnemySpec{boss=True}} = preservingMatrix $ do let (x,y) = pos color (Color3 (cos rho) (sin rho) (0.0 :: Double)) translate (Vector3 x y 0) rotate (2*(theta :: Double)) (Vector3 0 1.0 0) scale (120 :: Double) 120 120 renderObject Wireframe (Teapot 1.0) where theta = fromIntegral a rho = h * 3.14 / 2 renderGameObject !Explosion{position=pos,hp=h,size=s}= preservingMatrix $ do let (x,y) = pos color (Color3 h 0.0 0.0) translate (Vector3 x y 0) rotate (720*h) (Vector3 0 1.0 0) rotate (540*h) (Vector3 1.0 0 0) scale r r r renderObject Wireframe Icosahedron where r = s*(100 - h*h*80) renderGameObject !EnemyBullet{position=pos} = preservingMatrix $ do let (x,y) = pos color (Color3 (1.0 :: Double) 1.0 1.0) translate (Vector3 x y 0) scale (5 :: Double) 5 5 rotate (45 :: Double) (Vector3 0 0 (1.0 :: Double)) renderObject Wireframe Tetrahedron renderGameObject _ = return () data GameState = GameState {objects :: [GameObject]} initialGameState :: GameState initialGameState = GameState{objects= [(Player{position=(0.0,0.0),shotEnergy=0.0,hp=1.0}),(EnemyMaker{timer=0,deathtimer=0})]} renderGameState :: GameState -> IO () renderGameState !GameState{objects=os} = mapM_ renderGameObject os updateGameState :: [Key] -> GameState -> GameState updateGameState !ks !gs@(GameState { objects=os }) = newgs where newgs = GameState{objects = watcher $ concatMap (updateObject gs ks) os} playerpos :: GameState -> Point playerpos = position . findplayer findplayer :: GameState -> GameObject findplayer !GameState{objects=os} = player where [player] = filter (\o -> case o of Player{} -> True _ -> False) os findBoss :: GameState -> Maybe GameObject findBoss GameState{objects=os} = if (length bosses==0) then Nothing else Just (head bosses) where bosses = filter(\o -> case o of Enemy{} -> boss( enemySpec o) _ -> False) os isGameover :: GameState -> Bool isGameover GameState{objects=os} = (GameoverSignal `elem` os) isClear :: GameState -> Bool isClear GameState{objects=os} = (ClearSignal `elem` os) type Point = (Double,Double) (+++) :: (Double, Double) -> (Double, Double) -> (Double, Double) (ax,ay) +++ (bx,by) = (ax+bx, ay+by) (-+-) :: (Num t1, Num t) => (t, t1) -> (t, t1) -> (t, t1) (!ax,!ay) -+- (!bx,!by) = (ax-bx,ay-by) (*++) :: (Num t) => (t, t) -> t -> (t, t) (!ax,!ay) *++ (!s) = (ax*s,ay*s) (***) :: (Num t) => (t, t) -> (t, t) -> (t, t) (!ax,!ay) *** (!bx,!by) = (ax*bx-ay*by,ay*bx+ax*by) sq :: Double -> Double sq !x = x*x distance, distance2 :: Point -> Point -> Double distance a b= sqrt $ distance2 a b distance2 !(ax,ay) !(bx,by) = sq(ax-bx) + sq(ay-by) outofmap :: Point -> Bool outofmap !(x,y) = (not $ abs x < 320) || (not $ abs y < 240)