Skip to content

Instantly share code, notes, and snippets.

@bbjubjub2494
Last active November 10, 2017 13:21
Show Gist options
  • Save bbjubjub2494/95799b35e2d3aac54cdd0e4a7c8d2037 to your computer and use it in GitHub Desktop.
Save bbjubjub2494/95799b35e2d3aac54cdd0e4a7c8d2037 to your computer and use it in GitHub Desktop.
functional conway game of life using sets
#!/usr/bin/env python3
"""Python 3.6 KISS implementation of Conway's game of life
Inspired by Stop Writing Classes by Jack Diederich,
https://www.youtube.com/watch?v=o9pEzgHorH0
"""
from collections import defaultdict
from itertools import *
from typing import *
Point = Tuple[int, int]
def neighbours(c: Point) -> Sequence[Point]:
x, y = c
return [
(x-1,y-1),(x ,y-1),(x+1,y-1),
(x-1,y ), (x+1,y ),
(x-1,y+1),(x ,y+1),(x+1,y+1),
]
Grid = FrozenSet[Point]
def tick(grid: Grid) -> Grid:
ncount: Dict[Point, int] = defaultdict(int)
for c in grid:
for n in neighbours(c):
ncount[n] += 1
return frozenset(c for c, x in ncount.items()
if x == 2 and c in grid
or x == 3)
"""some cheap basic non-exhaustive correctness tests"""
block = frozenset({(0,0), (0,1), (1,0), (1,1)})
assert tick(block) == block
blinker1 = frozenset({(1,0), (1,1), (1,2)})
blinker2 = frozenset({(0,1), (1,1), (2,1)})
assert tick(blinker1) == blinker2
assert tick(blinker2) == blinker1
glider1 = frozenset({(0,0), (1,1), (1,2), (2,1), (2,0)})
glider3 = frozenset({(1,0), (2,1), (2,2), (3,1), (1,2)})
assert tick(tick(glider1)) == glider3
"""simple RLE parser
Not accounted for: the usual ! terminator for RLE-encoded patterns on the internet.
"""
import re
_RLE_ELT = re.compile(r'(\d*)(b|o)(\$?)')
def rle_parse(data: str) -> Grid:
return frozenset(_rle_parse(data))
def _rle_parse(data: str) -> Iterator[Point]:
pos = 0
x, y = 0, 0
for elt in iter(lambda: _RLE_ELT.match(data, pos), None):
rep = int(elt[1]) if elt[1] else 1
if elt[2] == 'o':
yield from zip(range(x, x+rep), repeat(y))
if elt[3]:
x, y = 0, y+1
else:
x += rep
pos = elt.end()
acorn = frozenset({(0,0), (1,0), (1,2), (3,1), (4,0), (5,0), (6,0)})
diehard = frozenset({(0,1), (1,0), (1,1), (5,0), (6,0), (6,2), (7,0)})
glider = rle_parse('bo$2bo$3o')
gosper_glider_gun = rle_parse('24bo$22bobo$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o$2o8bo3bob2o4bobo$10bo5bo7bo$11bo3bo$12b2o')
herschel = rle_parse('o$3o$obo$2bo')
lay = frozenset({(0,0), (2,0), (2,1), (4,2), (4,3), (4,4), (6,3), (6,4), (6,5), (7,4)})
lay = frozenset({(0,0), (0,1), (0,2), (0,4), (1,0), (2,3), (2,4), (3,1), (3,2), (3,4), (4,0), (4,2), (4,4)})
line = rle_parse('8ob5o3b3o6b7ob5o')
lwss = frozenset({(0,0), (0,3), (1,4), (2,0), (2,4), (3,1), (3,2), (3,3), (3,4)})
mess = frozenset({(0,3), (0,5), (1,4), (1,5), (2,4), (3,0), (3,1), (3,2), (4,2), (5,1)})
r_pentomino = frozenset({(0,1), (0,2), (1,0), (1,1), (2,1)})
toad = frozenset({(0,1), (0,2), (0,3), (1,0), (1,1), (1,2)})
import contextlib, curses, time, math
# A character represents two cells on top of each other.
# This maps (top_cell_alive, bottom_cell_alive) to how it can be displayed.
_draw_charmap = {
(False, False): b' ',
(True, False): '\u2580'.encode(),
(False, True): '\u2584'.encode(),
(True, True): '\u2588'.encode(),
}
def _draw(win, grid: Grid) -> None:
"""draw the grid on the curses window win"""
win.clear()
if not grid:
win.noutrefresh()
return
xs, ys = zip(*grid)
# maximum more like supremum
xmin, ymin, xmax, ymax = min(xs), min(ys)//2, max(xs)+1, max(ys)//2+1
Y, X = win.getmaxyx()
xhalf1, yhalf1 = X // 2, Y // 2
xhalf2, yhalf2 = X - xhalf1, Y - yhalf1
xmin, ymin = max(xmin, -xhalf1), max(ymin, -yhalf1)
xmax, ymax = min(xmax, xhalf2), min(ymax, yhalf2)
for winy, y in enumerate(range(ymin, ymax), yhalf1 + ymin):
y *= 2
for winx, x in enumerate(range(xmin, xmax), xhalf1 + xmin):
win.insstr(winy, winx, _draw_charmap[(x, y) in grid, (x, y+1) in grid])
win.noutrefresh()
class suppress(contextlib.suppress, contextlib.ContextDecorator):
pass
@curses.wrapper
@suppress(KeyboardInterrupt)
def main(stdscr):
FREQ = .2
curses.curs_set(0)
win = stdscr
Y, X = win.getmaxyx()
grid_win = win.derwin(Y-1, X, 0, 0)
stat_win = win.derwin(Y-1, 0)
grid = gosper_glider_gun
_draw(grid_win, grid)
stat_win.insstr('-- INIT --', curses.A_BOLD)
stdscr.refresh()
time.sleep(FREQ*4 - math.fmod(time.time(), FREQ))
for n in count(1):
grid = tick(grid)
time.sleep(FREQ - math.fmod(time.time(), FREQ))
_draw(grid_win, grid)
stat_win.clear()
stat_win.insstr(str(n).rjust(X))
stat_win.noutrefresh()
curses.doupdate()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment