From: Ian Jackson Date: Tue, 20 Oct 2009 18:32:15 +0000 (+0100) Subject: Merge branch 'stable-3.x' X-Git-Tag: 5.0^2~6 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~yarrgweb/git?a=commitdiff_plain;h=c9225ff45ee5e69855cb24cfb648d903dbba54a7;hp=ce2957283c84ae291297359f8f2b1b5f0e5fbf20;p=ypp-sc-tools.db-test.git Merge branch 'stable-3.x' --- diff --git a/.gitignore b/.gitignore index a058839..9beb60b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ yarrg/t.* yarrg/u.* yarrg/yarrg +yarrg/routesearch yarrg/_*.* yarrg/OCEAN-*.db diff --git a/yarrg/Commods.pm b/yarrg/Commods.pm index 59ad3e1..372fe16 100644 --- a/yarrg/Commods.pm +++ b/yarrg/Commods.pm @@ -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); } diff --git a/yarrg/CommodsDatabase.pm b/yarrg/CommodsDatabase.pm index 79744ce..c72bc2d 100644 --- a/yarrg/CommodsDatabase.pm +++ b/yarrg/CommodsDatabase.pm @@ -45,7 +45,7 @@ BEGIN { @ISA = qw(Exporter); @EXPORT = qw(&db_setocean &db_writer &db_connect $dbh &db_filename &db_doall &db_onconflict - &dbr_filename &dbr_connect); + &dbr_filename &dbr_connect &db_connect_core); %EXPORT_TAGS = ( ); @EXPORT_OK = qw(); @@ -60,7 +60,7 @@ sub dbr_connect ($$) { return connect_core(dbr_filename($datadir,$ocean)); } -sub connect_core ($) { +sub db_connect_core ($) { my ($fn)= @_; my $h= DBI->connect("dbi:SQLite:$fn",'','', { AutoCommit=>0, diff --git a/yarrg/CommodsWeb.pm b/yarrg/CommodsWeb.pm index 198185d..ab2a4a3 100644 --- a/yarrg/CommodsWeb.pm +++ b/yarrg/CommodsWeb.pm @@ -48,9 +48,11 @@ BEGIN { our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); $VERSION = 1.00; @ISA = qw(Exporter); - @EXPORT = qw(&dbw_connect &ocean_list &sourcebasedir + @EXPORT = qw(&dbw_connect &dbw_filename &ocean_list &sourcebasedir &to_json_shim &to_json_protecttags - &set_ctype_utf8 + &set_ctype_utf8 &webdatadir + &expected_error &dbw_lookup_string + &printable &tr_datarow &prettyprint_age &meta_prettyprint_age); %EXPORT_TAGS = ( ); @@ -72,20 +74,25 @@ sub sourcebasedir () { return dotperllibdir().'/..'; } -sub datadir () { - my $edir= $ENV{'YARRG_DATA_DIR'}; +sub some_datadir ($) { + my ($what) = @_; + my $edir= $ENV{"YARRG_${what}_DIR"}; return $edir if defined $edir; my $dir= dotperllibdir(); - if (stat "$dir/DATA") { - return "$dir/DATA"; + my $dirwhat= "$dir/$what"; + if (stat $dirwhat) { + return $dirwhat; } elsif ($!==&ENOENT) { return "$dir"; } else { - die "stat $dir/DATA $!"; + die "stat $dirwhat $!"; } return '.'; } +sub webdatadir () { return some_datadir('WEBDATA'); } +sub datadir () { return some_datadir('DATA'); } + my @ocean_list; sub ocean_list () { @@ -105,11 +112,17 @@ sub ocean_list () { return @ocean_list; } -sub dbw_connect ($) { +sub dbw_filename ($) { my ($ocean) = @_; die "unknown ocean $ocean ?" unless grep { $_ eq $ocean } ocean_list(); - return dbr_connect(datadir(), $ocean); + return dbr_filename(datadir(), $ocean); +} + +sub dbw_connect ($) { + my ($ocean) = @_; + my $fn= dbw_filename($ocean); + return db_connect_core($fn); } sub to_json_shim ($) { @@ -151,4 +164,69 @@ 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; +} + +sub printable ($) { # printable($m) where $m is the Mason request object + my ($m) = @_; + my $a= scalar $m->caller_args(-1); + foreach my $t (qw(pdf ps html pdf2 ps2)) { + return $t if $a->{"printable_$t"}; + } + return 0; +} + +sub tr_datarow ($$) { + my ($m, $lineno) = @_; + $lineno &= 1; + if (!printable($m)) { + $m->print(""); + } else { + $m->print(""); + } +} + +package CommodsWeb::ExpectedError; + +sub emsg ($) { + my ($self) = @_; + return $self->{Emsg}; +} + 1; diff --git a/yarrg/Makefile b/yarrg/Makefile index 446ef98..a9c833b 100644 --- a/yarrg/Makefile +++ b/yarrg/Makefile @@ -33,14 +33,23 @@ CFLAGS += $(WARNINGS) $(WERROR) $(OPTIMISE) $(DEBUG) TARGETS= yarrg -all: clean-other-directory $(TARGETS) +default: clean-other-directory $(TARGETS) +all: default routesearch -CONVERT_OBJS= convert.o ocr.o pages.o structure.o common.o rgbimage.o resolve.o +CONVERT_OBJS= convert.o ocr.o pages.o structure.o rgbimage.o resolve.o +COMMON_OBJS= common.o +ROUTESEARCH_OBJS= rsvalue.o rsmain.o rssql.o rssearch.o -yarrg: $(CONVERT_OBJS) -lnetpbm -lXtst -lX11 -lpcre -lm +yarrg: $(CONVERT_OBJS) $(COMMON_OBJS) -lnetpbm -lXtst -lX11 -lpcre -lm $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) -$(CONVERT_OBJS): ocr.h convert.h structure.h common.h +$(CONVERT_OBJS): common.h ocr.h convert.h structure.h + +routesearch: $(ROUTESEARCH_OBJS) $(COMMON_OBJS) -lsqlite3 -lglpk -lm + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +$(ROUTESEARCH_OBJS): common.h rscommon.h +$(COMMON_OBJS): common.h clean: rm -f *.o core core.* *~ vgcore.* diff --git a/yarrg/README b/yarrg/README index 0a84f5c..d5062ac 100644 --- a/yarrg/README +++ b/yarrg/README @@ -48,12 +48,14 @@ Options to vary the processing: --test-servers Set default servers to be the test servers, not the real live ones (doesn't affect explicit settings). -Controlling what happens to the results - only one at a time: - --upload (default) Upload to the YARRG and PCTB servers - --tsv Print data as clean tab-separated-values file - --raw-tsv Dump the raw (not deduped, unsorted) OCR'd data - --best-prices Print best buy and sell price for each commodity - --arbitrage Print arbitrage opportunities +Controlling what happens to the results - one or more: + --upload (default) Upload to both the YARRG and PCTB servers + --upload-pctb Upload to the PCTB servers + --upload-yarrg Upload to the YARRG servers + --tsv Print data as clean tab-separated-values file + --raw-tsv Dump the raw (not deduped, unsorted) OCR'd data + --best-prices Print best buy and sell price for each commodity + --arbitrage Print arbitrage opportunities Privacy options, which control conversations with the dictionary server: --dict-local-only * Do not talk to the server even to fetch new dictionary. @@ -172,6 +174,7 @@ for assisting players of Yohoho Puzzle Pirates. ypp-sc-tools and YARRG are Copyright (C) 2009 Ian Jackson 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 diff --git a/yarrg/TODO b/yarrg/TODO index 6b227eb..8e85c93 100644 --- a/yarrg/TODO +++ b/yarrg/TODO @@ -1,56 +1,30 @@ 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 - - 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 - - adjustable potential cost of losses (rather than fixed - 1e-BIG per league) - use power formula (compound interest) - suggest 0.5% - - 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 +allow unticking based on minimum margin or minimum profit -C needs ypp client and network connection -N needs network connection -W needs to be done by someone with Windows +initial/final stocks feature -D dependencies unsatisfied +query_routesearch should show capital for each voyage +query_routesearch should support ending in specific place(s) diff --git a/yarrg/common.c b/yarrg/common.c index cc33235..35b8969 100644 --- a/yarrg/common.c +++ b/yarrg/common.c @@ -33,6 +33,13 @@ void *mmalloc(size_t sz) { sysassert( r= malloc(sz) ); return r; } +void *mcalloc(size_t sz) { + void *r; + if (!sz) return 0; + sysassert( r= malloc(sz) ); + memset(r, 0, sz); + return r; +} void *mrealloc(void *p, size_t sz) { assert(sz); void *r; @@ -40,129 +47,123 @@ void *mrealloc(void *p, size_t sz) { return r; } +DEFINE_VWRAPPERF(, progress, ) +DEFINE_VWRAPPERF(, progress_log, ) +DEFINE_VWRAPPERF(, progress_spinner, ) +DEFINE_VWRAPPERF(, warning, ) +DEFINE_VWRAPPERF(, fatal, NORET) -FILE *dbfile; -static const char *basepath; /* as passed in by caller */ -static pid_t dbzcat; - -int dbfile_gzopen(const char *basepath_spec) { - assert(!dbfile); - - basepath= basepath_spec; +static int last_progress_len; - char *zpath= masprintf("%s.gz", basepath); - int e= gzopen(zpath, O_RDONLY, &dbfile, &dbzcat, 0); - free(zpath); - if (e) { errno=e; sysassert(errno==ENOENT); return 0; } +static void vprogress_core(int spinner, const char *fmt, va_list al) { + int r; - return 1; -} + if (o_quiet) return; + if (!debug_flags && !isatty(2)) return; + + if (last_progress_len) + putc('\r',stderr); -int dbfile_open(const char *tpath) { - assert(!dbfile); + r= vfprintf(stderr,fmt,al); - basepath= tpath; + if (spinner) { + putc(spinner,stderr); + r++; + } - dbzcat= -1; - dbfile= fopen(tpath,"r"); - if (!dbfile) { sysassert(errno==ENOENT); return 0; } - return 1; -} + if (r < last_progress_len) { + fprintf(stderr,"%*s", last_progress_len - r, ""); + if (!r) putc('\r', stderr); + else while (last_progress_len-- > r) putc('\b',stderr); + } + last_progress_len= r; -void dbfile_close(void) { - gzclose(&dbfile, &dbzcat, basepath); + if (ferror(stderr) || fflush(stderr)) _exit(16); } - -#define dbassertgl(x) ((x) ? (void)0 : dbfile_assertfail(file,line,#x)) - -void dbfile_getsline(char *lbuf, size_t lbufsz, const char *file, int line) { - errno=0; - char *s= fgets(lbuf,lbufsz,dbfile); - sysassert(!ferror(dbfile)); - dbassertgl(!feof(dbfile)); - assert(s); - int l= strlen(lbuf); - dbassertgl(l>0); dbassertgl(lbuf[--l]=='\n'); - lbuf[l]= 0; + +void vprogress(const char *fmt, va_list al) { vprogress_core(0,fmt,al); } +void vprogress_spinner(const char *fmt, va_list al) { + static const char spinchars[]="/-\\"; + static int spinner; + + vprogress_core(spinchars[spinner],fmt,al); + spinner++; + spinner %= (sizeof(spinchars)-1); } -int dbfile_vscanf(const char *fmt, va_list al) { - int r= vfscanf(dbfile,fmt,al); - sysassert(!ferror(dbfile)); - return r; +void vprogress_log(const char *fmt, va_list al) { + if (o_quiet) return; + + progress(""); + vfprintf(stderr,fmt,al); + putc('\n',stderr); + fflush(stderr); } -int dbfile_scanf(const char *fmt, ...) { - va_list al; - va_start(al,fmt); - int r= dbfile_vscanf(fmt,al); - va_end(al); - return r; +void vwarning(const char *fmt, va_list al) { + progress(""); + fputs("Warning: ",stderr); + vfprintf(stderr,fmt,al); + fputs("\n",stderr); + fflush(stderr); } -void dbfile_assertfail(const char *file, int line, const char *m) { - if (dbzcat) - fatal("Error in dictionary file %s.gz:\n" - " Requirement not met at %s:%d:\n" - " %s", - basepath, file,line, m); - else if (dbfile) - fatal("Error in dictionary file %s at byte %ld:\n" - " Requirement not met at %s:%d:\n" - " %s", - basepath,(long)ftell(dbfile), file,line, m); - else - fatal("Semantic error in dictionaries:\n" - " Requirement not met at %s:%d:\n" - " %s", - file,line, m); +void vfatal(const char *fmt, va_list al) { + progress(""); + fputs("\n\nFatal error: ",stderr); + vfprintf(stderr,fmt,al); + fflush(stderr); + fputs("\n\n",stderr); + _exit(4); } -int gzopen(const char *zpath, int oflags, FILE **f_r, pid_t *pid_r, - const char *gziplevel /* 0 for read; may be 0, or "-1" etc. */) { - - int zfd= open(zpath, oflags, 0666); - if (zfd<0) return errno; - - int pipefds[2]; - sysassert(! pipe(pipefds) ); +void sysassert_fail(const char *file, int line, const char *what) { + int e= errno; + progress(""); + fprintf(stderr, + "\nfatal operational error:\n" + " unsuccessful execution of: %s\n" + " %s:%d: %s\n\n", + what, file,line, strerror(e)); + _exit(16); +} - int oi,io; const char *cmd; const char *stdiomode; - switch ((oflags & O_ACCMODE)) { - case O_RDONLY: oi=0; io=1; cmd="gunzip"; stdiomode="r"; break; - case O_WRONLY: oi=1; io=0; cmd="gzip"; stdiomode="w"; break; - default: abort(); +void waitpid_check_exitstatus(pid_t pid, const char *what, int sigpipeok) { + pid_t got; + int st; + for (;;) { + got= waitpid(pid, &st, 0); + if (pid==-1) { sysassert(errno==EINTR); continue; } + break; } - - sysassert( (*pid_r=fork()) != -1 ); - if (!*pid_r) { - sysassert( dup2(zfd,oi)==oi ); - sysassert( dup2(pipefds[io],io)==io ); - sysassert(! close(zfd) ); - sysassert(! close(pipefds[0]) ); - sysassert(! close(pipefds[1]) ); - execlp(cmd,cmd,gziplevel,(char*)0); - sysassert(!"execlp gzip/gunzip"); + sysassert( got==pid ); + + if (WIFEXITED(st)) { + if (WEXITSTATUS(st)) + fatal("%s failed with nonzero exit status %d", + what, WEXITSTATUS(st)); + } else if (WIFSIGNALED(st)) { + if (!sigpipeok || WTERMSIG(st) != SIGPIPE) + fatal("%s died due to signal %s%s", what, + strsignal(WTERMSIG(st)), WCOREDUMP(st)?" (core dumped)":""); + } else { + fatal("%s gave strange wait status %d", what, st); } - sysassert(! close(zfd) ); - sysassert(! close(pipefds[io]) ); - sysassert( *f_r= fdopen(pipefds[oi], stdiomode) ); +} - return 0; +char *masprintf(const char *fmt, ...) { + char *r; + va_list al; + va_start(al,fmt); + sysassert( vasprintf(&r,fmt,al) >= 0); + sysassert(r); + va_end(al); + return r; } -void gzclose(FILE **f, pid_t *p, const char *what) { - if (!*f) return; - - sysassert(!ferror(*f)); - sysassert(!fclose(*f)); - - if (*p != -1) { - char *process= masprintf("%s (de)compressor",what); - waitpid_check_exitstatus(*p,process,1); - free(process); - *p= -1; - } +unsigned debug_flags; - *f= 0; +void debug_flush(void) { + sysassert(!ferror(debug)); + sysassert(!fflush(debug)); } diff --git a/yarrg/common.h b/yarrg/common.h index 9d06fab..5decee2 100644 --- a/yarrg/common.h +++ b/yarrg/common.h @@ -39,12 +39,10 @@ #include #include #include +#include #include #include #include -#include - -#include #include #include @@ -71,19 +69,7 @@ typedef struct { /* both inclusive */ #define RECT_W(r) ((r).br.x - (r).tl.x + 1) #define RECT_H(r) ((r).br.y - (r).tl.y + 1) - - -#define DEBUG_FLAG_LIST \ - DF(findypp) \ - DF(pages) \ - DF(rect) \ - DF(pixmap) \ - DF(struct) \ - DF(ocr) \ - DF(rsync) \ - DF(structcolon) \ - DF(callout) - +#ifdef DEBUG_FLAG_LIST enum { #define DF(f) dbg__shift_##f, DEBUG_FLAG_LIST @@ -94,16 +80,18 @@ enum { DEBUG_FLAG_LIST #undef DF }; +#define DEBUGP(f) (!!(debug_flags & dbg_##f)) -unsigned debug_flags; +#endif /*DEBUG_FLAG_LIST*/ -#define DEBUGP(f) (!!(debug_flags & dbg_##f)) +#ifndef debug_flags +extern unsigned debug_flags; +#endif void debug_flush(void); -#define debug stderr - -const char *get_vardir(void); -const char *get_libdir(void); +#ifndef debug +# define debug stderr +#endif #define FMT(f,a) __attribute__((format(printf,f,a))) #define SCANFMT(f,a) __attribute__((format(scanf,f,a))) @@ -127,6 +115,20 @@ const char *get_libdir(void); /*---------- error handling ----------*/ +extern int o_quiet; + +void vwarning(const char *fmt, va_list) FMT(1,0); +void warning(const char *fmt, ...) FMT(1,2); + +void vprogress(const char *fmt, va_list) FMT(1,0); +void progress(const char *fmt, ...) FMT(1,2); + +void vprogress_log(const char *fmt, va_list) FMT(1,0); +void progress_log(const char *fmt, ...) FMT(1,2); + +void vprogress_spinner(const char *fmt, va_list) FMT(1,0); +void progress_spinner(const char *fmt, ...) FMT(1,2); + void vfatal(const char *fmt, va_list) FMT(1,0) NORET; void fatal(const char *fmt, ...) FMT(1,2) NORET; @@ -140,37 +142,11 @@ void waitpid_check_exitstatus(pid_t pid, const char *what, int sigpipeok); void *mmalloc(size_t sz); +void *mcalloc(size_t sz); void *mrealloc(void *p, size_t sz); - -#define dbassert(x) ((x) ? (void)0 : dbfile_assertfail(__FILE__,__LINE__,#x)) -void dbfile_assertfail(const char *file, int line, const char *m) NORET; - -FILE *dbfile; -void dbfile_getsline(char *lbuf, size_t lbufsz, const char *file, int line); -int dbfile_open(const char *tpath); /* 0: ENOENT; 1: worked */ -int dbfile_gzopen(const char *tpath); /* 0: ENOENT; 1: worked */ -void dbfile_close(void); /* idempotent */ - -int dbfile_scanf(const char *fmt, ...) SCANFMT(1,2); -int dbfile_vscanf(const char *fmt, va_list al) SCANFMT(1,0); - -int gzopen(const char *zpath, int oflags, FILE **f_r, pid_t *pid_r, - const char *gziplevel /* 0 for read; may be 0, or "-1" etc. */); - /* returns errno value from open */ -void gzclose(FILE **f, pid_t *p, const char *what); - /* also OK with f==0, or p==-1 */ - char *masprintf(const char *fmt, ...) FMT(1,2); -#define EXECLP_HELPER(helper, ...) do{ \ - char *helper_path= masprintf("%s/%s",get_libdir(),helper); \ - execlp(helper_path,helper, __VA_ARGS__); \ - sysassert(errno==ENOENT); \ - fatal("Failed to find helper program %s.\n" \ - "(Are you in the correct directory?)", helper); \ - }while(0) - #define ARRAYSIZE(a) ((sizeof((a)) / sizeof((a)[0]))) #define FILLZERO(obj) (memset(&(obj),0,sizeof((obj)))) diff --git a/yarrg/convert.c b/yarrg/convert.c index 4ba23b5..ce3ad0b 100644 --- a/yarrg/convert.c +++ b/yarrg/convert.c @@ -27,11 +27,6 @@ #include "convert.h" -void debug_flush(void) { - sysassert(!ferror(debug)); - sysassert(!fflush(debug)); -} - const char *get_vardir(void) { return "."; } const char *get_libdir(void) { return "."; } @@ -412,118 +407,128 @@ int main(int argc, char **argv) { } +FILE *dbfile; +static const char *basepath; /* as passed in by caller */ +static pid_t dbzcat; +int dbfile_gzopen(const char *basepath_spec) { + assert(!dbfile); -DEFINE_VWRAPPERF(, progress, ) -DEFINE_VWRAPPERF(, progress_log, ) -DEFINE_VWRAPPERF(, progress_spinner, ) -DEFINE_VWRAPPERF(, warning, ) -DEFINE_VWRAPPERF(, fatal, NORET) + basepath= basepath_spec; -static int last_progress_len; - -static void vprogress_core(int spinner, const char *fmt, va_list al) { - int r; - - if (o_quiet) return; - if (!debug_flags && !isatty(2)) return; + char *zpath= masprintf("%s.gz", basepath); + int e= gzopen(zpath, O_RDONLY, &dbfile, &dbzcat, 0); + free(zpath); + if (e) { errno=e; sysassert(errno==ENOENT); return 0; } - if (last_progress_len) - putc('\r',stderr); + return 1; +} - r= vfprintf(stderr,fmt,al); +int dbfile_open(const char *tpath) { + assert(!dbfile); - if (spinner) { - putc(spinner,stderr); - r++; - } + basepath= tpath; - if (r < last_progress_len) { - fprintf(stderr,"%*s", last_progress_len - r, ""); - if (!r) putc('\r', stderr); - else while (last_progress_len-- > r) putc('\b',stderr); - } - last_progress_len= r; + dbzcat= -1; + dbfile= fopen(tpath,"r"); + if (!dbfile) { sysassert(errno==ENOENT); return 0; } + return 1; +} - if (ferror(stderr) || fflush(stderr)) _exit(16); -} - -void vprogress(const char *fmt, va_list al) { vprogress_core(0,fmt,al); } -void vprogress_spinner(const char *fmt, va_list al) { - static const char spinchars[]="/-\\"; - static int spinner; - - vprogress_core(spinchars[spinner],fmt,al); - spinner++; - spinner %= (sizeof(spinchars)-1); +void dbfile_close(void) { + gzclose(&dbfile, &dbzcat, basepath); } -void vprogress_log(const char *fmt, va_list al) { - if (o_quiet) return; - - progress(""); - vfprintf(stderr,fmt,al); - putc('\n',stderr); - fflush(stderr); +#define dbassertgl(x) ((x) ? (void)0 : dbfile_assertfail(file,line,#x)) + +void dbfile_getsline(char *lbuf, size_t lbufsz, const char *file, int line) { + errno=0; + char *s= fgets(lbuf,lbufsz,dbfile); + sysassert(!ferror(dbfile)); + dbassertgl(!feof(dbfile)); + assert(s); + int l= strlen(lbuf); + dbassertgl(l>0); dbassertgl(lbuf[--l]=='\n'); + lbuf[l]= 0; } -void vwarning(const char *fmt, va_list al) { - progress(""); - fputs("Warning: ",stderr); - vfprintf(stderr,fmt,al); - fputs("\n",stderr); - fflush(stderr); +int dbfile_vscanf(const char *fmt, va_list al) { + int r= vfscanf(dbfile,fmt,al); + sysassert(!ferror(dbfile)); + return r; } -void vfatal(const char *fmt, va_list al) { - progress(""); - fputs("\n\nFatal error: ",stderr); - vfprintf(stderr,fmt,al); - fflush(stderr); - fputs("\n\n",stderr); - _exit(4); +int dbfile_scanf(const char *fmt, ...) { + va_list al; + va_start(al,fmt); + int r= dbfile_vscanf(fmt,al); + va_end(al); + return r; } -void sysassert_fail(const char *file, int line, const char *what) { - int e= errno; - progress(""); - fprintf(stderr, - "\nfatal operational error:\n" - " unsuccessful execution of: %s\n" - " %s:%d: %s\n\n", - what, file,line, strerror(e)); - _exit(16); +void dbfile_assertfail(const char *file, int line, const char *m) { + if (dbzcat) + fatal("Error in dictionary file %s.gz:\n" + " Requirement not met at %s:%d:\n" + " %s", + basepath, file,line, m); + else if (dbfile) + fatal("Error in dictionary file %s at byte %ld:\n" + " Requirement not met at %s:%d:\n" + " %s", + basepath,(long)ftell(dbfile), file,line, m); + else + fatal("Semantic error in dictionaries:\n" + " Requirement not met at %s:%d:\n" + " %s", + file,line, m); } -void waitpid_check_exitstatus(pid_t pid, const char *what, int sigpipeok) { - pid_t got; - int st; - for (;;) { - got= waitpid(pid, &st, 0); - if (pid==-1) { sysassert(errno==EINTR); continue; } - break; +int gzopen(const char *zpath, int oflags, FILE **f_r, pid_t *pid_r, + const char *gziplevel /* 0 for read; may be 0, or "-1" etc. */) { + + int zfd= open(zpath, oflags, 0666); + if (zfd<0) return errno; + + int pipefds[2]; + sysassert(! pipe(pipefds) ); + + int oi,io; const char *cmd; const char *stdiomode; + switch ((oflags & O_ACCMODE)) { + case O_RDONLY: oi=0; io=1; cmd="gunzip"; stdiomode="r"; break; + case O_WRONLY: oi=1; io=0; cmd="gzip"; stdiomode="w"; break; + default: abort(); } - sysassert( got==pid ); - - if (WIFEXITED(st)) { - if (WEXITSTATUS(st)) - fatal("%s failed with nonzero exit status %d", - what, WEXITSTATUS(st)); - } else if (WIFSIGNALED(st)) { - if (!sigpipeok || WTERMSIG(st) != SIGPIPE) - fatal("%s died due to signal %s%s", what, - strsignal(WTERMSIG(st)), WCOREDUMP(st)?" (core dumped)":""); - } else { - fatal("%s gave strange wait status %d", what, st); + + sysassert( (*pid_r=fork()) != -1 ); + if (!*pid_r) { + sysassert( dup2(zfd,oi)==oi ); + sysassert( dup2(pipefds[io],io)==io ); + sysassert(! close(zfd) ); + sysassert(! close(pipefds[0]) ); + sysassert(! close(pipefds[1]) ); + execlp(cmd,cmd,gziplevel,(char*)0); + sysassert(!"execlp gzip/gunzip"); } + sysassert(! close(zfd) ); + sysassert(! close(pipefds[io]) ); + sysassert( *f_r= fdopen(pipefds[oi], stdiomode) ); + + return 0; } -char *masprintf(const char *fmt, ...) { - char *r; - va_list al; - va_start(al,fmt); - sysassert( vasprintf(&r,fmt,al) >= 0); - sysassert(r); - va_end(al); - return r; +void gzclose(FILE **f, pid_t *p, const char *what) { + if (!*f) return; + + sysassert(!ferror(*f)); + sysassert(!fclose(*f)); + + if (*p != -1) { + char *process= masprintf("%s (de)compressor",what); + waitpid_check_exitstatus(*p,process,1); + free(process); + *p= -1; + } + + *f= 0; } diff --git a/yarrg/convert.h b/yarrg/convert.h index fadfe93..75f6179 100644 --- a/yarrg/convert.h +++ b/yarrg/convert.h @@ -28,9 +28,23 @@ #ifndef CONVERT_H #define CONVERT_H +#define DEBUG_FLAG_LIST \ + DF(findypp) \ + DF(pages) \ + DF(rect) \ + DF(pixmap) \ + DF(struct) \ + DF(ocr) \ + DF(rsync) \ + DF(structcolon) \ + DF(callout) + + #include "common.h" #include "ocr.h" +#include +#include #include #include #include @@ -93,18 +107,6 @@ extern FILE *screenshot_file; void fetch_with_rsync(const char *stem); void fetch_with_rsync_gz(const char *stem); -void vwarning(const char *fmt, va_list) FMT(1,0); -void warning(const char *fmt, ...) FMT(1,2); - -void vprogress(const char *fmt, va_list) FMT(1,0); -void progress(const char *fmt, ...) FMT(1,2); - -void vprogress_log(const char *fmt, va_list) FMT(1,0); -void progress_log(const char *fmt, ...) FMT(1,2); - -void vprogress_spinner(const char *fmt, va_list) FMT(1,0); -void progress_spinner(const char *fmt, ...) FMT(1,2); - enum flags { ff_singlepage= 000002, ff_testservers= 000004, @@ -151,6 +153,39 @@ extern enum mode o_mode; extern const char *o_ocean, *o_pirate; extern int o_quiet; + +#define dbassert(x) ((x) ? (void)0 : dbfile_assertfail(__FILE__,__LINE__,#x)) +void dbfile_assertfail(const char *file, int line, const char *m) NORET; + +FILE *dbfile; +void dbfile_getsline(char *lbuf, size_t lbufsz, const char *file, int line); +int dbfile_open(const char *tpath); /* 0: ENOENT; 1: worked */ +int dbfile_gzopen(const char *tpath); /* 0: ENOENT; 1: worked */ +void dbfile_close(void); /* idempotent */ + +int dbfile_scanf(const char *fmt, ...) SCANFMT(1,2); +int dbfile_vscanf(const char *fmt, va_list al) SCANFMT(1,0); + +int gzopen(const char *zpath, int oflags, FILE **f_r, pid_t *pid_r, + const char *gziplevel /* 0 for read; may be 0, or "-1" etc. */); + /* returns errno value from open */ +void gzclose(FILE **f, pid_t *p, const char *what); + /* also OK with f==0, or p==-1 */ + + +const char *get_vardir(void); +const char *get_libdir(void); + + +#define EXECLP_HELPER(helper, ...) do{ \ + char *helper_path= masprintf("%s/%s",get_libdir(),helper); \ + execlp(helper_path,helper, __VA_ARGS__); \ + sysassert(errno==ENOENT); \ + fatal("Failed to find helper program %s.\n" \ + "(Are you in the correct directory?)", helper); \ + }while(0) + + /*----- from pages.c -----*/ void screenshot_startup(void); @@ -168,5 +203,4 @@ extern int npages; extern const char *ocean, *pirate; extern char *archipelago, *island; - #endif /*CONVERT_H*/ diff --git a/yarrg/db-idempotent-populate b/yarrg/db-idempotent-populate index 143e2ef..1d106f2 100755 --- a/yarrg/db-idempotent-populate +++ b/yarrg/db-idempotent-populate @@ -102,6 +102,13 @@ db_doall(<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; +} diff --git a/yarrg/ocean-topology-graph b/yarrg/ocean-topology-graph index 55d15d9..e609e35 100755 --- a/yarrg/ocean-topology-graph +++ b/yarrg/ocean-topology-graph @@ -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).", } diff --git a/yarrg/ocr.c b/yarrg/ocr.c index 08bc60c..e373fbc 100644 --- a/yarrg/ocr.c +++ b/yarrg/ocr.c @@ -25,7 +25,6 @@ * sponsored by Three Rings. */ -#include "ocr.h" #include "convert.h" typedef struct { diff --git a/yarrg/rscommon.h b/yarrg/rscommon.h new file mode 100644 index 0000000..45a5b31 --- /dev/null +++ b/yarrg/rscommon.h @@ -0,0 +1,203 @@ +#ifndef RSCOMMON_H +#define RSCOMMON_H + +#include + +#define DEBUG_FLAG_LIST \ + DF(sql) \ + DF(sql2) \ + DF(value) \ + DF(value2) \ + DF(search) \ + DF(filter) \ + DF(check) \ + DF(tableau) \ + DF(lp) + +//#define debug_flags 0 + +#define debug debug_file + +#include "common.h" + +extern FILE *debug_file; +#define DEBUG_DEV "/dev/stdout" /* just for glpk */ + + +#define GRANUS 3 + +#define COUNTER_LIST \ + CTR(commodities_loaded) \ + CTR(trades_loaded) \ + CTR(islands_arbitrage) \ + CTR(ipairs_relevant) \ + CTR(quantities_loaded) \ + CTR(routes_considered) \ + CTR(routes_wrongfinalelim) \ + CTR(routes_quickelim) \ + CTR(routes_bucketelim) \ + CTR(routes_valued) \ + CTR(routes_wrongfinal) \ + CTRA(newbests_granu,GRANUS*2) \ + CTR(subroute_tails_valued) \ + CTR(subroutes_valued) \ + CTR(subroutes_nonempty) +#define CTR(x) extern int ctr_##x; +#define CTRA(x,n) extern int ctr_##x[n]; + COUNTER_LIST +#undef CTR +#undef CTRA + +#define SQL_MUST( call ) ({ \ + /* `call' is an expression returning result, using const char *sqe; \ + * chk1 and chk2 are blocks using sqe and int sqr; */ \ + const char *sql_must_call_string= #call; \ + int sqr; \ + if (DEBUGP(sql2)) fprintf(debug,"SQL %s", sql_must_call_string); \ + sqr= (call); \ + if (DEBUGP(sql2)) fprintf(debug," = %d\n", sqr); \ + if (sqr) sql_fatal("(unknown)", sqr, sql_must_call_string); \ + }) \ + +void sql_fatal(const char *stmt_what, int sqr, const char *act_what) NORET; + +#define SQL_STEP(ssh) (sql_step((ssh), #ssh, __FILE__, __LINE__)) +int sql_step(sqlite3_stmt *ssh, const char *ssh_string, + const char *file, int line); + +#define SQL_DISTINCT_DECL(cols, nintcols) \ + int cols[nintcols]; \ + cols[0]= -1; +#define SQL_DISTINCT_STEP(ssh, cols, nkeycols) \ + (sql_step_distinct((ssh), #ssh, __FILE__, __LINE__, \ + (cols), sizeof((cols))/sizeof((cols)[0]), nkeycols)) +int sql_step_distinct(sqlite3_stmt *ssh, const char *ssh_string, + const char *file, int line, + int *cols, int ncols, int nkeycols); + /* These work if we're making a query whose columns consist of: + * - keys: integer column(s) on which the results are sorted by the query + * - consequences: zero or more integer cols strictly dependent on the keys + * - extra: zero or more further (possibly non-integer) columns + * + * Call SQL_DISTINCT_DECL, passing intcols = the total number of keys and + * consequences; it will declare int cols[intfields]; + * + * Then each SQL_DISTINCT_STEP is like SQL_STEP only you have to + * pass the number of key columns and it only returns rows with + * distinct keys. Rows with all-identical keys are asserted to + * have identical consequences. After each call to + * SQL_DISTINCT_STEP the keys and consequences will be stored in + * cols. + */ + +int sql_single_int(const char *stmt); + +#define SQL_PREPARE(ss,stmt) ((ss)= sql_prepare((stmt),#ss)) +sqlite3_stmt *sql_prepare(const char *stmt, const char *what); + +#define SQL_BIND(ss,index,value) (sql_bind((ss),(index),(value),#ss,#value)) +void sql_bind(sqlite3_stmt *ss, int index, int value, + const char *ss_what, const char *val_what); + +#define MAX_ROUTELEN 20 + +extern sqlite3 *db; + +void setup_sql(const char *database); + + +typedef struct { + double distance_loss_factor; + struct TradesBlock *trades; + double route_tail_value; +} IslandPair; + +IslandPair *ipair_get_maybe(int si, int di); + +double value_route(int nislands, const int *islands, int exclude_arbitrage); +void setup_value(void); + +#define AP 2 /* 0=absolute, 1=perleague */ +#define A 0 +#define P 1 + +typedef struct { + double value[AP]; + int length; + int ports[MAX_ROUTELEN]; +} OnePotentialResult; + +typedef struct { + OnePotentialResult prs[AP]; +} Bucket; + +void setup_search(void); +void search(int start_isle, int final_isle /* -1 means any */, + Bucket ****buckets_base_io[GRANUS] + /* bucket_base[granui][finalthing][midthing]-> */); + +extern double max_mass, max_volu, max_capi; +extern double distance_loss_factor_per_league; +extern int max_dist, min_trade_maxprofit; + +#define LOSS_FACTOR_PER_DELAY_SLOT (1-1e-8) + +extern int islandtablesz; + +extern int narches; +extern char **archnames; +extern int *islandid2arch; + +extern int granusz_fin[GRANUS], granusz_mid[GRANUS]; + + +extern FILE *output; + + +#define NEW(ptr) ((ptr)= mmalloc(sizeof(*ptr))) + +#define MCALLOC(array, count) ((array)= mcalloc(sizeof(*(array)) * (count))) + +#define MCALLOC_INITEACH(array, count, init_this) ({ \ + MCALLOC((array), (count)); \ + int initi; \ + typeof(&(array)[0]) this; \ + for (initi=0, this=(array); initi<(count); initi++, this++) { \ + init_this; \ + } \ + }) + + +typedef struct { + double value; + Bucket *bucket; +} HighScoreEntry; + +extern int granus; +extern int nhighscores[GRANUS][AP]; +extern HighScoreEntry *highscores[GRANUS][AP]; + + +#define ONDEMAND(pointer_lvalue, calloc_size_count) \ + ((pointer_lvalue) ? : \ + ((pointer_lvalue) = mcalloc(sizeof(*(pointer_lvalue)) * calloc_size_count))) + + +static inline int isle2arch(int isle) { + int arch= islandid2arch[isle]; + assert(arch>=0); + return arch; +} + +static inline int route2midarch(const int *ports, int nports) { + int archs[nports], last_arch=-1, narchs=0, i; + for (i=0; i + +int o_quiet= 0; +double max_mass=-1, max_volu=-1, max_capi=-1; +double distance_loss_factor_per_league; +int max_dist=-1, min_trade_maxprofit=0; + +FILE *debug_file; +FILE *output; + +#define tabdebugf printf + + +#define CTR(x) int ctr_##x; +#define CTRA(x,n) int ctr_##x[n]; + COUNTER_LIST +#undef CTR +#undef CTRA + +static Bucket ****results[GRANUS]; + /* results[GRANUS][start_isle_ix][finalisle][midisle]-> */ + +static pid_t debugoutpid; + +int main(int argc, const char **argv) { + const char *arg; + int i, ap; + int granui; + const char *database=0; + const char *concur_base=0, *concur_rhs=0; + int concur_lim=-1; + +#ifndef debug_flags + debug_flags= ~( dbg_sql2 ); +#endif + + for (;;) { + arg= *++argv; + if (arg[0] != '-') break; + if (!strcmp(arg,"-d")) { + database= *++argv; + } else if (!strcmp(arg,"-C")) { + concur_base= *++argv; + concur_rhs= *++argv; + concur_lim= atoi(*++argv); + } else if (!strcmp(arg,"-g")) { + granus= atoi(*++argv); + assert(granus>=1 && granus<=GRANUS); +#ifndef debug_flags + } else if (!strcmp(arg,"-DN")) { + debug_flags= 0; + } else if (!strcmp(arg,"-D1")) { + debug_flags= ~(dbg_sql2|dbg_lp|dbg_value2); +#endif + } else { + abort(); + } + } + + if (debug_flags) { + /* glpk insists on writing stuff to stdout, and it does buffering, + * so we route all our debug through it this too */ + int realstdout; + sysassert( (realstdout= dup(1)) > 2 ); + sysassert( output= fdopen(realstdout,"w") ); + + int pfd[2]; + sysassert(! pipe(pfd) ); + sysassert( (debugoutpid= fork()) >=0 ); + if (!debugoutpid) { + sysassert( dup2(pfd[0],0)==0 ); + sysassert( dup2(2,1)==1 ); + sysassert(! close(pfd[0]) ); + sysassert(! close(pfd[1]) ); + sysassert(! execlp("cat","cat",(char*)0) ); + } + sysassert( dup2(pfd[1],1)==1 ); + sysassert(! close(pfd[0]) ); + sysassert(! close(pfd[1]) ); + + debug_file= stdout; + } else { + output= stdout; + debug_file= stderr; + } + + sysassert( !setvbuf(debug,0,_IOLBF,0) ); + + max_mass= atof(*argv++); + max_volu= atof(*argv++); + max_capi= atof(*argv++); + double loss_per_league= atof(*argv++); + distance_loss_factor_per_league= 1.0 - loss_per_league; + + min_trade_maxprofit= atoi(*argv++); + + if (concur_base) { + for (i=0; i= 0); + struct flock fl; + memset(&fl,0,sizeof(fl)); + fl.l_type= F_WRLCK; + r= fcntl(concfd, F_SETLK, &fl); + free(concfn); + if (!r) goto concur_ok; + sysassert( errno == EWOULDBLOCK ); + close(concfd); + } + fprintf(output,"@@@ concurrency limit exceeded (%d)\n", concur_lim); + exit(0); + + concur_ok: + /* deliberately leak concfd */ + fprintf(output,"concurrency slot %d\n", i); + } + + setup_sql(database); + setup_value(); + setup_search(); + + for (i=0; iprs[A].value[A])); + tabdebugf(" "); + tabdebugf("%4d",(int)(result->prs[P].value[P])); + } + } + tabdebugf("\n"); + } + } /* i */ + + for (ap=0; ap=0; pos--) { + HighScoreEntry *hs= &highscores[granui][ap][pos]; + Bucket *bucket= hs->bucket; + if (!bucket) continue; + OnePotentialResult *pr= &bucket->prs[ap]; + const int *const ports= pr->ports; + int nports; + for (nports=0; nports=0; nports++); + int finisle= ports[nports-1]; + int finarch= isle2arch(finisle); + int midisle= ports[nports/2]; + int midarch= route2midarch(ports,nports); + fprintf(output, + " @%2d %c#%2d | start%3d mid%d:%3d f%d:%3d" + " | %3dlg | %5d %5d %4d |", + pos, "ap"[ap], nhighscores[granui][ap] - pos, + ports[0], midarch,midisle, finarch,finisle, pr->length, + (int)hs->value, (int)pr->value[A], (int)pr->value[P]); + for (i=0; iislandid etc. */ +static sqlite3_stmt *ss_neigh; + +static int ports[MAX_ROUTELEN]; +static int final_isle; + +static Neighbour *get_neighbours(int isle) { + Neighbour **np= &neighbours[isle]; + Neighbour *head= *np; + if (head) return head; + + SQL_BIND(ss_neigh, 1, isle); + while (SQL_STEP(ss_neigh)) { + Neighbour *add; + NEW(add); + add->islandid= sqlite3_column_int(ss_neigh, 0); + add->dist= sqlite3_column_int(ss_neigh, 1); + add->next= head; + head= add; + } + SQL_MUST( sqlite3_reset(ss_neigh) ); + + *np= head; + return head; +} + + +static Bucket ***buckets_base[GRANUS]; + + +static double process_route(int nports, int totaldist, + double overestimate_excepting_tail) { + int i, ap, granui; + int leagues_divisor= totaldist + nports; + + ctr_routes_considered++; + + int wrong_final= final_isle && ports[nports-1] != final_isle; + + debugf("========== ROUTE"); + for (i=0; i=2) { + int pair[2], i; + pair[1]= ports[nports-1]; + guess[A]= overestimate_excepting_tail; + + for (i=0; iroute_tail_value < 0) { + ctr_subroute_tails_valued++; + ip->route_tail_value= value_route(2, pair, pair[0]!=pair[1]); + } + guess[A] += ip->route_tail_value; + } + guess[P]= guess[A] / leagues_divisor; + + if (wrong_final) { + ctr_routes_wrongfinalelim++; + debugf(" WFELIM\n"); + return guess[A]; + } + + for (granui=0; granui highscores[granui][A][0].value || + guess[P] > highscores[granui][P][0].value) + goto not_quickelim; + } + ctr_routes_quickelim++; + debugf(" QELIM %f %f\n", guess[A], guess[P]); + return guess[A]; + not_quickelim:; + } + + int finisle= ports[nports-1]; + int finarch= isle2arch(finisle); + + int midisle= ports[nports/2]; + int midarch= route2midarch(ports,nports); + + Bucket *buckets[GRANUS]; + for (granui=0; granui=2) { + for (granui=0; granui buckets[granui]->prs[ap].value[ap] && + guess[ap] > highscores[granui][ap][0].value) + goto not_bucketelim; + ctr_routes_bucketelim++; + debugf(" ELIM %f %f\n", guess[A], guess[P]); + return guess[A]; + not_bucketelim: + debugf(" COMPUTE %f %f\n", guess[A], guess[P]); + } + + ctr_routes_valued++; + + double value[AP]; + value[A]= value_route(nports, ports, 0); + value[P]= value[A] / leagues_divisor; + + if (wrong_final) { + ctr_routes_wrongfinal++; + return value[0]; + } + + for (granui=granus-1; granui>=0; granui--) { + Bucket *bucket= buckets[granui]; + + if (value[A] <= bucket->prs[A].value[A] && + value[P] <= bucket->prs[P].value[P]) + continue; + + debugf(" SOMEHOW %d BEST\n",granui); + + fildebugf("granu %d f%d:%3d mid%d:%3d ",granui, + finarch,finisle,midarch,midisle); + + for (ap=0; apprs[ap].value[ap]) { + debugf(" "); + } else { + int pos; + ctr_newbests_granu[granui*AP+ap]++; + bucket->prs[ap].length= totaldist; + memcpy(bucket->prs[ap].value, value, sizeof(value)); + memcpy(bucket->prs[ap].ports, ports, sizeof(*ports) * nports); + if (nports < MAX_ROUTELEN-1) bucket->prs[ap].ports[nports]= -1; + fildebugf("** "); + for (pos=0; pos < nscores; pos++) + if (scores[pos].bucket == bucket) goto found; + /* not found */ + pos= -1; + found: + for (;;) { + pos++; + if (pos >= nscores) break; /* new top */ + if (scores[pos].value >= value[ap]) break; /* found spot */ + if (pos>0) + scores[pos-1]= scores[pos]; + } + pos--; + if (pos>0) { + scores[pos].value= value[ap]; + scores[pos].bucket= bucket; + if (granui < granus-1 && + highscores[granui][A][0].bucket && + highscores[granui][P][0].bucket) { + /* both absolute and perleague are full at this granularity, + * so we don't care about anything more granular */ + fildebugf("\n STOP-GRANU "); + granus= granui+1; + } + } + fildebugf("@%2d/%2d ", pos, nscores); + } /* new best */ + } /* ap */ + } /* granui */ + + fildebugf(" route"); + + for (i=0; i= MAX_ROUTELEN) return; + + Neighbour *add; + for (add= get_neighbours(last_isle); add; add=add->next) { + int newdist= totaldist + add->dist; + if (newdist > max_dist) continue; + + recurse(add->islandid, nports, newdist, estimate); + } +} + +void search(int start_isle, int final_isle_spec, + Bucket ****buckets_base_io[GRANUS]) { + int granui; + for (granui=0; granui", debug); + } + fputs("\n",debug); + } + return 1; + default: fatal("SQL step failed at %s:%d: code %d: %s: %s", + file, line, sqr, sqlite3_errmsg(db), ssh_string); + } + } +} diff --git a/yarrg/rsvalue.c b/yarrg/rsvalue.c new file mode 100644 index 0000000..d3ffeeb --- /dev/null +++ b/yarrg/rsvalue.c @@ -0,0 +1,448 @@ +/**/ + +#include + +#include "rscommon.h" + +DEBUG_DEFINE_DEBUGF(value); +DEBUG_DEFINE_SOME_DEBUGF(value2,debug2f); + +typedef struct { int mass, volu; } CommodInfo; +static int commodstabsz; +static CommodInfo *commodstab; + +static sqlite3_stmt *ss_ipair_dist; +static sqlite3_stmt *ss_ite_buy, *ss_ite_sell; + + +#define MAX_LEGS (MAX_ROUTELEN-1) + +typedef struct { + int commodid, src_price, dst_price; +} Trade; + +#define TRADES_PER_BLOCK 10 + +typedef struct TradesBlock { + struct TradesBlock *next; + int ntrades; + Trade t[TRADES_PER_BLOCK]; +} TradesBlock; + +static IslandPair ***ipairs; /* ipairs[sislandid][dislandid] */ + +typedef struct IslandTradeEnd { + struct IslandTradeEnd *next; + /* key: */ + int commodid, price; + /* values: */ + int qty; + unsigned long generation; + int rownum; +} IslandTradeEnd; + +typedef struct { + IslandTradeEnd *src, *dst; +} IslandTradeEndHeads; + +IslandTradeEndHeads *itradeends; + /* itradeends[islandid].{src,dst}->commodid etc. */ + +static LPX *lp; +static unsigned long generation; + +static int nconstraint_rows; +static int constraint_rows[1+2+3*MAX_LEGS]; +static double constraint_coeffs[1+2+3*MAX_LEGS]; + /* dummy0, src, dst, for_each_leg( [mass], [volume], [capital] ) */ + +static void add_constraint(int row, double coefficient) { + nconstraint_rows++; /* glpk indices start from 1 !!! */ + constraint_rows [nconstraint_rows]= row; + constraint_coeffs[nconstraint_rows]= coefficient; +} + +static IslandTradeEnd *get_ite(const Trade *t, IslandTradeEnd **trades, + int price) { + IslandTradeEnd *search; + + for (search= *trades; search; search=search->next) + if (search->commodid==t->commodid && search->price==price) + return search; + abort(); +} + +static void avail_c(const Trade *t, IslandTradeEnd *ite, + int price, const char *srcdst, + int islandid, sqlite3_stmt *ss_ite) { + /* find row number of trade availability constraint */ + IslandTradeEnd *search= ite; + + if (search->generation != generation) { + search->rownum= lpx_add_rows(lp, 1); + lpx_set_row_bnds(lp, search->rownum, LPX_UP, 0, search->qty); + + if (DEBUGP(value) || DEBUGP(check)) { + char *name= masprintf("%s_i%d_c%d_%d_all", + srcdst, islandid, t->commodid, price); + lpx_set_row_name(lp,search->rownum,name); + + if (DEBUGP(check)) { + int nrows= lpx_get_num_rows(lp); + assert(search->rownum == nrows); + int i; + for (i=1; igeneration= generation; + } + + add_constraint(search->rownum, 1.0); +} + +static int setup_leg_constraints(double max_thing, int legs, const char *wh) { + int leg, startrow; + if (max_thing < 0 || !legs) return -1; + startrow= lpx_add_rows(lp, legs); + for (leg=0; leg 0); + add_constraint(startrow+leg, value); +} + +IslandPair *ipair_get_maybe(int si, int di) { + IslandPair **ipa; + + assert(si < islandtablesz); + assert(di < islandtablesz); + + if (!(ipa= ipairs[si])) return 0; + return ipa[di]; +} + +static IslandPair *ipair_get_create(int si, int di) { + IslandPair *ip, **ipa; + + assert(si < islandtablesz); + assert(di < islandtablesz); + + if (!(ipa= ipairs[si])) { + ipairs[si]= MCALLOC(ipa, islandtablesz); + } + if ((ip= ipa[di])) + return ip; + + ipa[di]= NEW(ip); + ip->trades= 0; + ip->route_tail_value= -1; + + if (si==di) ctr_islands_arbitrage++; + else ctr_ipairs_relevant++; + + debug2f("VALUE ipair_get(i%d,i%d) running...\n", si,di); + SQL_MUST( sqlite3_bind_int(ss_ipair_dist, 1, si) ); + SQL_MUST( sqlite3_bind_int(ss_ipair_dist, 2, di) ); + assert(SQL_STEP(ss_ipair_dist)); + int dist= sqlite3_column_int(ss_ipair_dist, 0); + ip->distance_loss_factor= pow(distance_loss_factor_per_league, dist); + sqlite3_reset(ss_ipair_dist); + + return ip; +} + +double value_route(int nislands, const int *islands, int exclude_arbitrage) { + int s,d; + + ctr_subroutes_valued++; + + /* We need to construct the LP problem. GLPK talks + * about rows and columns, which are numbered from 1. + * + * Each column is a `structural variable' ie one of the entries in + * the objective function. In our case the set of structural + * variable is, for each port, the set of Trades which collect at + * that island. (We use `port' to mean `specific visit to an + * island' so if an island appears more than once so do its trades.) + * We don't need to worry about crossing with all the possible + * delivery locations as we always deliver on the first port. + * We will call such a structural variable a Flow, for brevity. + * + * We iterate over the possible Flows adding them as columns as we + * go, and also adding their entries to the various constraints. + * + * Each row is an `auxiliary variable' ie one of the constraints. + * We have two kinds of constraint: + * - mass/volume/capital: one constraint for each sailed leg + * (unless relevant constraint is not satisfied) + * - quantity of commodity available for collection + * or delivery at particular price and island + * The former are numbered predictably: we have first all the mass + * limits, then all the volume limits, then all the capital limits + * (as applicable) - one for each leg, ie one for each entry + * in islands except the first. + * + * The latter are added as needed and the row numbers are stored in + * a data structure for later reuse. + */ + + assert(nislands >= 1); + assert(++generation); + + assert(!lp); + lp= lpx_create_prob(); + lpx_set_obj_dir(lp, LPX_MAX); + lpx_set_int_parm(lp, LPX_K_MSGLEV, DEBUGP(lp) ? 3 : 1); + lpx_set_int_parm(lp, LPX_K_PRESOL, 1); + + if (DEBUGP(value)) { + lpx_set_prob_name(lp,(char*)"value_route"); + lpx_set_obj_name(lp,(char*)"profit"); + } + + int legs= nislands-1; + int mass_constraints= setup_leg_constraints(max_mass, legs, "mass"); + int volu_constraints= setup_leg_constraints(max_volu, legs, "volu"); + int capi_constraints= setup_leg_constraints(max_capi, legs, "capi"); + + double delay_slot_loss_factor= 1.0; + for (s=0; + ss && di==si) + /* route has returned to si, no need to think more about s */ + goto next_s; + + /*----- actually add these trades to the LP problem -----*/ + + IslandPair *ip= ipair_get_maybe(islands[s], islands[d]); + + if (!ip || !ip->trades) + goto next_d; + + double loss_factor= delay_slot_loss_factor * ip->distance_loss_factor; + debugf(" SOME i%d#%d..i%d#%d dslf=%g dlf=%g lf=%g\n", + si,s, di,d, + delay_slot_loss_factor, ip->distance_loss_factor, loss_factor); + + TradesBlock *block; + for (block=ip->trades; block; block=block->next) { + int inblock; + for (inblock=0; inblockntrades; inblock++) { + Trade *t= &block->t[inblock]; + + debugf(" TRADE i%d#%d..i%d#%d c%d %d-%d ", + si,s, di,d, t->commodid, t->src_price, t->dst_price); + + IslandTradeEnd + *src_ite= get_ite(t, &itradeends[si].src, t->src_price), + *dst_ite= get_ite(t, &itradeends[di].dst, t->dst_price); + + int qty= src_ite->qty < dst_ite->qty ? src_ite->qty : dst_ite->qty; + int maxprofit= qty * (t->dst_price - t->src_price); + debugf("maxprofit=%d ",maxprofit); + if (maxprofit < min_trade_maxprofit) { + debugf("trivial\n"); + continue; + } + + nconstraint_rows=0; + + avail_c(t, src_ite, t->src_price, "src", si,ss_ite_sell); + avail_c(t, dst_ite, t->dst_price, "dst", di,ss_ite_buy); + + int leg; + for (leg=s; legcommodid].mass*1e-3); + add_leg_c(volu_constraints,leg, commodstab[t->commodid].volu*1e-3); + add_leg_c(capi_constraints,leg, t->src_price); + } + + double unit_profit= t->dst_price * loss_factor - t->src_price; + debugf(" unit profit %f\n", unit_profit); + if (unit_profit <= 0) continue; + + int col= lpx_add_cols(lp,1); + lpx_set_col_bnds(lp, col, LPX_LO, 0, 0); + lpx_set_obj_coef(lp, col, unit_profit); + lpx_set_mat_col(lp, col, nconstraint_rows, + constraint_rows, constraint_coeffs); + + if (DEBUGP(value)) { + char *name= masprintf("c%d_p%d_%d_p%d_%d", t->commodid, + s, t->src_price, d, t->dst_price); + lpx_set_col_name(lp, col, name); + free(name); + } + } /* inblock */ + } /* block */ + + /*----- that's done adding these trades to the LP problem -----*/ + + next_d:; + } /* for (d) */ + next_s:; + } /* for (s) */ + + double profit= 0; + + if (lpx_get_num_cols(lp)) { + ctr_subroutes_nonempty++; + + if (DEBUGP(lp)) + lpx_write_cpxlp(lp, (char*)DEBUG_DEV); + + int ipr= lpx_simplex(lp); + assert(ipr==LPX_E_OK); + + if (DEBUGP(lp)) + lpx_print_sol(lp, (char*)DEBUG_DEV); + + int lpst= lpx_get_status(lp); + assert(lpst == LPX_OPT); + profit= lpx_get_obj_val(lp); + } + + lpx_delete_prob(lp); + lp= 0; + + debugf(" %s %f\n", + exclude_arbitrage ? "base value" : "route value", + profit); + return profit; +} + +#define TRADE_FROM \ + " FROM sell, buy\n" \ + " WHERE sell.commodid=buy.commodid AND sell.price < buy.price\n" + +static void read_trades(void) { + /* We would like to use DISTINCT but sqlite3 is too stupid + * to notice that it could use the index to do the DISTINCT + * which makes it rather slow. */ + sqlite3_stmt *ss_trades; + +#define TRADE_COLS \ + "sell.commodid, sell.islandid, sell.price, buy.islandid, buy.price" + SQL_PREPARE(ss_trades, + " SELECT " TRADE_COLS "\n" + TRADE_FROM + " ORDER BY " TRADE_COLS); + + SQL_DISTINCT_DECL(cols,5); + while (SQL_DISTINCT_STEP(ss_trades,cols,5)) { + ctr_trades_loaded++; + IslandPair *ip= ipair_get_create(cols[1], cols[3]); + TradesBlock *block= ip->trades; + if (!block || ip->trades->ntrades >= TRADES_PER_BLOCK) { + NEW(block); + block->next= ip->trades; + ip->trades= block; + block->ntrades= 0; + } + Trade *trade= &block->t[block->ntrades]; + trade->commodid= cols[0]; + trade->src_price= cols[2]; + trade->dst_price= cols[4]; + block->ntrades++; + } + sqlite3_finalize(ss_trades); +} + +static void read_islandtradeends(const char *bs, int srcdstoff) { + +#define TRADEEND_KEYCOLS "%s.commodid, %s.islandid, %s.stallid" + char *stmt= masprintf(" SELECT " TRADEEND_KEYCOLS ", %s.price, %s.qty\n" + TRADE_FROM + " ORDER BY " TRADEEND_KEYCOLS, + bs,bs,bs,bs,bs, bs,bs,bs); + char *stmt_id= masprintf("qtys (%s)",bs); + sqlite3_stmt *ss= sql_prepare(stmt, stmt_id); + free(stmt); free(stmt_id); + + SQL_DISTINCT_DECL(cols,5); + while (SQL_DISTINCT_STEP(ss,cols,3)) { + ctr_quantities_loaded++; + IslandTradeEnd *search; + + int commodid= cols[0]; + int islandid= cols[1]; + int price= cols[3]; + int qty= cols[4]; + + IslandTradeEnd **trades= (void*)((char*)&itradeends[islandid] + srcdstoff); + + for (search= *trades; search; search=search->next) + if (search->commodid==commodid && search->price==price) + goto found; + /* not found, add new end */ + + NEW(search); + search->commodid= commodid; + search->price= price; + search->next= *trades; + search->generation= 0; + search->qty= 0; + *trades= search; + + found: + search->qty += qty; + } + sqlite3_finalize(ss); +} + +void setup_value(void) { + sqlite3_stmt *sst; + + commodstabsz= sql_single_int("SELECT max(commodid) FROM commods") + 1; + MCALLOC_INITEACH(commodstab, commodstabsz, + this->mass= this->volu= -1 + ); + + SQL_PREPARE(sst, + "SELECT commodid,unitmass,unitvolume FROM commods"); + while (SQL_STEP(sst)) { + ctr_commodities_loaded++; + int id= sqlite3_column_int(sst,0); + assert(id>=0 && id +<%perl> + +use CommodsWeb; + +my $printable= printable($m); +if ($printable =~ m/^pdf|^ps/) { + my $output; + my $got= $m->call_self(\$output); + if ($got) { + my @htargs= qw(htmldoc --continuous --gray --size 210x279mm + --left 1cm --right 1cm); + $printable =~ m/^[a-z]+/; + push @htargs, '-t',$&; + if ($printable =~ m/2$/) { + push @htargs, qw(--nup 2); + } + push @htargs, qw(-); + + my $tmpfile= IO::File::new_tmpfile(); + print $tmpfile $output or die $!; + $tmpfile->flush() or die $!; + seek $tmpfile,0,0 or die $!; + my $htmldoc= open HTMLDOC, "-|"; + defined $htmldoc or die $!; + if (!$htmldoc) { + eval { + $ENV{'HTMLDOC_NOCGI'}=1; + open STDIN, '<&', $tmpfile or die $!; + + exec @htargs; + die $!; + }; + print STDERR "HTMLDOC FAILURE $@"; + _exit(1); + } + my ($data,$read); + $r->content_type($printable eq 'pdf' ? 'application/pdf' : + 'application/postscript'); + while ($read= read HTMLDOC,$data,32768) { print $data; } + defined $read or die $!; + $?=0; $!=0; close HTMLDOC or die "$! $? $output "; + return; + } +} +set_ctype_utf8(); +$r->content_type('text/html; charset=UTF-8'); + + -% $m->call_next; - -<%init> -use CommodsWeb; -set_ctype_utf8(); -$r->content_type('text/html; charset=UTF-8'); - +% $m->call_next(); diff --git a/yarrg/web/check_capacitystring b/yarrg/web/check_capacitystring index 3d8f7a5..a79b6f1 100644 --- a/yarrg/web/check_capacitystring +++ b/yarrg/web/check_capacitystring @@ -32,57 +32,159 @@ This Mason component simply defines how to interpret capacities. - -<%attr> - - -<%method preparse> +<%method execute> <%args> -$h +$string +$dbh +$debugf <%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; } - - +$parse_term->($string); -<%method postquery> -<%args> -$h - -<%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); + diff --git a/yarrg/web/check_capitalstring b/yarrg/web/check_capitalstring new file mode 100644 index 0000000..53aceec --- /dev/null +++ b/yarrg/web/check_capitalstring @@ -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 + 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 . + + 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. + + + +<%method execute> +<%args> +$string +$dbh +$debugf + +<%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); + + + diff --git a/yarrg/web/check_distance b/yarrg/web/check_distance new file mode 100644 index 0000000..223cc5a --- /dev/null +++ b/yarrg/web/check_distance @@ -0,0 +1,68 @@ +<%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 + 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 . + + 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 distances. + + + +<%attr> +significant_nonempty => 1 + + +<%method execute> +<%args> +$string +$dbh +$debugf + + +<%perl> + +$_= $string; +s/^\s+//; s/\s+$//; + +my $leagues; +my $canon; + +if (!m/\S/) { + $leagues= 20; + $canon= '(default: 20 leagues)'; +} elsif (m/^([1-9]\d*)( leagues)?$/i) { + $leagues= $1; + $canon= "$leagues leagues"; +} else { + expected_error("Cannot understand distance \`$_'."); +} + +return ($canon,$leagues); + + + diff --git a/yarrg/web/check_islandstring b/yarrg/web/check_islandstring new file mode 100644 index 0000000..e8664d5 --- /dev/null +++ b/yarrg/web/check_islandstring @@ -0,0 +1,58 @@ +<%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 + 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 . + + 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 look up island names. + It is called by qtextstring. + + + +<%attr> +multiple => 1 +maxambig => 5 + + +<%method sqlstmt> + SELECT islandname,islandid,NULL + FROM islands WHERE islandname LIKE ? + + +<%method nomatch> + no island matches "<% $ARGS{spec} |h %>" + + +<%method ambiguous> + ambiguous island "<% $ARGS{spec} |h %>", + could be <% $ARGS{couldbe} |h %> + + +<%method manyambig> +   + diff --git a/yarrg/web/check_lossperleague b/yarrg/web/check_lossperleague index 5994f6f..9375355 100644 --- a/yarrg/web/check_lossperleague +++ b/yarrg/web/check_lossperleague @@ -29,37 +29,35 @@ sponsored by Three Rings. - This Mason component simply defines how to interpret capacities. + This Mason component simply defines how to interpret losses per league. - -<%attr> - - -<%method preparse> +<%method execute> <%args> -$h +$string +$dbh +$debugf <%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); + diff --git a/yarrg/web/copyrightdate b/yarrg/web/copyrightdate index e7d2dc8..6af4e75 100644 --- a/yarrg/web/copyrightdate +++ b/yarrg/web/copyrightdate @@ -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 diff --git a/yarrg/web/devel b/yarrg/web/devel index 513d50b..fecd77d 100755 --- a/yarrg/web/devel +++ b/yarrg/web/devel @@ -33,17 +33,18 @@ -YARRG (Yet Another Revenue Research Gatherer) + +YARRG (Yet Another Revenue Research Gatherer) YARRG - Yet Another Revenue Research Gatherer | -development -| introduction | documentation +| +development

YARRG development, contribution and troubleshooting

@@ -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).

+or later).

@@ -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.

diff --git a/yarrg/web/docs b/yarrg/web/docs index cbb1c0d..c211683 100755 --- a/yarrg/web/docs +++ b/yarrg/web/docs @@ -33,17 +33,17 @@ -Website documentation - YARRG +Website documentation - YARRG YARRG - Yet Another Revenue Research Gatherer | -development -| introduction | documentation +| +development

Looking up data in YARRG

@@ -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. -

Vessel capacity

+

Vessel capacity

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).

-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: +

+
sloop +
The capacity of a sloop, leaving no allowance for rum and shot +
wb - 1% +
The capacity of a war brig minus 1% +
13t 20kl +
13 tonnes (13,000kg), 20 kilolitres (20,000l) +
sloop - 10 small 40 rum +
The capacity of a sloop which remains after + 10 small shot and 40 rum are loaded +
2t plus 500kg minus 200kg +
2300kg, with no limit on volume +
+Evaluation is strictly from left to right. + +

+ +More formally: +

+ 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
+
-

Expected losses

+If the first term specifies only one of mass or volume, all the +subsequent terms may only adjust that same value. + +

Expected losses

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.

-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.

-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.

You can enter the value in the box either as a percentage, or as a -fraction 1/divisor, eg 1/200 is the same as 0.5%; in each +fraction 1/divisor, eg 1/2000 is the same as 0.05%; in each case it is taken as the loss for each league of the voyage. +

Available capital

+ +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. + +

+ +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 &> diff --git a/yarrg/web/dumptable b/yarrg/web/dumptable index e60415c..739f14d 100644 --- a/yarrg/web/dumptable +++ b/yarrg/web/dumptable @@ -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_advrouteopts b/yarrg/web/enter_advrouteopts new file mode 100644 index 0000000..95a7143 --- /dev/null +++ b/yarrg/web/enter_advrouteopts @@ -0,0 +1,107 @@ +<%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 + 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 . + + 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 entry fields for route planning + advanced parameters (capacity limits, etc). + + Some useful parameters are in %$routeparams: + ${ $routeparams->{EmsgRef} } + $routeparams->{SayRequiredCapacity} + The results are returned there: + $routeparams->{LossPerLeaguePct} + $routeparams->{MaxMass} + $routeparams->{MaxVolume} + $routeparams->{MaxCapital} + + + +<%args> +$qa +$dbh +$routeparams + + +<%method advanced> +Advanced options - you may leave these blank: +

+ + +% if (!$routeparams->{SayRequiredCapacity}) { +<& SELF:advanced &> +% } + +
+ +Vessel or capacity: +<&| qtextstring, qa => $qa, dbh => $dbh, prefix => 'cs', + thingstring => 'capacitystring', emsgstore => $routeparams->{EmsgRef}, + helpref => 'capacity', + onresults => sub { + ($routeparams->{MaxMass}, $routeparams->{MaxVolume}) = @_; + } + &> + size=40 + + +
+% if ($routeparams->{SayRequiredCapacity}) { +<& SELF:advanced &> +% } + + +
Available capital: + +<&| qtextstring, qa => $qa, dbh => $dbh, prefix => 'ac', + thingstring => 'capitalstring', emsgstore => $routeparams->{EmsgRef}, + helpref => 'capital', + onresults => sub { ($routeparams->{MaxCapital})= @_; } + &> + size=9 + + + +  +  + + +Expected losses: + +<&| qtextstring, qa => $qa, dbh => $dbh, prefix => 'll', + thingstring => 'lossperleague', emsgstore => $routeparams->{EmsgRef}, + helpref => 'losses', + onresults => sub { ($routeparams->{LossPerLeaguePct})= @_; } + &> + size=9 + + +<% $m->content %> + +
diff --git a/yarrg/web/enter_commod b/yarrg/web/enter_commod new file mode 100644 index 0000000..c3f5553 --- /dev/null +++ b/yarrg/web/enter_commod @@ -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 + 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 . + + 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. + + + +<%args> +$qa +$dbh +$emsg_r + +$commodname_r +$cmid_r + + +%#---------- textbox, user enters commodity as string ---------- +% if (!$qa->{Dropdowns}) { + +Enter commodity (abbreviations are OK):
+ +<&| 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; + +

+ +% } #---------- 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 index 0000000..fbdf2dc --- /dev/null +++ b/yarrg/web/enter_route @@ -0,0 +1,198 @@ +<%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 + 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 . + + 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). + + + +<%args> +$qa +$dbh +$emsg_r +$warningfs_r + +$enterwhat +$islandids_r +$archipelagoes_r + + +%#---------- textbox, user enters route as string ---------- +% if (!$qa->{Dropdowns}) { + +<% $enterwhat %> +% if (defined($archipelagoes_r)) { +(islands, or archipelagoes, +% } else { +(islands +% } +separated by |s or commas; abbreviations are OK):
+ +<&| qtextstring, qa => $qa, dbh => $dbh, emsgstore => $emsg_r, + thingstring => defined($archipelagoes_r) ? 'routestring' : 'islandstring', + prefix => 'rl', + onresults => sub { + foreach (@_) { + my ($canonname, $island, $arch) = @$_; + push @$islandids_r, $island; + push @$archipelagoes_r, defined $island ? undef : $arch + if defined $archipelagoes_r; + } + } + &> + 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('', + 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}, ''); +} + + + +<&| 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'); +} + + + + + +% for my $dd (0..$qa->{Dropdowns}-1) { + +% } + + + +% for my $dd (0..$qa->{Dropdowns}-1) { +% my $arch= $qa->{"archipelago$dd"}; +% $arch= 'none' if !defined $arch; + +% } + + +
+
+
+ +<%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 { + + Specified archipelago <% $arch %> but + island <% $ii->{Name} %> + which is in <% $iarch %>; using the island.

+<%perl> + }; + } + $arch= undef; + } + push @$archipelagoes_r, $arch; + push @$islandids_r, $island; +} + + +

+ +% } diff --git a/yarrg/web/footer b/yarrg/web/footer index 9837d53..75fcd15 100644 --- a/yarrg/web/footer +++ b/yarrg/web/footer @@ -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 NO WARRANTY. % if (!$isdevel) { Please see the YARRG Development webpage for details of how to obtain the client and server code and full details diff --git a/yarrg/web/intro b/yarrg/web/intro index 66dc15f..825189b 100755 --- a/yarrg/web/intro +++ b/yarrg/web/intro @@ -33,17 +33,18 @@ -YARRG (Yet Another Revenue Research Gatherer) + +YARRG (Yet Another Revenue Research Gatherer) YARRG - Yet Another Revenue Research Gatherer | -development -| introduction | documentation +| +development

Introduction to YARRG

@@ -68,7 +69,7 @@ website.

Uploading from Linux

The YARRG upload client uploads both to YARRG and to the -PCTB testing server. +PCTB testing server.

diff --git a/yarrg/web/lookup b/yarrg/web/lookup index 7b3100e..a2ccc8d 100755 --- a/yarrg/web/lookup +++ b/yarrg/web/lookup @@ -57,6 +57,8 @@ my %styles; Before => 'Query: ', Values => [ [ 'route', 'Trades for route' ], [ 'commod', 'Prices for commodity' ], + [ 'offers', 'Offers at location' ], + [ 'routesearch', 'Find profitable route' ], [ 'age', 'Data age' ] ] }, { Name => 'BuySell', Before => '', @@ -78,6 +80,12 @@ my %styles; [ 1, 'Show individual stalls' ], ], QuerySpecific => 1, + }, { Name => 'RouteSearchType', + Before => 'Type of routes to search for: ', + Values => [ [ 0, 'Open-ended' ], + [ 1, 'Circular' ], + ], + QuerySpecific => 1, }); foreach my $var (@vars) { @@ -118,7 +126,7 @@ $ours % } -<% ucfirst $ahtml{Query} %> - YARRG +<% ucfirst $ahtml{Query} %> - YARRG