|
From: <kr_...@us...> - 2003-07-13 10:32:19
|
Update of /cvsroot/htoolkit/gio/src/examples/life
In directory sc8-pr-cvs1:/tmp/cvs-serv5220
Added Files:
Life.hs Main.hs
Log Message:
Add Life example
--- NEW FILE: Life.hs ---
module Life where
import Graphics.UI.GIO
import Control.Monad(when)
type Generation = [[LifeCell]]
type CellSize = Int
type ClickPoint = Point
data LifeCell
= LifeCell
{ cx :: !Int
, cy :: !Int
, age :: !Int
} deriving Show
colours :: [Color]
colours = [red,magenta,green,yellow,cyan,blue]
ageToColour :: Int -> Color
ageToColour age
| age<=0 = colours !! 0
| age>=5 = colours !! 5
| otherwise = colours !! age
makeGeneration :: Generation
makeGeneration = []
makeLifeCell :: ClickPoint -> CellSize -> LifeCell
makeLifeCell (Point x y) size = LifeCell{cx=x `div` size, cy=y `div` size, age=0}
newLifeCell :: Int -> Int -> LifeCell
newLifeCell x y = LifeCell{cx=x,cy=y,age=0}
-- Rendering of LifeCells.
drawCells :: (LifeCell -> Canvas -> IO ()) -> Generation -> Canvas -> IO ()
drawCells f gen can = mapM_ (mapM_ (flip f can)) gen
drawCell :: CellSize -> LifeCell -> Canvas -> IO ()
drawCell size (LifeCell {cx=cx,cy=cy,age=age}) can = do
setCanvasPen can [color =: ageToColour age]
fillRect (Rect px py (px+size) (py+size)) can
where
px = cx*size
py = cy*size
eraseCell :: CellSize -> LifeCell -> Canvas -> IO ()
eraseCell size (LifeCell {cx=cx,cy=cy}) can = do
setCanvasPen can [color =: black]
fillRect (Rect px py (px+size) (py+size)) can
where
px = cx*size
py = cy*size
{- Insert a LifeCell to a Generation.
In a Generation LifeCells are ordered by increasing x-coordinate first, and by increasing y-coordinate second.
-}
insertCell :: LifeCell -> Generation -> Generation
insertCell c1@(LifeCell{cx=x1}) gen@(cs@(LifeCell{cx=x2,cy=y2}:x2ys) : cs_xs)
| x2<x1 = cs : insertCell c1 cs_xs
| x2==x1 = insertCelly c1 cs : cs_xs
| otherwise = [c1]:gen
where
insertCelly :: LifeCell -> [LifeCell] -> [LifeCell]
insertCelly c1@(LifeCell {cy=y1}) g@(c2@(LifeCell {cx=x2,cy=y2}):x2ys)
| y2<y1 = c2 : insertCelly c1 x2ys
| y2==y1 = c1 : x2ys
| otherwise = c1 : g
insertCelly c1 _ = [c1]
insertCell c1 [] = [[c1]]
{- Remove a LifeCell from a Generation.
-}
removeCell :: LifeCell -> Generation -> Generation
removeCell c1@(LifeCell {cx=x1,cy=y1}) gen@(cs@((LifeCell {cx=x2,cy=y2}):x2ys):cs_xs)
| x2<x1 = cs:removeCell c1 cs_xs
| x2>x1 = gen
| otherwise =
let cs1 = removeCelly c1 cs
in if null cs1 then cs_xs
else cs1 : cs_xs
where
removeCelly :: LifeCell -> [LifeCell] -> [LifeCell]
removeCelly c1@(LifeCell {cy=y1}) cs@(c2@(LifeCell {cx=x2,cy=y2}):x2ys)
| y2<y1 = c2 : removeCelly c1 x2ys
| y2==y1 = x2ys
| otherwise = cs
removeCelly _ _ = []
removeCell c ([]:cs_xs) = removeCell c cs_xs
removeCell c _ = []
{- Calculate the new Generation (first tuple result) and the Generation of LifeCells that die (second tuple result).
-}
lifeGame :: Generation -> (Generation,Generation)
lifeGame gen =
let
(next,_,die) = nextGen gen gen
next1 = celebrateSurvival next gen
in
(next1,die)
where
nextGen :: Generation -> Generation -> (Generation,Generation,Generation)
nextGen ((c@(LifeCell{cx=cx,cy=cy}):cs_x):cs_xs) gen
| neighbours34 (neighbours c gen) = (insertCell c gennext1,new,diednext)
| otherwise = (gennext1,new,insertCell c diednext)
where
(gennext,newbornsnext,diednext) = nextGen (cs_x:cs_xs) gen1
(gennext1,new) = newBorns c newbornsnext gennext gen
gen1 = shiftGeneration (cs_x:cs_xs) gen
neighbours34 [_,_,_] = True
neighbours34 [_,_,_,_] = True
neighbours34 _ = False
newBorns :: LifeCell -> Generation -> Generation -> Generation -> (Generation,Generation)
newBorns c newbornsnext gennext gen =
newBorns1 (newBornNeighbours c gen) newbornsnext gennext gen
where
newBorns1 (c@(LifeCell {cx=x1,cy=y1}):cs) newbornsnext gennext gen
| neighbours3 (neighbours c gen) = (insertCell c gennext1,insertCell c newbornsnext1)
| otherwise = next_genANDnewborns
where
(gennext1,newbornsnext1) = next_genANDnewborns
next_genANDnewborns = newBorns1 cs newbornsnext gennext gen
neighbours3 :: [LifeCell] -> Bool
neighbours3 [_,_,_] = True
neighbours3 _ = False
newBorns1 [] newbornsnext gennext _ = (gennext,newbornsnext)
-- newBornNeighbours c gen -> dead neighbours of c in gen in decreasing order.
newBornNeighbours :: LifeCell -> Generation -> [LifeCell]
newBornNeighbours (LifeCell {cx=cx,cy=cy}) gen =
newBornNeighbours1 (cx-1) (cx+1) (cy-1) gen []
where
newBornNeighbours1:: Int -> Int -> Int -> Generation -> [LifeCell] -> [LifeCell]
newBornNeighbours1 x xn y (cs@((LifeCell {cx=x2}):_):cs_xs) newborns
| x>xn = newborns
| x2<x = newBornNeighbours1 x xn y cs_xs newborns
| x2==x = newBornNeighbours2 x y (y+2) cs (newBornNeighbours1 (x+1) xn y cs_xs newborns)
| otherwise = (newLifeCell x y:newLifeCell x (y+1):newLifeCell x (y+2):newBornNeighbours1 (x+1) xn y cs_xs newborns)
newBornNeighbours1 x xn y [] newborns
| x>xn = newborns
| otherwise = (newLifeCell x y:newLifeCell x (y+1):newLifeCell x (y+2):newBornNeighbours1 (x+1) xn y [] newborns)
newBornNeighbours2:: Int -> Int -> Int -> [LifeCell] -> [LifeCell] -> [LifeCell]
newBornNeighbours2 x y yn (c@(LifeCell {cx=x2,cy=y2}):cs) cs_xs
| y>yn = cs_xs
| y2<y = newBornNeighbours2 x y yn cs cs_xs
| y2==y = newBornNeighbours2 x (y+1) yn cs cs_xs
| otherwise = (newLifeCell x y:newBornNeighbours2 x (y+1) yn cs cs_xs)
newBornNeighbours2 x y yn [] cs_xs
| y>yn = cs_xs
| otherwise = newLifeCell x y:newBornNeighbours2 x (y+1) yn [] cs_xs
shiftGeneration :: Generation -> Generation -> Generation
shiftGeneration ( (c@(LifeCell {cx=cx,cy=cy}):_):_) gen = shiftGeneration1 c{cx=cx-2,cy=cy-2} gen
shiftGeneration ([]:(c@(LifeCell {cx=cx,cy=cy}):_):_) gen = shiftGeneration1 c{cx=cx-2,cy=cy-2} gen
shiftGeneration partial_gen gen = gen
shiftGeneration1:: LifeCell -> Generation -> Generation
shiftGeneration1 c@(LifeCell {cx=x1,cy=y1}) gen@((c2@(LifeCell {cx=x2,cy=y2}):cs_x):cs_xs)
| x2<x1 = shiftGeneration1 c cs_xs
| x2==x1 && y2<y1 = shiftGeneration1 c (cs_x:cs_xs)
| otherwise = gen
shiftGeneration1 c ([]:cs_xs) = shiftGeneration1 c cs_xs
shiftGeneration1 c _ = []
-- neighbours c gen -> neighbours of c in gen in decreasing order.
neighbours :: LifeCell -> Generation -> [LifeCell]
neighbours (LifeCell {cx=x,cy=y}) gen =
neighbours1 (x-1) (x+1) (y-1) gen []
where
neighbours1:: Int -> Int -> Int -> Generation -> [LifeCell] -> [LifeCell]
neighbours1 x xn y (cs@((LifeCell {cx=x2,cy=y2}):_):cs_xs) neighbours
| x2<x = neighbours1 x xn y cs_xs neighbours
| x2<=xn = neighbours2 y (y+2) cs (neighbours1 (x+1) xn y cs_xs neighbours)
| otherwise = neighbours
neighbours1 _ _ _ [] neighbours = neighbours
neighbours2:: Int -> Int -> [LifeCell] -> [LifeCell] -> [LifeCell]
neighbours2 y yn (c@(LifeCell {cx=x2,cy=y2}):cs) cs_xs
| y2<y = neighbours2 y yn cs cs_xs
| y2<=yn = (c:neighbours2 (y+1) yn cs cs_xs)
| otherwise = cs_xs
neighbours2 _ _ [] cs_xs = cs_xs
nextGen ([]:cs_xs) gen = nextGen cs_xs gen
nextGen _ _ = ([],[],[])
celebrateSurvival :: Generation -> Generation -> Generation
celebrateSurvival new old = map (map (celebrate old)) new
where
celebrate :: Generation -> LifeCell -> LifeCell
celebrate old newcell
| found = newcell{age=age+1}
| otherwise = newcell{age=age}
where
(found,age) = getCellAge newcell old
getCellAge :: LifeCell -> Generation -> (Bool,Int)
getCellAge c1@(LifeCell {cx=x1}) (xs@((LifeCell {cx=x2}):_):xss)
| x1<x2 = (False,0)
| x1>x2 = getCellAge c1 xss
| otherwise = getCellAge' c1 xs
getCellAge _ _ = (False,0)
getCellAge' :: LifeCell -> [LifeCell] -> (Bool,Int)
getCellAge' c1@(LifeCell {cy=y1}) (LifeCell{cy=y2,age=age}:xs)
| y1<y2 = (False,0)
| y1>y2 = getCellAge' c1 xs
| otherwise = (True,age)
getCellAge' _ _ = (False,0)
--- NEW FILE: Main.hs ---
module Main where
import Graphics.UI.GIO
import Life
import Data.Char(ord,chr)
import Data.IORef
data Life = Life
{ gen :: Generation
, cellSize :: CellSize
} deriving Show
initialLife = Life
{ gen = makeGeneration
, cellSize = startCellSize
}
main :: IO ()
main = start "Life" "1.0" SDI [] $ do
ref <- newIORef initialLife
let mydomain@(Size dw dh) = getViewDomain startCellSize
myorigin = Point (dw `div` 2) (dh `div` 2)
w <- window [domain =: mydomain, origin =: myorigin, bgcolor =: black]
set w [on mouse =: onMouse w ref, on paint =: onPaint ref]
set w [origin =: myorigin]
mfile <- menu [title =: "File"] mainMenu
menuitem [title =: "About LifeGameExample...", on command =: onAbout] mfile
menuline mfile
menuitem [title =: "Exit", on command =: halt] mfile
mopts <- menu [title =: "Options"] mainMenu
msizes <- menu [title =: "Cell Size"] mopts
rg <- menuRadioGroup [] msizes
sequence [menuRadioItem [ title =: mkTitle size
, on command =: onNewSize size w ref] rg
| size <- [1..8]]
set rg [selected =: 7]
mcmd <- menu [title =: "Commands"] mainMenu
merase <- menuitem [title =: "Erase Cells", accel =: KeyChar '\^E'] mcmd
mplay <- menuitem [title =: "Play", accel =: KeyChar '\^P'] mcmd
mhalt <- menuitem [title =: "Halt", accel =: KeyChar '\^H', enabled =: False] mcmd
mstep <- menuitem [title =: "Step", accel =: KeyChar '\^S'] mcmd
t <- timer [enabled =: False, interval =: 40, on command =: onTimer w ref]
set merase [on command =: onErase w ref]
set mplay [on command =: onPlay mplay mstep mhalt t]
set mhalt [on command =: onHalt mplay mstep mhalt t]
set mstep [on command =: onTimer w ref]
where
mkTitle size = show size ++ " * " ++ show size
onTimer w ref = do
life <- readIORef ref
let size = cellSize life
let (next,died) = lifeGame (gen life)
let render can = drawCells (drawCell size) next can >> drawCells (eraseCell size) died can
writeIORef ref (life{gen=next})
drawInWindow UnBuffered w render
onAbout = do
logo <- readBitmap "../images/logo.bmp" []
runAboutDialog "Life" "1.0"
"(C) Krasimir Angelov, 2003"
"The Life is an example program\nfreely distributed with HToolkit"
[] [] [] logo Nothing
onMouse w ref (MouseLeftDown pos mods)
| mods == justControl = do
life <- readIORef ref
let cell = makeLifeCell pos (cellSize life)
writeIORef ref (life{gen=removeCell cell (gen life)})
drawInWindow UnBuffered w (eraseCell (cellSize life) cell)
| mods == noModifiers = do
life <- readIORef ref
let cell = makeLifeCell pos (cellSize life)
writeIORef ref (life{gen=insertCell cell (gen life)})
drawInWindow UnBuffered w (drawCell (cellSize life) cell)
onMouse w ref (MouseDrag pos mods) = do
life <- readIORef ref
let cell = makeLifeCell pos (cellSize life)
writeIORef ref (life{gen=insertCell cell (gen life)})
drawInWindow UnBuffered w (drawCell (cellSize life) cell)
onMouse w ref _ = return ()
onPaint ref can _ _ = do
life <- readIORef ref
drawCells (drawCell (cellSize life)) (gen life) can
-- onErase sets the current generation to empty and clears the window.
onErase :: Window -> IORef Life -> IO ()
onErase w ref = do
life <- readIORef ref
writeIORef ref (life{gen=makeGeneration})
repaint w
-- play starts the computation of successive generations given the current set of life cells.
onPlay :: MenuItem -> MenuItem -> MenuItem -> Timer -> IO ()
onPlay mplay mstep mhalt timer = do
set mplay [enabled =: False]
set mstep [enabled =: False]
set mhalt [enabled =: True ]
set timer [enabled =: True ]
-- onHalt stops the computation of successive generations, but does not change the current generation.
onHalt :: MenuItem -> MenuItem -> MenuItem -> Timer -> IO ()
onHalt mplay mstep mhalt timer = do
set mplay [enabled =: True]
set mstep [enabled =: True]
set mhalt [enabled =: False]
set timer [enabled =: False]
-- onNewSize changes the size in which life cells are rendered and redraws the window.
onNewSize :: Int -> Window -> IORef Life -> IO ()
onNewSize newSize w ref = do
life <- readIORef ref
oldOrigin <- get w origin
let oldSize = cellSize life
newOrigin = Point ((px oldOrigin) `div` oldSize*newSize) ((py oldOrigin) `div` oldSize*newSize)
set w [domain =: getViewDomain newSize, origin =: newOrigin]
writeIORef ref (life{cellSize=newSize})
-- Given the size in which to render life cells, getViewDomain calculates the corresponding ViewDomain:
getViewDomain :: CellSize -> Size
getViewDomain size =
let Size w h = universe
in Size (w*size) (h*size)
-- Program constants.
universe = Size 2000 2000
startCellSize = 8
|