6 # usage: ./yppedia-chart-parser <Oceanname>
7 # updates OCEAN-Oceanname.db and _ocean-<oceanname>.txt
8 # from YPPedia (chart and ocean page) and source-info.txt
10 # This is part of ypp-sc-tools, a set of third-party tools for assisting
11 # players of Yohoho Puzzle Pirates.
13 # Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 # Yohoho and Puzzle Pirates are probably trademarks of Three Rings and
29 # are used without permission. This program is not endorsed or
30 # sponsored by Three Rings.
32 use strict (qw(vars));
35 use Graph::Undirected;
39 my $widists= Graph::Undirected->new();
40 my $wiarchs= Graph::Undirected->new();
54 my @msgkinds= qw(change warning error);
58 sub pmsg ($$) { push @{ $msgs{$_[0]} }, "$_[0]: $_[1]\n"; }
59 sub warning ($) { pmsg("warning",$_[0]); }
60 sub error ($) { pmsg("error", $_[0]); }
61 sub change ($) { pmsg("change", $_[0]); }
62 sub print_messages () {
63 foreach my $k (@msgkinds) {
66 foreach my $m (sort @$ms) {
67 next if $msgprinted{$m};
70 $msgkindprinted{$k}++;
74 sub progress ($) { print "($_[0])\n"; }
78 open DEBUG, ">/dev/null" or die $!;
81 last unless $ARGV[0] =~ m/^-/;
84 if ($_ eq '--debug') {
85 open DEBUG, ">&STDOUT" or die $!;
86 select(DEBUG); $|=1; select(STDOUT);
87 } elsif ($_ eq '--stdin-chart') {
96 my $ocean= shift @ARGV;
102 my $tp= (0+$x ^ 0+$y) & 1;
103 defined $parity or $parity=$tp;
104 $tp==$parity or warning("line $.: parity error $x,$y is $tp not $parity");
105 my $n= "$_[0],$_[1]";
106 $winode2lines{$n}{$.}++;
110 sub yppedia_chart_parse () {
111 # We don't even bother with tag soup; instead we do line-oriented parsing.
115 s/^\s*//; chomp; s/\s+$//; s/\s+/ /g;
116 s/\<\/?(?:b|em)\>//g;
117 s/\{\{Chart\ style\|[^{}]*\}\}//g;
118 next unless m/\{\{/; # only interested in chart template stuff
120 my ($x,$y, $arch,$island,$solid,$dirn);
121 my $nn= sub { return nn_xy($x,$y) };
124 m/^\{\{ chart\ label \|(\d+)\|(\d+)\| .*
125 \'\[\[ [^][\']* \| (\S+)\ archipelago \]\]\'*\}\}$/xi) {
126 printf DEBUG "%2d,%-2d arch %s\n", $x,$y,$arch;
127 push @wiarchlabels, [ $x,$y,$arch ];
128 } elsif (($x,$y,$island) =
129 m/^\{\{ chart\ island\ icon \|(\d+)\|(\d+)\|
130 ([^| ][^|]*[^| ]) \| .*\}\}$/xi) {
132 $wiisland2node{$island}= $n;
133 $winode2island{$n}= $island;
134 $widists->add_vertex($n);
135 $wiarchs->add_vertex($n);
136 printf DEBUG "%2d,%-2d island %s\n", $x,$y,$island;
137 } elsif (($solid,$x,$y,$dirn) =
138 m/^\{\{ chart\ league((?:\ solid)?) \|(\d+)\|(\d+)\|
139 ([-\/\\o]) \| .*\}\}$/xi) {
140 next if $dirn eq 'o';
142 my ($bx,$by) = ($x,$y);
143 if ($dirn eq '-') { $bx+=2; }
144 elsif ($dirn eq '\\') { $bx++; $by++; }
145 elsif ($dirn eq '/') { $x++; $by++; }
148 my $nb= nn_xy($bx,$by);
149 $widists->add_weighted_edge($nn->(), $nb, 1);
150 $wiarchs->add_edge($nn->(), $nb) if $solid;
151 $wiarchs->add_edge($nn->(), $nb) if $solid;
153 printf DEBUG "%2d,%-2d league %-6s %s %s\n", $x,$y,
154 $solid?'solid':'dotted', $dirn, $nb;
156 m/^\{\{ chart\ head \}\}$/xi
160 warning("line $.: ignoring incomprehensible: $_");
165 sub yppedia_graphs_add_shortcuts () {
166 # We add edges between LPs we know about, as you can chart
167 # between them. Yppedia often lacks these edges.
169 foreach my $p ($widists->vertices) {
170 my ($ax,$ay) = $p =~ m/^(\d+)\,(\d+)$/ or die;
171 my $add_shortcut= sub {
172 my $q= sprintf "%d,%d", $ax+$_[0], $ay+$_[1];
173 return unless $widists->has_vertex($q);
174 return if $widists->has_edge($p,$q);
175 printf DEBUG "%-5s league-shortcut %-5s\n", $p, $q;
176 $widists->add_weighted_edge($p,$q,1);
178 $add_shortcut->( 2,0);
179 $add_shortcut->(+1,1);
180 $add_shortcut->(-1,1);
184 sub yppedia_graphs_prune_boring () {
185 # Prune the LP database by eliminating boring intermediate vertices
186 foreach my $delete ($widists->vertices()) {
187 next if exists $winode2island{$delete};
188 my @neigh= $widists->neighbours($delete);
189 next unless @neigh==2;
191 map { $weight += $widists->get_edge_weight($delete, $_) } @neigh;
192 $widists->add_weighted_edge(@neigh, $weight);
193 $widists->delete_vertex($delete);
194 printf DEBUG "%-5s elide %5s %-5s %2d\n", $delete, @neigh, $weight;
198 sub yppedia_graphs_check () {
199 # Check that it's connected.
200 foreach my $cc ($widists->connected_components()) {
201 next if 2*@$cc > $widists->vertices();
202 my $m= "disconnected league point(s):";
203 foreach my $n (@$cc) {
204 $m .= "\n LP $n, def. yppedia line(s): ".
205 join(',', sort keys %{ $winode2lines{$n} });
211 sub yppedia_archs_sourceinfo () {
212 # Assign archipelagoes according to the source-info file
213 foreach my $arch (sort keys %{ $oceans{$ocean} }) {
214 foreach my $islename (sort keys %{ $oceans{$ocean}{$arch} }) {
215 my $islenode= $wiisland2node{$islename};
216 if (!defined $islenode) {
217 error("island $islename in source-info but not in WP map");
220 my $ccix= $wiarchs->connected_component_by_vertex($islenode);
221 my $oldarch= $wiccix2arch{$ccix};
222 error("island in $arch in source-info".
223 " connected to $oldarch as well: $islename")
224 if defined $oldarch && $oldarch ne $arch;
225 printf DEBUG "%-5s force-island-arch cc%-2d %-10s %s\n",
226 $islenode, $ccix, $arch, $islename;
227 $wiccix2arch{$ccix}= $arch;
232 sub yppedia_archs_chart_labels () {
233 # Assign archipelago labels to groups of islands
235 foreach my $label (@wiarchlabels) {
236 my ($ax,$ay,$arch) = @$label;
237 my $best_ccmulti= -1;
240 # print DEBUG "$ax,$ay arch-island-search $arch\n";
241 $ay += 1; $ax += 2; # coords are rather to the top left of label
242 foreach my $vertex ($wiarchs->vertices()) {
243 next unless exists $winode2island{$vertex};
244 my $ccix= $wiarchs->connected_component_by_vertex($vertex);
245 my @cc= $wiarchs->connected_component_by_index($ccix);
246 my $ccmulti= @cc > 1;
247 my ($vx,$vy) = split /,/, $vertex;
248 my $d2= ($vx-$ax)*($vx-$ax) + ($vy-$ay)*($vy-$ay);
249 my $cmp= $ccmulti <=> $best_ccmulti
251 printf DEBUG "%2d,%-2d arch-island-search %5s d2=%4d cc%-2d".
252 " #cc=%2d ccmulti=%d cmp=%2d %s\n",
253 $ax,$ay, $vertex, $d2, $ccix, scalar(@cc), $ccmulti, $cmp,
254 $winode2island{$vertex};
255 next unless $cmp > 0;
258 $best_ccmulti= $ccmulti;
260 die 'no island vertices?!' unless defined $best_n;
261 my $ccix= $wiarchs->connected_component_by_vertex($best_n);
263 "%2d,%-2d arch-island-select %-5s d2=%4d cc%-2d %-10s %s\n",
264 $ax,$ay, $best_n, $ccix, $best_d2, $arch, $winode2island{$best_n};
265 my $desc= join "\n", map {
266 my $in= $winode2island{$_};
267 " LP $_". (defined $in ? ", $in" : "");
268 } sort $wiarchs->connected_component_by_index($ccix);
270 if (exists $wiccix2arch{$ccix} and $wiccix2arch{$ccix} ne $arch) {
271 error("archipelago determination failed, wrongly merged:\n".
272 " archipelago $arch\n".
273 " archipelago $wiccix2arch{$ccix}\n".
277 $wiccix2arch{$ccix}= $arch;
278 # print "$ccix $arch ::\n$desc\n";
282 sub yppedia_archs_fillbynearest() {
283 # Assign islands not labelled above to archipelagoes.
285 # We do this by, for each connected component (set of islands
286 # linked by purchaseable charts), searching for the nearest other
287 # connected component which has already been assigned an arch.
288 # `Nearest' means shortest distance of unpurchaseable charts, in
291 # we need only consider vertices which weren't `boring intermediate
292 # vertices' (removed during optimisation as being of order 2)
293 my @ccs_useful= map {
294 [ grep { $widists->has_vertex($_) } @$_ ]
295 } $wiarchs->connected_components();
299 foreach my $sourceccix (0..$#ccs_useful) {
300 next if defined $wiccix2arch{$sourceccix};
301 next unless $ccs_useful[$sourceccix];
303 my @sourcecc= $wiarchs->connected_component_by_index($sourceccix);
304 my @islandnodes= grep { $winode2island{$_} } @sourcecc;
305 next unless @islandnodes; # don't care, then
307 foreach my $islandnode (@islandnodes) {
308 printf DEBUG "%-5s arch-join-need cc%-2d %s\n",
309 $islandnode, $sourceccix, $winode2island{$islandnode};
311 my $best_dist= 9999999;
312 my ($best_target, $best_targetccix, $best_source);
313 foreach my $targetccix (0..$#ccs_useful) {
314 next unless defined $wiccix2arch{$targetccix}; # not helpful
315 next unless $ccs_useful[$targetccix];
316 foreach my $target ($wiarchs->
317 connected_component_by_index($targetccix)) {
318 next unless $widists->has_vertex($target);
319 foreach my $source (@sourcecc) {
320 my $target_dist= widist($target,$source);
321 next unless defined $target_dist;
322 next if $target_dist >= $best_dist;
323 $best_dist= $target_dist;
324 $best_source= $source;
325 $best_target= $target;
326 $best_targetccix= $targetccix;
330 die "no possible target ?!" unless defined $best_target;
332 my $arch= $wiccix2arch{$best_targetccix};
333 my $best_island= $winode2island{$best_target};
334 printf DEBUG "%-5s arch-join-to %-5s dist=%2d cc%-2d %-10s %s\n",
335 $best_source, $best_target, $best_dist,
336 $best_targetccix, $arch,
337 defined($best_island) ? $best_island : "-";
339 push @assignments, [ $sourceccix, $arch ];
341 foreach my $assign (@assignments) {
342 $wiccix2arch{$assign->[0]}= $assign->[1];
346 sub yppedia_graph_shortest_paths () {
347 $wialldists= $widists->APSP_Floyd_Warshall();
352 my $pl= $wialldists->path_length($p,$q);
353 # die "$p $q" unless defined $pl;
354 # my @pv= $wialldists->path_vertices($p,$q);
355 # if (@pv == $pl) { return $pl; }
356 # printf DEBUG "%-5s PATHLENGTH %-5s pl=%s pv=%s\n", $p,$q,$pl,join('|',@pv);
360 sub winode2arch ($) {
362 my $ccix= $wiarchs->connected_component_by_vertex($node);
363 return $wiccix2arch{$ccix};
365 sub wiisland2arch ($) {
367 my $node= $wiisland2node{$island};
368 die "$island ?" unless defined $node;
369 return winode2arch($node);
372 sub compare_island_lists () {
373 foreach my $island (sort keys %dbisland2arch) {
374 my $node= $wiisland2node{$island};
375 if (!defined $node) {
376 error("would delete island: $island");
379 my $wiarch= winode2arch($node);
380 if (!defined $wiarch) {
381 error("island has no arch: $island");
384 my $dbarch= $dbisland2arch{$island};
385 if ($wiarch ne $dbarch) {
386 change("archipelago change from $dbarch to $wiarch".
387 " for island $island");
390 foreach my $island (sort keys %wiisland2node) {
391 my $wtarch= $wtisland2arch{$island};
392 my $wiarch= wiisland2arch($island);
394 if (!defined $wtarch) {
395 error("island from chart not found on ocean page: $island");
396 } elsif (defined $wiarch and $wtarch ne $wiarch) {
397 error("island in $wtarch on ocean page but".
398 " concluded $wiarch from chart: $island");
402 my $dbarch= $dbisland2arch{$island};
403 if (!defined $dbarch) {
404 my $wiarch= wiisland2arch($island);
405 if (!defined $wiarch) {
406 error("new island has no arch: $island");
408 # We check arches of non-new islands above
410 change("island new in $wiarch: $island");
414 foreach my $island (sort keys %wtisland2arch) {
415 my $node= $wiisland2node{$island};
416 next if defined $node;
417 error("island on ocean page but not in chart: $island");
422 sub shortest_path_reduction ($$) {
425 # Takes a graph $g (and a string for messages $what) and returns
426 # a new graph which is the miminal shortest path transient reduction
429 # We also check that the shortest path closure of the intended result
430 # is the same graph as the input. Thus the input must itself be
431 # a shortest path closure; if it isn't, we die.
433 my $proof=<<'END'; # way to make a big comment
435 Premises and definitions:
437 1. F is an undirected weighted graph with positive edge weights.
439 2. All graphs we will consider have the same vertices as F
440 and none have self-edges.
442 3. G = Closure(F) is the graph of cliques whose edge weights
443 are the shortest paths in F, one clique for each connected
446 3a. |XY| for vertices X, Y is the weight of the edge XY in G.
447 If XY is not in G, |XY| is infinite.
449 4. A `reduction' of G is a subgraph K of G such that Closure(K) = G.
450 The reduction is `minimal' if there is no strict subgraph K'
451 of K such that Closure(K') = G.
453 5. Now each edge of G may be:
454 - `unnecessary': included in no minimal reductions of G.
455 - `essential': included in all minimal reductions of G.
456 - `contingent': included in some but not all.
458 6. Consider for any edge AC between the vertices A and C,
459 whether there is any B such that |AB|+|BC| = |AC| ?
460 (There can be no B such that the sum < |AC| since that would
461 mean that |AC| wasn't equal to the shortest path length.)
463 6a. No such B: AC is therefore the only shortest path from A to C
464 (since G is not a multigraph). AC is thus an essential edge.
466 6b. Some such B: Call all such edges AC `questionable'.
468 6c. Thus all edges are essential or questionable.
470 7. Suppose AC is a shortest contingent edge. AC must be
471 questionable since it is not essential. Suppose it is
472 made questionable by the existence of B such that |AB|+|BC| =
473 |AC|. Consider AB and BC. Since |AB| and |BC| are positive,
474 |BC| and |AB| must be < |AC| ie AB and BC are shorter than AC.
475 Since AC is a shortest contingent edge, there must be shortest
476 paths in G for AB and BC consisting entirely of essential edges.
478 8. Therefore it is always safe to remove AC since the paths
479 A..B and B..C will definitely still remain and provide a path
480 A..B..C with length |AB|+|BC| = |AC|.
482 9. Thus AC is unnecessary, contradicting the assumption in 7.
483 There are therefore no shortest contingent edges, and
484 thus no contingent edges.
486 10. We can construct a minimal reduction directly: for each edge
487 AC in G, search for a vertex B such that |AB|+|BC| = |AC|.
488 If we find none, AC is essential. If we find one then AC is
489 not essential and is therefore unnecessary.
493 printf DEBUG "spr %s before %d\n", $what, scalar($g->edges());
495 my $result= Graph::Undirected->new();
496 foreach my $edge_ac ($g->edges()) {
497 next if $edge_ac->[0] eq $edge_ac->[1];
498 my $edgename_ac= join ' .. ', @$edge_ac;
499 printf DEBUG "spr %s edge %s\n", $what, $edgename_ac;
500 my $w_ac= $g->get_edge_weight(@$edge_ac);
502 foreach my $vertex_b ($g->vertices()) {
503 next if grep { $_ eq $vertex_b } @$edge_ac;
504 my $w_ab= $g->get_edge_weight($edge_ac->[0], $vertex_b);
505 next unless defined $w_ab;
506 next if $w_ab >= $w_ac;
507 my $w_bc= $g->get_edge_weight($vertex_b, $edge_ac->[1]);
508 next unless defined $w_ac;
509 next if $w_ab + $w_bc > $w_ac;
511 printf DEBUG "spr %s edge %s unnecessary %s\n",
512 $what, $edgename_ac, $vertex_b;
517 printf DEBUG "spr %s edge %s essential\n", $what, $edgename_ac;
518 $result->add_weighted_edge(@$edge_ac,$w_ac);
521 printf DEBUG "spr %s result %d\n", $what, scalar($result->edges());
523 my $apsp= $result->APSP_Floyd_Warshall();
524 foreach my $ia (sort $g->vertices()) {
525 foreach my $ib (sort $g->vertices()) {
526 my $din= $g->get_edge_weight($ia,$ib);
527 my $dout= $apsp->path_length($ia,$ib);
528 $din= defined($din) ? $din : 'infinity';
529 $dout= defined($dout) ? $dout : 'infinity';
530 error("$what spr apsp discrepancy in=$din out=$dout".
538 sub yppedia_graph_spr () {
539 my $base= Graph::Undirected->new();
540 foreach my $na (sort keys %winode2island) {
541 my $ia= $winode2island{$na};
542 foreach my $nb (sort keys %winode2island) {
543 my $ib= $winode2island{$nb};
544 $base->add_weighted_edge($ia,$ib, widist($na,$nb));
547 $wispr= shortest_path_reduction('wi',$base);
550 sub yppedia_ocean_fetch_start ($) {
553 push @args, '--chart' if $chart;
555 open OCEAN, '-|', "./yppedia-ocean-scraper", @args or die $!;
557 sub yppedia_ocean_fetch_done () {
558 $?=0; $!=0; close OCEAN; $? and die $?; $! and die $!;
561 sub yppedia_ocean_fetch_chart () {
563 open OCEAN, "<& STDIN" or die $!;
564 yppedia_chart_parse();
566 yppedia_ocean_fetch_start(1);
567 yppedia_chart_parse();
568 yppedia_ocean_fetch_done();
572 sub yppedia_ocean_fetch_text () {
573 yppedia_ocean_fetch_start(0);
580 die unless defined $arch;
581 $wtisland2arch{$'}= $arch;
588 yppedia_ocean_fetch_done();
591 sub compare_distances () {
592 foreach my $ia (sort keys %dbisland2arch) {
593 my $na= $wiisland2node{$ia};
594 next unless defined $na;
595 foreach my $ib (sort keys %dbisland2arch) {
596 next unless $ia le $ib; # do every pair only once
597 my $dbdist= $dbspr->get_edge_weight($ia,$ib);
598 my $widist= $wispr->get_edge_weight($ia,$ib);
599 next unless defined $dbdist || defined $widist;
601 if (!defined $widist) {
602 warning(sprintf "route delete %2d for %s .. %s",
604 } elsif (!defined $dbdist) {
605 change(sprintf "route new %2d for %s .. %s",
607 } elsif ($dbdist != $widist) {
608 change(sprintf "route change %2d to %2d for %s .. %s",
609 $dbdist, $widist, $ia,$ib);
615 #========== database handling ==========
617 sub database_fetch_ocean () {
619 $sth= $dbh->prepare('SELECT islandname, archipelago FROM islands');
621 undef %dbisland2arch;
622 $dbdists= Graph::Undirected->new();
623 while ($row= $sth->fetchrow_hashref) {
624 print DEBUG "database-island $row->{'islandname'}".
625 " $row->{'archipelago'}\n";
626 $dbisland2arch{$row->{'islandname'}}= $row->{'archipelago'};
628 $sth= $dbh->prepare('SELECT dist, a.islandname a, b.islandname b
630 JOIN islands AS a ON dists.aiid==a.islandid
631 JOIN islands AS b ON dists.biid==b.islandid');
633 while ($row= $sth->fetchrow_hashref) {
634 $dbdists->add_weighted_edge($row->{'a'}, $row->{'b'}, $row->{'dist'});
638 sub database_graph_spr () {
639 $dbspr= shortest_path_reduction('db',$dbdists);
642 sub database_do_updates () {
643 my $addisland= $dbh->prepare(<<'END')
644 INSERT OR IGNORE INTO islands (islandname, archipelago) VALUES (?, ?);
647 foreach my $island (sort keys %wiisland2node) {
648 my $wiarch= wiisland2arch($island);
649 $addisland->execute($island, $wiarch);
657 my $adddist= $dbh->prepare(<<'END')
658 INSERT INTO dists VALUES
659 ((SELECT islandid FROM islands WHERE islandname == ?),
660 (SELECT islandid FROM islands WHERE islandname == ?),
664 my $addroute= $dbh->prepare(<<'END')
665 INSERT INTO routes VALUES
666 ((SELECT islandid FROM islands WHERE islandname == ?),
667 (SELECT islandid FROM islands WHERE islandname == ?),
671 foreach my $ia (sort keys %wiisland2node) {
672 my $na= $wiisland2node{$ia};
673 foreach my $ib (sort keys %wiisland2node) {
674 my $nb= $wiisland2node{$ib};
675 my $apdist= $ia eq $ib ? 0 : widist($na,$nb);
676 die "$ia $ib" unless defined $apdist;
677 my $sprdist= $wispr->get_edge_weight($ia,$ib);
678 die "$ia $ib $apdist $sprdist" if
679 defined($sprdist) && $sprdist != $apdist;
681 $adddist->execute($ia,$ib,$apdist);
682 $addroute->execute($ia,$ib,$sprdist) if defined $sprdist;
686 # select ia.islandname, ib.islandname, d.dist from dists as d, islands as ia on d.aiid = ia.islandid, islands as ib on d.biid = ib.islandid order by ia.islandname, ib.islandname;
690 #========== update _ocean-*.txt ==========
694 sub localtopo_rewrite () {
695 $localtopo_path= '_ocean-'.(lc $ocean).'.txt';
696 my $fh= new IO::File "$localtopo_path.tmp", 'w';
697 print $fh "# autogenerated - do not edit\n" or die $!;
698 print $fh "ocean $ocean\n" or die $!;
700 foreach my $isle (sort keys %wtisland2arch) {
701 my $arch= $wtisland2arch{$isle};
702 push @{ $arches{$arch} }, $isle;
704 foreach my $arch (sort keys %arches) {
705 print $fh " $arch\n" or die $!;
706 foreach my $isle (@{ $arches{$arch} }) {
707 print $fh " $isle\n" or die $!;
710 print $fh "\n" or die $!;
714 sub localtopo_commit () {
715 rename "$localtopo_path.tmp", $localtopo_path or die $!;
718 #========== main program ==========
720 parse_info_serverside();
722 progress("fetching yppedia chart"); yppedia_ocean_fetch_chart();
723 progress("adding shortcuts"); yppedia_graphs_add_shortcuts();
724 progress("pruning boring vertices"); yppedia_graphs_prune_boring();
725 progress("checking yppedia graphs"); yppedia_graphs_check();
726 progress("setting archs from source-info"); yppedia_archs_sourceinfo();
727 progress("computing shortest paths"); yppedia_graph_shortest_paths();
728 progress("setting archs from labels"); yppedia_archs_chart_labels();
729 progress("setting archs from nearby"); yppedia_archs_fillbynearest();
730 progress("computing yppedia spr"); yppedia_graph_spr();
733 progress("fetching yppedia ocean text"); yppedia_ocean_fetch_text();
740 progress("reading database");
741 database_fetch_ocean();
742 progress("computing database spr"); database_graph_spr();
744 progress("comparing islands"); compare_island_lists();
745 progress("comparing distances"); compare_distances();
750 foreach my $k (@msgkinds) {
751 my $n= $msgkindprinted{$k};
753 printf STDERR "*** %d%s %ss\n", $n, $iteration?' additional':'', $k;
756 if ($msgs{'error'}) {
757 print STDERR "*** errors, aborting update\n";
761 if (!%msgkindprinted) {
762 progress("updating database"); database_do_updates();
763 progress("updating _ocean-*.txt"); localtopo_rewrite();
765 print STDERR "*** --stdin-chart, aborting!\n";
768 progress("committing database"); $dbh->commit();
769 progress("committing _ocean-*.txt"); localtopo_commit();
774 my $default= !$msgkindprinted{'warning'};
775 printf STDERR "*** confirm update %s ? ", $default?'(y/n)':'(n/y)';
778 printf STDERR "[--stdin-chart]\n";
782 $!=0; my $result= <STDIN>; defined $result or die $!;
784 $result= $default?'y':'n' if !length $result;
785 $result= $result =~ m/^y/i;
788 printf STDERR "*** updated abandoned at your request\n";
793 undef %msgkindprinted;