Last active
July 28, 2020 12:01
-
-
Save vyznev/0ef7eb2ac98645b284dc8e647f10d685 to your computer and use it in GitHub Desktop.
Convert non-totalistic hexagonal CA rules to MAP rules
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 | |
""" | |
This script converts hexagonal isotropic (possibly non-totalistic) CA rules[1] | |
into the equivalent MAP rules[2]. It runs under both Python 2 and Python 3, and | |
can be used either as a stand-along script (with the rules given as command line | |
arguments) or as a Golly plug-in. | |
[1]: http://www.conwaylife.com/wiki/Isotropic_non-totalistic_Life-like_cellular_automaton#Hexagonal_neighbourhood | |
[2]: http://golly.sourceforge.net/Help/Algorithms/QuickLife.html | |
""" | |
import re | |
from base64 import b64encode | |
def hex2map (rulestr, mapbits=7): | |
""" | |
Convert a hexagonal isotropic (possibly non-totalistic) CA rule string | |
into the equivalent MAP rule. By default, the result will be a 3+22 | |
character hex MAP rule string; to generate a 3+86 character rectangular | |
MAP rule, use mapbits=9 instead of the default mapbits=7. | |
""" | |
# validate rule string format and extract the birth/survive strings | |
match = re.match( | |
"^\s*b((?:[0156]|[234]-?[omp]*)*)/s((?:[0156]|[234]-?[omp]*)*)h\s*$", | |
rulestr.lower() | |
) | |
if not match: | |
return None | |
# initialize rule structure (list of lists of dicts) | |
hexrule = [ [], [] ] | |
for array in hexrule: | |
for count in range(7): | |
array.append({"o": 0, "m": 0, "p": 0}) | |
# set birth/survive bits in rule based on input string | |
for oldstate in range(2): | |
nhoods = match.group(oldstate + 1) | |
for m in re.finditer("([0-6])(-?)([omp]*)", nhoods): | |
count = int(m.group(1)) | |
negate = bool(m.group(2)) | |
letters = m.group(3) | |
for letter in "omp": | |
newstate = 0 | |
if letter in letters or not letters: | |
newstate = 1 | |
if negate and letters: | |
newstate = 1 - newstate | |
hexrule[oldstate][count][letter] = newstate | |
# convert hex rule to map rule | |
maprule = bytearray(2**mapbits // 8) | |
for mapindex in range(2**mapbits): | |
bits = [ (mapindex >> i) & 1 for i in reversed(range(mapbits)) ] | |
# reorder bits in clockwise order around the center cell | |
if mapbits == 7: | |
neighbors = [bits[i] for i in (0,1,4,6,5,2)] | |
oldstate = bits[3] | |
elif mapbits == 9: | |
# ignore bits 2=NE and 6=SW | |
neighbors = [bits[i] for i in (0,1,5,8,7,3)] | |
oldstate = bits[4] | |
else: | |
raise NotImplementedError("only 7 and 9 bit MAPs are supported") | |
count = sum(neighbors) | |
# canonicalize neighbors so that the longest run of dead cells | |
# comes first and the longest run of live cells comes last | |
rotations = [neighbors, list(reversed(neighbors))] | |
while len(rotations) < 12: | |
neighbors.append(neighbors.pop(0)) | |
rotations += [neighbors, list(reversed(neighbors))] | |
neighbors = min(rotations) | |
# determine symmetry letter from longest run | |
# (this always yields "o" for 0/1/5/6 neighbors) | |
if count <= 3: | |
maxgap = 0 | |
while maxgap < 6 and not neighbors[maxgap]: | |
maxgap += 1 | |
letter = "omp"[6 - count - maxgap] | |
else: | |
maxrun = 0 | |
while maxrun < 6 and neighbors[5 - maxrun]: | |
maxrun += 1 | |
letter = "omp"[count - maxrun] | |
# set bit in map rule | |
if hexrule[oldstate][count][letter]: | |
maprule[mapindex // 8] |= (0x80 >> (mapindex % 8)) | |
# return base64 encoded map | |
return "MAP" + b64encode(maprule).decode('ascii').strip("=") | |
if __name__ == '__main__': | |
# looks like we're being run as a stand-alone script | |
import argparse | |
parser = argparse.ArgumentParser(description='Convert hex CA rule strings to MAP rules.') | |
parser.add_argument('rule', nargs='+', help='input hex rule (e.g. B245/S3H or B2o/S2m34H)') | |
parser.add_argument('-l', '--long-map', dest='mapbits', action='store_const', const=9, default=7, | |
help='generate 3+86 character instead of 3+22 character MAP rules') | |
args = parser.parse_args() | |
for hexrule in args.rule: | |
maprule = hex2map(hexrule, args.mapbits) | |
if maprule: | |
print(maprule) | |
else: | |
print(hexrule + " does not look like a valid hex rule. :(") | |
else: | |
# assume we're being run as a Golly plug-in | |
import golly as g | |
prompt = "Enter hex rule (e.g. B245/S3H or B2o/S2m34H):" | |
hexrule = "B2o/S2m34H" | |
while True: | |
hexrule = g.getstring(prompt, hexrule, "Set Hex Rule") | |
maprule = hex2map(hexrule) | |
if maprule: | |
g.setrule(maprule) | |
break | |
else: | |
prompt = "The string you entered does not appear to be a valid hex rule. Try again:" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment