module LifeGameExample // ************************************************************************************************** // // This is the LifeGame program. // // The program has been written in Clean 2.0 and uses the Clean Standard Object I/O library 1.2.2 // // ************************************************************************************************** import StdEnv, StdIO import Life, Help :: Life = { gen :: !Generation , size:: !CellSize } initialLife = { gen = makeGeneration , size= StartCellSize } Start :: *World -> *World Start world = startLife (openIds 6 world) startLife :: ([Id],*World) -> *World startLife ([eraseID,playID,haltID,stepID,windowID,timerID],world) = startIO SDI initialLife initialise [ProcessClose closeProcess] world where // initialise creates the gui of the life. initialise pst # (error,pst) = openTimer undef timer pst | error<>NoError = abort "LifeGameExample could not open timer." # (error,pst) = openMenu undef file pst | error<>NoError = abort "LifeGameExample could not open Life menu." # (error,pst) = openMenu undef options pst | error<>NoError = abort "LifeGameExample could not open Options menu." # (error,pst) = openMenu undef commands pst | error<>NoError = abort "LifeGameExample could not open Commands menu." # (size, pst) = accPIO getProcessWindowSize pst # (error,pst) = openWindow undef (window size) pst | error<>NoError = abort "LifeGameExample could not open Life window." | otherwise = pst // window defines the window that displays the universe and its inhabitants. window size = Window "Life" NilLS [ WindowId windowID , WindowClose (noLS closeProcess) , WindowMouse onlyMouseDown Able (noLS1 track) , WindowViewDomain (getViewDomain StartCellSize) , WindowViewSize size , WindowOrigin zero , WindowHScroll (stdScrollFunction Horizontal StartCellSize) , WindowVScroll (stdScrollFunction Vertical StartCellSize) , WindowLook True (look initialLife) , WindowPen [PenBack Black] ] // timer defines the timer that calculates subsequent life generations. timer = Timer 0 NilLS [ TimerId timerID , TimerSelectState Unable , TimerFunction (noLS1 (\_->step)) ] // file defines the "File" menu, containing only the quit command to terminate the program. file = Menu "&File" ( MenuItem "&About LifeGameExample..." [MenuFunction (noLS (showAbout "Life" "LifeHelp"))] :+: MenuSeparator [] :+: MenuItem "&Quit" [MenuShortKey 'q',MenuFunction (noLS closeProcess)] ) [] // options defines the "Options" menu to set the size of the displayed cells. options = Menu "&Options" ( SubMenu "Cell Size" ( RadioMenu [ (title (2^i),Nothing,Just (char i),noLS (newsize (2^i))) \\ i<-[0..4] ] 4 [] ) [] ) [] where title size = toString size +++ " * " +++ toString size char i = toChar (fromChar '1'+i) // commands defines the "Commands" menu to run and halt the computations of life generations. commands= Menu "&Commands" ( MenuItem "&Erase Cells" [MenuId eraseID,MenuShortKey 'e',MenuFunction (noLS erase)] :+: MenuItem "&Play" [MenuId playID, MenuShortKey 'p',MenuFunction (noLS play)] :+: MenuItem "&Halt" [MenuId haltID, MenuShortKey 'h',MenuFunction (noLS halt), MenuSelectState Unable] :+: MenuItem "&Step" [MenuId stepID, MenuShortKey 's',MenuFunction (noLS step)] ) [] // play starts the computation of successive generations given the current set of life cells. play :: (PSt Life) -> PSt Life play life = appListPIO [ disableWindowMouse windowID , disableMenuElements [eraseID,playID,stepID] , enableMenuElements [haltID] , enableTimer timerID ] life // halt stops the computation of successive generations, but does not change the current generation. halt :: (PSt Life) -> PSt Life halt life = appListPIO [ enableWindowMouse windowID , disableMenuElements [haltID] , enableMenuElements [eraseID,playID,stepID] , disableTimer timerID ] life // step calculates the next generation and displays it. step :: (PSt Life) -> PSt Life step life=:{ls=state=:{gen,size},io} # state = {state & gen=next} # io = appWindowPicture windowID render io # io = setWindowLook windowID False (True,look state) io = {life & ls=state, io=io} where (next,died) = lifeGame gen render = drawCells (drawCell size) next o (drawCells (eraseCell size) died) // erase sets the current generation to empty and clears the window. erase :: (PSt Life) -> PSt Life erase life=:{ls=state,io} # state = {state & gen=makeGeneration} # io = setWindowLook windowID True (True,look state) io = {life & ls=state, io=io} // newsize changes the size in which life cells are rendered and redraws the window. newsize :: Int (PSt Life) -> PSt Life newsize newSize life=:{ls=state=:{size=oldSize},io} # state = {state & size=newSize} # (viewframe,io)= getWindowViewFrame windowID io oldOrigin = viewframe.corner1 newOrigin = {x=oldOrigin.x/oldSize*newSize,y=oldOrigin.y/oldSize*newSize} # io = setWindowLook windowID False (True,look {state & gen=makeGeneration}) io # io = setWindowViewDomain windowID (getViewDomain newSize) io # io = moveWindowViewFrame windowID (toVector newOrigin-toVector oldOrigin) io # io = setWindowLook windowID True (True,look state) io = {life & ls=state, io=io} // The window look: look :: Life SelectState UpdateState *Picture -> *Picture look {gen,size} _ {newFrame} picture # picture = unfill newFrame picture # picture = drawCells (drawCell size) gen picture = picture // The window mouse accepts only MouseDown user actions: onlyMouseDown :: MouseState -> Bool onlyMouseDown (MouseDown _ _ _) = True onlyMouseDown (MouseDrag _ _) = True onlyMouseDown _ = False // The window mouse action places and removes alive cells: track :: MouseState (PSt Life) -> PSt Life track mouse life=:{ls=state=:{gen,size},io} | modifiers.commandDown # state = {state & gen=removeCell cell gen} # io = appWindowPicture windowID (eraseCell size cell) io # io = setWindowLook windowID False (True,look state) io = {life & ls=state, io=io} | otherwise # state = {state & gen=insertCell cell gen} # io = appWindowPicture windowID (drawCell size cell) io # io = setWindowLook windowID False (True,look state) io = {life & ls=state, io=io} where (pos,modifiers) = case mouse of (MouseDown pos mods _) -> (pos,mods) (MouseDrag pos mods) -> (pos,mods) cell = makeLifeCell pos size // Given the size in which to render life cells, getViewDomain calculates the corresponding ViewDomain: getViewDomain :: CellSize -> ViewDomain getViewDomain size = {corner1={x=size*left,y=size*top},corner2={x=size*right,y=size*bottom}} where {corner1={x=left,y=top},corner2={x=right,y=bottom}} = Universe // Program constants. Universe :== {corner1={x=(-1000),y=(-1000)},corner2={x=1000,y=1000}} StartCellSize :== 8