3
\$\begingroup\$

Golly is a cross-platform, Python and Perl extendable (like in this case, Python), free Conway's Game of Life simulator written in C++ (or Python) that runs in arbitrary speed and large, marvelous patterns.

Herschel is a pattern named after William Herschel (the heptomino resembles Saturn's planetary symbol ♄, and somewhere in the naming process Saturn got confused with Uranus, which was discovered by Herschel). Herschels frequently appear in the evolution of chaotic patterns.

The following script is a Golly script that searches for positions where one in a list of still lifes can create a Herschel (or another pattern specified in the "herschels" list) when reacting with the pattern already in the board.

import golly as g
from sys import exit

herschels = []

testrect = g.getselrect();
if testrect == []:
    g.exit("Select the area where the still life is placeable!")

if g.empty():
    g.exit("There is no pattern to add bait!")

stillLifes = []

def addStillLife():
    while True:
        stillLife = g.getstring("What still life to test for? (RLE)", "2o$2o!", "HRF v0.1")
        stillLifes.append(g.parse(stillLife))
        addAnother = g.getstring("Add another still life? (y|n)", "n", "HRF v0.1")
        if addAnother.lower() == "n":
            break
        elif addAnother.lower() != "y":
            g.exit("Invalid letter!")

def removeFirstLine(str):
    result = str.rsplit('\n')
    result.pop(0)
    return ''.join(result)

def addResultDesired():
    while True:
        herschelDesired = g.getstring("What result to find? (RLE)", "3o$obo$obo!", "HRF v0.1")
        herschels.append(g.parse(herschelDesired))
        addAnother = g.getstring("Add another result desired? (y|n)", "n", "HRF v0.1")
        if addAnother.lower() == "n":
            break
        elif addAnother.lower() != "y":
            g.exit("Invalid letter!")

addStillLife()
addResultDesired()
debug = g.getstring("Debug mode? (y|n)", "n", "HRF v0.1")
if debug.lower() == "y":
    g.autoupdate(True)
elif debug.lower() != "n":
    g.exit("Invalid letter!")

def testReaction(xpos, ypos, sli):
    orig = g.getcells(g.getrect())
    g.putcells(stillLifes[sli], xpos, ypos)
    this = g.getcells(g.getrect())
    i = 0
    oldgeneration = g.getgen()
    while i < 301:
        g.step()
        for y in herschels:
            if g.getcells(g.getrect()) == y:
                break
        i += 1
    if i == 301:
        g.show("No result found at x:" + str(xpos) + ", y:" + str(ypos) + ", still life " + str(sli))
    g.clear(0)
    g.clear(1)
    if i == 301:
        this = g.putcells(orig)
    else:A
        g.show("Found reaction!")
        g.putcells(this)
    g.setgen(oldgeneration)


n = 0
while n < testrect[2]:
    m = 0
    while m < testrect[3]:
        o = 0
        while o < len(stillLifes):
            testReaction(n + testrect[0], m + testrect[1], o)
            o += 1
        m += 1
    n += 1
\$\endgroup\$
3
\$\begingroup\$

Iterations

The pythonic way to iterate is to use the for keyword, usually without bothering about indices. I suggest you have a look at the presentation : Ned Batchelder: Loop like a native: while, for, iterators, generators (PyCon US) (video on Youtube/text with slides).

In your case, most loops can be rewritten. For instance :

n = 0
while n < testrect[2]:
    m = 0
    while m < testrect[3]:
        o = 0
        while o < len(stillLifes):
            testReaction(n + testrect[0], m + testrect[1], o)
            o += 1
        m += 1
    n += 1

could be written :

for n in range(testrect[2])
    for m in range(testrect[3]):
        for o, _ in enumerate(stillLifes):
            testReaction(n + testrect[0], m + testrect[1], o)

Also, the for loop accepts an optional else block after it which is called when the loop exited with no break (just because the thing we are iterating over has been fully consumed). This can be useful to rewrite :

i = 0
while i < 301:
    g.step()
    for y in herschels:
        if g.getcells(g.getrect()) == y:
            break
    i += 1
if i == 301:
    g.show("No result found at x:" + str(xpos) + ", y:" + str(ypos) + ", still life " + str(sli))

in a more straightforward way (and without repeating 301 in 2 places) :

for i in range(300)
    g.step()
    for y in herschels:
        if g.getcells(g.getrect()) == y:
            break
else:
    g.show("No result found at x:" + str(xpos) + ", y:" + str(ypos) + ", still life " + str(sli))

Disclaimer for the snipper above: I may have misread your original code so this might be wrong.

User input

At the moment, there are a few issues with the way you deal with user input.

  • you have to provide "HRF v0.1" several times.

You could write a function to hide implementation details and remove duplicated logic :

def get_user_string(prompt, initial):
    title = "HRF v0.1"
    return g.getstring(prompt, initial, title)
  • You are calling lower() on the same string multiple times. If only the lowercase version of the string is interesting to you, you could simple do : addAnother = get_user_string("Add another result desired? (y|n)", "n").lower().

  • You have logic to ask boolean input and check it in multiple places

  • Your program stops in case of invalid logic, it might be better to ask the user to re-input something.

In order to fix these issues, you could define something like :

def get_user_string(prompt, initial):
    title = "HRF v0.1"
    return g.getstring(prompt, initial, title)

def get_user_bool(prompt, initial):
    while True:
        string = get_user_string(prompt, initial).lower()
        if string == "n":
            return False:
        elif string == "y":
            return True
        else:
            golly.error("Invalid input.")

That you could use like this :

    addAnother = get_user_bool("Add another result desired? (y|n)", "n")
    if not addAnother:
        break

or this :

if get_user_bool("Debug mode? (y|n)", "n"):
    g.autoupdate(True)

This shows a better Separation of concerns.

Code organisation

Instead of dealing with global variables, it would be cleaner to try to find alternatives. In your case, using function argument and return values should make things clearer.

You could define :

def get_user_still_lifes():
    stillLifes = []
    while True:
        stillLife = get_user_string("What still life to test for? (RLE)", "2o$2o!")
        stillLifes.append(g.parse(stillLife))
        if not get_user_bool("Add another still life? (y|n)", "n"):
            break
    return stillLifes

def get_user_desired_results():
    herschels = []
    while True:
        herschelDesired = get_user_string("What result to find? (RLE)", "3o$obo$obo!")
        herschels.append(g.parse(herschelDesired))
        if not get_user_bool("Add another result desired? (y|n)", "n"):
            break
    return herschels

and use it like this :

stillLifes = get_user_still_lifes()
herschels = get_user_desired_results()

Also it is a good habit to put the part of your code actually doing things behind an if __name__ == "__main__" guard.

Style

Python has a style guide called PEP 8. It is an interesting read and it is probably worth trying to follow it if you don't have a good reason not to. For instance, your code does not follow the naming convention. You'll find various tools to check your code compliancy to PEP 8.

Final (untested) code

The following code has not been tested, feel free to edit it to fix errors and typos I may have introduced.

import golly as g
from sys import exit


def get_user_string(prompt, initial):
    title = "HRF v0.1"
    return g.getstring(prompt, initial, title)

def get_user_bool(prompt, initial):
    while True:
        string = get_user_string(prompt, initial).lower()
        if string == "n":
            return False:
        elif string == "y":
            return True
        else:
            golly.error("Invalid input.")

def get_user_rectangle():
    while True:
        rect = g.getselrect();
        if rect == []:
            golly.error("Select the area where the still life is placeable!")
        elif g.empty():
            g.error("There is no pattern to add bait!")
        else:
            return rect

def get_user_still_lifes():
    still_lifes = []
    while True:
        stillLife = get_user_string("What still life to test for? (RLE)", "2o$2o!")
        still_lifes.append(g.parse(stillLife))
        if not get_user_bool("Add another still life? (y|n)", "n"):
            break
    return still_lifes

def get_user_desired_results():
    herschels = []
    while True:
        herschelDesired = get_user_string("What result to find? (RLE)", "3o$obo$obo!")
        herschels.append(g.parse(herschelDesired))
        if not get_user_bool("Add another result desired? (y|n)", "n"):
            break
    return herschels

def test_reaction(still_lifes, herschels, xpos, ypos, sli):
    orig = g.getcells(g.getrect())
    g.putcells(still_lifes[sli], xpos, ypos)
    this = g.getcells(g.getrect())
    oldgeneration = g.getgen()
    for i in range(300)
        g.step()
        for y in herschels:
            if g.getcells(g.getrect()) == y:
                break
    else:
        g.show("No result found at x:" + str(xpos) + ", y:" + str(ypos) + ", still life " + str(sli))
    g.clear(0)
    g.clear(1)
    if i == 301:
        this = g.putcells(orig)
    else:A
        g.show("Found reaction!")
        g.putcells(this)
    g.setgen(oldgeneration)

if __name__ == "__main__":
    testrect = get_user_rectangle()
    still_lifes = get_user_still_lifes()
    herschels = get_user_desired_results()
    if get_user_bool("Debug mode? (y|n)", "n"):
        g.autoupdate(True)

    for n in range(testrect[2])
        for m in range(testrect[3]):
        for o, _ in enumerate(still_lifes):
            test_reaction(still_lifes, herschels, n + testrect[0], m + testrect[1], o)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.