Last active
August 9, 2019 08:39
-
-
Save vyznev/538447a872ce211f7f73aca4a50557b4 to your computer and use it in GitHub Desktop.
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).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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