chiark / gitweb /
WIP island determination; rename rawimage etc. to rgbimage
[ypp-sc-tools.main.git] / ypp-restock-rum
index 7e10a46e2deb82e12ad87f5aca13be316cdcea51..1b0a11f48cfaee7ad58c46def2192031cebcbf2e 100755 (executable)
 #!/usr/bin/perl -w
-use strict qw (vars);
+# This is part of ypp-sc-tools, a set of third-party tools for assisting
+# players of Yohoho Puzzle Pirates.
+#
+# Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Yohoho and Puzzle Pirates are probably trademarks of Three Rings and
+# are used without permission.  This program is not endorsed or
+# sponsored by Three Rings.
+
+use strict qw(vars);
 use IO::Handle;
 use POSIX;
 
-our ($ship, %have, %price);
-
 my $usage = <<END
 usage:
-  .../ypp-restock-rum [<prices>] [<ship> <stocks>]
-where
- <prices> is three arguments: the prices for swill, grog and fine rum
- <ship> is a ship name or an amount of fine rum
- <stocks> are amounts on board for swill, grog and fine rum
+  .../ypp-restock-rum [<information> ...]
+
+where <information> is
+
+  have  [<swill> <grog>] <fine> [<hold_shot>+<loaded_shot>]
+  want  [<swill> <grog>] <fine> [<shot>]
+  price  <swill> <grog>  <fine> [<shot>]
+
+Each of which may appear only once, except \`have' which may appear
+more than once which case we calculate the financial implications
+at each stage.
+
+Missing entries count as zero (and zeroes are not displayed in the output).
+In price, \`x' means the commodity is unavailable.
+
+The intended use is that at the start of the pillage you run:
+   ypp-restock-rum have ... want ...
+to check that stocks are sufficient, or perhaps:
+   ypp-restock-rum have ... want ... price ...
+if you want to stock up some more.  At the end of the pillage,
+run the same thing again with some extra parameters:
+   ypp-restock-rum have .. want ... have ... price ...
+and it will tell you how you did and what the restock should be like.
 END
 ;
 
-sub main () {
-    if (@ARGV==4) {
-       ($ship, $have{Swill}, $have{Grog}, $have{Fine}) = @ARGV;
-       print_inputs();
-       compute_restock_requirements();
-    } elsif (@ARGV==3) {
-       ($price{Swill}, $price{Grog}, $price{Fine}) = @ARGV;
-       print_inputs();
-       compute_cheapest_rum();
-    } elsif (@ARGV==7) {
-       ($price{Swill}, $price{Grog}, $price{Fine},
-        $ship,
-        $have{Swill}, $have{Grog}, $have{Fine}) = @ARGV;
-       print_inputs();
-       compute_cheapest_rum();
-       compute_restock_requirements();
-       compute_restock_cheapest_rum();
-    } else {
-       die $usage;
+our (@kinds) = qw(Swill Grog Fine Shot);
+our (@proofs) = qw(40 60 100);
+
+sub parse_info ($$$\@$) {
+    my ($omitswgok,$default,$multishot, $storeary, $what) = @_;
+    @ARGV or badusage("missing value for information argument \`$_'");
+    badusage("$what: specified more than once")
+       if defined $storeary->[2];
+    my (@v) = ();
+    while (@ARGV and $ARGV[0] =~ m/^\d|^x$/) {
+       $_ = shift @ARGV;
+       push @v, $_;
     }
+    if (@v==1 or @v==2) {
+       badusage("$what: swill and grog amounts must be specified")
+           unless $omitswgok;
+       @v=($default,$default,@v);
+    }
+    if ($multishot and @v==4 and length $v[3]) {
+       $v[3] =~ m/^0*(\d+)\+0*(\d+)$/ or
+           badusage("$what: shot must be specified as <hold>+<loaded>");
+       $v[3] = $1 + $2;
+    }
+    if (@v==3) {
+       push @v, $default;
+    }
+    if (@v != 4) {
+       badusage("$what: invalid syntax (wrong number of parameters)");
+    }
+    my $i=0;
+    foreach $_ (@v) {
+       if ($default>0 and m/^x$/) {
+           $_ = $default;
+       } elsif (m/^0*(\d+)$/) {
+           $_= $1;
+       } else {
+           badusage("$what: $kinds[$i] \`$_': bad syntax");
+       }
+       $i++;
+    }
+    @$storeary = @v;
 }
 
-our @rums= qw(Swill Grog Fine);
-our %proof= qw(Swill  40
-              Grog   60
-              Fine  100);
+our (@have,@want,@price);
+
+sub parse_args () {
+    if (!@ARGV) {
+       print STDERR <<END or die $!;
+
+ypp-restock-rum is part of ypp-sc-tools  Copyright (C) 2009 Ian Jackson
+This program comes with ABSOLUTELY NO WARRANTY; this is free software,
+and you are welcome to redistribute it under certain conditions.
+For details, read the top of the ypp-restock-rum file.
+END
+        badusage("need some information to go on");
+    }
+    while (@ARGV) {
+       $_ = shift @ARGV;
+       if (m/^have$/) {
+           parse_info(1,0,1, @{ $have[@have] }, 'have');
+       } elsif (m/^want$/) {
+           parse_info(1,0,0, @want, 'want');
+       } elsif (m/^prices?$|^costs?$/) {
+           parse_info(0,1e7,0, @price, 'price');
+       } else {
+           badusage("unknown information argument \`$_'");
+       }
+    }
+}
+
+sub badusage ($) {
+    my ($m) = @_;
+    print STDERR "\nbad usage: $m\n\n$usage\n";
+    exit 16;
+}
 
 our $ff = '%6.1f';
 
-sub prvff ($$\%$) {
+sub valid ($) {
+    my ($x) = @_;
+    defined $x and $x and $x<1e5;
+}    
+
+sub prvff ($$\@$) {
     my ($what, $format, $ary, $unit) = @_;
-    printf("%-40s", "$what:");
-    map {
-       my $x= $ary->{$_};
-       my $y= defined $x ? sprintf $format, $x : '   x';
-       printf " %-10s", $y;
-    } @rums;
+    printf("%-25s", "$what:");
+    for my $i (qw(0 1 2 3)) {
+       my $x= $ary->[$i];
+       my $y= valid($x) ? sprintf $format, $x : '    ';
+       printf " %-9s", $y;
+    }
     printf "  %s\n", $unit;
 }
 
-sub pr ($\%$) {
+sub pr ($\@$) {
     my ($what, $ary, $unit) = @_;
-    prvff($what, '%4d  ', %$ary, $unit);
+    prvff($what, '%4d  ', @$ary, $unit);
 }
 
-sub prf ($\%$) {
+sub prf ($\@$) {
     my ($what, $ary, $unit) = @_;
-    prvff($what, $ff, %$ary, $unit);
+    prvff($what, $ff, @$ary, $unit);
 }
 
 sub pr1 ($$) {
     my ($k,$v) = @_;
     printf "%-20s %s\n", "$k:", $v;
 }
+sub fmt_stock_index ($) {
+    my ($si) = @_;
+    @have==1 ? '' : ' #'.$si;
+}
 
-our ($best_kind);
+sub rum_total (\@) {
+    my ($rums) = @_;
+    my $total= 0;
+    foreach my $i (qw(0 1 2)) {
+       $total += $rums->[$i] * $proofs[$i] / 100;
+    }
+    return $total;
+}
+
+our @norm_price;
+our ($best, $best_norm_price);
+
+sub print_inputs () {
+    printf("%25s",'');
+    map { printf " %5s    ", $_ } @kinds;
+    print "\n\n";
+    pr('prices', @price, 'poe ea.') if valid(@price);
+    pr('target stocks', @want, 'units') if valid(@want);
+    my $si=0; for my $stocks (@have) {
+       pr('actual stocks'.fmt_stock_index(++$si),
+          @$stocks, 'units');
+    }
+    print "\n";
+}
 
 sub compute_cheapest_rum() {
-    $best_kind= undef;
-    my %norm_price;
-    my $best_norm_price= 1e6;
-    foreach $_ (@rums) {
-       next unless defined $price{$_};
-       $norm_price{$_} = $price{$_} * $proof{Fine} / $proof{$_};
-       if ($norm_price{$_} <= $best_norm_price) {
-           $best_kind= $_;
-           $best_norm_price= $norm_price{$_};
+    return unless @price;
+
+    my (@perorder) = map { $_*10 } @price;
+    prf('equiv. ordering price', @perorder, 'poe/order');
+
+    $best= undef;
+    $best_norm_price= 1e6;
+    for my $i (qw(0 1 2)) {
+       next unless $price[$i];
+       $norm_price[$i] = $price[$i] * 100 / $proofs[$i];
+       if ($norm_price[$i] <= $best_norm_price) {
+           $best= $i;
+           $best_norm_price= $norm_price[$i];
        }
     };
-    prf('normalised prices', %norm_price, 'poe/fine');
-    if (defined $best_kind) {
-       printf "best is %s\n\n", $best_kind;
+    prf('normalised prices', @norm_price, 'poe/fine');
+
+    if (defined $best) {
+       printf "best is %-10s%*s^^\n",
+           $kinds[$best],
+           $best*10+10, '';
+       my (@bestperorder) = map {
+           $best_norm_price * $proofs[$_] / 100 * 10;
+       } qw(0 1 2);
+       #push @bestperorder, $perorder[3];
+       prf('best is equiv. ordering', @bestperorder, 'poe/order');
+    }
+    print "\n";
+}
+
+sub pr1s ($) {
+    my ($x) = @_;
+    if (valid($x)) {
+       printf ' %9.1f', $x;
     } else {
-       die "no rum available ?\n";
+       printf "          ";
     }
 }
 
-our $have_proof;
+sub compute_stock_values() {
+    return unless @have;
+    print @have>1 ? <<END
 
-sub print_inputs () {
-    printf("%40s",'');
-    map { printf " %5s     ", $_ } @rums;
-    print "\n\n";
-    if (defined $price{Swill}) {
-       map { $price{$_}= undef if $price{$_} eq 'x' } @rums;
-       pr('prices', %price, 'poe ea.');
-    }
-    if (defined $have{Swill}) {
-       $have_proof= 0;
-       map { $have_proof += $have{$_} * $proof{$_} } @rums;
-       pr('stock on board', %have, 'rum');
+                _____Rum_____      ___Shot___      total      _____Profit___
+             fine equiv.  value   qty    value     value     per leg     total
+END
+: <<END
+                             Rum      Rum             Shot    Shot     total
+                            equiv.   value           stocks   value    value
+END
+;
+
+    my $initial_value;
+    my $last_value;
+    my $si=0; for my $stocks (@have) {
+       my $stock_rum = rum_total(@$stocks);
+       my $rum_value= defined($best) ? $stock_rum * $best_norm_price : 0;
+       my $shot_value= valid($price[3]) ? $stocks->[3] * $price[3] : 0;
+       my $total_value= $rum_value + $shot_value;
+
+       printf "%-10s ", 'stocks'.fmt_stock_index(++$si).':';
+       print '            ' if @have==1;
+       pr1s($stock_rum);
+       pr1s($rum_value);
+       print '        ' if @have==1;
+       printf "%6s", $stocks->[3] ? $stocks->[3] : '';
+       pr1s($shot_value);
+       pr1s($total_value);
+       
+       if (defined $last_value) {
+           printf(" %10.1f %10.1f",
+                  $total_value - $last_value,
+                  $total_value - $initial_value);
+       }
+       $initial_value= $total_value unless defined $initial_value;
+       $last_value= $total_value;
+       print "\n";
     }
+    print "\n" if @have>1;
+    print "\n";
 }
 
-our ($need_proof, %need, %buy);
+sub pr2 ($$$) {
+    my ($k,$v1,$v2) = @_;
+    printf "%-25s %-23s %s\n", "$k:", $v1, $v2;
+}
+
+sub pr2rs ($$$) {
+    my ($k,$rum,$shot) = @_;
+    pr2($k,
+       valid($rum) ? sprintf("%12.1f fine equiv", $rum) : '',
+       valid($shot) ? sprintf("%10d shot", $shot) : '');
+}
 
 sub compute_restock_requirements () {
-    if ($ship =~ m/^\d+/) {
-       $need{Fine} = $ship;
-    } else {
-       $ship =~ y/_/ /;
-       open F, "/home/ian/private/puzzle-pirates" or die $!;
-       my $this_ship= 0;
-       my $the_ship;
-       while (<F>) {
-           if (!m/\S/ || m/^\s*\#/) {
-               $this_ship= 0;
-               next;
-           }
-           if (!m/^\@/) {
-               next;
-           }
-           if (m/^\@(( [A-Z][-a-z]+){2,})\s*$/) {
-               $this_ship= (uc $1 eq uc " $ship" or
-                            uc $+ eq uc " $ship");
-               $the_ship= $1;
-               next;
+    return unless @want;
+    
+    my $rum_want= rum_total(@want);
+
+    my $stocks= @have ? $have[-1] : [qw(0 0 0 0)];
+    my $rum_have= rum_total(@$stocks);
+    
+    pr2rs('desired stock level', $rum_want, $want[3]);
+
+    my $rum_need = $rum_want - $rum_have;
+    my $shot_need = $want[3] - $stocks->[3];
+
+    if (@have) {
+       pr2rs('current stock', $rum_have, $stocks->[3]);
+       pr2rs('restock requirement', $rum_need, $shot_need);
+       print "\n";
+    }
+
+    if (@price) {
+       my ($rum_buy,$shot_buy) = ('','');
+       my ($rum_bill,$shot_bill) = qw(0 0);
+       my $ok=1;
+       if ($rum_need > 0) {
+           if (defined $best) {
+               my $rum_qty= $rum_need * 100 / $proofs[$best];
+               $rum_qty= ceil($rum_qty);
+               $rum_buy= sprintf('%12s %-11s  ',
+                                 "** $rum_qty","$kinds[$best] **");
+               $rum_bill= $rum_qty * $price[$best];
+           } else {
+               $rum_buy= '   (rum unavailable)';
+               $ok= 0;
            }
-           next unless $this_ship;
-           if (m/^\@\s+(\d+)\s+fine\s*/) {
-               $need{Fine} = $1;
-               last;
+       }
+       if ($shot_need > 0) {
+           if (valid($price[3])) {
+               $shot_buy= sprintf('%7s shot **', "** $shot_need");
+               $shot_bill= $shot_need * $price[3];
+           } else {
+               $shot_buy= '  (shot unavailable)';
+               $ok= 0;
            }
        }
-       die $! if F->error;
-       die "unknown ship $ship" unless defined $need{Fine};
-       if (defined $ship) {
-           pr1("vessel",$the_ship);
+       if (length($rum_buy) or length($shot_buy)) {
+           pr2($ok
+               ? sprintf('for a total of %d poe', $rum_bill + $shot_bill)
+               : 'for',
+               $rum_bill ? sprintf("%12d poe       ", $rum_bill) : '',
+               $shot_bill ? sprintf("%10d poe", $shot_bill) : '');
+           pr2('BUY', $rum_buy, $shot_buy);
+       } else {
+           print "stocks are sufficient.\n";
        }
+       print "\n";
     }
-    pr1('desired stock level', sprintf("%4d fine rum", $need{Fine}));
-    $need_proof= $need{Fine} * $proof{Fine} - $have_proof;
-    map {
-       $buy{$_} = $need_proof / $proof{$_};
-    } @rums;
-    pr1("stock equivalent", sprintf "$ff fine rum", $have_proof / $proof{Fine});
-    pr1("restock equivalent", sprintf "$ff fine rum", $need_proof / $proof{Fine});
-    prf('would need', %buy, 'rum');
-}
-
-sub compute_restock_cheapest_rum() {
-    my %bill;
-    map {
-       $bill{$_} = $buy{$_} * $price{$_} if defined $price{$_};
-    } @rums;
-    prf('nominal bill', %bill, 'poe');
-    print "\n";
-    if ($need_proof < 0) {
-       printf "stocks are sufficient";
-    } else {
-       my $buy= ceil($buy{$best_kind});
-       printf "buy %d %s at %d poe each for %d poe",
-           $buy, $best_kind, $price{$best_kind}, $buy * $price{$best_kind};
-    }
-    print "\n\n";
 }
 
-main();
+parse_args();
+print_inputs();
+compute_cheapest_rum();
+compute_stock_values();
+compute_restock_requirements();
+
+STDOUT->error and die $!;
+close STDOUT or die $!;