From d5403500fa6a5edb16303bbc6043968708adaced Mon Sep 17 00:00:00 2001 From: Thorn Avery Date: Sun, 18 Apr 2021 13:29:09 +1200 Subject: [PATCH] first commit --- .gitignore | 4 + LICENSE | 2 + README.md | 63 +++++++++++++++ Setup.hs | 2 + cellularAutomata.cabal | 30 ++++++++ flake.lock | 43 +++++++++++ flake.nix | 19 +++++ nix/cellularAutomata.nix | 13 ++++ overlay.nix | 3 + release.nix | 47 ++++++++++++ src/Main.hs | 194 +++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 420 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Setup.hs create mode 100644 cellularAutomata.cabal create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/cellularAutomata.nix create mode 100644 overlay.nix create mode 100644 release.nix create mode 100644 src/Main.hs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..580f715 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +result +result-doc +*.swp + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5531a16 --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +if u use dis repo u agree to not be a chud. +military and corps get out. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff1b0e5 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# cellularAutomata + +a small application for running a one-dimensional cellular automata from random inputs, using comonads + +## usage + +the program will default to the size of the window +`-w` and `-g` inputs can be given to determine the width and height, respectively + +## requirements + + * `getOpt` + * `ncurses` (for detecting term width/height) + +## building / running + +builds using nix + +from a local folder: +``` +nix build . +./result/bin/cellularAutomata +``` + +from the repo directly: +``` +nix run github:techieAgnostic/cellularAutomata -- -w 40 -g 25 +``` + +it may also be included as a flake input, as one normally would, and added to the package list using the included overlay + +## example + +`./cellularAutomata -w 40 -g 25` + +``` +██ ████ █ █████ █ ███ █ ███ ███ +██ █ ███ █ ██ █ ███ ██ █ █ █ █ ██ + █ ███ ███ █ ███ █ █ ██ ██ ███ + ███ █ █ █ █ ██ █ ███████ ██ ██ +███ █ █ █ ██ █ ██ ██ ███ ██ + █ █ █ ██████ █████ ██████ █ █ +██ ██ █ █ █ ██ █ █ ██ ██ █ +██ ██ ██ ███ █ █ ███ ███ █ █ █ + █ ███████ ██ ███ █ █ █ █ █ ███ █ ██ +█ █ █████ █ █ ██ █ ██ █ █ +███ █ ██ █ █ ████ █ ██ ██ +█ █ █ ████ █ █ █ ███ ██ █ █████████ + ██ █ █ ██ █ ███████ █ ██ + █████ ██ █ █████ █ ███ █ ███ + ██ █ ██ ███ ███ █ ██ █ █ ██ +████ █ █████ ██ ██ █ █ ███ ██ █ ████ + █ ███ █ ██ ██ ██ █ █████ █ ██ +█ █ ███ ██ █ ██ ████████ █ ██ █ ███ +█ █ █ ██ ████ █ ███ █ ███ █ █ +██ █ █████ █ █ ██ █ █ ██ █ +██ █ ██ ███ ██ █ ███ █ █ ██ █ █ +████ █████ ██ █ ██ ███ ███ ██████ ███ + █ █ █ ██ █████ █ █ ██ ██ █ █ + █ █ █ ███ ██ █ ██ ███ █ █ +██ █ █ ███ █ ███ █ █ ███ █ ███ █ █ + █ █ ██ █ █ █ █ ██ █ █ █ ██ +``` diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/cellularAutomata.cabal b/cellularAutomata.cabal new file mode 100644 index 0000000..5e84036 --- /dev/null +++ b/cellularAutomata.cabal @@ -0,0 +1,30 @@ +cabal-version: >=1.10 +-- Initial package description 'cellularAutomata.cabal' generated by 'cabal +-- init'. For further documentation, see +-- http://haskell.org/cabal/users-guide/ + +name: cellularAutomata +version: 0.1.0.0 +-- synopsis: +-- description: +-- bug-reports: +-- license: +license-file: LICENSE +author: Thorn Avery +maintainer: s@p7.co.nz +-- copyright: +-- category: +build-type: Simple + +executable cellularAutomata + main-is: Main.hs + -- other-modules: + -- other-extensions: + build-depends: base >=4.13 && <4.14 + , random + , turtle + , brick + , process + hs-source-dirs: src + default-language: Haskell2010 + extra-libraries: ncurses diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..1b78a66 --- /dev/null +++ b/flake.lock @@ -0,0 +1,43 @@ +{ + "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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..2d6785a --- /dev/null +++ b/flake.nix @@ -0,0 +1,19 @@ +{ + 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; + }; +} diff --git a/nix/cellularAutomata.nix b/nix/cellularAutomata.nix new file mode 100644 index 0000000..bfa6576 --- /dev/null +++ b/nix/cellularAutomata.nix @@ -0,0 +1,13 @@ +{ 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; +} diff --git a/overlay.nix b/overlay.nix new file mode 100644 index 0000000..6314995 --- /dev/null +++ b/overlay.nix @@ -0,0 +1,3 @@ +final: prev: { + cellularAutomata = (import ./release.nix) prev; +} diff --git a/release.nix b/release.nix new file mode 100644 index 0000000..a1075cc --- /dev/null +++ b/release.nix @@ -0,0 +1,47 @@ +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 diff --git a/src/Main.hs b/src/Main.hs new file mode 100644 index 0000000..7d4ad1c --- /dev/null +++ b/src/Main.hs @@ -0,0 +1,194 @@ +module Main where + +--import System.Random +import Control.Monad +import System.Process +import System.Random +import System.Console.GetOpt +import System.Environment(getArgs, getProgName) +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 + +-- 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 -- +--------------- + +-- 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 = do + options <- parseArgs + rng <- getStdGen + let cs = map (\x -> if x then Alive else Dead) $ ilobs rng + let w = (optWidth options) + let h = (optGenerations options) + let wh = (w + 1) `div` 2 + let m = head cs + let l = take wh $ drop 1 cs + let r = take wh $ drop wh $ drop 1 cs + let s = Space (l ++ (repeat Dead)) m (r ++ (repeat Dead)) + runAutomata s h w