brillo-examples-1.13.3: picture/Conway/World.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ParallelListComp #-}
{-# LANGUAGE PatternGuards #-}
module World where
import Brillo.Interface.Pure.Simulate
import Cell
import Control.Monad
import Data.Vector qualified as Vec
import System.Random
type Vec = Vec.Vector
-- Index ----------------------------------------------------------------------
-- | An index into the vector holding all the cells.
type Index = Int
-- | The x y coordinate of a cell.
type Coord = (Int, Int)
indexOfCoord :: World -> Coord -> Index
indexOfCoord world (x, y) =
x + y * (worldWidth world)
coordOfIndex :: World -> Index -> Coord
coordOfIndex world i =
( i `mod` worldWidth world
, i `div` worldWidth world
)
-- World ----------------------------------------------------------------------
data World
= World
{ worldCells :: Vec Cell
, worldWidth :: Int
, worldHeight :: Int
, worldCellSize :: Int
-- ^ Width and height of each cell.
, worldCellSpace :: Int
-- ^ Number of pixels to leave between each cell.
, worldCellOldAge :: Int
-- ^ Cells less than this age are drawn with the color ramp
, worldSimulationPeriod :: Float
-- ^ Seconds to wait between each simulation step.
, worldElapsedTime :: Float
-- ^ Time that has elapsed since we drew the last step
}
-- | Make a new world of a particular size.
randomWorld :: (Int, Int) -> IO World
randomWorld (width, height) =
do
bools <- replicateM (width * height) randomIO
return $
World
{ worldCells = Vec.fromList $ map cellOfBool bools
, worldWidth = width
, worldHeight = height
, worldCellSize = 5
, worldCellSpace = 1
, worldCellOldAge = 20
, worldSimulationPeriod = 0.1
, worldElapsedTime = 0
}
-- | Convert a bool to a live or dead cell.
cellOfBool :: Bool -> Cell
cellOfBool b =
case b of
True -> CellAlive 0
False -> CellDead
-- | Get the cell at a particular coordinate in the world.
getCell :: World -> Coord -> Cell
getCell world coord@(x, y)
| x < 0 || x >= worldWidth world = CellDead
| y < 0 || y >= worldHeight world = CellDead
| otherwise =
worldCells world Vec.! indexOfCoord world coord
-- | Get the neighbourhood of cells around this coordinate.
getNeighbourhood :: World -> Coord -> [Cell]
getNeighbourhood world (ix, iy) =
let indexes =
[ (x, y)
| x <- [ix - 1 .. ix + 1]
, y <- [iy - 1 .. iy + 1]
, not (x == ix && y == iy)
]
in map (getCell world) indexes
-- | Compute the next cell state depending on its neighbours.
stepCell :: Cell -> [Cell] -> Cell
stepCell cell neighbours =
let live = length (filter isAlive neighbours)
in case cell of
CellAlive age -> if elem live [2, 3] then CellAlive (age + 1) else CellDead
CellDead -> if live == 3 then CellAlive 0 else CellDead
-- | Compute the next state of the cell at this index in the world.
stepIndex :: World -> Int -> Cell -> Cell
stepIndex world index cell =
let coord = coordOfIndex world index
neigh = getNeighbourhood world coord
in stepCell cell neigh
-- | Compute the next world state.
stepWorld :: World -> World
stepWorld world =
world{worldCells = Vec.imap (stepIndex world) (worldCells world)}
-- | Simulation function for worlds.
simulateWorld :: ViewPort -> Float -> World -> World
simulateWorld _ time world
-- If enough time has passed then it's time to step the world.
| worldElapsedTime world >= (worldSimulationPeriod world) =
let world' = stepWorld world
in world'{worldElapsedTime = 0}
-- Wait some more.
| otherwise =
world{worldElapsedTime = worldElapsedTime world + time}