Compare commits

..

10 commits

Author SHA1 Message Date
thornAvery 12e100c417 sholud be in test branch lmao 2021-10-15 00:06:04 +00:00
thornAvery e564146c4c should probably do this in a different branch 2021-10-14 22:34:25 +00:00
Thorn Avery 16925c182e added delay option 2021-04-21 11:19:57 +12:00
Thorn Avery 1d0d27cf07 updated inputs 2021-04-21 11:12:16 +12:00
Thorn Avery 68c7916fa4 clean up + easier rule generation 2021-04-21 10:45:00 +12:00
Thorn Avery 95808f5a9b updated readme 2021-04-20 19:52:24 +12:00
Thorn Avery bdda683c42 working 2d animation, no memory leak 2021-04-20 19:44:30 +12:00
Thorn Avery d582c20af3 working 2d animation, but leaks memory 2021-04-20 14:25:18 +12:00
Thorn Avery b45de145fe 2d automata 2021-04-19 20:17:34 +12:00
Thorn Avery 8ba2ad5da5 changed license to a real one 2021-04-18 14:31:15 +12:00
16 changed files with 527 additions and 451 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
result result
result-doc result-doc
*.swp *.swp
*.prof

11
LICENSE
View file

@ -1,2 +1,9 @@
if u use dis repo u agree to not be a chud. Copyright 2021 Thorn Avery
military and corps get out.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

125
README.md
View file

@ -1,11 +1,11 @@
# cellularAutomata # cellularAutomata
a small application for running a one-dimensional cellular automata from random inputs, using comonads a small application for running cellular automata from random inputs, using comonads
## usage ## usage
the program will default to the size of the window the program will default to the size of the window
`-w` and `-g` inputs can be given to determine the width and height, respectively `-w` and `-h` inputs can be given to determine the width and height, respectively
## requirements ## requirements
@ -69,123 +69,8 @@ due to haskell's laziness, the comonad space can (and should) be infinite in bot
as such, the functions to change our focus within the space assumes an infinite list, and i have defaulted to "clamping" our focus at the edges if a finite list is given on either direction. im not sure this is semantically the correct choice, but in regular usage (with infinite lists) it should not come up. as such, the functions to change our focus within the space assumes an infinite list, and i have defaulted to "clamping" our focus at the edges if a finite list is given on either direction. im not sure this is semantically the correct choice, but in regular usage (with infinite lists) it should not come up.
## example currently, as we are animating in a window, the program will turn the initially infinite grid into a grid the size of the window, therefore avoidinig memory leaks at the sides.
`./cellularAutomata -w 40 -g 25` in the future if we add the ability to render animated gifs of a cellular automata, it may be of use to keep the infinite grid, in the knowledge that we will not be running it indefinitely, but instead collecting garbage at the end of the animation.
``` this is the difference between a truly infinite grid, and one that is simply larger than our viewspace (but still finite).
██ ████ █ █████ █ ███ █ ███ ███
██ █ ███ █ ██ █ ███ ██ █ █ █ █ ██
█ ███ ███ █ ███ █ █ ██ ██ ███
███ █ █ █ █ ██ █ ███████ ██ ██
███ █ █ █ ██ █ ██ ██ ███ ██
█ █ █ ██████ █████ ██████ █ █
██ ██ █ █ █ ██ █ █ ██ ██ █
██ ██ ██ ███ █ █ ███ ███ █ █ █
█ ███████ ██ ███ █ █ █ █ █ ███ █ ██
█ █ █████ █ █ ██ █ ██ █ █
███ █ ██ █ █ ████ █ ██ ██
█ █ █ ████ █ █ █ ███ ██ █ █████████
██ █ █ ██ █ ███████ █ ██
█████ ██ █ █████ █ ███ █ ███
██ █ ██ ███ ███ █ ██ █ █ ██
████ █ █████ ██ ██ █ █ ███ ██ █ ████
█ ███ █ ██ ██ ██ █ █████ █ ██
█ █ ███ ██ █ ██ ████████ █ ██ █ ███
█ █ █ ██ ████ █ ███ █ ███ █ █
██ █ █████ █ █ ██ █ █ ██ █
██ █ ██ ███ ██ █ ███ █ █ ██ █ █
████ █████ ██ █ ██ ███ ███ ██████ ███
█ █ █ ██ █████ █ █ ██ ██ █ █
█ █ █ ███ ██ █ ██ ███ █ █
██ █ █ ███ █ ███ █ █ ███ █ ███ █ █
█ █ ██ █ █ █ █ ██ █ █ █ ██
```
example using `rule3` and a non-random starting position:
`./cellularAutomata -w 80 -g 80`
```
███
█ █ █
██ █ ██
█ █ █
███ ███ ███
█ █ █ █ █
██ ██ ███ ██ ██
█ █ █
███ ███ ███
█ █ █ █ █ █ █ █ █
██ █ ██ ██ █ ██ ██ █ ██
█ █ █ █ █
███ ███ ███ ███ ███
█ █ █ █ █ █ █ █ █ █ █
██ ██ ██ ██ ██ █ ██ ██ ██ ██ ██
█ █ █
███ ███ ███
█ █ █ █ █ █ █ █ █
██ █ ██ ██ █ ██ ██ █ ██
█ █ █ █ █ █ █ █ █
███ ███ ███ ███ ███ ███ ███ ███ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ ██ ███ ██ ██ ██ ██ ███ ██ ██ ██ ██ ███ ██ ██
█ █ █ █ █
███ ███ ███ ███ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ █ ██ ██ █ ██ ██ █ ██ ██ █ ██ ██ █ ██
█ █ █ █ █ █ █ █ █ █ █
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
█ █ █
███ ███ ███
█ █ █ █ █ █ █ █ █
██ █ ██ ██ █ ██ ██ █ ██
█ █ █ █ █ █ █ █ █
███ ███ ███ ███ ███ ███ ███ ███ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ ██ ███ ██ ██ ██ ██ ███ ██ ██ ██ ██ ███ ██ ██
█ █ █ █ █ █ █ █
█ ███ ███ ███ ███ ███ ███ ███ ██
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ ██ █ ██ ██ █ ██ ██ █ ██ ██ █ ██ ██ █ ██ ██ █ ██ ██ █ ██ ██ █
█ █ █ █ █ █ █ █ █ █ █ █
█ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ██
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ █
█ █ █
███ ███ ███
█ █ █ █ █ █ █ █ █
██ █ ██ ██ █ ██ ██ █ ██
█ █ █ █ █ █ █ █ █
███ ███ ███ ███ ███ ███ ███ ███ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ ██ ███ ██ ██ ██ ██ ███ ██ ██ ██ ██ ███ ██ ██
█ █ █ █ █ █ █
███ ███ ███ ███ ███ ███ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ █ ██ ██ █ ██ ██ █ ██ ██ █ ██ ██ █ ██ ██ █ ██ ██ █ ██
█ █ █ █ █ █ █ █ █ █ █ █ █
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ █ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
███
█ █ █
██ █ ██
█ █ █
███ ███ ███
█ █ █ █ █
██ ██ ███ ██ ██
█ █ █
███ ███ ███
█ █ █ █ █ █ █ █ █
██ █ ██ ██ █ ██ ██ █ ██
█ █ █ █ █
███ ███ ███ ███ ███
█ █ █ █ █ █ █ █ █ █ █
██ ██ ██ ██ ██ █ ██ ██ ██ ██ ██
█ █ █
```

View file

@ -3,7 +3,7 @@ cabal-version: >=1.10
-- init'. For further documentation, see -- init'. For further documentation, see
-- http://haskell.org/cabal/users-guide/ -- http://haskell.org/cabal/users-guide/
name: cellularAutomata name: cellular-automata
version: 0.1.0.0 version: 0.1.0.0
-- synopsis: -- synopsis:
-- description: -- description:
@ -11,20 +11,28 @@ version: 0.1.0.0
-- license: -- license:
license-file: LICENSE license-file: LICENSE
author: Thorn Avery author: Thorn Avery
maintainer: s@p7.co.nz maintainer: ta@p7.co.nz
-- copyright: -- copyright:
-- category: -- category:
build-type: Simple 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
, random , random
, 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

View file

@ -1,43 +0,0 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1601282935,
"narHash": "sha256-WQAFV6sGGQxrRs3a+/Yj9xUYvhTpukQJIcMbIi7LCJ4=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "588973065fce51f4763287f0fda87a174d78bf48",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1604368813,
"narHash": "sha256-UOLaURSO448k+4bGJlaSMYeo2F5F6CuFo9VoYDkhmsk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d105075a1fd870b1d1617a6008cb38b443e65433",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-20.09",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,19 +0,0 @@
{
description = "a basic cellular automata using comonads";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-20.09";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
overlays = [ (import ./overlay.nix) ];
inherit system;
};
in {
defaultPackage = pkgs.cellularAutomata;
}) // {
overlay = import ./overlay.nix;
};
}

View file

@ -1,13 +0,0 @@
{ mkDerivation, base, brick, lib, ncurses, process, random, turtle
}:
mkDerivation {
pname = "cellularAutomata";
version = "0.1.0.0";
src = ./..;
isLibrary = false;
isExecutable = true;
executableHaskellDepends = [ base brick process random turtle ];
executableSystemDepends = [ ncurses ];
license = "unknown";
hydraPlatforms = lib.platforms.none;
}

View file

@ -1,3 +0,0 @@
final: prev: {
cellularAutomata = (import ./release.nix) prev;
}

View file

@ -1,47 +0,0 @@
bspkgs:
let
dontCheckPackages = [ ];
doJailbreakPackages = [ ];
dontHaddockPackages = [ ];
config = {
packageOverrides = pkgs: rec {
haskellPackages =
let
generatedOverrides = haskellPackagesNew: haskellPackagesOld:
let
toPackage = file: _: {
name = builtins.replaceStrings [ ".nix" ] [ "" ] file;
value = haskellPackagesNew.callPackage
( ./. + "/nix/${file}") { };
};
in
pkgs.lib.mapAttrs' toPackage
(builtins.readDir ./nix);
makeOverrides =
function: names: haskellPackagesNew: haskellPackagesOld:
let
toPackage = name: {
inherit name;
value = function haskellPackagesOld.${name};
};
in
builtins.listToAttrs (map toPackage names);
composeExtensionsList =
pkgs.lib.fold pkgs.lib.composeExtensions (_: _: {});
manualOverrides = haskellPackagesNew: haskellPackagesOld: {
};
in
pkgs.haskellPackages.override {
overrides = composeExtensionsList [
generatedOverrides
(makeOverrides pkgs.haskell.lib.dontCheck dontCheckPackages)
(makeOverrides pkgs.haskell.lib.doJailbreak doJailbreakPackages)
(makeOverrides pkgs.haskell.lib.dontHaddock dontHaddockPackages)
manualOverrides
];
};
};
};
pkgs = import bspkgs.path { inherit config; system = bspkgs.system; };
in
pkgs.haskellPackages.cellularAutomata

136
src/Automata.hs Normal file
View file

@ -0,0 +1,136 @@
{-# LANGUAGE DeriveGeneric #-}
module Automata where
import Comonad
import Spaces.Space1
import Spaces.Space2
import System.Random
import GHC.Generics
import Control.DeepSeq
import Data.Maybe
-----------------------
-- 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
------------------------
-- grabbing neighbors --
------------------------
-- we want to be able to create a list of (Maybe CellState)
-- representing each neighbor, this way it will work on the
-- edges, and also we can fix the position of ecah neighbor
-- so that rules can be directional also.
grabNeighbors :: Space2 CellState -> [(Maybe CellState)]
grabNeighbors s = let
tl = grabTopLeft s
t = grabTop s
tr = grabTopRight s
l = grabLeft s
r = grabRight s
bl = grabBotLeft s
b = grabBot s
br = grabBotRight s
in [tl, t, tr, l, r, bl, b, br]
grabTemplate :: (Space2 CellState -> Maybe (Space2 CellState))
-> Space2 CellState -> Maybe CellState
grabTemplate f s = case f s of
Nothing -> Nothing
Just x -> Just $ extract x
grabTop, grabBot, grabLeft, grabRight :: Space2 CellState -> Maybe CellState
grabTop = grabTemplate up2
grabBot = grabTemplate down2
grabLeft = grabTemplate left2
grabRight = grabTemplate right2
maycom :: (a -> Maybe a) -> (a -> Maybe a) -> a -> Maybe a
maycom f g s = do
x <- f s
y <- g x
return y
grabTopLeft, grabTopRight, grabBotLeft, grabBotRight :: Space2 CellState -> Maybe CellState
grabTopLeft = grabTemplate (maycom up2 left2)
grabTopRight = grabTemplate (maycom up2 right2)
grabBotLeft = grabTemplate (maycom down2 left2)
grabBotRight = grabTemplate (maycom down2 right2)
filtJust :: [(Maybe a)] -> [a]
filtJust [] = []
filtJust (Nothing:as) = filtJust as
filtJust ((Just a):as) = a:(filtJust as)
numMatch :: CellState -> [(Maybe CellState)] -> Int
numMatch c = length . (filter (== c)) . filtJust
--------------
-- 2d rules --
--------------
conway :: Space2 CellState -> CellState
conway s = case extract s of
Rock -> Paper
Paper -> if numSci == 3 then Scissors else Paper
Scissors -> if numSci == 2 || numSci == 3 then Scissors else Paper
where
numSci = numMatch Scissors ns
ns = grabNeighbors s
rps :: Space2 CellState -> CellState
rps s
= case extract s of
Rock -> if (numNs Paper) > 2 then Paper else Rock
Paper -> if (numNs Scissors) > 2 then Scissors else Paper
Scissors -> if (numNs Rock) > 2 then Rock else Scissors
where
numNs c = numMatch c $ grabNeighbors s

70
src/BrickStuff.hs Normal file
View file

@ -0,0 +1,70 @@
{-# LANGUAGE OverloadedStrings #-}
module BrickStuff where
import Automata
import Spaces.Space2
import System.Random
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
-----------------
-- brick stuff --
-----------------
data Tick = Tick
type Name = ()
-- App definition
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
}
-- Handling events
theMap :: AttrMap
theMap = attrMap V.defAttr
[ (rockAttr, V.red `on` V.blue)
, (scissorsAttr, V.green `on` V.red)
, (paperAttr, V.blue `on` V.green)
]
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
bw = mat2 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"
initGame :: IO (Space2 CellState)
initGame = do
rng <- getStdGen
return $ createRandSpace2 rng

12
src/Comonad.hs Normal file
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)

View file

@ -1,212 +1,34 @@
module Main where module Main where
--import System.Random import Comonad
import Automata
import BrickStuff
import Options
import Spaces.Space2
import Brick
import Brick.BChan
import Control.Monad import Control.Monad
import System.Process import Control.Applicative
import System.Random import Control.Monad.IO.Class
import System.Console.GetOpt import Control.Concurrent
import System.Environment(getArgs, getProgName) import qualified Graphics.Vty as V
import Data.Maybe (fromMaybe)
-------------------
-- 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)
------------
-- spaces --
------------
-- a locally focussed space
data Space t = Space [t] t [t]
-- 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 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 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
-----------------------
-- 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 = Alive | Dead
deriving Eq
-- how the states are displayed on screen
-- this should probably be input to a function
-- rather than hardcoded
instance Show CellState
where
show Alive = ""
show Dead = " "
-- 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
-- take a space and a rule and
-- return the next space
step :: (Space t -> t) -> Space t -> Space t
step f w = w =>> f
---------------
-- rng stuff --
---------------
-- takes a generator and returns
-- an infinite list of bools
ilobs :: StdGen -> [Bool]
ilobs rng = b : (ilobs r)
where
(b,r) = random rng
-----------------
-- gross io bs --
-----------------
-- everything below this line deals with
-- input/output, and is therefore gross
-- i will clean this up one day, but it
-- hurts my soul.
------------------------
-- command line flags --
------------------------
-- structure containing the programs options
data Options = Options
{ optWidth :: Int
, optGenerations :: Int
} deriving Show
-- the default options for the program
-- the width and generations are injected
-- and intended to be gotten at runtime
-- to match the window dimensions
defaultOptions :: Int -> Int -> Options
defaultOptions w h = Options
{ optWidth = w
, optGenerations = h
}
-- the avaliable options
options :: [OptDescr (Options -> Options)]
options =
[ Option ['w'] ["width"]
(ReqArg (\w opts -> opts { optWidth = (read w) }) "WIDTH")
"term width"
, Option ['g'] ["generations"]
(ReqArg (\t opts -> opts { optGenerations = (read t) }) "GENERATIONS")
"time steps to simulate"
]
-- parse the options into the structure
-- erroring if encountering a flag not known to us
parseArgs :: IO Options
parseArgs = do
argv <- getArgs
progName <- getProgName
tw <- readProcess "tput" [ "cols" ] ""
th <- readProcess "tput" [ "lines" ] ""
case getOpt RequireOrder options argv of
(opts, [], []) -> return (foldl (flip id) (defaultOptions (read tw) (read th)) opts)
(_, _, errs) -> ioError (userError (concat errs ++ helpMessage))
where
header = "Usage: " ++ progName ++ " [OPTION...]"
helpMessage = usageInfo header options
--------------- ---------------
-- main loop -- -- main loop --
--------------- ---------------
-- simply print the current space, then recurse to the next
runAutomata :: Space CellState -> Int -> Int -> IO ()
runAutomata s 0 w = putStrLn $ concat $ map show $ boundw w s
runAutomata s n w = do
putStrLn $ concat $ map show $ boundw w s
runAutomata (step rule s) (n - 1) w
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 = (optGenerations options) let h = (optHeight options)
let wh = (w + 1) `div` 2 chan <- newBChan 1
let m = head cs forkIO $ forever $ do
let l = take wh $ drop 1 cs writeBChan chan Tick
let r = take wh $ drop wh $ drop 1 cs threadDelay $ (optTime options) * 10000
let s = Space (l ++ (repeat Dead)) m (r ++ (repeat Dead)) g <- initGame
-- non-random starting position for rule3 (the serpinski triangle) let buildVty = V.mkVty V.defaultConfig
--let s = Space (repeat Dead) Alive (repeat Dead) initialVty <- buildVty
runAutomata s h w void $ customMain initialVty buildVty (Just chan) (app h w) (clamp2 w h g)

61
src/Options.hs Normal file
View file

@ -0,0 +1,61 @@
module Options where
import System.Environment
import System.Console.GetOpt
import System.Process
------------------------
-- command line flags --
------------------------
-- structure containing the programs options
data Options = Options
{ optWidth :: Int
, optGenerations :: Int
, optHeight :: Int
, optTime :: Int
} deriving Show
-- the default options for the program
-- the width and generations are injected
-- and intended to be gotten at runtime
-- to match the window dimensions
defaultOptions :: Int -> Int -> Options
defaultOptions w h = Options
{ optWidth = w
, optGenerations = 40
, optHeight = h
, optTime = 7
}
-- the avaliable options
options :: [OptDescr (Options -> Options)]
options =
[ Option ['w'] ["width"]
(ReqArg (\w opts -> opts { optWidth = (read w) }) "WIDTH")
"term width"
, Option ['g'] ["generations"]
(ReqArg (\t opts -> opts { optGenerations = (read t) }) "GENERATIONS")
"time steps to simulate"
, Option ['h'] ["height"]
(ReqArg (\t opts -> opts { optHeight = (read t) }) "HEIGHT")
"term height"
, Option ['t'] ["time"]
(ReqArg (\t opts -> opts { optTime = (read t) }) "TIME")
"delay time"
]
-- parse the options into the structure
-- erroring if encountering a flag not known to us
parseArgs :: IO Options
parseArgs = do
argv <- getArgs
progName <- getProgName
tw <- readProcess "tput" [ "cols" ] ""
th <- readProcess "tput" [ "lines" ] ""
case getOpt RequireOrder options argv of
(opts, [], []) -> return (foldl (flip id) (defaultOptions (read tw) (read th)) opts)
(_, _, errs) -> ioError (userError (concat errs ++ helpMessage))
where
header = "Usage: " ++ progName ++ " [OPTION...]"
helpMessage = usageInfo header options

78
src/Spaces/Space1.hs Normal file
View file

@ -0,0 +1,78 @@
{-# LANGUAGE DeriveGeneric #-}
module Spaces.Space1 where
import Comonad
import System.Random
import Control.DeepSeq
import GHC.Generics
-- a locally focussed space
data Space t = Space [t] t [t]
deriving (Generic, Generic1, Show)
-- allowing strict evaluation of a space
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)
-- moving a space focus right
right :: Space t -> Maybe (Space t)
right (Space _ _ []) = Nothing
right (Space l c (r:rs)) = Just $ Space (c:l) r rs
-- moving a space's focus left
left :: Space t -> Maybe (Space t)
left (Space [] _ _) = Nothing
left (Space (l:ls) c r) = Just $ Space ls l (c:r)
-- iterate until we reach an edge
finterate :: (a -> Maybe a) -> a -> [a]
finterate f x = case (f x) of
Nothing -> []
Just y -> y : finterate f y
-- our space is a comonad
instance Comonad Space where
-- duplicate creats a meta space
duplicate w =
Space (finterate left w)
w
(finterate right w)
-- extract simply returns the focussed element
extract (Space _ c _) = c
-- clamp an infinite space to a finite space
-- relative to center
clampRel :: Int -> Int -> Space t -> Space t
clampRel x y (Space l m r) = Space (take x l) m (take y r)
-- as above, but with a set width
-- if the width is even, we need to take one less from the left
clamp :: Int -> Space t -> Space t
clamp w (Space l m r) = Space (take ln l) m (take h r)
where
h = w `div` 2
ln = h - (if even w then 1 else 0)
-- materialises a space, will hang if infinite
mat :: Space t -> [t]
mat (Space l m r) = (reverse l) ++ (m:r)
-- as above, but clamps to a given size first
matn :: Int -> Space t -> [t]
matn n = mat . (clamp n)
-- create a randomly filled space
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

122
src/Spaces/Space2.hs Normal file
View file

@ -0,0 +1,122 @@
{-# LANGUAGE DeriveGeneric #-}
module Spaces.Space2 where
import Comonad
import Spaces.Space1
import System.Random
import Data.Maybe
import Control.DeepSeq
import GHC.Generics
-- a nested space
data Space2 t = Space2 [(Space t)] (Space t) [(Space t)]
deriving (Generic, Generic1, Show)
-- generating strict data instances
instance NFData a => NFData (Space2 a)
instance NFData1 Space2
-- we can fmap into this structure by recursively fmapping
-- the inner spaces
instance Functor Space2 where
fmap f (Space2 u m d) =
Space2 (fmap (fmap f) u) (fmap f m) (fmap (fmap f) d)
-- map a partial function, converting to non maybe values
fintermap :: (a -> Maybe a) -> [a] -> [a]
fintermap _ [] = []
fintermap f (a:as) = case f a of
Nothing -> []
Just y -> y : fintermap f as
f :: ((Space2 a) -> Maybe (Space2 a)) -> Space (Space2 a) -> Maybe (Space (Space2 a))
f g (Space l m r) = case (g m) of
Nothing -> Nothing
Just y -> Just $ Space (fintermap g l) y (fintermap g r)
-- comonad instance for our 2d space
instance Comonad Space2 where
-- to duplicate we must recursively duplicate in all directions
-- the focussed space becomes the whole space, with left and right
-- mapped to each side.
-- to do the up and down lists, each needs to be the middle space
-- mapped up and down as far as we can.
-- up2 and down2 will return Nothing when they cant go further
duplicate w =
Space2 (finterate (f up2) dm) dm (finterate (f down2) dm)
where
dm = Space (finterate left2 w) w (finterate right2 w)
-- to extract we simply recursively extract
extract (Space2 _ m _) = extract m
-- directional moving of focus
up2 :: Space2 t -> Maybe (Space2 t)
up2 (Space2 [] _ _) = Nothing
up2 (Space2 (u:us) m d) = Just $ Space2 us u (m:d)
down2 :: Space2 t -> Maybe (Space2 t)
down2 (Space2 _ _ []) = Nothing
down2 (Space2 u m (d:ds)) = Just $ Space2 (m:u) d ds
noLeft :: Space t -> Bool
noLeft (Space [] _ _) = True
noLeft _ = False
noRight :: Space t -> Bool
noRight (Space _ _ []) = True
noRight _ = False
-- left and right require mapping further
-- we are assuming things are rectangular (maybe a bad idea?)
left2 :: Space2 t -> Maybe (Space2 t)
left2 (Space2 u m d) =
if check
then Nothing
else Just $ Space2 (fmap (f . left) u) (f $ left m) (fmap (f . left) d)
where
check = noLeft m
f l = fromJust l
right2 :: Space2 t -> Maybe (Space2 t)
right2 (Space2 u m d) =
if check
then Nothing
else Just $ Space2 (fmap (f . right) u) (f $ right m) (fmap (f . right) d)
where
check = noRight m
f l = fromJust l
-- clamp as we do in 1d Spaces
clampRel2 :: Int -> Int -> Int -> Int -> Space2 t -> Space2 t
clampRel2 w x y z (Space2 u m d) = Space2 (take w $ fmap f u) (f m) (take x $ fmap f d)
where
f = clampRel y z
clamp2 :: Int -> Int -> Space2 t -> Space2 t
clamp2 w h = clampRel2 nu nd nl nr
where
nu = h `div` 2
nd = nu - (if even h then 1 else 0)
nr = w `div` 2
nl = nr - (if even w then 1 else 0)
mat2 :: Space2 t -> [[t]]
mat2 (Space2 u m d) = (reverse (fmap mat u)) ++ ((mat m):(fmap mat d))
matn2 :: Int -> Int -> Space2 t -> [[t]]
matn2 w h = mat2 . (clamp2 w h)
step :: Comonad w => (w t -> t) -> w t -> w t
step f w = w =>> f
-- create a randomly filled space
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