Thread for basic questions

A place, especially for newcomers, to ask questions and learn the basics.
lemon41625
Posts: 370
Joined: January 24th, 2020, 7:39 am
Location: 小红点 (if you know where that is)

Re: Thread for basic questions

Post by lemon41625 »

dvgrn wrote: February 4th, 2016, 11:28 am
Saka wrote:This thread is for requesting python scripts, I would like to request a apgcode to rle and if possible rle to apgcode
The necessary pieces are already available, but kind of scattered around in several threads. Here's a quick-and-dirty attempt to put the apgcode-to-cell-list and cell-list-to-RLE functions together:

apgcode-to-clipboard-RLE.py:

Code: Select all

# apgcode-to-clipboard-RLE:
#   takes an input apgcode, and copies equivalent RLE (with a header line)
#   into the clipboard, ready to be pasted into Golly.
#
# decodeCanon function:  creates a pattern cell list from a canonical apgcode,
#   an alphanumeric representation used in apgsearch by Adam P. Goucher
#
# By Arie Paap 
# Sept. 2014
# 
# ord2() from apgsearch, by Adam P. Goucher

import golly as g

g.setrule("Life")

def decodeCanon(canonPatt):
    chars = "0123456789abcdefghijklmnopqrstuvwxyz"
    
    ox = 0
    x = 0
    y = 0
    clist = []
    
    ii = 0
    
    while ii < len(canonPatt):
        c = canonPatt[ii]
        if (c == 'y'):
            ii += 1
            x += 4 + ord2(canonPatt[ii])
            
        elif (c == 'x'):
            x += 3
        
        elif (c == 'w'):
            x += 2
        
        elif (c == '0'):
            x += 1
        
        elif (c == 'z'):
            x = ox
            y += 5
            
        else:
            u = ord2(c)
            v = 1
            for jj in xrange(0,5):
                if (u & v):
                    clist += [x, y+jj]
                v = v << 1
            x += 1
            
        ii += 1
    
    return clist

# Converts a base-36 case-insensitive alphanumeric character into a
# numerical value.
def ord2(char):

    x = ord(char)

    if ((x >= 48) & (x < 58)):
        return x - 48

    if ((x >= 65) & (x < 91)):
        return x - 55

    if ((x >= 97) & (x < 123)):
        return x - 87

    return -1

# Python function to convert a cell list to RLE
# Author: Nathaniel Johnston (nathaniel@nathanieljohnston.com), June 2009.
#          DMG: Refactored slightly so that the function input is a simple cell list.
#               No error checking added.
#               TBD:  check for multistate rule, show appropriate warning.
# --------------------------------------------------------------------

def chunks(l, n):
    for i in range(0, len(l), n):
        yield l[i:i+n]

# --------------------------------------------------------------------

def giveRLE(clist):
   clist_chunks = list (chunks (g.evolve(clist,0), 2))
   mcc = min(clist_chunks)
   rl_list = [[x[0]-mcc[0],x[1]-mcc[1]] for x in clist_chunks]
   rle_res = ""
   rle_len = 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+"!"

canonPatt = g.getstring('Enter apgcode','xp2_31e8gzoo1vg54','')
canonPatt = canonPatt.strip().lower()
canonPatt = canonPatt.split('_')[-1]

if not canonPatt:
    g.exit('Pattern is empty')

patt = decodeCanon(canonPatt)

RLE = giveRLE(patt)
header="x = " + str(max(patt[0::2])-min(patt[0::2])+1)+", y = " + str(max(patt[1::2])-min(patt[1::2])+1)+", rule = B3/S23\n"
g.setclipstr(header + RLE)
g.show(RLE)
In this implementation, the input is a Golly dialog box, and the output goes to the clipboard. It would be easy to change it so that the input comes from the clipboard also.

It would probably be good to add some error checking in that case -- make sure that what's on the clipboard really looks like an apgcode, before you go trying to convert it. This version should work okay (based on my very minimal testing so far) as long as the input really is a valid apgcode.

The conversion in the other direction is somewhat simpler. You can dig most of it straight out of the old Python version of apgsearch. Can someone else throw that one together?
biggiemac wrote: February 4th, 2016, 7:11 pm This'll probably work. I tested it for spaceships, still lives and oscillators. Just select a rectangle in golly containing only the pattern you care about (plus any amount of empty space) and the apgcode of the object will go to your clipboard. As a side effect, the script makes a new layer.

You can certainly get nonsense out of it, by selecting an active object or multiple disjoint objects. But if you use it well it'll do the right thing.

Code: Select all

import golly as g

# Golly selection to apgcode (in clipboard)
# stolen shamelessly from apgsearch.  Thanks Adam!

def bijoscar(maxsteps):

    initpop = int(g.getpop())
    initrect = g.getrect()
    if (len(initrect) == 0):
        return 0
    inithash = g.hash(initrect)

    for i in xrange(maxsteps):

        g.run(1)

        if (int(g.getpop()) == initpop):

            prect = g.getrect()
            phash = g.hash(prect)

            if (phash == inithash):

                period = i + 1

                if (prect == initrect):
                    return period
                else:
                    return -period
    return -1


def canonise():
    
    p = bijoscar(1000)

    representation = "#"
    for i in range(abs(p)):
        rect = g.getrect()
        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))
        g.run(1)
    
    if (p<0):
        prefix = "q"+str(abs(p))
    elif (p==1):
        prefix = "s"+str(g.getpop())
    else:
        prefix = "p"+str(p)

    g.setclipstr("x"+prefix+"_"+representation)

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

    representation = ""

    chars = "0123456789abcdefghijklmnopqrstuvwxyz"

    for v in xrange(int((breadth-1)/5)+1):
        zeroes = 0
        if (v != 0):
            representation += "z"
        for u in xrange(length):
            baudot = 0
            for w in xrange(5):
                x = ox + a*u + b*(5*v + w)
                y = oy + c*u + d*(5*v + w)
                baudot = (baudot >> 1) + 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

g.duplicate()
g.clear(1)
canonise()

Or, for the extra lazy, a script that will take you directly to the catagolue page.

Code: Select all

import golly as g
import webbrowser

# Golly selection directly to catagolue page
# stolen shamelessly from apgsearch.  Thanks Adam!

def bijoscar(maxsteps):

    initpop = int(g.getpop())
    initrect = g.getrect()
    if (len(initrect) == 0):
        return 0
    inithash = g.hash(initrect)

    for i in xrange(maxsteps):

        g.run(1)

        if (int(g.getpop()) == initpop):

            prect = g.getrect()
            phash = g.hash(prect)

            if (phash == inithash):

                period = i + 1

                if (prect == initrect):
                    return period
                else:
                    return -period
    return -1


def canonise():

    p = bijoscar(1000)

    representation = "#"
    for i in range(abs(p)):
        rect = g.getrect()
        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))
        g.run(1)
    
    if (p<0):
        prefix = "q"+str(abs(p))
    elif (p==1):
        prefix = "s"+str(g.getpop())
    else:
        prefix = "p"+str(p)

    rule = str.replace(g.getrule(),"/","").lower()
    
    webbrowser.open_new("http://catagolue.appspot.com/object?apgcode=x"+prefix+"_"+representation+"&rule="+rule)

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

    representation = ""

    chars = "0123456789abcdefghijklmnopqrstuvwxyz"

    for v in xrange(int((breadth-1)/5)+1):
        zeroes = 0
        if (v != 0):
            representation += "z"
        for u in xrange(length):
            baudot = 0
            for w in xrange(5):
                x = ox + a*u + b*(5*v + w)
                y = oy + c*u + d*(5*v + w)
                baudot = (baudot >> 1) + 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

g.duplicate()
g.clear(1)
canonise()
Taken from script request thread.
Download CAViewer: https://github.com/jedlimlx/Cellular-Automaton-Viewer

Supports:
BSFKL, Extended Generations, Regenerating Generations, Naive Rules, R1 Moore, R2 Cross and R2 Von Neumann INT
And some others...
User avatar
yujh
Posts: 3153
Joined: February 27th, 2020, 11:23 pm
Location: I'm not sure where I am, so please tell me if you know
Contact:

Re: Thread for basic questions

Post by yujh »

lemon41625 wrote: May 6th, 2020, 5:52 am Cool


Taken from script request thread.
Thanks very much!
Puffer(?)
Rule modifier

B34kz5e7c8/S23-a4ityz5k
b2n3-q5y6cn7s23-k4c8
B3-kq6cn8/S2-i3-a4ciyz8
B3-kq4z5e7c8/S2-ci3-a4ciq5ek6eik7

Bored of Conway's Game of Life? Try Pedestrian Life -- not pedestrian at all!
pew
Posts: 17
Joined: May 8th, 2020, 7:58 pm

Re: Thread for basic questions

Post by pew »

Hi, completely new to this. I've been using apgsearch, but just seeing some text saying I found something cool isn't very satisfying, plus it doesn't mention anything about still lifes. Sometimes in Golly I like to create a random big pattern and let it run, its very relaxing. Is there a way I can get like a census of the objects after everything has settled, with a Golly script? Census including still lifes, oscillators, ships, etc.
Hunting
Posts: 4401
Joined: September 11th, 2017, 2:54 am

Re: Thread for basic questions

Post by Hunting »

pew wrote: May 8th, 2020, 8:03 pm plus it doesn't mention anything about still lifes.
Minor: SLs are most of the time uninteresting.
hkoenig
Posts: 299
Joined: June 20th, 2009, 11:40 am

Re: Thread for basic questions

Post by hkoenig »

pew wrote:I like to create a random big pattern and let it run, its very relaxing.
Back in the 1990s, over a period of years, I would let randomly generated 1024x1024 patterns run on idle machines at home and the office. I agree that seeing that activity can be entertaining, almost hypnotic. I enjoyed coming in to the office on Monday morning and examining the weekend's results for any interesting results, then rerunning the same seed on one of my test machines as I took care of office related overhead. Found several interesting things, including the 3 Glider construction of the Pentadecathlon, that way.
Hunting wrote:Minor: SLs are most of the time uninteresting.
To you, perhaps.

But to others knowing that sort of objects are appearing can help understand what is going on. There was another recent comment about "memorizing" patterns. Memorizing isn't important, but learning how to recognize patterns is, and that requires seeing patterns evolve.

It really is too bad that all the apgsearches don't have a verbose, graphical UI that could show some of the interesting results it obtains. If anyone were really interested in broadening the appeal of Life, that's an area that's been neglected for to long. (Because, let's face it, doing a good UI takes time and effort, and there's no glory in it, especially if you aren't being paid to do it.)
Hunting
Posts: 4401
Joined: September 11th, 2017, 2:54 am

Re: Thread for basic questions

Post by Hunting »

hkoenig wrote: May 8th, 2020, 8:33 pm
Hunting wrote:Minor: SLs are most of the time uninteresting.
To you, perhaps.

But to others knowing that sort of objects are appearing can help understand what is going on. There was another recent comment about "memorizing" patterns. Memorizing isn't important, but learning how to recognize patterns is, and that requires seeing patterns evolve.
Ah, don't quote me on that. My nature causes my rudeness to new users.
pew
Posts: 17
Joined: May 8th, 2020, 7:58 pm

Re: Thread for basic questions

Post by pew »

hkoenig wrote: May 8th, 2020, 8:33 pm
pew wrote:I like to create a random big pattern and let it run, its very relaxing.
Back in the 1990s, over a period of years, I would let randomly generated 1024x1024 patterns run on idle machines at home and the office. I agree that seeing that activity can be entertaining, almost hypnotic. I enjoyed coming in to the office on Monday morning and examining the weekend's results for any interesting results, then rerunning the same seed on one of my test machines as I took care of office related overhead. Found several interesting things, including the 3 Glider construction of the Pentadecathlon, that way.
Hunting wrote:Minor: SLs are most of the time uninteresting.
To you, perhaps.

But to others knowing that sort of objects are appearing can help understand what is going on. There was another recent comment about "memorizing" patterns. Memorizing isn't important, but learning how to recognize patterns is, and that requires seeing patterns evolve.

It really is too bad that all the apgsearches don't have a verbose, graphical UI that could show some of the interesting results it obtains. If anyone were really interested in broadening the appeal of Life, that's an area that's been neglected for to long. (Because, let's face it, doing a good UI takes time and effort, and there's no glory in it, especially if you aren't being paid to do it.)
That's awesome, thanks for sharing. I haven't come across anything that cool yet, but finding a new still life or oscillator I haven't seen yet is and watching how it forms is very interesting. But I take it then there is no such program yet? That's a shame. Maybe a future project then
User avatar
dvgrn
Moderator
Posts: 12023
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Thread for basic questions

Post by dvgrn »

pew wrote: May 8th, 2020, 8:03 pm Hi, completely new to this. I've been using apgsearch, but just seeing some text saying I found something cool isn't very satisfying, plus it doesn't mention anything about still lifes. Sometimes in Golly I like to create a random big pattern and let it run, its very relaxing. Is there a way I can get like a census of the objects after everything has settled, with a Golly script? Census including still lifes, oscillators, ships, etc.
Just checking -- do you know how to go look at your recent hauls on Catagolue? You get some reasonably nice browsable pages of results there.

It's also possible to use code from the apgsearch 1.x Python script to perform a local census on some random huge pattern that you've created and run on your system, without uploading to Catagolue. But that takes a little rewriting, I think, unless someone can point to where the census code has been separated from the uploading functionality.
User avatar
Ian07
Moderator
Posts: 899
Joined: September 22nd, 2018, 8:48 am
Location: Pennsylvania, US

Re: Thread for basic questions

Post by Ian07 »

dvgrn wrote: May 8th, 2020, 9:39 pm It's also possible to use code from the apgsearch 1.x Python script to perform a local census on some random huge pattern that you've created and run on your system, without uploading to Catagolue. But that takes a little rewriting, I think, unless someone can point to where the census code has been separated from the uploading functionality.
I actually did this with v0.3 a while ago, since that version was made before Catagolue. Download the ordinary zip file from here, then replace the main apgsearch script with this:

Code: Select all

# Optimised soup searcher, v0.3 (beta release).
#
# -- Processes roughly 100 soups per (second . core . GHz).
#
# -- Can perfectly identify oscillators with period < 1000, well-separated
#    spaceships of low period, and certain infinite-growth patterns (such
#    guns and puffers, including both naturally-occurring types of switch
#    engine).
#
# -- Separates most pseudo-objects into their constituent parts, including
#    all pseudo-still-lifes of 18 or fewer live cells (which is the maximum
#    theoretically possible, given there is a 19-cell pseudo-still-life
#    with two distinct decompositions).
#
# -- Correctly separates non-interacting standard spaceships, irrespective
#    of their proximity. In particular, a LWSS-on-LWSS is registered as two
#    LWSSes, whereas an LWSS-on-HWSS is registered as a single spaceship
#    (since they interact by suppressing sparks).
#
# -- At least 99.9999999% reliable at identifying objects.
#
# -- Scores soups based on the total excitement of the ash objects.
#
# By Adam P. Goucher, with contributions by Andrew Trevorrow,
# Tom Rokicki, Nathaniel Johnston and Dave Greene.

import golly as g
from glife import rect, pattern
import time
import math
import operator
import hashlib
import datetime
import os

# Takes approximately 350 microseconds to construct a 16-by-16 soup based
# on a SHA-256 cryptographic hash in the obvious way.
def hashsoup(instring):
    thesoup = g.parse(g.getclipstr())
    g.putcells(thesoup, -8, -8)


# 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(duration):

    representation = "#"

    # We need to compare each phase to find the one with the smallest
    # description:
    for t in xrange(duration):

        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))

        g.run(1)

    return representation

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

    representation = ""

    chars = "0123456789abcdefghijklmnopqrstuvwxyz"

    for v in xrange(int((breadth-1)/5)+1):
        zeroes = 0
        if (v != 0):
            representation += "z"
        for u in xrange(length):
            baudot = 0
            for w in xrange(5):
                x = ox + a*u + b*(5*v + w)
                y = oy + c*u + d*(5*v + w)
                baudot = (baudot >> 1) + 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

    
# This explodes pseudo-still-lifes and pseudo-oscillators into their
# constituent parts.
#
# -- Requires the period (if oscillatory) and graph-theoretic diameter
#    to not exceed 4096.
# -- Never mistakenly separates a true object.
# -- Correctly separates most pseudo-still-lifes, including the famous:
#    http://www.conwaylife.com/wiki/Quad_pseudo_still_life
# -- Works perfectly for all still-lifes of up to 17 bits.
# -- Doesn't separate 'locks', of which the smallest example has 18
#    bits and is unique:
#
#     ** **
#     ** **
#
#    * *** *
#    ** * **
#
# To use this function (standalone), merely copy it into a script of
# the following form:
#
#   import golly as g
#
#   def pseudo_bangbang():
#
#   [...]
#
#   pseudo_bangbang()
#
# and execute it in Golly with a B3/S23 universe containing any still-
# lifes or oscillators you want to separate. Pure objects correspond to
# connected components in the final state of the universe.
#
# This has dependencies on the rules ContagiousLife, PercolateInfection
# and EradicateInfection.
#
# Not to be confused with the Unix shell instruction for repeating the
# previous instruction as a superuser (sudo !!), or indeed with any
# parodies of this song: https://www.youtube.com/watch?v=YswhUHH6Ufc
#
# Adam P. Goucher, 2014-08-25
def pseudo_bangbang():

    g.setrule("ContagiousLife")
    g.setbase(2)
    g.setstep(12)
    g.step()

    celllist = g.getcells(g.getrect())

    for i in xrange(0, len(celllist)-1, 3):
        
        # Only infect cells that haven't yet been infected:
        if (g.getcell(celllist[i], celllist[i+1]) <= 2):

            # Seed an initial 'infected' (red) cell:
            g.setcell(celllist[i], celllist[i+1], g.getcell(celllist[i], celllist[i+1]) + 2)

            prevpop = 0
            currpop = int(g.getpop())

            # Continue infecting until the entire component has been engulfed:
            while (prevpop != currpop):

                # Percolate the infection to every cell in the island:
                g.setrule("PercolateInfection")
                g.setbase(2)
                g.setstep(12)
                g.step()

                # Transmit the infection across any bridges.
                g.setrule("ContagiousLife")
                g.setbase(2)
                g.setstep(12)
                g.step()

                prevpop = currpop
                currpop = int(g.getpop())
                
            g.fit()
            g.update()

            # Red becomes green:
            g.setrule("EradicateInfection")
            g.step()


# Counts the number of live cells of each degree:
def degreecount():

    celllist = g.getcells(g.getrect())
    counts = [0,0,0,0,0,0,0,0,0]

    for i in xrange(0, len(celllist), 2):

        x = celllist[i]
        y = celllist[i+1]

        degree = -1

        for ux in xrange(x - 1, x + 2):
            for uy in xrange(y - 1, y + 2):

                degree += g.getcell(ux, uy)

        counts[degree] += 1

    return counts

# Counts the number of live cells of each degree in generations 1 and 2:
def degreecount2():

    g.run(1)
    a = degreecount()
    g.run(1)
    b = degreecount()

    return (a + b)

# If the universe consists only of disjoint *WSSes, this will return
# a triple (l, w, h) giving the quantities of each *WSS. Otherwise,
# this function will return (-1, -1, -1).
#
# This should only be used to separate period-4 moving objects which
# may contain multiple *WSSes.
def countxwsses():

    degcount = degreecount2()
    if (degreecount2() != degcount):
        # Degree counts are not period-2:
        return (-1, -1, -1)

    # Degree counts of each standard spaceship:
    hwssa = [1,4,6,2,0,0,0,0,0,0,0,0,4,4,6,1,2,1]
    mwssa = [2,2,5,2,0,0,0,0,0,0,0,0,4,4,4,1,2,0]
    lwssa = [1,2,4,2,0,0,0,0,0,0,0,0,4,4,2,2,0,0]
    hwssb = [0,0,0,4,4,6,1,2,1,1,4,6,2,0,0,0,0,0]
    mwssb = [0,0,0,4,4,4,1,2,0,2,2,5,2,0,0,0,0,0]
    lwssb = [0,0,0,4,4,2,2,0,0,1,2,4,2,0,0,0,0,0]

    # Calculate the number of standard spaceships in each phase:
    hacount = degcount[17]
    macount = degcount[16]/2 - hacount
    lacount = (degcount[15] - hacount - macount)/2
    hbcount = degcount[8]
    mbcount = degcount[7]/2 - hbcount
    lbcount = (degcount[6] - hbcount - mbcount)/2

    # Determine the expected degcount given the calculated quantities:
    pcounts = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: hacount*x, hwssa))
    pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: macount*x, mwssa))
    pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: lacount*x, lwssa))
    pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: hbcount*x, hwssb))
    pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: mbcount*x, mwssb))
    pcounts = map(lambda x, y: x + y, pcounts, map(lambda x: lbcount*x, lwssb))

    # Compare the observed and expected degcounts (to eliminate nonstandard spaceships):
    if (pcounts != degcount):
        # Expected and observed values do not match:
        return (-1, -1, -1)

    # Return the combined numbers of *WSSes:
    return(lacount + lbcount, macount + mbcount, hacount + hbcount)


class Soup:

    # A dict to store memoized decompositions of possibly-pseudo-objects
    # into constituent parts. This is initialised with the unique minimal
    # pseudo-still-life (two blocks on lock) that cannot be automatically
    # separated by the routine pseudo_bangbang(). Any larger objects are
    # ambiguous, such as this one:
    #
    #     *
    #    * * **
    #     ** **
    #
    #    * *** *
    #    ** * **
    #
    # Is it a (block on (lock on boat)) or ((block on lock) on boat)?
    # Ahh, the joys of non-associativity.
    #
    # See http://paradise.caltech.edu/~cook/Workshop/CAs/2DOutTot/Life/StillLife/StillLifeTheory.html
    decompositions = {"xs18_3pq3qp3": ["xs14_3123qp3", "xs4_33"]}

    # A dict of objects in the form {"identifier": ("common name", points)}
    #
    # As a rough heuristic, an object is worth 15 + log2(n) points if it
    # is n times rarer than the pentadecathlon.
    commonnames = {"xp3_co9nas0san9oczgoldlo0oldlogz1047210127401": ("pulsar", 9),
                   "xp15_4r4z4r4": ("pentadecathlon", 15),
                   "xp2_31ago": ("bipole", 17),
                   "xp2_0g0k053z32": ("quadpole", 17),
                   "xp2_g8gid1e8z1226": ("great on-off", 18),
                   "xp2_rhewehr": ("spark coil", 18),
                   "xp8_gk2gb3z11": ("figure-8", 19),
                   "xp4_37bkic": ("mold", 20),
                   "xp2_31a08zy0123cko": ("quadpole on ship", 20),
                   "xp2_g0k053z11": ("tripole", 20),
                   "xp4_ssj3744zw3": ("mazing", 22),
                   "xp8_g3jgz1ut": ("blocker", 23),
                   "xp3_695qc8zx33": ("jam", 23),
                   "xp30_w33z8kqrqk8zzzw33": ("cis-queen-bee-shuttle", 23),
                   "xp30_w33z8kqrqk8zzzx33": ("trans-queen-bee-shuttle", 23),
                   "xp4_8eh5e0e5he8z178a707a871": ("cloverleaf", 24),
                   "xp5_idiidiz01w1": ("octagon II", 25),
                   "xp6_ccb7w66z066": ("unix", 25),
                   "xp14_j9d0d9j": ("tumbler", 26),
                   "xp3_025qzrq221": ("trans-tub-eater", 27),
                   "xp3_4hh186z07": ("caterer", 28),
                   "xp3_025qz32qq1": ("cis-tub-eater", 29),
                   "xp8_wgovnz234z33": ("Tim Coe's p8", 30),
                   "xp5_3pmwmp3zx11": ("fumarole", 32),
                   "xq288_gg8ugicy333oooz1o0h8y9355y133zw1yboozwo772zw1": ("block-laying switch engine", 14),
                   "xq384_037boz0gg1ya33zg978y988y3ooz0120d52y6gsxgzyf4c4qf6xgy0ggzy233y4eaj44swewg0h0vooog9ezye111y51zyu8kc": ("glider-producing switch engine", 16),
                   "xq4_6frc": ("lightweight spaceship", 8),
                   "xq4_27dee6": ("middleweight spaceship", 10),
                   "xq4_27deee6": ("heavyweight spaceship", 12),
                   "xq4_153": ("glider", 0),
                   "xp2_7": ("blinker", 0),
                   "xs4_33": ("block", 0),
                   "xs4_252": ("tub", 0),
                   "xs5_253": ("boat", 0),
                   "xs6_bd": ("snake", 0),
                   "xs6_356": ("ship", 0),
                   "xs6_696": ("beehive", 0),
                   "xs6_25a4": ("barge", 0),
                   "xs6_39c": ("carrier", 0),
                   "xp2_7e": ("toad", 0),
                   "xp2_2a54": ("clock", 0),
                   "xp2_318c": ("beacon", 0),
                   "xs7_3lo": ("long snake", 0),
                   "xs7_25ac": ("long boat", 0),
                   "xs7_178c": ("eater", 0),
                   "xs7_2596": ("loaf", 0),
                   "xs8_178k8": ("twit", 0),
                   "xs8_32qk": ("hook with tail", 0),
                   "xs8_69ic": ("mango", 0),
                   "xs8_6996": ("pond", 0),
                   "xs8_25ak8": ("long barge", 0),
                   "xs8_3pm": ("shillelagh", 0),
                   "xs8_312ko": ("canoe", 0),
                   "xs8_31248c": ("very long snake", 0),
                   "xs8_35ac": ("long ship", 0),
                   "xs12_g8o653z11": ("ship-tie", 0),
                   "xs14_g88m952z121": ("half-bakery", 0),
                   "xs14_69bqic": ("paperclip", 0),
                   "xs9_31ego": ("integral sign", 0),
                   "xs10_g8o652z01": ("boat-tie", 0),
                   "xs14_g88b96z123": ("big ess", 0),
                   "xs16_g88m996z1221": ("bipond", 0),
                   "xs12_raar": ("table on table", 0),
                   "xs9_4aar": ("hat", 0),
                   "xs10_35ako": ("very long ship", 0),
                   "xs9_178ko": ("trans-boat-with-tail", 0),
                   "xs15_354cgc453": ("moose antlers", 0),
                   "xs14_6970796": ("cis-mirrored r-bee", 0),
                   "xs10_32qr": ("block on table", 0),
                   "xs16_j1u0696z11": ("beehive on dock", 0),
                   "xs14_j1u066z11": ("block on dock", 0),
                   "xs11_g8o652z11": ("boat-tie-ship", 0),
                   "xs9_25ako": ("very long boat", 0),
                   "xs16_69egmiczx1": ("scorpion", 0),
                   "xs18_rhe0ehr": ("dead spark coil", 0),
                   "xs17_2ege1ege2": ("twinhat", 0),
                   "xs10_178kk8": ("beehive with tail", 0),
                   "xs10_69ar": ("loop", 0),
                   "xs14_69bo8a6": ("fourteener", 0),
                   "xs14_39e0e93": ("bookends", 0),
                   "xs9_178kc": ("cis-boat-with-tail", 0)}

    # A tally of objects that have occurred during this run of apgsearch:
    objectcounts = {}

    # Any soups with positive scores, and the number of points.
    soupscores = {}

    # Temporary list of unidentified objects:
    unids = []

    # Things like glider guns and large oscillators belong here:
    superunids = []
    gridsize = 0
    resets = 0

    # For profiling purposes:
    qlifetime = 0.0
    ruletime = 0.0
    gridtime = 0.0

    # Increment object count by given value:
    def incobject(self, obj, incval):
        if (incval > 0):
            if obj in self.objectcounts:
                self.objectcounts[obj] = self.objectcounts[obj] + incval
            else:
                self.objectcounts[obj] = incval

    # Increment soup score by given value:
    def awardpoints(self, soupid, incval):
        if (incval > 0):
            if soupid in self.soupscores:
                self.soupscores[soupid] = self.soupscores[soupid] + incval
            else:
                self.soupscores[soupid] = incval

    # Increment soup score by appropriate value:
    def awardpoints2(self, soupid, obj):
        if obj in self.commonnames:
            self.awardpoints(soupid, self.commonnames[obj][1])
        elif (obj[0] == 'x'):
            prefix = obj.split('_')[0]
            prenum = int(prefix[2:])
            if (obj[1] == 's'):
                self.awardpoints(soupid, 10) # rare still-lifes are limited to 10 points
            elif (obj[1] == 'p'):
                if (prenum == 2):
                    self.awardpoints(soupid, 20) # p2 oscillators are limited to 20 points
                elif ((prenum == 3) | (prenum == 4)):
                    self.awardpoints(soupid, 30) # p3 and p4 oscillators are limited to 30 points
                else:
                    self.awardpoints(soupid, 40)
            else:
                self.awardpoints(soupid, 50)
        else:
            self.awardpoints(soupid, 50)

    # Assuming the pattern has stabilised, perform a census:
    def census(self, stepsize):

        g.setrule("CoalesceObjects")
        g.setbase(2)
        g.setstep(stepsize)
        g.step()

        g.setrule("IdentifyGliders")
        g.setbase(2)
        g.setstep(2)
        g.step()

        g.setrule("ClassifyObjects")
        g.setbase(2)
        g.setstep(max(8, stepsize))
        g.step()

        # Only do this if we have an infinite-growth pattern:
        if (stepsize > 8):
            g.setrule("HandlePlumes")
            g.setbase(2)
            g.setstep(1)
            g.step()
            g.setrule("ClassifyObjects")
            g.setstep(stepsize)
            g.step()

        # Remove any gliders:
        g.setrule("ExpungeGliders")
        g.run(1)
        pop5 = int(g.getpop())
        g.run(1)
        pop6 = int(g.getpop())
        self.incobject("xq4_153", (pop5 - pop6)/5)

        # Remove any blocks, blinkers and beehives:
        g.setrule("ExpungeObjects")
        g.run(1)
        pop1 = int(g.getpop())
        g.run(1)
        pop2 = int(g.getpop())
        g.run(1)
        pop3 = int(g.getpop())
        g.run(1)
        pop4 = int(g.getpop())

        # Blocks, blinkers and beehives removed by ExpungeObjects:
        self.incobject("xs4_33", (pop1-pop2)/4)
        self.incobject("xp2_7", (pop2-pop3)/5)
        self.incobject("xs6_696", (pop3-pop4)/8)

    # Removes an object incident with (ix, iy) and returns the cell list:
    def grabobj(self, ix, iy):

        allcells = [ix, iy, g.getcell(ix, iy)]
        g.setcell(ix, iy, 0)
        livecells = []
        deadcells = []

        marker = 0
        ll = 3

        while (marker < ll):
            x = allcells[marker]
            y = allcells[marker+1]
            z = allcells[marker+2]
            marker += 3

            if ((z % 2) == 1):
                livecells.append(x)
                livecells.append(y)
            else:
                deadcells.append(x)
                deadcells.append(y)

            for nx in xrange(x - 1, x + 2):
                for ny in xrange(y - 1, y + 2):

                    nz = g.getcell(nx, ny)
                    if (nz > 0):
                        allcells.append(nx)
                        allcells.append(ny)
                        allcells.append(nz)
                        g.setcell(nx, ny, 0)
                        ll += 3

        return livecells

    # Command to Grab, Remove and IDentify an OBJect:
    def gridobj(self, ix, iy):

        allcells = [ix, iy, g.getcell(ix, iy)]
        g.setcell(ix, iy, 0)
        livecells = []
        deadcells = []

        # This tacitly assumes the object is smaller than 1000-by-1000.
        # But this is okay, since it is only used by the routing logic.
        dleft = ix + 1000
        dright = ix - 1000
        dtop = iy + 1000
        dbottom = iy - 1000

        lleft = ix + 1000
        lright = ix - 1000
        ltop = iy + 1000
        lbottom = iy - 1000

        lpop = 0
        dpop = 0

        marker = 0
        ll = 3

        while (marker < ll):
            x = allcells[marker]
            y = allcells[marker+1]
            z = allcells[marker+2]
            marker += 3

            if ((z % 2) == 1):
                livecells.append(x)
                livecells.append(y)
                lleft = min(lleft, x)
                lright = max(lright, x)
                ltop = min(ltop, y)
                lbottom = max(lbottom, y)
                lpop += 1
            else:
                deadcells.append(x)
                deadcells.append(y)
                dleft = min(dleft, x)
                dright = max(dright, x)
                dtop = min(dtop, y)
                dbottom = max(dbottom, y)
                dpop += 1

            for nx in xrange(x - 1, x + 2):
                for ny in xrange(y - 1, y + 2):

                    nz = g.getcell(nx, ny)
                    if (nz > 0):
                        allcells.append(nx)
                        allcells.append(ny)
                        allcells.append(nz)
                        g.setcell(nx, ny, 0)
                        ll += 3

        lwidth = max(0, 1 + lright - lleft)
        lheight = max(0, 1 + lbottom - ltop)
        dwidth = max(0, 1 + dright - dleft)
        dheight = max(0, 1 + dbottom - dtop)

        llength = max(lwidth, lheight)
        lbreadth = min(lwidth, lheight)
        dlength = max(dwidth, dheight)
        dbreadth = min(dwidth, dheight)

        self.gridsize = max(self.gridsize, llength)

        # **** Routing logic ****
        #
        # This checks against definitions of known small common objects
        # in B3/S23, so that they don't have to be 'manually' canonised.
        
        objid = "unidentified"

        if (lpop < 3):
            objid = "nothing"
        elif (lpop == 3):
            objid = "xp2_7" #blinker
        elif (lpop == 4):
            if (dpop == 0):
                objid = "xs4_33" #block
            else:
                objid = "xs4_252" #tub
        elif (lpop == 5):
            if (dpop == 1):
                objid = "xs5_253" #boat
            else:
                objid = "xq4_153" #glider
        elif (lpop == 6):
            if (dpop == 1):
                objid = "xs6_356" #ship
            else:
                if (lbreadth == 2):
                    if (dpop == 2):
                        objid = "xs6_bd" #snake
                    else:
                        objid = "xp2_7e" #toad in resting phase
                elif (lbreadth == 4):
                    if (dpop == 2):
                        objid = "xs6_25a4" #barge
                    elif (dpop == 6):
                        objid = "xp2_2a54" #clock
                    else:
                        objid = "xp2_7e" #toad in panting phase
                        for i in xrange(0, 12, 2):
                            if (livecells[i] == lleft):
                                if (livecells[i + 1] == ltop | livecells[i + 1] == lbottom):
                                    objid = "xp2_318c" #beacon
                else:
                    objid = "xs6_696" #beehive
                    for i in xrange(0, 12, 2):
                        if (livecells[i] == lleft):
                            if (livecells[i + 1] == ltop | livecells[i + 1] == lbottom):
                                objid = "xs6_39c" #aircraft carrier
        elif (lpop == 7):
            if (dpop == 2):
                if (dlength == 3):
                    objid = "xs7_3lo" #long snake
                else:
                    objid = "xs7_25ac" #long boat
            else:
                if (dlength == 3):
                    objid = "xs7_178c" #eater (fishhook)
                else:
                    objid = "xs7_2596" #loaf
        elif (lpop == 8):
            if (dpop == 4):
                if (dlength == 4):
                    if (lbreadth == 5):
                        objid = "xs8_178k8" #tub with tail
                    else:
                        objid = "xs8_32qk" #hook with tail
                elif (dlength == 3):
                    objid = "xs8_69ic" #mango
                else:
                    objid = "xs8_6996" #pond
            elif (dpop == 3):
                if (lbreadth == 5):
                    objid = "xs8_25ak8" #long barge
                else:
                    objid = "xs8_3pm" #shillelagh
            else:
                if (lbreadth == 2):
                    objid = "xs8_rr" #block on block
                elif (lbreadth == 5):
                    objid = "xs8_312ko" #canoe
                elif (lbreadth == 4):
                    if (llength == 6):
                        objid = "xs8_31248c" #very long snake
                    else:
                        diagcount = 0
                        for i in xrange(0, 12, 2):
                            if ((livecells[i] - lleft) == (livecells[i + 1] - ltop)):
                                diagcount += 1
                        if (diagcount == 2):
                            objid = "xs8_35ac" #long ship
                        else:
                            objid = "xp2_318c" #beacon
        elif (lpop == 12):
            # Ship-ties are just about common enough to warrant being handled in the routing logic:
            if ((lwidth == 6) & (lheight == 6)):
                bitstring = 0
                for i in xrange(0, 24, 2):
                    bitstring += (1 << ((livecells[i] - lleft) + 6*(livecells[i + 1] - ltop)))
                if ((bitstring == 52217012547) | (bitstring == 3306785328)):
                    objid = "xs12_g8o653z11"
        elif (lpop == 14):
            # As is the miraculous half-bakery:
            if ((lwidth == 7) & (lheight == 7)):
                bitstring = 0
                for i in xrange(0, 28, 2):
                    bitstring += (1 << ((livecells[i] - lleft) + 7*(livecells[i + 1] - ltop)))
                if ((bitstring == 213590917399170) | (bitstring == 8970354435120) |
                    (bitstring == 26700311308320) | (bitstring == 143505703994502)):
                    objid = "xs14_g88m952z121"

        # **** End of routing logic ****

        if (objid == "xs8_rr"):
            # A biblock should be registered as two blocks:
            self.incobject("xs4_33", 2)
        elif (objid == "unidentified"):
            # This has passed through the routing logic without being identified,
            # so save it in a temporary list for later identification:
            self.unids.append(livecells)
            self.unids.append(lleft)
            self.unids.append(ltop)
        elif (objid != "nothing"):
            # The object is non-empty, so add it to the census:
            self.incobject(objid, 1)


    # Tests for population periodicity:
    def naivestab(self, period, security, length):

        depth = 0
        prevpop = 0
        for i in xrange(length):
            g.run(period)
            currpop = int(g.getpop())
            if (currpop == prevpop):
                depth += 1
            else:
                depth = 0
            prevpop = currpop
            if (depth == security):
                # Population is periodic.
                return True

        return False

    # This should catch most short-lived soups with few gliders produced:
    def naivestab2(self, period, length):

        for i in xrange(length):
            r = g.getrect()
            if (len(r) == 0):
                return True
            pop0 = int(g.getpop())
            g.run(period)
            hash1 = g.hash(r)
            pop1 = int(g.getpop())
            g.run(period)
            hash2 = g.hash(r)
            pop2 = int(g.getpop())

            if ((hash1 == hash2) & (pop0 == pop1) & (pop1 == pop2)):

                if (g.getrect() == r):
                    return True
                
                g.run((2*int(max(r[2], r[3])/period)+1)*period)
                hash3 = g.hash(r)
                pop3 = int(g.getpop())
                if ((hash2 == hash3) & (pop2 == pop3)):
                    return True

        return False
            
    # Runs a pattern until stabilisation with a 99.99996% success rate.
    # False positives are handled by a later error-correction stage.
    def stabilise3(self):

        # Phase I of stabilisation detection, designed to weed out patterns
        # that stabilise into a cluster of low-period oscillators within
        # about 6000 generations.

        if (self.naivestab2(12, 10)):
            return 4;

        if (self.naivestab(12, 30, 200)):
            return 4;

        if (self.naivestab(30, 30, 200)):
            return 5;

        # Phase II of stabilisation detection, which is much more rigorous
        # and based on oscar.py.

        # Should be sufficient:
        prect = [-2000, -2000, 4000, 4000]

        # initialize lists
        hashlist = []        # for pattern hash values
        genlist = []         # corresponding generation counts

        for j in xrange(4000):

            g.run(30)

            h = g.hash(prect)

            # 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]
                    break
                else:
                    period = (int(g.getgen()) - genlist[pos])

                    prevpop = g.getpop()

                    for i in xrange(20):
                        g.run(period)
                        currpop = g.getpop()
                        if (currpop != prevpop):
                            period = max(period, 4000)
                            break
                        prevpop = currpop
                        
                    return max(1 + int(math.log(period, 2)),3)

            hashlist.insert(pos, h)
            genlist.insert(pos, int(g.getgen()))

        g.setalgo("HashLife")
        g.setbase(2)
        g.setstep(16)
        g.step()
        stepsize = 12
        g.setalgo("QuickLife")

        return 12

    # Differs from oscar.py in that it detects absolute cycles, not eventual cycles.
    def bijoscar(self, maxsteps):

        initpop = int(g.getpop())
        initrect = g.getrect()
        if (len(initrect) == 0):
            return 0
        inithash = g.hash(initrect)

        for i in xrange(maxsteps):

            g.run(1)

            if (int(g.getpop()) == initpop):

                prect = g.getrect()
                phash = g.hash(prect)

                if (phash == inithash):

                    period = i + 1

                    if (prect == initrect):
                        return period
                    else:
                        return -period
        return -1

    # For a non-moving unidentified object, we check the dictionary of
    # memoized decompositions of possibly-pseudo-objects. If the object is
    # not already in the dictionary, it will be memoized.
    #
    # Low-period spaceships are also separated by this routine, although
    # this is less important now that there is a more bespoke prodecure
    # to handle disjoint unions of standard spaceships.
    #
    # @param moving  a bool which specifies whether the object is moving
    def enter_unid(self, unidname, soupid, moving):

        if not(unidname in self.decompositions):

            # Separate into pure components:
            if (moving):
                g.setrule("CoalesceObjects")
                g.setbase(2)
                g.setstep(3)
                g.step()
            else:
                pseudo_bangbang()

            listoflists = [] # which incidentally don't contain themselves.

            # Someone who plays the celllo:
            celllist = g.join(g.getcells(g.getrect()), [0])

            for i in xrange(0, len(celllist)-1, 3):
                if (g.getcell(celllist[i], celllist[i+1]) != 0):
                    livecells = self.grabobj(celllist[i], celllist[i+1])
                    if (len(livecells) > 0):
                        listoflists.append(livecells)

            listofobjs = []

            for livecells in listoflists:

                g.new("Subcomponent")
                g.setalgo("QuickLife")
                g.setrule("B3/S23")
                g.putcells(livecells)
                period = self.bijoscar(1000)
                canonised = canonise(abs(period))
                if (period < 0):
                    listofobjs.append("xq"+str(0-period)+"_"+canonised)
                elif (period == 1):
                    listofobjs.append("xs"+str(len(livecells)/2)+"_"+canonised)
                else:
                    listofobjs.append("xp"+str(period)+"_"+canonised)

            self.decompositions[unidname] = listofobjs

        # Actually add to the census:
        for comp in self.decompositions[unidname]:
            self.incobject(comp, 1)
            self.awardpoints2(soupid, comp)

    # This function has lots of arguments (hence the name):
    #
    # @param gsize     the square-root of the number of soups per page
    # @param gspacing  the minimum distance between centres of soups
    # @param ashes     a list of cell lists
    # @param stepsize  binary logarithm of amount of time to coalesce objects
    # @param intergen  binary logarithm of amount of time to run HashLife
    # @param pos       the index of the first soup on the page
    def teenager(self, gsize, gspacing, ashes, stepsize, intergen, pos):

        # For error-correction:
        if (intergen > 0):
            g.setalgo("HashLife")
            g.setrule("B3/S23")

        # If this gets incremented, we panic and perform error-correction:
        pathological = 0

        # Draw the soups:
        for i in xrange(gsize * gsize):

            x = int(i % gsize)
            y = int(i / gsize)

            g.putcells(ashes[3*i], gspacing * x, gspacing * y)

        # Because why not?
        g.fit()
        g.update()

        # For error-correction:
        if (intergen > 0):
            g.setbase(2)
            g.setstep(intergen)
            g.step()

        # Apply rules to coalesce objects and expunge annoyances such as
        # blocks, blinkers, beehives and gliders:
        start_time = time.clock()
        self.census(stepsize)
        end_time = time.clock()
        self.ruletime += (end_time - start_time)

        # Now begin identifying objects:
        start_time = time.clock()
        celllist = g.join(g.getcells(g.getrect()), [0])

        if (len(celllist) > 2):
            for i in xrange(0, len(celllist)-1, 3):
                if (g.getcell(celllist[i], celllist[i+1]) != 0):
                    self.gridobj(celllist[i], celllist[i+1])

        # If we have leftover unidentified objects, attempt to canonise them:
        while (len(self.unids) > 0):
            ux = int(0.5 + float(self.unids[-2])/float(gspacing))
            uy = int(0.5 + float(self.unids[-1])/float(gspacing))
            soupid = ux + (uy * gsize) + pos
            unidname = self.process_unid()
            if (unidname == "PATHOLOGICAL"):
                pathological += 1
            if (unidname != "nothing"):

                if ((unidname[0] == 'U') & (unidname[1] == 'S') & (unidname[2] == 'S')):
                    
                    # Union of standard spaceships:
                    countlist = unidname.split('_')
                    
                    self.incobject("xq4_6frc", int(countlist[1]))
                    for i in xrange(int(countlist[1])):
                        self.awardpoints2(soupid, "xq4_6frc")

                    self.incobject("xq4_27dee6", int(countlist[2]))
                    for i in xrange(int(countlist[2])):
                        self.awardpoints2(soupid, "xq4_27dee6")
                        
                    self.incobject("xq4_27deee6", int(countlist[3]))
                    for i in xrange(int(countlist[3])):
                        self.awardpoints2(soupid, "xq4_27deee6")
                        
                elif ((unidname[0] == 'x') & ((unidname[1] == 's') | (unidname[1] == 'p'))):
                    self.enter_unid(unidname, soupid, False)
                else:
                    if ((unidname[0] == 'x') & (unidname[1] == 'q') & (unidname[3] == '_')):
                        # Separates low-period (<= 9) non-standard spaceships in medium proximity:
                        self.enter_unid(unidname, soupid, True)
                    else:
                        self.incobject(unidname, 1)
                        self.awardpoints2(soupid, unidname)

        end_time = time.clock()
        self.gridtime += (end_time - start_time)

        return pathological

    # This basically orchestrates everything:
    def stabilise_soups_parallel(self, root, pos, gsize):

        ashes = []
        stepsize = 3

        g.new("Random soups")
        g.setalgo("QuickLife")
        g.setrule("B3/S23")

        gspacing = 0

        # Generate and run the soups until stabilisation:
        for i in xrange(gsize * gsize):

            # Generate the soup from the SHA-256 of the concatenation of the
            # seed with the index:
            hashsoup(root + str(pos + i))

            # Run the soup until stabilisation:
            start_time = time.clock()
            stepsize = max(stepsize, self.stabilise3())
            end_time = time.clock()
            self.qlifetime += (end_time - start_time)

            # Ironically, the spelling of this variable is incurrrect:
            currrect = g.getrect()
            ashes.append(g.getcells(currrect))

            if (len(currrect) == 4):
                ashes.append(currrect[0])
                ashes.append(currrect[1])
                # Choose the grid spacing based on the size of the ash:
                gspacing = max(gspacing, 2*currrect[2])
                gspacing = max(gspacing, 2*currrect[3])
                g.select(currrect)
                g.clear(0)
            else:
                ashes.append(0)
                ashes.append(0)
            g.select([])

        # Account for any extra enlargement caused by running CoalesceObjects:
        gspacing += 2 ** (stepsize + 1) + 1000

        # Remember the dictionary, just in case we have a pathological object:
        prevdict = self.objectcounts.copy()
        prevscores = self.soupscores.copy()
        prevunids = self.superunids[:]

        if (self.teenager(gsize, gspacing, ashes, stepsize, 0, pos) > 0):
            # Arrrggghhhh, there's a pathological object!
            # Usually this means that naive stabilisation detection returned a false positive.
            self.resets += 1
            
            # Reset the object counts:
            self.objectcounts = prevdict
            self.soupscores = prevscores
            self.superunids = prevunids

            # 2^18 generations should suffice. This takes about 30 seconds in
            # HashLife, but error-correction only occurs very infrequently, so
            # this has a negligible impact on mean performance:
            gspacing += 2 ** 19
            stepsize = max(stepsize, 12)
            
            # Clear the universe:
            g.new("Error-correcting phase")
            self.teenager(gsize, gspacing, ashes, stepsize, 18, pos)

        # Erase any ashes. Not least because England usually loses...
        ashes = []

    # Pop the last unidentified object from the stack, and attempt to
    # ascertain its period and classify it.
    def process_unid(self):

        g.new("Unidentified object")
        g.setalgo("QuickLife")
        g.setrule("B3/S23")
        y = self.unids.pop()
        x = self.unids.pop()
        livecells = self.unids.pop()
        g.putcells(livecells, -x, -y, 1, 0, 0, 1, "or")
        period = self.bijoscar(1000)
        
        if (period == -1):
            # Infinite growth pattern, probably. The only infinite-growth
            # patterns that have been known to occur naturally in the history
            # of GoL are the block-laying and glider-producing switch-engines,
            # so we include code for identifying probable switch-engines.

            prevpop = g.getpop()
            
            for i in xrange(20):
                g.run(1152)
                currpop = g.getpop()
                diff = int(currpop) - int(prevpop)
                if ((diff != 128) & (diff != 177)):
                    # not a switch engine
                    break
                prevpop = currpop

                if (i == 19):
                    if (diff == 128):
                        # block-laying switch engine
                        return "xq288_gg8ugicy333oooz1o0h8y9355y133zw1yboozwo772zw1"
                    else:
                        # glider-producing switch engine
                        return "xq384_037boz0gg1ya33zg978y988y3ooz0120d52y6gsxgzyf4c4qf6xgy0ggzy233y4eaj44swewg0h0vooog9ezye111y51zyu8kc"

            # Okay, so it's not a switch-engine. It may be an unstabilised
            # ember that slipped through the net, but this will be handled
            # by error-correction (unless it persists another 2^18 gens,
            # which is so unbelievably improbable that you are more likely
            # to be picked up by a passing ship in the vacuum of space).
            self.superunids.append(livecells)
            self.superunids.append(x)
            self.superunids.append(y)
            
            return "PATHOLOGICAL"
        elif (period == 0):
            return "nothing"
        else:
            if (period == -4):

                triple = countxwsses()

                if (triple != (-1, -1, -1)):

                    # Union of Standard Spaceships:
                    return ("USS_" + str(triple[0]) + "_" + str(triple[1]) + "_" + str(triple[2]))

            
            canonised = canonise(abs(period))

            if (canonised == "#"):

                # Okay, we know that it's an oscillator or spaceship with
                # a non-astronomical period. But it's too large to canonise
                # in any of its phases (i.e. transcends a 40-by-40 box).
                self.superunids.append(livecells)
                self.superunids.append(x)
                self.superunids.append(y)
                
                return "OVERSIZED"
            
            else:

                # Prepend a prefix according to whether it is a still-life,
                # oscillator or moving object:
                if (period == 1):
                    return ("xs"+str(len(livecells)/2)+"_"+canonised)
                elif (period > 0):
                    return ("xp"+str(period)+"_"+canonised)
                else:
                    return ("xq"+str(0-period)+"_"+canonised)

    # This doesn't really do much, since unids should be empty and
    # actual pathological/oversized objects will rarely arise naturally.
    def display_unids(self):

        self.unids.extend(self.superunids)

        g.new("Unidentified objects")
        g.setalgo("QuickLife")
        g.setrule("B3/S23")

        rowlength = 1 + int(math.sqrt(len(self.unids)/3))

        for i in xrange(len(self.unids)/3):

            xpos = i % rowlength
            ypos = int(i / rowlength)

            g.putcells(self.unids[3*i], xpos * (self.gridsize + 8) - self.unids[3*i + 1], ypos * (self.gridsize + 8) - self.unids[3*i + 2], 1, 0, 0, 1, "or")

        g.fit()
        g.update()

    # Saves a machine-readable textual file containing the census:
    def save_progress(self, numsoups, root):

        g.show("Saving progress...")

        # Count the total number of objects:
        totobjs = 0
        censustable = "@CENSUS TABLE\n"
        tlist = sorted(self.objectcounts.iteritems(), key=operator.itemgetter(1), reverse=True)
        for objname, count in tlist:
            totobjs += count
            censustable += objname + " " + str(count) + "\n"

        g.show("Writing header information...")

        # The MD5 hash of the root string:
        md5root = hashlib.md5(root).hexdigest()

        # Header information:
        results = "@MD5 "+md5root+"\n"
        results += "@ROOT "+root+"\n"
        results += "@NUM_SOUPS "+str(numsoups)+"\n"
        results += "@NUM_OBJECTS "+str(totobjs)+"\n"

        results += "\n"

        # Census table:
        results += censustable

        g.show("Compactifying score table...")

        results += "\n"

        # Number of soups to record:
        highscores = 1

        results += "@TOP "+str(highscores)+"\n"

        ilist = sorted(self.soupscores.iteritems(), key=operator.itemgetter(1), reverse=True)

        # Empty the high score table:
        self.soupscores = {}
        
        for soupnum, score in ilist[:highscores]:
            self.soupscores[soupnum] = score
            results += str(soupnum) + " " + str(score) + "\n"

        g.show("Writing progress file...")

        dirname = g.getdir("data")
        separator = dirname[-1]
        progresspath = dirname + "apgsearch" + separator + "progress" + separator
        if not os.path.exists(progresspath):
            os.makedirs(progresspath)

        filename = progresspath + "search_" + md5root + ".txt"
        
        try:
            f = open(filename, 'w')
            f.write(results)
            f.close()
        except:
            g.warn("Unable to create progress file:\n" + filename)

        
    # Display results in Help window:
    def display_census(self, numsoups, root):

        results = "<html>\n<title>Census results</title>\n<body bgcolor=\"#FFFFCE\">\n"
        results += "<p>Census results after processing " + str(numsoups) + " soups (with "+str(self.resets)+" resets):\n"

        tlist = sorted(self.objectcounts.iteritems(), key=operator.itemgetter(1), reverse=True)    
        results += "<p><center>\n"
        results += "<table cellspacing=1 border=2 cols=2>\n"
        results += "<tr><td>&nbsp;Object&nbsp;</td><td align=center>&nbsp;Common name&nbsp;</td><td align=right>&nbsp;Count&nbsp;</td></tr>\n"
        for objname, count in tlist:
            if (objname[0] == 'x'):
                if (objname[1] == 'p'):
                    results += "<tr bgcolor=\"#CECECF\">"
                elif (objname[1] == 'q'):
                    results += "<tr bgcolor=\"#CEFFCE\">"
                else:
                    results += "<tr>"
            else:
                results += "<tr bgcolor=\"#FFCECE\">"
            results += "<td>"
            results += "&nbsp;"
            results += objname
            results += "&nbsp;"
            # As a compromise, we only show ASCII art for oscillators and
            # spaceships, not still-lifes. This economises on vertical space
            # and helps to make more interesting objects more prominent.
            if ((objname[0] == 'x') & (objname[1] != 's')):
                # This enables one to click on the ASCII art to open the pattern in Golly:
                results += "<pre><a href=\"lexpatt:\">\n"
                # http://ferkeltongs.livejournal.com/15837.html
                compact = objname.split('_')[1] + "z"
                i = 0
                strip = []
                while (i < len(compact)):
                    c = ord2(compact[i])
                    if (c >= 0):
                        if (c < 32):
                            # Conventional character:
                            strip.append(c)
                        else:
                            if (c == 35):
                                # End of line:
                                if (len(strip) == 0):
                                    strip.append(0)
                                for j in xrange(5):
                                    for d in strip:
                                        if ((d & (1 << j)) > 0):
                                            results += "o"
                                        else:
                                            results += "."
                                    results += "\n"
                                strip = []
                            else:
                                # Multispace character:
                                strip.append(0)
                                strip.append(0)
                                if (c >= 33):
                                    strip.append(0)
                                if (c == 34):
                                    strip.append(0)
                                    i += 1
                                    d = ord2(compact[i])
                                    for j in xrange(d):
                                        strip.append(0)
                    i += 1
                # End of pattern representation:
                results += "</a></pre>\n"
            results += "</td><td align=center>&nbsp;"
            if (objname in self.commonnames):
                results += self.commonnames[objname][0]
            results += "&nbsp;</td><td align=right>&nbsp;" + str(count) + "&nbsp;"
            results += "</td></tr>\n"
        results += "</table>\n</center>\n"

        ilist = sorted(self.soupscores.iteritems(), key=operator.itemgetter(1), reverse=True)
        results += "<p><center>\n"
        results += "<table cellspacing=1 border=2 cols=2>\n"
        results += "<tr><td>&nbsp;Soup number&nbsp;</td><td align=right>&nbsp;Score&nbsp;</td></tr>\n"
        for soupnum, score in ilist[:50]:
            results += "<tr><td>&nbsp;"
            results += root + str(soupnum)
            results += "<pre><a href=\"lexpatt:\">\n"
            hashdigest = hashlib.sha256(root + str(soupnum)).digest()
            for j in xrange(32):
                t = ord(hashdigest[j])
                for k in xrange(8):
                    if (t & (1 << (7 - k))):
                        results += "o"
                    else:
                        results += "."
                if ((j % 2) == 1):
                    results += "\n"
            results += "</a></pre>\n"
            results += "&nbsp;</td><td align=right>&nbsp;" + str(score) + "&nbsp;</td></tr>\n"
        results += "</table>\n</center>\n"
        results += "</body>\n</html>\n"

        dirname = g.getdir("data")
        separator = dirname[-1]
        apgpath = dirname + "apgsearch" + separator
        if not os.path.exists(apgpath):
            os.makedirs(apgpath)
        
        htmlname = apgpath + "latest_census.html"
        try:
            f = open(htmlname, 'w')
            f.write(results)
            f.close()
            g.open(htmlname)
        except:
            g.warn("Unable to create html file:\n" + htmlname)
        

# Converts a base-36 case-insensitive alphanumeric character into a
# numerical value.
def ord2(char):

    x = ord(char)

    if ((x >= 48) & (x < 58)):
        return x - 48

    if ((x >= 65) & (x < 91)):
        return x - 55

    if ((x >= 97) & (x < 123)):
        return x - 87

    return -1




# Obtain the parameters to conduct the search:
number = 1
rootstring = datetime.datetime.now().isoformat()+"_"
# initpos = int(g.getstring("Initial position: ", "0"))
initpos = 0

start_time = time.clock()

soup = Soup()

scount = 0

# We have 100 soups per page, instead of one. This parallel approach
# was suggested by Tomas Rokicki, and results in approximately a
# fourfold increase in soup-searching speed!
sqrtspp = 1
spp = sqrtspp ** 2

# Do stuff repeatedly:
for i in xrange(int((number-1)/spp)+1):
    soup.stabilise_soups_parallel(rootstring, scount + initpos, sqrtspp)
    scount = spp*(i+1)
    g.show(str(scount) + " soups processed ("+str(int(scount/(time.clock() - start_time)))+" per second) : (type 's' to see latest census or 'q' to quit).")

    # Automatically save progress every 250000 soups:
    if ((scount % 250000) == 0):
        soup.save_progress(scount, rootstring)
    
    event = g.getevent()
    if event.startswith("key"):
        evt, ch, mods = event.split()
        if ch == "s":
            soup.save_progress(scount, rootstring)
            soup.display_census(scount, rootstring)
        elif ch == "q":
            break

end_time = time.clock()

# Give the number of soups processed together with the amount of time
# elapsed (and indications as to which parts of the script are taking
# the longest).
g.show(str(scount) + " soups processed in " + str(end_time - start_time) +
       "(" + str(soup.qlifetime) + ", " + str(soup.ruletime) + ", " + str(soup.gridtime) + ") secs.")

soup.save_progress(scount, rootstring)
soup.display_unids()
soup.display_census(scount, rootstring)
You'll also need to add this script to remove headers from RLEs, since the Golly functions only accept "raw" RLEs:

Code: Select all

import golly as g

g.copy()

s = g.getclipstr()

i = 0
while s[i] != '\n':
    i += 1

index = i + 1

g.setclipstr(s[index:len(s)].replace(' ', '').replace('\n', '').replace('\r', ''))
Once you've done that, copy the pattern you want to census, run the header-removing script, then run the modified apgsearch, and it should pop out the results from that pattern.
Wiki: http://www.conwaylife.com/wiki/User:Ian07
Discord: lan07 (yes, that's a lowercase L)

xs24_69bobjob96
GUYTU6J
Posts: 2200
Joined: August 5th, 2016, 10:27 am
Location: 拆哪!I repeat, CHINA! (a.k.a. 种花家)
Contact:

Re: Thread for basic questions

Post by GUYTU6J »

It is usually said that glider syntheses are of two types, incremental and soup-based. But as far as I see there are five groups.

Code: Select all

x = 480, y = 34, rule = B3/S23
409bo$407bobo61bobo$408b2o61b2o$412bo59bo$367bo44bobo$367bobo42b2o
46bo$367b2o92bo3bobo3b2o$409bo49b3o4b2o3bobo$410b2o54bo4bo$135bo
273b2o$133b2o223bo97b3o$134b2o45bo176bobo97bo$122bobo50bobo2bo48bo
123bo4b2o97bo8b2o$123b2o51b2o2b3o46bobo122b2o110b2o$123bo52bo46bo
5b2o42bo3b2o3bo51bo3b2o3bo9b2o52bo3b2o3bo48bo2bo$77bo5bo138bobo8b
2o37bobobo2bobobo7bo41bobobo2bobobo61bobobo2bobobo45b8o$78b2o2bo
100b2o38bo9bobo37bo3b2o3bo8bobo40bo3b2o3bo7bo12b2o41bo3b2o3bo45b
10o$77b2o3b3o88b2o8bobo32b2o13bo34b2o21b2o36b2o19bobo11bobo35b2o
53b2o3bo8bo$172bo2bo7bo33bo2bo7b3o37bobo58bobo18bobo11bo37b2o52bo
2bo4b6o$137bobo35bo44bo7bo40b3o22b2o34b3o18bo69b2o36bo5bo2bo4b4o$
128bo8b2o36bo44bo8bo40b2o22bobo34b2o84b2o2b2o36bo5bo2bo3bo4bo$82b
2o43bobo8bo34bobo42bobo47bobo23bo34bobo70b3o12b2o2bob2o32bobo12bo
3bobo$83b2o41bobo43bobo42bobo47bobo58bobo70bob2o17b3o31bobo14bo3bo
bo$82bo42bobo43bobo42bobo47bobo58bobo70bobo52bobo21bo$41bo84bo12b
2o31bo44bo49bo60bo72b2o53bo22bo$6bo32b2o97b2o130b2o59b2o89b2o52bo
2bo$5bo34b2o47b2o41b2o6bo33b4o41b4o46b4o57b4o72b2o14b2o34b4o15b2o$
5b3o38bo37b2o2bo2bo35b2o2bo2bo38bo4bo39bo4bo44bobo2bo55bobo2bo67b
2o2b2o49bo4bo$obo41b2o38b2o2bo2bo35b2o2bo2bo38bo3bobo38bo3bobo43b
2o2bobo54b2o2bobo66b2o2bob2o47bo3bobo$b2o37b2o3b2o42b2o41b2o40bo3b
obo38bo3bobo47bobo58bobo70b3o48bo3bobo$bo37bobo138bo44bo49b2o59b2o
126bo$39bo140bo44bo48b3o58b3o126bo$38b2o49b2o41b2o43bo2bo41bo2bo
47bobo58bobo70b2o52bo2bo$89b2o41b2o44b2o43b2o48b2o59b2o71b2o53b2o!
#C [[ LABEL 4 50 2 "Type I" ]]
#C [[ LABEL 40 50 2 "Type II" ]]
#C [[ LABEL 80 50 2 "Type IV" ]]
#C [[ LABEL 130 50 2 "Type V" ]]
#C [[ LABEL 467 50 2 "Type III" ]]
Type II is soup-based, type III is incremental. Type I is for those very direct syntheses, often a result of enumerated glider collisions. Type IV makes a pseudo object by placing one or more objects beside others. Type V puts many synchronized reactions together to yield the product; the reactions are either direct results from glider collisions (e.g. in the 8G loafer recipe), or results from glider+other object collisions (e.g. in the 17G Rob's p16 recipe). Nowadays very notable syntheses involve lots of type III and IV steps before the final type V activation.
This classification may suggest an understanding of difficulties in syntheses. Type I has no difficulty because all syntheses in this category are known. Type IV is a bit harder because one needs to consider how to place the object(s) with correct clearance. Type III requires good memory (or yet-to-come computer scripts). The most difficult is the type V; it needs skilled treatment with predecessor-finding programmes.
User avatar
gameoflifemaniac
Posts: 1249
Joined: January 22nd, 2017, 11:17 am
Location: There too

Re: Thread for basic questions

Post by gameoflifemaniac »

Is a single-engine Cordership possible?
I was so socially awkward in the past and it will haunt me for the rest of my life.

Code: Select all

b4o25bo$o29bo$b3o3b3o2bob2o2bob2o2bo3bobo$4bobo3bob2o2bob2o2bobo3bobo$
4bobo3bobo5bo5bo3bobo$o3bobo3bobo5bo6b4o$b3o3b3o2bo5bo9bobo$24b4o!
User avatar
dvgrn
Moderator
Posts: 12023
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Thread for basic questions

Post by dvgrn »

gameoflifemaniac wrote: May 12th, 2020, 8:06 am Is a single-engine Cordership possible?
This has been asked and answered already. It's either

"theoretically possible, but increasingly unlikely the longer apgsearch runs"

or

"definitely possible with a silly amount of engineering"

depending on your definition of "single-engine Cordership".
User avatar
apg
Moderator
Posts: 3007
Joined: June 1st, 2009, 4:32 pm

Re: Thread for basic questions

Post by apg »

dvgrn wrote: May 12th, 2020, 8:33 am "definitely possible with a silly amount of engineering"

depending on your definition of "single-engine Cordership".
I think it's possible with a non-silly amount of engineering. It should be possible to have a Geminoid-like thing (except where the tape bounces parallel to the direction of travel, instead of perpendicular) which chases a BLSE using a slow-salvo:

Code: Select all

x = 554, y = 576, rule = B3/S23
5bo$6bob2obobo$3o3b4o3bo$6bo2b2ob2o$5bo$3b3o$3b3o17b2o$23b2o7$31b2o$
31b2o$19b4o$23bo$5bob2ob2o7bo4bo$4b5o2b2o7b2obo$4bo2b3obo10bo$10bo$39b
2o$39b2o3$11b2o$11b2o16b2o$29b2o2$47b2o$25b2o20b2o$25b2o2$19b2o$19b2o$
44b2o$44b2o3$40b2o$40b2o5$63b2o$63b2o3$35b2o$35b2o16b2o$53b2o2$71b2o$
49b2o20b2o$49b2o2$43b2o$43b2o$68b2o$68b2o3$64b2o$64b2o5$87b2o$87b2o3$
59b2o$59b2o16b2o$77b2o2$95b2o$73b2o20b2o$73b2o2$67b2o$67b2o$92b2o$92b
2o3$88b2o$88b2o5$111b2o$111b2o3$83b2o$83b2o16b2o$101b2o2$119b2o$97b2o
20b2o$97b2o2$91b2o$91b2o$116b2o$116b2o3$112b2o$112b2o5$135b2o$135b2o3$
107b2o$107b2o16b2o$125b2o2$143b2o$121b2o20b2o$121b2o2$115b2o$115b2o$
140b2o$140b2o3$136b2o$136b2o5$159b2o$159b2o3$131b2o$131b2o16b2o$149b2o
3$145b2o$145b2o2$139b2o$139b2o58$191b3o$191bo$192bo31$216b3o$216bo$
217bo34$252b3o$252bo$253bo19$272b3o$272bo$273bo19$292b3o$292bo$293bo
19$312b3o$312bo$313bo19$332b3o$332bo$333bo19$352b3o$352bo$353bo19$372b
3o$372bo$373bo19$392b3o$392bo$393bo19$412b3o$412bo$413bo19$432b3o$432b
o$433bo19$452b3o$452bo$453bo19$472b3o$472bo$473bo18$509b3o$509bo$510bo
18$531b3o$531bo$532bo18$551b3o$551bo$552bo!
What do you do with ill crystallographers? Take them to the mono-clinic!
User avatar
dvgrn
Moderator
Posts: 12023
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Thread for basic questions

Post by dvgrn »

calcyman wrote: May 12th, 2020, 9:15 amI think it's possible with a non-silly amount of engineering. It should be possible to have a Geminoid-like thing (except where the tape bounces parallel to the direction of travel, instead of perpendicular) which chases a BLSE using a slow-salvo...
To clarify, the "Geminoid-like thing" would build a set of reflectors out of that target block extracted from the BLSE -- right? Another set of reflectors would be already constructed (?) way back where the slow salvo came from.

Then it would transfer its data into the new loop, shoot down its old loop, and also build something that shoots down all the BLSE's debris... fast enough that it can all be removed within one replication cycle of the Geminoid-like thing.

... This might fit perfectly well into my definition of "silly amount of engineering", actually. But I guess it's technically possible, and I can see how it might be a less silly amount of engineering than some other possible projects, like a width-1 spaceship for example.
User avatar
gameoflifemaniac
Posts: 1249
Joined: January 22nd, 2017, 11:17 am
Location: There too

Re: Thread for basic questions

Post by gameoflifemaniac »

If making a quadratic replicator using the 0E0P metacell is overkill, maybe one could make a smaller one?
I was so socially awkward in the past and it will haunt me for the rest of my life.

Code: Select all

b4o25bo$o29bo$b3o3b3o2bob2o2bob2o2bo3bobo$4bobo3bob2o2bob2o2bobo3bobo$
4bobo3bobo5bo5bo3bobo$o3bobo3bobo5bo6b4o$b3o3b3o2bo5bo9bobo$24b4o!
User avatar
dvgrn
Moderator
Posts: 12023
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Thread for basic questions

Post by dvgrn »

gameoflifemaniac wrote: May 12th, 2020, 10:08 am If making a quadratic replicator using the 0E0P metacell is overkill, maybe one could make a smaller one?
Sure! Pavgran was working on one a while back that should run quite well in Golly. It just needs a good chunk of research put into solving the remaining "trivial" timing problems.
User avatar
apg
Moderator
Posts: 3007
Joined: June 1st, 2009, 4:32 pm

Re: Thread for basic questions

Post by apg »

gameoflifemaniac wrote: May 12th, 2020, 10:08 am If making a quadratic replicator using the 0E0P metacell is overkill, maybe one could make a smaller one?
Most of the complexity of the 0E0P metacell is a result of having the DNA tightly rolled up into a compact nucleus. A quadratic replicator would need to either:

(a) solve these problems all over again and end up just as complicated;
(b) have a diameter linear in the tape length, which opens up a completely different set of problems.

Otherwise, you'll end up with something similar to Hutton-replicator, which has initial exponential growth followed by long-term linear growth as a result of different instances competing for empty space.
What do you do with ill crystallographers? Take them to the mono-clinic!
User avatar
dvgrn
Moderator
Posts: 12023
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Thread for basic questions

Post by dvgrn »

calcyman wrote: May 12th, 2020, 11:01 am
gameoflifemaniac wrote: May 12th, 2020, 10:08 am If making a quadratic replicator using the 0E0P metacell is overkill, maybe one could make a smaller one?
Most of the complexity of the 0E0P metacell is a result of having the DNA tightly rolled up into a compact nucleus. A quadratic replicator would need to either:

(a) solve these problems all over again and end up just as complicated;
(b) have a diameter linear in the tape length, which opens up a completely different set of problems.
Yeah, I'm not too interested in the linear option -- there are already plenty of self-constructing things that look like long boring lines.

However, there's an intermediate option between (a) and (b). You can make the DNA storage diamond-shaped, with no HashLife-killing back and forth glider streams. The resulting replicator is not nearly as complex as the 0E0P, and Golly should be able to run it through a replication cycle in seconds or minutes, not weeks or months. The "HalfLoopStepRep" archive contains some rules and patterns that simulate the types of quadratic growth that something like this can generate, depending on whether the diamond loops are self-destructing or not.

Isn't it true that any possible quadratic-growth pattern will slow down eventually due to competition between descendants for limited space?
User avatar
gameoflifemaniac
Posts: 1249
Joined: January 22nd, 2017, 11:17 am
Location: There too

Re: Thread for basic questions

Post by gameoflifemaniac »

Can we make a smaller linear propagator using Snarks instead of the Silver reflector?
I was so socially awkward in the past and it will haunt me for the rest of my life.

Code: Select all

b4o25bo$o29bo$b3o3b3o2bob2o2bob2o2bo3bobo$4bobo3bob2o2bob2o2bobo3bobo$
4bobo3bobo5bo5bo3bobo$o3bobo3bobo5bo6b4o$b3o3b3o2bo5bo9bobo$24b4o!
User avatar
dvgrn
Moderator
Posts: 12023
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Thread for basic questions

Post by dvgrn »

gameoflifemaniac wrote: May 13th, 2020, 4:51 am Can we make a smaller linear propagator using Snarks instead of the Silver reflector?
Probably. I don't know how much smaller it would end up being, though. The linear propagator's recipes were all hand-optimized, and the 9fd elbow-op toolkit that it uses is more efficient than anything that slsparse knows about.

And very likely the old Spartan Silver reflector is cheaper to build than either a pair of Snarks (for the 180-degree reflector in the SE) or most other known merge circuits (for the mechanism that inserts a copy of the construction data into the child propagator).

So I'm sure someone could reduce the size of the linear propagator somehow, but it would be a lot of work and the results might not be too impressive. An interesting option might be to rework the structure to be diamond-shaped instead of just a Long Boring Line (TM), so that it runs really fast in Golly. But implementing that would make the bounding box bigger, not smaller, at least if it was done with single-channel recipes compiled by slsparse.

The bounding box could be made much smaller by building a bank of reflectors at both ends instead of just a single pair of reflectors. But then the pattern would run much slower in Golly -- not sure about StreamLife, I forget how far apart the streams have to be before StreamLife can take shortcuts safely.
User avatar
Moosey
Posts: 4314
Joined: January 27th, 2019, 5:54 pm
Location: almsworth uk
Contact:

Re: Thread for basic questions

Post by Moosey »

Since life is Turing Complete, would it be reasonable to construct a pattern with speed ~c/TREE(3) ? If so, how large would the pattern have to be (in its minimal size, by bounding box)
κ is measurable iff there is a nontrivial elementary embedding j:V→M (M transitive) with critical point κ
pew
Posts: 17
Joined: May 8th, 2020, 7:58 pm

Re: Thread for basic questions

Post by pew »

Ian07 wrote: May 9th, 2020, 2:01 pm
Sweet! I'll check it out. Thanks.


A minor question about the catalogue site. I've been perusing my hauls and believe I found the first occurrence of (xs28_cc0v1u0o4kozx121074), but it does not show up under my discoveries. What qualifies to appear under there? (I have two xp2's in there.)

Also, when using a script like gfind or qfind, does it use some kind of random seeding to search or when you run it does it do the same work every time (that presumably another user has already tried.)
User avatar
Saka
Posts: 3626
Joined: June 19th, 2015, 8:50 pm
Location: Indonesia
Contact:

Re: Thread for basic questions

Post by Saka »

pew wrote: May 15th, 2020, 3:13 am
A minor question about the catalogue site. I've been perusing my hauls and believe I found the first occurrence of (xs28_cc0v1u0o4kozx121074), but it does not show up under my discoveries. What qualifies to appear under there? (I have two xp2's in there.)

Also, when using a script like gfind or qfind, does it use some kind of random seeding to search or when you run it does it do the same work every time (that presumably another user has already tried.)
1. For still lives, only new still lives that are larger than 30 cells are listed in the Discoveries section.

2. No, they do not use any random seeding. If you run the same search, you will get the same output.
jarring cyan
Hunting
Posts: 4401
Joined: September 11th, 2017, 2:54 am

Re: Thread for basic questions

Post by Hunting »

pew wrote: May 15th, 2020, 3:13 am Also, when using a script like gfind or qfind, does it use some kind of random seeding to search or when you run it does it do the same work every time (that presumably another user has already tried.)
No. In fact, with them you can eliminate the possibility of a spaceship existing in the specified restrictions.

gfind and qfind are not just "try random patterns and see what happens", they are search algorithms.
GUYTU6J
Posts: 2200
Joined: August 5th, 2016, 10:27 am
Location: 拆哪!I repeat, CHINA! (a.k.a. 种花家)
Contact:

Re: Thread for basic questions

Post by GUYTU6J »

A little question:
dvgrn wrote: July 3rd, 2019, 10:06 am
Moosey wrote:If we did, would it be CC or CP? ...
[W]ould a slightly smaller CC reflector be really awesome? Or is it fine to stay with CC cenarks?
It would be color-changing. You can tell from the inside corner of an intersection of glider tracks like the one shown above. If the inside corner is one cell wide, it's color-preserving, like a Snark; if it's two cells wide, it's color-changing. ...
This method is indeed simple enough to tell the relative color (preserved/changed) of the input&output gliders of a reflector. It does not rely on the definition of absolute color given in the wiki for glider. Then, what's the point of defining the "absolute" color of a glider, if its difference is actually what we care about?
Post Reply