packages feed

smarties-1.2.1: examples/tutorial/Main.hs

{-|
Module      : Main
Description : Conway Game of Life example
Copyright   : (c) Peter Lu, 2018
License     : GPL-3
Maintainer  : chippermonky@gmail.com
Stability   : experimental
-}

module Main where

import           Control.Concurrent
import           Control.Monad      hiding (sequence)
import           Data.List
import           Data.List.Index    (ifoldl)
import qualified Data.Vector        as V
import           Prelude            hiding (sequence)
import           Smarties
import           System.Random
import           Text.Printf

-- world size parameters
width :: Int
width = 20
height :: Int
height = 20
numberCells :: Int
numberCells = width * height

-- types
type Pos = (Int, Int)

addPos :: Pos -> Pos -> Pos
addPos (x1,y1) (x2,y2) = (x1+x2, y1+y2)

wrapCoords :: Pos -> Pos
wrapCoords (x,y) = (x `mod` width, y `mod` height)

flattenCoords :: Pos -> Int
flattenCoords (x,y) = y * width + x

wrapFlattenCoords :: Pos -> Int
wrapFlattenCoords = flattenCoords . wrapCoords

indexToPos :: Int -> Pos
indexToPos p = (p `mod` width, p `div` width)

neighbors :: [Pos]
neighbors = [(-1,0),(1,0),(0,-1),(0,1),(1,1),(1,-1),(-1,1),(-1,-1)]

countTrue :: [Bool] -> Int
countTrue = foldl (\acc x -> if x then acc+1 else acc) 0

-- behavior tree types
type Grid = V.Vector Bool
type PerceptionType = (Pos, Grid)
type ActionType = Bool

-- behavior tree methods
countNeighbors  :: NodeSequence g PerceptionType ActionType Int
countNeighbors = do
  (pos, grid) <- getPerception
  return . countTrue . map ((grid V.!) . wrapFlattenCoords . addPos pos) $ neighbors

ifNeighborsMoreThan :: Int -> NodeSequence g PerceptionType ActionType ()
ifNeighborsMoreThan x = do
  n <- countNeighbors
  condition (n > x)

ifNeighborsLessThan :: Int -> NodeSequence g PerceptionType ActionType ()
ifNeighborsLessThan = flipResult . ifNeighborsMoreThan . (\x -> x-1)

die :: NodeSequence g PerceptionType ActionType ()
die = fromAction $ SimpleAction (\_ -> False)

born :: NodeSequence g PerceptionType ActionType ()
born = fromAction $ SimpleAction (\_ -> True)

ifAlive :: NodeSequence g PerceptionType ActionType ()
ifAlive = do
  (pos, grid) <- getPerception
  condition $ grid V.! (wrapFlattenCoords pos)

ifDead :: NodeSequence g PerceptionType ActionType ()
ifDead = flipResult $ ifAlive

-- | our behavior tree
-- note rule 2. is a just a noop (Any live cell with two or three live neighbors lives on to the next generation.)
conwayTree :: NodeSequence g PerceptionType ActionType ()
conwayTree = do
  selector [
    -- rule 1. Any live cell with fewer than two live neighbors dies, as if by underpopulation.
    do
      ifAlive
      ifNeighborsLessThan 2
      die
    -- rule 3. Any live cell with more than three live neighbors dies, as if by overpopulation.
    , do
      ifAlive
      ifNeighborsMoreThan 3
      die
    -- rule 4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
    , do
      ifDead
      ifNeighborsMoreThan 2
      ifNeighborsLessThan 4
      born]

-- useful for testing :)
potatoTree :: NodeSequence g PerceptionType ActionType ()
potatoTree = do
  selector [
    do
      ifDead
      born
    , do
      ifAlive
      die]


-- | helper for writing grid to console :)
renderGrid :: Grid -> String
renderGrid = Data.List.Index.ifoldl func "" . V.toList where
    func acc i x = output where
        nl = if (i+1) `mod` width == 0 then "\n" else ""
        se = case x of
            True  -> "😱"
            False -> "🌱"
        output = printf "%s%s%s" acc se nl


-- | do conway rules for each grid cell
-- TODO make this run concurrently :). Need to pregen list of generators.
runConway :: g -> Grid -> (g, Grid)
runConway g0 grid = (g', V.zipWith ($) (V.fromList fns) grid) where
  (g', fns) = mapAccumL runTree g0 (map indexToPos [0..numberCells])
  runTree g pos = (g'', if null os then id else \_ -> last os) where
    (g'', _, _, os) = execNodeSequence conwayTree g (pos, grid)

-- | go!
main :: IO ()
main = do
  stdgen <- getStdGen
  let
    genesis = V.fromList . take numberCells $ randoms stdgen
    cycleOnce (n :: Int) (g, s) = do
      putStrLn "done"
      printf "gen %d\n" n
      putStrLn $ renderGrid s
      let (g',s') = runConway g s
      threadDelay 100000
      cycleOnce (n+1) (g',s')
  cycleOnce 0 (stdgen, genesis)