Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@theY4Kman
Forked from mikelane/game_of_life.py
Last active March 11, 2019 17:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save theY4Kman/ef5796cf1087f79c2312963d0151d668 to your computer and use it in GitHub Desktop.
Save theY4Kman/ef5796cf1087f79c2312963d0151d668 to your computer and use it in GitHub Desktop.
Conway's Game of Life implemented using a 2d convolution.
import random
import sys
import time
from io import StringIO
import click
import numpy as np
from asciimatics.event import MouseEvent
from asciimatics.screen import Screen
from scipy.ndimage import convolve
np.set_printoptions(threshold=20000, linewidth=350, formatter={"str_kind": lambda x: x})
@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,
)
screen = Screen.open(height=len(board) + 10) # extra space for summaries, etc
try:
next_board = board
_print_board(screen, next_board, mark=mark)
# Timestamp to begin processing the board after
# (allows us to stop the world for user input)
resume_board_at = 0
count = 0
cutoff = 1
while count < number_of_iterations:
count += 1
while True:
# Handle user input
event = screen.get_event()
# Allow user to add new cells
if event and isinstance(event, MouseEvent):
x = event.x // 2
y = event.y
if x < board_width and y < board_height:
board[y, x] = not board[y, x]
if board[y, x]:
screen.paint(mark, x=x * 2, y=y, colour=Screen.COLOUR_GREEN)
screen.refresh()
resume_board_at = time.monotonic() + 1.0
if time.monotonic() < resume_board_at:
screen.print_at(
f'Paused for interaction. Resuming in {resume_board_at - time.monotonic():.2f}s',
x=0,
y=board_height,
)
screen.refresh()
time.sleep(0.1)
else:
break
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(screen, next_board, mark=mark)
screen.print_at(
f"count: {count}, "
f"cutoff: {cutoff:0.3f}, "
f"diff: {np.sum(board == next_board)}/{board.size} ({np.sum(board == next_board)/board.size:0.4f})",
x=0,
y=len(board),
)
screen.refresh()
if _is_similar(board, next_board, cutoff):
sys.exit(0)
board = next_board
finally:
screen.close()
def _print_board(screen: Screen, board, mark="0"):
to_print = np.where(board == 1, mark, " ")
for y, row in enumerate(to_print):
buffer = StringIO()
np.savetxt(buffer, row, '%s', encoding='utf-8')
screen.print_at(buffer.getvalue(), 0, y)
def _is_similar(board, next_board, cutoff):
return np.sum(board == next_board) / board.size > cutoff
if __name__ == "__main__":
run_game()
scipy
numpy
click
asciimatics
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment