#!/usr/local/bin/perl -w

### pams - poslfit's analyser of Maven simulations
###
### (C) 1999 by John J. Chew, III <jjchew@math.utoronto.ca>
### 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 ? "<b>$string</b>" : $string;
	}

# $string = Colour_Text $colour, $string
sub Colour_Text ($$) { 
	my ($colour, $string) = @_;
	(defined $colour && length($colour) && $::opt_H)
		? "<font color=\"#$colour\">$string</font>"
		: $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"
<h3><a name="$tag">$title</a></h3>
<a href="#toc">Back to top</a><p>
EOS
		: "\n$title\n\n"; 
	}

sub Heading_4 ($) { my ($title) = @_;
	print $::opt_H ? "<h4>$title</h4>\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 "<dl>\n";
		while ($#_ > 0) {
			print '<dt>' . shift(@_) . "</dt>\n";
			print '<dd>' . shift(@_) . "</dd>\n";
			}
		print "</dl>\n";
		}
	else {
		while ($#_ > 0) {
			print shift(@_) . " = ";
			print shift(@_) . "\n";
			}
		}
	}
 
# &htmlpre($bool);
sub htmlpre ($) { if ($::opt_H) { print $_[0] ? "<pre>\n" : "</pre>\n"; } }

# &htmlref($href, $label);
sub htmlref ($$) { print $::opt_H ? "<a href=\"$_[0]\">$_[1]</a>" : $_[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 "<html>\n";
		print "<head>\n";
		print "<title>Maven Simulation Analysis</title>\n";
		print "<link rev=made href=\"http://www.math.utoronto.ca/~jjchew/\">\n";
		print "</head>\n";
		print "<body>\n";
		print "<i><a href=\"index.html\">Back to Scrabble Analysis</a></i>\n";
		print "<h2>Maven Simulation Analysis</h2>\n";
		print "<h3><a name=toc>Table of Contents</a></h3>\n";
		print "<ul>\n";
		print "<li><a href=\"#ibd\">Initial Board Diagram</a>\n";
		print "<li><a href=\"#ic\">Initial Conditions</a>\n";
		print "<li><a href=\"#spt\">Statistics by Play and Turn</a>\n";
		print "<li><a href=\"#shp\">Square Hit Probabilities</a>\n";
		print "<li><a href=\"#sv\">Square Values</a>\n";
		print "<li><a href=\"#tesvd\">Total Expected Square Value Diagrams</a>\n";
		print "<li><a href=\"#ed\">Equity Distribution</a>\n";
		print "</ul>\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 '<p>';
		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 '<ul>';
	print"\n";
	print HTML_Only '<li>'; print Bold_Text 'Player Rack:'; print "   $global::InitialRacks[0]\n";
	print HTML_Only '<li>'; print Bold_Text 'Opponent Rack:'; 
		print length($::opt_r) ? "must contain '$::opt_r'" : "(no restrictions)","\n";
	print HTML_Only '<li>'; print Bold_Text 'Score:'; print "         @global::InitialScores\n";
	print HTML_Only "</ul>\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 "<a href=\"#toc\">Back to top</a>\n</body></html>";
	}

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"
		 ;
	}


