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 || 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 || dtime==130 || 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 || 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 = [Explosion{position = pos, hp = 1.0, size = 1.0} | (oldhp <= 0 && not isBoss)] 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{}) = [(e{hp = hp e - (2.4e-2 / size e)}) | (hp e > 0)] 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 = sqrt . distance2 a 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)