import json
import numpy as np
import pandas as pd
import panel as pn
pn.extension('deckgl')
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.
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
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
}
]
}
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 = 'danger'
periodic_cb.start()
else:
periodic_toggle.name = 'Run'
periodic_toggle.button_type = 'success'
periodic_cb.stop()
def update_period(event):
periodic_cb.period = event.new
board = new_board(30, 30)
gol = pn.pane.DeckGL(board_json, width=600, height=600)
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 = gol.add_periodic_callback(run_gol, start=False, period=period.value)
pn.Column(
'## Game of Life (using Deck.GL)',
gol,
pn.Row(period, periodic_toggle, reset)
).servable()