7QP2V7ZBVZ7SKQ3SYV6NYA7V77G3Y7GF5V6EPIXLIYFSMFRWR4VAC module Test whereimport System.Exitmain :: IO ()main = exitWith ExitSuccess
module Main whereimport Noveimport Nove.Verseimport Zero.Queueimport Data.Bifunctor ( bimap )import Data.List ( find, intersperse )import Data.Foldable ( toList )import Data.Map as Map ( Map, elems, filterWithKey )import Control.Monad ( join )import Terminal.Gameimport System.Random ( randoms, initStdGen )-- Game IO loopmain :: IO ()main = do(rs,r) <- splitAt (length units) . randoms <$> initStdGenplayGame $ Game {gTPS = 10 ,gInitState = (State { rand = r , center = 0 , keys = (Nothing,mempty) } , generate (verse 11) rs) ,gLogicFunction = logic ,gDrawFunction = draw }wheregenerate :: Verse Unit -> [Int] -> Verse Unitgenerate v rs = foldr ($) (verse 11)$ set Robot { name = "@" , ops = mempty } 0: set Robot { name = "r" , ops = Move <$> cycle [U,N,L] } (shift v 1 U 0): set Robot { name = "g" , ops = Move <$> cycle [N,L,U] } (shift v 2 U 0): set Robot { name = "b" , ops = Move <$> cycle [L,U,N] } (shift v 1 U $ shift v 1 H 0): set Robot { name = "i" , ops = Move <$> repeat N } (shift v 8 I 0): set Robot { name = "n" , ops = Move <$> repeat I } (shift v 2 I 0): set Robot { name = "x" , ops = mempty } (shift v 7 M 0): zipWith set units (flip mod (length $ nodes v) <$> rs)units :: [Unit]units = replicate 4 Wall <> replicate 6 Boxdata Unit = None | Wall | Box | Robot { name :: String , ops :: [Op] }deriving Showdata Op = Lock | Move Dirderiving Showinstance Atom Unit wherevoid = Nonemove a| Robot {} <- a , Move d : _ <- ops a = Just d| otherwise = Nothinglock a| Wall <- a = True| Robot {} <- a = True| otherwise = Falsedata State = State{ rand :: [Int], center :: Int, keys :: (Maybe Char,Queue Char)}-- Logiclogic :: GEnv -> (State,Verse Unit) -> Event -> Either () (State,Verse Unit)logic _ _ (KeyPress 'q') = Left ()logic _ (s,v) (KeyPress k) = Right (s { keys = keys' } , v)where-- prevent repeat before tickkeys' :: (Maybe Char,Queue Char)| Just (q,_) <- dequeue $ snd $ keys s , k == q = keys s| otherwise = enqueue k <$> keys slogic _ (s,v) Tick = Right (s' , sim (Automata demo) s' v)wherez :: State| Just (c,q) <- dequeue (snd $ keys s) = s { keys = (Just c , q) }| otherwise = s { keys = (Nothing,snd $ keys s) }s' :: State| Just 'H' <- k = z { center = shift v 1 H $ center s }| Just 'J' <- k = z { center = shift v 1 N $ center s }| Just 'K' <- k = z { center = shift v 1 I $ center s }| Just 'L' <- k = z { center = shift v 1 L $ center s }| otherwise = zwherek = fst $ keys z-- Drawdraw :: GEnv -> (State,Verse Unit) -> Planedraw e (s,v) = centerFull e $ hcat [hex,ui] & (1,1) % makeTransparent ' ' (word "nove demo")wherer :: Intr = radius vcanvas :: Planecanvas = blankPlane (2 * succ (2 * r) + 2) (succ (2 * r) + 2)hex :: Planehex = foldl (&) canvas $ [ write (x,y) | x <- [-r..r] , y <- [-r..r] , abs (x + y) <= r ]write :: (Int,Int) -> Drawwrite (x,y) = c %.< cell grapheme # stylewherea :: Unita = node n v-- get index of node taking scroll into accountn :: Intn = shift v mx L . shift v my I $ coordToIndex v (mod (x - div y r * r) (3 * r) , mod y r)where(mx,my) = indexToCoord v (center s)-- stretch, tilt, margin, translate to library coordinate system (1-based (y,x))c :: (Int,Int)c = join bimap succ (y + r , 2 * (x + r) + y)grapheme :: Char| None <- a = '∙'| Wall <- a = '#'| Box <- a = 'o'| Robot {} <- a = '@'style :: Draw| None <- a = rgbColor $ sRGB24 0x66 0x66 0x66 -- #666666| Wall <- a = rgbColor $ sRGB24 0x99 0x99 0x99 -- #999999| Box <- a = rgbColor $ sRGB24 0x66 0x66 0x66 -- #666666| Robot {} <- a , "@" <- name a = rgbColor $ sRGB24 0x66 0x66 0x66 -- #666666| Robot {} <- a , "r" <- name a = rgbColor $ sRGB24 0x99 0x66 0x66 -- #996666| Robot {} <- a , "g" <- name a = rgbColor $ sRGB24 0x66 0x99 0x66 -- #669966| Robot {} <- a , "b" <- name a = rgbColor $ sRGB24 0x66 0x66 0x99 -- #666699| Robot {} <- a , "n" <- name a = rgbColor $ sRGB24 0x99 0x99 0x66 -- #999966| Robot {} <- a , "i" <- name a = rgbColor $ sRGB24 0x66 0x99 0x99 -- #669999| otherwise = rgbColor $ sRGB24 0x66 0x66 0x66ui :: Plane-- ui = word "> ui content here <"ui = foldl (&) canvas [(1,1) % vcat (intersperse (cell ' ') [key,info])]wherekey = word (maybe id (:) c $ toList q) # color Black Vivid(c,q) = keys sinfo| Just m <- find me $ nodes v = word $ show m| otherwise = word "no robot found!" # rgbColor (sRGB24 0x99 0x44 0x44)-- utilme :: Unit -> Boolme a| Robot {} <- a , "@" <- name a = True| otherwise = False-- Automatademo :: State -> Map Dir Unit -> Unit -> Unitdemo s ns u| me u = me'| Robot {} <- u = u { ops = drop 1 $ ops u }| otherwise = uwhereme'| Just 'h' <- fst $ keys s = u { ops = [Move H] }| Just 'j' <- fst $ keys s = u { ops = [Move N] }| Just 'k' <- fst $ keys s = u { ops = [Move I] }| Just 'l' <- fst $ keys s = u { ops = [Move L] }| otherwise = u { ops = [] }
module Nove whereimport Nove.Verseimport Nove.Logic ( step )import Data.Map as Map ( Map ){- State should be independent of time and not recorded-- so every recorded frame should be enough to reconstruct the universe without it-- state stores stuff like the currently focused cell-- it does not provide information about the universe-- State can however influence the universe-- as is the case for the random seed or user input-}-- a cell is defined by its behaviournewtype Automata state a = Automata { automata :: Atom a => state -> Map Dir a -> a -> a }instance Semigroup (Automata state a) whereAutomata g <> Automata f = Automata (\s ns -> g s ns . f s ns)instance Monoid (Automata state a) wheremempty = Automata (\_ _ -> id)sim :: Atom a => Automata state a -> state -> Verse a -> Verse asim c = step . automata c
module Nove.Verse whereimport Data.IntMap as IntMap ( IntMap, fromDistinctAscList, (!), adjust, insert )import Data.Tuple ( swap ){- Index coordinates+ + + + z z z x x x y y yz z z x + - - - - - - - - -z z z x x + x x x y y y z z zz z z x x x + -> x x x y y y z z zx x y y y + x x x y y y z z zx y y y + - - - - - - - - -y y y + y y y z z z x x x+ + + + + + + + 6 7 8 0 1 2 3 4 5 0 0 0 0 0 0 0 0 06 7 8 0 + 2 2 2 2 + - - - - - - - - - - - - - - - - - -6 7 8 0 1 + 1 1 1 1 1 + 0 1 2 3 4 5 6 7 8 2 2 2 2 2 2 2 2 26 7 8 0 1 2 + 0 0 0 0 0 0 + 0 1 2 3 4 5 6 7 8 1 1 1 1 1 1 1 1 11 2 3 4 5 + 2 2 2 2 2 + 0 1 2 3 4 5 6 7 8 0 0 0 0 0 0 0 0 02 3 4 5 + 1 1 1 1 + - - - - - - - - - - - - - - - - - -3 4 5 + 0 0 0 + 3 4 5 6 7 8 0 1 2 2 2 2 2 2 2 2 2 2x y x yPerformance should be predictable: time over space -}-- | Verseclass Atom a wherevoid :: a -- empty celmove :: a -> Maybe Dir -- wants to movelock :: a -> Booldata Verse a = Verse{ radius :: Int, nodes :: IntMap a}verse :: Atom a => Int -> Verse averse r = vwherev = Verse{ radius = r, nodes = IntMap.fromDistinctAscList $ zip [0..] (replicate (3 * r * r) void)}-- | NavigationcoordToIndex :: Verse a -> (Int,Int) -> IntcoordToIndex v (x,y) = y * (radius v * 3) + xindexToCoord :: Verse a -> Int -> (Int,Int)indexToCoord v = swap . flip divMod (radius v * 3)distance :: Int -> Int -> Intdistance _ _ = undefineddata Dir = I | L | M | N | H | Uderiving ( Eq, Ord, Enum, Bounded, Show )opposite :: Dir -> Diropposite I = Nopposite L = Hopposite M = Uopposite N = Iopposite H = Lopposite U = Mshift :: Verse a -> Int -> Dir -> Int -> Intshift v n d i| L <- d = coordToIndex v $ f (mod (x + n) (r * 3) , y)| I <- d = coordToIndex v $ f (x , y + n)| U <- d = shift v n I . shift v n H $ i| H <- d = shift v (negate n) L i| N <- d = shift v (negate n) I i| M <- d = shift v (negate n) U iwhere(x,y) = indexToCoord v ir = radius vf (x',y')| y' >= r || y' < 0 = (mod (x' + t * 2 * r) (r * 3) , mod y' r)| otherwise = (x' , y')wheret = div y' r -- outbound multiplier-- | Manipulationnode :: Atom a => Int -> Verse a -> anode i v = nodes v IntMap.! iupd :: Atom a => (a -> a) -> Int -> Verse a -> Verse aupd f i v = v { nodes = IntMap.adjust f i $ nodes v }set :: Atom a => a -> Int -> Verse a -> Verse aset a i v = v { nodes = IntMap.insert i a $ nodes v }
module Nove.Utils whereimport Data.List.NonEmpty-- | Historydata History a = History { past :: NonEmpty a , future :: [a] }-- step backwards in timebefore :: History a -> History abefore (History (m :| p : ps) f) = History (p :| ps) (m:f)before h = h-- step forwards in timeafter :: History a -> History aafter (History p (m:f)) = History (m <| p) fafter h = h-- go to presentultimate :: History a -> History aultimate h@(History _ []) = hultimate h = ultimate $ after h-- get currentmoment :: History a -> amoment (History (m :| _) _) = m-- record momentrecord :: a -> History a -> History arecord m (History p f) = History (m <| p) f
module Nove.Logic whereimport Nove.Verseimport Zero ( total, floyd )import Data.IntMap as IntMap ( IntMap, (!), foldrWithKey, fromDistinctAscList )import Data.Map as Map ( Map, fromList, (!) )import Data.Bifunctor ( bimap )data Memo = Memo{ mdest :: [Int], mfree :: Bool}-- | Step in simulationstep :: forall a. Atom a => (Map Dir a -> a -> a) -> Verse a -> Verse astep f v = v { nodes = nodes' }where-- fold go with future memo(memo',nodes') = bimap intmap intmap $ IntMap.foldrWithKey go mempty $ nodes vwhereintmap :: [v] -> IntMap vintmap = IntMap.fromDistinctAscList . zip [0..]-- simulate atom and apply fgo :: Int -> a -> ([Memo], [a]) -> ([Memo], [a])go i a (ms,as) = (m : ms , f ns a' : as)wherens :: Map Dir ans = fromList [(dir , nodes v IntMap.! shift v 1 dir i) | dir <- total]-- memoization for this nodem :: Memom = Memo{ mdest = dest, mfree = free}-- chain of destinationsdest :: [Int]| Just dir <- move a , d <- shift v 1 dir i = d : mdest (memo' IntMap.! d)| otherwise = []-- want to move heremvin :: [Dir]mvin = filter (\dir -> move (nodes v IntMap.! shift v 1 dir i) == Just (opposite dir)) total-- is this node free?free :: Bool| lock a , null dest = False| length mvin > 1 = False -- stall| Just (_,d) <- floyd $ i : dest = i == d -- loops back to itself , floyd $ cycle [0,1] => Just (2,1) , so (i /= d) which means stall when reciprocal)| otherwise = all (mfree . (memo' IntMap.!)) $ take 1 dest -- dest is free-- new atoma' :: a| free , [dir] <- mvin = ns Map.! dir| d : _ <- dest , mfree (memo' IntMap.! d) = void -- move out| otherwise = a
cabal-version: 3.0name: noveversion: 0.1.0.0author: jrvieiramaintainer: github@jrvieira.comextra-source-files: CHANGELOG.mdcommon optsdefault-language: GHC2021default-extensions: LexicalNegationghc-options: -Wall -O2libraryimport: optshs-source-dirs: srcexposed-modules: Noveexposed-modules: Nove.Verse, Nove.Logic, Nove.Utilsbuild-depends: base, zero, containers, unordered-containers, hashable, colour, ansi-terminal-gametest-suite testimport: optstype: exitcode-stdio-1.0hs-source-dirs: testmain-is: Test.hsbuild-depends: base, noveexecutable demoimport: optsghc-options: -threaded -rtsoptshs-source-dirs: testmain-is: Demo.hsbuild-depends: base, zero, nove, random, containers, ansi-terminal-game
packages: ./nove.cabal-- , ./src/*/*.cabalsource-repository-packagetype: gitlocation: git@gitlab.com:jrvieira1/zero.gitbranch: mainprofiling: Trueprofiling-detail: all-functions
let SessionLoad = 1let s:so_save = &g:so | let s:siso_save = &g:siso | setg so=0 siso=0 | setl so=-1 siso=-1let v:this_session=expand("<sfile>:p")silent onlysilent tabonlycd ~/noveif expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''let s:wipebuf = bufnr('%')endiflet s:shortmess_save = &shortmessif &shortmess =~ 'A'set shortmess=aoOAelseset shortmess=aoOendifbadd +1 src/Nove.hsbadd +44 src/Nove/Logic.hsbadd +74 test/Demo.hsbadd +33 src/Nove/Verse.hsargglobal%argdel$argadd .edit src/Nove/Verse.hslet s:save_splitbelow = &splitbelowlet s:save_splitright = &splitrightset splitbelow splitrightwincmd _ | wincmd |vsplitwincmd _ | wincmd |vsplit2wincmd hwincmd wwincmd wlet &splitbelow = s:save_splitbelowlet &splitright = s:save_splitrightwincmd tlet s:save_winminheight = &winminheightlet s:save_winminwidth = &winminwidthset winminheight=0set winheight=1set winminwidth=0set winwidth=1exe 'vert 1resize ' . ((&columns * 139 + 209) / 419)exe 'vert 2resize ' . ((&columns * 139 + 209) / 419)exe 'vert 3resize ' . ((&columns * 139 + 209) / 419)argglobalbalt src/Nove.hssetlocal foldmethod=indentsetlocal foldexpr=0setlocal foldmarker={{{,}}}setlocal foldignore=#setlocal foldlevel=1setlocal foldminlines=1setlocal foldnestmax=1setlocal foldenablelet s:l = 33 - ((32 * winheight(0) + 52) / 104)if s:l < 1 | let s:l = 1 | endifkeepjumps exe s:lnormal! ztkeepjumps 33normal! 025|wincmd wargglobalif bufexists(fnamemodify("src/Nove/Logic.hs", ":p")) | buffer src/Nove/Logic.hs | else | edit src/Nove/Logic.hs | endifif &buftype ==# 'terminal'silent file src/Nove/Logic.hsendifbalt src/Nove.hssetlocal foldmethod=indentsetlocal foldexpr=0setlocal foldmarker={{{,}}}setlocal foldignore=#setlocal foldlevel=1setlocal foldminlines=1setlocal foldnestmax=1setlocal foldenablelet s:l = 44 - ((43 * winheight(0) + 52) / 104)if s:l < 1 | let s:l = 1 | endifkeepjumps exe s:lnormal! ztkeepjumps 44normal! 010|wincmd wargglobalif bufexists(fnamemodify("test/Demo.hs", ":p")) | buffer test/Demo.hs | else | edit test/Demo.hs | endifif &buftype ==# 'terminal'silent file test/Demo.hsendifbalt src/Nove/Logic.hssetlocal foldmethod=indentsetlocal foldexpr=0setlocal foldmarker={{{,}}}setlocal foldignore=#setlocal foldlevel=1setlocal foldminlines=1setlocal foldnestmax=1setlocal foldenablelet s:l = 74 - ((60 * winheight(0) + 52) / 104)if s:l < 1 | let s:l = 1 | endifkeepjumps exe s:lnormal! ztkeepjumps 74normal! 0wincmd w2wincmd wexe 'vert 1resize ' . ((&columns * 139 + 209) / 419)exe 'vert 2resize ' . ((&columns * 139 + 209) / 419)exe 'vert 3resize ' . ((&columns * 139 + 209) / 419)tabnext 1if exists('s:wipebuf') && len(win_findbuf(s:wipebuf)) == 0 && getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'silent exe 'bwipe ' . s:wipebufendifunlet! s:wipebufset winheight=1 winwidth=20let &shortmess = s:shortmess_savelet &winminheight = s:save_winminheightlet &winminwidth = s:save_winminwidthlet s:sx = expand("<sfile>:p:r")."x.vim"if filereadable(s:sx)exe "source " . fnameescape(s:sx)endiflet &g:so = s:so_save | let &g:siso = s:siso_saveset hlsearchnohlsearchdoautoall SessionLoadPostunlet SessionLoad" vim: set ft=vim :
# nove## sim engine for hex grid automata
_darcs.git*.DS_Store