Browse Source

working 2d animation, but leaks memory

master
Thorn Avery 3 years ago
parent
commit
d582c20af3
6 changed files with 363 additions and 180 deletions
  1. +8
    -0
      cellularAutomata.cabal
  2. +6
    -2
      nix/cellularAutomata.nix
  3. +89
    -0
      src/Automata.hs
  4. +12
    -0
      src/Comonad.hs
  5. +72
    -178
      src/Main.hs
  6. +176
    -0
      src/Spaces.hs

+ 8
- 0
cellularAutomata.cabal View File

@@ -18,6 +18,8 @@ build-type: Simple


executable cellularAutomata executable cellularAutomata
main-is: Main.hs main-is: Main.hs
ghc-options: -threaded
-O2
-- other-modules: -- other-modules:
-- other-extensions: -- other-extensions:
build-depends: base >=4.13 && <4.14 build-depends: base >=4.13 && <4.14
@@ -25,6 +27,12 @@ executable cellularAutomata
, turtle , turtle
, brick , brick
, process , process
, containers
, linear
, microlens
, microlens-th
, vty
, deepseq
hs-source-dirs: src hs-source-dirs: src
default-language: Haskell2010 default-language: Haskell2010
extra-libraries: ncurses extra-libraries: ncurses

+ 6
- 2
nix/cellularAutomata.nix View File

@@ -1,4 +1,5 @@
{ mkDerivation, base, brick, lib, ncurses, process, random, turtle
{ mkDerivation, base, brick, containers, deepseq, lib, linear
, microlens, microlens-th, ncurses, process, random, turtle, vty
}: }:
mkDerivation { mkDerivation {
pname = "cellularAutomata"; pname = "cellularAutomata";
@@ -6,7 +7,10 @@ mkDerivation {
src = ./..; src = ./..;
isLibrary = false; isLibrary = false;
isExecutable = true; isExecutable = true;
executableHaskellDepends = [ base brick process random turtle ];
executableHaskellDepends = [
base brick containers deepseq linear microlens microlens-th process
random turtle vty
];
executableSystemDepends = [ ncurses ]; executableSystemDepends = [ ncurses ];
license = "unknown"; license = "unknown";
hydraPlatforms = lib.platforms.none; hydraPlatforms = lib.platforms.none;


+ 89
- 0
src/Automata.hs View File

@@ -0,0 +1,89 @@
{-# LANGUAGE DeriveGeneric #-}

module Automata where

import Comonad
import Spaces
import System.Random
import GHC.Generics
import Control.DeepSeq

-----------------------
-- cellular automata --
-----------------------

-- the states our cells can be in
-- may need to provide an ordering
-- may need to generalise the number
-- of states
data CellState = Rock | Paper | Scissors
deriving (Eq, Bounded, Enum, Generic)

instance NFData CellState

instance Random CellState where
random g = case randomR (fromEnum (minBound :: CellState), fromEnum (maxBound :: CellState)) g of
(r, g') -> (toEnum r, g')
randomR (a,b) g = case randomR (fromEnum a, fromEnum b) g of
(r, g') -> (toEnum r, g')

-- how the states are displayed on screen
-- this should probably be input to a function
-- rather than hardcoded
instance Show CellState
where
show Rock = "⬤"
show Paper = " "
show Scissors = "_"

-- -- a rule stating how a cell is determined
-- rule :: Space CellState -> CellState
-- rule (Space (l:_) _ (r:_))
-- | l == r = Dead
-- | otherwise = Alive
--
-- -- a second rule for example
-- rule2 :: Space CellState -> CellState
-- rule2 (Space (l1:l2:_) m (r1:r2:_))
-- | m == Alive && numAlive == 1 = Dead
-- | m == Alive && numAlive == 4 = Dead
-- | m == Dead && numAlive == 3 = Alive
-- | otherwise = m
-- where
-- ns = [l1, l2, r1, r2]
-- numAlive = length $ filter (== Alive) ns
--
-- rule3 :: Space CellState -> CellState
-- rule3 (Space (l:_) m (r:_))
-- | l == r = m
-- | otherwise = if m == Alive then Dead else Alive

--------------
-- 2d rules --
--------------

rps :: Space2 CellState -> CellState
rps (Space2 u m d)
= case me of
Rock -> if (length $ filter (== Paper) ns) > 2 then Paper else Rock
Paper -> if (length $ filter (== Scissors) ns) > 2 then Scissors else Paper
Scissors -> if (length $ filter (== Rock) ns) > 2 then Rock else Scissors
where
f b (Space (l:_) m (r:_)) = [l,r] ++ (if b then [m] else [])
f b (Space [] m (r:_)) = [r] ++ (if b then [m] else [])
f b (Space (l:_) m []) = [l] ++ (if b then [m] else [])
f b (Space [] m []) = if b then [m] else []
safeHead _ [] = []
safeHead b (x:_) = f b x
ns = concat [ (safeHead True u), (f False m), (safeHead True d) ]
me = extract m

--conway :: Space2 CellState -> CellState
--conway (Space2 (u:_) m (d:_))
-- = case me of
-- Alive -> if (length ns) == 2 || (length ns == 3) then Alive else Dead
-- Dead -> if (length ns) == 3 then Alive else Dead
-- where
-- f b (Space (l:_) m (r:_)) = [l,r] ++ (if b then [m] else [])
-- ns = filter (== Alive) $ concat [ (f True u), (f False m), (f True d) ]
-- me = extract m

+ 12
- 0
src/Comonad.hs View File

@@ -0,0 +1,12 @@
module Comonad where

-------------------
-- comonad class --
-------------------

class Functor w => Comonad w
where
(=>>) :: w a -> (w a -> b) -> w b
extract :: w a -> a
duplicate :: w a -> w (w a)
x =>> f = fmap f (duplicate x)

+ 72
- 178
src/Main.hs View File

@@ -1,3 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}

module Main where module Main where


import Control.Monad import Control.Monad
@@ -6,122 +8,45 @@ import System.Random
import System.Console.GetOpt import System.Console.GetOpt
import System.Environment(getArgs, getProgName) import System.Environment(getArgs, getProgName)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Comonad
import Spaces
import Automata
import Brick
import Brick.BChan (newBChan, writeBChan)
import qualified Brick.Widgets.Border as B
import qualified Brick.Widgets.Border.Style as BS
import qualified Brick.Widgets.Center as C
import qualified Graphics.Vty as V
import Control.Applicative
import Control.Monad.IO.Class
import Control.Concurrent
import Control.DeepSeq


-------------------
-------------------

class Functor w => Comonad w
where
(=>>) :: w a -> (w a -> b) -> w b
extract :: w a -> a
duplicate :: w a -> w (w a)
x =>> f = fmap f (duplicate x)

------------
------------

data Space t = Space [t] t [t]

instance Functor Space where
fmap f (Space l c r) = Space (map f l) (f c) (map f r)

instance Comonad Space where
-- duplicate will create a new space where
-- the focussed element is our original space
-- and each side is increasingly shifted copies
-- in that direction
duplicate w =
Space (tail $ iterate left w)
w
(tail $ iterate right w)
-- extract simply returns the focussed element
extract (Space _ c _) = c

right :: Space t -> Space t
right s@(Space l c []) = s
right (Space l c (r:rs)) = Space (c:l) r rs

left :: Space t -> Space t
left s@(Space [] c r) = s
left (Space (l:ls) c r) = Space ls l (c:r)

bound :: Int -> Int -> Space t -> [t]
bound i j (Space l c r) = (reverse (take i l)) ++ (c:(take j r))

boundw :: Int -> Space t -> [t]
boundw n = bound (x-m) x
where
o = if odd n then 1 else 0
m = if even n then 1 else 0
x = (n - o) `div` 2

-----------------------
-----------------------
-----------------
-- brick stuff --
-----------------


data CellState = Alive | Dead
deriving (Eq, Bounded, Enum)
data Tick = Tick
type Name = ()


instance Random CellState where
random g = case randomR (fromEnum (minBound :: CellState), fromEnum (maxBound :: CellState)) g of
(r, g') -> (toEnum r, g')
randomR (a,b) g = case randomR (fromEnum a, fromEnum b) g of
(r, g') -> (toEnum r, g')
-- App definition


instance Show CellState
where
show Alive = "█"
show Dead = " "
app :: Int -> Int -> App (Space2 CellState) Tick Name
app h w = App { appDraw = drawUI h w
, appChooseCursor = neverShowCursor
, appHandleEvent = handleEvent
, appStartEvent = return
, appAttrMap = const theMap
}


rule :: Space CellState -> CellState
rule (Space (l:_) _ (r:_))
| l == r = Dead
| otherwise = Alive
-- Handling events


rule2 :: Space CellState -> CellState
rule2 (Space (l1:l2:_) m (r1:r2:_))
| m == Alive && numAlive == 1 = Dead
| m == Alive && numAlive == 4 = Dead
| m == Dead && numAlive == 3 = Alive
| otherwise = m
where
ns = [l1, l2, r1, r2]
numAlive = length $ filter (== Alive) ns

rule3 :: Space CellState -> CellState
rule3 (Space (l:_) m (r:_))
| l == r = m
| otherwise = if m == Alive then Dead else Alive

step :: Comonad w => (w t -> t) -> w t -> w t
step f w = w =>> f
theMap :: AttrMap
theMap = attrMap V.defAttr
[ (rockAttr, V.red `on` V.blue)
, (scissorsAttr, V.green `on` V.red)
, (paperAttr, V.blue `on` V.green)
]


--------------- ---------------
-- rng stuff -- -- rng stuff --
@@ -134,74 +59,6 @@ ilobs rng = b : (ilobs r)
where where
(b,r) = random rng (b,r) = random rng


takeGive :: Int -> [a] -> ([a],[a])
takeGive n as = ( (take n as), (drop n as) )

--------------------------
--------------------------

data Space2 t =
Space2 [(Space t)]
(Space t)
[(Space t)]

instance Functor Space2 where
fmap f (Space2 u m d) =
Space2 (fmap (fmap f) u) (fmap f m) (fmap (fmap f) d)

instance Comonad Space2 where
duplicate w =
Space2 (tail $ iterate (f up2) dm)
dm
(tail $ iterate (f down2) dm)
where
f g (Space l m r) = Space (fmap g l) (g m) (fmap g r)
dm = Space (tail $ iterate left2 w) w (tail $ iterate right2 w)
extract (Space2 _ m _) = extract m

down2 :: Space2 t -> Space2 t
down2 w@(Space2 u m []) = w
down2 (Space2 u m (d:ds)) = Space2 (m:u) d ds

up2 :: Space2 t -> Space2 t
up2 w@(Space2 [] m d) = w
up2 (Space2 (u:us) m d) = Space2 us u (m:d)

left2 :: Space2 t -> Space2 t
left2 (Space2 u m d) = Space2 (fmap left u) (left m) (fmap left d)

right2 :: Space2 t -> Space2 t
right2 (Space2 u m d) = Space2 (fmap right u) (right m) (fmap right d)

bound2 :: Int -> Int -> Int -> Int -> Space2 t -> [[t]]
bound2 u d l r (Space2 uw mw dw) = (reverse (take u (map (bound l r) uw))) ++ ((bound l r mw):(take d (map (bound l r) dw)))

bound2w :: Int -> Int -> Space2 t -> [[t]]
bound2w x y = bound2 (r-q) r (n-m) n
where
o = if odd x then 1 else 0
m = if even x then 1 else 0
n = (x - o) `div` 2
p = if odd y then 1 else 0
q = if even y then 1 else 0
r = (y - p) `div` 2

--------------
--------------

conway :: Space2 CellState -> CellState
conway (Space2 (u:_) m (d:_))
= case me of
Alive -> if (length ns) == 2 || (length ns == 3) then Alive else Dead
Dead -> if (length ns) == 3 then Alive else Dead
where
f b (Space (l:_) m (r:_)) = [l,r] ++ (if b then [m] else [])
ns = filter (== Alive) $ concat [ (f True u), (f False m), (f True d) ]
me = extract m

----------------- -----------------
-- gross io bs -- -- gross io bs --
----------------- -----------------
@@ -262,47 +119,50 @@ parseArgs = do
header = "Usage: " ++ progName ++ " [OPTION...]" header = "Usage: " ++ progName ++ " [OPTION...]"
helpMessage = usageInfo header options helpMessage = usageInfo header options


initGame :: IO (Space2 CellState)
initGame = do
rng <- getStdGen
return $ createRandSpace2 rng

--------------- ---------------
-- main loop -- -- main loop --
--------------- ---------------


createRandSpace :: StdGen -> Space CellState
createRandSpace rng =
Space (tail $ map snd $ iterate f (r1, Alive))
(fst (random rng))
(tail $ map snd $ iterate f (r2, Alive))
where
f (r,b) = let (nb,nr) = (random r) in (nr,nb)
(r1,r2) = split rng

createRandSpace2 :: StdGen -> Space2 CellState
createRandSpace2 rng =
Space2 (tail $ map snd $ iterate f (r1, (createRandSpace r1)))
(createRandSpace rng)
(tail $ map snd $ iterate f (r2, (createRandSpace r2)))
where
f (r,s) = let (nr1,nr2) = split r in (nr2, (createRandSpace nr1))
(r1,r2) = split rng

--runAutomata :: Space2 CellState -> Int -> Int -> IO ()
--runAutomata s 0 w = putStrLn $ concat $ map show $ boundw w s
--runAutomata s n w = do

main :: IO () main :: IO ()
main = do main = do
options <- parseArgs options <- parseArgs
rng <- getStdGen
let cs = map (\x -> if x then Alive else Dead) $ ilobs rng
let w = (optWidth options) let w = (optWidth options)
let h = (optHeight options) let h = (optHeight options)
let g = (optGenerations options)
let s = createRandSpace2 rng
mapM_ (f w h) (loop conway g s)
chan <- newBChan 1
forkIO $ forever $ do
writeBChan chan Tick
threadDelay 100000
g <- initGame
let buildVty = V.mkVty V.defaultConfig
initialVty <- buildVty
void $ customMain initialVty buildVty (Just chan) (app h w) (clamp2cw w h g)

handleEvent :: (Space2 CellState) -> BrickEvent Name Tick -> EventM Name (Next (Space2 CellState))
handleEvent g (AppEvent Tick) = continue $ step rps g
handleEvent g (VtyEvent (V.EvKey (V.KChar 'q') [])) = halt g
handleEvent g _ = continue g

drawUI :: Int -> Int -> Space2 CellState -> [Widget Name]
drawUI h w g = [ C.center $ drawGrid h w g ]

drawGrid :: Int -> Int -> Space2 CellState -> Widget Name
drawGrid h w g = vBox rows
where where
f w h s = do
mapM_ putStrLn $ map (concat . (map show)) $ bound2w w h s
putStrLn (take w (repeat '-'))
loop f n s = take n $ iterate (step f) s
bw = bound2cw w h g
rows = [ hBox $ cellsInRow r | r <- bw ]
cellsInRow y = map drawCell y

drawCell :: CellState -> Widget Name
drawCell Paper = withAttr paperAttr $ str " "
drawCell Scissors = withAttr scissorsAttr $ str " "
drawCell Rock = withAttr rockAttr $ str " "

rockAttr, scissorsAttr, paperAttr :: AttrName
rockAttr = "rockAttr"
paperAttr = "paperAttr"
scissorsAttr = "scissorsAttr"

+ 176
- 0
src/Spaces.hs View File

@@ -0,0 +1,176 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}

module Spaces where

import Comonad
import System.Random
import Control.DeepSeq
import GHC.Generics

------------
-- spaces --
------------

-- a locally focussed space
data Space t = Space [t] t [t]
deriving (Generic, Generic1)

instance NFData a => NFData (Space a)
instance NFData1 Space

-- spaces are also functors
instance Functor Space where
fmap f (Space l c r) = Space (map f l) (f c) (map f r)

-- our space is a comonad
instance Comonad Space where
-- duplicate will create a new space where
-- the focussed element is our original space
-- and each side is increasingly shifted copies
-- in that direction
duplicate w =
Space (tail $ iterate left w)
w
(tail $ iterate right w)
-- extract simply returns the focussed element
extract (Space _ c _) = c

-- functions for moving the point
-- of locality.
-- todo: question the empty list cases
-- most spaces should be infinite
right :: Space t -> Space t
right w@(Space l m []) = w
right (Space l c (r:rs)) = Space (c:l) r rs

left :: Space t -> Space t
left w@(Space [] m r) = w
left (Space (l:ls) c r) = Space ls l (c:r)

-- bound will take an infinite space
-- and bound it by i and j on each side
-- (not including the focus) and
-- turn it into a list for printing
bound :: Int -> Int -> Space t -> [t]
bound i j (Space l c r) = (reverse (take i l)) ++ (c:(take j r))

-- boundw works as above, but the
-- entire list will be the size
-- given
boundw :: Int -> Space t -> [t]
boundw n = bound (x-m) x
where
o = if odd n then 1 else 0
m = if even n then 1 else 0
x = (n - o) `div` 2

---------------
-- 2d spaces --
---------------

data Space2 t =
Space2 [(Space t)]
(Space t)
[(Space t)]
deriving (Generic, Generic1)

instance NFData a => NFData (Space2 a)
instance NFData1 Space2

instance Functor Space2 where
fmap f (Space2 u m d) =
Space2 (fmap (fmap f) u) (fmap f m) (fmap (fmap f) d)

instance Comonad Space2 where
duplicate w =
Space2 (tail $ iterate (f up2) dm)
dm
(tail $ iterate (f down2) dm)
where
f g (Space l m r) = Space (fmap g l) (g m) (fmap g r)
dm = Space (tail $ iterate left2 w) w (tail $ iterate right2 w)
extract (Space2 _ m _) = extract m

down2 :: Space2 t -> Space2 t
down2 w@(Space2 u m []) = w
down2 (Space2 u m (d:ds)) = Space2 (m:u) d ds

up2 :: Space2 t -> Space2 t
up2 w@(Space2 [] m d) = w
up2 (Space2 (u:us) m d) = Space2 us u (m:d)

left2 :: Space2 t -> Space2 t
left2 (Space2 u m d) = Space2 (fmap left u) (left m) (fmap left d)

right2 :: Space2 t -> Space2 t
right2 (Space2 u m d) = Space2 (fmap right u) (right m) (fmap right d)

bound2 :: Int -> Int -> Int -> Int -> Space2 t -> [[t]]
bound2 u d l r (Space2 uw mw dw) = (reverse (take u (map (bound l r) uw))) ++ ((bound l r mw):(take d (map (bound l r) dw)))

bound2w :: Int -> Int -> Space2 t -> [[t]]
bound2w x y = bound2 (r-q) r (n-m) n
where
o = if odd x then 1 else 0
m = if even x then 1 else 0
n = (x - o) `div` 2
p = if odd y then 1 else 0
q = if even y then 1 else 0
r = (y - p) `div` 2

bound2cw :: NFData t => Int -> Int -> Space2 t -> [[t]]
bound2cw x y w = bound2 (r-q) r (n-m) n $ clamp2 (r-q+1) (r+1) (n-m+1) (n+1) w
where
o = if odd x then 1 else 0
m = if even x then 1 else 0
n = (x - o) `div` 2
p = if odd y then 1 else 0
q = if even y then 1 else 0
r = (y - p) `div` 2

clamp2cw :: NFData t => Int -> Int -> Space2 t -> Space2 t
clamp2cw x y w = clamp2 (r-q+1) (r+1) (n-m+1) (n+1) w
where
o = if odd x then 1 else 0
m = if even x then 1 else 0
n = (x - o) `div` 2
p = if odd y then 1 else 0
q = if even y then 1 else 0
r = (y - p) `div` 2

clamp2 :: NFData t => Int -> Int -> Int -> Int -> Space2 t -> Space2 t
clamp2 u d l r (Space2 uw mw dw)
= force $ Space2 (take u $ fmap (clamp l r) uw)
(clamp l r mw)
(take d $ fmap (clamp l r) dw)

clamp :: NFData t => Int -> Int -> Space t -> Space t
clamp x y (Space l m r) = force $ Space (take x l) m (take y r)

-- take a space and a rule and
-- return the next space
step :: Comonad w => (w t -> t) -> w t -> w t
step f w = w =>> f

-------------------
-- Random Spaces --
-------------------

createRandSpace :: Random a => StdGen -> Space a
createRandSpace rng =
Space (tail $ map snd $ iterate f (r1, (fst (random rng))))
(fst (random rng))
(tail $ map snd $ iterate f (r2, (fst (random rng))))
where
f (r,b) = let (nb,nr) = (random r) in (nr,nb)
(r1,r2) = split rng

createRandSpace2 :: Random a => StdGen -> Space2 a
createRandSpace2 rng =
Space2 (tail $ map snd $ iterate f (r1, (createRandSpace r1)))
(createRandSpace rng)
(tail $ map snd $ iterate f (r2, (createRandSpace r2)))
where
f (r,s) = let (nr1,nr2) = split r in (nr2, (createRandSpace nr1))
(r1,r2) = split rng

Loading…
Cancel
Save