From 880764da7db6fee914db54655e121c0e3690bd8e Mon Sep 17 00:00:00 2001 From: Daniel Thompson Date: Fri, 17 Apr 2020 17:30:31 +0100 Subject: [PATCH] wasp: apps: Conway's Game of Life --- res/gameoflife.png | Bin 0 -> 7780 bytes wasp/apps/gameoflife.py | 246 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 res/gameoflife.png create mode 100644 wasp/apps/gameoflife.py diff --git a/res/gameoflife.png b/res/gameoflife.png new file mode 100644 index 0000000000000000000000000000000000000000..11cea0f623c2bc37a91ad66cc521a9cc1ce3b19d GIT binary patch literal 7780 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 literal 0 HcmV?d00001 diff --git a/wasp/apps/gameoflife.py b/wasp/apps/gameoflife.py new file mode 100644 index 0000000..95883c6 --- /dev/null +++ b/wasp/apps/gameoflife.py @@ -0,0 +1,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: + r = v >> 10 + g = (v >> 8) & 7 + b = (v >> 5) & 3 + + return (r << 13) | (g << 7) | (b << 1) | 0x9c73 + +@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@\xd7B\x02B\x02B?\x16L?\x15' + b'L?\x16B\x02B\x02B?\x1bB?\x1eD?\x1d' + b'D?\x1eB?\x17\x80\xbe\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\x97\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\x1dDB"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\x08DB\x02' + b'B\x06\x82\x06\x82\x06B\x02B=H\x04\x84\x04\x84\x04' + b'H\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(1000) + + 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)