chiark / gitweb /
89a78af26f4969b56f94215c0782abf00e6f0ad1
[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 my $anychip = ['0.8', '0'];
12
13 our $page_pre = <<END;
14 72 25.4 div dup scale
15 %210 0 translate
16 %90 rotate
17 7 30 translate
18 END
19
20 sub colour ($) {
21   my ($c) = @_;
22   if ($c =~ m{^(\d+)/(\d+)/(\d+)$}) {
23     return (join ' ', map { $_ / 255.0 } ($1,$2,$3)).' setrgbcolor';
24   } elsif ($c =~ m/[^ 0-9.]/) {
25     return $c;
26   } elsif ($c =~ m/^\s*\S+\s*$/) {
27     return "$c setgray";
28   } elsif ($c =~ m/./) {
29     return "$c setrgbcolor";
30   } else {
31     return '';
32   }
33 }
34
35 our $black = colour('0');
36
37 sub chip ($$) {
38   my ($cary, $pips) = @_; # put in a gsave translate
39   my $o = <<END;
40   newpath
41   0 0 chip 0.5 mul 0 360 arc
42   gsave 1 setlinewidth $black stroke grestore
43   ${\ colour($cary->[0]) } fill
44 END
45   if ($pips) {
46     $o .= <<END;
47   ${\ colour($cary->[1]) }
48 END
49   }
50   my $spot = sub {
51     my ($x,$y) = @_;
52     $o .= <<END;
53     newpath
54     spot 0.5 sqrt mul 1.1 mul dup
55     $x mul exch $y mul
56     spot 0.5 mul
57     0 360 arc fill
58 END
59   };
60
61   $spot->( 0, 0) if $pips & 1;
62   $spot->(-1,-1) if $pips & 6;
63   $spot->(+1,+1) if $pips & 6;
64   $spot->(-1,+1) if $pips & 4;
65   $spot->(+1,-1) if $pips & 4;
66
67   $o;
68 }
69
70 sub veepsspot ($) {
71   my ($chip) = @_;
72   <<END;
73 gsave
74   0 chip -0.5 mul translate
75   $black
76   newpath 0 0 chip 0.5 mul 0 360 arc stroke
77   /Times-Bold findfont 7 scalefont setfont
78   -1 -5 translate
79   0 0 moveto (?) show
80   newpath
81    -1 -1 moveto 6 0 rlineto 0 7 rlineto -6 0 rlineto
82     closepath 0.5 setlinewidth stroke
83 grestore
84   $chip
85 END
86 }
87
88 sub anychip ($) {
89   my ($pips) = @_;
90   my $fsz = 10;
91   <<END;
92 gsave
93   chip -0.5 mul  $fsz -0.30 mul  moveto
94   /Helvetica-Bold findfont $fsz scalefont setfont
95   (Any) dup stringwidth  3 2 roll show
96   pop pop
97   20 0 translate
98   ${\ chip($anychip, $pips) }
99 grestore
100 END
101 }
102
103 sub ruby () { # put in gsave translate
104   <<END;
105   newpath
106     rubysz neg  0 moveto
107     0  rubysz neg lineto
108     rubysz      0 lineto
109     0      rubysz lineto
110     closepath
111     ${\ colour('1 .2 .2') } gsave fill grestore
112     $black 1 setlinewidth stroke
113 END
114 }
115
116 sub exposition ($) {
117   my ($text) = @_;
118   my $fontsz = 6;
119   my $o = <<END;
120     /Times-Roman findfont $fontsz scalefont setfont $black
121 END
122   $text =~ s/\n$//;
123   my @lines = split /\n/, $text;
124   foreach my $y (0..$#lines) {
125     my $l = $lines[$y];
126     $l =~ s/[()\\]/\\$&/g;
127     my $yd = $fontsz * (0.5*@lines - $y);
128     $o .= <<END;
129     0 0.70 dc moveto
130     0 $yd rmoveto
131     ($l)  dup stringwidth pop -0.5 mul 0 rmoveto
132     show
133 END
134   }
135   $o;
136 }
137
138 sub num_players ($;$) {
139   my ($n, $plus) = @_;
140   my $o = '';
141
142   $o .= <<END;
143   gsave
144     -0.94 0.50 dc translate
145     0.3 dup scale
146 END
147
148   $o .= <<END x $n;
149     pot_image 18 0 translate
150 END
151
152   $o .= <<END if defined $plus;
153     -9 -5 moveto
154     /Helvetica-Bold findfont 25 scalefont setfont
155     ($plus) show
156 END
157
158   $o .= <<END;
159   grestore
160 END
161   return $o;
162 }
163
164 sub general_book ($$) { # put in a gsave
165   my ($this, $costs) = @_;
166   my $o = <<END;
167 tw  0  translate
168
169 3 setlinewidth
170 0.9 setgray
171 newpath
172 0 0 dc moveto
173 0 1 dc lineto stroke
174
175 $black
176 1 setlinewidth
177
178 newpath
179 -1 0  dc moveto
180 +1 0  dc lineto
181 +1 1  dc lineto
182 -1 1  dc lineto
183 closepath stroke
184
185 newpath
186 0 0 dc         moveto
187 0 thirdlineh dc rlineto
188 -1 thirdlineh dc lineto stroke
189
190 /thirddivline {               % xprop
191   newpath
192   dup -3 div  0               dc moveto
193       -3 div  thirdlineh      dc lineto stroke
194                               %
195 } def
196
197 END
198
199   $o .= <<END if @$costs == 3;
200 1 thirddivline
201 2 thirddivline
202 END
203
204   foreach my $costi (0..2) {
205     my ($cost, $pips);
206     if (@$costs == 3) {
207       $cost = $costs->[$costi];
208       $pips = qw(1 2 4)[$costi];
209     } else {
210       next unless $costi == 1;
211       $cost = $costs->[0];
212       $pips = $costs->[1];
213     }
214     $o .= <<END
215   costfont setfont
216   -2.5 $costi add 3 div
217 gsave
218   dup costcirch dc translate
219   ${\ chip($this,$pips) }
220 grestore
221   costtexth exch costtextdx add exch dc moveto
222   $black
223   ($cost)
224   dup stringwidth pop  -0.5 mul  costtextsz neg  rmoveto
225   show
226 END
227   }
228
229   $o;
230 }
231   
232 sub book_green () {
233   my $o = general_book($green, [qw(5 9 15)]);
234
235   $o .= <<END;
236 gsave 0.25 0.15 dc translate ${\ chip($pumpkin,0) } grestore
237 gsave 0.25 0.40 dc translate ${\ chip($green,0) } grestore
238
239 /Times-Bold findfont
240 dup
241   6 scalefont setfont
242   0.40 0.17 dc moveto (last) show
243 %
244   8 scalefont setfont
245   0.40 0.09 dc moveto (3) show
246 /Times-Roman findfont
247   38 scalefont setfont
248   0.45 0.14 dc moveto (}) show
249
250 gsave
251   0.85 0.275 dc translate
252   ${\ ruby() }
253 grestore
254 END
255
256   $o .= exposition(<<END);
257 For each pumpkin in the last 3 chips,
258 receive 1 ruby.
259 But, not more rubies than the number
260 of green chips in your pot.
261 END
262
263   $o;
264 }
265   
266 sub book_red () {
267   my $o = general_book($red, [qw(4 9 16)]);
268
269   $o .= <<END;
270 gsave 0.35 0.30 dc translate ${\ chip($pumpkin,0) } grestore
271
272 /Times-Bold findfont
273   15 scalefont setfont
274   0.50 0.24 dc moveto (+1) show
275
276 END
277
278   $o .= exposition(<<END);
279 The next 1/2/4 pumpkins you place are
280 each moved one extra space.
281 (After applying any other special effects;
282 one extra space no matter how many reds)
283 END
284
285   $o;
286 }
287
288 sub book_blue () {
289   my $o = general_book($blue, [qw(4 10 18)]);
290
291   $o .= <<END;
292 /Times-Bold findfont 15 scalefont setfont $black
293
294 gsave
295   0.50 0.65 dc translate
296   0.65 dup scale
297   gsave -10 0 translate ${\ chip($pumpkin,0) } grestore
298   arrow
299 grestore
300 END
301
302   my $exchip = sub {
303     my ($that, $pips) = @_;
304     <<END;
305   gsave ${\ chip($that, $pips) } grestore
306 END
307   };
308   my $exslash = sub {
309     <<END;
310     8 -5 moveto (/) show
311     20 0 translate
312 END
313   };
314   
315   my $exchline = sub {
316     my ($y, $pips, $content) = @_;
317     <<END;
318 gsave 
319   0.16 0.15 0.16 $y mul add dc translate
320   0.60 dup scale
321   gsave ${\ chip($blue,$pips) } grestore
322   8 -3 moveto (:) show
323   3 0 translate
324   0.8 dup scale
325   21 0 translate
326 $content
327 grestore
328 END
329   };
330
331   $o .= $exchline->(2, 1, <<END);
332   ${\ $exchip->($green,1) }  ${\ $exslash->() }
333   ${\ $exchip->($red,  1) }  ${\ $exslash->() }
334   ${\ $exchip->($blue, 1) }  ${\ $exslash->() }
335   ${\ $exchip->($yellow, 1) }
336 END
337
338   $o .= $exchline->(1, 2, <<END);
339   ${\ $exchip->($moth,1) }  ${\ $exslash->() }
340   ${\ $exchip->($purple,1) }  ${\ $exslash->() }
341   ${\ anychip(2) }
342 END
343
344   $o .= $exchline->(0, 4, <<END);
345   ${\ anychip(4) }
346 END
347
348   $o .= exposition(<<END);
349 If the previous chip placed was a pumpkin,
350 you may exchange it as follows:
351
352  
353 END
354
355   $o;
356 }
357
358 sub book_lotus () {
359   my $o = general_book($lotus, [8, 0]);
360
361   $o .= <<END;
362 /Times-Bold findfont 15 scalefont setfont $black
363
364 gsave
365   0.36 0.38 dc translate
366   0.80 dup scale
367   gsave ${\ chip($lotus,0) } grestore
368
369   9 -4 moveto (=) show
370 grestore
371 gsave
372   0.20 0.15 dc translate
373   0.80 dup scale
374   gsave ${\ chip($pumpkin,0) } grestore
375   chip 0.5 mul 0 translate
376   gsave ${\ chip($pumpkin,0) } grestore
377
378   10 -4.5 moveto (... +1) show
379
380 grestore
381 END
382 <<END;
383 gsave
384   0.16 0.20  dc translate
385   0.60 dup scale
386
387   gsave ${\ veepsspot(chip($pumpkin, 0)) } grestore
388   gsave 0 20 translate ${\ chip($purple, 1) } grestore
389   27 0 translate
390 grestore
391 END
392
393
394   $o .= exposition(<<END);
395 The value of this chip is
396 1 higher than the number of pumpkins
397 previously placed in the pot (but max.4)
398 END
399
400   $o;
401 }
402
403 sub book_purple () {
404   my $o = general_book($purple, [10, 1]);
405
406   my $input = <<END;
407   gsave ${\ veepsspot(chip($pumpkin, 0)) } grestore
408   gsave 0 20 translate ${\ chip($purple, 1) } grestore
409 END
410
411   $o .= <<END;
412 /Times-Bold findfont 15 scalefont setfont $black
413 gsave
414   0.16 0.20  dc translate
415   0.60 dup scale
416
417 $input
418   8 -12 moveto (...) show
419   27 0 translate
420 $input
421 grestore
422
423 gsave
424 /Times-Roman findfont
425   32 scalefont setfont
426   0.48 0.14 dc moveto (}) show
427 grestore
428
429 gsave
430    0.83 0.25 dc translate
431    0.85 dup scale
432    ${\ chip($anychip, 0) }
433 grestore
434
435 gsave
436   0.72 0.22 dc translate
437   0.50 dup scale
438    $black 0 0 moveto (?+?) show
439 grestore
440 END
441
442   $o .= exposition(<<END);
443 For each pumpkin in the pot (but
444  not more than the number of purple chips),
445 add up the VPs of the covered spaces.
446 Buy 1/2 chips of up to that total value.
447 END
448
449   $o;
450 }
451
452 sub book_yellow () {
453   my $o = general_book($yellow, [qw(7 12 19)]);
454
455   $o .= <<END;
456 gsave
457   0.50 0.62 dc translate
458   0.65 dup scale
459   -26 0 translate
460   gsave 26 0 translate ${\ chip($anychip,0) } grestore
461   gsave 46 0 translate ${\ chip($pumpkin,0) } grestore
462   gsave -30 rotate bag_image grestore
463   8 14 translate
464   -120 rotate
465   ${\ arrow_any("
466     gsave
467      arrowlen 0 translate
468   -85 rotate
469     putback_len neg 0
470       putback_len
471       240 0 arc
472     stroke
473     grestore
474   ") }
475 grestore
476 END
477
478   my $exchip = sub {
479     my ($that, $pips) = @_;
480     <<END;
481   gsave ${\ chip($that, $pips) } grestore
482 END
483   };
484   my $exslash = sub {
485     <<END;
486     8 -5 moveto (/) show
487     20 0 translate
488 END
489   };
490   
491   my $exchline = sub {
492     my ($y, $pips, $content) = @_;
493     <<END;
494 gsave 
495   0.20 0.12 0.16 $y mul add dc translate
496   0.60 dup scale
497   gsave ${\ chip($yellow,$pips) } grestore
498   8 -3 moveto (:) show
499   3 0 translate
500   0.8 dup scale
501   24 0 translate
502 $content
503 grestore
504 END
505   };
506
507   $o .= $exchline->(2, 1, <<END);
508   ${\ $exchip->($white,1) }  ${\ $exslash->() }
509   ${\ $exchip->($anychip,1) }  ${\ $exslash->() }
510   ${\ $exchip->($lotus,0) }
511 END
512
513   $o .= $exchline->(1, 2, <<END);
514   ${\ $exchip->($white,2) }  ${\ $exslash->() }
515   ${\ $exchip->($anychip,2) }
516 END
517
518   $o .= $exchline->(0, 4, <<END);
519   ${\ $exchip->($white,3) }  ${\ $exslash->() }
520   ${\ $exchip->($anychip,4) }
521 END
522
523   $o .= exposition(<<END);
524 Put a chip, no bigger than the yellow,
525 whose next placed chip is a pumpkin,
526 back in your bag.                
527
528  
529 END
530
531   $o;
532 }
533
534 sub black_common ($) {
535   my ($second) = @_;
536   my $font = <<END;
537 /Helvetica-Bold findfont 5.5 scalefont setfont
538 END
539   <<END;
540     gsave
541       .45 .27 dc translate
542       droplet_image
543       3.5 -1 translate .7 dup scale
544       arrow
545     grestore
546     gsave
547       .40 .08 dc translate
548       $font
549       ($second) 
550         dup stringwidth .5 mul exch .5 mul exch translate
551         dup stringwidth -1 mul exch -1 mul exch moveto show
552       .7 dup scale 7 3 translate
553       ${\ ruby() }
554     grestore
555     gsave
556       .15 .37 dc translate
557
558       8.2 3.5 moveto
559       $font
560       (closest) show
561
562       .7 dup scale
563       gsave
564         17 0 translate
565         180 rotate
566         arrow
567       grestore
568       gsave
569         35 0 translate
570         arrow
571         newpath 0 0 moveto -18 0 rlineto stroke
572       grestore
573
574       ${\ chip($pumpkin,0) }
575
576       51 0 translate
577       ${\ chip($moth,   1) }
578     grestore
579 END
580 }
581
582 sub book_black_pair () {
583   my $o = general_book($moth, [10, 1]);
584
585   $o .= num_players(2);
586   $o .= black_common('equal:');
587
588   $o .= exposition(<<END);
589 The player with the shortest distance
590 between a black chip and a pumpkin
591 gets to move their droplet.
592
593 If tied, both players get a ruby.
594 END
595
596   $o;
597 }
598
599 sub book_black_more () {
600   my $o = general_book($moth, [10, 1]);
601
602   $o .= num_players(3,'+');
603   $o .= black_common('2nd:');
604
605   $o .= exposition(<<END);
606
607 The player(s) with the shortest distance
608 between a black chip and a pumpkin
609 get to move their droplet.
610 If only one player won a droplet, the
611 player(s) with the next-shortest distance
612 get a ruby.
613 END
614
615   $o;
616 }
617
618 sub arrow_any ($) { <<END;
619   $black 1 setlinewidth
620     newpath
621        $_[0]
622        arrowlen 0 moveto
623        arrowhead dup neg exch  rmoveto
624        arrowhead dup neg       rlineto
625        arrowhead neg dup       rlineto
626        stroke
627 END
628 };
629
630
631 our $ps_framing = <<END;
632 %!
633
634 $page_pre
635
636 /tw 57.5 def
637 /th 73 def
638 /bdiag 5 def
639 /thirdlineh 0.45 def
640 /costcirch 0.3 def
641 /chip 15 def
642 /spot 3.5 def
643 /arrowlen 6 def
644 /arrowhead 3 def
645 /putback_len 10 def
646
647 /costtexth 0.215 def
648 /costtextsz 12 def
649 /costtextdx -0.03 def
650 /costfont /Heletica-BoldOblique findfont costtextsz scalefont def
651
652 /rubysz 4 def
653
654 % diagonal conversion
655 /dc {                    % xprop yprop
656     dup th mul           % xprop yprop y
657     3 1 roll             % y xprop yprop
658     bdiag mul neg        % y xprop x-margin-at-this-height
659     tw add               % y xprop x-width-at-this-height
660     exch mul             % y x-width-at-this-height xprop
661     exch                 % x y
662 } def
663
664 /arrow {
665   ${\ arrow_any("0 0 moveto  arrowlen 0 rlineto") }
666 } def
667
668 END
669
670 sub def_image ($$) {
671   my ($ncomps, $name) = @_;
672
673   my $ext = $ncomps == 1 ? 'pgm' : $ncomps == 3 ? 'ppm' : die;
674   my $oper = $ncomps == 1 ? 'image' :
675              $ncomps == 3 ? 'false 3 colorimage' : die;
676   my $magic = $ncomps == 1 ? 'P2' : $ncomps == 3 ? 'P3' : die;
677
678   open B, "$name.$ext" or die $!;
679   <B> eq "$magic\n" or die "$name.$ext expected $magic";
680   local $/ = undef;
681   my @d = split ' ', <B>;
682   close B;
683
684   my $w = shift @d;
685   my $h = shift @d;
686   my $maxval = shift @d;
687   die unless $maxval eq 255;
688
689   my $sz = $w/17.0;
690   my $hsz = $sz/2;
691
692   $ps_framing .= <<END . '{<';
693 /${name}_image {
694   $w $h 8 
695   [ -$sz 0 0  -$sz  $w 2 div  $h 2 div  ]
696 END
697   for (my $i=0; $i< $w*$h*$ncomps; $i++) {
698     $ps_framing .= sprintf "%02x", shift @d;
699   }
700   $ps_framing .= <<END;
701 >} $oper
702 } def
703 END
704 }
705
706 sub tile {
707   my @tiles;
708   my $index = 1;
709
710   foreach my $spec (@_) {
711     my @datas;
712     foreach my $stem (split m{/}, $spec) {
713       my $func = $stem;
714       $func =~ y/-/_/;
715       $func = ${*::}{"book_$func"} // die "$func ?";
716       my $data = $func->();
717       $data .= <<END;
718     /Courier-Bold findfont 6 scalefont setfont
719     -0.98 0.94 dc moveto
720     ($index) show
721 END
722       open F, ">book-$stem.ps" or die $!;
723       print F $ps_framing, $data, "\nshowpage\n" or die $!;
724       close F or die $!;
725       push @datas, $data;
726     }
727     foreach my $dd (@datas) {
728       push @tiles, $dd;
729     }
730     $index++;
731   }
732
733   my $pos = 0;
734   my $o = '';
735   my $showpage = sub {
736     $o .= <<END;
737 showpage
738 $page_pre
739 END
740   };
741
742   while (my $d = pop @tiles) {
743     if ($pos >= 5) {
744       $pos -= 5;
745       $showpage->();
746     }
747     $o .= <<END;
748 gsave
749 END
750     if ($pos < 3) {
751       $o .= <<END
752         0   th 10 add  $pos mul  translate
753 END
754     } else {
755       $o .= <<END
756 tw 2 mul 7 add
757    dup    th add  0 translate
758    90 rotate
759    $pos 3 sub mul  0 translate
760 END
761     }
762     $o .= "\n".$d."\n";
763     $o .= <<END;
764 grestore
765 END
766     $pos++;
767   }
768   $showpage->();
769   $o;
770 }
771
772 def_image(1, "bag");
773 def_image(1, "pot");
774 def_image(3, "droplet");
775
776 print $ps_framing or die $!;
777
778 print "gsave\n" or die $!;
779
780 print tile(@ARGV) or die $! if 1;
781