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