Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pegsol/Makefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
test:
ruby converter.rb
# ruby -I. generator.rb
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove the comment?

./pegsol-instances.py # Only analyzes instances, doesn't generate them.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a simple test for the new Python script?

33 changes: 33 additions & 0 deletions pegsol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Adaption of Puzzle library from Solipeg 2.2
(http://ourworld.compuserve.com/homepages/cade/psionsof.htm, changed all
unmovable pegs to movable pegs)

Solipeg, a Classic Marble Puzzle Game for the Psion Series 3a, 3c, Siena
Version 2.2 (and 2.2 Lite) Copyright (C) 1993, 1994, 1995, 1996 J Cade Roux

"The game Puzzle-Peg (Third Edition, 1924, Lubbers & Bell Mfg. Co.,Clinton,
Iowa, USA, 50 cents) included a small booklet (23 pages),"Problems in
Puzzle-Peg", which details over 100 different end-game-type problems which
can be played on the standard English board, sent in by players of their
earlier versions. It also contained adverts for other games of theirs. All
104 of the problems are included in this distribution - embedded in the
library. The Lite version contains only the standard two starting positions,
although an external library is provided which is comparable to the internal
library."




# pddl-generators memo

The original code was probably written for ruby 1.8.
The code was fixed for ruby 2.x.
This ruby code reproduces instances takes from the book.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This ruby code reproduces instances takes from the book.
This ruby code reproduces instances taken from the book.


`ruby converter.rb` seems to generate a template for `instances.rb`.
`ruby -I. generator.rb` will generate problem files.


A new randomized implementation (generate.py) will generate a new board
which is not guaranteed to be solvable.
It generates a random board state and has a customizable board size and a corner drop off size.
142 changes: 142 additions & 0 deletions pegsol/generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/bin/env python3

"""
A randomized Pegsol generator for sequential STRIPS with negative preconditions.
"""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add: "Generated instances are not guaranteed to be solvable."



import argparse
import random
import numpy as np
import numpy.random as nr
import functools
import textwrap

parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--rows", type=int, default=7, help="Number of rows, must be odd.")
parser.add_argument("--cols", type=int, default=7, help="Number of columns, must be odd.")
parser.add_argument("--rowcuts", type=int, default=2, help="Number of rows to cut from each corner. Should be less than floor(rows/2)")
parser.add_argument("--colcuts", type=int, default=2, help="Number of cols to cut from each corner. Should be less than floor(cols/2)")
parser.add_argument("occupancy", type=float, help="Probability that the pegs are initially occupied. (0, 1]")
# parser.add_argument("--european", action="store_true", help="If present, corners are cut in a triangular form (a European pegsol board)")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment.

parser.add_argument("--seed", type=int, default=1)
args = parser.parse_args()


random.seed(args.seed)

# for development
# rows = 7
# cols = 7
# rowcuts = 2
# colcuts = 2
# occupancy = 0.8
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment?



rows = args.rows
cols = args.cols
rowcuts = args.rowcuts
colcuts = args.colcuts
occupancy = args.occupancy

assert rows % 2 == 1, f"rows={rows} must be odd"
assert cols % 2 == 1, f"cols={cols} must be odd"
assert rowcuts < rows // 2, f"rowcuts={rowcuts} must be less than floor(rows={rows}/2)"
assert rowcuts < rows // 2, f"rowcuts={rowcuts} must be less than floor(rows={rows}/2)"
assert colcuts < cols // 2, f"colcuts={colcuts} must be less than floor(cols={cols}/2)"
assert occupancy <= 1.0, f"occupancy={occupancy} must be in 0 < occupancy <= 1"
assert 0.0 < occupancy, f"occupancy={occupancy} must be in 0 < occupancy <= 1"

total = rows * cols - 4 * rowcuts * colcuts

################################################################
# board generation

INVALID = -1
FREE = 0
PEG = 1

# initialize free:0 or peg:1
board = (nr.random((rows, cols)) < occupancy).astype(int)

# cut the corners
board[:rowcuts,:colcuts] = -1
board[-rowcuts:,:colcuts] = -1
board[:rowcuts,-colcuts:] = -1
board[-rowcuts:,-colcuts:] = -1


################################################################
# pddl generation

def pos(i,j):
return f"pos-{i}-{j}"


def objects():
return [ pos(i,j) for i,j in zip(*np.where(board != INVALID)) ] + ["-", "location"]


def init():
return [
["=", ["total-cost"], "0"],
["move-ended"],
*list(in_line()),
*[ ["free", pos(i,j)] for i,j in zip(*np.where(board == FREE)) ],
*[ ["occupied", pos(i,j)] for i,j in zip(*np.where(board == PEG)) ],
]


def in_line():
for i,j in zip(*np.where(board != INVALID)):
# horizontal move
if j+2 < cols and board[i, j] != INVALID and board[i, j+1] != INVALID and board[i, j+2] != INVALID:
yield ["IN-LINE", pos(i,j), pos(i,j+1), pos(i,j+2), ]
yield ["IN-LINE", pos(i,j+2), pos(i,j+1), pos(i,j), ]
# vertical move
if i+2 < rows and board[i, j] != INVALID and board[i+1, j] != INVALID and board[i+2, j] != INVALID:
yield ["IN-LINE", pos(i,j), pos(i+1,j), pos(i+2,j), ]
yield ["IN-LINE", pos(i+2,j), pos(i+1,j), pos(i,j), ]


def goal():
return [
"and",
*[ ["free", pos(i,j)] for i,j in zip(*np.where(board != INVALID)) if i != (rows//2) and j != (cols//2) ],
["occupied", pos((rows//2), (cols//2))]
]


def problem():
return [
"define", ["problem", "pegsolprob"],
[":domain", "pegsolitaire-sequential"],
[":objects", *objects()],
[":init", *init()],
[":goal", goal()],
[":metric", "minimize", ["total-cost"]],
]


def print_sexp(obj,indent=0):
if isinstance(obj, str):
return obj
assert isinstance(obj, list), f"{obj} is not str or list"
if len(obj) > 4:
return textwrap.indent(
"(" + "\n".join(
map(lambda x: print_sexp(x,indent+1),
obj)) + ")",
" "*indent)
else:
return textwrap.indent(
"(" + " ".join(
map(lambda x: print_sexp(x,indent+1),
obj)) + ")",
" "*indent)

print_sexp(problem())




8 changes: 4 additions & 4 deletions pegsol/generator.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/ruby

require 'ftools'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If ftools/fileutils is only used to create non-nested directories, then Dir.mkdir(string) is enough.
No need to require a library that may change again in the future.

require 'fileutils'
require 'instances.rb'

class PegSolitaire
Expand Down Expand Up @@ -483,19 +483,19 @@ def pad(counter, maxCounter)
def processConfig(configName, configNum, maxNum)
pegSolitaire = PegSolitaire.new(configName, $config_names[configNum-1])

File.makedirs("tempo-sat")
FileUtils.mkdir_p("tempo-sat")
domainFile = File.new("tempo-sat/domain.pddl", "w")
pegSolitaire.printDomainTemporal(domainFile)
problemFile = File.new("tempo-sat/p#{pad(configNum,maxNum)}.pddl", "w")
pegSolitaire.printProblemTemporal(problemFile, pad(configNum,maxNum), pegSolitaire)

File.makedirs("seq-sat")
FileUtils.mkdir_p("seq-sat")
domainFile = File.new("seq-sat/domain.pddl", "w")
pegSolitaire.printDomainSequential(domainFile)
problemFile = File.new("seq-sat/p#{pad(configNum,maxNum)}.pddl", "w")
pegSolitaire.printProblemSequential(problemFile, pad(configNum,maxNum), pegSolitaire)

File.makedirs("netben-opt")
FileUtils.mkdir_p("netben-opt")
domainFile = File.new("netben-opt/domain.pddl", "w")
pegSolitaire.printDomainNetBenefit(domainFile)
problemFile1 = File.new("netben-opt/p#{pad(configNum,maxNum)}-1.pddl", "w")
Expand Down