life.monkey2 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. 'An implementation of Conway's Game of Life
  2. 'http://en.wikipedia.org/wiki/Conway's_Game_of_Life
  3. 'To try different patterns, pause the 'game', click cells,
  4. 'then unpause.
  5. 'For best performance run in "Release" mode.
  6. #Import "<std.monkey2>"
  7. #Import "<mojo.monkey2>"
  8. Using std..
  9. Using mojo..
  10. Function Main()
  11. New AppInstance
  12. New LifeApp(1280, 720)
  13. App.Run()
  14. End
  15. '------------------------------
  16. Class LifeApp Extends Window
  17. Field iterations:UInt 'number of iterations
  18. Field lastMS:ULong 'time of last iteration
  19. Field speed:UInt 'iterations per second
  20. Field speedMin:UInt = 1
  21. Field speedMax:UInt = 20
  22. Field paused:Bool
  23. Field suspended:Bool
  24. Field grid:CellGrid
  25. Field gridRenderer:GridRenderer
  26. Field gridArea:Recti 'canvas area to draw the grid image onto
  27. Field initialized:Bool = False 'false if grid and gridRenderer objects not yet created
  28. Field fontHeight:UInt = 30 'should be able to get this value from canvas.Font.Height but can't yet?
  29. Field cellWidthPixels:UInt = 16
  30. Field cellHeightPixels:UInt = 16
  31. Method New(width:Int, height:Int)
  32. Super.New("Conway's Game of Life", width, height)
  33. ClearColor = Color.Black
  34. 'Disable vsync if possible.
  35. SwapInterval = 0
  36. End
  37. 'Create and initialize a cell grid and an associated Grid rendering object.
  38. Method Start:Void()
  39. paused = True
  40. iterations = 0
  41. speed = speedMax / 2
  42. 'Grid's top left pixel coordinate
  43. Local gridTL := New Vec2i(10, fontHeight + 10)
  44. 'Grid's bottom right pixel coordinate
  45. Local gridBR := New Vec2i(Width - 10, Height - fontHeight - 10)
  46. 'Define a target area on which to draw the grid, using the above vectors
  47. gridArea = New Recti(gridTL, gridBR)
  48. 'Calculate grid size based on the draw area's dimensions
  49. 'and desired cell size in pixels.
  50. Local gridCols := Floor(gridArea.Width / cellWidthPixels)
  51. Local gridRows := Floor(gridArea.Height / cellHeightPixels)
  52. 'Create the grid of cells.
  53. grid = New CellGrid(gridCols, gridRows)
  54. 'Create the grid renderer, passing in a reference to the grid and the cell pixel dimensions.
  55. gridRenderer = New GridRenderer(grid, cellWidthPixels, cellHeightPixels)
  56. SeedRnd(Microsecs())
  57. paused = False
  58. lastMS = Millisecs()
  59. 'Call the garbage collector. Maybe it can free some memory (or maybe not)
  60. 'if this Start method was called during a WindowResize event.
  61. GCCollect()
  62. End
  63. Method OnWindowEvent(event:WindowEvent) Override
  64. If event.Type = EventType.WindowResized
  65. 'Make a new grid and associated grid renderer based
  66. 'on the window's new dimensions.
  67. Start()
  68. App.RequestRender()
  69. Elseif event.Type = EventType.WindowClose
  70. App.Terminate()
  71. Elseif event.Type=EventType.WindowGainedFocus
  72. suspended=False
  73. App.RequestRender() 'kick start rendering...
  74. Elseif event.Type=EventType.WindowLostFocus
  75. suspended=True
  76. Endif
  77. End
  78. Method OnKeyEvent( event:KeyEvent ) Override
  79. If event.Type = EventType.KeyUp
  80. Select event.Key
  81. Case Key.Escape
  82. App.Terminate()
  83. Case Key.Space
  84. paused = Not paused
  85. Case Key.Up
  86. SpeedUp()
  87. Case Key.Down
  88. SpeedDown()
  89. Case Key.Tab
  90. gridRenderer.ToggleGridOverlay()
  91. Case Key.Enter
  92. 'Reset, but retain the current speed.
  93. grid.Init()
  94. iterations = 0
  95. Case Key.C
  96. grid.Clear()
  97. End
  98. Endif
  99. End
  100. Method OnMouseEvent( event:MouseEvent ) Override
  101. Select event.Type
  102. Case EventType.MouseDown
  103. If event.Button = 1
  104. 'if the clicked coordinate matches a cell then toggle it alive/dead.
  105. Local cell:Cell = gridRenderer.GetCell(event.Location.X - gridArea.TopLeft.X, event.Location.Y - gridArea.TopLeft.Y)
  106. If cell Then cell.ToggleDead()
  107. Endif
  108. End
  109. End
  110. Method OnRender(canvas:Canvas) Override
  111. If Not initialized
  112. 'This small If block only runs once at the start of the program.
  113. '(Mojo must finish "constructing" before the Window's Width and Height properties
  114. 'return correct results - used in the Start method).
  115. Start()
  116. initialized = True
  117. Endif
  118. If Not suspended App.RequestRender()
  119. canvas.Color = Color.White
  120. gridRenderer.Render()
  121. Local statusLine := "Iterations: " + iterations + " FPS: " + Fps() + " Speed: " + speed + " "
  122. canvas.DrawText(statusLine, 10, 10)
  123. canvas.DrawText("[Space] - pause, [Enter] - new, [Up][Down] - speed, [Tab] - toggle grid, [c] - clear, [Left-Click] - Toggle cell alive/dead" , 10, Height - fontHeight)
  124. canvas.DrawImage(gridRenderer.Image, gridArea.TopLeft.X, gridArea.TopLeft.Y)
  125. If paused
  126. canvas.Color = Color.Red
  127. canvas.DrawText("PAUSED - Press [space] to resume", 10 + canvas.Font.TextWidth(statusLine), 10)
  128. endif
  129. '---
  130. 'Update the grid at the current speed (iterations per second).
  131. If Not paused
  132. If Millisecs() >= (lastMS + (1000 / speed))
  133. grid.Iterate()
  134. iterations += 1
  135. lastMS = Millisecs()
  136. Endif
  137. Endif
  138. '---
  139. End
  140. Method SpeedUp:Void()
  141. If speed >= speedMax Then Return
  142. speed += 1
  143. End
  144. Method SpeedDown:Void()
  145. If speed <= speedMin Then Return
  146. speed -= 1
  147. End
  148. End
  149. '----------------------------------
  150. 'This class renders a cell grid to an image. It can also return
  151. 'a cell reference based on a given pixel coordinate.
  152. Class GridRenderer
  153. Field _grid:CellGrid
  154. 'Dimensions of the rectangle to render per cell.
  155. Field _rw:UInt, _rh:UInt
  156. 'Image on which to render the grid. (And an associated canvas).
  157. Field _image:Image
  158. Field _iCanvas:Canvas
  159. Field _showGridOverlay:Bool
  160. Method New(grid:CellGrid, rw:UInt, rh:UInt)
  161. _grid = grid
  162. _rw = rw
  163. _rh = rh
  164. _image = New Image(_grid.Cols * _rw + 1, _grid.Rows * _rh + 1)
  165. _iCanvas = New Canvas(_image)
  166. _showGridOverlay = True
  167. End
  168. Property Image:Image()
  169. Return _image
  170. End
  171. Method ToggleGridOverlay:Void()
  172. _showGridOverlay = Not _showGridOverlay
  173. End
  174. Method GetCell:Cell(x:Float, y:Float)
  175. 'Return a cell reference if a given pixel coordinate falls inside it.
  176. Local col:UByte = Floor(x / _rw)
  177. Local row:UByte = Floor(y / _rh)
  178. 'Null will be returned if col or row are invalid.
  179. Return _grid.GetCell(col, row)
  180. End
  181. Method Render:Void()
  182. 'Render the grid (and overlay lines if necessary) onto the image canvas.
  183. _iCanvas.Clear(Color.Black)
  184. For Local c:Int = 0 until _grid.Cols
  185. For Local r:Int = 0 Until _grid.Rows
  186. Local curCell:Cell = _grid.GetCell(c, r)
  187. _iCanvas.Color = Color.DarkGrey
  188. If Not(curCell.Dead) Then _iCanvas.Color = Color.Green
  189. _iCanvas.DrawRect((c * _rw), (r * _rh), _rw, _rh)
  190. Next
  191. Next
  192. If _showGridOverlay
  193. _iCanvas.Color = Color.Grey
  194. For Local x := 0 To _image.Width Step _rw
  195. _iCanvas.DrawLine(x, 0, x, _image.Height)
  196. Next
  197. For Local y := 0 to _image.Height Step _rh
  198. _iCanvas.DrawLine(0, y, _image.Width, y)
  199. Next
  200. Endif
  201. _iCanvas.Flush()
  202. End
  203. End
  204. '----------------------------------
  205. 'A cell can either be dead or alive.
  206. 'Pending dead = killed off in the second pass of the current iteration.
  207. Class Cell
  208. Field _dead:Bool
  209. Field _pendingDead:Bool
  210. Field _col:UInt, _row:UInt
  211. Method New()
  212. Dead = True
  213. PendingDead = False
  214. End
  215. Method ToggleDead:Void()
  216. _dead = Not _dead
  217. _pendingDead = _dead
  218. End
  219. Method ApplyPending:Void()
  220. Dead = PendingDead
  221. End
  222. Property Dead:Bool()
  223. Return _dead
  224. Setter (value:Bool)
  225. _dead = value
  226. End
  227. Property PendingDead:Bool()
  228. Return _pendingDead
  229. Setter (value:Bool)
  230. _pendingDead = value
  231. End
  232. Property Col:UInt()
  233. Return _col
  234. Setter (value:UInt)
  235. _col = value
  236. End
  237. Property Row:UInt()
  238. Return _row
  239. Setter (value:UInt)
  240. _row = value
  241. End
  242. End
  243. '----------------------------------
  244. Class CellGrid
  245. Field cols:UInt
  246. Field rows:UInt
  247. Field data:Cell[,]
  248. Method New(cols:UInt, rows:UInt)
  249. Self.cols = cols
  250. Self.rows = rows
  251. data = New Cell[cols, rows]
  252. For Local c:UInt = 0 Until cols
  253. For Local r:UInt = 0 Until rows
  254. data[c, r] = New Cell()
  255. data[c, r].Col = c
  256. data[c, r].Row = r
  257. Next
  258. Next
  259. Init()
  260. End
  261. Property Cols:UInt()
  262. Return cols
  263. End
  264. Property Rows:UInt()
  265. Return rows
  266. End
  267. Method GetCell:Cell(col:UInt, row:UInt)
  268. If (col < cols) And (col >= 0) And (row < rows) And (row >= 0)
  269. Return data[col, row]
  270. Endif
  271. Return Null
  272. End
  273. Method Clear:Void()
  274. For Local c:UInt = 0 Until cols
  275. For Local r:UInt = 0 Until rows
  276. data[c, r].Dead = True
  277. Next
  278. Next
  279. End
  280. Method Init:Void()
  281. For Local c:UInt = 0 Until cols
  282. For Local r:UInt = 0 Until rows
  283. 'Approx 25% of the cells will start alive
  284. If Floor(Rnd(0, 100)) <= 25
  285. data[c, r].Dead = False
  286. Else
  287. data[c, r].Dead = True
  288. Endif
  289. Next
  290. Next
  291. End
  292. Method Iterate:Void()
  293. 'First pass - determine what cells will live and die
  294. 'Make the results 'pending'.
  295. For Local c:UInt = 0 Until cols
  296. For Local r:UInt = 0 Until rows
  297. Local curCell:Cell = data[c, r]
  298. curCell.PendingDead = True
  299. Local liveCount:UInt = CountLiveNeighbours(curCell.Col, curCell.Row)
  300. If curCell.Dead
  301. If (liveCount = 3) Then curCell.PendingDead = False
  302. Else
  303. curCell.PendingDead = False
  304. If (liveCount < 2) Or (liveCount > 3)
  305. curCell.PendingDead = True
  306. Endif
  307. Endif
  308. Next
  309. Next
  310. 'Second pass - modify the grid by applying the pending changes.
  311. For Local c:UInt = 0 Until cols
  312. For Local r:UInt = 0 Until rows
  313. data[c, r].ApplyPending()
  314. Next
  315. Next
  316. End
  317. Method CountLiveNeighbours:UInt(cc:Int, cr:Int)
  318. Local count:UInt = 0
  319. ' --- Boundary checking
  320. Local startc:Int = cc - 1
  321. If startc < 0 Then startc = 0
  322. Local endc:Int = cc + 1
  323. If endc >= cols Then endc = cols - 1
  324. Local startr:Int = cr - 1
  325. If startr < 0 Then startr = 0
  326. Local endr:Int = cr + 1
  327. If endr >= rows Then endr = rows - 1
  328. ' ---
  329. 'Count the live neighbours of the given cell.
  330. For Local c:Int = startc To endc
  331. For Local r:Int = startr To endr
  332. If Not(data[c, r].Dead) Then count += 1
  333. End
  334. End
  335. 'Adjust according to the given cell's status.
  336. If Not(data[cc, cr].Dead) Then count -= 1
  337. Return count
  338. End
  339. End
  340. '----------------------------------
  341. 'Frames-per-second function taken from the MX2 "Commanche" sample.
  342. Function Fps:Float()
  343. Global FPStime:Float, frameCounter:Float, frameTimer:Float, totalFrames:Float
  344. frameCounter+= 1
  345. totalFrames+= 1
  346. If frameTimer < Millisecs()
  347. FPStime = frameCounter
  348. frameTimer = 1000 + Millisecs()
  349. frameCounter = 0
  350. Endif
  351. Return FPStime
  352. End Function