Haskellで作るコンソールLife Game

Fortranと呼ばれる古代言語を書き連ねる日々に嫌気が差したので、副作用という穢れを払うためHaskellの力を借りることにしました.
いまもHaskellの勉強中なのですが、結局モナドは、関数や値を包み込みこんで`<<=`といった演算子や特定の関数で操作を行うことを明示する、OOPでいうカプセル化の概念に近いものなのかな？
まぁやっぱりプログラミングを学ぶには見るより、書くのが一番だと思うので、コンソール上で動作する簡単なLifeGameを書いてみました.

##ゲーム状態の定義
フィールドの状態としてはセルをその座標`Pos`型で示し、全体としては`Board`型で管理します.
`Board`は生きたセルを要素とする`Pos`型のリストです. また必要なライブラリもimportしておきます.

```haskell
import Control.Monad
import System.Random

type Pos = (Int, Int)
type Board = [Pos]
```

##フィールドのサイズ
変更しやすくするため横幅`width`と縦幅`height`も関数で定義しておきます.

```haskell
width :: Int
width = 50

height :: Int
height = 50
```

##各セルの状態チェック
次に`Pos`型の座標から`Board`内にそのセルがあるかをチェックし、そのセルが生きているかを`Bool`型で返す関数`isAlive`を用意します. また逆に`isEmpty`関数はセルにライフがいないかを返します.

```haskell
isAlive :: Board -> Pos -> Bool
isAlive b p = p `elem` b

isEmpty :: Board -> Pos -> Bool
isEmpty b p = not (isAlive b p)
```

##周りのセル取得
`neighbers`関数はセル座標`Pos`からその周り8方向のセルを取得します. 今回はフィールドから外に出ると逆側に出る周期条件を採用しているため、`wrap`関数で境界部分の周囲のセル取得に対応します.

```haskell
wrap :: Pos -> Pos
wrap (x, y) = (((x-1) `mod` width) + 1,
               ((y-1) `mod` height)+ 1)

neighbers :: Pos -> [Pos]
neighbers (x, y) = map wrap [(x-1, y-1), (x, y-1),
                             (x+1, y-1), (x-1, y),
                             (x+1, y),   (x-1, y+1),
                             (x, y+1),   (x+1, y+1)]
```

##次世代のライフ
`liveneighbers`関数は周囲の生きたセルの数を「`Pos`型の引数から返す関数」を返す関数です.
そして`survivors`は現世代から次世代で生き残るセルのリストを返します.
一方`births`は次世代で新たにライフが生まれるセルのリストを返します.
`births`内で使っている`rmdups`はリストから重複要素を除いて1つにします.
そして最後に`survivors`と`births`から`nextgen`で次世代のライフを取得します.

```haskell
liveneighbers :: Board -> Pos -> Int
liveneighbers b = length . filter (isAlive b) . neighbers

survivors :: Board -> [Pos]
survivors b = [p | p <- b, liveneighbers b p `elem` [2, 3]]

rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) = x : rmdups (filter (/= x) xs)

births :: Board -> [Pos]
births b = [p | p <- rmdups (concatMap neighbers b),
                isEmpty b p,
                liveneighbers b p == 3]

nextgen :: Board -> Board
nextgen b = survivors b ++ births b
```

##描画用関数
次にコンソールに表示するための関数を用意します.
`cls`はコンソールの画面をクリアします. そして`goto`でカーソルの場所を移動させ、`writeat`でライフを表示します. 実際に画面表示をするのは`showcells`で、アクションリストの要素アクションを順次実行する`seqn`関数を使って描画していきます.

```haskell
cls :: IO ()
cls = putStr "\ESC[2J"

goto :: Pos -> IO ()
goto (x, y) = putStr ("\ESC[" ++ show y ++ ";" ++ show x ++ "H")

writeat :: Pos -> String -> IO ()
writeat p xs = do goto p
                  putStr xs

seqn :: [IO a] -> IO ()
seqn [] = return ()
seqn (a:as) = do a
                 seqn as

showcells :: Board -> IO ()
showcells b = seqn [writeat p "O" | p <- b]
```

##ゲームサイクル
最後にゲーム本体を定義します.
`wait`は世代間で描画の間を取るためだけの関数です.

```haskell
lifegame :: Board -> IO ()
lifegame b = do cls
                showcells b
                wait 5000
                lifegame (nextgen b)
```

##初期状態
`lifegame`関数を呼ぶには初期状態の`Board`を与えてあげないと行けないので、それを返す関数を用意します.
`makeLife`で乱数を使って任意の場所のセル座標を返します.
そして`initBoard`で指定した数のライフを持つ`Board`を取得します.今回は`Board`サイズの1/5の数としています.

```haskell
makeLife :: IO Pos
makeLife = do
        x <- getStdRandom $ randomR(1, width) :: IO Int
        y <- getStdRandom $ randomR(1, height) :: IO Int
        return (x, y)

initBoard :: IO Board
initBoard = replicateM (width * height `div` 5) makeLife
```

##完成!!!
```haskell
main :: IO ()
main = lifegame =<< initBoard
```

さぁ実行してみましょう!!!!!

![lifegame](https://cl.ly/1g2B360b3M1U/lifegame.png)




...........
はい！！！すばらしいですね！！！すばらしいです！！！

..........
閑話休題

関数型でまともにコード書いたのは初めてですが、haskellの型->型の型を強く意識させる仕様は関数の細分化を促し、そのため関数それぞれの独立性が強いように感じました. 安定性・安全性が高いといわれているのもなんだかわかる気がします. なにより単純な関数の組み合わせで複雑な処理が簡潔に書けるのは気持ちいいですね. 次は練習がてら前々からやろうと思ってたProject EulerをHaskellで書いてみようかな.