-
Notifications
You must be signed in to change notification settings - Fork 1
/
gameoflife.scm
123 lines (110 loc) · 4.5 KB
/
gameoflife.scm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
(define-module (game-of-life)
#:export (game-of-life
pretty-print-grid
grid-from-file
send
new-instance))
(use-modules
;pattern matching
(ice-9 match)
;read-string
(ice-9 rdelim)
;list manipulations
(srfi srfi-1))
(define (game-of-life)
; initialisation
(let ((grid #nil)
(width #nil)
(height #nil))
; private implementation
(define (getgrid)
(array->list grid))
(define (setgrid! new-grid)
(match (storegrid new-grid)
((new-height new-width new-grid)
(set! grid new-grid)
(set! height new-height)
(set! width new-width))))
(define (step!)
(set! grid (updategrid grid height width)))
; public interface
(define (self message)
(cond ((eqv? message 'getgrid) getgrid) ;() -> [[int]]
((eqv? message 'setgrid!) setgrid!) ;[[int]] -> ()
((eqv? message 'step!) step!) ;() -> ()
(else (error "Undefined message" message))))
self))
;---- internal function implementations
; use arrays internally for lookup performance: https://www.gnu.org/software/guile/manual/html_node/Array-Procedures.html#Array-Procedures
(define (storegrid grid)
(let ((height (length grid))
(width (length (car grid)))
(grid-as-array (list->array 2 grid)))
(list height width grid-as-array)))
; rules here
(define (updategrid grid height width)
(letrec ((update-cell (lambda (old-cell i j)
(let ((num-neighbors (live-neighbors i j)))
(cond ((eq? old-cell 1) ;live cells survive with 2/3 live neighbours
(cond ((or (= num-neighbors 2) (= num-neighbors 3)) 1)
(else 0)))
(else (cond ((= num-neighbors 3) 1) ; dead cells reanimate with 3 live neighbours
(else 0)))))))
(live-neighbors (lambda (i j)
(+ (neighbor-count (- i 1) (- j 1))
(neighbor-count (- i 1) j)
(neighbor-count (- i 1) (+ j 1))
(neighbor-count i (- j 1))
(neighbor-count i (+ j 1))
(neighbor-count (+ i 1) (- j 1))
(neighbor-count (+ i 1) j)
(neighbor-count (+ i 1) (+ j 1)))))
(neighbor-count (lambda (i j)
(cond ((< i 0) 0)
((>= i height) 0)
((< j 0) 0)
((>= j width) 0)
(else (array-ref grid i j))))))
(list->array 2 (map (lambda (i)
(map (lambda (j)
(update-cell (array-ref grid i j) i j))
(iota width)))
(iota height)))))
;---- other public functions
;string representation of grid
;[[int]] -> string
(define* (pretty-print-grid grid #:optional (live #\o) (dead #\-))
(let ((int->cell (lambda (int)
(cond ((= int 1) live)
(else dead)))))
(string-join
(map (lambda (row)
(list->string (map int->cell row)))
grid)
"\n")))
; filename -> [[int]]
; file should contain an rectangular array of characters. The 'alive' character representation can be optionally set (assumed to be 'o' (lowercase O))
(define* (grid-from-file infile #:optional (live #\o))
(let* ((char->cell (lambda (char)
(cond ((eq? char live) 1)
(else 0))))
(row->cells (lambda (row)
(map (lambda (char)
(char->cell char))
(string->list row)))))
(filter (compose not null?)
(map (lambda (row)
(row->cells row))
(string-split
(string-trim
(call-with-input-file infile read-string))
#\newline)))))
;---- OOP sugar functions
(define (send message object . args)
(let ((method (object message)))
(cond ((procedure? method) (apply method args))
(else (error "Error in method lookup " method)))))
(define (new-instance class . parameters)
(apply class parameters))
;---- Helper functions
(define (compose f g) (lambda (x) (f (g x))))