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 newtype 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 | (not isBoss) = spec | (oldhp > 0.75) = EnemySpec{ways=0,spread=0.1,speed=5.0,freq=10,endurance=300.0,boss=True} | (oldhp > 0.50) = EnemySpec{ways=8,spread=0.15,speed=3.0,freq=30,endurance=300.0,boss=True} | (oldhp > 0.25) = EnemySpec{ways=2,spread=1.2,speed=15.0,freq=10,endurance=300.0,boss=True} | (oldhp > 0.05) = EnemySpec{ways=40,spread=0.075,speed=3.0,freq=60,endurance=400.0,boss=True} | (oldhp > 0.00) = EnemySpec{ways=15,spread=0.2,speed=16.0,freq=20,endurance=900.0,boss=True} | otherwise = 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)