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 and Solo.
11 # Command-line syntax is
13 # print.py <game-name> <format>
15 # <game-name> is one of `rect', `rectangles', `pattern', `solo'.
16 # <format> is two numbers separated by an x: `2x3', for example,
17 # 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
95 def pattern_format(s
):
99 params
, seed
= string
.split(s
, ":")
100 w
, h
= map(string
.atoi
, string
.split(params
, "x"))
101 rowdata
= map(lambda s
: string
.split(s
, "."), string
.split(seed
, "/"))
102 assert len(rowdata
) == w
+h
103 # I'm going to arbitrarily choose to use 7pt text for the
104 # numbers, and a 14pt grid pitch.
107 gutter
= 8 # between the numbers and the grid
108 # Find the maximum number of numbers in each dimension, to
109 # determine the border size required.
110 xborder
= reduce(max, map(len, rowdata
[w
:]))
111 yborder
= reduce(max, map(len, rowdata
[:w
]))
112 # Set up coordinate system. I'm going to put the origin at the
113 # _top left_ of the grid, so that both sets of numbers get
114 # drawn the same way.
115 pw
= (w
+ xborder
) * gridpitch
+ gutter
116 ph
= (h
+ yborder
) * gridpitch
+ gutter
117 ret
.coords
= (xborder
* gridpitch
+ gutter
, w
* gridpitch
, \
118 yborder
* gridpitch
+ gutter
, h
* gridpitch
)
119 # Draw the internal grid lines. Every fifth one is thicker, as
121 psprint(ret
, "newpath 0.1 setlinewidth")
122 for x
in xrange(1,w
):
124 psprint(ret
, "%g 0 moveto 0 %g rlineto" %
(x
* gridpitch
, -h
* gridpitch
))
125 for y
in xrange(1,h
):
127 psprint(ret
, "0 %g moveto %g 0 rlineto" %
(-y
* gridpitch
, w
* gridpitch
))
128 psprint(ret
, "stroke")
129 psprint(ret
, "newpath 0.75 setlinewidth")
130 for x
in xrange(5,w
,5):
131 psprint(ret
, "%g 0 moveto 0 %g rlineto" %
(x
* gridpitch
, -h
* gridpitch
))
132 for y
in xrange(5,h
,5):
133 psprint(ret
, "0 %g moveto %g 0 rlineto" %
(-y
* gridpitch
, w
* gridpitch
))
134 psprint(ret
, "stroke")
135 # Draw round the grid exterior.
136 psprint(ret
, "newpath 1.5 setlinewidth")
137 psprint(ret
, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
138 (-h
* gridpitch
, w
* gridpitch
, h
* gridpitch
))
139 psprint(ret
, "closepath stroke")
140 # And draw the numbers.
141 psprint(ret
, "/Helvetica findfont %g scalefont setfont" % textht
)
145 xo
= (i
+ 0.5) * gridpitch
146 yo
= (gutter
+ 0.5 * gridpitch
)
148 xo
= -(gutter
+ 0.5 * gridpitch
)
149 yo
= ((i
-w
) + 0.5) * -gridpitch
150 for j
in range(len(ns
)-1, -1, -1):
151 psprint(ret
, "%g %g (%s) ctshow" %
(xo
, yo
, ns
[j
]))
156 return ret
.coords
, ret
.s
162 params
, seed
= string
.split(s
, ":")
163 c
, r
= map(string
.atoi
, string
.split(params
, "x"))
167 if seed
[0] in '_'+string
.lowercase
:
168 if seed
[0] in string
.lowercase
:
169 grid
.extend([-1] * (ord(seed
[0]) - ord('a') + 1))
171 elif seed
[0] in string
.digits
:
173 while len(seed
) > 0 and seed
[0] in string
.digits
:
176 grid
.append(string
.atoi(ns
))
177 assert cr
* cr
== len(grid
)
178 # I'm going to arbitrarily choose to use 9pt text for the
179 # numbers, and a 16pt grid pitch.
182 # Set up coordinate system.
183 pw
= ph
= gridpitch
* cr
184 ret
.coords
= (pw
/2, pw
/2, ph
/2, ph
/2)
185 psprint(ret
, "%g %g translate" %
(-ret
.coords
[0], -ret
.coords
[2]))
186 # Draw the thin internal grid lines.
187 psprint(ret
, "newpath 0.1 setlinewidth")
188 for x
in xrange(1,cr
):
190 psprint(ret
, "%g 0 moveto 0 %g rlineto" %
(x
* gridpitch
, cr
* gridpitch
))
191 for y
in xrange(1,cr
):
193 psprint(ret
, "0 %g moveto %g 0 rlineto" %
(y
* gridpitch
, cr
* gridpitch
))
194 psprint(ret
, "stroke")
195 # Draw the thicker internal grid lines.
196 psprint(ret
, "newpath 1 setlinewidth")
197 for x
in xrange(r
,cr
,r
):
198 psprint(ret
, "%g 0 moveto 0 %g rlineto" %
(x
* gridpitch
, cr
* gridpitch
))
199 for y
in xrange(c
,cr
,c
):
200 psprint(ret
, "0 %g moveto %g 0 rlineto" %
(y
* gridpitch
, cr
* gridpitch
))
201 psprint(ret
, "stroke")
202 # Draw round the grid exterior, thicker still.
203 psprint(ret
, "newpath 1.5 setlinewidth")
204 psprint(ret
, "0 0 moveto 0 %g rlineto %g 0 rlineto 0 %g rlineto" % \
205 (cr
* gridpitch
, cr
* gridpitch
, -cr
* gridpitch
))
206 psprint(ret
, "closepath stroke")
207 # And draw the numbers.
208 psprint(ret
, "/Helvetica findfont %g scalefont setfont" % textht
)
214 s
= chr(ord('a') + n
- 10)
216 s
= chr(ord('0') + n
)
217 psprint(ret
, "%g %g (%s) ctshow" % \
218 ((x
+0.5)*gridpitch
, (cr
-y
-0.5)*gridpitch
, s
))
219 return ret
.coords
, ret
.s
223 "rectangles": rect_format
,
224 "pattern": pattern_format
,
228 if len(sys
.argv
) < 3:
229 sys
.stderr
.write("print.py: expected two arguments (game and format)\n")
232 formatter
= formatters
.get(sys
.argv
[1], None)
233 if formatter
== None:
234 sys
.stderr
.write("print.py: unrecognised game name `%s'\n" % sys
.argv
[1])
238 format
= map(string
.atoi
, string
.split(sys
.argv
[2], "x"))
239 except ValueError, e
:
242 sys
.stderr
.write("print.py: expected format such as `2x3' as second" \
247 ppp
= xx
* yy
# puzzles per page
251 s
= sys
.stdin
.readline()
253 if s
[-1:] == "\n": s
= s
[:-1]
256 pages
= int((len(ids
) + ppp
- 1) / ppp
)
258 # Output initial DSC stuff.
259 print "%!PS-Adobe-3.0"
260 print "%%Creator: print.py from Simon Tatham's Puzzle Collection"
261 print "%%DocumentData: Clean7Bit"
262 print "%%LanguageLevel: 1"
263 print "%%Pages:", pages
264 print "%%DocumentNeededResources:"
265 print "%%+ font Helvetica"
266 print "%%DocumentSuppliedResources: procset Puzzles 0 0"
267 print "%%EndComments"
268 print "%%BeginProlog"
269 print "%%BeginResource: procset Puzzles 0 0"
272 print " newpath 0 0 moveto (X) true charpath flattenpath pathbbox"
273 print " 3 -1 roll add 2 div 3 1 roll pop pop sub moveto"
274 print " dup stringwidth pop 0.5 mul neg 0 rmoveto show"
276 print "%%EndResource"
279 print "%%IncludeResource: font Helvetica"
285 for i
in xrange(1, pages
+1):
286 print "%%Page:", i
, i
289 # Do the drawing for each puzzle, giving a set of PS fragments
290 # and bounding boxes.
291 fragments
= [['' for i
in xrange(xx
)] for i
in xrange(yy
)]
292 lrbound
= [(0,0) for i
in xrange(xx
)]
293 tbbound
= [(0,0) for i
in xrange(yy
)]
297 if puzzle_index
>= len(ids
):
299 coords
, frag
= formatter(ids
[puzzle_index
])
300 fragments
[y
][x
] = frag
302 lrbound
[x
] = (max(lb
, coords
[0]), max(rb
, coords
[1]))
304 tbbound
[y
] = (max(tb
, coords
[2]), max(bb
, coords
[3]))
305 puzzle_index
= puzzle_index
+ 1
307 # Now we know the sizes of everything, do the drawing in such a
308 # way that we provide equal gutter space at the page edges and
309 # between puzzle rows/columns.
312 if len(fragments
[y
][x
]) > 0:
314 print "clippath flattenpath pathbbox pop pop translate"
315 print "clippath flattenpath pathbbox 4 2 roll pop pop"
316 # Compute the total height of all puzzles, which
317 # we'll use it to work out the amount of gutter
318 # space below this puzzle.
319 htotal
= reduce(lambda a
,b
:a
+b
, map(lambda (a
,b
):a
+b
, tbbound
), 0)
320 # Now compute the total height of all puzzles
321 # _below_ this one, plus the height-below-origin of
323 hbelow
= reduce(lambda a
,b
:a
+b
, map(lambda (a
,b
):a
+b
, tbbound
[y
+1:]), 0)
324 hbelow
= hbelow
+ tbbound
[y
][1]
325 print "%g sub %d mul %d div %g add exch" %
(htotal
, yy
-y
, yy
+1, hbelow
)
326 # Now do all the same computations for width,
327 # except we need the total width of everything
328 # _before_ this one since the coordinates work the
330 wtotal
= reduce(lambda a
,b
:a
+b
, map(lambda (a
,b
):a
+b
, lrbound
), 0)
331 # Now compute the total height of all puzzles
332 # _below_ this one, plus the height-below-origin of
334 wleft
= reduce(lambda a
,b
:a
+b
, map(lambda (a
,b
):a
+b
, lrbound
[:x
]), 0)
335 wleft
= wleft
+ lrbound
[x
][0]
336 print "%g sub %d mul %d div %g add exch" %
(wtotal
, x
+1, xx
+1, wleft
)
338 sys
.stdout
.write(fragments
[y
][x
])
341 print "restore showpage"