cellularAutomata/src/Main.hs

188 lines
5.0 KiB
Haskell
Raw Normal View History

2021-04-19 22:25:18 -04:00
{-# LANGUAGE OverloadedStrings #-}
2021-04-17 21:29:09 -04:00
module Main where
import Control.Monad
import System.Process
import System.Random
import System.Console.GetOpt
import System.Environment(getArgs, getProgName)
import Data.Maybe (fromMaybe)
2021-04-19 22:25:18 -04:00
import Comonad
2021-04-20 03:44:30 -04:00
import Spaces.Space2
import Spaces.Space1
2021-04-19 22:25:18 -04:00
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
2021-04-17 21:29:09 -04:00
2021-04-19 22:25:18 -04:00
-----------------
-- brick stuff --
-----------------
2021-04-17 21:29:09 -04:00
2021-04-19 22:25:18 -04:00
data Tick = Tick
type Name = ()
2021-04-19 04:17:34 -04:00
2021-04-19 22:25:18 -04:00
-- App definition
2021-04-17 21:29:09 -04:00
2021-04-19 22:25:18 -04:00
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
}
2021-04-17 21:29:09 -04:00
2021-04-19 22:25:18 -04:00
-- Handling events
2021-04-17 21:29:09 -04:00
2021-04-19 22:25:18 -04:00
theMap :: AttrMap
theMap = attrMap V.defAttr
[ (rockAttr, V.red `on` V.blue)
, (scissorsAttr, V.green `on` V.red)
, (paperAttr, V.blue `on` V.green)
]
2021-04-17 21:29:09 -04:00
---------------
-- 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
2021-04-19 04:17:34 -04:00
, optHeight :: Int
2021-04-17 21:29:09 -04:00
} 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
2021-04-19 04:17:34 -04:00
, optGenerations = 40
, optHeight = h
2021-04-17 21:29:09 -04:00
}
-- 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"
2021-04-19 04:17:34 -04:00
, Option ['h'] ["height"]
(ReqArg (\t opts -> opts { optHeight = (read t) }) "HEIGHT")
"term height"
2021-04-17 21:29:09 -04:00
]
-- 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
2021-04-19 22:25:18 -04:00
initGame :: IO (Space2 CellState)
initGame = do
rng <- getStdGen
return $ createRandSpace2 rng
2021-04-17 21:29:09 -04:00
---------------
-- main loop --
---------------
main :: IO ()
main = do
options <- parseArgs
let w = (optWidth options)
2021-04-19 04:17:34 -04:00
let h = (optHeight options)
2021-04-19 22:25:18 -04:00
chan <- newBChan 1
forkIO $ forever $ do
writeBChan chan Tick
threadDelay 100000
g <- initGame
let buildVty = V.mkVty V.defaultConfig
initialVty <- buildVty
2021-04-20 03:44:30 -04:00
void $ customMain initialVty buildVty (Just chan) (app h w) (clamp2 w h g)
2021-04-19 22:25:18 -04:00
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
2021-04-19 04:17:34 -04:00
where
2021-04-20 03:44:30 -04:00
bw = mat2 g
2021-04-19 22:25:18 -04:00
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"
2021-04-20 03:44:30 -04:00
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