#!/usr/local/bin/perl -w ### pams - poslfit's analyser of Maven simulations ### ### (C) 1999 by John J. Chew, III ### All Rights Reserved ### ### Analyse a Maven simulation log and report useful statistics. use strict; use Carp; require 'getopts.pl'; ## prototypes sub Bold_Text ($); sub Colour_Text ($$); sub Define_Rack_Filter ($); sub Define_Dynamic_Subs ($$$); sub Eval_Must_Succeed ($); sub Heading_3 ($$); sub Heading_4 ($); sub HTML_Only ($); sub htmldl ($;); sub htmlpre ($); sub htmlref ($$); sub Initialise_Globals (); sub Main(); sub Parse_Command_Line (); sub Parse_Coordinates ($); sub Parse_Log (); sub Place_Word ($$$$); sub Print_Report (); sub Report_Version_And_Quit (); sub Setup_Standard_Output ($); sub Usage (); Main; ## subroutines # $string = Bold_Text $string; sub Bold_Text ($) { my ($string) = @_; $::opt_H ? "$string" : $string; } # $string = Colour_Text $colour, $string sub Colour_Text ($$) { my ($colour, $string) = @_; (defined $colour && length($colour) && $::opt_H) ? "$string" : $string; } sub Define_Dynamic_Subs ($$$) { my ($turn_board_usage, $global_board_usage, $tiles_played) = @_; # dynamic subroutines for doing board diagram analysis use the following globals # for the sake of efficiency (all in $global2::) # # choice pp turn tCoord tScore tWord my $eval_string = 'sub analyse_pass () {'; $eval_string .= "\$global::DiagramCount{\$global2::choice}++ if \$global2::turn == $turn_board_usage;" if defined $turn_board_usage; $eval_string .= '$global::GDiagramCount{$global2::choice}++;' if $global_board_usage; $eval_string .= '$global2::pp->{tp}[$global2::turn][0]++;' if $tiles_played; $eval_string .= '}'; Eval_Must_Succeed $eval_string; $eval_string = ''; $global2::tWord = $global2::tCoord = ''; # avoid hapax legomenon errors $eval_string .= <<"EOS" if defined $turn_board_usage; if (\$global2::turn == $turn_board_usage) { \$global::DiagramCount{\$global2::choice}++; my \$j = \$position; for my \$i (1..length(\$global2::tWord)) { \$global::DiagramUsage{\$global2::choice}[\$j]++; \$global::DiagramValue{\$global2::choice}[\$j]+= \$global2::tScore; } continue { \$j += \$delta; } } EOS $eval_string .= <<"EOS" if $global_board_usage; \$global::GDiagramCount{\$global2::choice}++; my \$j = \$position; for my \$i (1..length(\$global2::tWord)) { \$global::GDiagramValue{\$global2::choice}[\$j] += \$global2::turn%2 ? \$global2::tScore : -\$global2::tScore; } continue { \$j += \$delta; } EOS $eval_string .= <<"EOS" if $tiles_played; \$global2::pp->{tp}[\$global2::turn] [Place_Word \$global::Board[\$global2::turn], \$global2::tWord, \$position, \$delta]++; EOS $eval_string = 'my($position, $delta) = Parse_Coordinates $global2::tCoord;' . $eval_string if $eval_string ne ''; $eval_string = "sub analyse_word () { $eval_string }"; Eval_Must_Succeed $eval_string; } sub Define_Rack_Filter ($) { my $required_tiles = shift @_; if (defined $required_tiles && length($required_tiles)) { Eval_Must_Succeed 'sub filter { local($_) = @_; s/' . join('// && s/', split('', $required_tiles)) . '//; }'; } else { Eval_Must_Succeed 'sub filter { 1; }'; } } sub Eval_Must_Succeed ($) { my $string = shift @_; eval $string; confess "Eval_Must_Succeed:\n$string\n\neval failed: $@\n" if $@; } sub Heading_3 ($$) { my ($title, $tag) = @_; print $::opt_H ? <<"EOS"

$title

Back to top

EOS : "\n$title\n\n"; } sub Heading_4 ($) { my ($title) = @_; print $::opt_H ? "

$title

\n" : "\n$title\n\n"; } # $string = HTML_Only $string; sub HTML_Only ($) { my ($html) = @_; $::opt_H ? $html : ''; } # &htmldl($tag1, $def1, $tag2, $def2, ...); sub htmldl ($;) { if ($::opt_H) { print "
\n"; while ($#_ > 0) { print '
' . shift(@_) . "
\n"; print '
' . shift(@_) . "
\n"; } print "
\n"; } else { while ($#_ > 0) { print shift(@_) . " = "; print shift(@_) . "\n"; } } } # &htmlpre($bool); sub htmlpre ($) { if ($::opt_H) { print $_[0] ? "
\n" : "
\n"; } } # &htmlref($href, $label); sub htmlref ($$) { print $::opt_H ? "$_[1]" : $_[1]; } sub Initialise_Globals () { # initial position $global::Board = ''; # position after -1st play %global::Board = () if $::opt_B || $::opt_t || defined $::opt_b; # position after nth play @global::Board = () if $::opt_t; # number of plays logged for global diagram %global::GDiagramCount = () if $::opt_B; # values of each square in global diagram %global::GDiagramValue = () if $::opt_B; # initial racks @global::InitialRacks = (); # version number for this script $global::kVersion = '2.007'; # 2.007 fixed hit table captions # maximum observed equity $global::MaxEquity = -1000 if $::opt_e; # maximum observed iteration depth $global::MaxTurn = -1; # minimum observed equity $global::MinEquity = 1000 if $::opt_e; # minimum observed iteration depth $global::MinTurn = 1000; # per play stats: $global::Play{$choice}{'stat_key'}, see below %global::Play = (); # analysis start time $global::StartTime = time; } sub Main () { # @ARGV = ('hd:jjc:scrabble:analysis:bin:1997 05 28 hazanim'); Parse_Command_Line; Initialise_Globals; if ($::opt_v) { Report_Version_And_Quit; } Setup_Standard_Output($::opt_o); Define_Rack_Filter($::opt_r); Define_Dynamic_Subs($::opt_b, $::opt_B, $::opt_t); Parse_Log; { my $elapsed_time = time - $global::StartTime; warn "\n\007Loaded file in $elapsed_time second" . ($elapsed_time == 1 ? ".\n" : "s.\n"); } Print_Report; warn "\nComplete.\n"; } sub Parse_Command_Line () { my @argv = split(/:/, $0); @argv = split(/\s+/, pop @argv); shift(@argv); unshift(@::ARGV, @argv); $::opt_b = undef; $::opt_B = undef; $::opt_e = 0; $::opt_h = 1E50; $::opt_H = 0; $::opt_m = 0; $::opt_o = undef; $::opt_p = 0; $::opt_q = 0; $::opt_r = ''; $::opt_t = 0; $::opt_v = 0; &Getopts('b:Beh:Hmo:qpr:t-:v') || Usage; Usage if (defined $::opt_b) && $::opt_b !~ /^\d+$/; } # ($index, $delta) = Parse_Coordinates $string sub Parse_Coordinates ($) { $_[0] =~ /^([A-O])([1-9]|1[0-5])$/ ? ($2*15+ord($1)-80, 15) # assumes ASCII or equivalent encoding : $_[0] =~ /^([1-9]|1[0-5])([A-O])$/ ? ($1*15+ord($2)-80, 1) # assumes ASCII or equivalent encoding : confess "Can't parse coordinate '$_[0]'\n"; } sub Parse_Log () { my $qFirstIteration = 1; load:while (<>) { # if in preamble or first line of simulation if (1../^[a-z?]+:/) { # line of board if (/^\S\s\S\s\S\s\S\s\S\s/) { tr/\-*$#+a-zA-Z/.=\-"'A-Za-z/; tr/.'"\-=a-zA-Z//dc; $global::Board .= $_; next; } # initial scores elsif (/^\d/) { @global::InitialScores = split(/\s+/, $_); next; } # initial racks elsif (!/:/) { push(@global::InitialRacks, $_); next; } } # check for and ignore final partial line unless(/\n$/) { warn "Aborting on incomplete line: $_\n"; last; } # report periodically during long runs unless ($. % 2500) { my $elapsed_time = time - $global::StartTime; my $seconds = $elapsed_time % 60; $elapsed_time = ($elapsed_time - $seconds) / 60; my $minutes = $elapsed_time % 60; my $hours = ($elapsed_time - $minutes) / 60; printf STDERR "%d:%02d:%02d %6d lines\n", $hours, $minutes, $seconds, $.; } my @fields; my $equity; my $maven_equity; { my $opponent_rack; # split line into fields ($opponent_rack, $global2::choice, @fields) = split(/\t/); die "Aborting because this shouldn't appear in a log file:\n$_" unless $#fields >= 0; # check for required tiles in opponent rack next load unless &filter($opponent_rack); } print if $::opt_p; # set up data structures first time this $global2::choice is encountered $global2::pp = $global::Play{$global2::choice}; if ($qFirstIteration && !defined $global2::pp) { $global2::pp = $global::Play{$global2::choice} = { # 'chs', 0, # choice score - set below 'eh', {}, # equity histogram 'n', [], # number of iterations that reached this depth 'ne', 0, # number of equity values summed = number of runs 's2ne', 0, # sum of square of net equity 'sne', 0, # sum of net equity 's2re', 0, # sum of square of residual equity 'sre', 0, # sum of residual equity 's2ts', [], # sum of squares of turn scores 'sts', [], # sum of turn scores 'tp', [], # tiles played }; if (defined $::opt_b) { $global::DiagramCount{$global2::choice} = 0; $global::DiagramUsage{$global2::choice} = [ (0) x 225 ]; $global::DiagramValue{$global2::choice} = [ (0) x 225 ]; } if (defined $::opt_B) { $global::GDiagramCount{$global2::choice} = 0; $global::GDiagramValue{$global2::choice} = [ (0) x 225 ]; } if ($global2::choice =~ /^-/) { $global::Board{$global2::choice} = $global::Board if $::opt_t; $global2::pp->{'chs'} = 0; $equity = 0; } else { my ($word, $coord, $score) = ($global2::choice =~ /^ *([a-z]+) ([A-O]\d\d?|\d\d?[A-O]) +(\d+)$/); if (!defined $score) { warn "Can't parse '$global2::choice'.\nAborting load"; last; } if ($::opt_B || $::opt_t || defined $::opt_b) { $global::Board{$global2::choice} = $global::Board; my ($index, $delta) = Parse_Coordinates $coord; Place_Word $global::Board{$global2::choice}, $word, $index, $delta; } $global2::pp->{'chs'} = $score; $equity = $score; } } else { $qFirstIteration = 0; $equity = $global2::pp->{'chs'}; } $maven_equity = pop @fields; $global2::turn = 0; for my $tPlay (@fields) { # set up data structures on first hit at this depth unless ($global2::pp->{'n'}[$global2::turn]++) { $global2::pp->{'s2ts'}[$global2::turn] = 0; # sum of square of score $global2::pp->{'sts'}[$global2::turn] = 0; # sum of score if ($::opt_t) { $global2::pp->{'tp'}[$global2::turn] = []; } } if ($::opt_t) { $global::Board[$global2::turn] = $global2::turn ? $global::Board[$global2::turn-1] : $global::Board{$global2::choice}; } # process move if ($tPlay =~ /^-/) { # &analyse_pass uses $choice, $pp, $turn from $global2:: for efficiency reasons &analyse_pass; } else { ($global2::tWord, $global2::tCoord, $global2::tScore) = ($tPlay =~ /^ *([a-z]+) ([A-O]\d\d?|\d\d?[A-O]) +(\d+)$/); if (!defined $global2::tScore) { warn "Can't parse '$tPlay'.\nAborting load"; last load; } # &analyse_word silently imports $choice, $turn, $tWord, $tCoord, $tScore from $global2:: for efficiency reasons &analyse_word; $global2::pp->{'s2ts'}[$global2::turn] += $global2::tScore*$global2::tScore; $global2::pp->{'sts'}[$global2::turn] += $global2::tScore; $equity += $global2::turn % 2 ? $global2::tScore : -$global2::tScore; } $global2::turn++; } $global::MinTurn = $global2::turn if $global::MinTurn > $global2::turn; $global::MaxTurn = $global2::turn if $global::MaxTurn < $global2::turn; # tote up equity $maven_equity =~ s/^\s*=\s*// || do { warn "Can't parse '$maven_equity'.\nAborting load"; last load; }; # residual equity { my $residual_equity = $maven_equity - $equity; $global2::pp->{'s2re'} += $residual_equity * $residual_equity; $global2::pp->{'sre'} += $residual_equity; } # net equity; $global2::pp->{'s2ne'} += $maven_equity * $maven_equity; $global2::pp->{'sne'} += $maven_equity; # equity count $global2::pp->{'ne'}++; # equity histogram if ($::opt_e) { my $theEquity = $::opt_m ? 0+$maven_equity : $equity; $global2::pp->{'eh'}{$theEquity}++; # min, max equity $global::MinEquity = $theEquity if $theEquity < $global::MinEquity; $global::MaxEquity = $theEquity if $theEquity > $global::MaxEquity; } last if $. > $::opt_h; } } # $number_of_tiles_placed = Place_Word $board, $word, $index, $delta sub Place_Word ($$$$) { my ($board, $word, $index, $delta) = @_; confess "PW($board,$word,$index,undef)\n" unless defined $delta; if ($delta == 1) { # horizontal my $what_was_on_board = substr($board, $index, length($word)); substr($_[0], $index, length($word)) = $word; # print "Replaced '$what_was_on_board' with '$_[1]' at offset $_[2] and delta $_[3].\n"; $what_was_on_board =~ tr/A-Za-z//dc; } else { # vertical my $placed = 0; for my $tPW1 (0..length($word)-1) { $placed++ if substr($_[0], $index, 1) !~ /^[A-Za-z]/; substr($_[0], $index, 1) = substr($word, $tPW1, 1); $index += $delta; } $placed; } } sub Print_Report () { # print header information if ($::opt_H) { print "\n"; print "\n"; print "Maven Simulation Analysis\n"; print "\n"; print "\n"; print "\n"; print "Back to Scrabble Analysis\n"; print "

Maven Simulation Analysis

\n"; print "

Table of Contents

\n"; print "\n"; } unless ($::opt_q) { print "====================\n" unless $::opt_H; print "This is a simulation analysis prepared by "; &htmlref('http://www.math.utoronto.ca/~jjchew/software/pams/', 'pams'), print " v.$global::kVersion ("; &htmlref('http://synge.math.utoronto.ca:7777/look/p', 'poslfit'), print "'s analyser of\n"; print "Maven simulations), a Perl script that runs on Macintosh, Unix, DOS and other\n"; print "platforms and analyses logs generated by Brian Sheppard's Maven program.\n"; print HTML_Only '

'; print "\n"; Heading_3 'Initial Board Diagram', 'ibd'; &htmlpre(1); print " A B C D E F G H I J K L M N O\n"; for my $i (0..14) { printf "%2d", $i+1; for my $j (0..14) { print ' ', substr($global::Board, 15*$i+$j, 1); } print "\n"; } &htmlpre(0); } chop @global::InitialRacks; Heading_3 'Initial Conditions', 'ic'; print HTML_Only '

\n"; # find widest play title $global::MaxWidth = 0; for my $play (keys %global::Play) { my $title = $play; $title =~ s/^\s*(.*\S)\s+\d+$/$1/; $title =~ s/- +/-/; $global::Play{$play}{'title'} = $title; my $length = length($title); $global::MaxWidth = $length if $length > $global::MaxWidth; } $global::LabelFormat = "%13s "; $global::ValueIntFormat = "%${global::MaxWidth}d "; $global::ValueStringFormat = "%${global::MaxWidth}s "; $global::ValueScoreFormat = "%${global::MaxWidth}.2f "; # sort column titles @global::Plays = sort { ($global::Play{$a}{'ne'} > 0 && $global::Play{$b}{'ne'} > 0) ? ($global::Play{$b}{'sne'}/$global::Play{$b}{'ne'} <=> $global::Play{$a}{'sne'}/$global::Play{$a}{'ne'} || $global::Play{$a}{'title'} cmp $global::Play{$b}{'title'} ) : $global::Play{$a}{'title'} cmp $global::Play{$b}{'title'} } keys %global::Play; # begin 'stats by play and turn' section Heading_3 'Statistics by Play and Turn', 'spt'; # print column titles &htmlpre(1); printf $global::LabelFormat, ''; for my $p (@global::Plays) { printf $global::ValueStringFormat, $global::Play{$p}{'title'}; } print "\n"; # print number of iterations performed for each play printf $global::LabelFormat, 'Iterations:'; for my $p (@global::Plays) { printf $global::ValueIntFormat, $global::Play{$p}{'ne'}; } print "\n"; if ($global::MinTurn != $global::MaxTurn) { # print mean iteration depth for each play printf $global::LabelFormat, 'Mean Depth:'; for my $p (@global::Plays) { my $tSum = 0; my $np = $global::Play{$p}->{'n'}; for my $n (@$np) { $tSum += $n; } printf $global::ValueScoreFormat, $tSum/$np->[0]; } print "\n"; } # print first play score for each play printf $global::LabelFormat, 'MyScore[0]:'; for my $p (@global::Plays) { printf $global::ValueScoreFormat, $global::Play{$p}{'chs'}; } print "\n"; # print rest of play scores and %-reached statistics for my $turn (0..$global::MaxTurn-1) { printf $global::LabelFormat, ('OppScore[','MyScore[')[$turn%2].(1+($turn>>1)).']:'; for my $p (@global::Plays) { my $pp = $global::Play{$p}; my $n = $pp->{'n'}[$turn]; if ($n) { printf $global::ValueScoreFormat, $pp->{'sts'}[$turn]/$n; } else { printf $global::ValueStringFormat, ''; } } print "\n"; # 95% confidence interval is +- 1.96*sd/sqrt(n) # where sd := sqrt((sum2-(sum**2)/n)/(n-1)) printf $global::LabelFormat, '95% +- '; for my $p (@global::Plays) { my $pp = $global::Play{$p}; my $n = $pp->{'n'}[$turn]; if (defined $n && $n > 1) { printf $global::ValueScoreFormat, 1.96*sqrt(($pp->{'s2ts'}[$turn]-$pp->{'sts'}[$turn]**2/$n)/($n*$n-$n)); } else { printf $global::ValueStringFormat, ''; } } print "\n"; if ($global::MinTurn != $global::MaxTurn) { printf $global::LabelFormat, 'Reached%:'; for my $p (@global::Plays) { my $pp = $global::Play{$p}; my $n = $pp->{'n'}[$turn] || 0; printf $global::ValueScoreFormat, 100*$n/$pp->{'ne'}; } print "\n"; } if ($::opt_t) { for my $tp (0..7) { printf $global::LabelFormat, "${tp}lp%:"; for my $p (@global::Plays) { my $pp = $global::Play{$p}; my $n = $pp->{'tp'}[$turn][$tp] || 0; printf $global::ValueScoreFormat, 100*$n/$pp->{'ne'}; } print "\n"; } } } # print maven equity statistics for each play printf $global::LabelFormat, 'ResidualEq:'; for my $p (@global::Plays) { printf $global::ValueScoreFormat, $global::Play{$p}{'sre'}/$global::Play{$p}{'ne'}; } print "\n"; printf $global::LabelFormat, '95% +- '; for my $p (@global::Plays) { my $pp = $global::Play{$p}; my $n = $pp->{'ne'}; if ($n > 1) { printf $global::ValueScoreFormat, 1.96*sqrt(($pp->{'s2re'}-$pp->{'sre'}**2/$n)/($n*$n-$n)); } else { printf $global::ValueStringFormat, "#N/A"; } } print "\n"; printf $global::LabelFormat, 'NetEquity:'; for my $p (@global::Plays) { printf $global::ValueScoreFormat, $global::Play{$p}{'sne'}/$global::Play{$p}{'ne'}; } print "\n"; printf $global::LabelFormat, '95% +- '; for my $p (@global::Plays) { my $pp = $global::Play{$p}; my $n = $pp->{'ne'}; if ($n > 1) { printf $global::ValueScoreFormat, 1.96*sqrt(($pp->{'s2ne'}-$pp->{'sne'}**2/$n)/($n*$n-$n)); } else { printf $global::ValueStringFormat, "#N/A"; } } &htmlpre(0); unless ($::opt_q) { Heading_4 'Legend:'; &htmldl( "Iterations ", "number of analysed simulation runs (involving this choice)", ($global::MinTurn != $global::MaxTurn) ? ("Mean Depth ", "average number of turns simulation runs lasted") : (), "MyScore[n] ", "average number of points you score on your nth turn", "OppScore[n]", "average number of points opponent scores on his/her nth turn", "95% +- ", "95% confidence margin for preceding figure", ($global::MinTurn != $global::MaxTurn) ? ("Reached% ", "percentage of runs which reach this depth") : (), $::opt_t ? ("0lp%, etc. ", "percentage of runs where 0, 1, 2, ..., 7 tiles get played") : (), "ResidualEq ", "equity unaccounted for by raw scores (final rack leave, etc.)", "NetEquity ", "equity as reported by Maven", ); } # print board diagram statistics if (defined $::opt_b) { for my $k (0..1) { my $title = ('Square Hit Probabilities', 'Square Values')[$k]; my $tag = ('shp', 'sv')[$k]; Heading_3 $title, $tag; unless ($::opt_q) { if (!$k) { print "Square Hit Probabilities are the percentage of runs in which the main word\n"; print "played covers a particular square.\n\n"; print HTML_Only "Large values are shaded orange to red.\n"; } else { print "Square Values are the average number of points scored by plays whose main\n"; print "word covers a particular square.\n\n"; print HTML_Only "Large values are shaded orange to red.\n"; } } for my $p (@global::Plays) { my $pos = 0; Heading_4 sprintf("$title for %sTurn[%d] when MyTurn[0]='%s':\n", ('Opp','My')[$::opt_b % 2], 1 + ($::opt_b >> 1), $global::Play{$p}{'title'}); &htmlpre(1); print " A B C D E F G H I J K L M N O\n"; for my $i (0..14) { printf "%2d ", $i+1; for my $j (0..14) { if ($global::DiagramUsage{$p}[$pos]) { my $v = $k ? $global::DiagramValue{$p}[$pos]/$global::DiagramCount{$p} : 100*$global::DiagramUsage{$p}[$pos]/$global::DiagramCount{$p}; print Colour_Text $v>=20 ? 'FF0000' : $v>=10 ? 'FF6000' : $v>=5 ? 'FFA000' : '', sprintf('%5.1f', $v); } else { printf " %s ", substr($global::Board{$p}, 15*$i+$j, 1); } $pos++; } print "\n"; } &htmlpre(0); } # for $p (@global::Plays) } # for $k } # if (defined $::opt_b) # print global board diagram statistics if ($::opt_B) { Heading_3 'Total Expected Square Value Diagrams', 'tesvd'; unless ($::opt_q) { print "\n"; print "The following tables show for each square the average number of points that\n"; print "plays whose main word covers that square contributes to the final score.\n"; print "Positive values indicate hotspots in our favour, negative values show hotspots\n"; print "in opponent's favour.\n"; print HTML_Only "Values are colour-coded: red (below -10), orange (below -5), ". "light blue (above +5), blue (above +10)."; } print "\n"; for my $p (@global::Plays) { my $pos = 0; Heading_4 "Total Expected Square Values when MyTurn[0]='$global::Play{$p}{'title'}':\n"; &htmlpre(1); print " A B C D E F G H I J K L M N O\n"; for my $i (0..14) { printf "%2d ", $i+1; for my $j (0..14) { if ($global::GDiagramValue{$p}[$pos]) { my $v = $global::GDiagramValue{$p}[$pos]/$global::Play{$p}{'ne'}; print Colour_Text $v<=-10 ? 'FF0000' : $v<=-5 ? 'FFA000' : $v>=10 ? '0000FF' : $v>=5 ? '00A0FF' : '', sprintf("%5.1f", $v); } else { printf " %s ", substr($global::Board{$p}, 15*$i+$j, 1); } $pos++; } print "\n"; } &htmlpre(0); print "\n"; } # for $p (@global::Plays) } # if (defined $::opt_B) # print equity distribution if ($::opt_e) { my %cumEH; if ($global::MinTurn != $global::MaxTurn && !$::opt_m) { warn "You should use '-m' for endgame analyses.\n"; } for my $p (@global::Plays) { $cumEH{$p} = 0; } Heading_3 'Equity Distribution', 'ed'; unless ($::opt_q) { print "The tabulated value is the percentage of runs in which the difference in scores\n"; print "has been changed by an amount less than or equal to the value in the first column.\n"; print "\n"; print HTML_Only "Best value is shown in dark blue, second best in light blue.\n"; } # print column titles &htmlpre(1); printf $global::LabelFormat, ''; for my $p (@global::Plays) { printf $global::ValueStringFormat, $global::Play{$p}{'title'}; } print "\n"; for my $equity ($global::MinEquity..$global::MaxEquity) { printf $global::LabelFormat, "$equity:"; my @percentages = (); for my $p (@global::Plays) { my $e = $global::Play{$p}{'eh'}{$equity}; $cumEH{$p} += $e if $e; push(@percentages, sprintf($global::ValueScoreFormat, 100*$cumEH{$p}/$global::Play{$p}{'ne'})); } my @sorted_percentages = (sort @percentages)[0,1]; for my $percentage (@percentages) { print Colour_Text $percentage == $sorted_percentages[0] ? '0000FF' : $percentage == $sorted_percentages[1] ? '00A0FF' : '', $percentage; } print "\n"; } &htmlpre(0); } print HTML_Only "Back to top\n"; } sub Report_Version_And_Quit () { die "$0 version $global::kVersion\n"; } sub Setup_Standard_Output ($) { my ($output) = @_; if (defined $output) { open(OUT, ">$output") || die "Can't creat($output): $!\n"; select(OUT); } } sub Usage () { die "Usage: $0 [-b turn] [-B] [-e] [-h lines] [-H] [-m] [-o file] [-p]\n" ." [-r tiles] [-v] [file...]\n" ."\n" ." -b turn display board usage diagrams for the numbered turn\n" ." -B display global board usage diagram\n" ." -e display equity distribution table\n" ." -h lines read only first however many lines\n" ." -H output in HTML format\n" ." -m use Maven equity calculations in distribution tables\n" ." -o file write output to file\n" ." -p print parsed lines for debugging purposes\n" ." -q print terse (quiet) version of report\n" ." -r tiles analyse only those racks where opponent hold tiles\n" ." -t display number of tiles played each turn\n" ." -v display version number\n" ; }