3 # This program accepts a series of newline-separated game IDs on
4 # stdin and formats them into PostScript to be printed out. You
5 # specify using command-line options which game the IDs are for,
6 # and how many you want per page.
8 # Supported games are those which are sensibly solvable using
9 # pencil and paper: Rectangles, Pattern, Solo, Net.
11 # Command-line syntax is
13 # print.py <game-name> <format>
15 # <game-name> is one of `rect', `rectangles', `pattern', `solo',
16 # `net'. <format> is two numbers separated by an x: `2x3', for
17 # example, means two columns by three rows.
19 # The program will then read game IDs from stdin until it sees EOF,
20 # and generate as many PostScript pages on stdout as it needs.
22 # The resulting PostScript will automatically adapt itself to the
23 # size of the clip rectangle, so that the puzzles are sensibly
24 # distributed across whatever paper size you decide to use.
34 for i
in xrange(len(a
)):
48 params
, seed
= string
.split(s
, ":")
49 w
, h
= map(string
.atoi
, string
.split(params
, "x"))
52 if seed
[0] in '_'+string
.lowercase
:
53 if seed
[0] in string
.lowercase
:
54 grid
.extend([-1] * (ord(seed
[0]) - ord('a') + 1))
56 elif seed
[0] in string
.digits
:
58 while len(seed
) > 0 and seed
[0] in string
.digits
:
61 grid
.append(string
.atoi(ns
))
62 assert w
* h
== len(grid
)
63 # I'm going to arbitrarily choose to use 7pt text for the
64 # numbers, and a 14pt grid pitch.
67 # Set up coordinate system.
70 ret
.coords
= (pw
/2, pw
/2, ph
/2, ph
/2)
71 psprint(ret
, "%g %g translate" %
(-ret
.coords
[0], -ret
.coords
[2]))
72 # Draw the internal grid lines, _very_ thin (the player will
73 # need to draw over them visibly).
74 psprint(ret
, "newpath 0.01 setlinewidth")
76 psprint(ret
, "%g 0 moveto 0 %g rlineto" %
(x
* gridpitch
, h
* gridpitch
))
78 psprint(ret
, "0 %g moveto %g 0 rlineto" %
(y
* gridpitch
, w
* gridpitch
))
79 psprint(ret
, "stroke")
80 # Draw round the grid exterior, much thicker.
81 psprint(ret
, "newpath 1.5 setlinewidth")
82 psprint(ret
, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
83 (h
* gridpitch
, w
* gridpitch
, -h
* gridpitch
))
84 psprint(ret
, "closepath stroke")
85 # And draw the numbers.
86 psprint(ret
, "/Helvetica findfont %g scalefont setfont" % textht
)
91 psprint(ret
, "%g %g (%d) ctshow" % \
92 ((x
+0.5)*gridpitch
, (h
-y
-0.5)*gridpitch
, n
))
93 return ret
.coords
, ret
.s
99 params
, seed
= string
.split(s
, ":")
101 if params
[-1:] == "w":
104 w
, h
= map(string
.atoi
, string
.split(params
, "x"))
109 n
= string
.atoi(seed
[0], 16)
111 while len(seed
) > 0 and seed
[0] in 'hv':
115 hbarriers
.append((x
, y
+1))
117 vbarriers
.append((x
+1, y
))
120 assert w
* h
== len(grid
)
121 # I'm going to arbitrarily choose a 24pt grid pitch.
127 # Set up coordinate system.
130 ret
.coords
= (pw
/2, pw
/2, ph
/2, ph
/2)
131 psprint(ret
, "%g %g translate" %
(-ret
.coords
[0], -ret
.coords
[2]))
132 # Draw the base grid lines.
133 psprint(ret
, "newpath 0.02 setlinewidth")
134 for x
in xrange(1,w
):
135 psprint(ret
, "%g 0 moveto 0 %g rlineto" %
(x
* gridpitch
, h
* gridpitch
))
136 for y
in xrange(1,h
):
137 psprint(ret
, "0 %g moveto %g 0 rlineto" %
(y
* gridpitch
, w
* gridpitch
))
138 psprint(ret
, "stroke")
139 # Draw round the grid exterior.
140 psprint(ret
, "newpath")
142 psprint(ret
, "2 setlinewidth")
143 psprint(ret
, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
144 (h
* gridpitch
, w
* gridpitch
, -h
* gridpitch
))
145 psprint(ret
, "closepath stroke")
147 psprint(ret
, "newpath 2 setlinewidth 1 setlinecap")
148 for x
, y
in hbarriers
:
149 psprint(ret
, "%g %g moveto %g 0 rlineto" % \
150 (x
* gridpitch
, (h
- y
) * gridpitch
, gridpitch
))
151 for x
, y
in vbarriers
:
152 psprint(ret
, "%g %g moveto 0 -%g rlineto" % \
153 (x
* gridpitch
, (h
- y
) * gridpitch
, gridpitch
))
154 psprint(ret
, "stroke")
155 # And draw the symbol in each box.
156 for i
in xrange(len(grid
)):
160 # Rotate to canonical form.
165 elif v
in (3,6,9,12):
167 elif v
in (7,11,13,14):
169 # Centre on an area in the corner of the tile.
170 psprint(ret
, "gsave")
174 hoffset
= smalloffset
178 voffset
= smalloffset
179 psprint(ret
, "%g %g translate" % \
180 ((x
+ hoffset
) * gridpitch
, (h
- y
- voffset
) * gridpitch
))
181 psprint(ret
, "%g dup scale" %
(float(gridpitch
) * scale
/ 2))
182 psprint(ret
, "newpath 0.07 setlinewidth")
183 # Draw the radial lines.
184 for dx
, dy
, z
in ((1,0,1), (0,1,2), (-1,0,4), (0,-1,8)):
186 psprint(ret
, "0 0 moveto %d %d lineto" %
(dx
, dy
))
187 psprint(ret
, "stroke")
188 # Draw additional figures if desired.
190 # Endpoints have a little empty square at the centre.
191 psprint(ret
, "newpath %g %g moveto 0 -%g rlineto" % \
192 (squaresize
, squaresize
, squaresize
* 2))
193 psprint(ret
, "-%g 0 rlineto 0 %g rlineto closepath fill" % \
194 (squaresize
* 2, squaresize
* 2))
195 # Get back out of the centre section.
196 psprint(ret
, "grestore")
197 # Draw the endpoint square in large in the middle.
199 psprint(ret
, "gsave")
200 psprint(ret
, "%g %g translate" % \
201 ((x
+ 0.5) * gridpitch
, (h
- y
- 0.5) * gridpitch
))
202 psprint(ret
, "%g dup scale" %
(float(gridpitch
) / 2))
203 psprint(ret
, "newpath %g %g moveto 0 -%g rlineto" % \
204 (squaresize
, squaresize
, squaresize
* 2))
205 psprint(ret
, "-%g 0 rlineto 0 %g rlineto closepath fill" % \
206 (squaresize
* 2, squaresize
* 2))
207 psprint(ret
, "grestore")
208 return ret
.coords
, ret
.s
210 def pattern_format(s
):
214 params
, seed
= string
.split(s
, ":")
215 w
, h
= map(string
.atoi
, string
.split(params
, "x"))
216 rowdata
= map(lambda s
: string
.split(s
, "."), string
.split(seed
, "/"))
217 assert len(rowdata
) == w
+h
218 # I'm going to arbitrarily choose to use 7pt text for the
219 # numbers, and a 14pt grid pitch.
222 gutter
= 8 # between the numbers and the grid
223 # Find the maximum number of numbers in each dimension, to
224 # determine the border size required.
225 xborder
= reduce(max, map(len, rowdata
[w
:]))
226 yborder
= reduce(max, map(len, rowdata
[:w
]))
227 # Set up coordinate system. I'm going to put the origin at the
228 # _top left_ of the grid, so that both sets of numbers get
229 # drawn the same way.
230 pw
= (w
+ xborder
) * gridpitch
+ gutter
231 ph
= (h
+ yborder
) * gridpitch
+ gutter
232 ret
.coords
= (xborder
* gridpitch
+ gutter
, w
* gridpitch
, \
233 yborder
* gridpitch
+ gutter
, h
* gridpitch
)
234 # Draw the internal grid lines. Every fifth one is thicker, as
236 psprint(ret
, "newpath 0.1 setlinewidth")
237 for x
in xrange(1,w
):
239 psprint(ret
, "%g 0 moveto 0 %g rlineto" %
(x
* gridpitch
, -h
* gridpitch
))
240 for y
in xrange(1,h
):
242 psprint(ret
, "0 %g moveto %g 0 rlineto" %
(-y
* gridpitch
, w
* gridpitch
))
243 psprint(ret
, "stroke")
244 psprint(ret
, "newpath 0.75 setlinewidth")
245 for x
in xrange(5,w
,5):
246 psprint(ret
, "%g 0 moveto 0 %g rlineto" %
(x
* gridpitch
, -h
* gridpitch
))
247 for y
in xrange(5,h
,5):
248 psprint(ret
, "0 %g moveto %g 0 rlineto" %
(-y
* gridpitch
, w
* gridpitch
))
249 psprint(ret
, "stroke")
250 # Draw round the grid exterior.
251 psprint(ret
, "newpath 1.5 setlinewidth")
252 psprint(ret
, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
253 (-h
* gridpitch
, w
* gridpitch
, h
* gridpitch
))
254 psprint(ret
, "closepath stroke")
255 # And draw the numbers.
256 psprint(ret
, "/Helvetica findfont %g scalefont setfont" % textht
)
260 xo
= (i
+ 0.5) * gridpitch
261 yo
= (gutter
+ 0.5 * gridpitch
)
263 xo
= -(gutter
+ 0.5 * gridpitch
)
264 yo
= ((i
-w
) + 0.5) * -gridpitch
265 for j
in range(len(ns
)-1, -1, -1):
266 psprint(ret
, "%g %g (%s) ctshow" %
(xo
, yo
, ns
[j
]))
271 return ret
.coords
, ret
.s
277 params
, seed
= string
.split(s
, ":")
278 c
, r
= map(string
.atoi
, string
.split(params
, "x"))
282 if seed
[0] in '_'+string
.lowercase
:
283 if seed
[0] in string
.lowercase
:
284 grid
.extend([-1] * (ord(seed
[0]) - ord('a') + 1))
286 elif seed
[0] in string
.digits
:
288 while len(seed
) > 0 and seed
[0] in string
.digits
:
291 grid
.append(string
.atoi(ns
))
292 assert cr
* cr
== len(grid
)
293 # I'm going to arbitrarily choose to use 9pt text for the
294 # numbers, and a 16pt grid pitch.
297 # Set up coordinate system.
298 pw
= ph
= gridpitch
* cr
299 ret
.coords
= (pw
/2, pw
/2, ph
/2, ph
/2)
300 psprint(ret
, "%g %g translate" %
(-ret
.coords
[0], -ret
.coords
[2]))
301 # Draw the thin internal grid lines.
302 psprint(ret
, "newpath 0.1 setlinewidth")
303 for x
in xrange(1,cr
):
305 psprint(ret
, "%g 0 moveto 0 %g rlineto" %
(x
* gridpitch
, cr
* gridpitch
))
306 for y
in xrange(1,cr
):
308 psprint(ret
, "0 %g moveto %g 0 rlineto" %
(y
* gridpitch
, cr
* gridpitch
))
309 psprint(ret
, "stroke")
310 # Draw the thicker internal grid lines.
311 psprint(ret
, "newpath 1 setlinewidth")
312 for x
in xrange(r
,cr
,r
):
313 psprint(ret
, "%g 0 moveto 0 %g rlineto" %
(x
* gridpitch
, cr
* gridpitch
))
314 for y
in xrange(c
,cr
,c
):
315 psprint(ret
, "0 %g moveto %g 0 rlineto" %
(y
* gridpitch
, cr
* gridpitch
))
316 psprint(ret
, "stroke")
317 # Draw round the grid exterior, thicker still.
318 psprint(ret
, "newpath 1.5 setlinewidth")
319 psprint(ret
, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
320 (cr
* gridpitch
, cr
* gridpitch
, -cr
* gridpitch
))
321 psprint(ret
, "closepath stroke")
322 # And draw the numbers.
323 psprint(ret
, "/Helvetica findfont %g scalefont setfont" % textht
)
329 s
= chr(ord('a') + n
- 10)
331 s
= chr(ord('0') + n
)
332 psprint(ret
, "%g %g (%s) ctshow" % \
333 ((x
+0.5)*gridpitch
, (cr
-y
-0.5)*gridpitch
, s
))
334 return ret
.coords
, ret
.s
339 "rectangles": rect_format
,
340 "pattern": pattern_format
,
344 if len(sys
.argv
) < 3:
345 sys
.stderr
.write("print.py: expected two arguments (game and format)\n")
348 formatter
= formatters
.get(sys
.argv
[1], None)
349 if formatter
== None:
350 sys
.stderr
.write("print.py: unrecognised game name `%s'\n" % sys
.argv
[1])
354 format
= map(string
.atoi
, string
.split(sys
.argv
[2], "x"))
355 except ValueError, e
:
358 sys
.stderr
.write("print.py: expected format such as `2x3' as second" \
363 ppp
= xx
* yy
# puzzles per page
367 s
= sys
.stdin
.readline()
369 if s
[-1:] == "\n": s
= s
[:-1]
372 pages
= int((len(ids
) + ppp
- 1) / ppp
)
374 # Output initial DSC stuff.
375 print "%!PS-Adobe-3.0"
376 print "%%Creator: print.py from Simon Tatham's Puzzle Collection"
377 print "%%DocumentData: Clean7Bit"
378 print "%%LanguageLevel: 1"
379 print "%%Pages:", pages
380 print "%%DocumentNeededResources:"
381 print "%%+ font Helvetica"
382 print "%%DocumentSuppliedResources: procset Puzzles 0 0"
383 print "%%EndComments"
384 print "%%BeginProlog"
385 print "%%BeginResource: procset Puzzles 0 0"
388 print " newpath 0 0 moveto (X) true charpath flattenpath pathbbox"
389 print " 3 -1 roll add 2 div 3 1 roll pop pop sub moveto"
390 print " dup stringwidth pop 0.5 mul neg 0 rmoveto show"
392 print "%%EndResource"
395 print "%%IncludeResource: font Helvetica"
401 for i
in xrange(1, pages
+1):
402 print "%%Page:", i
, i
405 # Do the drawing for each puzzle, giving a set of PS fragments
406 # and bounding boxes.
407 fragments
= [['' for i
in xrange(xx
)] for i
in xrange(yy
)]
408 lrbound
= [(0,0) for i
in xrange(xx
)]
409 tbbound
= [(0,0) for i
in xrange(yy
)]
413 if puzzle_index
>= len(ids
):
415 coords
, frag
= formatter(ids
[puzzle_index
])
416 fragments
[y
][x
] = frag
418 lrbound
[x
] = (max(lb
, coords
[0]), max(rb
, coords
[1]))
420 tbbound
[y
] = (max(tb
, coords
[2]), max(bb
, coords
[3]))
421 puzzle_index
= puzzle_index
+ 1
423 # Now we know the sizes of everything, do the drawing in such a
424 # way that we provide equal gutter space at the page edges and
425 # between puzzle rows/columns.
428 if len(fragments
[y
][x
]) > 0:
430 print "clippath flattenpath pathbbox pop pop translate"
431 print "clippath flattenpath pathbbox 4 2 roll pop pop"
432 # Compute the total height of all puzzles, which
433 # we'll use it to work out the amount of gutter
434 # space below this puzzle.
435 htotal
= reduce(lambda a
,b
:a
+b
, map(lambda (a
,b
):a
+b
, tbbound
), 0)
436 # Now compute the total height of all puzzles
437 # _below_ this one, plus the height-below-origin of
439 hbelow
= reduce(lambda a
,b
:a
+b
, map(lambda (a
,b
):a
+b
, tbbound
[y
+1:]), 0)
440 hbelow
= hbelow
+ tbbound
[y
][1]
441 print "%g sub %d mul %d div %g add exch" %
(htotal
, yy
-y
, yy
+1, hbelow
)
442 # Now do all the same computations for width,
443 # except we need the total width of everything
444 # _before_ this one since the coordinates work the
446 wtotal
= reduce(lambda a
,b
:a
+b
, map(lambda (a
,b
):a
+b
, lrbound
), 0)
447 # Now compute the total height of all puzzles
448 # _below_ this one, plus the height-below-origin of
450 wleft
= reduce(lambda a
,b
:a
+b
, map(lambda (a
,b
):a
+b
, lrbound
[:x
]), 0)
451 wleft
= wleft
+ lrbound
[x
][0]
452 print "%g sub %d mul %d div %g add exch" %
(wtotal
, x
+1, xx
+1, wleft
)
454 sys
.stdout
.write(fragments
[y
][x
])
457 print "restore showpage"