import random, argparse
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
class GridCage():
__CAGES__=[
[(0,1)],
[(1,0)],
[(0,1), (0,2)],
[(1,0), (2,0)],
[(0,1), (1,1)],
[(0,1), (-1,1)],
[(1,0), (1,1)],
[(1,0), (0,1)],
[(1,0), (0,1), (1,1)],
[(1,0), (0,1), (0,2)],
[(0,1), (0,2), (-1,2)],
[(1,0), (2,0), (0,1)],
[(1,0), (2,0), (2,1)],
]
__CAGE_CLASSIFICATION__ = [
"2", "2",
"3", "3",
"l", "l", "l", "l",
"Q",
"L", "L", "L", "L"]
def __init__(self):
self.cells = []
def addCell(self, row, col):
self.cells.append((row, col))
def setValOp(self, value, operation):
self.value = value
self.operation = operation
def getValOp(self):
return "%d%s"%(self.value, self.operation)
def containsCell(self, row, col):
return (row, col) in self.cells
def getCells(self):
return self.cells
def __repr__(self):
return "%d%s: %s"%(self.value, self.operation, str(self.cells))
class HoloGen:
def __init__(self, opts):
self.opts = opts
self.n = opts.size
if opts.onecages == -1:
self.singleCageCount = self.n/2
else:
self.singleCageCount = opts.onecages
self.grid = [0 for i in xrange(self.n**2)]
self.cages = []
self.cageForCell={}
def generate(self):
self.generateGrid()
self.generateCages()
self.generateCageValues()
def generateGrid(self):
value = 0
while value < self.n:
value += 1
for row in xrange (0, self.n):
attempts = 0
column = -1
while True:
column = random.randint(0, self.n-1)
attempts += 1
if attempts == 20:
break
if self.getCell(row, column) != 0:
continue
if self.valueInColumn(column, value):
continue
break
if attempts == 20:
self.clearValue(value)
value -= 1
break
self.setCell(row, column, value)
def generateCages(self):
restart = True
attempts = 0
while restart:
restart = False
attempts += 1
if (attempts > self.n**2):
raise Exception("Cannot create Cages")
self.createSingleCages()
for row in xrange(self.n):
for col in xrange(self.n):
if self.inCage(row, col):
continue
possibleCages = self.getPossibleCages(row, col)
if len(possibleCages) == 0:
self.cages = []
restart = True
break
cageOffsets = random.choice(possibleCages)
cage = GridCage()
cage.addCell(row, col)
for offset in cageOffsets:
newRow = row+offset[0]
newCol = col+offset[1]
cage.addCell(newRow, newCol)
self.cages.append(cage)
if restart:
break
def inCage(self, row, col):
for cage in self.cages:
if cage.containsCell(row, col):
return True
return False
def createSingleCages(self):
singleRows = []
singleCols = []
singleVals = []
for i in xrange(self.singleCageCount):
attempts = 0
while True:
attempts += 1
if (attempts > 10*self.singleCageCount):
raise Exception("Cannot create Single Cages")
row = random.randint(0, self.n-1)
col = random.randint(0, self.n-1)
val = self.getCell(row, col)
if not row in singleRows \
and not col in singleCols \
and not val in singleVals:
break
singleRows.append(row)
singleCols.append(col)
singleVals.append(val)
cage = GridCage()
cage.addCell(row, col)
self.cages.append(cage)
def getPossibleCages(self, row, col):
pc = []
for idx, offsets in enumerate(GridCage.__CAGES__):
ct = GridCage.__CAGE_CLASSIFICATION__[idx]
if not ct in self.opts.cagetypes:
continue
valid = True
for offset in offsets:
newRow = row+offset[0]
newCol = col+offset[1]
if newRow < 0 or newCol < 0 or newRow >= self.n or newCol >= self.n:
valid = False
if self.inCage(newRow, newCol):
valid = False
if valid:
for _ in range(self.opts.cagetypes.count(ct)):
pc.append(offsets)
return pc
def generateCageValues(self):
cageNumber = 0
for cage in self.cages:
cageNumber += 1
cellList = cage.getCells()
for cell in cellList:
self.cageForCell[cell] = cageNumber
if len(cellList) == 1:
cell = cellList[0]
cage.setValOp(self.getCell(cell[0], cell[1]), "")
elif len(cellList) == 2:
operations = [x for x in self.opts.mathops]
random.shuffle(operations)
for operation in operations:
val1 = self.getCell(cellList[0][0], cellList[0][1])
val2 = self.getCell(cellList[1][0], cellList[1][1])
larger = max(val1, val2)
smaller = min(val1, val2)
if operation is "d":
if larger % smaller == 0:
break
else:
break
else:
raise Exception("No operation can be assigned to a cage.")
if operation is "a":
cage.setValOp(val1+val2, "+")
elif operation is "s":
cage.setValOp(larger-smaller, "-")
elif operation is "m":
cage.setValOp(val1*val2, "*")
elif operation is "d":
cage.setValOp(larger/smaller, "/")
else:
raise Exception("Invalid Operation %s."%operation)
else:
operations = [x for x in self.opts.mathops.replace("s","").replace("d","")]
operation = random.choice(operations)
if operation is "a":
s = 0
for cell in cellList:
s += self.getCell(cell[0], cell[1])
cage.setValOp(s, "+")
elif operation is "m":
p = 1
for cell in cellList:
p *= self.getCell(cell[0], cell[1])
cage.setValOp(p, "*")
else:
raise Exception("Invalid Operation %s."%operation)
def dump(self):
for row in reversed(xrange(self.n)):
for col in xrange (self.n):
print ("%2d|%d" % (self.getCell(row, col), self.cageForCell[(row, col)])),
print
def drawPDF(self, name, solution = False):
pdf = Canvas(name, pagesize=A4)
pdf.translate(0*mm, 297.0*mm)
gs = (210.0/(self.n+2)) * mm
pdf.translate(gs, -gs)
if not self.opts.title is None:
pdf.saveState()
pdf.setFontSize(gs/3)
pdf.drawString(0, gs/10.0, self.opts.title)
pdf.restoreState()
pdf.translate(0, -self.n*gs)
pdf.saveState()
pdf.setLineWidth(0.3*mm)
pdf.setStrokeColorRGB(0.5,0.5,0.5)
self.drawBaseGrid(pdf, gs)
pdf.restoreState()
pdf.saveState()
pdf.setFont("Helvetica", gs/4)
pdf.setLineCap(1)
pdf.setLineWidth(1*mm)
self.drawCageBorders(pdf, gs)
self.drawCageLabels(pdf, gs)
pdf.restoreState()
if solution:
pdf.saveState()
num = 0
pdf.setFont("Helvetica", gs/2)
for row in xrange(self.n):
for col in xrange (self.n):
num += 1
pdf.drawCentredString((col+0.5)*gs, (row+0.3)*gs , \
"%d"%self.getCell(row, col))
pdf.restoreState()
pdf.showPage()
pdf.save()
def drawBaseGrid(self, pdf, gs):
for row in xrange(self.n):
for col in xrange (self.n):
pdf.rect(col*gs, row*gs, gs, gs, stroke = True, fill = False)
def drawCageBorders(self, pdf, gs):
pdf.rect(0, 0, self.n*gs, self.n*gs, stroke=True, fill=False)
for row in xrange(self.n):
for col in xrange (self.n):
currentCage = self.cageForCell[(row, col)]
if row != self.n-1:
northCage = self.cageForCell[(row+1, col)]
if currentCage != northCage:
pdf.line((col)*gs, (row+1)*gs, (col+1)*gs, (row+1)*gs)
if col != self.n-1:
eastCage = self.cageForCell[(row, col+1)]
if currentCage != eastCage:
pdf.line((col+1)*gs, (row)*gs, (col+1)*gs, (row+1)*gs)
def drawCageLabels(self, pdf, gs):
for cage in self.cages:
baseCell = cage.getCells()[0]
pdf.drawString((baseCell[1]+0.05) * gs, (baseCell[0]+0.05) * gs, cage.getValOp())
def index(self, row, col):
return (row-1)*self.n+(col-1)
def getCell(self, row, col):
return self.grid[self.index(row, col)]
def setCell(self, row, col, val):
self.grid[self.index(row, col)] = val
def valueInColumn(self, column, value):
for row in xrange(0, self.n):
if self.getCell(row, column) == value:
return True
return False
def clearValue(self, value):
for row in xrange(0, self.n):
for col in xrange (0, self.n):
if self.getCell(row, col) == value:
self.setCell(row, col, 0)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generates Holokens and solutions of arbitrary sizes. More information on http://chu.in-chemnitz.de/programmieren/python/hologen.php")
parser.add_argument('-s', '--size', default='4', type=int, help="Size of the game. Default 4.")
parser.add_argument('-f', '--filename', default='out', help="Basename of the PDFs (without .pdf!). Creates two pdfs. <filename>.pdf and solution_<filename>.pdf. Default 'out'.")
parser.add_argument('-o', '--onecages', default='-1', type=int, help="Number of cages of size 1. (o <= s). Default s/2.")
parser.add_argument('-m', '--mathops', default="asmd", help="Allowd mathematical operations (a=add, s=sub, m=multipy, d=divide). Use mutiple times to increase probability. Default: asmd. Example use '-m am' for add and multiply only.")
parser.add_argument('-c', '--cagetypes', default="23QlL", help="Use cages of this type (see webpage). Use mutiple times to increase probability. Default 23QlL")
parser.add_argument('-t', '--title', default=None, help="Page title.")
parser.add_argument('-v', '--verbose', default=False, const=True, action='store_const', help="Show some more information during generate.")
opts = parser.parse_args()
hologen = HoloGen(opts)
hologen.generate()
if opts.verbose:
hologen.dump()
hologen.drawPDF(opts.filename+".pdf")
hologen.drawPDF("solution_"+opts.filename+".pdf", True)