HaskellでCyclic Cellular Automaton

# 循環的セル・オートマトン
twitterで見かけて興味を持ったので書いてみた糞コード。

<blockquote class="twitter-tweet" lang="ja"><p lang="ja" dir="ltr">セル・オートマトンについて調べていたら、「Cyclic Cellular Automata」というのに出くわしたんだけど、動画を見る限り、完全に頭おかしい挙動をしているので、是非実装してみたい……&#10;&#10;<a href="https://t.co/WUTJDWS9gF">https://t.co/WUTJDWS9gF</a></p>&mdash; えせはら(似非原重雄) (@esehara) <a href="https://twitter.com/esehara/status/634946538016387074">2015, 8月 22</a></blockquote>

CCAはセル・オートマトンの一種で、概ね次のようなものである：

1. セルはn段階の状態を持つ（n段階目の次の状態は第1段階に循環する）
2. 周囲に閾値以上の次段階セルがいるとき、自分も次ステップで次段階になる

したがって：

1. 近傍の種類（ムーア近傍かフォン・ノイマン近傍か）
2. 近傍の範囲
3. 閾値
4. セルの取りうる状態数

が指定されれば、このようなセル・オートマトンが決定される。
これに基づいてコード化されたCCAのカタログが以下にある

http://www.mirekw.com/ca/rullex_cycl.html

これにしたがって、ここでは "M 1 3 3" で「ムーア近傍、周囲1近傍、閾値3、状態数3」のCCAを表し、"N 1 1 14"で、「フォン・ノイマン近傍、周囲1近傍、閾値1、状態数14」のCCAを表すことにする。

# 適当に書く

Repaライブラリは使ってますが（repa, repa-algorithms, gloss, gloss-raster）、もの凄い適当なので

```shell-session
ghc -O3 -with-rtsopts="-N4 -H256m -K128m" -threaded CCA.hs
```

とかしてコンパイルしないとマトモな速度になりません。
セルはHSV色相環を状態数で均等分して色を決めRGBに変換して着色しています。

## 恒例のウンコード
```hs:CCA.hs
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad.ST (runST)
import System.Random (getStdRandom,random)
import System.Environment (getArgs)
import Data.Array.Repa as Repa
import Data.Array.Repa.Algorithms.Randomish (randomishIntArray)
import Graphics.Gloss.Interface.Pure.Game
import Graphics.Gloss.Data.Color
import Graphics.Gloss.Data.Picture
import Graphics.Gloss.Raster.Array

data Neighbour = Moore | Neumann deriving (Show, Eq)

data Rule = Rule { neighbour:: Neighbour
                 , range::Int
                 , threshold::Int
                 , types::Int } deriving (Show, Eq)

parse :: String -> Rule
parse str = parse' (words str)
parse' [w,x,y,z] = case w of
  "N" -> Rule {neighbour=Neumann, range=read x, threshold=read y, types=read z}
  "M" -> Rule {neighbour=Moore, range=read x, threshold=read y, types=read z}

type Field = Array U DIM2 Int

initialize :: (Int,Int) -> Rule -> IO Field
initialize (x,y) rule = do
  seed <- getStdRandom random
  return $ randomishIntArray (Z:.x:.y) 0 (types rule -1) seed

count :: Field -> DIM2 -> Rule -> Int
count f (Z :.x :.y) rule = case neighbour rule of
  Moore -> sum $ Prelude.map check
           [torus (i,j)|i<-[x-n..x+n],j<-[y-n..y+n]]
  Neumann -> sum $ Prelude.map check
           [torus (i,j)|i<-[x-n..x+n],j<-[y-n..y+n],(abs (x-i)+abs (y-j))<=n]
  where
      n = range rule
      (Z :.xmax :.ymax) = extent f
      torus (x,y) = ((mod x xmax),(mod y ymax))
      check (i,j) = if f!(Z:.i:.j) == mod (f!(Z:.x:.y)+1) (types rule) then 1 else 0

aggregate :: Field -> Rule -> Field
aggregate f r = runST $ do
  computeP $ Repa.traverse f id (\_ sh -> count f sh r)   

step :: Rule -> Field -> Field
step rule f = runST $ do
  computeP $ Repa.zipWith evolve f (aggregate f rule)
    where evolve x y = if y >= (threshold rule) then mod (x+1) (types rule) else x

toRGB :: Float -> Color
toRGB n = case (hIdx::Int) of
  0 -> makeColor v t p a
  1 -> makeColor q v p a
  2 -> makeColor p v t a
  3 -> makeColor p q v a
  4 -> makeColor t p v a
  5 -> makeColor v p q a
  _ -> error "hsvToRGB: hue outside of range [0..360]"
  where
  h = 360.0 * n
  s = 0.7
  v = 0.8
  a = 0.9
  hIdx = mod (floor (h / 60)) 6
  f    = h/60 - fromIntegral (hIdx::Int)
  p    = v*(1-s)
  q    = v*(1-s*f)
  t    = v*(1-s*(1-f))

render :: Rule -> Int -> Color
render r n = toRGB $ fromIntegral n /(fromIntegral $ types r)

main = do
  str <- getArgs
  let rule = parse $ str !! 0
  ini <- initialize (size,size) rule
  playArray dispmode (csize,csize) frq ini (Repa.map (render rule)) event (\_ -> step rule)
    where 
      dispmode =  (InWindow "Cyclic Cellular Automaton" fsize (40, 40))
      frq = 50
      size = 300
      csize = 1
      fsize = (csize*size,csize*size)
      event (EventKey (MouseButton LeftButton) Down  _ _ ) _ = undefined
      event  _ w = w

```

## 使い方
止めたいときは画面をクリックすると`undefined`で例外を吐いて死にます（お手軽）。Escキーでも可（これはGlossライブラリの仕様）。

```shell-session
./CCA "M 1 3 3"
./CCA "N 1 1 14"
```

## 実行結果

"M 1 3 3"
![スクリーンショット 2015-08-27 14.01.57.png](https://qiita-image-store.s3.amazonaws.com/0/33744/3ba6ca39-24cb-2bcb-c566-51271da4c2b1.png)

"N 1 1 14"
![スクリーンショット 2015-08-27 14.02.11.png](https://qiita-image-store.s3.amazonaws.com/0/33744/3b73ee23-f8b1-9a12-5de6-1732451255d8.png)

# 感想
リファクタリング？　何それ、おいしいの？　な糞コードですが意外に遊べました。