So this is my first draft of a census script in Python, and it is what I'm planning on using for the online census program (see this thread). As such, there are a couple of things to note:
1) The script does not use a database of known objects, so it is pretty versatile for working in various rules.
2) The script is able to distinguish pretty well between close objects that do not interact (eg. a row of blinkers) and close objects that do interact (eg. it will count a block on table as one object). Because it's such a simple script though, it does sometimes group close but unrelated objects together.
3) If you have any suggestions for speed improvements or anything of the sort, please do let me know.
4) The output of the script is easiest to explain with an example (it uses the RLE format). {'2o$2o': 6, '3o': 4} as output would mean that the field has six blocks and four horizontal blinkers.
5) The script is painfully slow for very large censuses (such as those produced by HighLife after a replicator and some junk finally stabilizes).
Edit: I just realized that, as always, I didn't comment my code. The idea of the script is to loop through each cell on the field. If that cell has any other cells in its Moore neighbourhood, add those to the same "set" as the original cell. Repeat for any new cells that you add until you've exhausted that connected component. Now add any cells that are 2 cells away from at least 2 cells in the connected component you already found (this makes it possible to count disconnected objects like "toad" and "block on table" as single objects, while avoiding considering closely-spaced blinkers as single objects). Repeat these procedures until you can't anymore, and then store that set of cells as one object. Repeat until you've exhausted all the cells on the board.
Code: Select all
# A simple Python census script for use with Golly.
# Author: Nathaniel Johnston (nathaniel@nathanieljohnston.com), June 2009.
# v1.04
# Collects statistics regarding how many of each type of object are on the field.
# The input pattern is assumed to be oscillatory (can include escaping gliders/spaceships as well as oscillators/still lifes).
# Works with any Life-like rule (though not always as well as with regular Life)
# Is able to distinguish between close objects that do not interact (eg. blinkers with one dead cell between them)
# and close objects that do interact (eg. block on table) reasonably well.
# It will never mistakenly split up an oscillator into two subpieces
# It may occassionally mistakenly group two unrelated objects together as one
import golly as g
cens_list = []
clist = []
axx = [1,1,-1,-1,0,0,0,0]
ayy = [1,-1,1,-1,0,0,0,0]
axy = [0,0,0,0,1,1,-1,-1]
ayx = [0,0,0,0,1,-1,1,-1]
# --------------------------------------------------------------------
def getRLE(rl_list):
rle_res = ""
rle_len = 1
rl_list.sort(cmp = lambda x,y: (x[0]-y[0])+500*(x[1]-y[1]))
rl_y = rl_list[0][1] - 1
rl_x = 0
for rl_i in rl_list:
if rl_i[1] == rl_y:
if rl_i[0] == rl_x + 1:
rle_len += 1
else:
if rle_len == 1: rle_strA = ""
else: rle_strA = str (rle_len)
if rl_i[0] - rl_x - 1 == 1: rle_strB = ""
else: rle_strB = str (rl_i[0] - rl_x - 1)
rle_res = rle_res + rle_strA + "o" + rle_strB + "b"
rle_len = 1
else:
if rle_len == 1: rle_strA = ""
else: rle_strA = str (rle_len)
if rl_i[1] - rl_y == 1: rle_strB = ""
else: rle_strB = str (rl_i[1] - rl_y)
if rl_i[0] == 1: rle_strC = "b"
elif rl_i[0] == 0: rle_strC = ""
else: rle_strC = str (rl_i[0]) + "b"
rle_res = rle_res + rle_strA + "o" + rle_strB + "$" + rle_strC
rle_len = 1
rl_x = rl_i[0]
rl_y = rl_i[1]
if rle_len == 1: rle_strA = ""
else: rle_strA = str (rle_len)
rle_res = rle_res[2:] + rle_strA + "o"
return rle_res
# --------------------------------------------------------------------
def chunks(l, n):
for i in range(0, len(l), n):
yield tuple(l[i:i+n])
# --------------------------------------------------------------------
def census():
keylist = []
# Set cct_limit to the maximum number of generations that you want to look for oscillation
# when building the census. Can be made precise in conjunction with Golly's included oscar.py script
cct_limit = 10
cct = 1
cens = {}
clist = list (chunks (g.getcells (g.getrect()), 2))
cens_list = set(clist[:])
while cct <= cct_limit:
g.run(1)
clist = list (chunks (g.getcells (g.getrect()), 2))
cens_list = cens_list | set (clist)
cct += 1
cens_list = list(cens_list)
while len(cens_list) > 0:
wt = {}
curcells = [cens_list.pop(0)]
for j in curcells:
g.dokey( g.getkey() ) # allow keyboard interaction
wu = {}
tlist=cens_list[:]
for i in tlist:
c_dist = [abs(i[0] - j[0]), abs(i[1] - j[1])]
if (max(c_dist) == 2):
try:
if(wt[i] == 1):
curcells.append (i)
cens_list.remove (i)
except:
wt[i] = 1
wu[i] = 1
elif (max(c_dist) <= 1):
curcells.append (i)
cens_list.remove (i)
if(len(wu) >= 2):
for i in wu:
if not i in curcells:
curcells.append (i)
cens_list.remove (i)
tccells = curcells[:]
for j in tccells:
if not j in clist:
curcells.remove(j)
if len(curcells) > 0:
pllist = []
rlelist = []
for i in range(0,len(curcells)):
pllist.append(curcells[i][0])
pllist.append(curcells[i][1])
for i in range(0,8):
rotlist = list (chunks (g.transform(pllist,0,0,axx[i],axy[i],ayx[i],ayy[i]), 2))
mcc = min(rotlist)
rotlist = [[x[0]-mcc[0],x[1]-mcc[1]] for x in rotlist]
curRLE = getRLE(rotlist)
rlelist.append(curRLE)
intlist = list(set(keylist) & set(rlelist))
if(len(intlist) > 0):
cens[intlist[0]] += 1
else:
keylist.append(curRLE)
cens[curRLE] = 1
g.show (str (cens))
census()