#!/usr/bin/perl -w
+# 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;
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 differences and the profit
-for each one.
+more than once which case we calculate the financial implications
+at each stage.
-If /<shot> is not specified at all, relevant information about shot is
-not reported. For have and want, specifying an empty string means
-zero. Entirely Missing <swill>/<grog>/ is taken as if both were
-specified and zero.
+Missing entries count as zero (and zeroes are not displayed in the output).
+In price, \`x' means the commodity is unavailable.
-In price, missing entries mean 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
;
-our (@kinds) = qw(swill grog fine shot);
+our (@kinds) = qw(Swill Grog Fine Shot);
our (@proofs) = qw(40 60 100);
sub parse_info ($$$\@$) {
badusage("$what: specified more than once")
if defined $storeary->[2];
my (@v) = ();
- while (@ARGV and $ARGV[0] =~ m/^\d/) {
+ while (@ARGV and $ARGV[0] =~ m/^\d|^x$/) {
$_ = shift @ARGV;
push @v, $_;
}
push @v, $default;
}
if (@v != 4) {
- badusage("$what: invalid syntax (wrong number of /s)");
+ badusage("$what: invalid syntax (wrong number of parameters)");
}
my $i=0;
foreach $_ (@v) {
- $_ = $default if !length;
- m/^0*(\d+)$/ or badusage("$what: $kinds[$i] \`$_': bad syntax");
- $_= $1;
+ if ($default>0 and m/^x$/) {
+ $_ = $default;
+ } elsif (m/^0*(\d+)$/) {
+ $_= $1;
+ } else {
+ badusage("$what: $kinds[$i] \`$_': bad syntax");
+ }
$i++;
}
@$storeary = @v;
}
-our (@haves,@want,@price);
+our (@have,@want,@price);
sub parse_args () {
- @ARGV or badusage("need some information to go on");
+ 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, @{ $haves[@haves] }, 'BUG');
+ parse_info(1,0,1, @{ $have[@have] }, 'have');
} elsif (m/^want$/) {
parse_info(1,0,0, @want, 'want');
- } elsif (m/^price$/) {
- parse_info(0,1e6,0, @price, 'price');
+ } elsif (m/^prices?$|^costs?$/) {
+ parse_info(0,1e7,0, @price, 'price');
} else {
badusage("unknown information argument \`$_'");
}
sub badusage ($) {
my ($m) = @_;
- print STDERR "bad usage: $m\n\n$usage\n";
+ print STDERR "\nbad usage: $m\n\n$usage\n";
exit 16;
}
our $ff = '%6.1f';
+sub valid ($) {
+ my ($x) = @_;
+ defined $x and $x and $x<1e5;
+}
+
sub prvff ($$\@$) {
my ($what, $format, $ary, $unit) = @_;
- printf("%-40s", "$what:");
- for my $i (qw(0 1 2)) {
+ printf("%-25s", "$what:");
+ for my $i (qw(0 1 2 3)) {
my $x= $ary->[$i];
- my $y= $x>0 && $x<1e5 ? sprintf $format, $x : ' x';
- printf " %-10s", $y;
+ my $y= valid($x) ? sprintf $format, $x : ' ';
+ printf " %-9s", $y;
}
printf " %s\n", $unit;
}
my ($k,$v) = @_;
printf "%-20s %s\n", "$k:", $v;
}
+sub fmt_stock_index ($) {
+ my ($si) = @_;
+ @have==1 ? '' : ' #'.$si;
+}
+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;
+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() {
return unless @price;
+ my (@perorder) = map { $_*10 } @price;
+ prf('equiv. ordering price', @perorder, 'poe/order');
+
$best= undef;
- my $best_norm_price= 1e5;
+ $best_norm_price= 1e6;
for my $i (qw(0 1 2)) {
next unless $price[$i];
- $norm_price[$i] = $price[$i] * $proofs[$i] / $proofs[$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) {
- printf "best is %s\n\n", $kinds[$best];
- } else {
- die "no rum available ?\n";
+ 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";
}
-parse_args();
-use Data::Dumper;
-print Dumper({ Have => \@haves, Want => \@want, Price => \@price });
-compute_cheapest_rum();
+sub pr1s ($) {
+ my ($x) = @_;
+ if (valid($x)) {
+ printf ' %9.1f', $x;
+ } else {
+ printf " ";
+ }
+}
-__DATA__
+sub compute_stock_values() {
+ return unless @have;
+ print @have>1 ? <<END
-our ($best);
+ _____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
+;
-our $have_proof;
+ 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;
-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');
+ 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});
- printf "buy %d %s at %d poe each for %d poe",
- $buy, $best, $price{$best}, $buy * $price{$best};
- }
- 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 $!;