Skip to content

Instantly share code, notes, and snippets.

@vyznev vyznev/make_lls_grid.py
Last active Feb 6, 2018

Embed
What would you like to do?
Generate Logic Life Search (LLS) input grid for finding spaceships and oscillators, with additional features not yet supported natively by LSS (like strobing background and gradually moving search area).
#!/usr/bin/python
import re
import sys
import argparse
def fail(msg):
sys.stderr.write(sys.argv[0] + ": " + msg + "\n")
sys.exit(1)
parser = argparse.ArgumentParser(description="Generate LLS input grid for finding spaceships and oscillators.")
parser.add_argument('width', type=int, help="width of pattern bounding box")
parser.add_argument('height', type=int, help="height of pattern bounding box")
parser.add_argument('-f', '--first-gen', type=int, nargs=2, help="width and height of first generation")
parser.add_argument('-p', '--period', type=int, default=2, help="period until first generation reappears")
parser.add_argument('-x', '--shift-x', type=int, default=0, help="horizontal shift over period")
parser.add_argument('-y', '--shift-y', type=int, default=0, help="vertical shift over period")
parser.add_argument('-m', '--mirror', choices=("", "none", "x", "y", "d", "diag", "a", "antidiag", "9", "90", "1", "180", "2", "270"), help="mirror or rotate pattern after period")
parser.add_argument('--fixed', nargs='+', default=[], help="fixed cell values in initial pattern (e.g. \"a1=1 a2=b1=b2=0\"")
parser.add_argument('--wide', action='store_true', help="allow intermediate generations to use the whole (width+x)*(height+y) cell area")
parser.add_argument('--strobe', action='store_true', help="use a strobing background for B0 rules")
args = parser.parse_args()
# validate pattern dimensions
if args.period < 1:
fail("Period must be positive.")
if args.strobe and args.period % 2:
fail("Period must be even for strobing rules.")
if args.width < 1 or args.height < 1:
fail("Width and height must be at least 1.")
if args.first_gen is None:
gen0_width = args.width
gen0_height = args.height
else:
gen0_width, gen0_height = args.first_gen
if gen0_width < 1 or gen0_height < 1:
fail("First generation width and height must be at least 1.")
if gen0_width > args.width or gen0_height > args.height:
fail("First generation width and height may not be greater than for later generations.")
total_width = args.width + 4 + abs(args.shift_x)
total_height = args.height + 4 + abs(args.shift_y)
# glide / rotate symmetry handling
mirror_x = args.mirror in ("x", "a", "antidiag", "9", "90", "1", "180")
mirror_y = args.mirror in ("y", "a", "antidiag", "1", "180", "2", "270")
transpose = args.mirror in ("d", "diag", "a", "antidiag", "9", "90", "2", "270")
if transpose and gen0_width != gen0_height:
fail("Only square grids are supported for --mirror=%s." % args.mirror)
# build dictionary of fixed cells
fixed = {}
for eqn in args.fixed:
eqn = eqn.strip()
if eqn == "": continue
terms = eqn.split("=")
if len(terms) < 2:
fail("Invalid equation \"%s\" in --fixed=\"%s\"." % (eqn, args.fixed))
val = terms.pop().strip()
if val != "0" and val != "1":
fail("Invalid constant \"%s\" in equation \"%s\"." % (val, eqn))
for term in terms:
term = term.strip()
if not re.match(r'^[a-z]+[1-9][0-9]*$', term):
fail("Invalid term \"%s\" in equation \"%s\"." % (term, eqn))
if term in fixed and fixed[term] != val:
fail("Inconsistent assignment for \"%s\" in --fixed=\"%s\"." % (term, args.fixed))
fixed[term] = val
# helper function for generating cell symbol names
def cellname(row, col):
name = ""
while col >= 0:
col, letter = divmod(col, 26)
name = chr(ord('a') + letter) + name
col -= 1
return name + str(row+1)
maxlen = max(2, len(cellname(args.width-1, args.height-1)))
cellfmt = "%%-%ss" % maxlen
for gen in range(args.period + 1):
# initialize grid to all empty cells
void = str(gen % 2) if args.strobe else "0"
grid = [[void] * total_width for i in range(total_height)]
# set fixed borders
for row in range(total_height):
grid[row][0] = grid[row][total_width-1] = void + "'"
for col in range(1, total_width-1):
grid[0][col] = grid[total_height-1][col] = void + "'"
# draw in the search area
if gen in (0, args.period):
dy = 2 + (args.height - gen0_height) // 2 + max(0, args.shift_y if gen else -args.shift_y)
dx = 2 + (args.width - gen0_width) // 2 + max(0, args.shift_x if gen else -args.shift_x)
for y in range(gen0_height):
for x in range(gen0_width):
row = y
col = x
if gen and mirror_y: row = gen0_height - row - 1
if gen and mirror_x: col = gen0_width - col - 1
if gen and transpose: row, col = col, row
name = cellname(row, col)
grid[y + dy][x + dx] = fixed[name] if name in fixed else name
else:
if args.wide:
top = left = 2
bottom = total_height - 2
right = total_width - 2
else:
top = 2 + int(round(args.shift_y * gen * 1.0 / args.period))
left = 2 + int(round(args.shift_x * gen * 1.0 / args.period))
bottom = top + args.height
right = left + args.width
for row in range(top, bottom):
for col in range(left, right):
grid[row][col] = '*'
# print output
if gen > 0:
print("")
for line in grid:
print(" ".join(cellfmt % cell for cell in line))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.