chiark / gitweb /
Merge branch 'stable-3.x'
authorIan Jackson <ian@liberator.relativity.greenend.org.uk>
Thu, 24 Sep 2009 18:11:13 +0000 (19:11 +0100)
committerIan Jackson <ian@liberator.relativity.greenend.org.uk>
Thu, 24 Sep 2009 18:11:13 +0000 (19:11 +0100)
28 files changed:
yarrg/Commods.pm
yarrg/CommodsWeb.pm
yarrg/README
yarrg/TODO
yarrg/db-idempotent-populate
yarrg/ocean-topology-graph
yarrg/source-info.txt
yarrg/update-master-info
yarrg/web/check_capacitystring
yarrg/web/check_capitalstring [new file with mode: 0644]
yarrg/web/check_lossperleague
yarrg/web/copyrightdate
yarrg/web/devel
yarrg/web/docs
yarrg/web/dumptable
yarrg/web/enter_commod [new file with mode: 0644]
yarrg/web/enter_route [new file with mode: 0644]
yarrg/web/footer
yarrg/web/intro
yarrg/web/lookup
yarrg/web/qtextstring
yarrg/web/qtextstringcheck
yarrg/web/query_age
yarrg/web/query_commod
yarrg/web/query_offers [new file with mode: 0644]
yarrg/web/query_route
yarrg/web/routetrade
yarrg/web/tabsort

index 59ad3e1384cf9dcab67d350a775a617ce9613ba6..372fe16f51544081e9378d68563a91a5b38868df 100644 (file)
@@ -36,7 +36,8 @@ BEGIN {
     @ISA         = qw(Exporter);
     @EXPORT      = qw(&parse_info_clientside &fetch_with_rsync
                      &parse_info_serverside &parse_info_serverside_ocean
-                     %oceans %commods %clients %routes %route_mysteries
+                     %oceans %commods %clients
+                     %vessels %shotname2damage
                      &parse_pctb_commodmap %pctb_commodmap @pctb_commodmap
                      &get_our_version &check_tsv_line
                      &pipethrough_prep &pipethrough_run
@@ -54,8 +55,10 @@ our $masterinfoversion= 2; # version we understand
 
 our %oceans; # eg $oceans{'Midnight'}{'Ruby'}{'Eta Island'}= $sources;
 our %clients; # eg $clients{'ypp-sc-tools'}= [ qw(last-page) ];
-our %routes; # eg $routes{'Midnight'}{'Orca'}{'Tinga'}= $sources  NB abbrevs!
-our %route_mysteries; # eg $route_mysteries{'Midnight'}{'Norse'}= 3
+our %vessels; # eg $vessels{'War Brig'}{Shot}='medium'
+              #    $vessels{'War Brig'}{Volume}= 81000
+              #    $vessels{'War Brig'}{Mass}= 54000
+our %shotname2damage; # eg $shotname2damage{'medium'}= 3;
 # $sources = 's[l]b';
 #       's' = Special Circumstances; 'l' = local ; B = with Bleach
 
@@ -104,11 +107,24 @@ sub parse_info1 ($$$) {
                    $oceans{$ocean}{$arch}{$_} .= $src;
                };
            });
-       } elsif (m/^routes (\w+)$/) {
-           my $ocean= $1;
+       } elsif (m/^vessels$/) {
+           @ctx= (sub {
+               return if m/^[-+|]+$/;
+               m/^ \| \s* ([A-Z][a-z\ ]+[a-z]) \s*
+                   \| \s* (small|medium|large) \s*
+                   \| \s* ([1-9][0-9,]+) \s*
+                   \| \s* ([1-9][0-9,]+) \s*
+                   \| $/x
+                   or die;
+               my $name= $1;
+               my $v= { Shot => $2, Volume => $3, Mass => $4 };
+               foreach my $vm (qw(Volume Mass)) { $v->{$vm} =~ s/,//g; }
+               $vessels{$name}= $v;
+           });
+       } elsif (m/^shot$/) {
            @ctx= (sub {
-               m/^(\S[^\t]*\S),\s*(\S[^\t]*\S),\s*([1-9][0-9]{0,2})$/ or die;
-               $routes{$ocean}{$1}{$2}= $3;
+               m/^ ([a-z]+) \s+ (\d+) $/x or die;
+               $shotname2damage{$1}= $2;
            });
        } elsif (m/^client (\S+.*\S)$/) {
            my $client= $1;
@@ -163,22 +179,6 @@ sub parse_info1 ($$$) {
        }
     };
     foreach (@rawcm) { &$ca($_,$src); }
-
-    foreach my $on (keys %routes) {
-       my $routes= $routes{$on};
-       my $ocean= $oceans{$on};
-       die unless defined $ocean;
-       
-       my @allislands;
-       foreach my $an (sort keys %$ocean) {
-           my $arch= $ocean->{$an};
-           push @allislands, sort keys %$arch;
-       }
-       parse_info_maproutes($on, \@allislands, $routes);
-       foreach my $route (values %$routes) {
-           parse_info_maproutes($on, \@allislands, $route);
-       }
-    }
 }
 
 sub parse_info_clientside () {
@@ -203,21 +203,6 @@ sub fetch_with_rsync ($) {
     return $local;
 }
 
-sub parse_info_maproutes ($$$) {
-    my ($on, $allislands, $routemap) = @_;;
-    foreach my $k (sort keys %$routemap) {
-       my @ok= grep { index($_,$k) >= 0 } @$allislands;
-       die "ambiguous $k" if @ok>1;
-       if (!@ok) {
-           $route_mysteries{$on}{$k}++;
-           delete $routemap->{$k};
-       } elsif ($ok[0] ne $k) {
-           $routemap->{$ok[0]}= $routemap->{$k};
-           delete $routemap->{$k};
-       }
-    }
-}
-
 sub parse_info_serverside () {
     parse_info1('source-info.txt','s',0);
 }
index 198185d32df4f1855fd4ca691d809b482f91bf04..adcff3492c154682263fe00698cf6fc6d8c7863a 100644 (file)
@@ -51,6 +51,7 @@ BEGIN {
     @EXPORT      = qw(&dbw_connect &ocean_list &sourcebasedir
                      &to_json_shim &to_json_protecttags
                      &set_ctype_utf8
+                     &expected_error &dbw_lookup_string
                      &prettyprint_age &meta_prettyprint_age);
     %EXPORT_TAGS = ( );
 
@@ -151,4 +152,48 @@ BEGIN { eval '
 }
 
 
+sub dbw_lookup_string ($$$$$$$$) { # => ( $emsg, @dbresults )
+    my ($each,
+       $sth, $stmt_nqs, $abbrev_initials, $maxambig,
+       $em_nomatch, $em_manyambig, $emf_ambiguous) = @_;
+    
+    $each =~ s/^\s*//;  $each =~ s/\s*$//;  $each =~ s/\s+/ /g;
+    my %m;
+    my $results;
+    my @pats= ("$each", "$each \%", "$each\%", "\%$each\%");
+    if ($abbrev_initials) {
+       push @pats, join ' ', map { "$_%" } split //, $each;
+    }
+    foreach my $pat (@pats) {
+       $sth->execute(($pat) x $stmt_nqs);
+       $results= $sth->fetchall_arrayref();
+       last if @$results==1;
+       $m{ $_->[0] }=1 for @$results;
+       $results= undef;
+    }
+    if (!$results) {
+       if (!%m) {
+           return $em_nomatch;
+       } elsif (keys(%m) > $maxambig) {
+           return $em_manyambig;
+       } else {
+           return $emf_ambiguous->($each, join(', ', sort keys %m));
+       }
+    }
+    return (undef, @{ $results->[0] });
+}
+
+sub expected_error ($) {
+    my $r= { Emsg => $_[0] };
+    bless $r, 'CommodsWeb::ExpectedError';
+    die $r;
+}
+
+package CommodsWeb::ExpectedError;
+
+sub emsg ($) {
+    my ($self) = @_;
+    return $self->{Emsg};
+}
+
 1;
index 39c60908ee1ab4e1f3ed2b416325d286459ee969..b542a1cca8e33e543853972df9f6845e05eb7ecd 100644 (file)
@@ -172,6 +172,7 @@ for assisting players of Yohoho Puzzle Pirates.
 ypp-sc-tools and YARRG are
 Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
 Copyright (C) 2009 Clare Boothby
+Copyright (C) 2009 Steve Early
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of
index 6b227ebe739dae3f4283f233c218c5fbead918c6..817ac0e121a6a8e1affcfb96c9fc829542774036 100644 (file)
@@ -1,56 +1,31 @@
 UPLOADER
 --------
 
-       sometimes fails to work on Sage - sunshine widget resets or something
+detect all unexpected mouse movements
 
-       detect all unexpected mouse movements
+more flexible installation arrangements
 
-       more flexible installation arrangements
+figure out why pctb.ilk.org isn't working
 
-  W    windows uploader
+windows uploader
 
 DATABASE/DICTIONARY MANAGER
 ---------------------------
 
-       commodity mass/volume in live database
-       eliminate black dye from live database
+eliminate black dye from live database
 
-       when update rejected print better error message including
       broken commodity name
+when update rejected print better error message including
+ broken commodity name
 
-       notice commodities deleted from source-info and warn about them
+notice commodities deleted from source-info and warn about them
 
-       support Opal and Jade (currently there are some unicode problems)
+support Opal and Jade (currently there are some unicode problems)
 
 WEBSITE
 -------
 
-       multi-visit routes / circular routes
+allow unticking based on minimum margin or minimum profit
 
-       adjustable potential cost of losses (rather than fixed
-               1e-BIG per league)
-               use power formula (compound interest)
-               suggest 0.5%
+initial/final stocks feature
 
-       initial/final stocks feature
-
-       max volume/mass
-
-       max capital
-
-       better documentation
-
-       printable voyage trading plan
-
-
-KEYLETTERS
-----------
-
-P      needed before public release
-O      needed before public release to support multiple oceans
-
-C      needs ypp client and network connection
-N      needs network connection
-W      needs to be done by someone with Windows
-
-D      dependencies unsatisfied
+printable voyage trading plan
index 143e2ef9d518e2e733d221365c372971b3ad29e0..1d106f2b4f9ef6924cebc6f19b0677ce8352d008 100755 (executable)
@@ -102,6 +102,13 @@ db_doall(<<END)
        dist            INTEGER                 NOT NULL,
        PRIMARY KEY (aiid, biid)
  );
+ CREATE TABLE IF NOT EXISTS vessels (
+       name            TEXT                    NOT NULL,
+       mass            INTEGER                 NOT NULL,
+       volume          INTEGER                 NOT NULL,
+       shot            INTEGER                 NOT NULL,
+       PRIMARY KEY (name)
+ );
 END
     ;
 
@@ -136,8 +143,20 @@ END
     $dbh->commit;
 }
 
-#---------- island list ----------
-#---------- routes ----------
-# now done by yppedia-chart-parser
-
-__DATA__
+#---------- vessel types ----------
+{
+    my $idempotent= $dbh->prepare(<<'END')
+ INSERT OR REPLACE INTO vessels (name, shot, mass, volume)
+                         VALUES (?,?,?,?)
+END
+    ;
+    foreach my $name (sort keys %vessels) {
+       my $v= $vessels{$name};
+       my $shotdamage= $shotname2damage{$v->{Shot}};
+       die "no shot damage for shot $v->{Shot} for vessel $name"
+           unless defined $shotdamage;
+       my @qa= ($name, $shotdamage, map { $v->{$_} } qw(Mass Volume));
+       $idempotent->execute(@qa);
+    }
+    $dbh->commit;
+}
index 55d15d97e8f5b20d530360c1fde16144cbea77f9..e609e35778188dc147390cd3a451dc315c95da62 100755 (executable)
@@ -20,7 +20,9 @@ $dbh->disconnect();
 #print Dumper($results);
 
 print "strict graph $ocean {\n";
-#print "    nodesep=10;\n";
+print "    splines=true;\n";
+print "    nslimit=10;\n";
+print "    mclimit=10;\n";
 
 foreach my $row (@$islands) {
     my ($id,$str) = @$row;
@@ -29,8 +31,8 @@ foreach my $row (@$islands) {
 }
 foreach my $row (@$routes) {
     my ($ia,$ib,$dist) = @$row;
-    print "    n$ia -- n$ib [ len=2, label=$dist ];\n";
-    #len=$dist, minlen=$dist, weight=".(1.0/$dist).", len=".($dist*0.25+1).",
+    print "    n$ia -- n$ib [ w=".(1.0/($dist*$dist)).", len=".(0.5*$dist+1).", label=$dist ];\n";
+    #len=$dist, minlen=$dist, ,
     #w=".(1.0/$dist).", 
 }
 
index b7d285b620601fbe29ce39fb5416287a3cb9cc4b..88b93113ff41ee5bc8b4c55c387733be0dd58e9b 100644 (file)
@@ -1,4 +1,36 @@
 
+vessels
+#|   Ship Name    |Gun Size|Volume | Mass  |
+ |Sloop           |small   |20,250 |13,500 |
+ |----------------+--------+-------+-------|
+ |Cutter          |small   |60,750 |40,500 |
+ |----------------+--------+-------+-------|
+ |Dhow            |medium  |20,250 |13,500 |
+ |----------------+--------+-------+-------|
+ |Longship        |small   |20,250 |13,500 |
+ |----------------+--------+-------+-------|
+ |Baghlah         |medium  |27,000 |18,000 |
+ |----------------+--------+-------+-------|
+ |Merchant brig   |medium  |135,000|90,000 |
+ |----------------+--------+-------+-------|
+ |War brig        |medium  |81,000 |54,000 |
+ |----------------+--------+-------+-------|
+ |Merchant galleon|large   |405,000|270,000|
+ |----------------+--------+-------+-------|
+ |Xebec           |medium  |182,250|121,500|
+ |----------------+--------+-------+-------|
+ |War frigate     |large   |324,000|216,000|
+ |----------------+--------+-------+-------|
+ |Grand frigate   |large   |810,000|540,000|
+# From http://yppedia.puzzlepirates.com/Ship; when updating,
+# delete unused columns and check heading is the same as above.
+# If fields reordered must change parser in Commods.pm.
+
+shot
+ small 2
+ medium        3
+ large 4
+
 commods
  kraken's blood                1kg
  %c dye                        1kg
index c0bbbffe1a303928159b120e66ebef046b55f8ef..e2b0973db453b093e9b6f261b60c5f2969701e8b 100755 (executable)
@@ -95,7 +95,7 @@ sub process_some_info ($$$) {
            next if $h =~ m/^nocommods/;
        }
        next if $sfn =~ m/source-info/ && $h =~ m/^ocean\b/;
-       next if $h =~ m/^client\b/;
+       next if $h =~ m/^client|^vessels|^shot\b/;
 
        print $df $_, "\n" or die $!;
     }
index 3d8f7a52701fd2983c09ca6bdc6629af1467ec91..a79b6f1ac06f1dffbed02820e33a819a1e31bb29 100644 (file)
  This Mason component simply defines how to interpret capacities.
 
 </%doc>
-
-<%attr>
-</%attr>
-
-<%method preparse>
+<%method execute>
 <%args>
-$h
+$string
+$dbh
+$debugf
 </%args>
 <%perl>
 
-my $def= sub {
-       my ($what,$val) = @_;
-       if (defined $h->{$what}) {
-               $h->{Emsg}= "Multiple definitions of maximum $what.";
+my $commodsth;
+
+my @mv_names= qw(mass volume);
+my @mv_units= qw(kg l);
+
+my (@mv)= (undef,undef);
+return ('',@mv) unless $string =~ m/\S/;
+
+my @canon= ();
+my ($signum,$signopstr)= (+1,undef);
+my $show_answer=0;
+my $first_term=1;
+my $last_signopstr= 'NONE';
+
+my $canon_numeric= sub {
+       my ($val,$mvi) = @_;
+       sprintf "%g%s", $val, $mv_units[$mvi];
+};
+
+my $parse_values= sub {
+  local ($_) = @_;
+  $debugf->("TERM VALUES '$_'");
+  $_ .= ' ';
+  my $def= sub {
+       my ($mvi,$val) = @_;
+       if ($first_term) {
+               expected_error("Initial term specifies".
+                               " $mv_names[$mvi] more than once.")
+                       if defined $mv[$mvi];
+               $mv[$mvi]= $val;
+       } else {
+               expected_error("Cannot add or subtract mass to/from volume")
+                       unless defined $mv[$mvi];
+               $mv[$mvi] += $signum * $val;
+       }
+       push @canon, $canon_numeric->($val,$mvi);
+  };
+  while (m/\S/) {
+       $debugf->("VALUE '$_'");
+       my $iqtyrex= '[1-9] \d{0,8}';
+       my $fqtyrex= '\d{1,9} \. \d{0,3} |' . $iqtyrex;
+       if    (s/^( $fqtyrex ) \s* kg \s+ //xo) { $def->(0, $1          ); }
+       elsif (s/^( $fqtyrex ) \s* t  \s+ //xo) { $def->(0, $1 * 1000.0 ); }
+       elsif (s/^( $fqtyrex ) \s* l  \s+ //xo) { $def->(1, $1          ); }
+       elsif (s/^( $fqtyrex ) \s* kl \s+ //xo) { $def->(1, $1 * 1000.0 ); }
+       elsif (s/^( $iqtyrex ) \s* ([a-z ]+) \s+ //xo) {
+               my ($qty,$spec) = ($1,$2);
+               $debugf->("VALUE COMMOD $qty '$spec'");
+               expected_error("Capacity specification must start with".
+                              " ship size or amount with units")
+                       if $first_term;
+               $commodsth ||=
+                   $dbh->prepare("SELECT commodname,unitmass,unitvolume
+                                    FROM commods WHERE commodname LIKE ?");
+               my ($emsg,$commod,@umv)=
+                   dbw_lookup_string($spec,$commodsth,1,0,0,
+                               "No commodity or unit matches \`$spec'",
+                               "Ambiguous commodity (or unit) \`$spec'",
+                               undef);
+               expected_error($emsg) if defined $emsg;
+               $debugf->("VALUE COMMOD FOUND '$commod' @umv");
+               foreach my $mvi (0,1) {
+                      next unless defined $mv[$mvi];
+                      $mv[$mvi] += $signum * $qty * $umv[$mvi] * 0.001;
+               }
+               push @canon, sprintf "%d", $qty;
+               push @canon, $commod;
+       } else {
+               s/\s+$//;
+               expected_error("Did not understand value \`$_'");
        }
-       print STDERR "SET $what $val\n";
-       $h->{$what}= $val;
+  }
 };
 
-foreach $_ (split /\s+/, ${ $h->{String} }) {
-       print STDERR "ITEM \`$_'\n";
-       next unless length;
-       if (m/^([1-9]\d{0,8})l$/) {
-               $def->('volume', $1);
-       } elsif (m/^([1-9]\d{0,8})kg$/) {
-               $def->('mass', $1);
-       } elsif (m/^([1-9]\d{0,5}(?:\.\d{0,3})?)kl/) {
-               $def->('volume', $1 * 1000);
-       } elsif (m/^([1-9]\d{0,5}(?:\.\d{0,3})?)t/) {
-               $def->('mass', $1 * 1000);
+my $parse_term= sub {
+       local ($_) = @_;
+       $debugf->("TERM '$_' signum=$signum");
+       s/^\s+//; s/\s+$//;
+       expected_error("empty term in capacity") unless m/\S/;
+       if (m/^\s*(\d{1,2}(?:\.\d{0,4})?)\%\s*$/) {
+               $debugf->("TERM PERCENT $1");
+               expected_error("percentage may not be first item")
+                       if $first_term;
+               my $pct= 100.0 + $signum * $1;
+               foreach (@mv) {
+                       next unless defined;
+                       $_ *= $pct / 100.0;
+               }
+               push @canon, sprintf "%g%%", $pct;
+       } elsif (!m/[^a-z]/) {
+               $debugf->("TERM NAME");
+               expected_error("Name (should be unit or commodity) \`$_'".
+                               " without preceding quantity")
+                       unless $first_term;
+               my $sth= $dbh->prepare("SELECT name,mass,volume".
+                                      "  FROM vessels WHERE name LIKE ?");
+               my ($emsg,$ship,@smv)=
+                   dbw_lookup_string($_,$sth,1,1,2,
+                               "Ship name `$_' not understood.",
+                               "Too many matching ship types.",
+                               sub { "Ambiguous - could be $_[0]" });
+               expected_error($emsg) if defined $emsg;
+               $debugf->("TERM NAME SHIP '$ship' @smv");
+               $show_answer= 1;
+               @mv = @smv;
+               push @canon, $ship;
        } else {
-               ${ $h->{Emsg} }= "Cannot understand capacity \`$_'.";
-               last;
+               $parse_values->($_);
        }
+       $first_term= 0;
+};
+
+while ($string =~ s/^(.*?)(\bminus\b|-|\bplus\b|\+)//) {
+       my ($lhs)= ($1);
+       my @nextsign= $2 =~ m/^p|^\+/ ? (+1,'+') : (-1,'-');
+       $show_answer= 1;
+       $debugf->("GROUP S='$2'");
+       $parse_term->($lhs);
+       ($signum,$signopstr)= @nextsign;
+       push @canon, ($last_signopstr=$signopstr)
+               if $signopstr ne $last_signopstr;
 }
-</%perl>
-</%method>
+$parse_term->($string);
 
-<%method postquery>
-<%args>
-$h
-</%args>
-<%perl>
+my $canon= join ' ', @canon;
 
-if (defined $h->{'mass'} or defined $h->{'volume'}) {
-       @{ $h->{Results} } = [ $h->{'mass'}, $h->{'volume'} ];
+if ($show_answer) {
+       $canon .= "  [=";
+       foreach my $mvi (0,1) {
+               next unless defined $mv[$mvi];
+               $canon .= ' '.$canon_numeric->($mv[$mvi], $mvi);
+       }
+       $canon .= "]";
+}
 
-       ${ $h->{Canon} }=
- 'mass limit: '.(defined $h->{'mass'} ? $h->{'mass'} .'kg' : 'none').'; '.
- 'volume limit: '.(defined $h->{'volume'} ? $h->{'volume'} .'l' : 'none').'.';
+$debugf->("FINISHING canon='$canon'");
+
+foreach my $mvi (0,1) {
+       next unless defined $mv[$mvi];
+       next if $mv[$mvi] >= 0;
+       expected_error(sprintf "%s limit is negative: %s",
+               ucfirst($mv_names[$mvi]), $canon_numeric->($mv[$mvi], $mvi));
 }
 
+return ($canon, @mv);
+
 </%perl>
 </%method>
diff --git a/yarrg/web/check_capitalstring b/yarrg/web/check_capitalstring
new file mode 100644 (file)
index 0000000..53aceec
--- /dev/null
@@ -0,0 +1,62 @@
+<%doc>
+
+ This is part of the YARRG website.  YARRG is a tool and website
+ for assisting players of Yohoho Puzzle Pirates.
+
+ Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ Copyright (C) 2009 Clare Boothby
+
+  YARRG's client code etc. is covered by the ordinary GNU GPL (v3 or later).
+  The YARRG website is covered by the GNU Affero GPL v3 or later, which
+   basically means that every installation of the website will let you
+   download the source.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero 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.
+
+
+ This Mason component simply defines how to interpret capital.
+
+</%doc>
+
+<%method execute>
+<%args>
+$string
+$dbh
+$debugf
+</%args>
+<%perl>
+
+$_= $string;
+s/^\s+//; s/\s+$//;
+
+my $capital;
+my $canon;
+
+if (!m/\S/) {
+       $canon= '';
+} elsif (m/^([1-9]\d*)( PoE)?$/i) {
+       $capital= $1;
+       $canon= "$capital PoE";
+} else {
+       expected_error("Cannot understand capital \`$_'.");
+}
+
+return ($canon,$capital);
+
+</%perl>
+</%method>
index 5994f6fdd8373831c1402499a5de0f7ddbc02fa7..937535521dd4209a254a7efa2d9d81464f6b2527 100644 (file)
  sponsored by Three Rings.
 
 
- This Mason component simply defines how to interpret capacities.
+ This Mason component simply defines how to interpret losses per league.
 
 </%doc>
-
-<%attr>
-</%attr>
-
-<%method preparse>
+<%method execute>
 <%args>
-$h
+$string
+$dbh
+$debugf
 </%args>
 <%perl>
 
-$_= ${ $h->{String} };
+$_= $string;
 s/^\s+//; s/\s+$//;
 
-my $res= sub {
-       my ($pct,$str) = @_;
-       push @{ $h->{Results} }, [ $pct ];
-       ${ $h->{Canon} }= "$str per league";
-};
+my ($pct,$str);
 
 if (!m/\S/) {
+       $str= '';
 } elsif (m/^(\d{1,2}(?:\.\d{0,5})?)\%$/) {
-       $res->( $1 * 1.0, sprintf("%g%%", $1) );
+       $pct= $1 * 1.0;
+       $str= sprintf("%g%%", $1);
 } elsif (m/^1\s*\/\s*([1-9]\d{0,4})/) {
-       $res->( 100.0/$1, sprintf("1/%d", $1) );
+       $pct= 100.0/$1;
+       $str= sprintf("1/%d", $1);
 } else {
-       ${ $h->{Emsg} }= "Cannot understand loss per league \`$_'.";
-       return;
+       expected_error("Cannot understand loss per league \`$_'.");
 }
 
+return ("$str per league", $pct);
+
 </%perl>
 </%method>
index e7d2dc83b901c4a2331c353f299c5746b1306c39..6af4e759c890a8ddedbef82e2325f8801c595ef4 100644 (file)
@@ -1 +1 @@
-Copyright 2009 Ian Jackson, Clare Boothby
\ No newline at end of file
+Copyright 2009 Ian Jackson, Clare Boothby, Steve Early
\ No newline at end of file
index 513d50b284411f2516f3a70c0f3c3a7291cd358a..fecd77dc1b25449701391f4a8a65d6b888703d6b 100755 (executable)
 
 
 </%doc>
-<html><head><title>YARRG (Yet Another Revenue Research Gatherer)</title>
+<html lang="en"><head>
+<title>YARRG (Yet Another Revenue Research Gatherer)</title>
 </head><body>
 
 <a href="lookup">YARRG</a> -
  Yet Another Revenue Research Gatherer
 |
-<b>development</b>
-|
 <a href="intro">introduction</a>
 |
 <a href="docs">documentation</a>
+|
+<b>development</b>
 
 <h1>YARRG development, contribution and troubleshooting</h1>
 
@@ -53,7 +54,7 @@ YARRG is Free Software - you may share and modify it.  See the
 licences for details.  Not only the client but also the website code
 is Free.  The yarrg client, support files, and so forth are under
 the GNU GPL (v3 or later); the website is under the GNU Affero GPL (v3
-or later).  </p>
+or later).
 
 <p>
 
@@ -123,9 +124,9 @@ has the specification of the mechanism and format for uploading to YARRG.
 If you would like to run a (perhaps modified) copy of the YARRG
 website it would be very easy for us to make our system send you
 copies of updates submitted by users of the official YARRG client, in
-the format expected by the YARRG code.  Please just ask us - it's just
-a matter of us adding your database instance's special email address
-to our alias file.
+the format expected by the YARRG code.  Please just ask us - at our
+end it's just a matter of us adding your database instance's special
+email address to our alias file.
 
 <p>
 
index cbb1c0dd2a30dea8055aef2fae6d78c4b27d7954..c211683717acf735bf05e23e34ba4892a2f16605 100755 (executable)
 
 
 </%doc>
-<html><head><title>Website documentation - YARRG</title>
+<html lang="en"><head><title>Website documentation - YARRG</title>
 </head><body>
 
 <a href="lookup">YARRG</a> -
  Yet Another Revenue Research Gatherer
 |
-<a href="devel">development</a>
-|
 <a href="intro">introduction</a>
 |
 <b>documentation</b>
+|
+<a href="devel">development</a>
 
 <h1>Looking up data in YARRG</h1>
 
@@ -101,7 +101,7 @@ After getting the results, you can untick various trades individually,
 and select `Update' to get a new plan.  The unticked trades will be
 excluded from the voyage plan (if any) and also from the totals.
 
-<h3>Vessel capacity</h3>
+<h3><a name="capacity">Vessel capacity</a></h3>
 
 If you don't specify a vessel or a vessel capacity, the trading plan
 will not take into account the fact that your voyage will be on a ship
@@ -110,13 +110,41 @@ which trades excessively cumbersome goods (eg. hemp, wood, iron).
 
 <p>
 
-So you should specify your vessel capacity.  Currently you must
-specify the actual mass and volume, as two numbers each with units.
-The system understands the units t (tonnes), kg, l and kl
-(kilolitres).  There should be a space between the two limits, and no
-space before the unit.
+So you should specify your vessel capacity.  You can enter things
+like:
+<dl>
+<dt>sloop
+<dd>The capacity of a sloop, leaving no allowance for rum and shot
+<dt>wb - 1%
+<dd>The capacity of a war brig minus 1%
+<dt>13t 20kl
+<dd>13 tonnes (13,000kg), 20 kilolitres (20,000l)
+<dt>sloop - 10 small 40 rum
+<dd>The capacity of a sloop which remains after
+    10 small shot and 40 rum are loaded
+<dt>2t plus 500kg minus 200kg
+<dd>2300kg, with no limit on volume
+</dl>
+Evaluation is strictly from left to right.
+
+<p>
+
+More formally:
+<pre>
+ capacity-string := [ first-term term* ]
+ term := ('+' | '-' | 'plus' | 'minus') (value+ | number'%')
+ value := mass | volume
+        | integer commodity-name-or-abbreviation
+ mass := number ('t' | 'kg')
+ volume := number ('kl' | 'l')
+ first-term := mass | volume | mass volume | volume mass
+             | ship-name-or-abbreviation
+</pre>
 
-<h3>Expected losses</h3>
+If the first term specifies only one of mass or volume, all the
+subsequent terms may only adjust that same value.
+
+<h3><a name="losses">Expected losses</a></h3>
 
 In theory if you were guaranteed to have a trouble-free voyage it
 would be worth trading goods at very low margins.  However, in
@@ -134,25 +162,44 @@ to do.
 
 <p>
 
-Trades whose margin is less than the expected loss are never selected.
-For example, if you select 1% loss per league, and plan a voyage of 5
-leagues, then any trade with a margin of less than 5.15% would be
-completely excluded (5.15% not 5% because the loss works like compound
-interest).  Theoretically very profitable trades which are close to
-the expected break-even point because of the distance can also be
-rejected by the optimiser in favour of shorter distance trades with
-theoretically smaller margins.
+Trades whose margin is less than the expected loss are never included
+in the suggested plan.  For example, if you select 1% loss per league,
+and plan a voyage of 5 leagues, then any trade with a margin of less
+than 5.15% would be completely excluded (5.15% not 5% because the loss
+works like compound interest).  Theoretically very profitable trades
+which are close to the expected break-even point because of the
+distance can also be rejected by the optimiser in favour of shorter
+distance trades with theoretically smaller margins, if it's not
+possible to do both.
 
 <p>
 
-As a guide: you may expect to lose between 0.1% and 1% per league.
-0.1% would correspond, for example, to losing one fight to brigands
-every ten 10-league voyages.
+As a guide: you may expect to lose between 0.01% and 1% per league.
+For example 0.1% would correspond to losing one fight to brigands (who
+take 10% if they win) for every 100 leagues sailed.
 
 <p>
 
 You can enter the value in the box either as a percentage, or as a
-fraction 1/<em>divisor</em>, eg 1/200 is the same as 0.5%; in each
+fraction 1/<em>divisor</em>, eg 1/2000 is the same as 0.05%; in each
 case it is taken as the loss for each league of the voyage.
 
+<h3><a name="capital">Available capital</a></h3>
+
+If you don't specify the amount of capital you have available to
+invest in the voyage, the trading plan will assume that your capital
+is unlimited.  If you specify an amount in PoE here, the trading plan
+will never require you to spend more than that amount on commodities.
+
+<p>
+
+The trading plan does not take into account accumulated profits from
+each leg of the journey when applying the available capital
+constraint.  For example, if you specify a journey from A to B to C
+and a capital limit of 10000 PoE, the trading plan will not tell you
+to buy 1000 peas at A for 10 PoE each, sail them to B and sell all of
+them for 20 PoE each, and then buy 2000 beans at B for 10 PoE each and
+sail them to C to sell for 20 PoE each even if such a trade would in
+fact be possible.  In practice this is unlikely to be a problem!
+
 <& footer &>
index e60415c8469bae1c94dd8ed5851bfe84e48588a4..739f14d8b04f8eb3a8993f3572637c9f804ce22d 100644 (file)
@@ -31,7 +31,7 @@
 
  This Mason component is helpful for debugging and developing.  It
  outputs plain HTML tables eg for SQL query results.  You can either:
-    <& dumptable, sth = $executed_statement_handle &>
+    <& dumptable, sth => $executed_statement_handle &>
  in which case it will consume the results of the statement and
  print them unconditionally, or do the equivalent of:
     <& dumptable:start, sth => $sth,              [ qa => $qa ] &> or
diff --git a/yarrg/web/enter_commod b/yarrg/web/enter_commod
new file mode 100644 (file)
index 0000000..c3f5553
--- /dev/null
@@ -0,0 +1,73 @@
+<%doc>
+
+ This is part of the YARRG website.  YARRG is a tool and website
+ for assisting players of Yohoho Puzzle Pirates.
+
+ Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ Copyright (C) 2009 Clare Boothby
+
+  YARRG's client code etc. is covered by the ordinary GNU GPL (v3 or later).
+  The YARRG website is covered by the GNU Affero GPL v3 or later, which
+   basically means that every installation of the website will let you
+   download the source.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero 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.
+
+
+ This Mason component generates form contents for selecting a commodity.
+
+
+</%doc>
+<%args>
+$qa
+$dbh
+$emsg_r
+
+$commodname_r
+$cmid_r
+</%args>
+
+%#---------- textbox, user enters commodity as string ----------
+% if (!$qa->{Dropdowns}) {
+
+Enter commodity (abbreviations are OK):<br>
+
+<&| qtextstring, qa => $qa, dbh => $dbh, emsgstore => $emsg_r,
+    thingstring => 'commodstring', prefix => 'cm',
+    onresults => sub { ($$commodname_r,$$cmid_r)= @{ $_[0] } if @_ }
+ &>
+ size=80
+</&>
+
+% } else { #---------- dropdowns, user selects from menus ----------
+
+%      my $sth= $dbh->prepare("SELECT commodname,commodid FROM commods
+%                                      ORDER BY commodname");
+%      $sth->execute();
+%      my $row;
+<select name="commodid">
+<option value="">Select commodity...</option>
+%      while ($row= $sth->fetchrow_arrayref) {
+%              my $selected= $qa->{'commodid'} eq $row->[1] ? 'selected' : '';
+<option value="<% $row->[1] %>" <% $selected %>><% $row->[0] |h %></option>
+%              ($$commodname_r,$$cmid_r) = @$row if $selected;
+%      }
+</select>
+<p>
+
+% } #---------- end of dropdowns, now common middle of page code ----------
diff --git a/yarrg/web/enter_route b/yarrg/web/enter_route
new file mode 100644 (file)
index 0000000..c21d967
--- /dev/null
@@ -0,0 +1,191 @@
+<%doc>
+
+ This is part of the YARRG website.  YARRG is a tool and website
+ for assisting players of Yohoho Puzzle Pirates.
+
+ Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ Copyright (C) 2009 Clare Boothby
+
+  YARRG's client code etc. is covered by the ordinary GNU GPL (v3 or later).
+  The YARRG website is covered by the GNU Affero GPL v3 or later, which
+   basically means that every installation of the website will let you
+   download the source.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero 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.
+
+
+ This Mason component generates form contents for selecting a list
+ of locations (eg, a route).
+
+
+</%doc>
+<%args>
+$qa
+$dbh
+$emsg_r
+$warningfs_r
+
+$enterwhat
+$islandids_r
+$archipelagoes_r
+</%args>
+
+%#---------- textbox, user enters route as string ----------
+% if (!$qa->{Dropdowns}) {
+
+<% $enterwhat %> (islands, or archipelagoes, separated by |s or commas;
+ abbreviations are OK):<br>
+
+<&| qtextstring, qa => $qa, dbh => $dbh, emsgstore => $emsg_r,
+    thingstring => 'routestring', prefix => 'rl',
+    onresults => sub {
+       foreach (@_) {
+       my ($canonname, $island, $arch) = @$_;
+               push @$islandids_r, $island;
+               push @$archipelagoes_r, defined $island ? undef : $arch;
+       }
+    }
+ &>
+ size=80
+</&>
+
+% } else { #---------- dropdowns, user selects from menus ----------
+
+<%perl>
+my %islandid2;
+my ($sth,$row);
+my @archlistdata;
+my %islandlistdata;
+$islandlistdata{'none'}= [ [ "none", "Select island..." ] ];
+
+my $optionlistmap= sub {
+       my ($optlist, $selected) = @_;
+       my $out='';
+       foreach my $entry (@$optlist) {
+               $out.= sprintf('<option value="%s" %s>%s</option>',
+                       encode_entities($entry->[0]),
+                       defined $selected && $entry->[0] eq $selected
+                               ? 'selected' : '',
+                       encode_entities($entry->[1]));
+       }
+       return $out;
+};
+
+$sth= $dbh->prepare("SELECT DISTINCT archipelago FROM islands
+                           ORDER BY archipelago;");
+$sth->execute();
+
+while ($row=$sth->fetchrow_arrayref) {
+       my ($arch)= @$row;
+       push @archlistdata, [ $arch, $arch ];
+       $islandlistdata{$arch}= [ [ "none", "Whole arch" ] ];
+}
+
+$sth= $dbh->prepare("SELECT islandid,islandname,archipelago
+                            FROM islands
+                           ORDER BY islandname;");
+$sth->execute();
+
+while ($row=$sth->fetchrow_arrayref) {
+       my $arch= $row->[2];
+       push @{ $islandlistdata{'none'} }, [ @$row ];
+       push @{ $islandlistdata{$arch} }, [ @$row ];
+       $islandid2{$row->[0]}= { Name => $row->[1], Arch => $arch };
+}
+
+my %resetislandlistdata;
+foreach my $arch (keys %islandlistdata) {
+       $resetislandlistdata{$arch}=
+               $optionlistmap->($islandlistdata{$arch}, '');
+}
+
+</%perl>
+
+<&| script &>
+ms_lists= <% to_json_protecttags(\%resetislandlistdata) %>;
+function ms_Setarch(dd) {
+  debug('ms_SetArch '+dd+' arch='+arch);
+  var arch= document.getElementsByName('archipelago'+dd).item(0).value;
+  var got= ms_lists[arch];
+  if (got == undefined) return; // unknown arch ?  hrm
+  debug('ms_SetArch '+dd+' arch='+arch+' got ok');
+  var select= document.getElementsByName('islandid'+dd).item(0);
+  select.innerHTML= got;
+  debug('ms_SetArch '+dd+' arch='+arch+' innerHTML set');
+}
+</&script>
+
+<table style="table-layout:fixed; width:90%;">
+
+<tr>
+%      for my $dd (0..$qa->{Dropdowns}-1) {
+<td>
+<select name="archipelago<% $dd %>" onchange="ms_Setarch(<% $dd %>)">
+<option value="none">Whole ocean</option>
+<% $optionlistmap->(\@archlistdata, $qa->{"archipelago$dd"}) %></select></td>
+%      }
+</tr>
+
+<tr>
+%      for my $dd (0..$qa->{Dropdowns}-1) {
+%              my $arch= $qa->{"archipelago$dd"};
+%              $arch= 'none' if !defined $arch;
+<td>
+<select name="islandid<% $dd %>">
+<% $optionlistmap->($islandlistdata{$arch}, $qa->{"islandid$dd"}) %>
+</select></td>
+%      }
+</tr>
+
+</table>
+
+<%perl>
+
+my $argorundef= sub {
+       my ($dd,$base) = @_;
+       my $thing= $qa->{"${base}${dd}"};
+       $thing= undef if defined $thing and $thing eq 'none';
+       return $thing;
+};
+
+for my $dd (0..$qa->{Dropdowns}-1) {
+       my $arch= $argorundef->($dd,'archipelago');
+       my $island= $argorundef->($dd,'islandid');
+       next unless defined $arch or defined $island;
+       if (defined $island and defined $arch) {
+               my $ii= $islandid2{$island};
+               my $iarch= $ii->{Arch};
+               if ($iarch ne $arch) {
+                       push @$warningfs_r, sub {
+</%perl>
+ Specified archipelago <% $arch %> but
+ island <% $ii->{Name} %>
+ which is in <% $iarch %>; using the island.<p>
+<%perl>
+                       };
+               }
+               $arch= undef;
+       }
+       push @$archipelagoes_r, $arch;
+       push @$islandids_r, $island;
+}
+
+</%perl>
+<p>
+
+% }
index 9837d533e26e35bb37aed0ac73669c70da4b9d0f..75fcd156268ed9d9001d90c8a37b72b50c2c4c06 100644 (file)
@@ -47,6 +47,7 @@ YARRG is Free Software.
 You may share and modify the code and the
 website, according to the terms of the GNU General Public Licence and
 the GNU Affero General Public Licence respectively (v3 or later).
+Note that there is <strong>NO WARRANTY</strong>.
 % if (!$isdevel) {
 Please see the <a href="devel">YARRG Development webpage</a> for
 details of how to obtain the client and server code and full details
index c77f183a7424b5e1a6874488721d0572cc0bc2b2..467de524182d93f6c4447694fc1995a3c0b51c2f 100755 (executable)
 
 
 </%doc>
-<html><head><title>YARRG (Yet Another Revenue Research Gatherer)</title>
+<html lang="en"><head>
+<title>YARRG (Yet Another Revenue Research Gatherer)</title>
 </head><body>
 
 <a href="lookup">YARRG</a> -
  Yet Another Revenue Research Gatherer
 |
-<a href="devel">development</a>
-|
 <b>introduction</b>
 |
 <a href="docs">documentation</a>
+|
+<a href="devel">development</a>
 
 <h1>Introduction to YARRG</h1>
 
@@ -68,7 +69,7 @@ website.
 <h2>Uploading from Linux</h2>
 
 The YARRG upload client uploads both to YARRG and to the
-<a href="pctb.ilk.org">PCTB testing server</a>.
+<a href="http://pctb.ilk.org/">PCTB testing server</a>.
 
 <p>
 
index 7b3100ee17b28b541416d63f2fe4aed1506d9207..7af431cee119f6e163439b83a08fcf84b6b8eedd 100755 (executable)
@@ -57,6 +57,7 @@ my %styles;
                Before => 'Query: ',
                Values => [     [ 'route', 'Trades for route' ],
                                [ 'commod', 'Prices for commodity' ],
+                               [ 'offers', 'Offers at location' ],
                                [ 'age', 'Data age' ] ]
        }, {    Name => 'BuySell',
                Before => '',
@@ -118,7 +119,7 @@ $ours
 % }
 </%method>
 
-<html><head><title><% ucfirst $ahtml{Query} %> - YARRG</title>
+<html lang="en"><head><title><% ucfirst $ahtml{Query} %> - YARRG</title>
 <style type="text/css">
 body {
   color: #000000;
@@ -141,11 +142,11 @@ tr.datarow1 { background: #ffffff; }
 <a href="<% $m->current_comp()->name() |u %>">YARRG</a> -
  Yet Another Revenue Research Gatherer
 |
-<a href="devel">development</a>
-|
 <a href="intro">introduction</a>
 |
 <a href="docs">documentation</a>
+|
+<a href="devel">development</a>
 <p>
 <%perl>
 
@@ -157,7 +158,7 @@ foreach my $var (@vars) {
 
 foreach my $var (keys %ARGS) {
        next unless $var =~
-               m/^(?: (?:route|commod|capacity)string |
+               m/^(?: (?:route|commod|capacity|capital)string |
                        lossperleague |
                        commodid |
                        islandid \d |
index 639e9abf25bc3a3db06a091f776138e856c32092..c958915ae33fdfa012c0e9ab639db0636d84b95e 100644 (file)
@@ -40,8 +40,9 @@ $qa => $m->caller_args(1)->{'qa'}
 $dbh
 $thingstring
 $emsgstore
-$perresult
+$onresults
 $prefix => 'ts';
+$helpref => undef;
 </%args>
 <%perl>
 my $stringval= $qa->{$thingstring};
@@ -97,13 +98,13 @@ register_onload(<%$p%>Needed);
  id="<% $thingstring %>" name="<% $thingstring %>"
  onchange="<%$p%>Needed();" onkeyup="<%$p%>Later();"
  value="<% $stringval |h %>"
- >
+ ><% defined($helpref) ? "<a href=\"docs#$helpref\">[?]</a>" : '' %>
 <br>
 <div id="<%$p%>results">&nbsp;</div><br>
 
 <%perl>
 if (length $thingstring) {
-       my ($emsg,$canonstring,$results)= $m->comp('qtextstringcheck',
+       my ($emsg,$canonstring,@results)= $m->comp('qtextstringcheck',
                what => $thingstring,
                ocean => $qa->{Ocean},
                string => $stringval,
@@ -113,10 +114,6 @@ if (length $thingstring) {
                $$emsgstore='' unless defined $$emsgstore;
                $$emsgstore .= $emsg. ' ';
        }
-
-       foreach my $entry (@$results) {
-#print STDERR "qts entry perresult \`@$entry'\n";
-               $perresult->(@$entry);
-       }
+       $onresults->(@results);
 }
 </%perl>
index a489d8e1232940e6c603c44e05f43eed3add47b9..9dce8287e208d8213418dd6432e49898c998a74a 100755 (executable)
@@ -44,6 +44,7 @@ $ctype => undef
 $string
 $what
 $dbh => undef
+$debug => 0
 </%args>
 
 <%flags>
@@ -55,6 +56,7 @@ use JSON;
 use Data::Dumper;
 use HTML::Entities;
 use CommodsWeb;
+use Scalar::Util qw(blessed);
 
 die if $what =~ m/[^a-z]/;
 my $chk= $m->fetch_comp("check_${what}");
@@ -62,74 +64,75 @@ my $chk= $m->fetch_comp("check_${what}");
 my $mydbh;
 $dbh ||= ($mydbh= dbw_connect($ocean));
 
-#print STDERR "qtsc string=\`$string'\n";
+my $debugf= !$debug ? sub { } : sub {
+    print "@_\n";
+};
 
-my ($sth, @sqlstmt_qs);
-if ($chk->method_exists('sqlstmt')) {
-       my $sqlstmt= $chk->scall_method("sqlstmt");
-       $sth= $dbh->prepare($sqlstmt);
-       @sqlstmt_qs= $sqlstmt =~ m/\?/g;
-}
+$debugf->("QTSC STRING '$string'");
 
 my $emsg= '';
 my @results;
-my @specs;
 my $canontext;
-my $hooks = {  Emsg => \$emsg,         String => \$string,
-               Results => \@results,   Specs => \@specs,
-               Canon => \$canontext
-           };
 
-if ($chk->method_exists('preparse')) {
-       $chk->call_method('preparse', h => $hooks);
-} else {
-       @specs= $chk->attr('multiple') ? (split m#[/|,]#, $string) : ($string);
-}
+$string =~ s/^\s*//;
+$string =~ s/\s$//;
+$string =~ s/\s+/ /g;
 
-no warnings qw(exiting);
-
-foreach my $each (@specs) {
-       $each =~ s/^\s*//;  $each =~ s/\s*$//;  $each =~ s/\s+/ /g;
-       next if !length $each;
-       my $err= sub { $emsg= $_[0]; last; };
-       my %m;
-       my $results;
-       foreach my $pat ("$each", "$each\%", "\%$each\%") {
-               $sth->execute(($pat) x @sqlstmt_qs);
-               $results= $sth->fetchall_arrayref();
-               last if @$results==1;
-               map { $m{ $_->[0] }=1 } @$results;
-               $results= undef;
+if ($chk->method_exists('execute')) {
+       ($canontext, @results)= eval {
+               $chk->call_method('execute',
+                               dbh => $dbh, string => $string,
+                               debugf => $debugf);
+       };
+       if ($@) {
+               die unless blessed $@ && $@->isa('CommodsWeb::ExpectedError');
+               $emsg= $@->emsg();
        }
-       if (!$results) {
-               if (!%m) {
-                       $err->($chk->scall_method("nomatch",
-                               spec => $each));
-               } elsif (keys(%m) > $chk->attr('maxambig')) {
-                       $err->($chk->scall_method("manyambig"));
-               } else {
-                       $err->($chk->scall_method("ambiguous",
-                               spec => $each,
-                               couldbe => join(', ', sort keys %m)));
+} else {
+       my $sqlstmt= $chk->scall_method("sqlstmt");
+       my $sth= $dbh->prepare($sqlstmt);
+       my @sqlstmt_nqs= $sqlstmt =~ m/\?/g;
+       my $sqlstmt_nqs= @sqlstmt_nqs;
+
+       my @specs= $chk->attr('multiple')
+               ? (split m#\s*[/|,]\s*#, $string)
+               : ($string);
+
+       foreach my $each (@specs) {
+               next unless $each =~ m/\S/;
+               my ($temsg, @tresults) =
+                   dbw_lookup_string($each,
+                       $sth, $sqlstmt_nqs,
+                       $chk->attr_exists('abbrev_initials'),
+                       $chk->attr('maxambig'),
+                       $chk->scall_method("nomatch", spec => $each),
+                       $chk->scall_method("manyambig"),
+                       sub {
+                               $chk->scall_method("ambiguous",
+                                       spec => $each, couldbe => $_[1])
+                       });
+               if (defined $temsg) {
+                       $emsg= $temsg;
+                       last;
                }
-       }
-       push @results, $results->[0];
-};
+               push @results, [ @tresults ];
+       };
+}
 
 if (!defined $canontext) {
        $canontext= join ' | ', map { $_->[0] } @results;
 }
-if ($chk->method_exists('postquery')) {
-       $chk->call_method('postquery', h => $hooks);
-}
 
 $emsg='' if !defined $emsg;
 @results=() if length $emsg;
 
-#print STDERR "qtsc emsg=\`$emsg' results=\`@results'\n";
+$debugf->("QTSC EMSG='$emsg' RESULTS='@results'");
 
 if ($format =~ /json/) {
-       $r->content_type($ctype or $format);
+       $ctype ||= $format;
+       die unless grep { $_ eq $ctype }
+               qw(application/json text/plain text/xml);
+       $r->content_type($ctype);
        my $jobj= {
                success => 1*!length $emsg,
                show => (length $emsg      ? $emsg                       :
@@ -147,6 +150,6 @@ $mydbh->rollback() if $mydbh;
 
 return  $emsg,
        $canontext,
-       [ @results ];
+       @results;
 
 </%perl>
index a02187eae01d1b85988382f3e82bc538c6ba010f..d9b234dd559e074353641f7c1ccd5fae7f9b4916 100644 (file)
@@ -80,7 +80,7 @@ $sth->execute();
 <tr id=<% $rowid %> class="<% 'datarow'.($rowix & 1) %>"
    > <td><% $row->{'archipelago'} |h
   %> <td><% $row->{'islandname'} |h
-  %> <td id="<% $cellid %>"><% prettyprint_age($age) %> </tr>
+  %> <td id="<% $cellid %>" align=right><% prettyprint_age($age) %> </tr>
 %      $rowix++;
 % }
 </table>
index b37fa39001e25ec966f234e4ff84735c340c18b7..3b2bf3554533f40a334d63690dac86f023b16764 100644 (file)
@@ -57,34 +57,10 @@ my $qa= \%ARGS;
 
 <form action="<% $quri->() |h %>" method="get">
 
-%#---------- textbox, user enters route as string ----------
-% if (!$qa->{Dropdowns}) {
-
-Enter commodity (abbreviations are OK):<br>
-
-<&| qtextstring, qa => $qa, dbh => $dbh,
-    thingstring => 'commodstring', emsgstore => \$emsg,
-    perresult => sub { ($commodname,$cmid)= @_; }
+<& enter_commod, qa => $qa, dbh => $dbh, emsg_r => \$emsg,
+       commodname_r => \$commodname,
+       cmid_r => \$cmid
  &>
- size=80
-</&>
-
-% } else { #---------- dropdowns, user selects from menus ----------
-
-%      my $sth= $dbh->prepare("SELECT commodname,commodid FROM commods
-%                                      ORDER BY commodname");
-%      $sth->execute();
-%      my $row;
-<select name="commodid">
-<option value="">Select commodity...</option>
-%      while ($row= $sth->fetchrow_arrayref) {
-%              my $selected= $commodid eq $row->[1] ? 'selected' : '';
-<option value="<% $row->[1] %>" <% $selected %>><% $row->[0] |h %></option>
-%              ($commodname,$cmid) = @$row if $selected;
-%      }
-</select>
-
-% } #---------- end of dropdowns, now common middle of page code ----------
 
 <input type=submit name=submit value="Go">
 % my $ours= sub { $_[0] =~ m/^commodstring|^commodid/; };
@@ -133,9 +109,14 @@ foreach my $bs (split /_/, $ARGS{BuySell}) {
 %      my $rowix= 0;
 %      while ($island= $islands->fetchrow_hashref) {
 %              if (!$rowix) {
-<table id="<% $bs %>_table">
+<table id="<% $bs %>_table" rules=groups>
+<colgroup span=2>
+<colgroup span=1>
+<colgroup span=2>
+<colgroup span=3>
 <tr>
-<th colspan=3>
+<th colspan=2>
+<th colspan=1>
 <th colspan=2>Prices
 <th colspan=3>Quantity at price
 <tr id="<% $bs %>_table_thr">
@@ -194,11 +175,11 @@ foreach my $bs (split /_/, $ARGS{BuySell}) {
      <td><% $s->[0]= $island->{'archipelago'} |h %>
      <td><% $s->[1]= $island->{'islandname'} |h %>
      <td><%          $stallname |h %>
-     <td><% $s->[3]= (length $bestqty ? $bestprice : '-') %>
-     <td><% $s->[4]= $median %>
-     <td><% $s->[5]= $bestqty %>
-     <td><% $s->[6]= $approxqty %>
-     <td><% $s->[7]= $cqty %>
+     <td align=right><% $s->[3]= (length $bestqty ? $bestprice : '-') %>
+     <td align=right><% $s->[4]= $median %>
+     <td align=right><% $s->[5]= $bestqty %>
+     <td align=right><% $s->[6]= $approxqty %>
+     <td align=right><% $s->[7]= $cqty %>
 </tr>
 %              for my $cix (0..$#$s) {
 %                      $ts_sortkeys{$cix}{$rowid}= $s->[$cix];
diff --git a/yarrg/web/query_offers b/yarrg/web/query_offers
new file mode 100644 (file)
index 0000000..76f45c0
--- /dev/null
@@ -0,0 +1,223 @@
+<%doc>
+
+ This is part of the YARRG website.  YARRG is a tool and website
+ for assisting players of Yohoho Puzzle Pirates.
+
+ Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
+ Copyright (C) 2009 Clare Boothby
+
+  YARRG's client code etc. is covered by the ordinary GNU GPL (v3 or later).
+  The YARRG website is covered by the GNU Affero GPL v3 or later, which
+   basically means that every installation of the website will let you
+   download the source.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero 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.
+
+
+ This Mason component generates the core of the `offers' query.
+
+
+</%doc>
+<%args>
+$quri
+$dbh
+$commodid => undef;
+$commodstring => '';
+$islandid => undef;
+$prselector
+$someresults
+$emsgokorprint
+</%args>
+
+<%perl>
+my $emsg;
+my @warningfs;
+my @islandids;
+my @archipelagoes;
+my ($commodname,$cmid);
+
+my $qa= \%ARGS;
+</%perl>
+
+<h1>Prices for commodity at location(s)</h1>
+
+% $prselector->('BuySell');
+
+<form action="<% $quri->() |h %>" method="get">
+
+<& enter_commod, qa => $qa, dbh => $dbh, emsg_r => \$emsg,
+       commodname_r => \$commodname,
+       cmid_r => \$cmid
+ &>
+
+<& enter_route, qa => $qa, dbh => $dbh, emsg_r => \$emsg,
+       warningfs_r => \@warningfs,
+       enterwhat => 'Enter location',
+       islandids_r => \@islandids,
+       archipelagoes_r => \@archipelagoes
+ &>
+
+<input type=submit name=submit value="Go">
+% my $ours= sub { $_[0] =~
+%    m/^commodstring|^commodid|^routestring|^archipelago|^island/;
+% };
+<& "lookup:formhidden", ours => $ours &>
+
+</form>
+
+%#========== results ==========
+<%perl>
+
+$emsgokorprint->($emsg) or $cmid=undef;
+return unless defined $cmid and @islandids;
+
+foreach my $wf (@warningfs) { $wf->(); }
+
+if ($qa->{'debug'}) {
+</%perl>
+<pre>
+bs= <% $qa->{BuySell} %>
+cmdid= <% $cmid %>
+islandids= <% join ',', map { defined($_) ? $_ : 'U' } @islandids %>
+</pre>
+<%perl>
+}
+
+my $locdesc;
+if (@islandids>1) {
+       $locdesc= ' at specified locations';
+} elsif (defined $islandids[0]) {
+       my $sth= $dbh->prepare("SELECT islandname FROM islands
+                                WHERE islandid == ?");
+       $sth->execute($islandids[0]);
+       $locdesc= ' at '.($sth->fetchrow_array())[0];
+} else {
+       $locdesc= ' in '.$archipelagoes[0];
+}
+
+my $now= time;
+
+my @conds;
+my @condvals;
+push @condvals, $cmid;
+foreach my $ix (0..$#islandids) {
+       my $iid= $islandids[$ix];
+       my $arch= $archipelagoes[$ix];
+       if (defined $iid) {
+               push @conds, 'offers.islandid == ?';
+               push @condvals, $iid;
+       } else {
+               push @conds, 'islands.archipelago == ?';
+               push @condvals, $arch;
+       }
+}
+foreach my $bs (split /_/, $qa->{BuySell}) {
+       my %da_ages;
+       my %ts_sortkeys;
+
+       die unless grep { $bs eq $_ } qw(buy sell);
+       my $ascdesc= $bs eq 'buy' ? 'DESC' : 'ASC';
+</%perl>
+<h2>Offers to <% uc $bs |h %> <% $commodname |h %> <% $locdesc %></h2>
+<%perl>
+       my $stmt= "
+           SELECT      archipelago, islandname,
+                       stallname, price, qty, timestamp,
+                       offers.stallid
+               FROM $bs AS offers
+               JOIN islands ON offers.islandid==islands.islandid
+               JOIN uploads ON offers.islandid==uploads.islandid
+               JOIN stalls ON offers.stallid==stalls.stallid
+               WHERE offers.commodid == ?
+                 AND ( ".join("
+                   OR ", @conds)."
+                    )
+               ORDER BY archipelago, islandname, price $ascdesc, qty ASC,
+                       stallname $ascdesc
+";
+       if ($qa->{'debug'}) {
+</%perl>
+<pre>
+<% $stmt %>
+<% join ',', @condvals |h %>
+</pre>
+<%perl>
+       }
+
+       my $row;
+       my $sth= $dbh->prepare($stmt);
+       $sth->execute(@condvals);
+       my $rowix= 0;
+</%perl>
+%      while ($row= $sth->fetchrow_arrayref) {
+%              if (!$rowix) {
+<table id="<% $bs %>_table" rules=groups>
+<colgroup span=2>
+<colgroup span=3>
+<colgroup span=1>
+<tr>
+<th>Archipelago
+<th>Island
+<th>Stall or Shoppe
+<th>Price
+<th>Quantity
+<th>Data age
+</tr>
+%              }
+%              my $rowid= ${bs}.$row->[6];
+%              my $tscellid= "c$rowid";
+%              my $age= $now - $row->[5];
+%              $da_ages{$rowid}= $age;
+%              $row->[5]= 
+<tr id=<% $rowid %> class="<% 'datarow'.($rowix & 1) %>" >
+%              foreach my $ci (0..4) {
+%                      my $val= $row->[$ci];
+%                      $ts_sortkeys{$ci}{$rowid}= $val;
+<td <% $ci >= 3 ? 'align=right' : '' %> ><% $val |h %>
+%              }
+<td id="<% $tscellid %>" align=right><% prettyprint_age($age) %>
+</tr>
+%              $rowix++;
+%      }
+%      if ($rowix) {
+</table>
+
+<&| tabsort, table => "${bs}_table", rowclass => 'datarow', cols => [
+       {}, {}, {},
+       { Numeric => 1, DoReverse => 1 },
+       { Numeric => 1, DoReverse => 1 },
+       { Numeric => 1, DoReverse => 1, SortKey => "${bs}_ages[rowid]" }],
+       sortkeys => "${bs}_sortkeys"
+  &>
+  <%$bs%>_sortkeys= <% to_json_protecttags(\%ts_sortkeys) %>;
+  <%$bs%>_ages= <% to_json_protecttags(\%da_ages) %>;
+</&tabsort>
+%      } else {
+No offers.
+%      }
+
+<%perl>
+}
+</%perl>
+
+<p>
+(Please don't use these pages to scrape data out of the YARRG
+database.  This will be a pain for you to program, slow to run, and
+pointlessly overload our server.  Instead, see our
+<a href="devel">information for developers</a>
+to find out how to get testing data or a real-time feed.)
index ea483578d565d6df529dffcfddf7d1c6a5c1e9cf..62c2aab9ee55c664f0c19f4bf992cafcc009e552 100644 (file)
@@ -40,17 +40,19 @@ $prselector
 $routestring => '';
 $capacitystring => '';
 $lossperleague => '';
+$capitalstring => '';
 $someresults
 $emsgokorprint
 </%args>
 
 <%perl>
 my $emsg;
+my @warningfs;
 my @archipelagoes;
 my @islandids;
-my %islandid2;
 my ($max_volume, $max_mass);
 my $lossperleaguepct;
+my $capital;
 
 my $qa= \%ARGS;
 
@@ -67,41 +69,40 @@ my $goupdate= sub { $be_post ? 'Update' : 'Go' };
 
 <h1>Specify route</h1>
 
-% $prselector->('ShowStalls');
-
-%#---------- textbox, user enters route as string ----------
+% # Sadly we need to do this rather hacky thing to make it be a POST
+% #  form if the user has already selected some thing(s)
 % if (!$qa->{Dropdowns}) {
+%     $startform->($routestring =~ m/\S/);
+% } else {
+%     $startform->(grep {
+%              defined $qa->{"archipelago$_"} ||
+%              defined $qa->{"islandid$_"}
+%      } (0..$qa->{Dropdowns}-1));
+% }
 
-Enter route (islands, or archipelagoes, separated by |s or commas;
- abbreviations are OK):<br>
-
-% $startform->($routestring =~ m/\S/);
+% $prselector->('ShowStalls');
 
-<&| qtextstring, qa => $qa, dbh => $dbh,
-    thingstring => 'routestring', emsgstore => \$emsg,
-    perresult => sub {
-       my ($canonname, $island, $arch) = @_;
-       push @islandids, $island;
-       push @archipelagoes, defined $island ? undef : $arch;
-    }
+<& enter_route, qa => $qa, dbh => $dbh, emsg_r => \$emsg,
+       warningfs_r => \@warningfs,
+       enterwhat => 'Enter route',
+       islandids_r => \@islandids,
+       archipelagoes_r => \@archipelagoes
  &>
- size=80
-</&>
+
+%#---------- textboxes, user enters details as strings ----------
+% if (!$qa->{Dropdowns}) {
 
 <strong>Advanced options - you may leave these blank:</strong>
 <p>
-<table>
-<tr>
-<td>
+<table><tr><td>
 
 Vessel or capacity:
 <&| qtextstring, qa => $qa, dbh => $dbh, prefix => 'cs',
     thingstring => 'capacitystring', emsgstore => \$emsg,
-    perresult => sub {
-        ($max_volume,$max_mass) = @_;
-    }
+    helpref => 'capacity',
+    onresults => sub { ($max_mass,$max_volume) = @_; }
  &>
- size=30
+ size=40
 </&>
 
 <td>
@@ -113,104 +114,24 @@ Expected losses:
 
 <&| qtextstring, qa => $qa, dbh => $dbh, prefix => 'll',
     thingstring => 'lossperleague', emsgstore => \$emsg,
-    perresult => sub { ($lossperleaguepct)= @_; }
+    helpref => 'losses',
+    onresults => sub { ($lossperleaguepct)= @_; }
  &>
- size=10
+ size=9
 </&>
 
 </table>
+<table><tr>
 
-% } else { #---------- dropdowns, user selects from menus ----------
-
-% $startform->(grep {
-%              defined $ARGS{"archipelago$_"} ||
-%              defined $ARGS{"islandid$_"}
-%      } (0..$qa->{Dropdowns}-1));
-
-<%perl>
-my ($sth,$row);
-my @archlistdata;
-my %islandlistdata;
-$islandlistdata{'none'}= [ [ "none", "Select island..." ] ];
-
-my $optionlistmap= sub {
-       my ($optlist, $selected) = @_;
-       my $out='';
-       foreach my $entry (@$optlist) {
-               $out.= sprintf('<option value="%s" %s>%s</option>',
-                       encode_entities($entry->[0]),
-                       defined $selected && $entry->[0] eq $selected
-                               ? 'selected' : '',
-                       encode_entities($entry->[1]));
-       }
-       return $out;
-};
-
-$sth= $dbh->prepare("SELECT DISTINCT archipelago FROM islands
-                           ORDER BY archipelago;");
-$sth->execute();
-
-while ($row=$sth->fetchrow_arrayref) {
-       my ($arch)= @$row;
-       push @archlistdata, [ $arch, $arch ];
-       $islandlistdata{$arch}= [ [ "none", "Whole arch" ] ];
-}
-
-$sth= $dbh->prepare("SELECT islandid,islandname,archipelago
-                            FROM islands
-                           ORDER BY islandname;");
-$sth->execute();
+<td>Available capital:
 
-while ($row=$sth->fetchrow_arrayref) {
-       my $arch= $row->[2];
-       push @{ $islandlistdata{'none'} }, [ @$row ];
-       push @{ $islandlistdata{$arch} }, [ @$row ];
-       $islandid2{$row->[0]}= { Name => $row->[1], Arch => $arch };
-}
-
-my %resetislandlistdata;
-foreach my $arch (keys %islandlistdata) {
-       $resetislandlistdata{$arch}=
-               $optionlistmap->($islandlistdata{$arch}, '');
-}
-
-</%perl>
-
-<&| script &>
-ms_lists= <% to_json_protecttags(\%resetislandlistdata) %>;
-function ms_Setarch(dd) {
-  debug('ms_SetArch '+dd+' arch='+arch);
-  var arch= document.getElementsByName('archipelago'+dd).item(0).value;
-  var got= ms_lists[arch];
-  if (got == undefined) return; // unknown arch ?  hrm
-  debug('ms_SetArch '+dd+' arch='+arch+' got ok');
-  var select= document.getElementsByName('islandid'+dd).item(0);
-  select.innerHTML= got;
-  debug('ms_SetArch '+dd+' arch='+arch+' innerHTML set');
-}
-</&script>
-
-<table style="table-layout:fixed; width:90%;">
-
-<tr>
-%      for my $dd (0..$qa->{Dropdowns}-1) {
-<td>
-<select name="archipelago<% $dd %>" onchange="ms_Setarch(<% $dd %>)">
-<option value="none">Whole ocean</option>
-<% $optionlistmap->(\@archlistdata, $ARGS{"archipelago$dd"}) %></select></td>
-%      }
-</tr>
-
-<tr>
-%      for my $dd (0..$qa->{Dropdowns}-1) {
-%              my $arch= $ARGS{"archipelago$dd"};
-%              $arch= 'none' if !defined $arch;
-<td>
-<select name="islandid<% $dd %>">
-<% $optionlistmap->($islandlistdata{$arch}, $ARGS{"islandid$dd"}) %>
-</select></td>
-%      }
-</tr>
+<&| qtextstring, qa => $qa, dbh => $dbh, prefix => 'ac',
+    thingstring => 'capitalstring', emsgstore => \$emsg,
+    helpref => 'capital',
+    onresults => sub { ($capital)= @_; }
+ &>
+ size=9
+</&>
 
 </table>
 
@@ -218,7 +139,7 @@ function ms_Setarch(dd) {
 
 <input type=submit name=submit value="<% $goupdate->() %>">
 % my $ours= sub { $_[0] =~
-%  m/^island|^archipelago|^routestring|^capacitystring|^lossperleague|^[RT]/;
+%  m/^island|^archipelago|^routestring|^capacitystring|^lossperleague|^capitalstring|^[RT]/;
 % };
 <& "lookup:formhidden", ours => $ours &>
 
@@ -227,32 +148,9 @@ function ms_Setarch(dd) {
 
 $emsgokorprint->($emsg) or @islandids=();
 
-my $argorundef= sub {
-       my ($dd,$base) = @_;
-       my $thing= $ARGS{"${base}${dd}"};
-       $thing= undef if defined $thing and $thing eq 'none';
-       return $thing;
-};
-
-for my $dd (0..$qa->{Dropdowns}-1) {
-       my $arch= $argorundef->($dd,'archipelago');
-       my $island= $argorundef->($dd,'islandid');
-       next unless defined $arch or defined $island;
-       if (defined $island and defined $arch) {
-               my $ii= $islandid2{$island};
-               my $iarch= $ii->{Arch};
-               if ($iarch ne $arch) {
-                       $someresults->();
-</%perl>
- Specified archipelago <% $arch %> but
- island <% $ii->{Name} %>
- which is in <% $iarch %>; using the island.<br>
-<%perl>
-               }
-               $arch= undef;
-       }
-       push @archipelagoes, $arch;
-       push @islandids, $island;
+foreach my $warningf (@warningfs) {
+       $someresults->();
+       $warningf->();
 }
 
 </%perl>
@@ -266,7 +164,8 @@ for my $dd (0..$qa->{Dropdowns}-1) {
    qa => $qa,
    max_mass => $max_mass,
    max_volume => $max_volume,
-   lossperleaguepct => $lossperleaguepct
+   lossperleaguepct => $lossperleaguepct,
+   max_capital => $capital
  &>
-</form>
 % }
+</form>
index 397e3854372787170afe7e1861de55150aa6495a..d90bf40fb7afe7993c0f0924e4ea38c2e6dc2ba0 100644 (file)
@@ -41,6 +41,7 @@ $qa
 $max_mass
 $max_volume
 $lossperleaguepct
+$max_capital
 </%args>
 <&| script &>
   da_pageload= Date.now();
@@ -49,6 +50,7 @@ $lossperleaguepct
 <%perl>
 
 my $loss_per_league= defined $lossperleaguepct ? $lossperleaguepct*0.01 : 1e-7;
+my $loss_per_delay_slot= 1e-8;
 
 my $now= time;
 
@@ -67,42 +69,43 @@ my $sd_condition= sub {
        }
 };
 
-my %islandpair;
-# $islandpair{$a,$b}= [ $start_island_ix, $end_island_ix ]
-
 my $specific= !grep { !defined $_ } @islandids;
-my $confusing= 0;
 
-foreach my $src_i (0..$#islandids) {
-       my $src_isle= $islandids[$src_i];
-       my $src_cond= $sd_condition->('sell',$src_i);
+my %ipair2subflowinfs;
+# $ipair2subflowinfs{$orgi,$dsti}= [ [$orgix,$distix], ... ]
+
+my @subflows;
+# $subflows[0]{Flow} = { ... }
+# $subflows[0]{Org} = $orgix
+# $subflows[0]{Dst} = $dstix
+
+foreach my $org_i (0..$#islandids) {
+       my $org_isle= $islandids[$org_i];
+       my $org_cond= $sd_condition->('sell',$org_i);
        my @dst_conds;
-       foreach my $dst_i ($src_i..$#islandids) {
+       foreach my $dst_i ($org_i..$#islandids) {
                my $dst_isle= $islandids[$dst_i];
-               my $dst_cond= $sd_condition->('buy',$dst_i);
-               if ($dst_i==$src_i and !defined $src_isle) {
+               # Don't ever consider sailing things round the houses:
+               next if defined $dst_isle and
+                       grep { $dst_isle == $_ } @islandids[$org_i..$dst_i-1];
+               next if defined $org_isle and
+                       grep { $org_isle == $_ } @islandids[$org_i+1..$dst_i];
+               my $dst_cond;
+               if ($dst_i==$org_i and !defined $org_isle) {
                        # we always want arbitrage, but mentioning an arch
                        # once shouldn't produce intra-arch trades
-                       $dst_cond=
-                               "($dst_cond AND sell.islandid = buy.islandid)";
+                       $dst_cond= "sell.islandid = buy.islandid";
+               } else {
+                       $dst_cond= $sd_condition->('buy',$dst_i);
                }
                push @dst_conds, $dst_cond;
 
-               if ($specific && !$confusing &&
-                   # With a circular route, do not carry goods round the loop
-                   !(($src_i==0 || $src_i==$#islandids) &&
-                     $dst_i==$#islandids &&
-                     $src_isle == $islandids[$dst_i])) {
-                       if ($islandpair{$src_isle,$dst_isle}) {
-                               $confusing= 1;
-print "confusing $src_i $src_isle  $dst_i $dst_isle\n";
-                       } else {
-                               $islandpair{$src_isle,$dst_isle}=
-                                       [ $src_i, $dst_i ];
-                       }
+               if ($specific) {
+                       push @{ $ipair2subflowinfs{$org_isle,$dst_isle} },
+                               [ $org_i, $dst_i ];
                }
        }
-       push @flow_conds, "$src_cond AND (
+       push @flow_conds, "$org_cond AND (
                        ".join("
                     OR ",@dst_conds)."
                )";
@@ -241,7 +244,6 @@ foreach my $v (qw(MaxMass MaxVolume)) {
 
                $f= {
                        Ix => scalar(@flows),
-                       Var => "f".@flows,
                        %$got
                };
                $f->{"org_stallid"}= $f->{"dst_stallid"}= 'all'
@@ -264,6 +266,8 @@ foreach my $v (qw(MaxMass MaxVolume)) {
 
 <%perl>
 
+my @sail_total;
+
 if (!@flows) {
        print 'No profitable trading opportunities were found.';
        return;
@@ -282,6 +286,18 @@ foreach my $f (@flows) {
                $f->{"Max$v"}= sprintf "%.1f", $f->{"Max${v}SortKey"} * 1e-6;
        }
 
+       my $sfis= $ipair2subflowinfs{$f->{'org_id'},$f->{'dst_id'}};
+       foreach my $sfi (@$sfis) {
+               my $subflow= {
+                       Flow => $f,
+                       Org => $sfi->[0],
+                       Dst => $sfi->[1],
+                       Var => sprintf "f%ss%s", $f->{Ix}, $sfi->[0]
+               };
+               push @{ $f->{Subflows} }, $subflow;
+               push @subflows, $subflow;
+       }
+
        $f->{MarginSortKey}= sprintf "%d",
                $f->{'dst_price'} * 10000 / $f->{'org_price'};
        $f->{Margin}= sprintf "%3.1f%%",
@@ -311,7 +327,7 @@ foreach my $f (@flows) {
                my $first= $base;
                do {
                        my $this= $uue % $base;
-print STDERR "uue=$uue this=$this ";
+#print STDERR "uue=$uue this=$this ";
                        $uue -= $this;
                        $uue /= $base;
                        $this += $first;
@@ -319,8 +335,8 @@ print STDERR "uue=$uue this=$this ";
                        $cmpu .= chr($this + ($this < 26 ? ord('a') :
                                              $this < 52 ? ord('A')-26
                                                         : ord('0')-52));
-print STDERR " uue=$uue this=$this cmpu=$cmpu\n";
-die "$cmpu $uue ?" if length $cmpu > 20;
+#print STDERR " uue=$uue this=$this cmpu=$cmpu\n";
+                       die "$cmpu $uue ?" if length $cmpu > 20;
                } while ($uue);
                $cmpu;
        } @uid;
@@ -364,20 +380,13 @@ die "$cmpu $uue ?" if length $cmpu > 20;
 }
 </%perl>
 
-% my $optimise= $specific && !$confusing && @islandids>1;
+% my $optimise= $specific;
 % if (!$optimise) {
 
 <p>
-% if (@islandids<=1) {
-Route contains only one location.
-% }
 % if (!$specific) {
 Route contains archipelago(es), not just specific islands.
 % }
-% if ($confusing) {
-Route is complex - it visits the same island several times
-and isn't a simple loop.
-% }
 Therefore, optimal voyage trade plan not calculated.
 
 % } else { # ========== OPTMISATION ==========
@@ -387,24 +396,30 @@ my $cplex= "
 Maximize
 
   totalprofit:
-                  ".(join "
-                  ", map {
-                       sprintf "%+.20f %s", $_->{ExpectedUnitProfit}, $_->{Var}
-                       } @flows)."
+";
+
+foreach my $sf (@subflows) {
+       my $eup= $sf->{Flow}{ExpectedUnitProfit};
+       $eup *= (1.0-$loss_per_delay_slot) ** $sf->{Org};
+       $cplex .= sprintf "
+               %+.20f %s", $eup, $sf->{Var};
+}
+$cplex .= "
 
 Subject To
 ";
 
-my %avail_csts;
+my %avail_lims;
 foreach my $flow (@flows) {
        if ($flow->{Suppress}) {
-               $cplex .= "
-   $flow->{Var} = 0
-";
+               foreach my $sf (@{ $flow->{Subflows} }) {
+                       $cplex .= "
+   $sf->{Var} = 0";
+               }
                next;
        }
        foreach my $od (qw(org dst)) {
-               my $cstname= join '_', (
+               my $limname= join '_', (
                        'avail',
                        $flow->{'commodid'},
                        $od,
@@ -412,57 +427,72 @@ foreach my $flow (@flows) {
                        $flow->{"${od}_price"},
                        $flow->{"${od}_stallid"},
                );
-                       
-               push @{ $avail_csts{$cstname}{Flows} }, $flow->{Var};
-               $avail_csts{$cstname}{Qty}= $flow->{"${od}_qty_agg"};
+
+               push @{ $avail_lims{$limname}{SubflowVars} },
+                       map { $_->{Var} } @{ $flow->{Subflows} };
+               $avail_lims{$limname}{Qty}= $flow->{"${od}_qty_agg"};
        }
 }
-foreach my $cstname (sort keys %avail_csts) {
-       my $c= $avail_csts{$cstname};
-       $cplex .= "
-   ". sprintf("%-30s","$cstname:")." ".
-       join("+", @{ $c->{Flows} }).
-       " <= ".$c->{Qty}."\n";
+foreach my $limname (sort keys %avail_lims) {
+       my $c= $avail_lims{$limname};
+       $cplex .=
+               sprintf("    %-30s","$limname:")." ".
+                       join("+", @{ $c->{SubflowVars} }).
+                       " <= ".$c->{Qty}."\n";
 }
 
 foreach my $ci (0..($#islandids-1)) {
-       my @rel_flows;
+       my @rel_subflows;
+
        foreach my $f (@flows) {
                next if $f->{Suppress};
-               next if $f->{'org_id'} == $f->{'dst_id'};
-               next unless grep { $f->{'org_id'} == $_ }
-                       @islandids[0..$ci];
-               next unless grep { $f->{'dst_id'} == $_ }
-                       @islandids[$ci+1..@islandids-1];
-               push @rel_flows, $f;
-#print " RELEVANT $ci $f->{Ix}  ";
+               my @relsubflow= grep {
+                       $_->{Org} <= $ci &&
+                       $_->{Dst} > $ci;
+               } @{ $f->{Subflows} };
+               next unless @relsubflow;
+               die unless @relsubflow == 1;
+               push @rel_subflows, @relsubflow;
+#print " RELEVANT $ci $relsubflow[0]->{Var} ";
        }
-#print " RELEVANT $ci COUNT ".scalar(@rel_flows)."  ";
-       next unless @rel_flows;
-       foreach my $mv (qw(mass volume)) {
-               my $max_vn= "max_$mv";
-               my $max= $mv eq 'mass' ? $max_mass : $max_volume;
-               next unless defined $max;
+#print " RELEVANT $ci COUNT ".scalar(@rel_subflows)."  ";
+       if (!@rel_subflows) {
+               foreach my $mv (qw(mass volume)) {
+                       $sail_total[$ci]{$mv}= 0;
+               }
+               next;
+       }
+
+       my $applylimit= sub {
+               my ($mv, $max, $f2val) = @_;
+               $max= 1e9 unless defined $max;
 #print " DEFINED MAX $mv $max ";
                $cplex .= "
    ". sprintf("%-10s","${mv}_$ci:")." ".
-       join(" + ", map { ($_->{"unit$mv"}*1e-3).' f'.$_->{Ix} } @rel_flows).
-       " <= $max";
-       }
+               join(" + ", map {
+#print " PART MAX $_->{Var} $_->{Flow}{Ix} ";
+                       $f2val->($_->{Flow}) .' '. $_->{Var};
+               } @rel_subflows).
+               " <= $max";
+       };
+
+       $applylimit->('mass',   $max_mass,   sub { $_[0]{'unitmass'}  *1e-3 });
+       $applylimit->('volume', $max_volume, sub { $_[0]{'unitvolume'}*1e-3 });
+       $applylimit->('capital',$max_capital,sub { $_[0]{'org_price'}       });
        $cplex.= "\n";
 }
 
 $cplex.= "
 Bounds
         ".(join "
-        ", map { "$_->{Var} >= 0" } @flows)."
+        ", map { "$_->{Var} >= 0" } @subflows)."
 
 ";
 
 $cplex.= "
 Integer
        ".(join "
-       ", map { "f$_" } (0..$#flows))."
+       ", map { $_->{Var} } @subflows)."
 
 End
 ";
@@ -479,38 +509,61 @@ if ($qa->{'debug'}) {
        my $input= pipethrough_prep();
        print $input $cplex or die $!;
        my $output= pipethrough_run_along($input, undef, 'glpsol',
-               qw(glpsol --cpxlp /dev/stdin -o /dev/stdout));
+               qw(glpsol --tmlim 2 --memlim 5 --intopt --cuts --bfs
+                         --cpxlp /dev/stdin -o /dev/stdout));
        print "<pre>\n" if $qa->{'debug'};
        my $found_section= 0;
        my $glpsol_out= '';
+       my $continuation='';
        while (<$output>) {
                $glpsol_out.= $_;
                print encode_entities($_) if $qa->{'debug'};
-               if (m/^\s*No\.\s+Column name\s+(?:St\s+)?Activity\s/) {
-                       die if $found_section>0;
+               if (m/^\s*No\.\s+(Row|Column) name\s+(?:St\s+)?Activity\s/) {
+                       die "$_ $found_section ?" if $found_section>0;
                        $found_section= 1;
                        next;
                }
                next unless $found_section==1;
-               next if m/^[- ]+$/;
-               if (!/\S/) {
-                       $found_section= 2;
-                       next;
+               if (!length $continuation) {
+                       next if !$continuation &&  m/^[- ]+$/;
+                       if (!/\S/) {
+                               $found_section= 0;
+                               next;
+                       }
+                       if (m/^ \s* \d+ \s+ \w+ $/x) {
+                               $continuation= $&;
+                               next;
+                       }
+               }
+               $_= $continuation.$_;
+               $continuation= '';
+               my ($varname, $qty) = m/^
+                       \s* \d+ \s+
+                       (\w+) \s+ (?: [A-Z*]+ \s+ )?
+                       ([0-9.]+) \s
+                       /x or die "$_ ?";
+               if ($varname =~ m/^f(\d+)s(\d+)$/) {
+                       my ($ix,$orgix) = ($1,$2);
+                       my $flow= $flows[$ix] or die;
+                       my @relsubflow= grep { $_->{Org} == $orgix }
+                               @{ $flow->{Subflows} };
+                       die "$ix $orgix @relsubflow" unless @relsubflow == 1;
+                       my $sf= $relsubflow[0];
+                       $sf->{OptQty}= $qty;
+                       $sf->{OptProfit}= $qty * $flow->{'unitprofit'};
+                       $sf->{OptCapital}= $qty * $flow->{'org_price'};
+               } elsif ($varname =~ m/^(mass|volume)_(\d+)$/) {
+                       my ($mv,$ix) = ($1,$2);
+                       $sail_total[$ix]{$mv}= $qty;
                }
-               my ($ix, $qty) =
-                       m/^\s*\d+\s+f(\d+)\s+\S+\s+(\d+)\s/ or die "$_ ?";
-               my $flow= $flows[$ix] or die;
-               $flow->{OptQty}= $qty;
-               $flow->{OptProfit}= $flow->{'unitprofit'} * $qty;
-               $flow->{OptCapital}= $flow->{OptQty} * $flow->{'org_price'};
        }
        print "</pre>\n" if $qa->{'debug'};
        my $prerr= "\n=====\n$cplex\n=====\n$glpsol_out\n=====\n ";
        pipethrough_run_finish($output,$prerr);
-       die $prerr unless $found_section;
+       map { defined $_->{OptQty} or die "$prerr $_->{Flow}{Ix}" } @subflows;
 };
 
-$addcols->({ DoReverse => 1, Special => sub {
+$addcols->({ DoReverse => 1, TotalSubflows => 1, Special => sub {
        my ($flow,$col,$v,$spec) = @_;
        if ($flow->{ExpectedUnitProfit} < 0) {
                $spec->{Span}= 3;
@@ -520,7 +573,7 @@ $addcols->({ DoReverse => 1, Special => sub {
 } }, qw(
                OptQty
        ));
-$addcols->({ Total => 0, DoReverse => 1 }, qw(
+$addcols->({ Total => 0, DoReverse => 1, TotalSubflows => 1 }, qw(
                OptCapital OptProfit
        ));
 
@@ -603,7 +656,14 @@ $addcols->({ Total => 0, DoReverse => 1 }, qw(
 %                      Span => 1,
 %                      Align => ($col->{Text} ? '' : 'align=right')
 %              };
-%              my $v= $flow->{$col->{Name}};
+%              my $cn= $col->{Name};
+%              my $v;
+%              if (!$col->{TotalSubflows}) {
+%                      $v= $flow->{$cn};
+%              } else {
+%                      $v= 0;
+%                      $v += $_->{$cn} foreach @{ $flow->{Subflows} };
+%              }
 %              if ($col->{Special}) { $col->{Special}($flow,$col,$v,$spec); }
 %              $col->{Total} += $v
 %                      if defined $col->{Total} and not $flow->{Suppress};
@@ -648,7 +708,7 @@ $addcols->({ Total => 0, DoReverse => 1 }, qw(
 <table rules=groups>
 % foreach my $i (0..$#islandids) {
 <tbody>
-<tr><td colspan=3>
+<tr><td colspan=4>
 %      $iquery->execute($islandids[$i]);
 %      my ($islandname) = $iquery->fetchrow_array();
 %      if (!$i) {
@@ -656,22 +716,34 @@ $addcols->({ Total => 0, DoReverse => 1 }, qw(
 %      } else {
 %              my $this_dist= $distance->($islandids[$i-1],$islandids[$i]);
 %              $total_dist += $this_dist;
+<%perl>
+               my $total_value= 0;
+               foreach my $sf (@subflows) {
+                       next unless $sf->{Org} < $i && $sf->{Dst} >= $i;
+                       $total_value +=
+                               $sf->{OptQty} * $sf->{Flow}{'dst_price'};
+               }
+</%perl>
 <strong>Sail to <% $islandname |h %></strong>
-- <% $this_dist |h %> leagues </td>
+- <% $this_dist |h %> leagues,
+ <% $total_value %>poe at risk
+ </td>
 %      }
 <%perl>
      my $age_reported= 0;
      my %flowlists;
+     #print "<tr><td colspan=6>" if $qa->{'debug'};
      foreach my $od (qw(org dst)) {
-       foreach my $f (@flows) {
+       #print " [[ i $i od $od " if $qa->{'debug'};
+       foreach my $sf (@subflows) {
+               my $f= $sf->{Flow};
                next if $f->{Suppress};
-               next unless $f->{"${od}_id"} == $islandids[$i];
-               next unless $f->{OptQty};
+               next unless $sf->{ucfirst $od} == $i;
+               #print " FLOW $f->{Ix} SUB #$sf->{Org}..$sf->{Dst} "
+               #       if $qa->{'debug'};
+               next unless $sf->{OptQty};
                my $arbitrage= $f->{'org_id'} == $f->{'dst_id'};
-               my $loop= $islandids[0] == $islandids[-1] &&
-                         ($i==0 || $i==$#islandids);
-               next if $loop and ($arbitrage ? $i :
-                       !!$i == !!($od eq 'org'));
+               die if $arbitrage and $sf->{Org} != $sf->{Dst};
                my $price= $f->{"${od}_price"};
                my $stallname= $f->{"${od}_stallname"};
                my $todo= \$flowlists{$od}{
@@ -689,29 +761,54 @@ $addcols->({ Total => 0, DoReverse => 1 }, qw(
                $$todo->{'stallname'}= $stallname;
                $$todo->{Price}= $price;
                $$todo->{Timestamp}= $f->{"${od}_timestamp"};
-               $$todo->{Qty} += $f->{OptQty};
+               $$todo->{Qty} += $sf->{OptQty};
                $$todo->{Total}= $$todo->{Price} * $$todo->{Qty};
                $$todo->{Stalls}= $f->{"${od}Stalls"};
                $$todo->{"${od}Arbitrage"}= 1 if $arbitrage;
        }
+       #print "]] " if $qa->{'debug'};
      }
+     #print "</tr>" if $qa->{'debug'};
 
-     my $total;
+     my ($total, $total_to_show);
      my $dline= 0;
-     my $show_flows= sub {
-       my ($od,$arbitrage,$collectdeliver) = @_;
+     my $show_total= sub {
+       my ($totaldesc, $sign) = @_;
+       if (defined $total) {
+               die if defined $total_to_show;
+               $total_total += $sign * $total;
+               $total_to_show= [ $totaldesc, $total ];
+               $total= undef;
+       }
+       $dline= 0;
+     };
+     my $show_total_now= sub {
+       my ($xinfo) = @_;
+       return unless defined $total_to_show;
+       my ($totaldesc,$totalwas) = @$total_to_show;
 </%perl>
-%
+<tr>
+<td colspan=1>
+<td colspan=2><% $xinfo %>
+<td colspan=2 align=right><% $totaldesc %>
+<td align=right><% $totalwas |h %> total
+<%perl>
+       $total_to_show= undef;
+     };
+</%perl>
+%    my $show_flows= sub {
+%      my ($od,$arbitrage,$collectdeliver) = @_;
 %      my $todo= $flowlists{$od};
 %      return unless $todo;
 %      foreach my $tkey (sort keys %$todo) {
 %              my $t= $todo->{$tkey};
 %              next if $t->{"${od}Arbitrage"} != $arbitrage;
+%              $show_total_now->('');
 %              if (!$age_reported++) {
 %                      my $age= $now - $t->{Timestamp};
 %                      my $cellid= "da_${i}";
 %                      $da_ages{$cellid}= $age;
-<td colspan=3>\
+<td colspan=2>\
 (Data age: <span id="<% $cellid %>"><% prettyprint_age($age) %></span>)
 %              } elsif (!defined $total) {
 %                      $total= 0;
@@ -743,25 +840,29 @@ $addcols->({ Total => 0, DoReverse => 1 }, qw(
 %              $dline ^= 1;
 %      }
 %    };
-%    my $show_total= sub {
-%      my ($totaldesc, $sign)= @_;
-%      if (defined $total) {
-<tr>
-<td colspan=3>
-<td colspan=2 align=right><% $totaldesc %>
-<td align=right><% $total |h %> total
-%              $total_total += $sign * $total;
-%      }
-%      $total= undef;
-%      $dline= 0;
 <%perl>
-     };
 
      $show_flows->('dst',0,'Deliver'); $show_total->('Proceeds',1);
      $show_flows->('org',1,'Collect'); $show_total->('(Arbitrage) outlay',-1);
      $show_flows->('dst',1,'Deliver'); $show_total->('(Arbitrage) proceeds',1);
      $show_flows->('org',0,'Collect'); $show_total->('Outlay',-1);
-
+     my $totals= '';
+     if ($i < $#islandids) {
+       $totals .=      "In hold $sail_total[$i]{mass}kg,".
+                       " $sail_total[$i]{volume} l";
+       my $delim= '; spare ';
+       my $domv= sub {
+               my ($max, $got, $units) = @_;
+               return unless defined $max;
+               $totals .= $delim;
+               $totals .= sprintf "%g %s", ($max-$got), $units;
+               $delim= ', ';
+       };
+       $domv->($max_mass,   $sail_total[$i]{mass},   'kg');
+       $domv->($max_volume, $sail_total[$i]{volume}, 'l');
+       $totals .= ".\n";
+     }
+     $show_total_now->($totals);
 }
 </%perl>
 <tbody><tr>
index e114319a0254f4780bfcaa6071093d39f016f23f..45d5e69e3763f56cbd2a1e1b888ad26261094116 100644 (file)
@@ -56,7 +56,7 @@ $cols
 
 % print $m->content();
 
-%      my $sortfn= "ts_sort__$table";
+%      my $sortfn= "ts_s_$table";
 function <% $sortfn %>(compar) {
   debug('sorting compar='+compar);
   var table= document.getElementById('<% $table %>');
@@ -115,9 +115,9 @@ function <% $sortfn %>(compar) {
 %      my $thhtml= '';
 %      next if $col->{NoSort};
 
-%      my $mapfn= "ts_compar${cix}_map__$table";
-function <% $mapfn %>(rowelement) {
-  var rowid = rowelement.id;
+%      my $mapfn= "ts_${cix}m_$table";
+function <% $mapfn %>(re) {
+  var rowid = re.id;
 %      if ($col->{SortKey}) {
   return <% $col->{SortKey} %>;
 %      } else {
@@ -130,24 +130,25 @@ function <% $mapfn %>(rowelement) {
 %      }
 }
 
-%      my $comparefn= "ts_compar${cix}_cmp0__$table";
+%      my $comparefn= "ts_${cix}c0_$table";
 function <% $comparefn %>(a,b) {
-  var a_key = <% $mapfn %>(a);
-  var b_key = <% $mapfn %>(b);
+  var ak = <% $mapfn %>(a);
+  var bk = <% $mapfn %>(b);
 %      if ($col->{Numeric}) {
-  return a_key - b_key
+  return ak - bk
 %      } else {
-  if (a_key < b_key) return -1;
-  if (a_key > b_key) return +1;
+  if (ak < bk) return -1;
+  if (ak > bk) return +1;
   return 0;
 %      }
 }
 
 %      foreach my $reverse (qw(1 0)) {
-%              my $tcomparefn= "ts_compar${cix}_cmp${reverse}__$table";
+%              my $tcomparefn= "ts_${cix}c${reverse}_$table";
 %              if ($reverse) {
 %                      next unless $col->{DoReverse};
 function <% $tcomparefn %>(a,b) { return -<% $comparefn %>(a,b); }
+
 %              }
 %              $thhtml .= "<a href=\"javascript:$sortfn($tcomparefn)\">".
 %                              ($reverse ? '&or;' : '&and;'). '</a>';