| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- 'An implementation of Conway's Game of Life
- 'http://en.wikipedia.org/wiki/Conway's_Game_of_Life
- 'To try different patterns, pause the 'game', click cells,
- 'then unpause.
- 'For best performance run in "Release" mode.
- #Import "<std.monkey2>"
- #Import "<mojo.monkey2>"
- Using std..
- Using mojo..
- Function Main()
- New AppInstance
-
- New LifeApp(1280, 720)
-
- App.Run()
- End
- '------------------------------
- Class LifeApp Extends Window
- Field iterations:UInt 'number of iterations
- Field lastMS:ULong 'time of last iteration
-
- Field speed:UInt 'iterations per second
- Field speedMin:UInt = 1
- Field speedMax:UInt = 20
-
- Field paused:Bool
- Field suspended:Bool
- Field grid:CellGrid
- Field gridRenderer:GridRenderer
- Field gridArea:Recti 'canvas area to draw the grid image onto
- Field initialized:Bool = False 'false if grid and gridRenderer objects not yet created
- Field fontHeight:UInt = 30 'should be able to get this value from canvas.Font.Height but can't yet?
- Field cellWidthPixels:UInt = 16
- Field cellHeightPixels:UInt = 16
- Method New(width:Int, height:Int)
- Super.New("Conway's Game of Life", width, height)
-
- ClearColor = Color.Black
- 'Disable vsync if possible.
- SwapInterval = 0
- End
-
- 'Create and initialize a cell grid and an associated Grid rendering object.
- Method Start:Void()
-
- paused = True
-
- iterations = 0
- speed = speedMax / 2
-
- 'Grid's top left pixel coordinate
- Local gridTL := New Vec2i(10, fontHeight + 10)
-
- 'Grid's bottom right pixel coordinate
- Local gridBR := New Vec2i(Width - 10, Height - fontHeight - 10)
-
- 'Define a target area on which to draw the grid, using the above vectors
- gridArea = New Recti(gridTL, gridBR)
-
- 'Calculate grid size based on the draw area's dimensions
- 'and desired cell size in pixels.
- Local gridCols := Floor(gridArea.Width / cellWidthPixels)
- Local gridRows := Floor(gridArea.Height / cellHeightPixels)
-
- 'Create the grid of cells.
- grid = New CellGrid(gridCols, gridRows)
-
- 'Create the grid renderer, passing in a reference to the grid and the cell pixel dimensions.
- gridRenderer = New GridRenderer(grid, cellWidthPixels, cellHeightPixels)
- SeedRnd(Microsecs())
-
- paused = False
-
- lastMS = Millisecs()
-
- 'Call the garbage collector. Maybe it can free some memory (or maybe not)
- 'if this Start method was called during a WindowResize event.
- GCCollect()
-
- End
-
- Method OnWindowEvent(event:WindowEvent) Override
-
- If event.Type = EventType.WindowResized
- 'Make a new grid and associated grid renderer based
- 'on the window's new dimensions.
- Start()
-
- App.RequestRender()
-
- Elseif event.Type = EventType.WindowClose
-
- App.Terminate()
-
- Elseif event.Type=EventType.WindowGainedFocus
-
- suspended=False
-
- App.RequestRender() 'kick start rendering...
-
- Elseif event.Type=EventType.WindowLostFocus
-
- suspended=True
-
- Endif
-
- End
-
- Method OnKeyEvent( event:KeyEvent ) Override
-
- If event.Type = EventType.KeyUp
-
- Select event.Key
-
- Case Key.Escape
- App.Terminate()
-
- Case Key.Space
- paused = Not paused
-
- Case Key.Up
- SpeedUp()
-
- Case Key.Down
- SpeedDown()
-
- Case Key.Tab
- gridRenderer.ToggleGridOverlay()
-
- Case Key.Enter
- 'Reset, but retain the current speed.
- grid.Init()
- iterations = 0
-
- Case Key.C
- grid.Clear()
- End
-
- Endif
-
- End
-
- Method OnMouseEvent( event:MouseEvent ) Override
-
- Select event.Type
- Case EventType.MouseDown
- If event.Button = 1
- 'if the clicked coordinate matches a cell then toggle it alive/dead.
- Local cell:Cell = gridRenderer.GetCell(event.Location.X - gridArea.TopLeft.X, event.Location.Y - gridArea.TopLeft.Y)
- If cell Then cell.ToggleDead()
- Endif
-
- End
-
- End
-
- Method OnRender(canvas:Canvas) Override
-
- If Not initialized
- 'This small If block only runs once at the start of the program.
- '(Mojo must finish "constructing" before the Window's Width and Height properties
- 'return correct results - used in the Start method).
- Start()
- initialized = True
- Endif
-
- If Not suspended App.RequestRender()
- canvas.Color = Color.White
- gridRenderer.Render()
- Local statusLine := "Iterations: " + iterations + " FPS: " + Fps() + " Speed: " + speed + " "
- canvas.DrawText(statusLine, 10, 10)
- canvas.DrawText("[Space] - pause, [Enter] - new, [Up][Down] - speed, [Tab] - toggle grid, [c] - clear, [Left-Click] - Toggle cell alive/dead" , 10, Height - fontHeight)
- canvas.DrawImage(gridRenderer.Image, gridArea.TopLeft.X, gridArea.TopLeft.Y)
- If paused
- canvas.Color = Color.Red
- canvas.DrawText("PAUSED - Press [space] to resume", 10 + canvas.Font.TextWidth(statusLine), 10)
- endif
- '---
-
- 'Update the grid at the current speed (iterations per second).
-
- If Not paused
-
- If Millisecs() >= (lastMS + (1000 / speed))
- grid.Iterate()
- iterations += 1
- lastMS = Millisecs()
- Endif
- Endif
- '---
- End
-
- Method SpeedUp:Void()
- If speed >= speedMax Then Return
- speed += 1
- End
-
- Method SpeedDown:Void()
- If speed <= speedMin Then Return
- speed -= 1
- End
-
- End
- '----------------------------------
- 'This class renders a cell grid to an image. It can also return
- 'a cell reference based on a given pixel coordinate.
- Class GridRenderer
- Field _grid:CellGrid
-
- 'Dimensions of the rectangle to render per cell.
- Field _rw:UInt, _rh:UInt
- 'Image on which to render the grid. (And an associated canvas).
- Field _image:Image
- Field _iCanvas:Canvas
-
- Field _showGridOverlay:Bool
-
- Method New(grid:CellGrid, rw:UInt, rh:UInt)
- _grid = grid
- _rw = rw
- _rh = rh
- _image = New Image(_grid.Cols * _rw + 1, _grid.Rows * _rh + 1)
- _iCanvas = New Canvas(_image)
- _showGridOverlay = True
- End
- Property Image:Image()
- Return _image
- End
- Method ToggleGridOverlay:Void()
- _showGridOverlay = Not _showGridOverlay
- End
- Method GetCell:Cell(x:Float, y:Float)
-
- 'Return a cell reference if a given pixel coordinate falls inside it.
- Local col:UByte = Floor(x / _rw)
- Local row:UByte = Floor(y / _rh)
-
- 'Null will be returned if col or row are invalid.
- Return _grid.GetCell(col, row)
- End
-
- Method Render:Void()
-
- 'Render the grid (and overlay lines if necessary) onto the image canvas.
-
- _iCanvas.Clear(Color.Black)
-
- For Local c:Int = 0 until _grid.Cols
- For Local r:Int = 0 Until _grid.Rows
- Local curCell:Cell = _grid.GetCell(c, r)
- _iCanvas.Color = Color.DarkGrey
- If Not(curCell.Dead) Then _iCanvas.Color = Color.Green
- _iCanvas.DrawRect((c * _rw), (r * _rh), _rw, _rh)
- Next
- Next
- If _showGridOverlay
- _iCanvas.Color = Color.Grey
- For Local x := 0 To _image.Width Step _rw
- _iCanvas.DrawLine(x, 0, x, _image.Height)
- Next
- For Local y := 0 to _image.Height Step _rh
- _iCanvas.DrawLine(0, y, _image.Width, y)
- Next
- Endif
- _iCanvas.Flush()
- End
- End
- '----------------------------------
- 'A cell can either be dead or alive.
- 'Pending dead = killed off in the second pass of the current iteration.
- Class Cell
- Field _dead:Bool
- Field _pendingDead:Bool
- Field _col:UInt, _row:UInt
-
- Method New()
- Dead = True
- PendingDead = False
- End
-
- Method ToggleDead:Void()
- _dead = Not _dead
- _pendingDead = _dead
- End
-
- Method ApplyPending:Void()
- Dead = PendingDead
- End
-
- Property Dead:Bool()
- Return _dead
- Setter (value:Bool)
- _dead = value
- End
-
- Property PendingDead:Bool()
- Return _pendingDead
- Setter (value:Bool)
- _pendingDead = value
- End
-
- Property Col:UInt()
- Return _col
- Setter (value:UInt)
- _col = value
- End
-
- Property Row:UInt()
- Return _row
- Setter (value:UInt)
- _row = value
- End
-
- End
- '----------------------------------
- Class CellGrid
- Field cols:UInt
- Field rows:UInt
- Field data:Cell[,]
- Method New(cols:UInt, rows:UInt)
- Self.cols = cols
- Self.rows = rows
- data = New Cell[cols, rows]
- For Local c:UInt = 0 Until cols
- For Local r:UInt = 0 Until rows
- data[c, r] = New Cell()
- data[c, r].Col = c
- data[c, r].Row = r
- Next
- Next
- Init()
- End
- Property Cols:UInt()
- Return cols
- End
-
- Property Rows:UInt()
- Return rows
- End
- Method GetCell:Cell(col:UInt, row:UInt)
- If (col < cols) And (col >= 0) And (row < rows) And (row >= 0)
- Return data[col, row]
- Endif
-
- Return Null
- End
- Method Clear:Void()
- For Local c:UInt = 0 Until cols
- For Local r:UInt = 0 Until rows
- data[c, r].Dead = True
- Next
- Next
- End
- Method Init:Void()
- For Local c:UInt = 0 Until cols
- For Local r:UInt = 0 Until rows
-
- 'Approx 25% of the cells will start alive
-
- If Floor(Rnd(0, 100)) <= 25
- data[c, r].Dead = False
- Else
- data[c, r].Dead = True
- Endif
-
- Next
- Next
- End
- Method Iterate:Void()
-
- 'First pass - determine what cells will live and die
- 'Make the results 'pending'.
- For Local c:UInt = 0 Until cols
- For Local r:UInt = 0 Until rows
-
- Local curCell:Cell = data[c, r]
-
- curCell.PendingDead = True
-
- Local liveCount:UInt = CountLiveNeighbours(curCell.Col, curCell.Row)
-
- If curCell.Dead
- If (liveCount = 3) Then curCell.PendingDead = False
- Else
- curCell.PendingDead = False
- If (liveCount < 2) Or (liveCount > 3)
- curCell.PendingDead = True
- Endif
- Endif
-
- Next
- Next
- 'Second pass - modify the grid by applying the pending changes.
- For Local c:UInt = 0 Until cols
- For Local r:UInt = 0 Until rows
- data[c, r].ApplyPending()
- Next
- Next
-
- End
- Method CountLiveNeighbours:UInt(cc:Int, cr:Int)
-
- Local count:UInt = 0
-
- ' --- Boundary checking
-
- Local startc:Int = cc - 1
- If startc < 0 Then startc = 0
-
- Local endc:Int = cc + 1
- If endc >= cols Then endc = cols - 1
-
- Local startr:Int = cr - 1
- If startr < 0 Then startr = 0
-
- Local endr:Int = cr + 1
- If endr >= rows Then endr = rows - 1
- ' ---
- 'Count the live neighbours of the given cell.
- For Local c:Int = startc To endc
- For Local r:Int = startr To endr
- If Not(data[c, r].Dead) Then count += 1
- End
- End
-
- 'Adjust according to the given cell's status.
- If Not(data[cc, cr].Dead) Then count -= 1
-
- Return count
-
- End
- End
- '----------------------------------
- 'Frames-per-second function taken from the MX2 "Commanche" sample.
- Function Fps:Float()
-
- Global FPStime:Float, frameCounter:Float, frameTimer:Float, totalFrames:Float
-
- frameCounter+= 1
-
- totalFrames+= 1
-
- If frameTimer < Millisecs()
- FPStime = frameCounter
- frameTimer = 1000 + Millisecs()
- frameCounter = 0
- Endif
-
- Return FPStime
-
- End Function
|