Page 1 of 1

Niemiec numbering to apgcode converter

Posted: November 6th, 2016, 3:03 pm
by chris_c
By analysing the stamp collections with addresses of the form http://codercontest.com/mniemiec/stamp/p1-NN.rle I put together some Golly scripts that can convert between Mark Niemiec's still life numbering of the form POP.ID and apgcode.

The good news about the stamp collections is that they appear to consist of distinct still lifes except for a single error. Namely in the file p1-14-2.rle it looks like 14.266 (ie. 7th row 6th column) should be an appropriately long snake. It's fairly clear that this is a manual error caused by having to special-case still lifes of large height while keeping within the 15x10 grid.

Anyway here is some code. The first script asks for a Niemiec number or an apgcode and then displays the still life and both of its encodings. It only works up to 17 bits and requires the attached file "translate17.txt.gz" to be in the same directory:

Code: Select all

import golly as g
import gzip

chars = "0123456789abcdefghijklmnopqrstuvwxyz"

def decodeCanon(apgcode):
   
    blank = False
    x = y = 0
    clist = []
   
    for c in apgcode[apgcode.find("_")+1:]:
        if blank:
            x += chars.index(c)
            blank = False
        else:
            if (c == 'y'):
                x += 4
                blank = True
            elif (c == 'x'):
                x += 3
            elif (c == 'w'):
                x += 2
            elif (c == 'z'):
                x = 0
                y += 5
            else:
                v = chars.index(c)
                for i in range(5):
                    if v & (1 << i):
                        clist += [x, y+i]
                x += 1

    return clist

code = g.getstring("Enter code:")

for s in gzip.open("translate17.txt.gz"):
    niemiec, apg = s.split()
    if code == apg or code == niemiec:
        g.new('')
        g.putcells(decodeCanon(apg))
        g.show(s)
        g.exit()

g.show("Didn't find code")
The second script is based on the apgcode canoniser scripts. It looks at the current selection (or the entire universe if there is no selection) and calculates the apgcode assuming that we are looking at a still life. It then searches for the corresponding Niemiec number. If found it displays both. Otherwise it displays ?? and only the apgcode.

EDIT 22/11/16: Fixed a bug in this script when the selected SL has close neighbours.

Code: Select all

import golly as g
import gzip

# Obtains a canonical representation of any oscillator/spaceship that (in
# some phase) fits within a 40-by-40 bounding box. This representation is
# alphanumeric and lowercase, and so much more compact than RLE. Compare:
#
# Common name: pentadecathlon
# Canonical representation: 4r4z4r4
# Equivalent RLE: 2bo4bo$2ob4ob2o$2bo4bo!
#
# It is a generalisation of a notation created by Allan Weschler in 1992.
def canonise():

    representation = "#"

    if g.getselrect():
        g.shrink()
        rect = g.getselrect()
    else:
        rect = g.getrect()

    if len(rect) == 0:
        return "0"

    if ((rect[2] <= 40) & (rect[3] <= 40)):
        # Fits within a 40-by-40 bounding box, so eligible to be canonised.
        # Choose the orientation which results in the smallest description:
        representation = compare_representations(representation, canonise_orientation(rect[2], rect[3], rect[0], rect[1], 1, 0, 0, 1))
        representation = compare_representations(representation, canonise_orientation(rect[2], rect[3], rect[0]+rect[2]-1, rect[1], -1, 0, 0, 1))
        representation = compare_representations(representation, canonise_orientation(rect[2], rect[3], rect[0], rect[1]+rect[3]-1, 1, 0, 0, -1))
        representation = compare_representations(representation, canonise_orientation(rect[2], rect[3], rect[0]+rect[2]-1, rect[1]+rect[3]-1, -1, 0, 0, -1))
        representation = compare_representations(representation, canonise_orientation(rect[3], rect[2], rect[0], rect[1], 0, 1, 1, 0))
        representation = compare_representations(representation, canonise_orientation(rect[3], rect[2], rect[0]+rect[2]-1, rect[1], 0, -1, 1, 0))
        representation = compare_representations(representation, canonise_orientation(rect[3], rect[2], rect[0], rect[1]+rect[3]-1, 0, 1, -1, 0))
        representation = compare_representations(representation, canonise_orientation(rect[3], rect[2], rect[0]+rect[2]-1, rect[1]+rect[3]-1, 0, -1, -1, 0))
        
    return "xs%d_%s" % (len(g.getcells(rect)) // 2, representation)

# A subroutine used by canonise:
def canonise_orientation(length, breadth, ox, oy, a, b, c, d):

    representation = ""

    chars = "0123456789abcdefghijklmnopqrstuvwxyz"

    for v in xrange(0, breadth, 5):
        zeroes = 0
        if (v != 0):
            representation += "z"
        for u in xrange(length):
            baudot = 0
            for w in xrange(v, v+5):
                x = ox + a*u + b*w
                y = oy + c*u + d*w
                baudot = (baudot >> 1)
                if w < breadth: baudot += 16*g.getcell(x, y)
            if (baudot == 0):
                zeroes += 1
            else:
                if (zeroes > 0):
                    if (zeroes == 1):
                        representation += "0"
                    elif (zeroes == 2):
                        representation += "w"
                    elif (zeroes == 3):
                        representation += "x"
                    else:
                        representation += "y"
                        representation += chars[zeroes - 4]
                zeroes = 0
                representation += chars[baudot]
    return representation

# Compares strings first by length, then by lexicographical ordering.
# A hash character is worse than anything else.
def compare_representations(a, b):

    if (a == "#"):
        return b
    elif (b == "#"):
        return a
    elif (len(a) < len(b)):
        return a
    elif (len(b) < len(a)):
        return b
    elif (a < b):
        return a
    else:
        return b

apgcode = canonise()

for s in gzip.open("translate17.txt.gz"):
    niemiec, apg = s.split()
    if apgcode == apg:
        g.show(s)
        g.exit()

g.show("?? " + apgcode)
Don't forget that you need to make sure that the above scripts can find the file "translate17.txt.gz". I'm not sure of the best way of guaranteeing this. Putting the script and the file in the same directory works for me.

EDIT 22/11/16: The attachment translate17.txt.gz has been updated due to a bug when canonicalising closely separated still lifes.

Re: Niemiec numbering to apgcode converter

Posted: November 22nd, 2016, 11:02 am
by chris_c
I have edited the above post due to the discovery of a bug. I overlooked the fact that the canonise_orientation function from the original apgsearch can inspect cells that are outside a rectangle of size length x breadth if breadth is not a multiple of 5.

This caused misclassification of a handful of still lifes and so the attachment translate17.txt.gz has been updated.

Also the second script in the above post suffered from the same problem and has been edited.