Slow Salvo Gliders

For discussion of specific patterns or specific families of patterns, both newly-discovered and well-known.
User avatar
The Turtle
Posts: 102
Joined: May 6th, 2015, 8:14 pm
Location: Chicago, Illinois

Re: Slow Salvo Gliders

Post by The Turtle » May 13th, 2015, 7:32 pm

Thanks, chris_c!
I can't run the program though. It gives me this error message:

Code: Select all

Traceback (most recent call last):
 File "<string>", line 1, in <module>
 File "C:\Users\*my name*\Desktop\Game of Life\Python\Scripts\Slow Salvo Gliders Updated.py", line 192, in <module> 
 for parity in range(period):
TypeError: range() integer end argument expected, got NoneType.
Only two things are constant: change and the speed of light.

chris_c
Posts: 966
Joined: June 28th, 2014, 7:15 am

Re: Slow Salvo Gliders

Post by chris_c » May 13th, 2015, 8:14 pm

The Turtle wrote:Thanks, chris_c!
I can't run the program though. It gives me this error message:

Code: Select all

TypeError: range() integer end argument expected, got NoneType.
That's strange. I'm not seeing that error on my computer. How about changing the line:

Code: Select all

if new_period != 1:
to

Code: Select all

if new_period is None or new_period == 2:
In the original version of the script it was just:

Code: Select all

if new_period is None:
and I gather that worked ok for you. Hopefully the new suggestion will too.

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

Re: Slow Salvo Gliders

Post by dvgrn » May 14th, 2015, 1:01 pm

dvgrn wrote:The block-move searches have already been done up to 9 gliders -- and they're all "atomic" recipes, so there aren't any that start with a (2,1) move. They're all trivially transformable to honeyfarm-target recipes.
Except the (2,1) atomic move itself, of course. That's the only atomic move that gets a lot more expensive if honeyfarms are used as the target -- but of course it's one of the most useful recipes out there. Strings of (2,1) moves are the cheapest recipes for a fairly large variety of different block moves toward the glider source.

So it's probably not worth losing the (2,1) target pull option, just to reduce the glider-output recipe from five gliders to four. Honeyfarms can do longer target pulls efficiently -- three-glider (12,5) and (9,8) pulls, along with sideways (7,-8) and (11,-12) moves -- but of course blocks can do the exact same thing.

In the long run simsim314's idea of multiple target types is a good one -- either block-based turns or honeyfarm-based turns could be used, depending on which one is more efficient, since it's theoretically easy to convert back and forth between the two objects. But it definitely makes the cost calculation a lot more complicated, and the business of optimization in general gets to be a big headache.

Really the absolute most efficient elbow recipe for any specific multi-glider slow salvo will probably be a thoroughly random-looking reaction. First create a medium-sized random-looking explosion, then aim a few gliders at the scattered junk... for many salvos, it might be possible for the total cost to drop below two input gliders per output glider. But it would be hugely costly to run the search to find just the right scattering of junk for each possible N-glider output salvo.

-- So really it seems reasonable to stick with a single simple block target at each elbow, for the time being!

Maybe the next useful step would be a script that takes a slow-salvo glider recipe, S1, as input -- i.e., an arbitrary list of glider lanes -- and outputs a longer slow salvo recipe S2 that can be aimed at a block to create S1. Then the same script can be run on S2 to create an S3 salvo, and so forth, for as many elbows as you might want.

User avatar
The Turtle
Posts: 102
Joined: May 6th, 2015, 8:14 pm
Location: Chicago, Illinois

Re: Slow Salvo Gliders

Post by The Turtle » May 14th, 2015, 4:20 pm

Chris_c,
Your modified program works now.
Thanks!
Only two things are constant: change and the speed of light.

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

Re: Slow Salvo Gliders

Post by dvgrn » May 15th, 2015, 10:29 am

The Turtle wrote:Chris_c,
Your modified program works now.
Thanks!
It's too bad that the program can't find the 5-glider turner recipe, or any other similar cases that might be out there. It might be interesting to adjust the program so it allows single glider outputs, in just one 90-degree direction, with the search continuing as long as the population or area of the leftover junk doesn't get too large.

Seems to me that that would fairly quickly build up a large collection of hyperefficient recipes for sideways slow salvos -- two gliders with an offset of +5 lanes, three gliders with offset of +5 then -8, etc., etc.

Compiling a full slow-salvo recipe would then be a matter of finding combinations like the above (N, N+5,N-8) in the recipe you want to build, and getting a block to one of the right locations to start the sequence.

Then the problem would be to shoot down leftover junk in the most efficient way to get back to a single block in the right place to start the next sequence.

-- I have no evidence yet that this would actually produce shorter salvos. But depending on how many combination salvos we find, we might be able to replace a large number of [glider recipe + block move]*3 (about 30 gliders) with just [combined glider recipe] + block move (12-15 gliders).

Does this new version of the search script find all mirror-image recipes, or is there a restriction that prevents that? If mirror-image recipes currently show up, that would make it easy to add the restriction about output in one direction only.

chris_c
Posts: 966
Joined: June 28th, 2014, 7:15 am

Re: Slow Salvo Gliders

Post by chris_c » May 15th, 2015, 1:04 pm

dvgrn wrote: It's too bad that the program can't find the 5-glider turner recipe, or any other similar cases that might be out there. It might be interesting to adjust the program so it allows single glider outputs, in just one 90-degree direction, with the search continuing as long as the population or area of the leftover junk doesn't get too large.
I have code that strips out gliders and carries on shooting at the remaing junk from the demonoid project, but in that case the artilliery was glider pairs separated by 10hd. It shouldn't be too difficult to make a hybrid of the two programs. I made a stab at doing the necessary coding this morning but when I realised it would take more than a few minutes I gave up. I might get it done over the weekend.

User avatar
The Turtle
Posts: 102
Joined: May 6th, 2015, 8:14 pm
Location: Chicago, Illinois

Re: Slow Salvo Gliders

Post by The Turtle » May 15th, 2015, 4:41 pm

chris_c,
Is it possible to organize the slow salvo results based on how far the block moves?
Is it possible to attach the Glue results to your slow salvos to return the block (and then organize them by glider number)?
Thanks for your time and effort!
The Turtle :wink:
Only two things are constant: change and the speed of light.

chris_c
Posts: 966
Joined: June 28th, 2014, 7:15 am

Re: Slow Salvo Gliders

Post by chris_c » May 15th, 2015, 6:50 pm

The Turtle wrote:chris_c,
Is it possible to organize the slow salvo results based on how far the block moves?
Is it possible to attach the Glue results to your slow salvos to return the block (and then organize them by glider number)?
Thanks for your time and effort!
The Turtle :wink:
I did no reorganisation of the results but this version of the script outputs lines containing x, y and #gliders to a file specified at the top of the script. If you can provide a CSV table containing the number of gliders needed for each block move then it would be easy to incorporate into the script. Oh, by the way, this version of the script only outputs recipes with the glider heading SE.

Code: Select all

import golly as g
from hashlib import sha256
from itertools import chain

OUTFILE = open("/home/user/life/slow_salvo.txt", "w")

#arbitrary numbers
MAX_GENERATIONS = 256
MAX_POPULATION = 40
MAX_GLIDERS = 6

#NE glider
GLIDER = g.parse('3o$2bo$bo!')

#put any ad-hoc patterns that you want to bombard with slow gliders here.
TARGET_PATTERNS = []#('known_splitter', 'bo$obo$b2o$5bo$4bobo$5bobo$6b2o!')]

#put simple targets here, along with rotational symmetry
SIMPLE_TARGETS = [
  ('block', '2o$2o!', 4),
#  ('blinker', '3o$!', 4),
#  ('tub', 'bo$obo$bo!', 4),
#  ('boat', 'b2o$obo$bo!', 1),
#  ('hive', 'b2o$o2bo$b2o!', 2),
#  ('ship', 'b2o$obo$2o!', 2),
#  ('loaf', 'b2o$o2bo$bobo$2bo!', 1),
#  ('lboat', '2b2o$bobo$obo$bo!', 1),
#  ('pond', 'b2o$o2bo$o2bo$b2o!', 4),
# ('tlight', '4bo$4bo$4bo2$3o3b3o2$4bo$4bo$4bo!', 4),
# ('hfarm', '6bo$5bobo$5bobo$6bo2$b2o7b2o$o2bo5bo2bo$b2o7b2o2$6bo$5bobo$5bobo$6bo!', 4),
]

def get_pattern_variants(cells, symmetry):
  variants = []
  for t in range(0, 4, symmetry):
    variants.append(cells)
    cells = g.transform(cells, 0, 0, 0, -1, 1, 0)
  return variants

TARGETS = []
for name, pattern in TARGET_PATTERNS:
  cells = g.parse(pattern)
  p = len(cells) / 2
  TARGETS.append((name, cells, p))

for name, pattern, sym in SIMPLE_TARGETS:
  cells = g.parse(pattern)
  variants = get_pattern_variants(cells, sym)
  for i, v in enumerate(variants):
    p = len(v) / 2
    TARGETS.append((name+str(i), v, p))
 
def patterns_identical(cells1, cells2):
  if len(cells1) != len(cells2):
    return False
  if sum(cells1) != sum(cells2):
    return False
  return sorted(zip(cells1[::2], cells1[1::2])) == sorted(zip(cells2[::2], cells2[1::2]))

def get_pattern_period(cells):
  temp_cells = cells
  for p in range(0, 2):
    temp_cells = g.evolve(temp_cells, 1)
    if patterns_identical(cells, temp_cells):
      return p+1
  return None

def get_shooting_range(cells):

  min_d1 = max_d1 = cells[0] + cells[1]
  min_d2 = cells[0] - cells[1]

  for i in range(2, len(cells), 2):
    min_d1 = min(min_d1, cells[i] + cells[i+1])
    max_d1 = max(max_d1, cells[i] + cells[i+1])
    min_d2 = min(min_d2, cells[i] - cells[i+1])
 
  min_lane = min_d1 - 6
  max_lane = max_d1 + 3
  shift = 6 - min_d2 // 2

  return min_lane, max_lane, shift

def get_pattern_to_try(cells, lane, parity, offset=50):
  glider = g.transform(GLIDER, lane - lane // 2 - offset, lane // 2 + offset)
  if parity % 2:
    glider = g.evolve(glider, 1)
  return list(chain(cells, glider))

offset = 0

def display_solution(start, lanes, debug, last_cells):

  global offset

  print >>OUTFILE, min(last_cells[::2]), min(last_cells[1::2]), len(lanes)
  OUTFILE.flush()

  cells = [c for n, c, _ in TARGETS if n == start][0]
  i = 100
  for lane in lanes:
    lane_num, parity = lane
    cells = get_pattern_to_try(cells, lane_num, parity, i)
    i += 100
  g.putcells(cells, 0, offset)
  for i, p in enumerate(debug):
    g.putcells(p, 100 + 100 * i, offset)
  g.putcells(last_cells, 100 + 100 * len(debug), offset)
  g.fit()
  g.update()
  g.show(' '.join(chain([str(start), str(len(lanes))], [str(lane) for lane in lanes])))
  offset += 400


randoms = []
for i in range(4096):
  randoms.append(int(sha256(str(i)).hexdigest()[:16], 16))

def to_hashable(cells):
  if not cells:
    return 0

  minx = min(cells[::2])
  miny = min(cells[1::2])
 
  hash = 0
  for i in range(0, len(cells), 2):
    hash ^= randoms[64 * (cells[i] & 63) + (cells[i+1] & 63)]

  return hash

def deltas(cells):
  return len(cells), sum(cells[::2]), sum(cells[1::2])

def bombard_final(start, lanes, cells, period, debug, flipx, flipy):

  cells = g.transform(cells, 0, 0, flipx, 0, 0, flipy)

  min_lane, max_lane, shift = get_shooting_range(cells)
 
  for lane_num in range(min_lane, max_lane + 1):

    for parity in range(period):
     
      lane = (lane_num, parity)
      start_cells = get_pattern_to_try(cells, lane[0], lane[1], shift)
      new_cells = g.evolve(start_cells, MAX_GENERATIONS)

      if len(new_cells) == 18:
        n1, dx1, dy1 = deltas(new_cells)
        n2, dx2, dy2 = deltas(g.evolve(new_cells, 4))
        if n1 != n2:
          continue
        dx = dx2-dx1
        dy = dy2-dy1
        if (dx, dy) == (5, 5):

          #Success??

          #flip back for display purposes
          start_cells = g.transform(start_cells, 0, 0, flipx, 0, 0, flipy)
          new_cells = g.transform(new_cells, 0, 0, flipx, 0, 0, flipy)
          #add
          debug.append(start_cells)
          lanes.append(lane)
          #display
          display_solution(start, lanes, debug, new_cells)
          #remove
          lanes.pop()
          debug.pop()

g.new('')

new_queue = []
for name, cells, _ in TARGETS:
  period = get_pattern_period(cells)
  new_queue.append( (name, [], cells, period, []) )

seen = set()

for n in range(MAX_GLIDERS):

  queue = new_queue
  new_queue = []
 
  count = 0

  for start, lanes, last, period, debug in queue:
 
    g.show(str((n+1,count,len(queue))))
    count += 1

    min_lane, max_lane, shift = get_shooting_range(last)

    for lane_num in range(min_lane, max_lane + 1):

      for parity in range(period):
       
        lane = (lane_num, parity)
        start_cells = get_pattern_to_try(last, lane[0], lane[1], shift)
        new_cells = g.evolve(start_cells, MAX_GENERATIONS)

        if not new_cells or len(new_cells) > 2 * MAX_POPULATION:
          continue

        new_period = get_pattern_period(new_cells)
        if new_period is None or new_period == 2:
          continue

        new_hashable = to_hashable(new_cells)       

        if new_hashable in seen:
          continue

        seen.add(new_hashable)
        if new_period > 1:
          seen.add(to_hashable(g.evolve(new_cells, 1)))
       
        new_lanes = lanes + [lane]
        new_debug = debug + [start_cells]
         
        bombard_final(start, new_lanes, new_cells, new_period, new_debug, 1, 1)
#        bombard_final(start, new_lanes, new_cells, new_period, new_debug, 1, -1)
#        bombard_final(start, new_lanes, new_cells, new_period, new_debug, -1, -1)

        if n + 1 < MAX_GLIDERS:
          new_queue.append( (start, new_lanes, new_cells, new_period, new_debug) )

OUTFILE.close()

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

Re: Slow Salvo Gliders

Post by dvgrn » May 15th, 2015, 7:35 pm

chris_c wrote:I did no reorganisation of the results but this version of the script outputs lines containing x, y and #gliders to a file specified at the top of the script.
I tried running this version of the code, and saw the "got NoneType" error.

On a hunch, I switched Golly from LifeHistory to Life -- multi-state to two-state -- and the script ran fine. I suspect that means there's a very easy fix...!

User avatar
The Turtle
Posts: 102
Joined: May 6th, 2015, 8:14 pm
Location: Chicago, Illinois

Re: Slow Salvo Gliders

Post by The Turtle » May 15th, 2015, 7:43 pm

chris_c wrote:I did no reorganisation of the results but this version of the script outputs lines containing x, y and #gliders to a file specified at the top of the script. If you can provide a CSV table containing the number of gliders needed for each block move then it would be easy to incorporate into the script. Oh, by the way, this version of the script only outputs recipes with the glider heading SE.
What is a CSV table?
Can you modify the program (not the SE glider version) to only output result that displace the block by either of these values (and organize them based on their results)?

Code: Select all

(-12, 11)
(-11, 13)
(-10, 12)
(-8, 7)
(-7, 9)
(-6, 5)
(-6, 8)
(-2, 1)
(0, 0)
(1, -2)
(1, 2)
(1, 7)
(2, 1)
(2, 4)
(3, 3)
(3, 5)
(3, 6)
(4, 2)
(4, 5)
(4, 8)
(5, -6)
(5, 3)
(5, 4)
(5, 7)
(5, 12)
(6, 3)
(6, 6)
(6, 14)
(7, -8)
(7, 1)
(7, 5)
(7, 13)
(8, -6)
(8, 4)
(8, 9)
(9, -7)
(9, 8)
(9, 11)
(10, 10)
(11, -12)
(11, 9)
(12, -10)
(12, 5)
(13, -11)
(13, 7)
(14, 6)
Sorry for being so dependent on other people to write code for me. :(
Last edited by The Turtle on May 16th, 2015, 1:13 pm, edited 3 times in total.
Only two things are constant: change and the speed of light.

User avatar
The Turtle
Posts: 102
Joined: May 6th, 2015, 8:14 pm
Location: Chicago, Illinois

Re: Slow Salvo Gliders

Post by The Turtle » May 15th, 2015, 7:50 pm

dvgrn wrote:
chris_c wrote:I did no reorganisation of the results but this version of the script outputs lines containing x, y and #gliders to a file specified at the top of the script.
I tried running this version of the code, and saw the "got NoneType" error.

On a hunch, I switched Golly from LifeHistory to Life -- multi-state to two-state -- and the script ran fine. I suspect that means there's a very easy fix...!
I have another error for the updated program. (It's not the "got NoneType" thing)

Code: Select all

Traceback (most recent call last):
 File "<string>", line 1, in <module>
 File "C:\Users\*my name*\Desktop\Game of Life\Python\Scripts\Slow Salvo Gliders Updated SE Gliders.py", line 5, in <module> 
 OUTFILE = open("/home/user/life/slow_salvo.txt", "w")
IOError: {Errno 2] No such file or directory:
'home/user/life/slow_salvo.txt'
Only two things are constant: change and the speed of light.

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

Re: Slow Salvo Gliders

Post by dvgrn » May 15th, 2015, 8:00 pm

The Turtle wrote:I have another error for the updated program. (It's not the "got NoneType" thing)...
I got that error too, but neglected to mention it. If you read the last two lines, it really does tell you what the problem is.

Just find at the top of the code where it says "home/user/life/slow_salvo.txt" and change that to a directory and filename combination that can actually exist on your system.

You can probably do quite a bit just importing the slow_salvo.txt output file into Excel (or Google Calc, or Star Office or whatever) and sort them there -- though we should probably modify the script so it outputs the actual list of lanes, and maybe the output glider lane. Right now it seems to be showing just the output block location and the total number of gliders.

EDIT: A CSV file is just a text file almost exactly like slow_salvo.txt, except the separator character between the numbers is a comma instead of a space -- thus "CSV", Comma Separated Values. CSV files have the minor advantage that spreadsheet programs like Excel can open them directly, without bothering with special import dialogs.

User avatar
The Turtle
Posts: 102
Joined: May 6th, 2015, 8:14 pm
Location: Chicago, Illinois

Re: Slow Salvo Gliders

Post by The Turtle » May 15th, 2015, 8:29 pm

dvgrn wrote:
The Turtle wrote:I have another error for the updated program. (It's not the "got NoneType" thing)...
I got that error too, but neglected to mention it. If you read the last two lines, it really does tell you what the problem is.

Just find at the top of the code where it says "home/user/life/slow_salvo.txt" and change that to a directory and filename combination that can actually exist on your system.
I changed the line of code to

Code: Select all

OUTFILE = open("/C:/Users/Holden/Desktop/Game of Life/Python/Results/Slow Salvo Gliders Updated SE Gliders.txt", "w")
, but it still doesn't work and gives me the same error (but different directory).
The file "Slow Salvo Gliders Updated SE Gliders.txt" is a blank file.
What is the error? Is it because of the spaces?
Only two things are constant: change and the speed of light.

chris_c
Posts: 966
Joined: June 28th, 2014, 7:15 am

Re: Slow Salvo Gliders

Post by chris_c » May 16th, 2015, 5:29 am

The Turtle wrote: I changed the line of code to

Code: Select all

OUTFILE = open("/C:/Users/Holden/Desktop/Game of Life/Python/Results/Slow Salvo Gliders Updated SE Gliders.txt", "w")
How about this?

Code: Select all

OUTFILE = open("C:\Users\Holden\Desktop\Game of Life\Python\Results\Slow Salvo Gliders Updated SE Gliders.txt", "w")
On Windows there is no slash at the start and the slashes are the other way round. Hopefully the spaces should not be a problem. Just make sure the directory "C:\Users\Holden\Desktop\Game of Life\Python\Results" exists and it should work. Also as dvgrn pointed out, make sure you start the script when in you are in Life mode and not in any kind of multi-state rule.

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

Re: Slow Salvo Gliders

Post by dvgrn » May 16th, 2015, 12:47 pm

chris_c wrote:On Windows there is no slash at the start and the slashes are the other way round. Hopefully the spaces should not be a problem.
Unfortunately there's another wrinkle: Python uses backslashes as escape characters in literal strings, so even so innocuous a change as using "\results\" instead of "\results\" has very strange effects -- you can get carriage returns (\r), tabs (\t), newlines (\n) and so on in the middle of your strings, replacing your perfectly good letters.

It's generally better to just stick with the forward slashes, actually -- Windows can handle those just fine, as long as there isn't one at the very beginning of the path before the drive letter!

Here's what I got when I ran the script, by the way -- I added a reaction number and sorted by X and Y:

Code: Select all

X Y gliderlist Reaction#
-29 -19 [-5,-7,5,-6,-16,-1,-26] 85
-26 -4 [-5,-14,3,5,9,-14,-20] 63
-25 -15 [4,1,13,2,-8,7,-18] 140
-25 -13 [-5,-12,-14,-13,-4,-18,-17] 65
-23 -14 [-5,-12,-9,-14,-8,-18,2] 71
-22 -17 [-6,-6,-8,-10,-4,-19,-18] 33
-22 -8 [-5,6,-18,-18,-25,-20,-14] 104
-22 0 [4,-6,11,13,17,-6,-12] 118
-21 -16 [5,-4,-6,-8,-2,-17,-16] 168
-21 -9 [4,-4,-6,-5,4,-10,-9] 120
-21 6 [-5,-12,-9,-14,-8,-40,2] 70
-20 -18 [-5,-7,-9,-3,-18,-17] 7
-20 -2 [-5,-7,-14,-18,-5,-4,6] 75
-19 -10 [4,-4,-1,-6,0,-10,10] 126
-18 -13 [-6,3,0,-2,4,-11,-10] 46
-18 -4 [4,14,-10,-10,-17,-12,-6] 158
-17 -12 [5,5,2,0,6,-9,-8] 181
-17 10 [4,-4,-1,-6,0,-32,10] 125
-16 -15 [-5,3,3,4,6,-15,-10] 100
-16 -14 [4,1,-1,5,-10,-9] 20
-16 -10 [-5,-12,1,-1,-1,6,-13] 73
-16 -9 [-5,1,-4,2,1,10,-4] 91
-16 2 [4,1,-6,-10,3,4,14] 130
-15 -16 [-5,-12,-11,-2,-5,1,-10] 68
-14 -20 [-6,-6,-13,-15,-14,5,-4] 30
-14 -11 [-6,-6,-15,-11,-6,-5,5] 28
-14 2 [-6,-6,-8,4,1,-8,-5] 37
-13 -19 [5,-4,-11,-13,-12,7,-2] 165
-13 -10 [-5,-7,5,-9,-13,-14,-1] 84
-13 -10 [5,-4,-13,-9,-4,-3,7] 163
-13 3 [5,-4,-6,6,3,-6,-3] 172
-12 -21 [-5,-12,-14,-13,6,-3] 4
-12 -14 [-5,-12,-14,6,-20,-33,-5] 66
-12 -12 [-5,-14,-10,-5,-4,6] 2
-12 -11 [4,11,11,12,14,-7,-2] 154
-12 -10 [-5,-7,-9,-18,-20,-15,-12] 80
-12 -6 [4,-4,9,7,7,14,-5] 128
-12 -5 [-5,-14,-10,-5,-11,-24,4] 60
-12 0 [-5,1,0,9,-1,5,-11] 94
-12 1 [-5,-7,5,2,-7,-4] 11
-12 5 [-5,-15,10,8,15,27,22] 56
-12 5 [-5,-15,19,16,23,35,30] 59
-12 8 [-5,1,0,1,12,-7,4] 93
-11 -12 [-5,6,-6,-8,-1,11,6] 105
-11 -12 [-5,6,3,0,7,19,14] 106
-11 -12 [4,-4,-3,6,3,9,-2] 123
-10 -23 [-6,-6,-8,-10,-4,-22,-12] 32
-10 -16 [-6,3,-5,-7,-6,13,4] 43
-10 -7 [-6,3,-7,-3,2,3,13] 41
-10 6 [-6,3,0,12,9,0,3] 50
-9 -22 [5,-4,-6,-8,-2,-20,-10] 167
-9 -18 [-6,-6,0,-1,-1,0,-6] 38
-9 -15 [5,5,-3,-5,-4,15,6] 178
-9 -6 [4,1,13,-1,-5,-6,7] 139
-9 -6 [5,5,-5,-1,4,5,15] 176
-9 7 [5,5,2,14,11,2,5] 185
-8 -24 [-5,-7,-9,-3,-21,-11] 6
-8 -17 [-6,-6,-8,-15,-7,-5,1] 31
-8 -17 [-5,-7,-14,-6,-30,-4,2] 76
-8 -17 [5,-4,2,1,1,2,-4] 173
-8 -10 [4,-4,-6,14,-12,-25,3] 121
-8 -8 [4,-6,-2,3,4,14] 15
-8 -6 [4,1,-1,-10,-12,-7,-4] 135
-8 -1 [4,-4,-6,-5,14,5] 17
-8 -1 [4,-6,-2,3,-3,-16,12] 115
-8 4 [-5,3,5,4,-5,9,17] 101
-8 4 [4,9,8,17,7,13,-3] 148
-8 5 [4,1,13,10,1,4] 24
-8 6 [-5,-12,1,-11,-17,3,-18] 72
-8 9 [4,-7,18,16,23,35,30] 111
-8 9 [4,-7,27,24,31,43,38] 114
-8 12 [4,9,8,9,20,1,12] 147
-7 -19 [-5,1,0,0,1,-5] 12
-7 -19 [-5,1,0,-14,0,1,-5] 92
-7 -16 [-5,-7,-14,-6,-19,-4,2] 77
-7 -16 [5,-4,-6,-13,-5,-3,3] 166
-7 -10 [-5,-12,-12,-2,-12,-9,-15] 67
-7 -9 [-5,-7,-15,5,11,19,5] 74
-7 -8 [4,14,2,0,7,19,14] 159
-7 -8 [4,14,11,8,15,27,22] 160
-7 2 [-5,3,0,7,-18,-10,6] 98
-6 -19 [-6,3,0,-2,4,-14,-4] 45
-6 -18 [-5,-7,-14,-6,-4,2] 5
-6 -13 [-5,-7,5,-1,-6,2,-9] 89
-6 -5 [-5,-12,-15,-12,-13,6,10] 64
-5 -18 [5,5,2,0,6,-12,-2] 180
-5 -15 [-5,-7,-9,-8,-27,-23,1] 82
-5 -14 [-6,3,8,7,7,8,2] 51
-5 -6 [-5,-7,5,-6,12,8,14] 86
-5 -4 [-6,-6,-16,9,20,8,-6] 27
-5 -4 [-5,-15,10,21,-14,9,-5] 57
-5 0 [-6,-6,-8,-1,-7,8,16] 34
-5 11 [-5,3,-3,5,3,9,-5] 95
-4 -20 [4,1,-1,5,-13,-3] 19
-4 -13 [-6,3,0,-7,1,3,9] 44
-4 -13 [4,1,-6,2,-22,4,10] 131
-4 -13 [5,5,10,9,9,10,4] 186
-4 -4 [-5,3,0,-15,-7,-1,10] 96
-4 -4 [-5,3,0,7,-18,-6,6] 99
-4 -3 [5,-4,-14,11,22,10,-4] 162
-4 1 [5,-4,-6,1,-5,10,18] 169
-4 8 [4,11,13,12,3,17,25] 155
-4 10 [4,-4,9,-3,-9,11,-10] 127
-3 -15 [4,9,8,8,9,3] 25
-3 -15 [4,9,8,-6,8,9,3] 146
-3 -12 [4,1,-6,2,-11,4,10] 132
-3 -12 [5,5,2,-5,3,5,11] 179
-3 -6 [4,-4,-4,6,-4,-1,-7] 122
-3 -5 [-5,-15,10,21,9,-5] 1
-3 -5 [-5,-15,10,21,9,0,-5] 58
-3 -5 [-5,3,6,0,11,-5,2] 103
-3 -5 [4,1,-7,13,19,27,13] 129
-3 -4 [-5,1,-4,-10,-9,-8,-3] 90
-3 -1 [-5,-7,0,-6,9,17] 8
-3 6 [4,11,8,15,-10,-2,14] 152
-2 -14 [4,1,-6,2,4,10] 18
-2 -14 [-5,-14,-9,-20,-21,0,5] 61
-2 -9 [4,1,13,7,2,10,-1] 144
-2 -1 [4,-4,-7,-4,-5,14,18] 119
-2 13 [-5,3,6,-12,-11,4,-5] 102
-1 -11 [4,1,-1,0,-19,-15,9] 137
-1 -4 [-5,-12,-9,-16,-12,-7,9] 69
-1 -3 [-6,-6,-15,2,-8,-10,-5] 29
-1 -2 [4,1,13,2,20,16,22] 141
-1 0 [-6,3,-8,17,28,16,2] 40
-1 0 [4,-7,18,29,-6,17,3] 112
-1 1 [-5,-14,-5,6,1,-10,0] 62
-1 4 [-6,3,0,7,1,16,24] 47
-1 15 [4,11,5,13,11,17,3] 149
0 -9 [-5,-7,-9,-12,-26,-15,0] 81
0 -2 [5,-4,-13,4,-6,-8,-3] 164
0 0 [4,11,8,-7,1,7,18] 150
0 0 [4,11,8,15,-10,2,14] 153
0 1 [5,5,-6,19,30,18,4] 175
0 5 [5,5,2,9,3,18,26] 182
1 -12 [-6,-6,-8,-1,-7,11,10] 35
1 -4 [-5,-14,3,-7,-9,-4] 3
1 -1 [4,-7,18,29,17,3] 14
1 -1 [4,-7,18,29,17,8,3] 113
1 -1 [4,11,14,8,19,3,10] 157
1 0 [4,9,4,-2,-1,0,5] 145
1 3 [4,1,8,2,17,25] 21
2 -11 [5,-4,-6,1,-5,13,12] 170
2 -10 [4,-6,-1,-12,-13,8,13] 116
2 1 [-5,-7,-14,-3,-21,-28,-22] 78
2 1 [-5,-7,-14,-3,-8,-12,-6] 79
2 15 [-5,3,0,-7,-9,6,-13] 97
2 17 [4,11,14,-4,-3,12,3] 156
2 18 [-5,-7,-9,0,-1,14,-7] 83
3 -13 [-5,-7,0,-6,12,11] 9
3 0 [4,-4,-1,-8,-4,1,17] 124
3 1 [-6,3,-7,10,0,-2,3] 42
3 5 [4,-6,3,14,9,-2,8] 117
4 -5 [4,1,-1,-4,-18,-7,8] 136
4 2 [5,5,-5,12,2,0,5] 177
4 18 [-5,-15,9,9,16,0,6] 55
5 -8 [-6,3,0,7,1,19,18] 48
5 0 [4,-6,11,1,-1,4] 16
6 -7 [5,5,2,9,3,21,20] 183
6 5 [4,1,-6,5,-13,-20,-14] 133
6 5 [4,1,-6,5,0,-4,2] 134
6 19 [4,11,8,1,-1,14,-5] 151
6 22 [4,1,-1,8,7,22,1] 138
7 -9 [4,1,8,2,20,19] 22
7 20 [-6,-6,-16,-3,9,3,32] 26
7 20 [-5,-15,-2,10,4,22,33] 54
8 7 [-5,-15,-12,-14,-7,5,0] 52
8 7 [-5,-15,-3,-6,1,13,8] 53
8 21 [5,-4,-14,-1,11,5,34] 161
8 22 [4,-7,17,17,24,8,14] 110
9 19 [-5,-15,-2,10,4,33] 0
11 24 [-6,3,-8,5,17,11,40] 39
11 24 [4,-7,6,18,12,30,41] 109
12 3 [-6,-6,-8,4,-4,-17,-11] 36
12 3 [-5,-7,5,-3,-16,10,-10] 87
12 11 [4,-7,-4,-6,1,13,8] 107
12 11 [4,-7,5,2,9,21,16] 108
12 25 [5,5,-6,7,19,13,42] 174
13 4 [-5,-7,5,-3,-16,21,-10] 88
13 4 [5,-4,-6,6,-2,-15,-9] 171
13 23 [4,-7,6,18,12,41] 13
14 2 [-5,-7,5,-3,-16,-10] 10
16 7 [-6,3,0,12,4,-9,-3] 49
16 7 [4,1,13,5,-8,18,-2] 142
17 8 [4,1,13,5,-8,29,-2] 143
17 8 [5,5,2,14,6,-7,-1] 184
18 6 [4,1,13,5,-8,-2] 23
To find the right reaction, just count down vertically in the attached file. The last number, Reaction#, in the above list is the first number in the long label string in the pattern.

Here's my quick-and-dirty revised script, which numbers the reactions and includes lots of other information, more or less useful:

Code: Select all

import golly as g
from hashlib import sha256
from itertools import chain
from glife.text import make_text

OUTFILE = open("C:/Users/Dave/Desktop/slow_salvo.txt", "w")

#arbitrary numbers
MAX_GENERATIONS = 256
MAX_POPULATION = 40
MAX_GLIDERS = 6

#NE glider
GLIDER = g.parse('3o$2bo$bo!')
solution_count=0

#put any ad-hoc patterns that you want to bombard with slow gliders here.
TARGET_PATTERNS = []#('known_splitter', 'bo$obo$b2o$5bo$4bobo$5bobo$6b2o!')]

#put simple targets here, along with rotational symmetry
SIMPLE_TARGETS = [
  ('block', '2o$2o!', 4),
#  ('blinker', '3o$!', 4),
#  ('tub', 'bo$obo$bo!', 4),
#  ('boat', 'b2o$obo$bo!', 1),
#  ('hive', 'b2o$o2bo$b2o!', 2),
#  ('ship', 'b2o$obo$2o!', 2),
#  ('loaf', 'b2o$o2bo$bobo$2bo!', 1),
#  ('lboat', '2b2o$bobo$obo$bo!', 1),
#  ('pond', 'b2o$o2bo$o2bo$b2o!', 4),
# ('tlight', '4bo$4bo$4bo2$3o3b3o2$4bo$4bo$4bo!', 4),
# ('hfarm', '6bo$5bobo$5bobo$6bo2$b2o7b2o$o2bo5bo2bo$b2o7b2o2$6bo$5bobo$5bobo$6bo!', 4),
]

def get_pattern_variants(cells, symmetry):
  variants = []
  for t in range(0, 4, symmetry):
    variants.append(cells)
    cells = g.transform(cells, 0, 0, 0, -1, 1, 0)
  return variants

TARGETS = []
for name, pattern in TARGET_PATTERNS:
  cells = g.parse(pattern)
  p = len(cells) / 2
  TARGETS.append((name, cells, p))

for name, pattern, sym in SIMPLE_TARGETS:
  cells = g.parse(pattern)
  variants = get_pattern_variants(cells, sym)
  for i, v in enumerate(variants):
    p = len(v) / 2
    TARGETS.append((name+str(i), v, p))

def patterns_identical(cells1, cells2):
  if len(cells1) != len(cells2):
    return False
  if sum(cells1) != sum(cells2):
    return False
  return sorted(zip(cells1[::2], cells1[1::2])) == sorted(zip(cells2[::2], cells2[1::2]))

def get_pattern_period(cells):
  temp_cells = cells
  for p in range(0, 2):
    temp_cells = g.evolve(temp_cells, 1)
    if patterns_identical(cells, temp_cells):
      return p+1
  return None

def get_shooting_range(cells):

  min_d1 = max_d1 = cells[0] + cells[1]
  min_d2 = cells[0] - cells[1]

  for i in range(2, len(cells), 2):
    min_d1 = min(min_d1, cells[i] + cells[i+1])
    max_d1 = max(max_d1, cells[i] + cells[i+1])
    min_d2 = min(min_d2, cells[i] - cells[i+1])

  min_lane = min_d1 - 6
  max_lane = max_d1 + 3
  shift = 6 - min_d2 // 2

  return min_lane, max_lane, shift

def get_pattern_to_try(cells, lane, parity, offset=50):
  glider = g.transform(GLIDER, lane - lane // 2 - offset, lane // 2 + offset)
  if parity % 2:
    glider = g.evolve(glider, 1)
  return list(chain(cells, glider))

offset = 0

def display_solution(start, lanes, debug, last_cells):

  global offset
  global solution_count
  
  print >>OUTFILE, min(last_cells[::2]), min(last_cells[1::2]), len(lanes)
  OUTFILE.flush()

  cells = [c for n, c, _ in TARGETS if n == start][0]
  i = 100
  for lane in lanes:
    lane_num, parity = lane
    cells = get_pattern_to_try(cells, lane_num, parity, i)
    i += 100
  g.putcells(cells, 0, offset+112)
  for i, p in enumerate(debug):
    g.putcells(p, 100 + 100 * i, offset+112)
  g.putcells(last_cells, 100 + 100 * len(debug), offset+112)
  g.fit()
  g.update()
  solution_count+=1
  outstr=' '.join(chain([str(solution_count), str(start),str(min(last_cells[::2])), str(min(last_cells[1::2])), str(len(lanes))], [str(lane) for lane in lanes]))
  g.show(outstr)
  g.putcells(make_text(outstr, "mono"),0, offset)
  offset += 500


randoms = []
for i in range(4096):
  randoms.append(int(sha256(str(i)).hexdigest()[:16], 16))

def to_hashable(cells):
  if not cells:
    return 0

  minx = min(cells[::2])
  miny = min(cells[1::2])

  hash = 0
  for i in range(0, len(cells), 2):
    hash ^= randoms[64 * (cells[i] & 63) + (cells[i+1] & 63)]

  return hash

def deltas(cells):
  return len(cells), sum(cells[::2]), sum(cells[1::2])

def bombard_final(start, lanes, cells, period, debug, flipx, flipy):

  cells = g.transform(cells, 0, 0, flipx, 0, 0, flipy)

  min_lane, max_lane, shift = get_shooting_range(cells)

  for lane_num in range(min_lane, max_lane + 1):

    for parity in range(period):
     
      lane = (lane_num, parity)
      start_cells = get_pattern_to_try(cells, lane[0], lane[1], shift)
      new_cells = g.evolve(start_cells, MAX_GENERATIONS)

      if len(new_cells) == 18:
        n1, dx1, dy1 = deltas(new_cells)
        n2, dx2, dy2 = deltas(g.evolve(new_cells, 4))
        if n1 != n2:
          continue
        dx = dx2-dx1
        dy = dy2-dy1
        if (dx, dy) == (5, 5):

          #Success??

          #flip back for display purposes
          start_cells = g.transform(start_cells, 0, 0, flipx, 0, 0, flipy)
          new_cells = g.transform(new_cells, 0, 0, flipx, 0, 0, flipy)
          #add
          debug.append(start_cells)
          lanes.append(lane)
          #display
          display_solution(start, lanes, debug, new_cells)
          #remove
          lanes.pop()
          debug.pop()

g.new('')

new_queue = []
for name, cells, _ in TARGETS:
  period = get_pattern_period(cells)
  new_queue.append( (name, [], cells, period, []) )

seen = set()

for n in range(MAX_GLIDERS):

  queue = new_queue
  new_queue = []

  count = 0

  for start, lanes, last, period, debug in queue:

    g.show(str((n+1,count,len(queue))))
    count += 1

    min_lane, max_lane, shift = get_shooting_range(last)

    for lane_num in range(min_lane, max_lane + 1):

      for parity in range(period):
       
        lane = (lane_num, parity)
        start_cells = get_pattern_to_try(last, lane[0], lane[1], shift)
        new_cells = g.evolve(start_cells, MAX_GENERATIONS)

        if not new_cells or len(new_cells) > 2 * MAX_POPULATION:
          continue

        new_period = get_pattern_period(new_cells)
        if new_period is None or new_period == 2:
          continue

        new_hashable = to_hashable(new_cells)       

        if new_hashable in seen:
          continue

        seen.add(new_hashable)
        if new_period > 1:
          seen.add(to_hashable(g.evolve(new_cells, 1)))
       
        new_lanes = lanes + [lane]
        new_debug = debug + [start_cells]
         
        bombard_final(start, new_lanes, new_cells, new_period, new_debug, 1, 1)
#        bombard_final(start, new_lanes, new_cells, new_period, new_debug, 1, -1)
#        bombard_final(start, new_lanes, new_cells, new_period, new_debug, -1, -1)

        if n + 1 < MAX_GLIDERS:
          new_queue.append( (start, new_lanes, new_cells, new_period, new_debug) )

OUTFILE.close()
Attachments
chris_c_search_results.mc
6- and 7-glider P1 slow salvos producing SW glider and block -- revised to include labels (but now much less runnable)
(346.33 KiB) Downloaded 297 times

chris_c
Posts: 966
Joined: June 28th, 2014, 7:15 am

Re: Slow Salvo Gliders

Post by chris_c » May 16th, 2015, 2:45 pm

dvgrn wrote: It's generally better to just stick with the forward slashes, actually -- Windows can handle those just fine, as long as there isn't one at the very beginning of the path before the drive letter!
Ah, thanks. I didn't know that.
dvgrn wrote: Here's what I got when I ran the script, by the way -- I added a reaction number and sorted by X and Y:
It looks like there are two reactions in there that return the block to the original position:

Code: Select all

0,0,7,151
0,0,7,154
From what I can tell they are of opposite color but the attached file is already at generation 2238 so it is difficult (but I suppose not impossible) to extract the recipes. Any chance that you have the generation 0 output still available?

EDIT: In the end I deduced that the recipes are as follows:

Code: Select all

x = 201, y = 105, rule = B3/S23
79b2o118b2o$79b2o118b2o6$76b3o117b3o$78bo119bo$77bo119bo13$68b3o117b3o
$70bo119bo$69bo119bo8$55b3o117b3o$57bo119bo$56bo119bo5$33b3o$35bo$34bo
12$136b3o$138bo$137bo$158b3o$160bo$159bo2$20b3o$22bo$21bo16$8b3o$10bo$
9bo5$118b3o$120bo$119bo10$3o$2bo$bo3$111b3o$113bo$112bo!

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

Re: Slow Salvo Gliders

Post by dvgrn » May 16th, 2015, 8:19 pm

chris_c wrote:From what I can tell they are of opposite color but the attached file is already at generation 2238 so it is difficult (but I suppose not impossible) to extract the recipes. Any chance that you have the generation 0 output still available?
Oops. Fixed. See above -- I posted the correct version of the pattern. The script really doesn't take that long to run to completion.

I tried adding labels this time, so you can match a reaction number to the appropriate label in the pattern. My slightly revised version of the script is posted in the same message. Next draft will maybe keep the labels from getting in the way of running the pattern...

User avatar
The Turtle
Posts: 102
Joined: May 6th, 2015, 8:14 pm
Location: Chicago, Illinois

Re: Slow Salvo Gliders

Post by The Turtle » May 20th, 2015, 4:55 pm

Three questions:
1. Can someone modify dvgrn's program so that the program only outputs blocks that return to their original position? It's hard to look through all the salvos, even with the numbered outputs and the text file.
2. What is the best website that teaches Python?
3. Even if I know Python, I still see some functions in the program that (I don't think) Python knows. Is there a list of these functions/variables that tells what the function is and what it does? (By the name of the function I can tell what the function does, but not for all functions) For example, I saw these in some scripts:

Code: Select all

import golly as g
g.getclipstr
g.note
g.show
g.setstep
g.setmag
g.setpos
g.putcells
g.parse
and so on...
Only two things are constant: change and the speed of light.

User avatar
calcyman
Moderator
Posts: 2964
Joined: June 1st, 2009, 4:32 pm

Re: Slow Salvo Gliders

Post by calcyman » May 20th, 2015, 5:02 pm

What do you do with ill crystallographers? Take them to the mono-clinic!

User avatar
The Turtle
Posts: 102
Joined: May 6th, 2015, 8:14 pm
Location: Chicago, Illinois

Re: Slow Salvo Gliders

Post by The Turtle » May 26th, 2015, 8:28 pm

I'm learning Python right now.
I ran an search for 8 glider block move glider things, or whatever you call it.
There were no results that moved the block by (0, 0). (except for the two 7-glider P1 slow salvos)
Can someone run the 9 glider search? My computer will overheat after about 4 hours, so I can't run the script with 9 gliders.

By the way, I would post the results if I knew how to attach files, but I don't know how. The RLE and text files exceed the 60000 character limit. How do you attach files?
Only two things are constant: change and the speed of light.

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

Re: Slow Salvo Gliders

Post by dvgrn » May 27th, 2015, 1:29 pm

The Turtle wrote:I'm learning Python right now.
Enjoy! Python is a lot of fun once you get used to it -- but there are definitely things about its interaction with Golly that take a little getting used to. Reading through the sample scripts that come with Golly is really useful, and so are the notes in the Help about cell lists and bounding rectangles.

When you get to that point, it's a good idea to set up some examples of two-state lists and multi-state lists -- e.g., straight B3/S23 versus LifeHistory. The H-to-G conduit identifier script includes some tricky ways to change back and forth between lists of coordinates and flat Golly cell lists, both two-state and multi-state -- that's how the Herschel or pre-Herschel ends up a different color from the label, which is a different color from the rest of the conduit.

Once you figure out exactly how all those conversions work -- and especially when you've developed some opinions on how I should have done it better! -- you'll understand quite a lot about using Python with Golly.
The Turtle wrote:I ran an search for 8 glider block move glider things, or whatever you call it.
There were no results that moved the block by (0, 0). (except for the two 7-glider P1 slow salvos)
Can someone run the 9 glider search? My computer will overheat after about 4 hours, so I can't run the script with 9 gliders.
Eight gliders is maybe about as far as I'd be inclined to search with this script -- I'm not sure how many days or weeks it would take to complete a 9-glider search. What exactly would you use a 9-glider invariant recipe for? Won't it usually make more sense to use one of the 7-glider recipes instead?

As you increase the number of gliders you'll pick up a few new output lanes, I suppose, but it seems unlikely that you'll get anywhere near full coverage -- for most recipes you'll have to move the block around a lot anyway. Or if you want lots of invariant-turner recipes, just append the (-X,-Y) block move to each (X,Y) result that you have from your 8-glider search... that might get pretty close to full coverage in a decent range of output lanes (?).

[It still looks to me like it will be enormously more efficient to not worry about getting back to (0,0) -- just let the block wander around a little bit, and pick the cheapest available recipe to get to the next output glider lane. That requires keeping a bigger toolkit of recipes handy, but it seems as if the block-move recipe list will be needed a lot anyway.]
The Turtle wrote:By the way, I would post the results if I knew how to attach files, but I don't know how. The RLE and text files exceed the 60000 character limit. How do you attach files?
There's a tab at the bottom of the Reply or Edit pages, to the right of "Options", where it says "Upload attachment".

User avatar
The Turtle
Posts: 102
Joined: May 6th, 2015, 8:14 pm
Location: Chicago, Illinois

Re: Slow Salvo Gliders

Post by The Turtle » May 27th, 2015, 4:53 pm

Thanks!
Here are the files:
Text: attached
RLE: It exceeds the limit of 256 kilobytes. (Is there a fix?)
Attachments
Slow Salvo 7 Gliders Updated Numbered.txt
Text File
(9.88 KiB) Downloaded 240 times
Only two things are constant: change and the speed of light.

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

Re: Slow Salvo Gliders

Post by dvgrn » May 27th, 2015, 5:38 pm

The Turtle wrote:RLE: It exceeds the limit of 256 kilobytes. (Is there a fix?)
Sure. Save it in either .mc.gz or rle.gz format, and it will most likely drop below the limit.

Those are really just compressed versions of .mc and .rle pattern files. So you could also, in Windows anyway, right-click and choose Send to > compressed (zipped) folder, and attach the resulting file, for approximately the same effect.

If any further searches get run, to nine gliders or above, it would be nice to use a version of the script that records the lane numbers for each recipe in the text file. It's a bit annoying to have to reconstruct those from the RLE.

Looks like I didn't post the minor revisions I added to accomplish that. Here's a copy, such as it is. The few changes are related to the new variable "solution_count" and "[str(lane) for lane in lanes]".

If someone gets around to it, it would be a fairly straightforward change to write each new set of N-glider partial recipes to a large file, so that searches could be stopped and continued later without having to rebuild all the intermediate targets.

Code: Select all

import golly as g
from hashlib import sha256
from itertools import chain
from glife.text import make_text

OUTFILE = open("C:/Users/Dave/Desktop/slow_salvo_v3.txt", "w")

#arbitrary numbers
MAX_GENERATIONS = 256
MAX_POPULATION = 40
MAX_GLIDERS = 6

#NE glider
GLIDER = g.parse('3o$2bo$bo!')
solution_count=0

#put any ad-hoc patterns that you want to bombard with slow gliders here.
TARGET_PATTERNS = []#('known_splitter', 'bo$obo$b2o$5bo$4bobo$5bobo$6b2o!')]

#put simple targets here, along with rotational symmetry
SIMPLE_TARGETS = [
  ('block', '2o$2o!', 4),
#  ('blinker', '3o$!', 4),
#  ('tub', 'bo$obo$bo!', 4),
#  ('boat', 'b2o$obo$bo!', 1),
#  ('hive', 'b2o$o2bo$b2o!', 2),
#  ('ship', 'b2o$obo$2o!', 2),
#  ('loaf', 'b2o$o2bo$bobo$2bo!', 1),
#  ('lboat', '2b2o$bobo$obo$bo!', 1),
#  ('pond', 'b2o$o2bo$o2bo$b2o!', 4),
# ('tlight', '4bo$4bo$4bo2$3o3b3o2$4bo$4bo$4bo!', 4),
# ('hfarm', '6bo$5bobo$5bobo$6bo2$b2o7b2o$o2bo5bo2bo$b2o7b2o2$6bo$5bobo$5bobo$6bo!', 4),
]

def get_pattern_variants(cells, symmetry):
  variants = []
  for t in range(0, 4, symmetry):
    variants.append(cells)
    cells = g.transform(cells, 0, 0, 0, -1, 1, 0)
  return variants

TARGETS = []
for name, pattern in TARGET_PATTERNS:
  cells = g.parse(pattern)
  p = len(cells) / 2
  TARGETS.append((name, cells, p))

for name, pattern, sym in SIMPLE_TARGETS:
  cells = g.parse(pattern)
  variants = get_pattern_variants(cells, sym)
  for i, v in enumerate(variants):
    p = len(v) / 2
    TARGETS.append((name+str(i), v, p))

def patterns_identical(cells1, cells2):
  if len(cells1) != len(cells2):
    return False
  if sum(cells1) != sum(cells2):
    return False
  return sorted(zip(cells1[::2], cells1[1::2])) == sorted(zip(cells2[::2], cells2[1::2]))

def get_pattern_period(cells):
  temp_cells = cells
  for p in range(0, 2):
    temp_cells = g.evolve(temp_cells, 1)
    if patterns_identical(cells, temp_cells):
      return p+1
  return None

def get_shooting_range(cells):

  min_d1 = max_d1 = cells[0] + cells[1]
  min_d2 = cells[0] - cells[1]

  for i in range(2, len(cells), 2):
    min_d1 = min(min_d1, cells[i] + cells[i+1])
    max_d1 = max(max_d1, cells[i] + cells[i+1])
    min_d2 = min(min_d2, cells[i] - cells[i+1])

  min_lane = min_d1 - 6
  max_lane = max_d1 + 3
  shift = 6 - min_d2 // 2

  return min_lane, max_lane, shift

def get_pattern_to_try(cells, lane, parity, offset=50):
  glider = g.transform(GLIDER, lane - lane // 2 - offset, lane // 2 + offset)
  if parity % 2:
    glider = g.evolve(glider, 1)
  return list(chain(cells, glider))

offset, offsetx = 0, 0

def display_solution(start, lanes, debug, last_cells):

  global offset
  global offsetx
  global solution_count
  
  print >>OUTFILE, solution_count, min(last_cells[::2]), min(last_cells[1::2]), len(lanes), [str(lane) for lane in lanes]
  OUTFILE.flush()

  cells = [c for n, c, _ in TARGETS if n == start][0]
  i = 100
  for lane in lanes:
    lane_num, parity = lane
    cells = get_pattern_to_try(cells, lane_num, parity, i)
    i += 100
  g.putcells(cells, offsetx, offset+112)
  for i, p in enumerate(debug):
    g.putcells(p, offsetx + 100 + 100 * i, offset+112)
  g.putcells(last_cells, offsetx + 100 + 100 * len(debug), offset+112)
  g.fit()
  g.update()
  solution_count+=1
  outstr=' '.join(chain([str(solution_count), str(start),str(min(last_cells[::2])), str(min(last_cells[1::2])), str(len(lanes))], [str(lane) for lane in lanes]))
  g.show(outstr)
  g.putcells(make_text(outstr, "mono"), offsetx, offset)
  offset += 1000
  if offset>49000:
    offset, offsetx=0, offsetx+2000

randoms = []
for i in range(4096):
  randoms.append(int(sha256(str(i)).hexdigest()[:16], 16))

def to_hashable(cells):
  if not cells:
    return 0

  minx = min(cells[::2])
  miny = min(cells[1::2])

  hash = 0
  for i in range(0, len(cells), 2):
    hash ^= randoms[64 * (cells[i] & 63) + (cells[i+1] & 63)]

  return hash

def deltas(cells):
  return len(cells), sum(cells[::2]), sum(cells[1::2])

def bombard_final(start, lanes, cells, period, debug, flipx, flipy):

  cells = g.transform(cells, 0, 0, flipx, 0, 0, flipy)

  min_lane, max_lane, shift = get_shooting_range(cells)

  for lane_num in range(min_lane, max_lane + 1):

    for parity in range(period):
     
      lane = (lane_num, parity)
      start_cells = get_pattern_to_try(cells, lane[0], lane[1], shift)
      new_cells = g.evolve(start_cells, MAX_GENERATIONS)

      if len(new_cells) == 18:
        n1, dx1, dy1 = deltas(new_cells)
        n2, dx2, dy2 = deltas(g.evolve(new_cells, 4))
        if n1 != n2:
          continue
        dx = dx2-dx1
        dy = dy2-dy1
        if (dx, dy) == (5, 5):

          #Success??

          #flip back for display purposes
          start_cells = g.transform(start_cells, 0, 0, flipx, 0, 0, flipy)
          new_cells = g.transform(new_cells, 0, 0, flipx, 0, 0, flipy)
          #add
          debug.append(start_cells)
          lanes.append(lane)
          #display
          display_solution(start, lanes, debug, new_cells)
          #remove
          lanes.pop()
          debug.pop()

g.new('')

new_queue = []
for name, cells, _ in TARGETS:
  period = get_pattern_period(cells)
  new_queue.append( (name, [], cells, period, []) )

seen = set()

for n in range(MAX_GLIDERS):

  queue = new_queue
  new_queue = []

  count = 0

  for start, lanes, last, period, debug in queue:

    g.show(str((n+1,count,len(queue))))
    count += 1

    min_lane, max_lane, shift = get_shooting_range(last)

    for lane_num in range(min_lane, max_lane + 1):

      for parity in range(period):
       
        lane = (lane_num, parity)
        start_cells = get_pattern_to_try(last, lane[0], lane[1], shift)
        new_cells = g.evolve(start_cells, MAX_GENERATIONS)

        if not new_cells or len(new_cells) > 2 * MAX_POPULATION:
          continue

        new_period = get_pattern_period(new_cells)
        if new_period is None or new_period == 2:
          continue

        new_hashable = to_hashable(new_cells)       

        if new_hashable in seen:
          continue

        seen.add(new_hashable)
        if new_period > 1:
          seen.add(to_hashable(g.evolve(new_cells, 1)))
       
        new_lanes = lanes + [lane]
        new_debug = debug + [start_cells]
         
        bombard_final(start, new_lanes, new_cells, new_period, new_debug, 1, 1)
#        bombard_final(start, new_lanes, new_cells, new_period, new_debug, 1, -1)
#        bombard_final(start, new_lanes, new_cells, new_period, new_debug, -1, -1)

        if n + 1 < MAX_GLIDERS:
          new_queue.append( (start, new_lanes, new_cells, new_period, new_debug) )

OUTFILE.close()

User avatar
The Turtle
Posts: 102
Joined: May 6th, 2015, 8:14 pm
Location: Chicago, Illinois

Re: Slow Salvo Gliders

Post by The Turtle » June 18th, 2015, 6:01 pm

Hello! I'm back. (after a long vacation)
This is kind of off topic, but I've been learning Python for two weeks, and I've been able to write this: (with Internet help)

Code: Select all

from math import floor, log
from sys import exit, argv as filename
from string import upper, lower
from inspect import currentframe

maximum, prompt = 64, "\n>> "
max_guesses = floor(log(maximum, 2)) + 2

def home():
	type_title("Guess the Number!")
	print
	data = user_choices("to read the rules", "to change the settings", "to play")
	change_page(data[0], data[1], "rules()", "settings()", "play()")

def rules():
	type_title("Rules")
	print
	print "1. Pick an integer from 0 to %d." % maximum
	print "2. I will ask you a yes or no question about your number."
	print "3. Answer the question with 'yes' or 'no'."
	print "4. I will try to guess your number in at most %d guesses!" % max_guesses
	data = user_choices("to go back", "to play")
	change_page(data[0], data[1], "home()", "play()")
def settings():
	type_title("Settings")
	print
	data = user_choices("to change the range of your number", "to go back", "to play")
	change_number = """
global maximum, max_guesses
count = 0
while True:
	count += 1
	try:
		print
		number = int(raw_input("Type in the desired range: 0 to "))
		if number <= 1:
			int("")
		maximum = number
		max_guesses = floor(log(maximum, 2)) + 2
		settings()
	except ValueError:
		print
		listen("Type in a non-negative integer.", count)"""
	change_page(data[0], data[1], change_number, "home()", "play()")

def play():
	type_title("Play")
	guess, question, count, guesses, answers = 0, 1, 1, [], []
	while True:
		if guess > maximum:
			find_mistake(guesses, answers)
		if question == max_guesses:
			print "\nIs your number %d?" % guess
			input = raw_input(prompt)
			if valid_inputs(input, "Yes"):
				print "\nYay! I win! I guessed your number in %d guesses. Type 'p' to play again or anything else to exit." % question
				if valid_inputs(raw_input(prompt), "play"):
					home()
				else:
					print
					exit(0)
			elif valid_inputs(input, "No"):
				find_mistake(guesses, answers)
			else:
				print
				listen("Type 'yes' or 'no'.", count)
				count += 1
		if guess == 0:
			print "\nIs your number divisible by %d?" % 2 ** question
		else:
			print "\nIf you subtract %d from your number, is the result divisible by %d?" % (guess, 2 ** question)
		input = raw_input(prompt)
		if valid_inputs(input, "Yes"):
			guesses.append(guess)
			question += 1
			count = 1
			answers.append(True)
		elif valid_inputs(input, "No"):
			guesses.append(guess)
			guess += 2 ** (question - 1)
			question += 1
			count = 1
			answers.append(False)
		else:
			print
			listen("Type 'yes' or 'no'.", count)
			count += 1

def find_mistake(guesses, answers):
	count = 1
	persons_fault = False
	if len(guesses) != len(answers):
		error("guesses length not equal to answers length")
	print "\nYou must have made a mistake, or I have a bug inside me. What was your number?"
	while True:
		number = raw_input(prompt)
		try:
			number = int(number)
			if number < 1 or number > maximum:
				print "\nRemember that your number must be between 0 and %d." % maximum,
				break
			else:
				for i in range(len(guesses)):
					if number == guesses[len(guesses) - 1]:
						print "Why did you say 'no' to 'Is your number %d?' when your number is %d?" % (number, number)
						break
						exit(0)
					if (number % 2 ** (i + 1) == guesses[i]) != answers[i]:
						persons_fault = True
						guess = ""
						not_guess = ""
						if guesses[i] == guesses[i + 1]:
							guess = "yes"
							not_guess = "no"
						else:
							guess = "no"
							not_guess = "yes"
						if guesses[i] == 0:
							print "\nYou answered %r to 'Is your number divisible by %d?', which is not true." % (guess, 2 ** (i + 1))
							break
						else:
							print "\nYou answered %r to 'If you subtract %d from your number, is the result divisible by %d?'." % (guess, guesses[i], 2 ** (i + 1))
							if number == guesses[i]:
								print "Remember that 0 is divisible by every number, including itself."
							else:
								print "You should have answered %s." % not_guess,
							break
				if persons_fault == False:
					print "\nFor debugging purposes below. Please report the four statements below to me."
					print guesses
					print answers
					print number
					print maximum
					print "\nI made a mistake.",
				break
		except ValueError:
			print
			listen("Type in an integer.", count)
			count += 1
	print "Type 'p' to play again or anything else to exit."
	input = raw_input(prompt)
	if valid_inputs(input, "play"):
		home()
	else:
		print
		exit(0)

def user_choices(*choices):
	first, words, phrases, output = [], [], [], ""
	for i in range(len(choices)):
		phrases.append(lower(choices[i]))
		words.append(phrases[i].rsplit(None, 1)[-1])
		first.append(words[i][0])
	if len(first) != len(set(first)):
		error("at least two arguments have same first letter")
	if len(choices) == 0:
		error("no arguments passed in function")
	if len(choices) == 1:
		output = "Type '%c' %s." % (first[0], phrases[0])
	if len(choices) == 2:
		output = "Type '%c' %s or '%c' %s." % (first[0], phrases[0], first[1], phrases[1])
	if len(choices) >= 3:
		output += "Type '%c' %s, " % (first[0], phrases[0])
		for i in range(1, len(choices) - 1):
			output += "type '%c' %s, " % (first[i], phrases[i])
		output += "or type '%c' %s." % (first[len(choices) - 1], phrases[len(choices) - 1])
	return words, output

def valid_inputs(var, word):
	first = upper(word[0])
	rest = lower(word[1 : len(word)])
	words = [first + rest, lower(first) + rest, first, lower(first)]
	return var in words

def change_page(words, output, *function):
	if len(function) != len(words):
		error("number of words not equal to number of functions")
	count = 0
	while True:
		count += 1
		listen(output, count)
		input = raw_input(prompt)
		for i in range(len(words)):
			if valid_inputs(input, words[i]):
				try:
					exec function[i]
				except NameError:
					error("%s is not defined" % function[i])

def error(description):
	print "  ERROR: %s" % description
	print "  File: \"%s\", line %d" % (filename[0], currentframe().f_back.f_lineno)
	exit(0)

def type_title(title):
	print
	print "-" * len(title)
	print title
	print "-" * len(title)

def listen(choices, number):
	if number == 1:
		print choices
	elif number == 2:
		print "Please %s" % lower(choices)
	elif number == 3:
		print "I said: please %s" % lower(choices)
	elif number >= 4:
		print "Listening is a skill. Following directions is a better skill. Try again next time.\n"
		exit(0)
	else:
		error("number must be a positive integer")

home()
I've spent a lot of time learning Python (even on vacation). Any suggestions?

EDIT:
I forgot. Don't run this in Golly; run this script in a command line. (I won't tell what this program does)

EDIT: (again)
Run this in Python 2.7.
Only two things are constant: change and the speed of light.

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

Re: Slow Salvo Gliders

Post by Scorbie » June 18th, 2015, 7:23 pm

Disclaimer: This post contains off-topic content.
The Turtle wrote:I've spent a lot of time learning Python (even on vacation). Any suggestions?
Nice to play that game. I think you won't have any problems with Python when writing golly scripts. You could take a look at golly Python API functions (here or at Help-python scripting) to write your own script. Don't hesitate to ask questions when you encounter any problems. Good Luck!

Post Reply