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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
|
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson
"""Conway's Game of Life.
On 11 April 2020 John H. Conway who, among many, many other
achievements, devised the rule set for his Game of Life, died of
complications from a COVID-19 infection.
The Game of Life is the first "toy" program I ever recall seeing on a
computer (running in a mid 1980s Apple Macintosh). It sparked something
even if "toy" is perhaps an underwhelming description of the Game of Life.
Either way it occupies a special place in my childhood. For that, this
application is dedicated to Professor Conway.
"""
import array
import machine
import micropython
import wasp
@micropython.viper
def xorshift12(v: int) -> int:
"""12-bit xorshift pseudo random number generator.
With only 12-bits of state this PRNG is another toy! It appears
here because it allows us to visit every possible 12-bit value
(except zero) whilst taking an interesting route. This allows us to
make the redraw (which is too slow to fully conceal) visually
engaging.
"""
v ^= v << 1
v ^= (v >> 3) & 0x1ff
v ^= (v << 7)
return v & 0xfff
@micropython.viper
def get_color(v: int) -> int:
"""Convert a 12-bit number into a reasonably bright RGB565 pixel"""
rgb = v ^ (v << 4)
while 0 == (rgb & 0xc710):
rgb += 0x2104
return rgb
@micropython.viper
def get_cell(board, stride: int, x: int, y: int) -> bool:
b = ptr32(board)
xw = x >> 5
xb = x & 0x1f
yw = y * (stride >> 5)
return bool(b[yw + xw] & (1 << xb))
@micropython.viper
def set_cell(board, stride: int, x: int, y: int, v: bool):
b = ptr32(board)
xw = x >> 5
xb = x & 0x1f
yw = y * (stride >> 5)
m = 1 << xb
c = b[yw + xw]
# viper doesn't implement bitwise not so we are having
# to clear bits using xor...
if v:
b[yw + xw] = c | m
elif c & m:
b[yw + xw] = c ^ m
@micropython.viper
def game_of_life(b, xmax: int, ymax: int, nb):
"""Run a single generation of Conway's Game of Life
1. Death by isolation: a cell dies if has fewer than two live neighbours.
2. Death by overcrowding: a cell dies if it has more than three live
neighbours.
3. Survival: a living cell continues to survive if it has two or three
neighbours.
4. Reproduction: a dead cell comes alive if it has exactly three
neighbours.
In the code below we have simplified the above rules to "a cell is
alive it has three live neighbours or if it was previously alive
and has two neighbours, otherwise it is dead.".
"""
board = ptr32(b)
next_board = ptr32(nb)
for y in range(1, ymax-1):
tm = int(get_cell(board, xmax, 0, y-1))
tr = int(get_cell(board, xmax, 1, y-1))
cm = int(get_cell(board, xmax, 0, y))
cr = int(get_cell(board, xmax, 1, y))
bm = int(get_cell(board, xmax, 0, y+1))
br = int(get_cell(board, xmax, 1, y+1))
for x in range(1, xmax-1):
tl = tm
tm = tr
tr = int(get_cell(board, xmax, x+1, y-1))
cl = cm
cm = cr
cr = int(get_cell(board, xmax, x+1, y))
bl = bm
bm = br
br = int(get_cell(board, xmax, x+1, y+1))
c = tl + tm + tr + cl + cr + bl + bm + br
set_cell(next_board, xmax, x, y, c == 3 or (cm and c == 2))
# 2-bit RLE, generated from res/gameoflife.png, 404 bytes
# The icon is a carefully selected generation of an "acorn", I wanted
# to avoid using a glider, they are overused to the point of cliche!
icon = (
b'\x02'
b'`@'
b'?\xff\xff\xee@\xf8B\x02B\x02B?\x16L?\x15'
b'L?\x16B\x02B\x02B?\x1bB?\x1eD?\x1d'
b'D?\x1eB?\x17\x80\xee\x82\x02\x82\x06\x82\x02\x82?'
b'\x0e\x88\x04\x88?\r\x88\x04\x88?\x0e\x82\x02\x82\x06B'
b'\x02\x82?\x03\xc0\x89\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02'
b'\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc25\xec4\xec5'
b'\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02\xc2\x02'
b'\xc2\x02\xc2\x02\xc2*B\x02B\x12\xc2\x06B\x06\xc2\x12'
b'B\x02B\x1dH\x10\xc4\x04D\x04\xc4\x10H\x1cH\x10'
b'\xc4\x04D\x04\xc4\x10H\x1dB\x02B\x12\xc2\x06B\x06'
b'\xc2\x12B\x02B\x1eB\x16\xc2\x0e\xc2\x16B\x1dD\x14'
b'\xc4\x0c\xc4\x14D\x1cD\x14\xc4\x0c\xc4\x14D\x1dB\x16'
b'\xc2\x0e\xc2\x16B\x1eB>B\x1dD<D\x1cD<'
b'D\x1dB>B"B\x02B\x06B\x02B\x02B\x0e'
b'B\x02B\x02B\x06B\x02B%H\x04L\x0cL\x04'
b'H$H\x04L\x0cL\x04H%B\x02B\x06B\x02'
b'B\x02B\x0eB\x02B\x02B\x06B\x02B2B\n'
b'B\x06B\nB=D\x08D\x04D\x08D<D\x08'
b'D\x04D\x08D=B\nB\x06B\nB>B\x02'
b'B\x06\x82\x06\x82\x06B\x02B=H\x04\x84\x04\x84\x04'
b'H<H\x04\x84\x04\x84\x04H=B\x02B\x06\x82\x06'
b'\x82\x06B\x02B>\x82\x02\x82\x16\x82\x02\x82=\x88\x14'
b'\x88<\x88\x14\x88=\x82\x02\x82\x16\x82\x02\x82>\x82\x02'
b'\x82\x02\x82\x0e\x82\x02\x82\x02\x82=\x8c\x0c\x8c<\x8c\x0c'
b'\x8c=\x82\x02\x82\x02\x82\x0e\x82\x02\x82\x02\x82?\xff\xff'
b'\xe2'
)
class GameOfLifeApp():
"""Application implementing Conway's Game of Life.
"""
NAME = 'Life'
ICON = icon
def __init__(self):
"""Initialize the application."""
self._board = array.array('I', [0] * (64*64//32))
self._next_board = array.array('I', self._board)
self._color = 1
self.touch(None)
def foreground(self):
"""Activate the application."""
self._draw()
wasp.system.request_event(wasp.EventMask.TOUCH)
wasp.system.request_tick(625)
def tick(self, ticks):
"""Notify the application that its periodic tick is due."""
wasp.system.keep_awake()
#t = machine.Timer(id=1, period=8000000)
#t.start()
game_of_life(self._board, 64, 64, self._next_board)
#t1 = t.time()
self._update()
#t2 = t.time()
#t.stop()
#del t
#wasp.watch.drawable.string('{:4.2f}s {:4.2f}s'.format(t1 / 1000000,
# t2 / 1000000), 6, 210)
def touch(self, event):
"""Notify the application of a touchscreen touch event."""
board = self._next_board
for i in range(len(board)):
board[i] = 0
board[62] = 32 << 16
board[64] = 8 << 16
board[66] = 103 << 16
if None != event:
self._update()
def _draw(self):
"""Draw the display from scratch."""
wasp.watch.drawable.fill()
board = self._board
for i in range(len(board)):
board[i] = 0
self._update()
def _update(self):
"""Update the dynamic parts of the application display."""
b = self._board
nb = self._next_board
self._board = nb
self._next_board = b
display = wasp.watch.display
lb = display.linebuffer
alive = memoryview(lb)[0:2*16]
self._color = xorshift12(self._color)
rgbhi = get_color(self._color)
rgblo = rgbhi & 0xff
rgbhi >>= 8
for i in range(0, len(alive), 2):
alive[i] = rgbhi
alive[i+1] = rgblo
for i in (0, 3, 12, 15):
alive[i*2] = 0
alive[i*2+1] = 0
dead = memoryview(lb)[2*16:4*16]
for i in range(len(dead)):
dead[i] = 0
def draw_cell(cell, display, px):
x = ((cell & 0x3f) - 2) * 4
y = ((cell >> 6) -2) * 4
if x < 0 or x >= 240 or y < 0 or y >= 240:
return
display.set_window(x, y, 4, 4)
display.write_data(px)
draw_cell(1, display, alive if b[1//32] & (1 << (1 & 0x1f)) else dead)
v = xorshift12(1)
while 1 != v:
me = b[v//32] & (1 << (v & 0x1f))
nx = nb[v//32] & (1 << (v & 0x1f))
if me != nx:
draw_cell(v, display, alive if nx else dead)
v = xorshift12(v)
draw_cell(0, display, alive if b[0//32] & (1 << (0 & 0x1f)) else dead)
|