Deckgl Game Of Life#

import json
import numpy as np
import pandas as pd
import panel as pn

pn.extension('deckgl', template='fast', sizing_mode="stretch_width")

pn.state.template.param.update(
    title="Deck.gl - Game of Life",
    main_max_width="768px"
)
<param.parameterized._ParametersRestorer object at 0x118956a50>

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 by modifying the JSON representation and triggering an update.

Declare Game of Life logic#

import random

def new_board(x, y, num_live_cells=2, num_dead_cells=3):
    """Initializes a board for Conway's Game of Life"""
    board = []
    for i in range(0, y):
        # Defaults to a 3:2 dead cell:live cell ratio
        board.append([random.choice([0] * num_dead_cells + [1] * num_live_cells) for _ in range(0, x)])
    return board

        
def get(board, x, y):
    """Return the value at location (x, y) on a board, wrapping around if out-of-bounds"""
    return board[y % len(board)][x % len(board[0])]


def assign(board, x, y, value):
    """Assigns a value at location (x, y) on a board, wrapping around if out-of-bounds"""
    board[y % len(board)][x % len(board[0])] = value


def count_neighbors(board, x, y):
    """Counts the number of living neighbors a cell at (x, y) on a board has"""
    return sum([
        get(board, x - 1, y),
        get(board, x + 1, y),
        get(board, x, y - 1),
        get(board, x, y + 1),
        get(board, x + 1, y + 1),
        get(board, x + 1, y - 1),
        get(board, x - 1, y + 1),
        get(board, x - 1, y - 1)])


def process_life(board):
    """Creates the next iteration from a passed state of Conway's Game of Life"""
    next_board = new_board(len(board[0]), len(board))
    for y in range(0, len(board)):
        for x in range(0, len(board[y])):
            num_neighbors = count_neighbors(board, x, y)
            is_alive = get(board, x, y) == 1
            if num_neighbors < 2 and is_alive:
                assign(next_board, x, y, 0)
            elif 2 <= num_neighbors <= 3 and is_alive:
                assign(next_board, x, y, 1)
            elif num_neighbors > 3 and is_alive:
                assign(next_board, x, y, 0)
            elif num_neighbors == 3 and not is_alive:
                assign(next_board, x, y, 1)
            else:
                assign(next_board, x, y, 0)
    return next_board

Set up DeckGL JSON#

points = {
    '@@type': 'PointCloudLayer',
    'data': [],
    'getColor': '@@=color',
    'getPosition': '@@=position',
    'getRadius': 40,
    'id': '0558257e-1a5c-43d7-bd98-1fba69981c6c'
}

board_json = {
    "initialViewState": {
        "bearing": 44,
        "latitude": 0.01,
        "longitude": 0.01,
        "pitch": 45,
        "zoom": 13
    },
    "layers": [points],
    "mapStyle": "",
    "views": [
        {
            "@@type": "MapView",
            "controller": True
        }
    ]
}

Declare callbacks to periodically update the board#

PINK = [155, 155, 255, 245]
PURPLE = [255, 155, 255, 245]

SCALING_FACTOR = 1000.0

def convert_board_to_df(board):
    """Makes the board matrix into a list for easier processing"""
    rows = []
    for x in range(0, len(board[0])):
        for y in range(0, len(board)):
            rows.append([[x / SCALING_FACTOR, y / SCALING_FACTOR], PURPLE if board[y][x] else PINK])
    return pd.DataFrame(rows, columns=['position', 'color'])

def run_gol(event=None):
    global board
    board = process_life(board)
    records = convert_board_to_df(board)
    points['data'] = records
    gol.param.trigger('object')
    
def reset_board(event):
    global board
    board = new_board(30, 30)
    run_gol()

def toggle_periodic_callback(event):
    if event.new:
        periodic_toggle.name = 'Stop'
        periodic_toggle.button_type = 'warning'
        periodic_cb.start()
    else:
        periodic_toggle.name = 'Run'
        periodic_toggle.button_type = 'primary'
        periodic_cb.stop()
        
def update_period(event):
    periodic_cb.period = event.new

Set up Panel and callbacks#

board = new_board(30, 30)

gol = pn.pane.DeckGL(board_json, height=400)

run_gol()

periodic_toggle = pn.widgets.Toggle(
    name='Run', value=False, button_type='primary', align='end', width=50
)
periodic_toggle.param.watch(toggle_periodic_callback, 'value')

period = pn.widgets.Spinner(name="Period (ms)", value=500, step=50, start=50,
                            align='end', width=100)
period.param.watch(update_period, 'value')

reset = pn.widgets.Button(name='Reset', button_type='warning', width=60, align='end')
reset.on_click(reset_board)

periodic_cb = pn.state.add_periodic_callback(run_gol, start=False, period=period.value)

settings = pn.Row(period, periodic_toggle, reset, width=400, sizing_mode="fixed")

description = """
**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.

This demo was **adapted from [PyDeck's Conway Game of Life example](https://github.com/uber/deck.gl/blob/66c75051d5b385db31f0a4322dff054779824783/bindings/pydeck/examples/06%20-%20Conway's%20Game%20of%20Life.ipynb)**, full copyright lies with the original authors.

This modified example demonstrates **how to display and update a `DeckGL` pane with a periodic callback** by modifying the JSON representation and triggering an update."""

pn.Column(
    '## Game of Life (using Deck.GL)',
    pn.Column(
        description,
        gol,
        settings
    ).servable()
)