shipsearch.py v0.67 is out!
Code: Select all
import subprocess
import random
import math
numships=5
foundships=0
commonspeeds=[(2,1,0),(3,1,0),(3,1,1),(4,1,0),(4,1,1),(5,1,0),(5,2,0),(5,1,1),(6,1,0),(6,1,1)]
searched=[]
with open('new-gliders.db.txt', 'r') as file:
shipslist = [line.strip() for line in file]
def parse(r):
rb=list(r.split("/")[0])
rs=list(r.split("/")[1])
rb.remove("B")
rs.remove("S")
return set(int(x) for x in rb), set(int(x) for x in rs)
def inrange(r1,r2,r3):
possible=list(range(9))
rb1, rs1 = parse(r1)
rb2, rs2 = parse(r2)
rb3, rs3 = parse(r3)
reqb,reqs=list(set(rb1)&set(rb2)),list(set(rs1)&set(rs2))
forb=list(set(possible) - rb1 - rb2)
fors=list(set(possible) - rs1 - rs2)
if set(rb3) & set(forb) or set(rs3) & set(fors):
return False
if set(reqb).issubset(rb3) and set(reqs).issubset(rs3):
return True
else:
return False
def canonize(period, dx, dy):
period2=int(period.replace("/2",""))
dx2, dy2=abs(int(dx)),abs(int(dy))
gcdr = math.gcd(period2, dx2, dy2)
period2, dx2, dy2 = period2 // gcdr, dx2 // gcdr, dy2 // gcdr
if dx2>=dy2:
return period2, dx2, dy2
else:
return period2, dy2, dx2
def getships(r):
chosen = r
shipperiods=[]
for line in shipslist:
parts = line.split(':')
minrule = parts[2]
maxrule = parts[3]
period = parts[4]
dx = parts[5]
dy = parts[6]
if inrange(minrule, maxrule, chosen):
addtolist=canonize(period,dx,dy)
if not addtolist in shipperiods:
shipperiods.append(addtolist)
return shipperiods
def findsmallestunknown(r):
periods=getships(r)
for k in commonspeeds:
if k not in periods:
return k
return None
def findship(rule, speed, width, symmetry):
global foundships
global searched
found=0
if [rule,speed,width,symmetry] in searched:
return
if (speed[1] != speed[2]) and (speed[1] == 0 or speed[2] == 0):
#print("Searching for " + str(speed[1]) + "c/" + str(speed[0]) + " in " + rule + ", width " + width + (" even " if symmetry=="e" else " odd ") + "symmetry.")
process=subprocess.run(["./qfind", "-r", rule, "-v", str(str(speed[1])+"c/"+str(speed[0])), "-w", width, "-s", symmetry, "-f", "1"], capture_output=True, text=True)
if "0 spaceships" in process.stdout:
searched.append([rule,speed,width,symmetry])
pass
else:
found=1
foundships+=1
get=0
print((str(speed[1]) if str(speed[1]) == 1 else "") +"c/"+str(speed[0]) + "o in " + rule)
for k in process.stdout.splitlines():
if "x =" in k:
get=1
if get==1:
print(k)
if k.strip()=="":
break
searched.append([rule,speed,width,symmetry])
return found
elif speed[1] == speed[2]:
level = int(width)*2*speed[0]
process=subprocess.run(["./gfind", rule+"/N"+str(speed[1])+"D"+str(speed[0])+"/L"+str(level)], capture_output=True, text=True)
if not 'x =' in process.stdout:
searched.append([rule,speed,width,symmetry])
pass
else:
found=1
foundships+=1
get=0
print((str(speed[1]) if str(speed[1]) == 1 else "")+"c/"+str(speed[0]) + "d in " + rule)
for k in process.stdout.splitlines():
if "x =" in k:
get=1
if get==1:
print(k)
if k.strip()=="":
break
searched.append([rule,speed,width,symmetry])
return found
else:
print("Oblique searching is currently not supported.")
return 0
def pickrule():
pick="B0"
while "B0" in pick:
pick=random.choice(shipslist).split(':')[2]
return str(pick)
print("SHIPSEARCH v0.67, by hibiscus - 28 September 2025")
while foundships < numships:
chos=pickrule()
for j in range(4,8):
if findsmallestunknown(chos)==None:
break
search=findship(chos,findsmallestunknown(chos),str(j),"o")
if search==1:
break
search=findship(chos,findsmallestunknown(chos),str(j),"e")
if search==1:
break
Changes include:
- Searches will no longer be carried out more than once. This means time won't be wasted finding duplicates.
- c/n ships won't have the offset printed (i.e. "c/6" instead of "1c/6").
Edit (2025/09/29 09:14): shipsearch v0.67 has indirectly made its first discovery. c/7o in B3567/S0458:
Code:
Select all
x = 13, y = 11, rule = B3567/S0458
6bo$3bob3obo$2b2ob3ob2o$2b2ob3ob2o$bo9bo$3o3bo3b3o$ob9obo$6bo$2b
2obobob2o$2bo3bo3bo$3b2o3b2o!
Edit 2 (2025/09/29 10:16): DroneBetter asked me on Discord to share some partials in the rule mentioned above, so here they are.
2c/5o partial:
Code:
Select all
x = 25, y = 47, rule = B3567/S0458
11b2o$6bob2o4b2obo$8b8o$4bob2o2b4o2b2obo$6bo3bo2bo3bo$2bobo2bo8bo2bobo$b4o3b3o2b3o3b4o$2ob2obob3o2b3obob2ob2o$bob4ob3o2b3ob4obo$3b2ob4o4b4ob2o$3b3obobo2bob2o2b3o$2bo2bobo3b2o2bob2o2bobo$b3obobo7b2o3b4o$ob2o2bo4b2ob4o2bo2bo$4b4o7b4o2b3o$2b2o3bobob7o$b6o2bob3ob4o$2b6obob3obobo$b9o3b5o$4obo4bo3bob2o$bo3bobobo2bo3bo$3bobob2o4bob2o$5b6o2b4o$3b4obo2b4ob2o$2b8o5b2o$b4obob7o2bo$bobo2b5ob3o2b2o$2b2o2bo3b2o2bobo$2b2o4bobo2b2o3bo$2b3o3bobo3bob2o$2bo2b4o2b4o2b3o$b2ob2ob2o3bob2ob3o$2bo3b2o2b2obo2b3o$2bob2ob2o6b2ob2o$b2ob2ob3ob3o3b2o$2b2o3bob4o2b6o$b3o3b4o2bob6o$3obo3b2ob3o2b5o$3bob3o8b4obo$o2bob3o4bo6b2o$bobob3o4bo2bob4o$obobob2o5bo3b6o$o2bo2b3o2b2ob2o4b2o$b6o5bo2b2o2bo2b2o$ob3o2bobob2o3bob5o$6bo5b2obob3o4bo$ob2o2b2ob2o3bob3obo2bo!
c/4d partial:
Code:
Select all
x = 62, y = 62, rule = B3567/S0458
9bo$6b2o3b3o2bo$4b12o$3b2o3bo2bo2b2o$2b2obo3b7ob3o$2bob2o2b2obo2bob3o$b2o3b2o3bobo2b2ob2o$b2o3b2o5b3obob2o$2b2obo11bobo3bo$obob2o5bob6o3b2o$2bobo5b2o2b2o$b6o2b2obo2bo2bobobo$b2obo6bo5b6o$b2obob2obo8b3o2b2o$2b4obob2o10b4ob3o$2b3o2bob3o7b7o2bo$bo3b2o2bo9bob2obob2o3bo$4b6o2bo9b2o2b6obo$4b2o3bob3o8b3o2b3ob2o$4bob3o3b2ob2o7b2o4b2obo$6b2o3b3obo8bob2ob3o3bo$12bob3o8b3o4b2obo$9bob2ob5o5bo4b7o$8b2o3b3ob2o4b3o2b2o4b2o2bo$13b4ob3ob3obob2o3b8o$15bo3bobobo4b5o2b2o2bo$14bob2o2b2o2bob3obobobo3b3o$14bob3ob2o4b3obo3b5o$14b2ob2o4b5o4bo$17b2obob4o3b2o2bo5bo$17bob2obo2b3obob2o4b2o$16b5obo2bo4b3o6b2o$18bo2b2o2b2obob2ob2obob3o$17bobob2obo4bo2b7ob2o$22b3ob2o4b2o6b2obobo$20b6obo5bobo3b2o5b5o$24b2obo4b2o2bo3bobo2b2o3b2o$24bo2bo2bo2bo11b4obobo$23b2ob2o2bob2o9bobob7o$24b3o2bob2o2bo8b5o2b3o$24bobo4b6o8b2o2b2o2b2o$33b2o8b2ob5o2b3o$36bo6b3o3b2o2b2ob2o$34bo3bo2b2ob3o7bo$39bob3ob6o2b2o3bobo$34bob5ob3o3b2o2b5o3bo$35b3ob3ob2obobob2obo3bo2b2o$35bob3obo2bo2bob5o4bo$35bob3obo2b3o3b3o3b3o$35bo2bob3ob2obob2obo4b2o$35b4ob3obob6o4bo$36bob2o6b3ob2o3b2o$37b3o5bob3o4bo$38b5ob4o5b2o$40b6o6b2o$41bo3bo5bo$42bo2bo2bob2o$42bo3bob2o$44bo2b3o2$44b3o$46bo!
Edit 3 (2025/09/29 11:19): v0.71 is out!
Code: Select all
import subprocess
import random
import math
numships=5
foundships=0
commonspeeds=[(2,1,0),(4,1,0),(3,1,0),(5,1,0),(4,1,1),(6,1,0),(3,1,1),(5,2,0),(7,1,0),(6,1,1)]
searched=[]
timeoutsecs=600
with open('new-gliders.db.txt', 'r') as file:
shipslist = [line.strip() for line in file]
def parse(r):
rb=list(r.split("/")[0])
rs=list(r.split("/")[1])
rb.remove("B")
rs.remove("S")
return set(int(x) for x in rb), set(int(x) for x in rs)
def inrange(r1,r2,r3):
possible=list(range(9))
rb1, rs1 = parse(r1)
rb2, rs2 = parse(r2)
rb3, rs3 = parse(r3)
reqb,reqs=list(set(rb1)&set(rb2)),list(set(rs1)&set(rs2))
forb=list(set(possible) - rb1 - rb2)
fors=list(set(possible) - rs1 - rs2)
if set(rb3) & set(forb) or set(rs3) & set(fors):
return False
if set(reqb).issubset(rb3) and set(reqs).issubset(rs3):
return True
else:
return False
def canonize(period, dx, dy):
period2=int(period.replace("/2",""))
dx2, dy2=abs(int(dx)),abs(int(dy))
gcdr = math.gcd(period2, dx2, dy2)
period2, dx2, dy2 = period2 // gcdr, dx2 // gcdr, dy2 // gcdr
if dx2>=dy2:
return period2, dx2, dy2
else:
return period2, dy2, dx2
def getships(r):
chosen = r
shipperiods=[]
for line in shipslist:
parts = line.split(':')
minrule = parts[2]
maxrule = parts[3]
period = parts[4]
dx = parts[5]
dy = parts[6]
if inrange(minrule, maxrule, chosen):
addtolist=canonize(period,dx,dy)
if not addtolist in shipperiods:
shipperiods.append(addtolist)
return shipperiods
def findsmallestunknown(r):
periods=getships(r)
for k in commonspeeds:
if k not in periods:
return k
return None
def findship(rule, speed, width, symmetry):
global foundships
global searched
found=0
if [rule,speed,width,symmetry] in searched:
return
if (speed[1] != speed[2]) and (speed[1] == 0 or speed[2] == 0):
try:
process=subprocess.run(["./qfind", "-r", rule, "-v", str(str(speed[1])+"c/"+str(speed[0])), "-w", width, "-s", symmetry, "-f", "1"], timeout=timeoutsecs, capture_output=True, text=True)
if "0 spaceships" in process.stdout:
searched.append([rule,speed,width,symmetry])
pass
else:
found=1
foundships+=1
get=0
print((str(speed[1]) if str(speed[1]) == 1 else "") +"c/"+str(speed[0]) + "o in " + rule)
for k in process.stdout.splitlines():
if "x =" in k:
get=1
if get==1:
print(k)
if k.strip()=="":
break
searched.append([rule,speed,width,symmetry])
return found
except subprocess.TimeoutExpired:
searched.append([rule,speed,width,symmetry])
return 0
elif speed[1] == speed[2]:
level = int(width)*2*speed[0]
try:
process=subprocess.run(["./gfind", rule+"/N"+str(speed[1])+"D"+str(speed[0])+"/L"+str(level)], timeout=timeoutsecs, capture_output=True, text=True)
if not 'x =' in process.stdout:
searched.append([rule,speed,width,symmetry])
pass
else:
found=1
foundships+=1
get=0
print((str(speed[1]) if str(speed[1]) == 1 else "")+"c/"+str(speed[0]) + "d in " + rule)
for k in process.stdout.splitlines():
if "x =" in k:
get=1
if get==1:
print(k)
if k.strip()=="":
break
searched.append([rule,speed,width,symmetry])
return found
except subprocess.TimeoutExpired:
searched.append([rule,speed,width,symmetry])
return 0
else:
print("Oblique searching is currently not supported.")
return 0
def pickrule():
pick="B0"
while "B0" in pick:
pick=random.choice(shipslist).split(':')[2]
return str(pick)
print("SHIPSEARCH v0.71, by hibiscus - 29 September 2025")
while foundships < numships:
chos=pickrule()
for j in range(4,8):
if findsmallestunknown(chos)==None:
break
search=findship(chos,findsmallestunknown(chos),str(j),"o")
if search==1:
break
search=findship(chos,findsmallestunknown(chos),str(j),"e")
if search==1:
break
A timeout feature has been introduced - by changing the value of "timeoutsecs", you can control how long shipsearch will allow searches to run. The default is 600s (10 minutes). The commonspeeds list has also been changed.
Edit 4 (2025/09/30 10:37): I found 2 more ships in the above rule: a c/5d and a c/4d.
Code:
Select all
x = 41, y = 41, rule = B3567/S0458
2bobobobo$b2o3bobo$3obobobobo$3b4obobo$ob2o2b3o3bo$3bobo3bobo$5o4b4o$
4bo3bob3o$5o2bob2o2bo$5b2ob2o2bo$2b2o2b3obo3b2o$5b3o4bobobo$4bob2obobo
2b2o$8bo7b4obo$10b3ob2o2b2o2b3o$10bobob2ob2ob5o$11bobo4b2o2b2obo$13bob
o3b2o2b3obo$13b4obobob5o$13b2ob2obo2b5o$15bob2obob2o$13bobo5bobobobo$
14b3ob3o5bob2o$14b8o3bo2b3o$14b2ob3o4b6o$16b4obob3o3b2o$18b2o2bobo2b2o
$17bo3bo2bobobo2bo$22b3ob3o$22b4o4b2o$23bobo3bo2b2o3bo$27bobo2bo4bo$
30b2ob7o$30bobo3b3o$32bo3b3obo$32bo2b2obo$32b5o$30b5o$32b4o$32bo$34bo!
Code:
Select all
x = 65, y = 65, rule = B3567/S0458
9bo$6b2o3b3o2bo$4b12o$3b2o3bo2bo2b2o$2b2obo3b7ob3o$2bob2o2b2obo2bob3o$
b2o3b2o3bobo2b2ob2o$b2o3b2o5b3obob2o$2b2obo11bobo3bo$obob2o5bob6o3b2o$
2bobo5b2o2b2o$b6o2b2obo2bo2bobobo$b2obo6bo5b6o$b2obob2obo8b3o2b2o$2b4o
bob2o10b4ob3o$2b3o2bob3o7b7o2bo$bo3b2o2bo9bob2obob2o3bo$4b6o2bo9b2o2b
6obo$4b2o3bob3o8b3o2b3ob2o$4bob3o3b2ob2o7b2o4b2obo$6b2o3b3obo8bob2ob3o
3bo$12bob3o8b3o4b2obo$9bob2ob5o5bo4b7o$8b2o3b3ob2o4b3o2b2o4b2o2bo$13b
4ob3ob3obob2o3b8o$15bo3bobobo4b5o2b2o2bo$14bob2o2b2o2bob3obobobo3b3o$
14bob3ob2o4b3obo3b5o$14b2ob2o4b5o4bo$17b2obob4o3b2o2bo5bo$17bob2obo2b
3obob2o4b2o$16b5obo2bo4b3o6b2obo$18bo2b2o2b2obob2ob3obob5o$17bobob2obo
4bo2b2o3bo7bo$22b3ob2o4bo5bo2b2obo$20b6obo4bo5bobo4bobo$24b2obo11b4o$
24bo2bo2bob2o5b2obo2bobobo$23b2ob2o2bo3b2o4bo9bo$24b3o2bob2o3b2o3b3o2b
obo$24bobo4b2o2b4o12bo$32bobobo2bo3bo5b2o$31b2obob2obo2b3o6bo$32bo6bob
2ob4o$34bo7b4o4b3o$33bobobo5b6o3bobo$39bo3bobob7o$35bobo5bob2o2b4o$39b
o5b2ob2ob2o$37bo3bo4b4ob2o$38bo2bo2bob2o6bobo$40bobobob4ob7o$44b6obo2b
4o$46bo4bo3bo$45bo4b3o2b4ob3o$51b9o3bo$50b3ob5o2b3o$51b2ob5o2b2o$54b4o
2bobobo$55bo4b2obo$54bo3b5o$54bob2ob2o$54bob3obo$55b2o2bo$58bo!
(2025/09/30 10:40): DB entries
Code: Select all
:hibiscus, 2025:B3567/S0458:B3567/S0458:7:0:1:13:9:4b5o$2b2ob3ob2o$2ob7ob2o$3b2obob2o$b2o3bo3b2o$obob5obobo$2bo2b3o2bo$2b3obob3o$3bobobobo!
:hibiscus, 2025:B3567/S0458:B3567/S0458:5:-1:1:40:40:3bo3bo$bobobobobo$5bobobo$2o2b2obob3o$3b3ob2o2bo$b8o3bo$5bob6o$10ob3o$4b7o$b3o2b3o2bo2bo$3bo2bobo2bobo2bo$3b2ob2ob3o3bo$5b3o7bo$7bo2bo2b4ob2o3bo$9bo3b11o$11b6obo4b3o$10bo2b5o5b2obo$14bob4o3b2obo$13b3ob4o3b2obo$13b2o2b6ob2obo$14bo3b7o2b2o$14bo4b3o4bo$14bo4b2o5b3o$13b5o2bo2bob5o$15b6o3b3o2b2o$15bo2b2o3b2o2b2obo$16b2o3b4obo3b2o$18b3ob2obo3bo2bo$20bob2obo2b2o$23b2o2b2o3bo$24b3o3b2o2bo$26bo3bo2bo2b3o$27bobo2bo4b2o$31bobob2o2bo$30bo3b2ob3o$33b5obo$31bobobo$31b2ob2o$31b2obo$33b3o!
:hibiscus, 2025:B3567/S0458:B3567/S0458:4:-1:1:63:63:7b2obo2bo$5b10obo$2bob3ob3ob2obo$3bobo2bo2bo4b2o$2bob2o3bo6b2o$b4obobobobo2bob3o$b2o2b2o4bob2ob3obo$2o5bo3bo3bo5bo$4obo7bo2b2obob2o$b2obo4bobob3obo2bo2bo$3o2bo4b3obob2o$bobo2b2ob2o4bob6o$b2o2bo4bo5b4ob2o$3o3bob2o8bo3bobob2o$bo4bo2b2o6b2o2bob5o$2bo2bobobobo7b6o2b2o$bob2obobobobo9bobo2bob2o$3b4ob5obo6bob8obo$5b2o4b4o7b3obobo$5bo2bo2b2o2bo8b2ob2obo2bo$6bo2bobo3bo7b2obo2b2o2b2o$7b2o2b2ob2obo5b2obo2bo4bo$8bo2b3ob2obo3b2o2bo3b2ob3obo$9bo4b2ob2ob3o2bo2b6ob4o$13b9o2bo2b3o2b5obo$14bo2bobo3bobob3o2bobo2bobo$13b2o2b2ob3o4b4obo2b4o$13b5obo4b3o2b2obo2b5o$15bob3o3b4o$16b2o2b2ob5obob2o2b2o2bo$16b2ob2ob2o2b2o3bo7bobo$22b2o5b2o2bobo3b5o$17bo5b5obo2b3o2bobob3o$19b2ob3o6b2o4b2obo4bo$20b3ob2o6bo7bo2b2o$22b3ob2obobo6b2o2b2o2bo$23b2ob2obo10b2o4b2o$22b2ob3o4b2o5bobo5b3o$23b2ob2o5bobo4bob2ob4o$25bobob4o2bobo4bo4b4o$31bob2obobo$30b3o3b2o3b3obo3bo$31b2o2bo2b2ob3o$31b2ob2o2bo2b4o4bobo$34bo8b2ob4ob2o$33bo4bo2bo3bobo2b5o$35b2obo5bob2o2b3o$36b4o4b4obo$37b3o4bo3b2o$37bobobob2o2b3ob3o$39bo5b2o6b2obo$43b4o2bobob4o$44b3o2bo8bo$45bo3b3o3b3o3bo$45bo4b2o2b3ob5o$51bob3ob2obo$50b2ob2o2b6o$53bob6obo$52bob4o2bobo$54bob2ob3o$54b9o$53b2obo2b2o$54bob3obo!