Skip to content

Instantly share code, notes, and snippets.

@mikelane
Last active March 21, 2022 22:52
Show Gist options
  • Save mikelane/89c580b7764f04cf73b32bf4e94fd3a3 to your computer and use it in GitHub Desktop.
Save mikelane/89c580b7764f04cf73b32bf4e94fd3a3 to your computer and use it in GitHub Desktop.
Conway's Game of Life implemented using a 2d convolution.
import click
import difflib
import numpy as np
import random
import sys
import time
from os import name, system
from scipy.ndimage import convolve
np.set_printoptions(threshold=20000, linewidth=350, formatter={"str_kind": lambda x: x})
def _clear_screen():
if name == "nt":
system("cls")
else:
system("clear")
def _print_board(board, mark="0"):
_clear_screen()
to_print = np.where(board == 1, mark, " ")
print(to_print)
def _is_similar(board, next_board, cutoff):
return np.sum(board == next_board) / board.size > cutoff
@click.command()
@click.option(
"--seed",
"board_seed",
type=click.Choice(["random", "gliders"]),
default="random",
help="How to initialize the board, default random",
)
@click.option(
"-w",
"--width",
"board_width",
type=int,
default=75,
help="board width, default 101 columns",
)
@click.option(
"-h",
"--height",
"board_height",
type=int,
default=37,
help="board height, default 51 rows",
)
@click.option(
"--stop_after",
"number_of_iterations",
default=float("inf"),
help="Stop after this many iterations, default is no limit",
)
@click.option(
"--sleep",
"sleep_duration",
type=float,
default=0.25,
help="How long to sleep before updating state",
)
@click.option(
"--alive_mark",
"mark",
type=str,
default="•",
help="What mark to use for a living cell",
)
def run_game(
board_width, board_height, board_seed, number_of_iterations, sleep_duration, mark
):
global boards
# Convolving on this kernel does a count of cells around the center
kernel = np.array([[1, 1, 1],
[1, 0, 1],
[1, 1, 1]], dtype=np.uint8)
# A well-known game of life stable, moving character
glider = np.array([[0, 1, 0],
[0, 0, 1],
[1, 1, 1]], dtype=np.uint8)
if board_seed == "gliders":
board = np.zeros(shape=(board_height, board_width), dtype=np.uint8)
h, w = glider.shape
num_gliders = board.size // (9 * 25)
for _ in range(num_gliders):
i, j = (
random.randint(0, board_height - h),
random.randint(0, board_width - w),
)
board[i : i + h, j : j + w] = glider
else:
board = np.random.randint(
0, 2, size=(board_height, board_width), dtype=np.uint8
)
_print_board(board, mark=mark)
count = 0
cutoff = 1
while count < number_of_iterations:
time.sleep(sleep_duration)
count += 1
# Run a single 2D convolutional filter over the board with constant 0 padding
convolved_board = convolve(board, kernel, mode="wrap")
# The kernel we used finds the sum of the 8 cells around a given cell
# So we can do a bit of fancy numpy work to get the next board
next_board = (
((board == 1) & (convolved_board > 1) & (convolved_board < 4))
| ((board == 0) & (convolved_board == 3))
).astype(np.uint8)
if count % 10 == 0:
cutoff -= 0.001
_print_board(next_board, mark=mark)
print(
f"count: {count}, cutoff: {cutoff:0.3f}, diff: {np.sum(board == next_board)}/{board.size} ({np.sum(board == next_board)/board.size:0.4f})"
)
if _is_similar(board, next_board, cutoff):
sys.exit(0)
board = next_board
if __name__ == "__main__":
run_game()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment