chiark / gitweb /
ce76aa794d8eeb11189b0c5d4a8a8e05df829e6f
[ypp-sc-tools.main.git] / ypp-restock-rum
1 #!/usr/bin/perl -w
2 use strict qw(vars);
3 use IO::Handle;
4 use POSIX;
5
6 my $usage = <<END
7 usage:
8   .../ypp-restock-rum [<information> ...]
9
10 where <information> is
11
12   have  [<swill> <grog>] <fine> [<hold_shot>+<loaded_shot>]
13   want  [<swill> <grog>] <fine> [<shot>]
14   price  <swill> <grog>  <fine> [<shot>]
15
16 Each of which may appear only once, except \`have' which may appear
17 more than once which case we calculate the differences and the profit
18 for each one.
19
20 If /<shot> is not specified at all, relevant information about shot is
21 not reported.  For have and want, specifying an empty string means
22 zero.  Entirely Missing <swill>/<grog>/ is taken as if both were
23 specified and zero.
24
25 In price, missing entries mean the commodity is unavailable.
26 END
27 ;
28
29 our (@kinds) = qw(Swill Grog Fine Shot);
30 our (@proofs) = qw(40 60 100);
31
32 sub parse_info ($$$\@$) {
33     my ($omitswgok,$default,$multishot, $storeary, $what) = @_;
34     @ARGV or badusage("missing value for information argument \`$_'");
35     badusage("$what: specified more than once")
36         if defined $storeary->[2];
37     my (@v) = ();
38     while (@ARGV and $ARGV[0] =~ m/^\d/) {
39         $_ = shift @ARGV;
40         push @v, $_;
41     }
42     if (@v==1 or @v==2) {
43         badusage("$what: swill and grog amounts must be specified")
44             unless $omitswgok;
45         @v=($default,$default,@v);
46     }
47     if ($multishot and @v==4 and length $v[3]) {
48         $v[3] =~ m/^0*(\d+)\+0*(\d+)$/ or
49             badusage("$what: shot must be specified as <hold>+<loaded>");
50         $v[3] = $1 + $2;
51     }
52     if (@v==3) {
53         push @v, $default;
54     }
55     if (@v != 4) {
56         badusage("$what: invalid syntax (wrong number of /s)");
57     }
58     my $i=0;
59     foreach $_ (@v) {
60         $_ = $default if !length;
61         m/^0*(\d+)$/ or badusage("$what: $kinds[$i] \`$_': bad syntax");
62         $_= $1;
63         $i++;
64     }
65     @$storeary = @v;
66 }
67
68 our (@have,@want,@price);
69
70 sub parse_args () {
71     @ARGV or badusage("need some information to go on");
72     while (@ARGV) {
73         $_ = shift @ARGV;
74         if (m/^have$/) {
75             parse_info(1,0,1, @{ $have[@have] }, 'have');
76         } elsif (m/^want$/) {
77             parse_info(1,0,0, @want, 'want');
78         } elsif (m/^price$/) {
79             parse_info(0,1e7,0, @price, 'price');
80         } else {
81             badusage("unknown information argument \`$_'");
82         }
83     }
84 }
85
86 sub badusage ($) {
87     my ($m) = @_;
88     print STDERR "\nbad usage: $m\n\n$usage\n";
89     exit 16;
90 }
91
92 our $ff = '%6.1f';
93
94 sub valid ($) {
95     my ($x) = @_;
96     defined $x and $x and $x<1e5;
97 }    
98
99 sub prvff ($$\@$) {
100     my ($what, $format, $ary, $unit) = @_;
101     printf("%-25s", "$what:");
102     for my $i (qw(0 1 2 3)) {
103         my $x= $ary->[$i];
104         my $y= valid($x) ? sprintf $format, $x : '    ';
105         printf " %-9s", $y;
106     }
107     printf "  %s\n", $unit;
108 }
109
110 sub pr ($\@$) {
111     my ($what, $ary, $unit) = @_;
112     prvff($what, '%4d  ', @$ary, $unit);
113 }
114
115 sub prf ($\@$) {
116     my ($what, $ary, $unit) = @_;
117     prvff($what, $ff, @$ary, $unit);
118 }
119
120 sub pr1 ($$) {
121     my ($k,$v) = @_;
122     printf "%-20s %s\n", "$k:", $v;
123 }
124 sub fmt_stock_index ($) {
125     my ($si) = @_;
126     @have==1 ? '' : ' #'.$si;
127 }
128
129 sub rum_total (\@) {
130     my ($rums) = @_;
131     my $total= 0;
132     foreach my $i (qw(0 1 2)) {
133         $total += $rums->[$i] * $proofs[$i] / 100;
134     }
135     return $total;
136 }
137
138 our @norm_price;
139 our ($best, $best_norm_price);
140
141 sub print_inputs () {
142     printf("%25s",'');
143     map { printf " %5s    ", $_ } @kinds;
144     print "\n\n";
145     pr('prices', @price, 'poe ea.') if valid(@price);
146     pr('target stocks', @want, 'units') if valid(@want);
147     my $si=0; for my $stocks (@have) {
148         pr('actual stocks'.fmt_stock_index(++$si),
149            @$stocks, 'units');
150     }
151     print "\n";
152 }
153
154 sub compute_cheapest_rum() {
155     return unless @price;
156
157     my (@perorder) = map { $_*10 } @price;
158     prf('equiv. ordering price', @perorder, 'poe/order');
159
160     $best= undef;
161     $best_norm_price= 1e6;
162     for my $i (qw(0 1 2)) {
163         next unless $price[$i];
164         $norm_price[$i] = $price[$i] * 100 / $proofs[$i];
165         if ($norm_price[$i] <= $best_norm_price) {
166             $best= $i;
167             $best_norm_price= $norm_price[$i];
168         }
169     };
170     prf('normalised prices', @norm_price, 'poe/fine');
171
172     if (defined $best) {
173         printf "best is %-10s%*s^^\n",
174             $kinds[$best],
175             $best*10+10, '';
176         my (@bestperorder) = map {
177             $best_norm_price * $proofs[$_] / 100 * 10;
178         } qw(0 1 2);
179         #push @bestperorder, $perorder[3];
180         prf('best is equiv. ordering', @bestperorder, 'poe/order');
181     }
182     print "\n";
183 }
184
185 sub pr1s ($) {
186     my ($x) = @_;
187     if (valid($x)) {
188         printf ' %9.1f', $x;
189     } else {
190         printf "          ";
191     }
192 }
193
194 sub compute_stock_values() {
195     return unless @have;
196     print <<END
197                  Rum      Rum     Shot    Shot     total      Profit    Profit
198                 equiv.   value   stocks   value    value      this leg   total
199 END
200 ;
201
202     my $initial_value;
203     my $last_value;
204     my $si=0; for my $stocks (@have) {
205         my $stock_rum = rum_total(@$stocks);
206         my $rum_value= defined($best) ? $stock_rum * $best_norm_price : 0;
207         my $shot_value= valid($price[3]) ? $stocks->[3] * $price[3] : 0;
208         my $total_value= $rum_value + $shot_value;
209
210         printf "%-10s ", 'stocks'.fmt_stock_index(++$si).':';
211         pr1s($stock_rum);
212         pr1s($rum_value);
213         printf "%6d", $stocks->[3];
214         pr1s($shot_value);
215         pr1s($total_value);
216         
217         if (defined $last_value) {
218             printf(" %10.1f %10.1f",
219                    $total_value - $last_value,
220                    $total_value - $initial_value);
221         }
222         $initial_value= $total_value unless defined $initial_value;
223         $last_value= $total_value;
224         print "\n";
225     }
226     print <<END
227                  fine      poe    units    poe       poe     delta-poe     poe
228
229 END
230 ;
231 }
232
233 sub pr2 ($$$) {
234     my ($k,$v1,$v2) = @_;
235     printf "%-20s %-23s %s\n", "$k:", $v1, $v2;
236 }
237
238 sub pr3 ($$$$) {
239     my ($k,$v1,$v2,$v3) = @_;
240     printf "%-20s %-23s %-20s %s\n", "$k:", $v1, $v2, $v3;
241 }
242
243 sub pr2rs ($$$) {
244     my ($k,$rum,$shot) = @_;
245     pr2($k,
246         valid($rum) ? sprintf("%12.1f fine equiv", $rum) : '',
247         valid($shot) ? sprintf("%10d shot", $shot) : '');
248 }
249
250 sub compute_restock_requirements () {
251     return unless @want;
252     
253     my $rum_want= rum_total(@want);
254
255     my $stocks= @have ? $have[-1] : [qw(0 0 0 0)];
256     my $rum_have= rum_total(@$stocks);
257     
258     pr2rs('desired stock level', $rum_want, $want[3]);
259
260     my $rum_need = $rum_want - $rum_have;
261     my $shot_need = $want[3] - $stocks->[3];
262
263     if (@have) {
264         pr2rs('current stock', $rum_have, $stocks->[3]);
265         pr2rs('restock requirement', $rum_need, $shot_need);
266         print "\n";
267     }
268
269     if (@price) {
270         my ($rum_buy,$shot_buy) = ('','');
271         my ($rum_bill,$shot_bill) = qw(0 0);
272         if (defined $best and $rum_need > 0) {
273             my $rum_qty= $rum_need * 100 / $proofs[$best];
274             $rum_qty= ceil($rum_qty);
275             $rum_buy= sprintf('%12s %-11s  ',"** $rum_qty","$kinds[$best] **");
276             $rum_bill= $rum_qty * $price[$best];
277         }
278         if ($shot_need > 0 and valid($price[3])) {
279             $shot_buy= sprintf('%7s shot **', "** $shot_need");
280             $shot_bill= $shot_need * $price[3];
281         }
282         if (length($rum_buy) or length($shot_buy)) {
283             pr3('for',
284                 $rum_bill ? sprintf("%12d poe       ", $rum_bill) : '',
285                 $shot_bill ? sprintf("%10d poe", $shot_bill) : '',
286                 sprintf("%7d total", $rum_bill + $shot_bill));
287             pr2('BUY', $rum_buy, $shot_buy);
288         } else {
289             print "stocks are sufficient.\n";
290         }
291         print "\n";
292     }
293 }
294
295 parse_args();
296 print_inputs();
297 compute_cheapest_rum();
298 compute_stock_values();
299 compute_restock_requirements();
300
301 __DATA__
302
303 #    if (defined $price{Swill}) {
304 #       map { $price{$_}= undef if $price{$_} eq 'x' } @rums;
305 #    }
306 #    if (defined $have{Swill}) {
307 #       $have_proof= 0;
308 #       map { $have_proof += $have{$_} * $proof{$_} } @rums;
309 #    }
310
311 our ($best);
312
313 our $have_proof;
314
315 our ($need_proof, %need, %buy);
316             
317
318 sub compute_restock_cheapest_rum() {
319     my %bill;
320     map {
321         $bill{$_} = $buy{$_} * $price{$_} if defined $price{$_};
322     } @rums;
323     prf('nominal bill', %bill, 'poe');
324     print "\n";
325     if ($need_proof < 0) {
326         printf "stocks are sufficient";
327     } else {
328         my $buy= ceil($buy{$best});
329         printf "buy %d %s at %d poe each for %d poe",
330             $buy, $best, $price{$best}, $buy * $price{$best};
331     }
332     print "\n\n";
333 }
334
335 main();