Skip to content

Instantly share code, notes, and snippets.

@vyznev
Last active July 28, 2020 12:01
Show Gist options
  • Save vyznev/0ef7eb2ac98645b284dc8e647f10d685 to your computer and use it in GitHub Desktop.
Save vyznev/0ef7eb2ac98645b284dc8e647f10d685 to your computer and use it in GitHub Desktop.
Convert non-totalistic hexagonal CA rules to MAP rules
#!/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