エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

わかりやすいコードPart2・その2

エイリアン登場。まだ当り判定がありません。
一度作ったものの書き直しだからというのもあるのですが、思いのほか作業が早い。言語の違いにもっと手こずると思っていただけに、少し予想外でした。「わかりやすいコード」を意識したことで、より抽象的なレベルで考えようと心がけたからなのかも。手続き型言語関数型言語では、ずいぶん違いがあるように感じたんですが、表現の違いほどには本質は違っていないんでしょうか。

一方で、言語固有の問題で頭を悩ましたり。うまく文字列が表示できない。ネットで情報を探すも見つからず。この部分は行き詰まりを見せている。困った。

以下、コード。

import Graphics.UI.GLUT
import Control.Exception
import System.Exit
import Data.IORef
import System.Random

type Pos  = (GLfloat, GLfloat)
type Area = (GLfloat, GLfloat, GLfloat, GLfloat)

data Direction = ToLeft | ToRight | ToUp | ToDown

data Alien = Alien Pos GLfloat

interval     = 100
windowWidth  = 320::GLsizei
windowHeight = 240::GLsizei

shapeShip    = [(-10, 0), (0, -10), (10, 0), (10, 10), (-10, 10)]
shapeAlien   = [(-5, -5), (5, -5), (10, 0), (5, 5), (2, 2), (-2, 2), (-5, 5),(-10, 0)]
shapeBullet  = [(-3, -5), (0, -10), (3, -5), (0, 0)]

colorShip    = Color3 (0.0::Double) 0.0 1.0
colorAlien   = Color3 (1.0::Double) 0.5 0.0
colorScore   = Color3 (1.0::Double) 0.0 0.0

initialEnv :: (Area, Pos, [Pos], [Alien], [Pos], Integer, Integer)
initialEnv = 
  (
    (0, 0, convf windowWidth, convf windowHeight),           -- window size
    (convf $ div windowWidth 2, convf $ div windowHeight 2), -- ship's position
    [],                                                      -- bullets' position
    [],                                                      -- aliens' position
    [],                                                      -- alien's bullets' position
    0,                                                       -- score
    2                                                        -- left
  )

initialAlien = Alien (0, 20) 5

convf :: (Integral a) => a -> GLfloat
convf = fromInteger.toInteger

convd :: (Integral a) => a -> GLdouble
convd = fromInteger.toInteger

includes (x1, y1, x2, y2) (x, y) = (x1 <= x) && (x <= x2) && (y1 <= y) && (y <= y2)

display env = do
  draw env

reshape (Size w h) = do
  viewport $= (Position 0 0, Size w h)
  loadIdentity
  ortho (-0.5) ((convd w) - 0.5) ((convd h) - 0.5) (-0.5) (-1.0) (1.0)

keyboardMouse env key keystate modifiers position = do
  case key of
    SpecialKey KeyLeft  -> do { modifyIORef env $ moveShip ToLeft;  draw env }
    SpecialKey KeyRight -> do { modifyIORef env $ moveShip ToRight; draw env }
    SpecialKey KeyUp    -> do { modifyIORef env $ moveShip ToUp;    draw env }
    SpecialKey KeyDown  -> do { modifyIORef env $ moveShip ToDown;  draw env }
    Char ' '            -> do
      (_, _, sbs, _, _, _, _) <- readIORef env
      if length sbs == 0
        then modifyIORef env (? (w, sp, _, as, abs, s, l) -> (w, sp, [sp], as, abs, s, l))
        else return ()
    Char 'q'            -> throwIO $ ExitException ExitSuccess
    _                   -> return ()

moveShip direction (win, ship, sbs, as, abs, score, left) =
  (win, (move direction ship), sbs, as, abs, score, left)
  where
    move ToLeft  (x, y) = (x - 5.0, y)
    move ToRight (x, y) = (x + 5.0, y)
    move ToUp    (x, y) = (x, y - 5.0)
    move ToDown  (x, y) = (x, y + 5.0)

timer env = do
  f <- randomRIO (0, 19)
  seed <- newStdGen
  fs <- return $ randomRs (0, 19) seed
  modifyIORef env $ updateEnv f fs
  draw env
  addTimerCallback interval $ timer env

updateEnv f fs (win, ship, sbs, as, abs, score, left) =
  (win, ship, moveBullet sbs, addAlien $ updateAlien as, filter (includes win) $ addBullet (updateBullet abs) as fs, score, left)
  where
    moveBullet []           = []
    moveBullet [pos@(x, y)] = if (includes win pos) then [(x, y - 10)] else []
    updateAlien []     = []
    updateAlien (a:as) = (updateAlienPos a):(updateAlien as)
    updateAlienPos (Alien (x, y) dx) = if (includes win (x + dx, y)) 
                                         then Alien (x + dx, y) dx
                                         else Alien (x, y + 10) (-dx)
    addAlien as = if f == 0 then initialAlien:as else as
    updateBullet []     = []
    updateBullet (b:bs) = (updateBulletPos b):(updateBullet bs)
    updateBulletPos (x, y) = (x, y + 10)
    addBullet bs [] _                      = bs
    addBullet bs ((Alien pos _):as) (f:fs) = if f == 0 
                                               then pos:(addBullet bs as fs)
                                               else addBullet bs as fs

draw env = do
  (_, ship, sbs, as, abs, score, left) <- readIORef env
  clear [ColorBuffer]
  drawShip ship
  mapM_ (drawBullet colorShip) sbs
  mapM_ drawAlien as
  mapM_ (drawBullet colorAlien) abs
  -- drawScore score left
  swapBuffers

drawShip pos = do
  color colorShip
  render shapeShip pos

drawBullet col pos = do
  color col
  render shapeBullet pos

drawAlien (Alien pos _) = do
  color colorAlien
  render shapeAlien pos

drawScore score left = do
  translate $ Vector3 (10::GLfloat) 10 0
  scale (0.1::GLfloat) (-0.1) 0
  color colorScore
  renderString Roman $ "SCORE : " ++ (setw 6 $ show score) ++ " / LEFT : " ++ show left
  where
    setw n s = (replicate (n - length s) '0') ++ s

render shape (x, y) = renderPrimitive Polygon $ mapM_ vertex $ map makeVertex3 shape
  where
    makeVertex3 (dx, dy) = Vertex3 (x + dx) (y + dy) (0.0::GLfloat)

main = do
  env <- newIORef initialEnv
  getArgsAndInitialize
  initialDisplayMode    $= [RGBAMode, DoubleBuffered]
  initialWindowPosition $= Position 100 100
  initialWindowSize     $= Size windowWidth windowHeight
  createWindow "to write more comprehensible code"
  clearColor            $= Color4 1.0 1.0 1.0 1.0
  displayCallback       $= display env
  reshapeCallback       $= Just reshape
  keyboardMouseCallback $= Just (keyboardMouse env)
  addTimerCallback interval $ timer env
  mainLoop