ライフゲーム
以前Haskellで書いたLifeゲーム
-- life.hs -- $ ghc -package HGL life.hs -o life -- ライフゲーム -- 描画のオーバヘッドがかなり大きい。 -- 描画をしなければかなり速く動作するが、描画をすると重たい。 -- HGLの使い方で何か高速化できるのか?? import Graphics.HGL import System.Random ------------------------------------------------------------------- -- 型定義、環境定義 ------------------------------------------------------------------- -- 状態の定義 data State = On | Off deriving (Eq, Show) -- セルの色の定義 colorOn = RGB 230 240 230 colorOff = RGB 10 10 30 -- スクリーン情報 -- 96セル width :: Int width = 673 height :: Int height = 673 numWidth :: Int numWidth = 96 numHeight :: Int numHeight = 96 -- 32セル {- width :: Int width = 513 height :: Int height = 513 numWidth :: Int numWidth = 32 numHeight :: Int numHeight = 32 -} {- -- 8セル width :: Int width = 161 height :: Int height = 161 numWidth :: Int numWidth = 8 numHeight :: Int numHeight = 8 -} cellWidth :: Int cellWidth = div width numWidth cellHeight :: Int cellHeight = div height numHeight ------------------------------------------------------------------- -- メイン ------------------------------------------------------------------- main = runGraphics $ -- do w <- openWindowEx "LIFE" Nothing (width, height) DoubleBuffered (Just 100) do w <- openWindowEx "LIFE" Nothing (width, height) Unbuffered (Just 100) drawBoard w eventLoop w initMatrix 0 ------------------------------------------------------------------- -- 碁盤の目を描画(初期化) ------------------------------------------------------------------- drawBoard :: Window -> IO () drawBoard w = do drawHorizontal w drawVertical w putStrLn "drawBoard" drawHorizontal :: Window -> IO () drawHorizontal w = do drawHline w 0 drawHline :: Window -> Int -> IO () drawHline w pos = do let p1 = (0, pos) let p2 = (width, pos) if pos < height then do drawInWindow w $ line p1 p2 drawHline w (pos + cellHeight) else return () drawVertical :: Window -> IO () drawVertical h = do drawVline h 0 drawVline :: Window -> Int -> IO () drawVline w pos = do let p1 = (pos, 0) let p2 = (pos, height) if pos < width then do drawInWindow w $ line p1 p2 drawVline w (pos + cellWidth) else return () ------------------------------------------------------------------- -- セルの描画 ------------------------------------------------------------------- -- マトリックスの描画 -- 時々画面を更新する。 -- 描画が遅いので間引いて描画するための関数 interval :: Int interval = 100 drawSomeTimes :: Window -> Matrix -> Int -> IO () drawSomeTimes w m count | mod count interval == 0 = drawUpdate w m | otherwise = return () drawUpdate :: Window -> Matrix -> IO () --drawUpdate w m = drawInWindow w $ overGraphics $ concat $ drawMatrix w m drawUpdate w m = directDraw w $ overGraphics $ concat $ drawMatrix w m drawMatrix :: Window -> [[State]] -> [[Graphic]] drawMatrix w m = sub w 0 m where sub :: Window -> Int -> [[State]] -> [[Graphic]] sub w x [] = [] sub w x (y:ys) = drawRow w x y : sub w (x+1) ys drawRow :: Window -> Int -> [State] -> [Graphic] drawRow w x xs = sub w x 0 xs where sub :: Window -> Int -> Int -> [State] -> [Graphic] sub w x y [] = [] sub w x y (z:zs) = drawCell w x y z : sub w x (y+1) zs drawCell :: Window -> Int -> Int -> State -> Graphic drawCell w x y s = do let p1 = (cellWidth * x + 1, cellHeight * y + 1) let p2 = ((fst p1) + cellWidth - 1, (snd p1) + cellHeight - 1) let region = rectangleRegion p1 p2 if s == On then withRGB colorOn $ regionToGraphic region else withRGB colorOff $ regionToGraphic region ------------------------------------------------------------------- -- イベントループ ------------------------------------------------------------------- -- ESCでアプリ終了 eventLoop :: Window -> Matrix -> Int -> IO () eventLoop w m count = do e <- maybeGetWindowEvent w case e of Just(Char {char=c}) -> if c == '\ESC' then closeWindow w else if c == 't' then do t <- getTime putStrLn (show t) eventLoop w m count else eventLoop w m count Nothing -> do putStrLn ((show count) ++ "\t" ++ (show (countOnCell m))) drawUpdate w m -- drawSomeTimes w m count let newM = nextMatrix m getWindowTick w eventLoop w newM (count+1) _ -> do eventLoop w m count ------------------------------------------------------------------ -- セル状態の更新 ------------------------------------------------------------------- type Matrix = [[State]] -- マトリックスの状態を更新する。 nextMatrix :: Matrix -> Matrix nextMatrix m = sub m 0 m where sub :: Matrix -> Int -> [[State]] -> [[State]] sub m x [] = [] sub m x (y:ys) = nextRow m x y : sub m (x+1) ys nextRow :: Matrix -> Int -> [State] -> [State] nextRow m x xs = sub m x 0 xs where sub :: Matrix -> Int -> Int -> [State] -> [State] sub m x y [] = [] sub m x y (z:zs) = nextCell m x y : sub m x (y+1) zs -- 指定されたセルの次状態を計算する。 -- ルール -- 自身がOnの場合、周辺のOnの個数が2、3の場合は次もOn -- 自身がOffの場合、周辺のOnの個数が3の場合は次はOn -- それら以外はOffとなる。 nextCell :: Matrix -> Int -> Int -> State nextCell m x y = if (getState m x y) == On then if ((sum == 2) || (sum == 3)) then On else Off else if (sum == 3) then On else Off where sum = length [ x | x <- neighborsList, x == On] neighborsList = (getState m (x-1) (y-1)) : (getState m x (y-1)) : (getState m (x+1) (y-1)) : (getState m (x-1) y ) : (getState m (x+1) y ) : (getState m (x-1) (y+1)) : (getState m x (y+1)) : (getState m (x+1) (y+1)) : [] -- 指定された座標の状態値を得る。 -- マトリックスの境界条件はトーラス型とする。 -- 即ち、右端は左端と繋がっており、上端は下端と繋がっている。 getState :: Matrix -> Int -> Int -> State getState m x y | x < 0 = getState m (x+numWidth) y | x >= numWidth = getState m (x-numWidth) y | y < 0 = getState m x (y+numHeight) | y >= numHeight = getState m x (y-numHeight) | otherwise = (m !! x) !! y -- マトリックス中の有効セルの個数を調べる。 countOnCell :: Matrix -> Int countOnCell m = length [x | x <- (concat m), x == On] -- <<alternative>> --countOnCell m = foldl (+) 0 (map countOnRow m) --countOnRow xs = length [x | x <- xs, x == On] ------------------------------------------------------------------- -- 初期パターンの設定 ------------------------------------------------------------------- -- マトリックスにパターンを挿入する。 insertPattern :: Matrix -> Pattern -> Matrix insertPattern m p = foldl insertDot m p insertDot :: Matrix -> (Int,Int) -> Matrix insertDot m p = insertRow 0 m p insertRow :: Int -> [[State]] -> (Int,Int) -> [[State]] insertRow i (x:xs) p | (fst p) == i = (insertCell 0 x p) : xs | otherwise = x : insertRow (i+1) xs p insertCell :: Int -> [State] -> (Int,Int) -> [State] insertCell i [] p = [] insertCell i (x:xs) p | (snd p) == i = On : xs | otherwise = x : insertCell (i+1) xs p -- 初期マトリックス initMatrix = insertPattern nullMatrix patterns -- 全てのセルがOffのマトリックス nullMatrix :: Matrix nullMatrix = [nullRow| n <- [1 .. numWidth]] nullRow = [Off| n <- [1 .. numWidth]] -- パターン type Pattern = [(Int,Int)] --patterns = concat [spaceShip] --patterns = concat [spaceShip, glider] patterns = randomPatterns 2 -- グライダー glider :: Pattern glider = [(5,5), (4,6), (4,7), (5,7), (6,7)] -- 宇宙船 spaceShip :: Pattern spaceShip = [ ( 15 ,15 ), ( 18 ,15 ), ( 14 ,16 ), ( 14 ,17 ), ( 18 ,17 ), ( 14 ,18 ), ( 15 ,18 ), ( 16 ,18 ), ( 17 ,18 )] -- -- 乱数で初期配置を行う。 -- 引数の数値は全体のセル数に対してどの程度Onのセルを埋めるかを指定する。 -- この数値で割り算をするため、数値が大きい方が、初期配置のセル数は少なくなる。 randomPatterns :: Int -> Pattern randomPatterns p = zip hRandomList vRandomList where n = (div (numWidth * numHeight) p) hRandomList = take n $ randomRs (0, numWidth-1) $ mkStdGen 17485 vRandomList = take n $ randomRs (0, numHeight-1) $ mkStdGen 32254