chiark / gitweb /
065f0113707fda16ed896a645944a081dadf13a4
[quacks.git] / pumpkin-books.ps.pl
1 #!/usr/bin/perl -w
2 # Books of Pumpkins, etc.  Extensions to Quacks of Quedlinburg
3 #  SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
4 # Copyright 2020-2021 Ian Jackson
5
6 use strict;
7
8 BEGIN { unshift @INC, '.'; }
9 use Quacks;
10
11 sub veepsspot ($) {
12   my ($chip) = @_;
13   <<END;
14 gsave
15   0 chip -0.5 mul translate
16   $black
17   newpath 0 0 chip 0.5 mul 0 360 arc stroke
18   /Times-Bold findfont 7 scalefont setfont
19   -1 -5 translate
20   0 0 moveto (?) show
21   newpath
22    -1 -1 moveto 6 0 rlineto 0 7 rlineto -6 0 rlineto
23     closepath 0.5 setlinewidth stroke
24 grestore
25   $chip
26 END
27 }
28
29 sub anychip ($) {
30   my ($pips) = @_;
31   my $fsz = 10;
32   <<END;
33 gsave
34   chip -0.5 mul  $fsz -0.30 mul  moveto
35   /Helvetica-Bold findfont $fsz scalefont setfont
36   (Any) dup stringwidth  3 2 roll show
37   pop pop
38   20 0 translate
39   ${\ chip($anychip, $pips) }
40 grestore
41 END
42 }
43
44 sub exposition ($) {
45   my ($text) = @_;
46   my $fontsz = 6;
47   my $o = <<END;
48     /Times-Roman findfont $fontsz scalefont setfont $black
49 END
50   $text =~ s/\n$//;
51   my @lines = split /\n/, $text;
52   foreach my $y (0..$#lines) {
53     my $l = $lines[$y];
54     $l =~ s/[()\\]/\\$&/g;
55     my $yd = $fontsz * (0.5*@lines - $y);
56     $o .= <<END;
57     0 0.70 dc moveto
58     0 $yd rmoveto
59     ($l)  dup stringwidth pop -0.5 mul 0 rmoveto
60     show
61 END
62   }
63   $o;
64 }
65
66 sub num_players ($;$) {
67   my ($n, $plus) = @_;
68   my $o = '';
69
70   $o .= <<END;
71   gsave
72     -0.94 0.50 dc translate
73     0.3 dup scale
74 END
75
76   $o .= <<END x $n;
77     pot_image 18 0 translate
78 END
79
80   $o .= <<END if defined $plus;
81     -9 -5 moveto
82     /Helvetica-Bold findfont 25 scalefont setfont
83     ($plus) show
84 END
85
86   $o .= <<END;
87   grestore
88 END
89   return $o;
90 }
91
92 sub general_book ($$) { # put in a gsave
93   my ($this, $costs) = @_;
94   my $o = <<END;
95 tw  0  translate
96
97 3 setlinewidth
98 0.9 setgray
99 newpath
100 0 0 dc moveto
101 0 1 dc lineto stroke
102
103 $black
104 1 setlinewidth
105
106 newpath
107 -1 0  dc moveto
108 +1 0  dc lineto
109 +1 1  dc lineto
110 -1 1  dc lineto
111 closepath stroke
112
113 newpath
114 0 0 dc         moveto
115 0 thirdlineh dc rlineto
116 -1 thirdlineh dc lineto stroke
117
118 /thirddivline {               % xprop
119   newpath
120   dup -3 div  0               dc moveto
121       -3 div  thirdlineh      dc lineto stroke
122                               %
123 } def
124
125 END
126
127   $o .= <<END if @$costs == 3;
128 1 thirddivline
129 2 thirddivline
130 END
131
132   foreach my $costi (0..2) {
133     my ($cost, $pips);
134     if (@$costs == 3) {
135       $cost = $costs->[$costi];
136       $pips = qw(1 2 4)[$costi];
137     } else {
138       next unless $costi == 1;
139       $cost = $costs->[0];
140       $pips = $costs->[1];
141     }
142     $o .= <<END
143   costfont setfont
144   -2.5 $costi add 3 div
145 gsave
146   dup costcirch dc translate
147   ${\ chip($this,$pips) }
148 grestore
149   costtexth exch costtextdx add exch dc moveto
150   $black
151   ($cost)
152   dup stringwidth pop  -0.5 mul  costtextsz neg  rmoveto
153   show
154 END
155   }
156
157   $o;
158 }
159   
160 sub book_green () {
161   my $o = general_book($green, [qw(5 9 15)]);
162
163   $o .= <<END;
164 gsave 0.25 0.15 dc translate ${\ chip($pumpkin,0) } grestore
165 gsave 0.25 0.40 dc translate ${\ chip($green,0) } grestore
166
167 /Times-Bold findfont
168 dup
169   6 scalefont setfont
170   0.40 0.17 dc moveto (last) show
171 %
172   8 scalefont setfont
173   0.40 0.09 dc moveto (3) show
174 /Times-Roman findfont
175   38 scalefont setfont
176   0.45 0.14 dc moveto (}) show
177
178 gsave
179   0.85 0.275 dc translate
180   ${\ ruby() }
181 grestore
182 END
183
184   $o .= exposition(<<END);
185 For each pumpkin in the last 3 chips,
186 receive 1 ruby.
187 But, not more rubies than the number
188 of green chips in your pot.
189 END
190
191   $o;
192 }
193   
194 sub book_red () {
195   my $o = general_book($red, [qw(4 9 16)]);
196
197   $o .= <<END;
198 gsave 0.35 0.30 dc translate ${\ chip($pumpkin,0) } grestore
199
200 /Times-Bold findfont
201   15 scalefont setfont
202   0.50 0.24 dc moveto (+1) show
203
204 END
205
206   $o .= exposition(<<END);
207 The next 1/2/4 pumpkins you place are
208 each moved one extra space.
209 (After applying any other special effects;
210 one extra space no matter how many reds)
211 END
212
213   $o;
214 }
215
216 sub book_blue () {
217   my $o = general_book($blue, [qw(4 10 18)]);
218
219   $o .= <<END;
220 /Times-Bold findfont 15 scalefont setfont $black
221
222 gsave
223   0.50 0.65 dc translate
224   0.65 dup scale
225   gsave -10 0 translate ${\ chip($pumpkin,0) } grestore
226   arrow
227 grestore
228 END
229
230   my $exchip = sub {
231     my ($that, $pips) = @_;
232     <<END;
233   gsave ${\ chip($that, $pips) } grestore
234 END
235   };
236   my $exslash = sub {
237     <<END;
238     8 -5 moveto (/) show
239     20 0 translate
240 END
241   };
242   
243   my $exchline = sub {
244     my ($y, $pips, $content) = @_;
245     <<END;
246 gsave 
247   0.16 0.15 0.16 $y mul add dc translate
248   0.60 dup scale
249   gsave ${\ chip($blue,$pips) } grestore
250   8 -3 moveto (:) show
251   3 0 translate
252   0.8 dup scale
253   21 0 translate
254 $content
255 grestore
256 END
257   };
258
259   $o .= $exchline->(2, 1, <<END);
260   ${\ $exchip->($green,1) }  ${\ $exslash->() }
261   ${\ $exchip->($red,  1) }  ${\ $exslash->() }
262   ${\ $exchip->($blue, 1) }  ${\ $exslash->() }
263   ${\ $exchip->($yellow, 1) }
264 END
265
266   $o .= $exchline->(1, 2, <<END);
267   ${\ $exchip->($moth,1) }  ${\ $exslash->() }
268   ${\ $exchip->($purple,1) }  ${\ $exslash->() }
269   ${\ anychip(2) }
270 END
271
272   $o .= $exchline->(0, 4, <<END);
273   ${\ anychip(4) }
274 END
275
276   $o .= exposition(<<END);
277 If the previous chip placed was a pumpkin,
278 you may exchange it as follows:
279
280  
281 END
282
283   $o;
284 }
285
286 sub book_lotus () {
287   my $o = general_book($lotus, [8, 0]);
288
289   $o .= <<END;
290 /Times-Bold findfont 15 scalefont setfont $black
291
292 gsave
293   0.36 0.38 dc translate
294   0.80 dup scale
295   gsave ${\ chip($lotus,0) } grestore
296
297   9 -4 moveto (=) show
298 grestore
299 gsave
300   0.20 0.15 dc translate
301   0.80 dup scale
302   gsave ${\ chip($pumpkin,0) } grestore
303   chip 0.5 mul 0 translate
304   gsave ${\ chip($pumpkin,0) } grestore
305
306   10 -4.5 moveto (... +1) show
307
308 grestore
309 END
310 <<END;
311 gsave
312   0.16 0.20  dc translate
313   0.60 dup scale
314
315   gsave ${\ veepsspot(chip($pumpkin, 0)) } grestore
316   gsave 0 20 translate ${\ chip($purple, 1) } grestore
317   27 0 translate
318 grestore
319 END
320
321
322   $o .= exposition(<<END);
323 The value of this chip is
324 1 higher than the number of pumpkins
325 previously placed in the pot (but max.4)
326 END
327
328   $o;
329 }
330
331 sub book_purple () {
332   my $o = general_book($purple, [10, 1]);
333
334   my $input = <<END;
335   gsave ${\ veepsspot(chip($pumpkin, 0)) } grestore
336   gsave 0 20 translate ${\ chip($purple, 1) } grestore
337 END
338
339   $o .= <<END;
340 /Times-Bold findfont 15 scalefont setfont $black
341 gsave
342   0.16 0.20  dc translate
343   0.60 dup scale
344
345 $input
346   8 -12 moveto (...) show
347   27 0 translate
348 $input
349 grestore
350
351 gsave
352 /Times-Roman findfont
353   32 scalefont setfont
354   0.48 0.14 dc moveto (}) show
355 grestore
356
357 gsave
358    0.83 0.25 dc translate
359    0.85 dup scale
360    ${\ chip($anychip, 0) }
361 grestore
362
363 gsave
364   0.72 0.22 dc translate
365   0.50 dup scale
366    $black 0 0 moveto (?+?) show
367 grestore
368 END
369
370   $o .= exposition(<<END);
371 For each pumpkin in the pot (but
372  not more than the number of purple chips),
373 add up the VPs of the covered spaces.
374 Buy 1/2 chips of up to that total value.
375 END
376
377   $o;
378 }
379
380 sub book_yellow () {
381   my $o = general_book($yellow, [qw(7 12 19)]);
382
383   $o .= <<END;
384 gsave
385   0.50 0.62 dc translate
386   0.65 dup scale
387   -26 0 translate
388   gsave 26 0 translate ${\ chip($anychip,0) } grestore
389   gsave 46 0 translate ${\ chip($pumpkin,0) } grestore
390   8 14 translate
391   -120 rotate
392   ${\ arrow_any("
393     gsave
394      arrowlen 0 translate
395   -85 rotate
396     -40 0
397       40
398       300 0 arc
399     stroke
400     grestore
401   ") }
402 grestore
403 END
404
405   my $exchip = sub {
406     my ($that, $pips) = @_;
407     <<END;
408   gsave ${\ chip($that, $pips) } grestore
409 END
410   };
411   my $exslash = sub {
412     <<END;
413     8 -5 moveto (/) show
414     20 0 translate
415 END
416   };
417   
418   $o .= exposition(<<END);
419 Move 1/2/4 already-placed pumpkins,
420 to after the yellow.
421 (always immediately after, only one space,
422 regardless of other rules)
423 END
424
425   $o;
426 }
427
428 sub black_common ($) {
429   my ($second) = @_;
430   my $font = <<END;
431 /Helvetica-Bold findfont 5.5 scalefont setfont
432 END
433   <<END;
434     gsave
435       .45 .27 dc translate
436       droplet_image
437       3.5 -1 translate .7 dup scale
438       arrow
439     grestore
440     gsave
441       .40 .08 dc translate
442       $font
443       ($second) 
444         dup stringwidth .5 mul exch .5 mul exch translate
445         dup stringwidth -1 mul exch -1 mul exch moveto show
446       .7 dup scale 7 3 translate
447       ${\ ruby() }
448     grestore
449     gsave
450       .15 .37 dc translate
451
452       8.2 3.5 moveto
453       $font
454       (closest) show
455
456       .7 dup scale
457       gsave
458         17 0 translate
459         180 rotate
460         arrow
461       grestore
462       gsave
463         35 0 translate
464         arrow
465         newpath 0 0 moveto -18 0 rlineto stroke
466       grestore
467
468       ${\ chip($pumpkin,0) }
469
470       51 0 translate
471       ${\ chip($moth,   1) }
472     grestore
473 END
474 }
475
476 sub book_black_pair () {
477   my $o = general_book($moth, [10, 1]);
478
479   $o .= num_players(2);
480   $o .= black_common('equal:');
481
482   $o .= exposition(<<END);
483 The player with the shortest distance
484 between a black chip and a pumpkin
485 gets to move their droplet.
486
487 If tied, both players get a ruby.
488 END
489
490   $o;
491 }
492
493 sub book_black_more () {
494   my $o = general_book($moth, [10, 1]);
495
496   $o .= num_players(3,'+');
497   $o .= black_common('2nd:');
498
499   $o .= exposition(<<END);
500
501 The player(s) with the shortest distance
502 between a black chip and a pumpkin
503 get to move their droplet.
504 If only one player won a droplet, the
505 player(s) with the next-shortest distance
506 get a ruby.
507 END
508
509   $o;
510 }
511 ps_start('7 30 translate');
512
513 sub def_image ($$) {
514   my ($ncomps, $name) = @_;
515
516   my $ext = $ncomps == 1 ? 'pgm' : $ncomps == 3 ? 'ppm' : die;
517   my $oper = $ncomps == 1 ? 'image' :
518              $ncomps == 3 ? 'false 3 colorimage' : die;
519   my $magic = $ncomps == 1 ? 'P2' : $ncomps == 3 ? 'P3' : die;
520
521   open B, "$name.$ext" or die $!;
522   <B> eq "$magic\n" or die "$name.$ext expected $magic";
523   local $/ = undef;
524   my @d = split ' ', <B>;
525   close B;
526
527   my $w = shift @d;
528   my $h = shift @d;
529   my $maxval = shift @d;
530   die unless $maxval eq 255;
531
532   my $sz = $w/17.0;
533   my $hsz = $sz/2;
534
535   $ps_framing .= <<END . '{<';
536 /${name}_image {
537   $w $h 8 
538   [ -$sz 0 0  -$sz  $w 2 div  $h 2 div  ]
539 END
540   for (my $i=0; $i< $w*$h*$ncomps; $i++) {
541     $ps_framing .= sprintf "%02x", shift @d;
542   }
543   $ps_framing .= <<END;
544 >} $oper
545 } def
546 END
547 }
548
549 sub tile {
550   my @tiles;
551   my $index = 1;
552
553   foreach my $spec (@_) {
554     my @datas;
555     foreach my $stem (split m{/}, $spec) {
556       my $func = $stem;
557       $func =~ y/-/_/;
558       $func = ${*::}{"book_$func"} // die "$func ?";
559       my $data = $func->();
560       $data .= <<END;
561     /Courier-Bold findfont 6 scalefont setfont
562     -0.98 0.94 dc moveto
563     ($index) show
564 END
565       open F, ">book-$stem.ps" or die $!;
566       print F $ps_framing, $data, "\nshowpage\n" or die $!;
567       close F or die $!;
568       push @datas, $data;
569     }
570     foreach my $dd (@datas) {
571       push @tiles, $dd;
572     }
573     $index++;
574   }
575
576   my $pos = 0;
577   my $o = '';
578   my $showpage = sub {
579     $o .= <<END;
580 showpage
581 $page_pre
582 END
583   };
584
585   while (my $d = pop @tiles) {
586     if ($pos >= 5) {
587       $pos -= 5;
588       $showpage->();
589     }
590     $o .= <<END;
591 gsave
592 END
593     if ($pos < 3) {
594       $o .= <<END
595         0   th 10 add  $pos mul  translate
596 END
597     } else {
598       $o .= <<END
599 tw 2 mul 7 add
600    dup    th add  0 translate
601    90 rotate
602    $pos 3 sub mul  0 translate
603 END
604     }
605     $o .= "\n".$d."\n";
606     $o .= <<END;
607 grestore
608 END
609     $pos++;
610   }
611   $showpage->();
612   $o;
613 }
614
615 def_image(1, "bag");
616 def_image(1, "pot");
617 def_image(3, "droplet");
618
619 print $ps_framing or die $!;
620
621 print "gsave\n" or die $!;
622
623 print tile(@ARGV) or die $! if 1;
624