import numpy as np import holoviews as hv import panel as pn from holoviews import opts from holoviews.streams import Tap, Counter, DoubleTap from scipy.signal import convolve2d hv.extension('bokeh') diehard = [[0, 0, 0, 0, 0, 0, 1, 0], [1, 1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 1, 1, 1]] boat = [[1, 1, 0], [1, 0, 1], [0, 1, 0]] r_pentomino = [[0, 1, 1], [1, 1, 0], [0, 1, 0]] beacon = [[0, 0, 1, 1], [0, 0, 1, 1], [1, 1, 0, 0], [1, 1, 0, 0]] acorn = [[0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0], [1, 1, 0, 0, 1, 1, 1]] spaceship = [[0, 0, 1, 1, 0], [1, 1, 0, 1, 1], [1, 1, 1, 1, 0], [0, 1, 1, 0, 0]] block_switch_engine = [[0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 1, 0, 1, 1], [0, 0, 0, 0, 1, 0, 1, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [1, 0, 1, 0, 0, 0, 0, 0]] glider = [[1, 0, 0], [0, 1, 1], [1, 1, 0]] unbounded = [[1, 1, 1, 0, 1], [1, 0, 0, 0, 0], [0, 0, 0, 1, 1], [0, 1, 1, 0, 1], [1, 0, 1, 0, 1]] shapes = {'Glider': glider, 'Block Switch Engine': block_switch_engine, 'Spaceship': spaceship, 'Acorn': acorn, 'Beacon': beacon, 'Diehard': diehard, 'Unbounded': unbounded} def step(X): nbrs_count = convolve2d(X, np.ones((3, 3)), mode='same', boundary='wrap') - X return (nbrs_count == 3) | (X & (nbrs_count == 2)) def update(pattern, counter, x, y): if x and y: pattern = np.array(shapes[pattern]) r, c = pattern.shape y, x = img.sheet2matrixidx(x,y) img.data[y:y+r,x:x+c] = pattern[::-1] else: img.data = step(img.data) return hv.Image(img) # Set up plot which advances on counter and adds pattern on tap title = 'Game of Life - Tap to place pattern, Doubletap to clear' img = hv.Image(np.zeros((100, 200), dtype=np.uint8)) counter, tap = Counter(transient=True), Tap(transient=True), pattern_dim = hv.Dimension('Pattern', values=sorted(shapes.keys())) dmap = hv.DynamicMap(update, kdims=[pattern_dim], streams=[counter, tap]) plot = dmap.opts( opts.Image(cmap='gray', clim=(0, 1), toolbar=None, responsive=True, min_height=800, title=title, xaxis=None, yaxis=None) ) # Add callback to clear on double tap def reset_data(x, y): img.data[:] = 0 reset = DoubleTap(transient=True, source=plot) reset.add_subscriber(reset_data) # Set up Panel app and periodic callback panel = pn.pane.HoloViews(plot, center=True, widget_location='right') def advance(): counter.event(counter=counter.counter+1) pn.state.add_periodic_callback(advance, period=50, start=False) panel.servable('Game of Life')