PasteRack.org
Paste # 15550
2019-02-16 23:43:02

Fork as a new paste.

Paste viewed 323 times.


Embed:

A Game of Life implementation using my toy Quadtree

  1. #lang typed/racket
  2.  
  3. ; Apologies that this one's kind of messy.
  4. ; I enjoyed how straightforward it was to port my previous implementation of Conway's Game
  5. ; to my implementation of the Quadtree data structure.
  6.  
  7. ; Here's a couple of examples of what the output looks like:
  8. ; https://i.imgur.com/2QYR0P5.gif
  9. ; https://i.imgur.com/Zo2sxT2.gif
  10. ; https://i.imgur.com/KcIJEIc.gif
  11.  
  12. (require "qtree.rkt")
  13. (require typed/racket/draw)
  14. (require typed/mrlib/gif)
  15.  
  16. ; constants. never enough constants
  17. (define cell-scale 5)
  18. (define canvas-size 500)
  19. (define frames-to-animate 50)
  20. (define output-file-name "your_filename_here")
  21.  
  22. (define living-cells
  23.   (quadtree-fold null (map (lambda ([y : Integer]) (coordinate y 50)) (range 10 90))))
  24.  
  25. (define moment-zero (list living-cells))
  26.  
  27. (define-type History (Listof Quadtree))
  28.  
  29. ; Given a coordinate and a Quadtree, determine if it survives.
  30. (: determine-cell-state (-> Quadtree coordinate Boolean))
  31. (define (determine-cell-state population cell)
  32.   (let ([living-neighbors (length (quadtree-neighbors population cell))]
  33.         [orig-state (quadtree-member? population cell)])
  34.     (if (< living-neighbors 2) #f                          ; if the cell has fewer than 2 neighbors, it dies.
  35.         (if (equal? living-neighbors 3) #t                 ; if the cell has exactly 3 neighbors, it comes to life.
  36.             (if (> living-neighbors 3) #f orig-state)))))  ; if the cell has more than 3 neighbors, it dies.
  37.  
  38. ; Advance time by one frame.
  39. (: tick (-> Quadtree Quadtree))
  40. (define (tick living-cells)
  41.   (let ([cell-list (descendant-coordinates living-cells)])
  42.     (quadtree-fold null
  43.                    (append-map (λ ([cell : coordinate])
  44.                                  (filter (curry determine-cell-state living-cells)
  45.                                          (neighbor-coords cell)))
  46.                                cell-list))))
  47.  
  48. ; Advances time an arbitrary number of frames.
  49. (: iterate-history (-> History Integer History))
  50. (define (iterate-history history i)
  51.   (cond
  52.     [(<= i 0) history]
  53.     [else (iterate-history (append history (list (tick (last history)))) (- i 1))]))
  54.  
  55. ; I'd like to find a cleaner way to do this.
  56. (: neighbor-coords (-> coordinate (Listof coordinate)))
  57. (define (neighbor-coords coordinate)
  58.   (let ([x : Integer (coordinate-x coordinate)]
  59.         [y : Integer (coordinate-y coordinate)])
  60.     (append-map (curry coordmap x) (range (- y 1) (+ y 2)))))
  61.  
  62. ; give all the ys for an x
  63. (: coordmap (-> Integer Integer (Listof coordinate)))
  64. (define (coordmap x y)
  65.   (map (λ ([z : Integer]) (coordinate z y))
  66.        (range (- x 1) (+ x 2))))
  67.  
  68. ; ronder
  69. (: render-frame (-> Quadtree (Instance Bitmap%)))
  70. (define (render-frame cells)
  71.   (let ([target (make-bitmap canvas-size canvas-size)])
  72.     (let ([dc : (Instance Bitmap-DC%) (new bitmap-dc% [bitmap target])])
  73.       (send dc set-background "black")
  74.       (send dc set-pen "black" 1 'transparent)
  75.       (send dc set-brush "black" 'solid)
  76.       (send dc draw-rectangle 0 0 canvas-size canvas-size)
  77.       (send dc set-brush "green" 'solid)
  78.       (send dc set-pen "green" 1 'solid)
  79.       (recursidraw dc (descendant-coordinates cells)) target)))
  80.  
  81. ; draww
  82. (: recursidraw (-> (Instance Bitmap-DC%) (Listof coordinate) (Instance Bitmap-DC%)))
  83. (define (recursidraw dc cells)
  84.   (cond
  85.     [(empty? cells) dc]
  86.     [else   (let ([cell : coordinate (first cells)])
  87.               (send dc draw-rectangle
  88.                     (* cell-scale (coordinate-y cell))
  89.                     (* cell-scale (coordinate-x cell)) cell-scale cell-scale)
  90.               (recursidraw dc (rest cells)))]))
  91.  
  92. ; mhm
  93. (define a-fine-mess (iterate-history moment-zero frames-to-animate))
  94.  
  95. ; spit it out
  96. (write-animated-gif (map (curry render-frame) a-fine-mess) 10 output-file-name)
  97.  
  98.  
  99. ; When I did my first tests of this implementation, I was initially very disappointed--
  100. ; it seemed that my fourth implementation of Conway's Game of Life in Racket was doomed to be
  101. ; the least-performant of all. However, I ran tests with longer values of frames-to-animate, and
  102. ; it seems like the quadtree implementation overtakes the previous implementation around 100
  103. ; frames in. I haven't tried turning off all the debugging/profiling stuff and compiling the bytecode,
  104. ; but I am curious how performant this can be made.
  105.  
  106. ; For comparison (moment-zero was set to equivalent values for both):
  107.  
  108. ; list-based GoL implementation
  109. ; > (time (void (iterate-history moment-zero 10)))
  110. ; cpu time: 11669 real time: 11761 gc time: 94
  111. ; > (time (void (iterate-history moment-zero 100)))
  112. ; cpu time: 255155 real time: 257376 gc time: 956
  113. ; > (time (void (iterate-history moment-zero 200)))
  114. ; cpu time: 496005 real time: 500710 gc time: 2087
  115.  
  116. ; quadtree-based GoL implementation:
  117. ; > (time (void (iterate-history moment-zero 10)))
  118. ; cpu time: 38672 real time: 39119 gc time: 326
  119. ; > (time (void (iterate-history moment-zero 100)))
  120. ; cpu time: 261972 real time: 254202 gc time: 9704
  121. ; > (time (void (iterate-history moment-zero 200)))
  122. ; cpu time: 354715 real time: 365443 gc time: 2088

=>