Golly scripts

For scripts to aid with computation or simulation in cellular automata.
Post Reply
User avatar
gameoflifeboy
Posts: 474
Joined: January 15th, 2015, 2:08 am

Re: Golly scripts

Post by gameoflifeboy » June 3rd, 2015, 1:34 am

I just completed a script that makes a list of URLs for hauls that are stored on your computer:

Code: Select all

# This script goes through your progress files and makes a list of catagolue pages for all hauls.
# It is recommended to remove any pre-catagolue progress files before running this script.

import glob
import golly as g

my_hauls = g.getdir('data') + 'my-hauls.txt'
hauls = open(my_hauls, 'r')
currhauls = hauls.readlines()
numhauls = len(currhauls)
for num in range(numhauls):
    currhauls[num] = currhauls[num].rstrip()
hauls.close()

def getrule(path):
    progfile = open(path, 'r')
    lines = progfile.readlines()
    if len(lines) < 7:
        g.warn("Empty progress file found (%s)" % path)
        return ''
    ruledecl = lines[3].rstrip()
    if ruledecl[:6] == '@RULE ':
        return ruledecl.strip('@RULE ').lower()
    else:
        g.warn("Progress file %s was formatted wrong. (possibly due to incompatible version of apgsearch)" % path)
        return ''
    progfile.close()

def getsym(path):
    progfile = open(path, 'r')
    lines = progfile.readlines()
    if len(lines) < 7:
        # it already warned about this earlier
        return ''
    symdecl = lines[4].rstrip()
    if symdecl[:10] == '@SYMMETRY ':
        return symdecl.strip('@SYMMETRY ')
    else:
        g.warn("Progress file %s was formatted wrong. (possibly due to incompatible version of apgsearch)" % path)
        return ''

progress = g.getdir('data') + 'apgsearch/progress/'
haullist = glob.glob(progress + 'search_*')
hauls = open(my_hauls, 'a')
newhauls = []
for path in haullist:
    haul_hash = path.replace(progress + 'search_', '').replace('.txt', '')
    rule = getrule(path)
    sym = getsym(path)
    if (rule == '' or sym == ''):
        continue
    haulurl = 'http://catagolue.appspot.com/haul/' + rule + '/' + sym + '/' + haul_hash
    if haulurl not in currhauls:
        hauls.write(haulurl + '\n')
        newhauls.append(haulurl)

hauls.close()
htmlfile = g.getdir('temp') + 'newhauls.html'
f = open(htmlfile, 'w')
f.write("<html><title>New hauls</title>\n<body>\n")
if newhauls == []:
    f.write("<p>No new hauls made since last scan.</p>\n")
else:
    f.write("<p>New hauls have been made:<br>\n")
    for url in newhauls:
        f.write("<a href = \"" + url + "\">" + url + "</a><br>\n")
    f.write("</p>\n")
f.write("<p>All haul pages are saved in <a href = \"" + my_hauls + "\">" + my_hauls + "</a>.</p>\n")
f.write("</body></html>")
f.close()
g.open(htmlfile)
EDIT: Fixed it.
Last edited by gameoflifeboy on June 3rd, 2015, 9:15 pm, edited 1 time in total.

wildmyron
Posts: 1544
Joined: August 9th, 2013, 12:45 am
Location: Western Australia

Re: Golly scripts

Post by wildmyron » June 3rd, 2015, 2:50 am

gmc_nxtman wrote:I've always wanted a string to table converter. Can someone make a good string to table converter, possibly with support for vonNeumannNeighborhood rules and hexagonalNeighborhood rules? Nothing too fancy, just gives you a box to input your rule string, and gives you a string of text to paste into a .table file.... :roll:
There are a number of existing scripts which generate rule table or rule tree files for various different sets of CA.
Non-totalistic Rules and Weighted Life (Scripts) has four such scripts, each tailored to different sets of neighbourhoods, including hexagonal generations. Another script by EricG converts hexagonal rules without generations and additionally writes to a .rule format.

There is also the rule generator in apgsearch which creates a set of auxillary rule files using rule tables for conducting a census on totalistic rules. The CoalesceObjects rules are three state rules which evolve with the same behaviour as the two state rule, but with the addition of a history state. That would be a good starting point if you are interested in modifying something to get precisely what you want.
The 5S project (Smallest Spaceships Supporting Specific Speeds) is now maintained by AforAmpere. The latest collection is hosted on GitHub and contains well over 1,000,000 spaceships.

Semi-active here - recovering from a severe case of LWTDS.

User avatar
gmc_nxtman
Posts: 1150
Joined: May 26th, 2015, 7:20 pm

Re: Golly scripts

Post by gmc_nxtman » June 3rd, 2015, 3:13 pm

Those are really cool scripts, but all I need is a script which takes in a life-like rulestring, and turns it into a table of the exact same rule. Also, is there some script, which upon input of some oscillator and its period, generates how many possible collisions there are with a single glider, and shows them in a table? I would like to find tumbler-glider reactions, to reintroduce the possibility of a true-period 14 gun, and once I find the suitable reactions I might start.
Maybe a true-period 15 gun could be built with results from pd-glider.py. I might do that..Other scripts I would want are a polyomino calculator/displayer, and an inverse-rule generator.

Universal life file converters would also be convenient. These are the ones I would need:

Life 1.06 to RLE
Life 1.05 to RLE
RLE to SoF
SoF to MC
Last edited by gmc_nxtman on June 28th, 2015, 11:50 pm, edited 1 time in total.

User avatar
Gustavo6046
Posts: 647
Joined: December 7th, 2013, 6:26 pm
Location: Brazil.

Re: Golly scripts

Post by Gustavo6046 » June 14th, 2015, 4:15 pm

I need a script to find in minutes (max. one hour or half) oscillators that generate far and usable sparks (which don't break the pattern with interference) or at least YOU recommend some...
*yawn* What a nothing-to-do day! Let's be the only person in the world to do CGOL during boring times. :)

User avatar
Kazyan
Posts: 1247
Joined: February 6th, 2014, 11:02 pm

Re: Golly scripts

Post by Kazyan » June 15th, 2015, 6:39 pm

Gustavo6046 wrote:I need a script to find in minutes (max. one hour or half) oscillators that generate far and usable sparks (which don't break the pattern with interference) or at least YOU recommend some...
We have a lot of oscillators, so we may already have something that fits. Here's the p46 shuttle:

Code: Select all

x = 29, y = 12, rule = B3/S23
4bo$2b5o10bo$bo2bob2o9b2o$o7bo9b2o$bo2bob2o5b2o2b2o$2b5o$4bo2$13b2o2b
2o$2o16b2o7b2o$2o15b2o8b2o$17bo!
What period do you want the oscillator to be, or does it not matter?
Tanner Jacobi
Coldlander, a novel, available in paperback and as an ebook. Now on Amazon.

User avatar
gameoflifeboy
Posts: 474
Joined: January 15th, 2015, 2:08 am

Re: Golly scripts

Post by gameoflifeboy » April 20th, 2016, 1:52 am

I made a script to discover which isotropic CA is the most like Life.

First make a random soup in Golly, then run the script and it will display a census of all 3x3 blocks appearing in the pattern for the specified number of generations.

When running large soups for 10 generations, I discovered that this is the rarest 3x3 square of cells to occur:

Code: Select all

o.o
.o.
o.o
This suggests that the isotropic rule most "similar" to Life is B3S234c. B38S23 is a close second.

Anyway, here is the script:

Code: Select all

#this one runs a lifepattern for a specified number of generations and censuses the 3x3 blocks of cells appearing from most common to rarest in a text file

import golly as g

neighbor_census = []

def cycle2(list9):
	return [list9[0], list9[3], list9[4], list9[5], list9[6], list9[7], list9[8], list9[1], list9[2]]

def perms(list9):
	clist9 = cycle2(list9)
	cclist9 = cycle2(clist9)
	ccclist9 = cycle2(cclist9)
	revlist9 = [list9[0], list9[1], list9[8], list9[7], list9[6], list9[5], list9[4], list9[3], list9[2]]
	crevlist9 = cycle2(revlist9)
	ccrevlist9 = cycle2(crevlist9)
	cccrevlist9 = cycle2(ccrevlist9)
	return [list9, clist9, cclist9, ccclist9, revlist9, crevlist9, ccrevlist9, cccrevlist9]

def comparelists(l1, l2):
	for index in range(9):
		if l1[index] < l2[index]:
			return -1
		if l1[index] > l2[index]:
			return 1
	#they're the same
	return 1

#returns the binary minimum isotropic permutation of a neighbor list.
def transform(neighbors):
	permutations = perms(neighbors)
	permutations.sort(cmp=comparelists)
	return permutations[0]

first = lambda x: x[1]

def census(neighbor_list):
	for lis in neighbor_list:
		tlis = transform(lis)
		found = False
		for index in range(len(neighbor_census)):
			if neighbor_census[index][0] == tlis:
				neighbor_census[index][1] += 1
				found = True
				break
		if not found:
			neighbor_census.append([tlis, 1])

def censusgen():
	neighbor_list = []
	bbox = g.getrect()
	for y in range(bbox[1], bbox[1] + bbox[3]):
		for x in range(bbox[0], bbox[0] + bbox[2]):
			neighbor_list.append([g.getcell(x, y), g.getcell(x, y-1), g.getcell(x+1, y-1), g.getcell(x+1, y), g.getcell(x+1, y+1), g.getcell(x, y+1), g.getcell(x-1, y+1), g.getcell(x-1, y), g.getcell(x-1, y-1)])
	census(neighbor_list)

def subst(num):
	if num == 0:
		return '.'
	else:
		return 'o'

def neighbors_string(neighbors):
	p_n = []
	for num in neighbors:
		p_n.append(subst(num))
	p_str = p_n[8] + p_n[1] + p_n[2] + '\n'
	p_str += p_n[7] + p_n[0] + p_n[3] + '\n'
	p_str += p_n[6] + p_n[5] + p_n[4] + '\n'
	return p_str

ngens = int(g.getstring("How many generations do you want to census your pattern?"))

for gen in range(ngens):
	censusgen()
	g.step()

neighbor_census.sort(key=first, reverse=True)

cens = g.getdir('temp') + 'cens.txt'

f = open(cens, 'w')

f.write("Here are all the 3x3 squares that occurred, in order of frequency:\n\n")

for pair in neighbor_census:
	f.write(neighbors_string(pair[0]))
	f.write(str(pair[1]))
	f.write('\n')

f.close()

g.open(cens)

drc
Posts: 1664
Joined: December 3rd, 2015, 4:11 pm

Re: Golly scripts

Post by drc » April 20th, 2016, 4:33 pm

gameoflifeboy wrote:the quote
I was actually thinking about this

drc
Posts: 1664
Joined: December 3rd, 2015, 4:11 pm

Re: Golly scripts

Post by drc » April 20th, 2016, 5:53 pm

I tested a few CAs for 100 generations on a 100x100 soup. B4c, B8, S4c and S8 are rarest for lots of rules.

D&N:

Code: Select all

o.o
...
o.o
2x2:

Code: Select all

ooo
o.o
ooo
LFOD:

Code: Select all

ooo
ooo
ooo
Seeds:

Code: Select all

ooo
ooo
ooo
B3/S234:

Code: Select all

ooo
ooo
ooo
Morley:

Code: Select all

ooo
o.o
ooo
Dotlife:

Code: Select all

o.o
.o.
o.o
Longlife:

Code: Select all

ooo
ooo
ooo
Maze:

Code: Select all

ooo
ooo
ooo

shouldsee
Posts: 406
Joined: April 8th, 2016, 8:29 am

Re: Golly scripts

Post by shouldsee » April 24th, 2016, 6:52 am

How do I measure cleanness (of puffers) in multi-state rules?

update:

I made a such attempt in rule "flashbf8df8". This updated pufferSea3.py now distinguish puffers from burning ones.

Puffers detected by default config:

Code: Select all

#CXRLE Pos=-13,0 Gen=144
x = 25, y = 23, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A2$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A2$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-13,0 Gen=108
x = 25, y = 25, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A2$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A4$8.A$7.4A$6.9A$5.6A.2A.2A$6.9A$7.4A$8.A!

#CXRLE Pos=-18,0 Gen=108
x = 30, y = 25, rule = flashbf8df8
21.A$20.4A$19.9A$18.6A.2A.2A$19.9A$20.4A$21.A2$8.A$7.4A$6.9A$5.6A.2A.
2A$6.9A$7.4A$8.A4$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-23,0 Gen=216
x = 35, y = 29, rule = flashbf8df8
26.A$25.4A$24.9A$23.6A.2A.2A$24.9A$25.4A$26.A2$13.A$12.4A$11.9A$10.6A
.2A.2A$11.9A$12.4A$13.A8$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-13,0 Gen=216
x = 25, y = 26, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A2$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A5$5.A$4.4A$3.9A$2.6A.2A.2A$3.9A$4.4A$5.A!

#CXRLE Pos=-15,0 Gen=216
x = 27, y = 26, rule = flashbf8df8
18.A$17.4A$16.9A$15.6A.2A.2A$16.9A$17.4A$18.A2$5.A$4.4A$3.9A$2.6A.2A.
2A$3.9A$4.4A$5.A5$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-13,0 Gen=180
x = 25, y = 26, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A2$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A5$9.A$8.4A$7.9A$6.6A.2A.2A$7.9A$8.4A$9.A!

#CXRLE Pos=-19,0 Gen=144
x = 31, y = 26, rule = flashbf8df8
22.A$21.4A$20.9A$19.6A.2A.2A$20.9A$21.4A$22.A2$9.A$8.4A$7.9A$6.6A.2A.
2A$7.9A$8.4A$9.A5$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-19,0 Gen=108
x = 31, y = 27, rule = flashbf8df8
22.A$21.4A$20.9A$19.6A.2A.2A$20.9A$21.4A$22.A3$9.A$8.4A$7.9A$6.6A.2A.
2A$7.9A$8.4A$9.A5$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-16,0 Gen=180
x = 28, y = 26, rule = flashbf8df8
19.A$18.4A$17.9A$16.6A.2A.2A$17.9A$18.4A$19.A2$6.A$5.4A$4.9A$3.6A.2A.
2A$4.9A$5.4A$6.A5$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-13,0 Gen=360
x = 25, y = 25, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A2$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A4$10.A$9.4A$8.9A$7.6A.2A.2A$8.9A$9.4A$10.A!

#CXRLE Pos=-20,0 Gen=432
x = 32, y = 25, rule = flashbf8df8
23.A$22.4A$21.9A$20.6A.2A.2A$21.9A$22.4A$23.A2$10.A$9.4A$8.9A$7.6A.2A
.2A$8.9A$9.4A$10.A4$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-13,0 Gen=288
x = 25, y = 24, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A2$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A3$6.A$5.4A$4.9A$3.6A.2A.2A$4.9A$5.4A$6.A!

#CXRLE Pos=-16,0 Gen=360
x = 28, y = 24, rule = flashbf8df8
19.A$18.4A$17.9A$16.6A.2A.2A$17.9A$18.4A$19.A2$6.A$5.4A$4.9A$3.6A.2A.
2A$4.9A$5.4A$6.A3$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-13,0 Gen=1008
x = 25, y = 28, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A2$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A7$7.A$6.4A$5.9A$4.6A.2A.2A$5.9A$6.4A$7.A!

#CXRLE Pos=-13,0 Gen=216
x = 25, y = 26, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A2$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A5$6.A$5.4A$4.9A$3.6A.2A.2A$4.9A$5.4A$6.A!

#CXRLE Pos=-1051,8
x = 50, y = 20, rule = flashbf8df8
40.A$12.A27.2A4.2A$11.4A27.3A3.A$10.9A.4A14.3A.A.A4.A$9.6A.2A.3A2.A
13.A.A.A2.B.B.A$10.9A.4A14.3A.A.A4.A$11.4A27.3A3.A$12.A21.2A4.2A4.2A$
33.A2.A3.A$34.2A2$31.A$3.A27.2A$2.4A27.4A.2A$.9A.4A14.3A.A.A4.A$6A.2A
.3A2.A13.A.A.A2.B.B.A$.9A.4A14.3A.A.A4.A$2.4A27.3A3.A$3.A27.2A4.2A$
31.A!

#CXRLE Pos=-24,0 Gen=252
x = 36, y = 27, rule = flashbf8df8
27.A$26.4A$25.9A$24.6A.2A.2A$25.9A$26.4A$27.A3$14.A$13.4A$12.9A$11.6A
.2A.2A$12.9A$13.4A$14.A5$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-18,0 Gen=864
x = 30, y = 27, rule = flashbf8df8
21.A$20.4A$19.9A$18.6A.2A.2A$19.9A$20.4A$21.A3$8.A$7.4A$6.9A$5.6A.2A.
2A$6.9A$7.4A$8.A5$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-19,0 Gen=1296
x = 31, y = 26, rule = flashbf8df8
22.A$21.4A$20.9A$19.6A.2A.2A$20.9A$21.4A$22.A3$9.A$8.4A$7.9A$6.6A.2A.
2A$7.9A$8.4A$9.A4$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-21,0 Gen=1296
x = 33, y = 25, rule = flashbf8df8
24.A$23.4A$22.9A$21.6A.2A.2A$22.9A$23.4A$24.A2$11.A$10.4A$9.9A$8.6A.
2A.2A$9.9A$10.4A$11.A4$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-25,0 Gen=288
x = 37, y = 26, rule = flashbf8df8
28.A$27.4A$26.9A$25.6A.2A.2A$26.9A$27.4A$28.A2$15.A$14.4A$13.9A$12.6A
.2A.2A$13.9A$14.4A$15.A5$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-13,0 Gen=1008
x = 25, y = 28, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A3$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A6$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-13,0 Gen=432
x = 25, y = 30, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A3$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A8$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-25,0 Gen=2160
x = 37, y = 26, rule = flashbf8df8
28.A$27.4A$26.9A$25.6A.2A.2A$26.9A$27.4A$28.A3$15.A$14.4A$13.9A$12.6A
.2A.2A$13.9A$14.4A$15.A4$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-689,-5
x = 50, y = 39, rule = flashbf8df8
47.3A2$47.A$48.A$45.3A$19.A24.A.A.A$18.5A2.2A16.A.B2.A$17.11A12.2A2.A
3.A$16.7A.A3.A11.2A2.A2.2A$17.11A14.A3.4A$18.5A2.2A9.2A2.A.A5.A$19.A
15.2A3.A.A2.A2.2A$34.3A2.2A2.3A$34.2A3.A4.A$6.A32.2A$5.5A2.2A24.A2.A$
4.11A8.A13.2A.A$3.7A.A3.A5.2A.2A11.2A.A.A$4.11A8.A16.3A$5.5A2.2A$6.A
3$3.A$2.5A2.2A$.11A8.A13.3A4.2A5.2A$7A.A3.A5.2A.2A17.A2.A2.3A$.11A8.A
20.2A5.2A$2.5A2.2A24.2A$3.A28.A3.A$32.A4.A$32.A3.A$33.A2.A$33.3A13.A$
49.A$46.A$45.A.A$45.A.A$46.A!

#CXRLE Pos=-1089,9
x = 50, y = 21, rule = flashbf8df8
14.A23.2A$13.4A20.A2.A$12.9A.4A4.A7.2A$11.6A.2A.3A2.A2.A.A17.A$12.9A.
4A3.2A$13.4A$14.A$47.3A$25.A21.2A$3.A20.A.A22.A$2.4A17.A3.A$.9A.4A8.A
3.A5.2A3.A10.A$6A.2A.3A2.A7.A3.A5.A4.A9.2A$.9A.4A9.A.A12.A8.2A$2.4A
19.A6.2A4.A$3.A28.2A2.B$36.B.A$33.A2.B$33.A4.A$33.A4.A$35.3A!

#CXRLE Pos=-24,0 Gen=864
x = 36, y = 26, rule = flashbf8df8
27.A$26.4A$25.9A$24.6A.2A.2A$25.9A$26.4A$27.A2$14.A$13.4A$12.9A$11.6A
.2A.2A$12.9A$13.4A$14.A5$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-13,0 Gen=4608
x = 25, y = 28, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A2$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A7$9.A$8.4A$7.9A$6.6A.2A.2A$7.9A$8.4A$9.A!

#CXRLE Pos=-13,0 Gen=3240
x = 25, y = 29, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A3$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A7$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!

#CXRLE Pos=-13,0 Gen=3240
x = 25, y = 26, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A2$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A5$10.A$9.4A$8.9A$7.6A.2A.2A$8.9A$9.4A$10.A!
	
#CXRLE Pos=-13,0 Gen=108
x = 25, y = 25, rule = flashbf8df8
16.A$15.4A$14.9A$13.6A.2A.2A$14.9A$15.4A$16.A2$3.A$2.4A$.9A$6A.2A.2A$
.9A$2.4A$3.A4$3.A$2.4A$.9A$6A.2A.2A$.9A$2.4A$3.A!
pufferSea3.py

Code: Select all

import golly as g
from glife import *
import random
from time import time
def add_ship(x,y):
	g.putcells(ship, x, y, 1, 0, 0, 1, "or")


def add_ships(x_1,y_1,x_2,y_2):
	g.select([-10,10,20,20])
	g.clear(1)
	g.clear(0)
	add_ship(0,0)
	add_ship(x_1,y_1)
	add_ship(x_1+x_2,y_1+y_2)


def custom_run(numstep,delay=0.1):
	newsecs=time()
	i=0
	while time()-newsecs<delay:
		g.run(1)
		i=i+1
		if g.empty(): break
		if i==numstep:	return True
		newsecs=time()

	return False

def popcount(rectcoords=g.getrect()):
	return int(len(g.getcells(rectcoords))/3)

	
def headbox():
	headbox=g.getrect()
	headbox[2]=50
	headbox[1]=-40
	headbox[3]=80
	return(headbox)

def oscillating():
    global period,popincr,rate
	# return True if the pattern is empty, stable or oscillating

    # first get current pattern's bounding box
    prect = head
    pbox = rect(prect)
    if pbox.empty:
        g.show("The pattern is empty.")
        return True

    # get current pattern and create hash of "normalized" version -- ie. shift
    # its top left corner to 0,0 -- so we can detect spaceships and knightships
    ## currpatt = pattern( g.getcells(prect) )
    ## h = hash( tuple( currpatt(-pbox.left, -pbox.top) ) )

    # use Golly's hash command (3 times faster than above code)
    h = g.hash(prect)

    # check if outer-totalistic rule has B0 but not S8
    # rule = g.getrule().split(":")[0]
    # hasB0notS8 = rule.startswith("B0") and (rule.find("/") > 1) and not rule.endswith("8")

    # determine where to insert h into hashlist
    pos = 0
    listlen = len(hashlist)
    while pos < listlen:
        if h > hashlist[pos]:
            pos += 1
        elif h < hashlist[pos]:
            # shorten lists and append info below
            del hashlist[pos : listlen]
            del genlist[pos : listlen]
            del poplist[pos : listlen]
            # del boxlist[pos : listlen]
            break
        else:
            # h == hashlist[pos] so pattern is probably oscillating, but just in
            # case this is a hash collision we also compare pop count and box size
			
			if (int(popcount(head)) - poplist[pos])==0:
				period = int(g.getgen()) - genlist[pos]
				bighead=head
				bighead[2]=bighead[2]+period
				popincr=int(popcount(bighead))-poplist[pos]
				rate=float(popincr)/float(period)
				if  (period % 2 > 0) :
					return False
				if  period == 1:
					if True:
						g.show("The pattern is stable.")
					else:
						show_spaceship_speed(1, 0, 0)
				elif True:
					g.show("Oscillator detected (period = " + str(period) + ")")
				return True
			else:
				# look at next matching hash value or insert if no more
				pos += 1

    # store hash/gen/pop/box info at same position in various lists
    hashlist.insert(pos, h)
    genlist.insert(pos, int(g.getgen()))
    poplist.insert(pos, int(popcount(head)))
    # boxlist.insert(pos, pbox)

    return False

def list_reset():
	global hashlist,poplist,genlist,boxlist
	hashlist=[]
	poplist=[]
	boxlist=[]
	genlist=[]	
	slist=[]

def fit_if_not_visible():
    try:
        r = rect(g.getrect())
        if (not r.empty) and (not r.visible()): g.fit()
    except:
        # getrect failed because pattern is too big
        g.fit()

g.setrule("flashbf8df8")
trimlim=int(g.getstring("How many trims allowed at most?","3"))
ship=g.parse(g.getstring("RLE string for your seed","3bo$2b4o$b9o$6ob2ob2o$b9o$2b4o$3bo!"))
wd=11
ht=14

wd=13
ht=8
ptn=0
foldername=g.getstring("folder name","temp")
# last=[-9,9,-2,9]
last0=g.getstring("last stopped at?","-50 -50 -50 -50").split()
last=[0]*4
for i in range(4):last[i]=int(last0[i])
# g.note(str(last))
#last=[-9,9,-10,11]
period=0
lowestperiod=int(g.getstring("loweset period to keep?","36"))
slist=[]



def add_section():
	global slist,section,slistold,l
	slistold=slist
	slist+=[0,0,period/2,0]
	l=len(slist)/4	


def map_sections(slist=slist):
	sectionlist=[0]*len(slist)
	for i in range(len(slist)/4):
		if i==0:
			sectionlist[4*i]=head[0]+head[2]
		else:
			sectionlist[4*i]=sectionlist[4*(i-1)]+period/2
		sectionlist[4*i+1]=head[1]
		sectionlist[4*i+2]=period/2
		sectionlist[4*i+3]=head[3]
	return sectionlist

		

def adjust_head():
	old=pattern(g.getcells(g.getrect()))

	
# COMPARRE FLYING INVARIANCE OF S1



def length_puffer(max=10):
	global rate,head
	slist=[]
	snum=0
	add_section()
	g.setgen("0")	
	initial=1
	while int(g.getgen())<max*period:
		head=headbox()	
		sections=map_sections()
		sirect=sections[4*snum:4*(snum+1)]
		# g.note(str(sirect))
		sPt=g.getcells(sirect)
		try:
			hash=g.hash(sirect)
		except:
			g.note("can't hash %s \n sections=%s"%(str(sirect),str(sections)))
		if initial:
			initial=0
		else:
			if snum>=2:
				sim1rect=sections[4*(snum-1):4*(snum)]
				if g.hash(sirect)==g.hash(sim1rect):
					if int(len(g.getcells(sirect))/3)==int(len(g.getcells(sim1rect))/3):
						snum=snum-1
						rate=float(int(len(g.getcells(sirect))/3))/float(period)
						g.select(sirect)
						g.show("sections="+str(sections))
						# g.exit()
						return snum
			if hash==hash_old:
				if int(len(sPt)/3)==int(len(sPt_old)/3):
					snum=snum+1
					add_section()
					initial=1

					continue

		
		sPt_old=sPt
		hash_old=hash
		g.run(period)
		if g.empty():
			break
	return -1





for x_1 in range(-wd,0):
	for y_1 in range(ht,ht*2):
		for x_2 in range(-wd,ht):
			for y_2 in range(ht,ht*2):
				if x_1<=last[0] :
					if y_1<=last[1]:
						if x_2<=last[2]:
							if y_2<=last[3]:
								continue
				add_ships(x_1,y_1,x_2,y_2)
				list_reset()
				custom_run(50)
				trimnum=0
				head=headbox()
				osci=oscillating()

				while trimnum!=trimlim+1 and not osci :
					g.show("%i stored, trimnum=%i, oscillating=%i,period=%i"%(ptn,trimnum,osci,period))	
					fit_if_not_visible()
					before=time()
					g.run(2)
					after=time()
					delay=after-before
					
					if g.empty(): break
					head=headbox()
					osci=oscillating()
					if delay>=0.05:
						g.select(head)
						temp=g.getcells(head)
						g.clear(1)
						trimnum=trimnum+1
						list_reset()

				if trimnum==trimlim+1 or g.empty() or period <lowestperiod:
					continue				
				puffer_len=length_puffer()
			
				if puffer_len!=-1:
					filename=g.getdir("patterns")+"/%s/gppPercent%i_puffer%ip_%s_%s_%s_%s_p%i.rle"%(foldername,int(100*rate),puffer_len,x_1,y_1,x_2,y_2,period)
				else:
					filename=g.getdir("patterns")+"/%s/gppPercent%i_burning_%s_%s_%s_%s_p%i.rle"%(foldername,int(100*rate),x_1,y_1,x_2,y_2,period)
				
				try:					
					if trimnum==0:
						add_ships(x_1,y_1,x_2,y_2)
						g.save(filename,"rle",True)
					else:
						g.store(temp,filename,"rle")
					ptn=ptn+1
				except:
					g.note("can't store")					
				
				


					

User avatar
Scorbie
Posts: 1692
Joined: December 7th, 2013, 1:05 am

Re: Golly scripts

Post by Scorbie » June 25th, 2016, 10:18 am

Here's a lua exercise I was working on today. It looks for "orbits" for general engines. I was using the switch engine in the script.
Edit Fixed bug from windows. Thanks Wildmyron!

Code: Select all

local g = golly()
local gp = require "gplus"

-- User settings
local engine = {
   name = "switchen", -- Used as savefile prefix.
   pat = gp.pattern("bobo$o$bo2bo$3b3o!"),
   period = 96,
   dx = -8,
   dy = -8
}
local randfillbox = {10, 10, 15, 10}
local protection = 10 -- width of empty cell border surrounding switch engine
local orbit_check_size = 50 -- N where the program checks orbit in NxN box
local promptduration = 50 -- Prompt status every 50 soups

local enginebox = gp.getminbox(engine.pat)
local protectbox = {0, 0, enginebox.wd+protection, enginebox.ht+protection}


-- Previous orbit search results
local orbitfilename = engine.name .. "_" .. orbit_check_size
local function loadorbits()
   -- Load from saved orbit file.
   local orbits = {}
   local orbfile = io.open(orbitfilename, "r")
   if orbfile == nil then
      return {}
   end
   for line in orbfile:lines() do
      orbits[line] = true
   end
   orbfile:close()
   return orbits
end

local function saveorbits(orbits)
   -- Save to an orbit file.
   local orbfile = assert(io.open(orbitfilename, "a+"))
   for orb, _ in pairs(orbits) do
      orbfile:write(orb, "\n")
   end
   orbfile:close()
   g.show("Orbits updated to " .. orbitfilename .. "!")
end


local function initialize()
   -- Put the pattern at gen 0 in the beginning.
   g.new("")
   g.select(randfillbox)
   g.randfill(34)
   g.select(protectbox)
   g.clear(gp.inside)
   engine.pat.put()
   g.select({})
end


local function getview()
   local cycles = tonumber(g.getgen())/engine.period
   return {engine.dx*cycles, engine.dy*cycles, orbit_check_size, orbit_check_size}
end

local function update_hashes(minhashes, gens)
   -- Check for orbits in the given pattern.

   -- Engine status   | Return value
   --------------------|-----------------
   -- Destroyed      | nil
   -- Nothing special   | 0
   -- Orbit detected   | period

   -- Used Gabriel Nivasch's "keep minima" algorithm.
   -- Code partially from oscar.lua.

   local check_bbox = getview()
   if #g.getcells(check_bbox) == 0 then
      return nil -- Engine destroyed.
   end
   local h = g.hash(check_bbox)
   -- Initial setting
   if #minhashes == 0 then
      minhashes[1] = h
      gens[1] = tonumber(g.getgen())
      return 0
   end
   for i = #minhashes,1,-1 do -- N.B. i is always the last index while looping.
      if minhashes[i] > h then
         minhashes[i] = nil
         gens[i] = nil
      elseif minhashes[i] < h then
         minhashes[i+1]=h
         gens[i+1] = tonumber(g.getgen())
         return 0
      else -- Orbit or maybe a hash collision... just live with it.
         local oldgen = gens[i]
         local currgen = tonumber(g.getgen())
         gens[i] = currgen
         --assert(currgen>oldgen)
         return currgen-oldgen
      end
   end
end


-- main

g.autoupdate(true)
g.new("Switch Engine Orbit Search")
g.setrule("B3/S23")
local orbits = loadorbits()
local neworbits = {}
local soups = 0
while true do
   if soups % promptduration == 0 then
      g.show(tostring(soups) .. " soups so far! (Press Esc to quit, Space to pause. P for profiling.)")
   end
   local evt = g.getevent()
   if evt == "key space none" then
      g.show("Paused. Press any key to continue.")
      while g.getevent() == "" do end
      g.show("Resumed.")
   elseif evt:sub(1,5) == "key p" then
      return
   end
   initialize()
   local minhashes = {}
   local gens = {}
   while true do
      g.run(engine.period)
      local status = update_hashes(minhashes, gens)
      --assert(#minhashes == #gens)
      if status == nil then -- Engine dead.
         break
      elseif status > 0 then -- Possibly an orbit.
         local period = status
         -- Discard orbit if it is already known.
         -- Update orbit info now...(for running several simultaneously.)
         orbits = loadorbits()
         if orbits[tostring(g.hash(getview()))] ~= nil then break end
         -- Add orbit to collection
         for i=1,period,engine.period do
            local newhash = tostring(g.hash(getview()))
            orbits[newhash] = true
            neworbits[newhash] = true
            g.run(engine.period)
         end
         -- Save pattern
         g.reset()
         local patfilename = orbitfilename .. "_" .. os.date("%Y-%m-%d-%X") .. ".rle"
         patfilename = patfilename:gsub(":", "")
         g.store(g.getcells(g.getrect()), patfilename)
         -- Save new orbit hashes
         saveorbits(neworbits)
         neworbits = {}
         g.show("New orbit of period " .. period .. " found! (Saved at " .. patfilename .. ")")
         g.update()
         break
      end
   end
   soups = soups + 1
end
(You can run two of 'em simultaneously :))
The bad thing is that it is stupidly slow (10? soups/s)
Unfortunately, several lua profilers didn't work out well for unknown reasons :? so I would have to put it up as it is right now.

Anyway, nice Lua practice.


Edit: @simsim could you share your code you used to find that 2-corderengine-block-puffer?
Last edited by Scorbie on July 18th, 2016, 8:35 am, edited 2 times in total.

User avatar
BlinkerSpawn
Posts: 1992
Joined: November 8th, 2014, 8:48 pm
Location: Getting a snacker from R-Bee's

Re: Golly scripts

Post by BlinkerSpawn » July 2nd, 2016, 9:55 pm

Scorbie wrote:Here's a lua exercise I was working on today. It looks for "orbits" for general engines. I was using the switch engine in the script.

Code: Select all

some lua
I can run the script for a few seconds but then get
Golly wrote:C:\Users\{ME}\AppData\Roaming\Golly\golly_clip.lua:149:
Can't create pattern file!
What's going on?
LifeWiki: Like Wikipedia but with more spaceships. [citation needed]

Image

User avatar
Scorbie
Posts: 1692
Joined: December 7th, 2013, 1:05 am

Re: Golly scripts

Post by Scorbie » July 3rd, 2016, 8:06 pm

BlinkerSpawn wrote:
Scorbie wrote:Here's a lua exercise I was working on today. It looks for "orbits" for general engines. I was using the switch engine in the script.

Code: Select all

some lua
I can run the script for a few seconds but then get
Golly wrote:C:\Users\{ME}\AppData\Roaming\Golly\golly_clip.lua:149:
Can't create pattern file!
What's going on?
Hmmm! Havent a clue. That file seems to be windows-specific, and I only used linux back then. The name golly-clip.lua seems to have something to do with clipboards, but I didn't use anything related to clipboards. Could you copy that lua file and post it here?

User avatar
dvgrn
Moderator
Posts: 10669
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Golly scripts

Post by dvgrn » July 3rd, 2016, 8:54 pm

Scorbie wrote:
BlinkerSpawn wrote:
Scorbie wrote:Here's a lua exercise I was working on today. It looks for "orbits" for general engines. I was using the switch engine in the script.

Code: Select all

some lua
I can run the script for a few seconds but then get
Golly wrote:C:\Users\{ME}\AppData\Roaming\Golly\golly_clip.lua:149:
Can't create pattern file!
What's going on?
Hmmm! Havent a clue. That file seems to be windows-specific, and I only used linux back then. The name golly-clip.lua seems to have something to do with clipboards, but I didn't use anything related to clipboards. Could you copy that lua file and post it here?
That's going to be the file in which the script was saved -- i.e., it looks like BlinkerSpawn copied the script text and ran it from the clipboard. You may not have thought of that possibility when you wrote the script...!

User avatar
BlinkerSpawn
Posts: 1992
Joined: November 8th, 2014, 8:48 pm
Location: Getting a snacker from R-Bee's

Re: Golly scripts

Post by BlinkerSpawn » July 4th, 2016, 5:02 pm

dvgrn wrote:That's going to be the file in which the script was saved -- i.e., it looks like BlinkerSpawn copied the script text and ran it from the clipboard. You may not have thought of that possibility when you wrote the script...!
This is indeed what I did.
LifeWiki: Like Wikipedia but with more spaceships. [citation needed]

Image

User avatar
Scorbie
Posts: 1692
Joined: December 7th, 2013, 1:05 am

Re: Golly scripts

Post by Scorbie » July 4th, 2016, 11:00 pm

BlinkerSpawn wrote:
dvgrn wrote:That's going to be the file in which the script was saved -- i.e., it looks like BlinkerSpawn copied the script text and ran it from the clipboard. You may not have thought of that possibility when you wrote the script...!
This is indeed what I did.
Hmm. Then a trivial solution for now would be save the script and run it. Can't really work on it right now...

User avatar
Scorbie
Posts: 1692
Joined: December 7th, 2013, 1:05 am

Re: Golly scripts

Post by Scorbie » July 18th, 2016, 8:34 am

Found the problem. Windows doesn't allow a colon to be inside a filename. Will edit the script.
Note: This script is fairly slow and I am not planning on maintaining this unless someone comes out with a way to profile golly lua scripts. (And then I'll probably use lua all the time.)

User avatar
BlinkerSpawn
Posts: 1992
Joined: November 8th, 2014, 8:48 pm
Location: Getting a snacker from R-Bee's

Re: Golly scripts

Post by BlinkerSpawn » July 19th, 2016, 12:29 pm

Scorbie wrote:Found the problem. Windows doesn't allow a colon to be inside a filename. Will edit the script.
Note: This script is fairly slow and I am not planning on maintaining this unless someone comes out with a way to profile golly lua scripts. (And then I'll probably use lua all the time.)
Thanks for the fix; it's running great now.
LifeWiki: Like Wikipedia but with more spaceships. [citation needed]

Image

User avatar
Scorbie
Posts: 1692
Joined: December 7th, 2013, 1:05 am

Re: Golly scripts

Post by Scorbie » July 19th, 2016, 6:59 pm

BlinkerSpawn wrote:
Scorbie wrote:Found the problem. Windows doesn't allow a colon to be inside a filename. Will edit the script.
Note: This script is fairly slow and I am not planning on maintaining this unless someone comes out with a way to profile golly lua scripts. (And then I'll probably use lua all the time.)
Thanks for the fix; it's running great now.
Good to hear that. You will already know it for now, but this script spawns new files in the directory it is in, so it is best to make an "orbitsrc" folder in your personal scripts directory and put it there.

User avatar
Scorbie
Posts: 1692
Joined: December 7th, 2013, 1:05 am

Re: Golly scripts

Post by Scorbie » September 11th, 2016, 12:45 pm

Here's a WIP on the same script in python. Algorithm changed a bit and it searches about 10 soups per second. I still need to work on saving and loading the patterns.
A strange regular pattern occurs with a reason I don't know. Anybody with ideas?

Code: Select all

import golly as g
from glife import pattern, getminbox
from collections import namedtuple
import itertools as it
from timeit import default_timer as timer
import unittest

Engine = namedtuple('Engine', 'name pattern period')

# Settings
ENGINE = Engine("switchen", pattern('bobo$o$bo2bo$3b3o!'), 96)
RANDFILL_RECT = [10, 10, 15, 15]
PADDING = 5
OSC_CHECK_INTERVAL = 500
SHOW_INTERVAL = 20

# Constants used inside program
box = getminbox(ENGINE.pattern)
CLEAR_RECT = [box[0]-PADDING, box[1]-PADDING,
              box[2]+2*PADDING, box[3]+2*PADDING]


def initialize():
    """Place the engine and random stuff at gen 0."""
    g.new("Orbit Search")
    g.setmag(2)
    g.setrule('')
    g.select(RANDFILL_RECT)
    g.randfill(30)
    g.select(CLEAR_RECT)
    g.clear(0)
    ENGINE.pattern.put()


def stabilize():
    """Run the pattern until the population oscillates.

    Return the populations in one cycle.
    """
    next_osc_check = OSC_CHECK_INTERVAL
    popchanges = []
    currpop = int(g.getpop())
    while True:
        lastpop = currpop
        g.run(ENGINE.period)
        currpop = int(g.getpop())
        popchanges.append(currpop-lastpop)
        # Check for oscillation every once in a while.
        if int(g.getgen()) > next_osc_check:
            next_osc_check += OSC_CHECK_INTERVAL
            popcycle = detect_pop_oscillation(popchanges)
            # g.note(str([popchanges, popcycle, popcycle is not None]))
            if popcycle is not None:
                return popcycle
        if int(g.getgen()) > 10000:
            g.exit()
        # if int(g.getgen()) > 5000:
        #     test(detect_pop_oscillation, [(popchanges, None)])
        #     g.exit()


def detect_pop_oscillation(poplist):
    """Get the minimum cycle with the longest match from the end of the
    poplist. The poplist should match at least 2 cycles.
    Longest match first, minimum cycle next.
    """
    l, revlist = len(poplist), poplist[::-1]
    # Set the minimum of the match length to get accepted as a new soln.
    # This gets bigger after more iterations.
    matchi, matchlen, match = None, 10, None
    for i, pop in enumerate(revlist):
        if i == 0:
            continue
        if pop != revlist[0]:
            continue
        if matchi is not None and i % matchi == 0:
            continue
        cyclelen = i
        ideal = it.cycle(revlist[:i])
        for j, pop2 in enumerate(revlist):
            if pop2 != ideal.next():
                break
        newmatchlen = j
        if newmatchlen > 1.5*cyclelen:
            if newmatchlen > matchlen:
                match = revlist[i-1:None:-1]
                matchi = i
            matchlen = newmatchlen
    return match


tests_dpo = [
    ([1,2,3]+[1,2]*6, [1,2]),
    ([5,6,4]+[1,2,3,4]*3, [1,2,3,4]),
    ([5,1,0,34,1,20]+[0]*100, [0])
    ]


def test(fn, tests, msg=""):
    """Test for functions.

    The unittest module doesn't work nicely on an embedded python
    setting like this.
    """
    for test, expected in tests:
        actual = fn(test)
        if expected is not None:
            assert actual == expected, (
                "{}\n"
                "actual: {}\n"
                "expected: {}\n"
                "test: {}".format(msg, actual, expected, test)
                )
        else:
            g.note(
                "{}\n"
                "test: {}\n"
                "result: {}\n".format(msg, test, actual)
                )
    else:
        g.note("{}\nTest Finished!".format(msg))


def main():
    g.autoupdate(True)
    soupcnt = 0
    start = timer()
    while True:
        initialize()
        pops = stabilize()
        soupcnt += 1
        if soupcnt % SHOW_INTERVAL == 0:
            current = timer()
            g.show("{} soups so far! ({} soups/s)"
                   .format(soupcnt, soupcnt/(current-start)))

# test(detect_pop_oscillation, tests_dpo, "<dpo_test>")
main()

User avatar
Scorbie
Posts: 1692
Joined: December 7th, 2013, 1:05 am

Re: Golly scripts

Post by Scorbie » September 18th, 2016, 4:54 am

Here's the almost-finished script. I am a little hasty on publishing this... The puftrain example makes false positives, but I'm tired and just gonna publish it without fixes. The switch engine works nice. I think this would be more suitable for searching orbits for small puffers.

orbitsrc.py

Code: Select all

from __future__ import print_function
import golly as g
from glife import pattern, getminbox
from collections import namedtuple, deque
import itertools as it
from timeit import default_timer as timer
from datetime import datetime

Engine = namedtuple('Engine', 'name pattern period')

# Switch engine
# ENGINE = Engine("switchen", pattern('bobo$o$bo2bo$3b3o!'), 48)
# RANDFILL_RECT = [10, 10, 15, 15]
# PADDING = 5
# INITIAL_RUN = 0
# OSC_CHECK_INTERVAL = 500
# SHOW_EVERY = 50

# Puffer train
ENGINE = Engine("puftrain",
                  pattern("b3o11b3o$o2bo10bo2bo$3bo4b3o6bo$"
                          "3bo4bo2bo5bo$2bo4bo8bo!",
                          x0=-9, y0=-5),
                  20)
RANDFILL_RECT = [-3, 2, 7, 7]
PADDING = 0
INITIAL_RUN = 2000
OSC_CHECK_INTERVAL = 1000
SHOW_EVERY = 10

# Constants used inside program
box = getminbox(ENGINE.pattern)
CLEAR_RECT = [box[0]-PADDING, box[1]-PADDING,
              box[2]+2*PADDING, box[3]+2*PADDING]


def initialize():
    """Place the engine and random stuff at gen 0."""
    g.new("Orbit Search")
    g.setmag(1)
    g.setrule('B3/S23')
    g.select(RANDFILL_RECT)
    g.randfill(30)
    g.select(CLEAR_RECT)
    g.clear(0)
    ENGINE.pattern.put()

def stabilize():
    """Run the pattern until the population oscillates.

    Return the populations in one cycle.
    """
    next_osc_check = INITIAL_RUN
    popchanges = []
    currpop = int(g.getpop())
    while True:
        lastpop = currpop
        g.run(ENGINE.period)
        currpop = int(g.getpop())
        popchanges.append(currpop-lastpop)
        # Check for oscillation every once in a while.
        if int(g.getgen()) > next_osc_check:
            next_osc_check += OSC_CHECK_INTERVAL
            popcycle = detect_pop_oscillation(popchanges)
            if popcycle is not None:
                return popcycle


def detect_pop_oscillation(poplist):
    """Get the minimum cycle with the longest match from the end of the
    poplist. The poplist should match at least 2 cycles.
    Longest match first, minimum cycle next.
    """
    l, revlist = len(poplist), poplist[::-1]
    # Set the minimum of the match length to get accepted as a new soln.
    # This gets bigger after more iterations.
    matchi, matchlen, match = None, 10, None
    for i, pop in enumerate(revlist):
        if i == 0:
            continue
        if pop != revlist[0]:
            continue
        if matchi is not None and i % matchi == 0:
            continue
        cyclelen = i
        ideal = it.cycle(revlist[:i])
        for j, pop2 in enumerate(revlist):
            if pop2 != ideal.next():
                break
        newmatchlen = j
        if newmatchlen > 2.5*cyclelen:
            if newmatchlen > matchlen:
                match = revlist[i-1:None:-1]
                matchi = i
            matchlen = newmatchlen
    return match


tests_dpo = [
    ([1,2,3]+[1,2]*6, [1,2]),
    ([5,6,4]+[1,2,3,4]*3, [1,2,3,4]),
    ([5,1,0,34,1,20]+[0]*100, [0])
    ]


def test(fn, tests, msg=""):
    """Test for functions.

    The unittest module doesn't work nicely on an embedded python
    setting like this. Hence this handwritten function.
    """
    for test, expected in tests:
        actual = fn(test)
        if expected is not None:
            assert actual == expected, (
                "{}\n"
                "actual: {}\n"
                "expected: {}\n"
                "test: {}".format(msg, actual, expected, test)
                )
        else:
            g.note(
                "{}\n"
                "test: {}\n"
                "result: {}\n".format(msg, test, actual)
                )
    else:
        g.note("{}\nTest Finished!".format(msg))


def normalize_poplist(poplist):
    """Among the identical population cycles, get the minimal representation.

    e.g. [1, 2, 3], [2, 3, 1] and [3, 1, 2] are all identical cycles.
    We choose [1, 2, 3] as it is the least of the three.
    """
    identical_poplist = [deque(poplist) for _ in range(len(poplist))]
    for i, ps in enumerate(identical_poplist):
        ps.rotate(i)
    return tuple(min(identical_poplist))


def read_poplists(popfilename=''):
    """Read and return the data of a population file."""
    with open(popfilename) as popfile:
        knownpoplist = {tuple(int(pop) for pop in line.strip().split())
                     for line in popfile}
    # We assert that poplist for each line is properly normalized.
    assert all(normalize_poplist(kp)==kp for kp in knownpoplist),\
        "kp: {}".format(kp)
    return knownpoplist


def write_new_poplist(popfilename, newpop):
    with open(popfilename, 'a') as popfile:
        assert normalize_poplist(newpop)==newpop,\
            "newpop: {}".format(newpop)
        print(*newpop, file=popfile)


def main():
    g.autoupdate(True)
    popfilename = g.opendialog("Choose the population file.",
                               "all files(*)|*", '', ENGINE.name)
    try:
        known_poplists = read_poplists(popfilename)
    except:
        known_poplists = set()
        popfilename = g.savedialog("Choose a name for a new population file.",
                                   "all files(*)|*", '', ENGINE.name)
    finally:
        open(popfilename, 'a+').close()
    soupcnt = 0
    start = timer()
    while True:
        initialize()
        poplist = normalize_poplist(stabilize())
        if sum(poplist) > 0 and poplist not in known_poplists:
            # g.note("{}\n\n{}".format(poplist, known_poplists))
            # g.exit()
            known_poplists.add(poplist)
            write_new_poplist(popfilename, poplist)
            g.reset()
            patfilename = "{}_{}.rle".format(
                ENGINE.name, datetime.now().isoformat().replace(':','-'))
            g.save(patfilename, "rle")
            g.show("Saved new p{} pattern at {}"
                   .format(ENGINE.period*len(poplist), patfilename))
        soupcnt += 1
        if soupcnt % SHOW_EVERY == 0:
            current = timer()
            g.show("{} soups so far! ({} soups/s)"
                   .format(soupcnt, soupcnt/(current-start)))

# test(detect_pop_oscillation, tests_dpo, "<dpo_test>")
main()

User avatar
BlinkerSpawn
Posts: 1992
Joined: November 8th, 2014, 8:48 pm
Location: Getting a snacker from R-Bee's

Re: Golly scripts

Post by BlinkerSpawn » October 12th, 2016, 10:52 pm

I remarked upon a code I had thought up a while ago on a post in another thread:
I, in the 'Extending apgcodes to larger patterns' thread, wrote:While looking at how RLEs are structure I came up with a code structure that reliably seems to end up smaller than RLE for small patterns I tried, however it's totally incompatible with apgcodes and would've been pretty awkward to handle (no written line breaks, among other things).
Here's a little script I created as a Lua exercise that can convert selections in Golly to (a variant of) the code mentioned (EDIT: Updated, please redownload):

Code: Select all

-- Convert some pattern to a relatively compact alphanumeric code.
-- Author: BlinkerSpawn, Oct 2016.

local g = golly()
local get = g.getcell
local r = g.getrect()

local out = "x"..r[3].."_y"..r[4]
local block = get(r[1], r[2]) --state of the current block
out = out.."_"..block
local codex = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local cellstt = 0 --state of the cell being looked at
local cellcnt = 0 --length of the current block
local trailing = "_" --last character on codestring
local blockcnt = 1 --usages of the last character on codestring
local consider = "" --character for current block

for row = r[2], r[2] + r[4] - 1 do
  for col = r[1], r[1] + r[3] - 1 do
    cellstt = get(col, row)
    if cellstt ~= block then
      if cellcnt > 25 then out = out.."Z"..cellcnt
      else
        consider = codex:sub(cellcnt,cellcnt)
        if consider ~= trailing then
          if blockcnt > 1 then out = out..blockcnt end
          out = out..consider
          trailing = consider
          blockcnt = 1
        else blockcnt = blockcnt + 1
        end
      end
    cellcnt = 1
    block = cellstt
    else cellcnt = cellcnt + 1
    end
  end
end
if blockcnt ~= 1 then out = out..blockcnt end
g.show(out)
g.setclipstr(out)
The original code (in the vein of the suggestion I made in that thread) used lowercase letters to signify long breaks in base-26, but while creating the script I realized that simple numbers were already in use and that they would serve the same purpose more efficiently.
Here's an example code, with the equivalent RLE for comparison:

Code: Select all

x7_y7_0BAEA3DA3CBACGCACABA2

Code: Select all

x = 7, y = 7, rule = B3/S23
2bo$bobo$bobo$2ob3o$6bo$2ob3o$2obo!
The code works as follows:
  • The first part ("x[width]_y[length]_[0/1]") specify the bounding box and the state of the NW cell.
  • Take the rows of your pattern and place them end-to-end - or just imagine you are. Your pattern is now representable as a sequence of contiguous blocks of alternating OFF and ON cells of varying lengths. Starting with the upper left cell and going by rows (w/o dropping count at row wraps) note the lengths of each contiguous block and denote it by the corresponding letter (A=1, B=2, ..., Y=25).
  • Sequences of like letters (typically AAAAA...) are represented as A[some number] as in RLEs.
  • If a block is longer than 25 cells, denote it as Z[block length]
  • The last block (the one including the lower right cell) isn't marked because it's state and length can be inferred.
Probably not any sort of actually useful tool but writing it was fun. :)
Last edited by BlinkerSpawn on October 16th, 2016, 8:54 pm, edited 7 times in total.
LifeWiki: Like Wikipedia but with more spaceships. [citation needed]

Image

User avatar
dvgrn
Moderator
Posts: 10669
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Golly scripts

Post by dvgrn » October 13th, 2016, 8:55 am

BlinkerSpawn wrote:Here's a little script I created as a Lua exercise that can convert selections in Golly to (a variant of) the code mentioned...
It's kind of funny -- there's some prior art for this kind of encoding. Koenig's SOF (Single Object Format) encoding uses the same exact method -- no "b" or "o" cell state specifications needed, because they're bound to alternate. The character set he used allowed for somewhat more compression, but wasn't particularly HTML-compatible if I recall correctly. And now the page where the SOF definition used to be found isn't even available any more...!

EDIT: Oh, wait -- here it is -- see the entry for "SOF format". Not sure when it moved to the definitions.php location.

User avatar
BlinkerSpawn
Posts: 1992
Joined: November 8th, 2014, 8:48 pm
Location: Getting a snacker from R-Bee's

Re: Golly scripts

Post by BlinkerSpawn » October 13th, 2016, 10:47 am

Thanks for bringing that to my attention! I might have tried to check, but I've been staying away from pentadecathlon.com after several connection problems that I'm sure have probably been resolved at this point.
I guess I'll just work on the conversion script (although I'll have to first find out how to read text from the clipboard in Lua and/or how to make Golly create dialog boxes).
On a mildly related note I just downloaded Atom and it's been very helpful.
LifeWiki: Like Wikipedia but with more spaceships. [citation needed]

Image

User avatar
dvgrn
Moderator
Posts: 10669
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Golly scripts

Post by dvgrn » October 13th, 2016, 11:38 am

BlinkerSpawn wrote:I guess I'll just work on the conversion script (although I'll have to first find out how to read text from the clipboard in Lua and/or how to make Golly create dialog boxes).
Lua supports g.getclipstr() and g.setclipstr(), just like Python.

There are rumors that much better dialog boxes than the current g.getstring() and file dialogs, will be coming along in a future release of Golly. Lua is going to allow for some serious paradigm-shifting to happen in that department...! But for now, working with the clipboard is probably the easiest thing.

The minor advantages that I see of SOF format over your new one are 1) each SOF code is one line, so it's easier to parse lists of objects; and 2) SOF just assumes that the first state is ON, so you don't need to encode an initial state. If the first state is actually OFF, then the SOF code gets a zero prefix -- i.e., a zero-length strip of ON cells.

Of course, the great majority of patterns actually start with an OFF strip, so it would probably make more sense to have OFF be the default.

The fact that your format encodes a bounding box, same as RLE, seems like both an advantage and a disadvantage. If it's not encoded then it can't be wrong -- but without a specified bounding box, it's somewhat less efficient to encode a full bounding-box rectangle around a pattern that allows it to be inverted reliably. Then again, when is it really critically important to be able to invert a pattern by changing one character?

User avatar
BlinkerSpawn
Posts: 1992
Joined: November 8th, 2014, 8:48 pm
Location: Getting a snacker from R-Bee's

Re: Golly scripts

Post by BlinkerSpawn » October 13th, 2016, 4:55 pm

dvgrn wrote:The fact that your format encodes a bounding box, same as RLE, seems like both an advantage and a disadvantage. If it's not encoded then it can't be wrong -- but without a specified bounding box, it's somewhat less efficient to encode a full bounding-box rectangle around a pattern that allows it to be inverted reliably.
Really only the X value was required, as it allowed me to drop line breaks: since they must appear every X cells, I dropped them and merged like blocks across the boundary.
I added the Y value simply for completeness' sake.
Are you saying I should drop this and try working with SOF instead? (Either way it's an excuse to learn Lua)
LifeWiki: Like Wikipedia but with more spaceships. [citation needed]

Image

Post Reply