Deckgl Game Of Life#
This demo was adapted from PyDeck’s Conway Game of Life example, full copyright lies with the original authors.
This modified example demonstrates how to display and update a DeckGL pane with a periodic callback. The board lives in a reactive expression and the pane binds to a pure function that turns the board into a deck.gl spec, so each tick only assigns a new board.
import numpy as np
import pandas as pd
import panel as pn
pn.extension('deckgl', sizing_mode="stretch_width")
Declare Game of Life logic#
The board is a 2D array that wraps around at its edges, so the neighbour count is the sum of the board shifted in all eight directions.
SIZE = 30
def new_board(size=SIZE, live_fraction=0.4):
"""Initializes a board for Conway's Game of Life"""
return (np.random.default_rng().random((size, size)) < live_fraction).astype(int)
def process_life(board):
"""Creates the next iteration from a passed state of Conway's Game of Life"""
neighbors = sum(
np.roll(board, (dy, dx), axis=(0, 1))
for dy in (-1, 0, 1) for dx in (-1, 0, 1)
if (dy, dx) != (0, 0)
)
return ((neighbors == 3) | ((board == 1) & (neighbors == 2))).astype(int)
Convert the board to a DeckGL spec#
PINK = [155, 155, 255, 245]
PURPLE = [255, 155, 255, 245]
SCALING_FACTOR = 1000.0
def board_to_spec(board):
ys, xs = np.indices(board.shape)
cells = pd.DataFrame({
'position': [[x / SCALING_FACTOR, y / SCALING_FACTOR] for x, y in zip(xs.ravel(), ys.ravel())],
'color': [PURPLE if alive else PINK for alive in board.ravel()],
})
return {
"initialViewState": {
"bearing": 44,
"latitude": 0.01,
"longitude": 0.01,
"pitch": 45,
"zoom": 13
},
"layers": [{
'@@type': 'PointCloudLayer',
'data': cells,
'getColor': '@@=color',
'getPosition': '@@=position',
'getRadius': 40,
'id': 'cells',
}],
"mapStyle": None,
"views": [{"@@type": "MapView", "controller": True}]
}
Set up Panel and callbacks#
A single periodic callback advances the board. It is created stopped, and its running and period parameters drive the toggle and the period input directly.
board = pn.rx(new_board())
def step():
board.rx.value = process_life(board.rx.value)
def reset_board(event):
board.rx.value = new_board()
periodic_cb = pn.state.add_periodic_callback(step, period=500, start=False)
running = periodic_cb.param.running.rx()
gol = pn.ui.DeckGL(board.rx.pipe(board_to_spec), height=400)
reset = pn.ui.Button(label='Reset', color='warning', width=80, align='end')
reset.on_click(reset_board)
settings = pn.ui.Row(
pn.ui.IntInput.from_param(periodic_cb.param.period, label="Period (ms)", description="", step=50, start=50, width=150),
pn.ui.Toggle.from_param(
periodic_cb.param.running, description="", label=running.rx.where('Stop', 'Run'),
color=running.rx.where('warning', 'primary'), align='end', width=80,
),
reset,
)
description = pn.ui.Markdown("""
**Conway's Game of Life** is a classic demonstration of *emergence*, where higher level patterns form from a few simple rules.
Fantastic patterns emerge when the game is let to run long enough.
The **rules** here, to borrow from [Wikipedia](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life), are as follows:
- Any live cell with fewer than two live neighbours dies, as if by underpopulation.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any live cell with more than three live neighbours dies, as if by overpopulation.
- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
""")
page = pn.ui.Page(
main=[pn.ui.Container(pn.ui.Column(description, gol, settings))],
title="Deck.gl - Game of Life",
)
page
Served App#
if pn.state.served:
page.servable()