#! /usr/bin/perl -w

# perl code to turn an SGF file into TeX diagrams
#   Copyright (C) 1997 Reid Augustin reid@netcom.com
#                      1000 San Mateo Dr.
#                      Menlo Park, CA 94025 USA
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 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 General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA

use strict;
require 5.001;

my $version = "version 3.3";

my $myName = $0;             # full pathname of this file
$myName =~ s".*/"";          # delete any preceding path

$version = "(sgf2tex) $version" if($myName ne 'sgf2tex');

my $commandLine = $0;

my $ii;
for ($ii = 0; $ii < @ARGV; $ii++) {
    $commandLine .= " $ARGV[$ii]";
}


my $help = "
$myName [options] [file.sgf]

    -h | -help                print this message and exit
    -v | -version             print version number and exit
    -i | -in                  input file name (STDIN for standard input)
    -o | -out                 output file name (STDOUT for standard output)
    -t | -top                 top line in diagram
    -b | -bottom              bottom line in diagram
    -l | -left                leftmost line in diagram
    -r | -right               rightmost line in diagram
    -break | -breakList       a list of first move in each diagram
    -m | -movesPerDiagram     number of moves per diagram
    -d | -doubleDigits        label stones modulo 100
    -n | -newNumbers          begin each diagram with number 1
    -rv | -relativeVarNums    start each variation from 1
    -av | -absoluteVarNums    move numbers in variation == main line numbers
    -cv | -correlativeVarNums start main variations from 1 
    -rl | -repeatLast         repeat last move as first in next diagram
    -il | -ignoreLetters      ignore SGF letters
    -im | -ignoreMarks        ignore SGF marks
    -iv | -ignoreVariations   ignore SGF variations
    -ip | -ignorePass         ignore SGF pass moves
    -ia | -ignoreAll          ignore SGF letters, marks, variations, and passes
    -firstDiagram             first diagram to print
    -lastDiagram              last diagram to print
    -longComments             allow page breaks in comments
    -simple                   use a very simple TeX format
    -coords                   print coordinates
    -twoColumn                use two-column format
    -bigFonts                 use fonts magnified 1.2 times
    -texComments              \\, { and } in comments not modified
    -gap                      gap in points between diagrams (default: 12)
  
     The -i and -o options are not needed with normal usage:
             $myName [options] name
 is equivalent to:
             $myName [options] -i name -o name.tex 
 or          $myName [options] -i name.sgf -o name.tex

     The breakList consists of a comma-separated list of numbers (NO
 spaces). Each number will be the last move in one diagram.
 -movesPerDiagram sets an upper limit on the number of moves per
 diagram. The default movesPerDiagram is 50 unless a breakList
 (without -movesPerDiagram) is set, in which case movesPerDiagram is
 set to a very large number. -breakList and -movesPerDiagram may be
 combined.

     -doubleDigits and -newNumbers are alternative schemes for avoiding
 large numerals.  -doubleDigits limits stone numbers to be between 1 and 100.
 Stone number 101 prints as 1.  -newNumbers causes each diagram to start with
 number 1.

     By default, variation diagrams start with stone number 1
 (-relativeVarNums).  Alternatively, variation numbers can be the same
 as the numbers in the main diagram (-absoluteVarNums) or they can start
 from 1 at the beginning of each variation tree (-correlativeVarNums).

     -longComments implements more elaborate TeX to allow page breaks
 in the comments. Use this if comments are very extensive. It cannot
 be used with -simple or -twoColumn.

     -simple uses a very simple TeX format. This option may be useful
 if you intend to edit the resulting TeX file by hand. -longComments
 and -simple should not be used together.

      -twoColumn uses a two-column format with small fonts. This cannot 
 be used with -coords or -longComments.

      -coords prints a coordinate grid. It cannot be used with
 -twoColumn.

      -texComments is appropriate if your sgf comments contain TeX.
 If this option is NOT used, \\ { and } are replaced by /, [ and ]
 since these characters are not available in TeX roman fonts. If
 this -texComments is used, this change is not made so you can
 put code to {\\bf change fonts} in your comments.

";

my $topMacros =
"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% This file was created by $myName $version with the following command line:
%
% $commandLine
%
% $myName was created by Reid Augustin.  The go fonts, TeX
% macros and TeX programming were designed by Daniel Bump.
%
% More information about the $myName package can be found at:
%
%               http://match.stanford.edu/bump/go.html
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%\n";
my $normalMacros="\\magnification=1200
\\input gooemacs
\\gool
\\newdimen\\diagdim
\\newdimen\\fulldim
\\newbox\\diagbox
\\newbox\\captionbox\n";
my $simpleMacros="\\magnification=1200
\\input gooemacs
\\raggedbottom
\\parindent=0pt\n";
my $twoColumnMacros="\\magnification=1200
\\input gotcmacs
\\raggedbottom
\\tolerance=10000
\\parindent=0pt\n";

# a blank board
my $blankBoard = 
'<[[[[[[[[[[[[[[[[[,
(+++++++++++++++++)
(+++++++++++++++++)
(++*+++++*+++++*++)
(+++++++++++++++++)
(+++++++++++++++++)
(+++++++++++++++++)
(+++++++++++++++++)
(+++++++++++++++++)
(++*+++++*+++++*++)
(+++++++++++++++++)
(+++++++++++++++++)
(+++++++++++++++++)
(+++++++++++++++++)
(+++++++++++++++++)
(++*+++++*+++++*++)
(+++++++++++++++++)
(+++++++++++++++++)
>]]]]]]]]]]]]]]]]].';

my $nineBoard = 
'<[[[[[[[,
(+++++++)
(+*+++*+)
(+++++++)
(+++*+++)
(+++++++)
(+*+++*+)
(+++++++)
>]]]]]]].';

my $thirteenBoard =
'<[[[[[[[[[[[,
(+++++++++++)
(+++++++++++)
(++*++*++*++)
(+++++++++++)
(+++++++++++)
(++*++*++*++)
(+++++++++++)
(+++++++++++)
(++*++*++*++)
(+++++++++++)
(+++++++++++)
>]]]]]]]]]]].';


# some global variables
use vars '%sgf';                # various game and printing info
use vars '%history';            # history of moves on each intersection
use vars '@moveColor';          # list of color for each move number
use vars '@nodeMove';           # move numbers corresponding to nodes
use vars '%board';              # reflects the current state of the board
use vars '%comment';            # store comments for each move number
use vars '%mark';               # SGF marks for each intersection
use vars '%letter';             # sgf letters for each intersection
use vars '$variationNum';       # the current variation number (like Variation 2.8.4)
use vars '%label';              # SGF labels on stones

my ($inHandle, $outHandle,  $inFileName, $outFileName, $verbose, $variationDepth);
my (@breakList, %texVariation, $fileID);
$variationDepth = 0;

my (@fileID);           # this array will hold 'ungotten' chars from $fileID
#my ($gotten) = '';      # this is helpful when debugging to see how much of the file has been read

# code to handle sgf file formats

sub getC {
    my ($fileID) = @_;
    my ($chr);

    if (scalar(@fileID)) {
        $chr = shift(@fileID);
    } else {
        $chr = getc($fileID);
#        if (defined($chr)) {
#            $gotten .= $chr;
#        }
    }
    return($chr);
}

sub ungetC {
    my ($fileID, $chr) = @_;

    unshift(@fileID, $chr);
}

sub skipToToken {
    my ($fileID) = @_;
    my ($chr);

    for(;;) {
        $chr = getC($fileID);
        if ($chr =~ m/\S/) {
            return($chr);       # non-blank
        }
        if (($chr eq '') && eof($fileID)) {
            last;
        }
    }
    return('');
}

sub printVerbose {
    my (@msg) = @_;

    printIndent(@msg) if ($verbose);
}

sub printIndent {
    my (@msg) = @_;

    print(' ' x $variationDepth);
    print(@msg);
}

sub SGF_ReadFile {
    my ($fileID) = @_;
    my ($chr, $start);

    $start = 1;
    for(;;) {
        $chr = getC($fileID);                   # collect chars til we see '(' as first char on a line
                                                # this allows emails and news postings to be read - the
                                                # pre-amble will get skipped here.
        if ($start && ($chr eq '(')) {
            $sgf{DiagramNum} = 0;
            $sgf{DiaFirstNode} = 0;
            $sgf{ParentVariation} = 0;          # root has no parent
            $sgf{IsContinuation} = 1;           # root isn't really a continuation, but it needs to act like one
            $sgf{VariationStartNode} = 1;
            $sgf{NumberOffset} = 0;             # start with no offset
            $sgf{BoardChange} = 0;
            $variationNum = '0';
            SGF_ProcessVariation ($fileID, 0);  # got the first line of the SGF part
            return;
        } elsif (($chr eq '') && eof($fileID)) {
            die("Couldn't find the start of the SGF part (no \"(\" as first character on a line).\n");
        }
        $start = ($chr eq "\n");
    }
}

sub SGF_ProcessVariation {
    my ($fileID) = @_;
    my ($chr);
    my ($unexpected) = '';

    $variationDepth++;
#printIndent("SGF_ProcessVariation variatioDepth is now $variationDepth\n");
    $chr = skipToToken($fileID);        # we start off inside the first '('
    while ($chr ne '') {
        if (($unexpected ne '') && 
            (($chr eq '(') || ($chr eq ')') || ($chr eq ';')) ) {
            print (STDERR "Unexpected input: $unexpected\n");
            $unexpected = '';
        }
        if ($chr eq '(') {      # start of a variation
            if ($sgf{IgnoreVariations}) {
                # just ignore it - the main line is next, continue parsing it
            } else {
                SGF_ProcessGameTree($fileID);
                #return;         # we're done
            }                   # and now continue with the main line
        } elsif ($chr eq ')') { # end of a variation
            printVerbose('Variation end: ');
            PrintDiagram($sgf{NodeNum});
            $variationDepth--;
            return;
        } elsif ($chr eq ';') { # a node in a sequence
            SGF_ProcessNode($fileID);
        } else {
            $unexpected .= $chr;
        }
        if (($variationDepth <= 1) && ($#breakList >= 0) && ($sgf{MoveNum} >= $breakList[0])) {
            printVerbose('BreakList: ');
            PrintDiagram($sgf{NodeNum});
            shift(@breakList);
        }
        if (($sgf{MoveNum} - $sgf{DiaLastMove}) >= $sgf{MovesPerDiagram}) {
            printVerbose('MovesPerDiagram: ');
            PrintDiagram($sgf{NodeNum});
        }
        $chr = skipToToken($fileID);
    }
    die("SGF_ProcessVariation: end of file without closing \")\".\n");
}

sub SGF_ProcessGameTree {
    my ($fileID) = @_;
    my ($chr, $nestLevel, @mainLine, $parentVariation, $start);

#printIndent("SGF_ProcessGameTree variationDepth = $variationDepth \n");
    $nestLevel = 1;                     # we already got the first '('
    for (;;) {
        $chr = getC($fileID);
        last if (($chr eq '') && eof($fileID));
        push(@mainLine, $chr);
        if ($chr eq "\\") {
            $chr = getC($fileID);       # take next char literally
            push (@mainLine, $chr);
        } elsif ($chr eq ')') {         # this is the end of the main line
            last unless (--$nestLevel)  # we've scanned through the mainLine and are at the variations
        } elsif ($chr eq '(') {
            $nestLevel++;
        } elsif ($chr eq '[') {         # start of property list
            while ($chr ne ']') {
                $chr = getC($fileID);
                if (($chr eq '') && eof($fileID)) {
                    die("SGF_ProcessGameTree: end of file without closing \"]\".\n");
                }
                push(@mainLine, $chr);
                if ($chr eq "\\") {
                    $chr = getC($fileID);       # take next char literally
                    push (@mainLine, $chr);
                }
            }
        }
    }
    {
        local $sgf{ParentVariation} = $variationNum;
        local $sgf{VariationParentStartNode} = $sgf{VariationStartNode};
        local $variationNum = $variationNum if ($variationDepth > 1);
        $variationNum .= '.0' if ($variationDepth > 1);
        #$variationNum =~ s/^\.//;
        for (;;) {                          # process the variations
            $chr = getC($fileID);
            last if (($chr eq '') && eof($fileID));
            if (($chr eq '(') && $start) {
                local %sgf = %sgf;
                local %history = %history;
                local @moveColor = @moveColor;
                local @nodeMove = @nodeMove;
                local %board = %board;
                local %comment;             # don't pass comments, marks, labels, or letters into variations
                local %mark;
                local %label;
                local %letter;
                if ($variationDepth <= 1) {
                    $sgf{VariationRootStartNode} = $sgf{NodeNum} + 1;  # save first branch off main line
                }
                $sgf{VariationStartNode} = $sgf{NodeNum} + 1;   # set the starting number to the next move
                $sgf{MoveThatSpawnedVar} = $sgf{MoveNum};       # set the move that spawned this variation
                $sgf{DiaLastMove} = $sgf{MoveNum} - 1;          # we're really starting from here
                $sgf{IsContinuation} = 0;                       # starting a new variation sequence
                $sgf{BoardChange} = 0;
                IncVariation();
                printIndent("Parsing variation $variationNum\n");
                SGF_ProcessVariation($fileID);
#printIndent("done with variation $variationNum\n");
            } elsif ($chr eq ')') {
                last;                       # done with this set of variations
            } else {
                $start = ($chr eq "\n") || ($chr eq ' ');
            }
        }
    }
#printIndent("restored variation $variationNum\n");
    my @tmpFileID = (@mainLine, @fileID);       # restore mainLine into the ungotten array
    @fileID = @tmpFileID;                       # (version 5.001 seems to need to pass through this tmp array)
    #SGF_ProcessVariation($fileID);              # finish the main line
                    # return to finish the main line
}

sub IncVariation {
    for( ; ; ) {
        if ($variationNum =~ m/(.*\.)([0-9]*)$/) {            # increment the variation number
            $variationNum = $1 . ($2 + 1);
        } else {
            $variationNum++;
        }
        last unless(defined($texVariation{$variationNum}));
    }
}

sub SGF_ProcessNode {
    my ($fileID) = shift;
    my ($shortPropID, $propVal, @propList, $prop, $p);

    $sgf{NodeNum}++;
    $sgf{CurrentLetter} = 'a';         # start each node with a new set of letters

    # sadly, the properties can be in any order.  for example, you might have
    #    a mark before the stone that is to be marked.  so we'll accumulate
    #    all the properties and put them into some sane order.
    for(;;) {
        $shortPropID = SGF_GetShortPropID(SGF_GetPropID($fileID));
        last if ($shortPropID eq '');
        $prop = "$shortPropID\[\[";
        for(;;) {
            $propVal = SGF_GetPropVal($fileID);
            last if ($propVal eq '');
            $prop .= "$propVal\[\["
        }
        unshift(@propList, $prop);
    }
    @propList = (sort {PropOrder($::a, $::b)} @propList);
    foreach $p (@propList) {
        SGF_ProcessProperty($p);
    }
}

# specify order in which to process properties:
# played stones should be before adding and marking.
# node name should be before comments but after anything that affects the board
my %propOrder = ( 'W', 10,  'B', 10,
                 'AW', 20, 'AB', 20, 'AE', 20,
                  'M', 30,  'L', 30, 'MA', 30, 'LB', 30,
                  'N', 45,
                  'C', 50);

sub PropOrder {
    my ($propIDa, $propIDb) = @_;
    my ($aa, $bb);

    $propIDa =~ s/\[.*//s;
    $propIDb =~ s/\[.*//s;
    if (defined($propOrder{$propIDa})) {
        $aa = $propOrder{$propIDa};
    } else {
        $aa = 0;
    }
    if (defined($propOrder{$propIDb})) {
        $bb = $propOrder{$propIDb};
    } else {
        $bb = 0;
    }
    return($aa <=> $bb);
}

sub OneTimeInit {

    return unless (defined($topMacros));
    print($outHandle $topMacros);
    undef($topMacros);
    if (!$sgf{Simple}) {
        print($outHandle $normalMacros);
    } elsif ($sgf{TwoColumn}) {
        print($outHandle $twoColumnMacros); 
    } else {
        print($outHandle $simpleMacros);
    }
    SetGameTitle();
}

sub SGF_ProcessProperty {
    my ($prop) = shift;
    my ($shortPropID, @propVals, $color, $propVal);

    ($shortPropID, @propVals) = split(/\[\[/, $prop);
    while (scalar(@propVals)) {
        $propVal = shift(@propVals);
        if (($shortPropID eq 'W') ||
            ($shortPropID eq 'B')) {            # White and Black (coordList)
            OneTimeInit();
            $sgf{MoveNum}++;
            $nodeMove[$sgf{NodeNum}] = $sgf{MoveNum};
            $color = ($shortPropID eq 'B') ? 1 : 0;

            if ($propVal eq 'tt') {
                if (!$sgf{IgnorePass}) {
                    AddComment('Pass');
                }
                $moveColor[$sgf{MoveNum}] = 'pass';
            } else {
                PlayStone(SGF_GetCoords($propVal), $color);
                CheckForDeadGroups(SGF_GetCoords($propVal), $color);
            }
            $sgf{BoardChange}++;
            return;
        } elsif (($shortPropID eq 'AW') ||
                 ($shortPropID eq 'AB')) {      # AddWhite and AddBlack (coordList)
            OneTimeInit();
            $color = ($shortPropID eq 'AB') ? 1 : 0;
            AddStone(SGF_GetCoords($propVal), $color);
            $sgf{BoardChange}++;
        } elsif ($shortPropID eq 'C') {         # Comment
            AddComment($propVal);
            $sgf{BoardChange}++;
        } elsif ($shortPropID eq 'N') {         # Name (node name)
            AddName($propVal);
            $sgf{BoardChange}++;
	} elsif ($shortPropID eq 'SZ') {
	    $sgf{BoardSize} = $propVal;
            if ($sgf{BottomLine} > $sgf{BoardSize} ) {
                $sgf{BottomLine} = $sgf{BoardSize} - 1
            }
            if ($sgf{RightLine} > $sgf{BoardSize} ) {
                $sgf{RightLine} = $sgf{BoardSize} - 1 
            }
        } elsif ($shortPropID eq 'AE') {        # AddEmpty
            RemoveStone(SGF_GetCoords($propVal));
            $sgf{BoardChange}++;
        } elsif ($shortPropID eq 'HA') {        # HAndicap
            Handicap($propVal);
            return;
        } elsif ($shortPropID eq 'L') {         # letter
            if (!$sgf{IgnoreLetters}) {
                AddLetter(SGF_GetCoords($propVal), $sgf{CurrentLetter}++);
                $sgf{BoardChange}++;
            }
        } elsif (($shortPropID eq 'M') || ($shortPropID eq 'MA')) {     # mark
            if (!$sgf{IgnoreMarks}) {
                my ($x, $y) = SGF_GetCoords($propVal);
                AddMark($x, $y, GetColor($x, $y));
                $sgf{BoardChange}++;
            }
        } elsif ($shortPropID eq 'LB') {        # label
            if (!$sgf{IgnoreMarks}) {
                my ($item, $point, $label, $x, $y);
                foreach $item (split(/\s+/, $propVal)) {
                    ($label, $point) = split(/:/, $item);
                    if (length($point) < 2) {   # didn't work? hmmm, let's try the other way around
                        ($point, $label) = split(/:/, $item);
                    }
                    AddLabel(SGF_GetCoords($point), $label);
                }
                $sgf{BoardChange}++;
            }
        } elsif (($shortPropID eq 'VW') ||      # VieW
                 ($shortPropID eq 'FF') ||      # FileFormat
                 ($shortPropID eq 'GM') ||      # GaMe
                 ($shortPropID eq 'KO') ||      # KO
                 ($shortPropID eq 'PL') ||      # PLayer
                 ($shortPropID eq 'WL') ||      # WhiteLeft (time)
                 ($shortPropID eq 'BL') ||      # BlackLeft (time)
                 ($shortPropID eq 'WS') ||      # WhiteSpecies
                 ($shortPropID eq 'BS')) {      # BlackSpecies
            # ignore
        } else {
            unless(defined($sgf{$shortPropID})) {
                $sgf{$shortPropID} = '';
            }
            $sgf{$shortPropID} .= $propVal;
        }
    }
}

# a property is an ID followed by value(s)
# the propertyID consists of text
sub SGF_GetPropID {
    my ($fileID) = @_;
    my ($chr, $pos);
    my ($propID) = '';

    $chr = skipToToken($fileID);
    while ($chr ne '') {
        if ($chr =~ m/\w/) {
            $propID .= $chr;
        } else {
            ungetC($fileID, $chr);
            last;
        }
        $chr = getC($fileID);
    }
    return($propID);
}

# a property is an ID followed by value(s)
# proertyVals consists of stuff inside brackets []
sub SGF_GetPropVal {
    my ($fileID) = @_;
    my ($chr, $pos);
    my ($propVal) = '';

    $chr = skipToToken($fileID);
    if ($chr ne '[') {
        ungetC($fileID, $chr);
        return('');              # no more proerty values
    }
    for(;;) {
        $chr = getC($fileID);
        if ($chr eq "\\") {
            $propVal .= getC($fileID);
        } elsif ($chr eq ']') {
            return($propVal);
        } elsif (($chr eq '') && eof($fileID)) {
            print(STDERR "Unterminated property: $propVal\n");
            return($propVal);
        } else {
            $propVal .= $chr;
        }
    }
}

sub SGF_GetShortPropID {
    my ($propID) = @_;

    return('') unless (defined($propID));
    $propID =~ s/[a-z]//g;      # just get rid of all lower case letters
    return($propID);
}

sub SGF_GetCoords {
    my ($coords) = @_;
    my ($x, $y) = unpack('C2', $coords);

    return($x - 0x61, $y - 0x61);
}

# return the color and number (if any) of $x, $y in the current diagram.
#   Note that this is not the same as what the board currently indicates
#   as the stone may have already been removed.
sub GetDiagramStone {
    my($x, $y) = @_;
    my($h, $firstMove, $type, $nodeNum, $moveNum);
    my ($color, $number) = ('none', 'none');

    $firstMove = $sgf{DiaLastMove};
    if (!$sgf{RepeatLast}) {
        $firstMove++;
    }
    if (defined($history{$x,$y})) {
        foreach $h (split(/;/, $history{$x,$y})) {
            # $type is one of these choices:
            #  -1     = removed stone
            #  0      = white stone
            #  1      = black stone
            ($nodeNum, $type) = split(/ /, $h);
            $moveNum = GetNodeMoveNum($nodeNum);
            if ($moveNum <= $firstMove) {        # in previous diagrams
                if (($type eq '0') || ($type eq '1')) {
                    $color = $type;             # no number though
                } elsif ($type eq '-1') {
                    $color = 'none';            # stone has been removed
                }
            } else {                        # in the current diagram
                if (($color eq 'none') && (($type eq '0') || ($type eq '1'))) {
                    $color = $type;
                    $number = $moveNum;
                    last;
                }
            }
        }
    }
    return ($color, $number);
}

sub GetColor {
    my($x, $y) = @_;
    my($h, $firstMove, $nodeNum, $moveNum, $type);
    my($color) = '';

    # return 0 if $x,$y has a white stone
    #        1 if $x,$y has a black stone
    #        '' if no stone
    $firstMove = $sgf{DiaLastMove};
    if (!$sgf{RepeatLast}) {
        $firstMove++;
    }
    if (defined($history{$x,$y})) {
        foreach $h (split(/;/, $history{$x,$y})) {
            # $type is one of these choices:
            #  -1     = removed stone
            #  0      = white stone
            #  1      = black stone
            ($nodeNum, $type) = split(/ /, $h);
            $moveNum = GetNodeMoveNum($nodeNum);
            if ($moveNum <= $firstMove) {            # in previous diagrams
                $color = $type;
            } else {                                # in next diagram
                if (($type eq '0') || ($type eq '1')) {
                    $color = $type;
                    last;
                }
            }
        }
    }
    return ($color);
}

sub AddStone {
    my($x, $y, $color) = @_;

    printVerbose("Adding stone at $x,$y, color=$color, move=$sgf{MoveNum}\n");
    if (defined($letter{$x,$y})) {
        printVerbose('Add stone over Letter: ');
        PrintDiagram($sgf{NodeNum} - 1);
    }
    $board{$x,$y} = $color;
    unless(defined($history{$x,$y})) {
        $history{$x,$y} = '';
    }
    $history{$x,$y} .= "$sgf{NodeNum} $color;";
    $moveColor[$sgf{MoveNum}] = $color
}

sub PlayStone {
    my($x, $y, $color) = @_;

    printVerbose("Playing $x,$y, color=$color, move=$sgf{MoveNum}\n");
    if (defined($letter{$x,$y})) {
        printVerbose('Play over Letter: ');
        PrintDiagram($sgf{NodeNum} - 1);
    }
    my ($diaColor, $diaNumber) = GetDiagramStone($x, $y);
    if (($diaColor ne 'none') && ($diaNumber eq 'none')) {      # is this a numberless stone?
        FlushMarks($x, $y, $diaColor);                          # see of adding a mark would flush
        ($diaColor, $diaNumber) = GetDiagramStone($x, $y);      # and try again
        if (($diaColor ne 'none') && ($diaNumber eq 'none')) {
            AddMark($x, $y, $diaColor);                         # didn't flush - go ahead and add a mark
        }
    }
    $board{$x,$y} = $color;
    unless(defined($history{$x,$y})) {
        $history{$x,$y} = '';
    }
    $history{$x,$y} .= "$sgf{NodeNum} $color;";
    $moveColor[$sgf{MoveNum}] = $color
}

sub RemoveStone {
    my($x, $y, $color) = @_;

    printVerbose("Removing $x,$y, move $sgf{MoveNum}\n");
    $board{$x,$y} = -1;                 # remove the stone from the board
    unless(defined($history{$x,$y})) {  # shouldn't be able to remove unless it's already got a history, but...
        $history{$x,$y} = '';
    }
    $history{$x,$y} .= "$sgf{NodeNum} -1;";
}

sub TeXifyText {
    my ($comm) = @_;

    if ($sgf{texComments}) {
        $comm =~ tr/<>_/[]-/            # \{} are untouched if texComments is true
    } else {
        $comm =~ s/\\/\//g;             #  \\ -> / since cmr10 has no backslash
        $comm =~ tr/{<>}_/[[]]-/;       #  cmr10 has no {<>}_ so substitute [[]]-
    }
    $comm =~ s/([&~^\$%#])/\\$1/g;      #  escape &~^$%#

    if ($sgf{Simple}) {
        $comm .= "\n\n";                # just tack two newlines to the end
    } else {
        $comm .= "\\hfil\\break\n"                   # and tack on onto the end too
    }
    return($comm);
}

sub AddComment {
    my($comm) = @_;

    return unless(defined($comm) && ($comm ne ''));     # no comment? return
    printVerbose("Adding comment at move $sgf{MoveNum}:\n$comm\n");
    $comm = TeXifyText($comm);
    unless (defined($comment{$sgf{NodeNum}})) {
	$comment{$sgf{NodeNum}} = '';
    }
    $comment{$sgf{NodeNum}} .= $comm;   # and tack the comment onto the diagram collection of comments
    if (!$sgf{Simple}) {
        $comment{$sgf{NodeNum}} =~ s/\n(\s*\n)+/\n\\hfil\\break\n/g;      # replace \n\n by \hfil\break
    }
}

sub AddName {
    my($name) = @_;

    return unless(defined($name) && ($name ne ''));     # no name? return
    printVerbose("Adding name at move $sgf{MoveNum}:\n$name\n");
    PrintDiagram($sgf{NodeNum} - 1);                    # flush previous - names start a new diagram
    $name = TeXifyText($name);
    if (!$sgf{Simple}) {
        $name =~ s/\n(\s*\n)+/\n\\hfil\\break\n/g;      # replace \n\n by \hfil\break
    }
    $sgf{DiaName} = "{\\bf $name}";
}

sub AddLetter {
    my($x, $y, $letter) = @_;

    printVerbose("Adding letter \"$letter\" at $x,$y move $sgf{MoveNum}\n");
    if ($board{$x,$y} >= 0) {
        print(STDERR "SGF file has \"$letter\" over a stone at $x,$y move $sgf{MoveNum}\n");
        print(STDERR "    I didn't think that was supposed to be possible...\n");
    } else {
        if (defined($letter{PrevLetterNode}) && ($sgf{NodeNum} != $letter{PrevLetterNode})) {
            printVerbose('New Letter : ');
            PrintDiagram($sgf{NodeNum} - 1);
        }
        unless (defined($letter{$x,$y})) {
            $letter{$x,$y} = '';
        }
        $letter{$x,$y} .= "$letter";
        $letter{PrevLetterNode} = $sgf{NodeNum};
    }
}

sub AddMark {
    my($x, $y, $color) = @_;

    printVerbose("Adding mark at $x,$y, color=$color, move=$sgf{MoveNum}\n");
    FlushMarks($x, $y, $color);
    if ($color eq '1') {
        $mark{$x,$y} = "$sgf{MoveNum} $color";
        $mark{Black} = "$x,$y";
        $mark{BlackMoveNum} = $sgf{MoveNum};
    } elsif ($color eq '0') {
        $mark{$x,$y} = "$sgf{MoveNum} $color";
        $mark{White} = "$x,$y";
        $mark{WhiteMoveNum} = $sgf{MoveNum};
    } else {
        # turns out that marks can be in the SGF file before the
        #   stone that is being marked (*sigh*).  but we pre-sorted
        #   the properties in SGF_ProcessNode, so we should never
        #   try to mark a stone that's not here
        print(STDERR "Trying to mark at $x,$y on move $sgf{MoveNum} but there's no stone there!\n");
    }
}

sub FlushMarks {
    my ($x, $y, $color) = @_;

    # We'll only allow one black and one white set of marks.  If a requirement
    #   for a new mark occurs, we need to print the current diagram so that
    #   comments relating to marks are un-ambiguous.
    #
    if (exists($label{$x,$y})) {
            printVerbose("Flush mark over label: ");
            PrintDiagram($sgf{NodeNum} - 1);
    } elsif ($color eq '1') {
        if (defined($mark{Black}) && ($mark{Black} ne "$x,$y") &&
            defined($mark{BlackMoveNum}) && ($mark{BlackMoveNum} != $sgf{MoveNum})) {
            printVerbose("Flush black mark: ");
            PrintDiagram($sgf{NodeNum} - 1);
        }
    } elsif ($color eq '0') {
        if (defined($mark{White}) && ($mark{White} ne "$x,$y") &&
            defined($mark{WhiteMoveNum}) && ($mark{WhiteMoveNum} != $sgf{MoveNum})) {
            printVerbose("Flush white mark: ");
            PrintDiagram($sgf{NodeNum} - 1);
        }
    }
}

sub FlushLabels {
    my ($x, $y, $moveNum, $label) = @_;

    # detect labels going on top of marks
    if (exists($mark{$x,$y})) {
        printVerbose("Flush label over mark: ");
        PrintDiagram($sgf{NodeNum} - 1);
        return;
    }
    # implement the following truth table to flush ambiguous labels:
    #      ----------------------------------------
    #      |location  move number label |ambiguous|
    #      |----------------------------|---------|
    #   7  |  same      same      same  |  no     |
    #   6  |  same      same      diff  |  yes    | 
    #   5  |  same      diff      same  |  no     | 
    #   4  |  same      diff      diff  |  yes    | 
    #   3  |  diff      same      same  |  no     | 
    #   2  |  diff      same      diff  |  no     | 
    #   1  |  diff      diff      same  |  yes    | 
    #   0  |  diff      diff      diff  |  no     | 
    #      ----------------------------------------
    my ($other_moveNum, $other_label, $ambiguous);
    foreach(keys(%label)) {
        ($other_moveNum, $other_label) = split(/ /, $label{$_});
        $ambiguous = 4 * ("$x,$y" eq $_) + 2 * ($moveNum == $other_moveNum) + ($label eq $other_label);
        if (($ambiguous == 1) || ($ambiguous == 4) || ($ambiguous == 6)) {
            printVerbose("Flush ambiguous ($ambiguous) label: ");
            PrintDiagram($sgf{NodeNum} - 1);
            return;
        }
    }
}

sub AddLabel {
    my($x, $y, $label) = @_;

    my $color = GetColor($x, $y); 
    unless($color ne '') {
        print(STDERR "Trying to label at $x,$y on move $sgf{MoveNum} but there's no stone there!  I'll AddLetter instead.\n");
        AddLetter($x, $y, $label);
        return;
    }
    $label = uc(substr($label, 0, 1));      # make sure label is a single upper case char
    printVerbose("Adding label at $x,$y, color=$color, label=$label, move=$sgf{MoveNum}\n");
    $label = 401 + ord($label) - ord('A');      # white labeled stones are from 401 to 426
    $label += 100 unless ($color);              # labeled black stones are from 501 to 526
    FlushLabels($x, $y, $sgf{MoveNum}, $label);
    $label{$x,$y} = "$sgf{MoveNum} $label";
}

sub Handicap {
    my($num) = @_;

    if ($num >= 2) {
        AddStone(3, 15, 1);
        AddStone(15, 3, 1);
    }
    if ($num >= 3) {
        AddStone(15, 15, 1);
    }
    if ($num >= 4) {
        AddStone(3, 3, 1);
    }
    if (($num == 5) || ($num == 7) || ($num == 9)) {
        AddStone(9, 9, 1);
    }
    if ($num >= 6) {
        AddStone(3, 9, 1);
        AddStone(15, 9, 1);
    }
    if ($num >= 8) {
        AddStone(9, 3, 1);
        AddStone(9, 15, 1);
    }
    if ($num > 9) {
        print(STDERR "Unknown handicap: $num");
    }
}

sub CheckForDeadGroups {
    my ($x, $y, $color) = @_;
    my ($otherColor) = 1 ^ $color;

    CheckIfDead($x + 1, $y, $otherColor); # first check the four neighboring stones of the other color
    CheckIfDead($x - 1, $y, $otherColor);
    CheckIfDead($x, $y + 1, $otherColor);
    CheckIfDead($x, $y - 1, $otherColor);
    CheckIfDead($x, $y,     $color);      # and finally we need to check the stone just placed
}

sub CheckIfDead {
    my ($x, $y, $color) = @_;

    if (($x < 0) || ($x > ($sgf{BoardSize} - 1)) || ($y < 0) || ($y > ($sgf{BoardSize} - 1))) {
        return;         # oops! off the board.
    }
    if ($board{$x,$y} < 0) {
        return;         # no stone here
    }
    if ($board{$x,$y} == $color) {
        if (!HasLibs($x, $y, $color, 0)) {
              # no liberties? - it's dead!
            RemoveGroup($x, $y, $color);
        }
    }
}

sub PrintBoard {
    my ($x, $y);

    for ($x = 0; $x < $sgf{BoardSize}; $x++) {
        for ($y = 0; $y < $sgf{BoardSize}; $y++) {
            printf('<%3d>', $board{$x,$y});
        }
        print "\n";
    }
}

sub HasLibs {
    my ($x, $y, $color, $depth) = @_;
    my ($thisStone);

    if ($depth > 1000) {
        print(STDERR "Oops, recursion > 1000 while checking for liberties at move $sgf{MoveNum} coords ($x,$y)\n");
        print(STDERR "This isn't supposed to be possible!  Aborting...\n");
        exit(1);
    }
    if (($x < 0) || ($x > ($sgf{BoardSize}-1)) || ($y < 0) || ($y > ($sgf{BoardSize}-1))) {
        return(0);              # oops! off the board.
    }
    $thisStone = $board{$x,$y};
    if ($thisStone == -2) {
        return(0);              # we've been here before
    }
    if ($thisStone == -1) {
        return(1);              # this is empty, the group has liberties
    }
    if ($thisStone != $color) {
        return(0);              # this is an opponents stone - no liberties here!
    }
    # this is a connected stone of the same color
    $board{$x,$y} = -2;     # mark that we've been here
    $depth++;
    if (HasLibs($x + 1, $y, $color, $depth) || HasLibs($x - 1, $y, $color, $depth) ||
        HasLibs($x, $y + 1, $color, $depth) || HasLibs($x, $y - 1, $color, $depth)) {
        $board{$x,$y} = $thisStone; # restore the trail
        return(1);              # yes! we're alive!
    }
    $board{$x,$y} = $thisStone;     # restore the trail
    return(0);                  # uh-oh! no liberties yet...
}

sub RemoveGroup {
    my ($x, $y, $color) = @_;

    if (($x < 0) || ($x > ($sgf{BoardSize}-1)) || ($y < 0) || ($y > ($sgf{BoardSize}-1))) {
        return(0);              # oops! off the board.
    }
    if ($board{$x,$y} == $color) {
        RemoveStone($x, $y);
        RemoveGroup($x + 1, $y, $color);  # remove any connected stones of the same color
        RemoveGroup($x - 1, $y, $color);
        RemoveGroup($x, $y + 1, $color);
        RemoveGroup($x, $y - 1, $color);
    }
}

sub SetGameTitle {
    my ($title)='';
    my (@lines, $line);

    push(@lines, $sgf{GN});             # GameName
    if (defined($sgf{EV})) {
	if (defined($sgf{RO})) {
	    push(@lines, "$sgf{EV} - Round $sgf{RO}"); # EVent name and ROund number
        } else {
            push(@lines, $sgf{EV});     # EVent
        }
    }
    if (defined($sgf{PW})) {
        if(defined($sgf{WR})) {
            push(@lines, "{\\bf White:} $sgf{PW} $sgf{WR}");    # PlayerWhite and WhiteRank
        } else {
            push(@lines, "{\\bf White:} $sgf{PW}");             # PlayerWhite
        }
    }
    if (defined($sgf{PB})) {
        if(defined($sgf{BR})) {
            push(@lines, "{\\bf Black:} $sgf{PB} $sgf{BR}");    # PlayerBlack and BlackRank
        } else {
            push(@lines, "{\\bf Black:} $sgf{PB}");             # PlayerBlack
        }
    }
    if (defined($sgf{DT})) {push(@lines, $sgf{DT})};                             # DaTe
    if (defined($sgf{PC})) {push(@lines, $sgf{PC})};                             # PlaCe
    if (defined($sgf{GC})) {push(@lines, $sgf{GC})};                             # GameComment
    if (defined($sgf{KM})) {                                                     # komi
        if ($sgf{KM} =~ m/(\d+\.\d+?)0*$/) {
            # remove ugly trailing zeros supplied by IGS
            $sgf{KM} = $1;
        }
        push(@lines, "{\\bf Komi}: $sgf{KM}");
    }
    if (defined($sgf{RE})) {push(@lines, "{\\bf Result}: $sgf{RE}")};            # result
    if (defined($sgf{TM})) {push(@lines, "{\\bf Time}: $sgf{TM}")};              # time constraints
    foreach $line (@lines) {
        next unless (defined($line));
        $line =~ s/\\([][)(\\])/$1/g;   # change escaped chars to non-escaped
        $title .= "$line\\hfil\\break\n";
    }
    if($title ne '') {
        print($outHandle "{\\noindent\n$title\\vfil}\n\\nobreak\n");
    }
}

sub preamble {
    my ($diaHeight, $diaWidth) = @_;
    my $bmodifier = $sgf{BigFonts} ? 'b' : '';

    if ($sgf{TwoColumn}) {
        return(sprintf("\\vbox{\\vbox to %3.1f pt{\\hsize= %3.1f pt\\$sgf{GoFont}\n", $diaHeight, $diaWidth));
    }
    if ($sgf{Simple}) {
        return(sprintf("\\vbox to %3.1f pt{\\hsize= %3.1f pt\\%s$sgf{GoFont}\n", $diaHeight, $diaWidth, $bmodifier));
    }
    return(sprintf("\\setbox\\diagbox=\\vbox to %3.1f pt{\\hsize= %3.1f pt\\%s$sgf{GoFont}\n",
                    $diaHeight, $diaWidth, $bmodifier));
}

sub interlude {
    my ($diaWidth, $diaHeight) = @_;
    my $interlude = '';

    # print coordinates
    if ($sgf{Coords}) {
	        $interlude = sprintf("\\vfil
\\hbox{\\hglue3pt\\vbox{\\hsize=%d pt\\settabs%d\\columns\\rm
\\+%s\\cr}\\hfil}
\\vglue5pt", 12*($sgf{RightLine}-$sgf{LeftLine}+1) * (1+.2*$sgf{BigFonts}),($sgf{RightLine}-$sgf{LeftLine}+1),substr("A&B&C&D&E&F&G&H&J&K&L&M&N&O&P&Q&R&S&T",2*$sgf{LeftLine},2*($sgf{RightLine}-$sgf{LeftLine}+1)));
    }
    # print the diagram title
    $diaWidth += 20;
    if (($sgf{TwoColumn})|| ($sgf{Simple})) {
        $interlude .= "}\n$sgf{Title}\\hfil\\break\n";
    } else {
        my $hangIndentLines = int(1 + $sgf{BigFonts} + ($diaHeight - (1+.2*$sgf{BigFonts})*$sgf{Gap})/ $sgf{FontSize});
        $interlude .= sprintf("\\vfil}
\\setbox\\captionbox=\\vbox{\\tolerance=10000\\vglue-8pt
\\parindent=0pt\\parskip=8pt\\vglue6pt\\lineskip=0pt\\baselineskip=12pt
\\hangindent $diaWidth pt\\hangafter-$hangIndentLines
\\noindent$sgf{Title}\\hfil\\break\\hfil\\break\n");
    }
    return($interlude);
}

sub postamble {

    if ($sgf{TwoColumn}) {
	    return("\n\n");
	}
    if ($sgf{LongComments}) {
        return("\\par\\vfil}
\\diagdim=\\ht\\diagbox
\\ifdim\\ht\\captionbox>280pt
\\vbox to 280pt{\\box\\diagbox\\vglue-\\diagdim\\vsplit\\captionbox to 280pt}
\\nointerlineskip\\unvbox\\captionbox
\\else
\\ifdim\\ht\\captionbox>\\diagdim\\fulldim=\\ht\\captionbox
  \\else\\fulldim=\\diagdim\\fi
\\vbox to\\fulldim{\\box\\diagbox\\vglue-\\diagdim\\box\\captionbox}
\\fi\n\n");
    }

    if ($sgf{Simple}) {
        return("\n\n");
    }
    # not LongComments and not Simple
    return("\\par\\vfil}
\\diagdim=\\ht\\diagbox
\\ifdim\\ht\\captionbox>\\diagdim\\fulldim=\\ht\\captionbox
  \\else\\fulldim=\\diagdim\\fi
\\vbox to\\fulldim{\\box\\diagbox\\vglue-\\diagdim\\box\\captionbox}\n\n");
}

sub GetGoFont {
    my ($moveNum, $x, $y) = @_;

    my ($color);
    if (defined($x) && defined($y)) {
        my @history = split(/;/, $history{$x,$y});
        unless (@history) {
            print(STDERR "GetGoFont: No history defined for $x, $y\n");
            return('');         # just leave it as it is
        }
        my ($node, $mv, $col);
        foreach (@history) {
            ($node, $col) = split(/ /);
            $mv = GetNodeMoveNum($node);
            if ($mv <= $moveNum) {
                $color = $col;
            }
        }
    } else {
        $color = $moveColor[$moveNum];
    }

    unless(defined($color)) {
        print(STDERR "GetGoFont: No color defined for move $moveNum\n");
        return('');             # leave it as is
    }
    return('') if ($color eq 'pass');

    my $parity = (($moveNum - $sgf{NumberOffset}) & 1) ^ $color;
    my $font = $parity ? 'goe' : 'goo';         # choose font based on color and odd/even number
    return('') if (defined ($sgf{GoFont}) && ($font eq $sgf{GoFont}));
    $sgf{GoFont} = $font;
    return("\\$font");
}

sub GetMoveNum {
    my ($moveNum) = @_;
    my $offset = 0;

    $moveNum -= $sgf{NumberOffset};
    if ($sgf{DoubleDigits}) {
        while (($moveNum - $offset > 100) && ($moveNum - $offset - 100 > $sgf{DiaFirstMove})) {
            $offset += 100;            # not exactly modulo 100, alas
        }
    }
    return($moveNum - $offset);
}

sub GetNodeMoveNum {
    my ($nodeNum) = @_;

    while($nodeNum > 0) {
        return($nodeMove[$nodeNum]) if (defined($nodeMove[$nodeNum]));
        $nodeNum--;
    }
    return(0);
}

sub GetNodeNextMoveNum {
    my ($nodeNum, $lastNode) = @_;

    unless(defined($lastNode)) {
        $lastNode = @nodeMove;  # just go to the end
    }
    while($nodeNum < $lastNode) {
        return($nodeMove[$nodeNum]) if (defined($nodeMove[$nodeNum]));
        $nodeNum++;
    }
    return(GetNodeMoveNum($nodeNum));   # oops - got to the end.
}

sub MovesInNodeRange {
    my ($firstNode, $lastNode) = @_;

    my ($ii, $count);
    $count = 0;
    for ($ii = $firstNode; $ii <= $lastNode; $ii++) {
        if(defined($nodeMove[$ii])) {
            $count++;
        }
    }
    return($count);
}

sub PrintDiagram {
    my ($lastNode) = @_;
    my ($nodeNum, $lastMove, $x, $y, $overLay, $overStone, $atStone, $textstone);
    my ($onWhat, $diaHeight, $diaWidth, $hangIndentLines, $bmodifier, $texDiagram);
    my (@overLays, $range);
    

    return unless($sgf{BoardChange});                # no change? don't print anything...
    $sgf{BoardChange} = 0;
    $lastMove = GetNodeMoveNum($lastNode);
    $texDiagram = '';
    $sgf{DiagramNum}++; # if this is a variation, parent hasn't been printed yet, so it's correct to increment

    # figure out what the numbering offset should be (based on various control flags)
    if ($variationDepth <= 1) {
            # not a variation
        $sgf{DiaFirstNode} = $sgf{DiaLastNode} + 1;
        $sgf{DiaFirstMove} = $sgf{DiaLastMove} + 1;
        if ($sgf{RepeatLast} && ($sgf{DiaLastMove} > 0)) {
            $sgf{DiaFirstMove}--;
        }
        if ($sgf{NewNumbersFlag}) {
            $sgf{NumberOffset} = $sgf{DiaFirstMove} - 1;
        } else {
            $sgf{NumberOffset} = 0;
        }
    } else {
            # this is a variation
        if ($sgf{IsContinuation}) {
            $sgf{DiaFirstNode} = $sgf{DiaLastNode} + 1;
        } else {
            $sgf{DiaFirstNode} = $sgf{VariationStartNode};
        }
        $sgf{DiaFirstMove} = GetNodeNextMoveNum($sgf{DiaFirstNode}, $lastNode);
        $onWhat = GetNodeMoveNum($sgf{DiaFirstNode} - 1);       # absolute move number that spawned variation
        $onWhat = $sgf{MoveThatSpawnedVar};                     # absolute move number that spawned variation
        $onWhat++;              # this isn't really correct, but it makes most diagrams come out
                                #     correctly - sigh, the difficulties of sgf format...
        if ($sgf{VarNumbersFlag} eq 'absolute') {               # same as Diagram numbers
            $sgf{NumberOffset} = 0;
        } elsif ($sgf{VarNumbersFlag} eq 'relative') {          # each variation starts at 1
            $onWhat -= GetNodeMoveNum($sgf{VariationParentStartNode} - 1);
            $sgf{NumberOffset} = GetNodeMoveNum($sgf{VariationStartNode} - 1);
        } elsif ($sgf{VarNumbersFlag} eq 'correlative') {       # each root variation starts at 1
            if ($variationDepth > 2) {
                $onWhat -= GetNodeNextMoveNum($sgf{VariationRootStartNode} - 1);
            }
            $sgf{NumberOffset} = GetNodeMoveNum($sgf{VariationRootStartNode} - 1);
        } else {                        # $sgf{VarNumbersFlag} eq 'absolute'
            print("Oops!  don't understand \$sgf{VarNumbersFlag} = $sgf{VarNumbersFlag}\n");
            $onWhat = $sgf{DiaFirstMove};  # the move number that spawned the variation
            $sgf{NumberOffset} = 0;
        }
        $onWhat = 'at move ' . $onWhat;
        if ($variationDepth == 2) {
            $onWhat .= " in Diagram $sgf{DiagramNum}";          # variation on a main line diagram
        } else {
            $onWhat .= " in Variation $sgf{ParentVariation}";   # variation on a variation
        }
        if ($sgf{IsContinuation}) {
            $onWhat = ' (continued)';  # never mind - it's just a continuation...
        }
    }
    $sgf{DiaLastNode} = $lastNode;
    $sgf{DiaLastMove} = $lastMove;
    $range = ': ';
    if ($sgf{DiaLastMove} <= 0) {
        $range = 'Starting Position';
        $sgf{DiaFirstMove} = 0;
    }
    my $movesInDia = MovesInNodeRange($sgf{DiaFirstNode}, $lastNode);
    if ($movesInDia > 1) {
        $range .= ($sgf{DiaFirstMove} - $sgf{NumberOffset}) .  '-' . ($sgf{DiaLastMove} - $sgf{NumberOffset});
    } elsif ($movesInDia == 1) {
        $range .= $sgf{DiaFirstMove} - $sgf{NumberOffset};
    } else {
        $range = '';
        if ($movesInDia < 0) {
            print("Oops! $movesInDia moves in diagram\n");
        }
    }

    if($variationDepth <= 1) {
        # we are printing a main-line diagram
        $sgf{Title} = "{\\bf Diagram $sgf{DiagramNum}}$range";
        printIndent("Printing Diagram $sgf{DiagramNum}$range\n");
        $texDiagram .= "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
        $texDiagram .= "%  Start of Diagram $sgf{DiagramNum}$range\n";
        $texDiagram .= "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
    } else {
        # we are printing a variation diagram
        $sgf{Title} = "{\\bf Variation $variationNum} $onWhat$range";
        printIndent("Printing Variation $variationNum $onWhat$range\n");
        $texDiagram .= "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
        $texDiagram .= "%  Start of Variation $variationNum $onWhat$range\n";
        $texDiagram .= "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
    }
    $sgf{GoFont} = 'goo' unless(defined($sgf{GoFont}));         # set a default

    # get some measurements based on font size
    $diaHeight = (1 + .2 * $sgf{BigFonts}) * ($sgf{FontSize} * ($sgf{BottomLine} - $sgf{TopLine} + 1) + $sgf{Gap});
    $diaWidth = $sgf{FontSize} * (1 + .2 * $sgf{BigFonts}) * ($sgf{RightLine} - $sgf{LeftLine} + 1);
    if ($sgf{Coords}) {
        $diaWidth += 15;
        $diaHeight += 15;
    }
    $texDiagram .= preamble($diaHeight, $diaWidth);
    foreach $y ($sgf{TopLine} .. $sgf{BottomLine}) {
        foreach $x ($sgf{LeftLine} ..  $sgf{RightLine}) {
            $texDiagram .= PrintStone(\@overLays, $x, $y);
        }
        if ($sgf{Coords}) {
            $texDiagram .= sprintf("\\raise 3pt\\hbox to 15pt{\\hglue5pt\\rm %d\\hfil}",$sgf{BoardSize}-$y);
        }
        $texDiagram .= "\n";
    }
    undef(%letter);
    undef(%mark);
    undef(%label);
    # print the coordinates and diagram title
    $texDiagram .= interlude($diaWidth, $diaHeight);

    # deal with the over-lay stones
    $textstone = $sgf{BigFont} ? "\\bigtextstone" : " \\textstone";     # choose font
    @overLays = sort {substr($::a, 5, 3) <=> substr($::b, 5, 3)} @overLays;

    my (%overOrder, @condensedOverlays, @overAt);
    while (scalar(@overLays)) {
        ($overStone, $atStone) = split(/ XqX /, shift(@overLays));
        if (defined($overOrder{$atStone})) {
            unless(defined($condensedOverlays[$overOrder{$atStone}])) {
                $condensedOverlays[$overOrder{$atStone}] = '';
            }
            $condensedOverlays[$overOrder{$atStone}] .= ", $textstone\{$overStone\}";
        } else {
            push(@condensedOverlays, "$textstone\{$overStone\}");
            push(@overAt, $atStone);
            $overOrder{$atStone} = $#overAt;
        }
    }
    while (scalar(@overAt)) {
        $overStone = shift(@condensedOverlays);
        $atStone = shift(@overAt);
        $overLay = "$overStone at $textstone\{$atStone\}";
        if (scalar(@overAt)) {
            $overLay .= ',';    # more to come, tack on a comma
        }
        $texDiagram .= "$overLay\\hfil\\break\\break\n";
    }
    if ($sgf{TwoColumn}) {
	$texDiagram .= "}\n";
    } else { 
	$texDiagram .= "\\hfil\\break\n";
    }
    # print the name for this diagram
    $texDiagram .= $sgf{DiaName};
    $sgf{DiaName} = '';
    # print the comments for this diagram
    for ($nodeNum = $sgf{DiaFirstNode}; $nodeNum <= $sgf{DiaLastNode}; $nodeNum++) {
        if (scalar($comment{$nodeNum})) {
            if (GetMoveNum(GetNodeMoveNum($nodeNum)) > 0) {
                $texDiagram .= GetMoveNum(GetNodeMoveNum($nodeNum)) . ': ';
            }
            $texDiagram .= $comment{$nodeNum};
        }
    }
    $texDiagram .= postamble();
    # now either print or store the diagram
    if ($variationDepth <= 1) {
        if (($sgf{DiagramNum} >= $sgf{FirstDiagram}) && ($sgf{DiagramNum} <= $sgf{LastDiagram})) {
            print($outHandle $texDiagram);
        }
        my $var;
        foreach $var (sort({CompareVariation($::a, $::b)} keys(%texVariation))) {
            if (defined($var) && defined($texVariation{$var})) {
                print($outHandle $texVariation{$var});
                undef($texVariation{$var});
            }
        }
    } else {
        unless (defined($texVariation{$variationNum})) {
            $texVariation{$variationNum} = '';
        }
        $texVariation{$variationNum} .= $texDiagram;      # save variations for after the main diagram
    }
    $sgf{IsContinuation} = 1;   # if we have more after this, it's a continuation
}

sub CompareVariation {

    my @aa = split(/\./, $a);
    my @bb = split(/\./, $b);

    my ($ii, $max, $return);
#print "CompareVariation($a, $b)\n";
    if (@aa > @bb) {
        $max = @aa;
    } else {
        $max = @bb;
    }
    for ($ii = 0; $ii < $max; $ii++) {
        $aa[$ii] = -1 unless(defined($aa[$ii]));
        $bb[$ii] = -1 unless(defined($bb[$ii]));
        $return = ($aa[$ii] <=> $bb[$ii]);
#print("ii=$ii, aa=$aa[$ii], bb=$bb[$ii], returns $return\n");
        last unless ($return == 0);
    }
    return($return);
}

sub PrintStone {
    my ($overLays, $x, $y) = @_;
    my ($stone, $atStone, $overStone, $underneath, $h, $nodeNum, $moveNum, $type);

    if (defined($history{$x,$y})) {
        foreach $h (split(/;/, $history{$x,$y})) {
            # $type is one of these choices:
            #  -1     = removed stone
            #  0      = white stone
            #  1      = black stone
            ($nodeNum, $type) = split(/ /, $h);
            $moveNum = GetNodeMoveNum($nodeNum);
            unless (defined($type)) {
                print(STDERR "Oops: history = $h\n");
            }
            if (($nodeNum < $sgf{DiaFirstNode}) || ($moveNum <= $sgf{NumberOffset})) {
                # this is in the previous diagrams, or the move number is
                #   before the start of the numberOffset.
                #   stones in this section are numberless.
                if ($type == -1) {
                    undef($stone);              # stone has been removed
                } elsif ($type == 0) {
                    $stone = "\\- !";           # numberless white stone
                } elsif ($type == 1) {
                    $stone = "\\- @";           # numberless black stone
                }
            } elsif ($nodeNum <= $sgf{DiaLastNode}) {
                # this is in the current diagram.
                if (defined $stone) {
                    # something is already on this intersection - figure out what to do
                    if (($type == 0) || ($type == 1)) {
                        # we need to make an overlay
                        if ($stone eq "\\- !") {        # numberless white stone
                            $stone = "\\- ;";           # becomes a marked white stone
                            unless(defined($atStone)) {
                                $atStone = "\\$sgf{GoFont}\\- ;;";      # marked white stone in text
                            }
                        } elsif ($stone eq "\\- @") {   # numberless black stone
                            $stone = "\\- :";           # becomes a marked black stone
                            unless(defined($atStone)) {
                                $atStone = "\\$sgf{GoFont}\\- ::";      # marked black stone in text
                            }
                        } else {                        # numbered or marked stone - stays the same
                            unless(defined($atStone)) {
                                $stone =~ m/(...)$/;    # get the three digits (or mark or label)
                                $atStone = "\\$sgf{GoFont}\\$1=";       # numbered or marked stone in the text
                            }
                        }
                        $overStone = "\\$sgf{GoFont}" . sprintf("\\%03d=", GetMoveNum($moveNum));
                        push(@{$overLays}, "$overStone XqX $atStone")
                    } elsif ($type == -1) {
                        if ($moveNum <= $sgf{DiaFirstMove}) {
                            undef($stone);              # stone removed before first move - take it away
                        }                               # else ignore it - we can't remove it til the next diagram
                    } else {
                        print(STDERR "Unknown type \"$type\" at ($x,$y), move $moveNum\n");
                    }
                } else {
                    # nothing is on this intersection - make a numbered stone
                    if (($type == 0) || ($type == 1)) {
                        $stone = GetGoFont($moveNum, $x, $y) . sprintf("\\%03d", GetMoveNum($moveNum)); # numbered stone
                    } elsif ($type == -1) {
                        # shouldn't be removing a stone from an empty intersection!
                        print(STDERR "Remove stone at empty intersection ($x,$y), move $moveNum\n");
                    } else {
                        print(STDERR "Unknown type \"$type\" at ($x,$y), move $moveNum\n");
                    }
                }
            }       # else it's past the current diagram - ignore it
        }
        if (defined($mark{$x,$y})) {
            my ($markNum, $markColor) = split(/ /, $mark{$x,$y});
            if (($markNum >= $sgf{DiaFirstMove}) &&
                ($markNum <= $sgf{DiaLastMove}) ) {
                if ($stone =~ m/\\[0-9]{3}/) {  # if stone is still numbered, get the three digits
                    $overStone = "\\$sgf{GoFont}$stone=";       # numbered stone in the text
                    if ($markColor == 0) {
                        $stone = "\\- ;";                       # becomes a marked white stone
                        $atStone = "\\$sgf{GoFont}\\- ;;";      # marked stone in text
                    } elsif ($markColor == 1) {
                        $stone = "\\- :";                       # becomes a marked black stone
                        $atStone = "\\$sgf{GoFont}\\- ::";      # marked stone in text
                    }
                    push(@{$overLays}, "$overStone XqX $atStone")
                } # if stone is not numbered, it's already been marked and overlayed
            }
        }
        if (defined($label{$x,$y})) {
            my ($labelNum, $label) = split(/ /, $label{$x,$y});
            if (($labelNum >= $sgf{DiaFirstMove}) &&
                ($labelNum <= $sgf{DiaLastMove}) ) {
                if ($stone =~ m/\\[0-9]{3}/) {                  # if stone is still numbered, get the three digits
                    $overStone = "\\$sgf{GoFont}$stone=";       # numbered stone in the text
                    $atStone = "\\$sgf{GoFont}\\$label=";       # labeled stone in text
                    push(@{$overLays}, "$overStone XqX $atStone")
                }
                $stone = "\\$label";                            # change stone to a labeled stone
            }
        }
    }
    if(defined($letter{$x,$y})) {
        if (defined($stone)) {
            print(STDERR "Tried to put a letter over a stone at ($x,$y), move $moveNum\n");
        } else {
            # draw a letter here
            $stone = "\\!  " . $letter{$x,$y};
            return($stone);
        }
    }
    if(defined($stone)) {
	if ($sgf{BoardSize} == 9) {
	    $underneath = substr($nineBoard, ($x * 10) + $y, 1);
	} elsif ($sgf{BoardSize} == 13) {
	    $underneath = substr($thirteenBoard, ($x * 14) + $y, 1);
	} else {
	    $underneath = substr($blankBoard, ($x * 20) + $y, 1);
	}
        if ($underneath eq '*') {
            $underneath = '+';          # no hoshi underneath if it's not empty
        }
        $stone .= $underneath;
    } else {                    # an empty intersection - add the part underneath the stone
	if ($sgf{BoardSize} == 9) {
	    $stone = "\\0??" . substr($nineBoard, ($x * 10) + $y, 1);
	} elsif ($sgf{BoardSize} == 13) {
	    $stone = "\\0??" . substr($thirteenBoard, ($x * 14) + $y, 1);
	} else {
	    $stone = "\\0??" . substr($blankBoard, ($x * 20) + $y, 1);
	}
    }
    return($stone);
}


#
# OK, now we've got everything defined, we can start executing stuff

#
# set the defaults
#
$sgf{NewNumbersFlag} = 0;
$sgf{VarNumbersFlag} = 'relative';
$sgf{LongComments} = 0;
$sgf{Simple} = 0;
$sgf{TwoColumn} = 0;
$sgf{DoubleDigits} = 0;
$sgf{TopLine} = $sgf{LeftLine} = 1;
$sgf{BottomLine} = $sgf{RightLine} = 19;
$sgf{MovesPerDiagram} = -1;
$sgf{MoveNum} = 0;
$sgf{NodeNum} = 0;
$sgf{BigFonts} = 0;
$sgf{DiaLastMove} = 0;
$sgf{RepeatLast} = 0;
$sgf{Coords} = 0;
$sgf{FirstDiagram} = 1;
$sgf{LastDiagram} = 10000;
$sgf{FontSize} = 12;
$sgf{BoardSize} = 19;
$sgf{DiaLastNode} = 0;
$sgf{Gap}=12;
$sgf{GoFont} = 'goo';           # usually.  it should get fixed up later if it's wrong
$sgf{DiaName} = '';

#
# parse the command line arguments:
#
my ($arg);
while (scalar(@ARGV)) {
    $arg = shift(@ARGV);
    if (($arg eq '-d') || ($arg eq '-doubleDigits')) {
        $sgf{DoubleDigits} = 1 
    } elsif (($arg eq '-i') || ($arg eq '-in')) {
        $inFileName = shift(@ARGV);
    } elsif (($arg eq '-o') || ($arg eq '-out')) {
        $outFileName = shift(@ARGV);
    } elsif (($arg eq '-m') || ($arg eq '-movesPerDiagram')) {
        $sgf{MovesPerDiagram} = shift(@ARGV);
    } elsif (($arg eq '-n') || ($arg eq '-newNumbers')) {
        $sgf{NewNumbersFlag} = 1;
    } elsif (($arg eq '-av') || ($arg eq '-absoluteVarNums')) {
        $sgf{VarNumbersFlag} = 'absolute';
    } elsif (($arg eq '-rv') || ($arg eq '-relativeVarNums')) {
        $sgf{VarNumbersFlag} = 'relative';
    } elsif (($arg eq '-cv') || ($arg eq '-correlativeVarNums')) {
        $sgf{VarNumbersFlag} = 'correlative';
    } elsif (($arg eq '-im') || ($arg eq '-ignoreMarks')) {
        $sgf{IgnoreMarks} = 1;
    } elsif (($arg eq '-il') || ($arg eq '-ignoreLetters')) {
        $sgf{IgnoreLetters} = 1;
    } elsif (($arg eq '-ip') || ($arg eq '-ignorePass')) {
        $sgf{IgnorePass} = 1;
    } elsif (($arg eq '-iv') || ($arg eq '-ignoreVariations')) {
        $sgf{IgnoreVariations} = 1;
    } elsif ($arg eq '-texComments') {
        $sgf{texComments} = 1;
    } elsif (($arg eq '-ia') || ($arg eq '-ignoreAll')) {
        $sgf{IgnoreVariations} = 1;
        $sgf{IgnoreLetters} = 1;
        $sgf{IgnoreMarks} = 1;
        $sgf{IgnorePass} = 1;
    } elsif (($arg eq '-rl') || ($arg eq '-repeatLast')) {
        $sgf{RepeatLast} = 1;
    } elsif (($arg eq '-break') || ($arg eq '-breakList')) {
        @breakList = sort {$::a <=> $::b} (split(/,/, shift(@ARGV)));
    } elsif (($arg eq '-t') || ($arg eq '-top')) {
        $sgf{TopLine} = shift(@ARGV);
    } elsif ($arg eq '-gap') {
	$sgf{Gap}=shift(@ARGV);
    } elsif (($arg eq '-b') || ($arg eq '-bottom')) {
        $sgf{BottomLine} = shift(@ARGV);
    } elsif (($arg eq '-l') || ($arg eq '-left')) {
        $sgf{LeftLine} = shift(@ARGV);
    } elsif (($arg eq '-r') || ($arg eq '-right')) {
        $sgf{RightLine} = shift(@ARGV);
    } elsif ($arg eq '-firstDiagram') {
        $sgf{FirstDiagram} = shift(@ARGV);
    } elsif ($arg eq "-coords") {
        $sgf{Coords} = 1;
    } elsif ($arg eq '-lastDiagram') {
        $sgf{LastDiagram} = shift(@ARGV);
    } elsif (($arg eq '-h') || ($arg eq '-help')) {
        print(STDOUT $help);
        exit(0);
    } elsif (($arg eq '-v')||($arg eq '-version')) {
        print(STDERR "$myName $version\n");
        exit(0);
    } elsif ($arg eq '-bigFonts') {
        $sgf{BigFonts} = 1;
    } elsif ($arg eq '-longComments') {
        $sgf{LongComments} = 1;
    } elsif ($arg eq '-simple') {
        $sgf{Simple} = 1;
    } elsif ($arg eq '-twoColumn') {
        $sgf{FontSize} = 10;
        $sgf{TwoColumn} = 1;
        $sgf{Simple} = 1;
    } elsif ($arg eq '-verbose') {
        $verbose = 1;
    } elsif (substr($arg, 0, 1) eq '-') {
        print(STDERR "\nUnknown option: $arg\n");
        print(STDERR $help);
        exit(1);
    } else {
        $inFileName = $arg;
    }
}

if ($sgf{Coords} && $sgf{TwoColumn}) {
    print(STDERR "\nWarning: -coords and -twoColumn cannot be used together - turning off coords.\n\n");
    $sgf{Coords} = 0;
}

if (($sgf{LongComments}) && ($sgf{Simple})) {
    if ($sgf{TwoColumn}) {
	print(STDERR "\nWarning: -longComments and -twoColumn cannot be used together - turning off -longComments.\n\n");
    } else {
	print(STDERR "\nWarning: -longComments and -simple cannot be used together - turning off longComments.\n\n");
    }
    $sgf{LongComments}=0;
}

if ($sgf{MovesPerDiagram} == -1) {
    $sgf{MovesPerDiagram} = scalar(@breakList) ? 10000 : 50;
}     

$sgf{TopLine}--;
$sgf{BottomLine}--;
$sgf{LeftLine}--;
$sgf{RightLine}--;

if ($sgf{TopLine} < 0) {
    $sgf{TopLine} = 0;
}
if ($sgf{LeftLine} < 0) {
    $sgf{LeftLine} = 0;
}
if ($sgf{BottomLine} > $sgf{BoardSize} ) {
    $sgf{BottomLine} = $sgf{BoardSize} - 1
}
if ($sgf{RightLine} > $sgf{BoardSize} ) {
    $sgf{RightLine} = $sgf{BoardSize} - 1 
}

# open the input file handle
if (!defined($inFileName) || ($inFileName eq '-')) {
    $inFileName = 'STDIN';
    $inHandle = \*STDIN;
} else {
    if (!-e $inFileName) {
        if (-e "$inFileName.sgf") {
            $inFileName = "$inFileName.sgf";
        } else {
            if (-e "$inFileName.mgt") {
                $inFileName = "$inFileName.mgt";
            } else {
                print(STDERR "Can't find $inFileName, $inFileName.sgf or $inFileName.mgt\n");
                exit(1);
            }
        }
    }
    unless (open(\*INHANDLE, "<$inFileName")) {
        print(STDERR "Can't open $inFileName for reading\n");
        exit(1);
    }
    $inHandle = \*INHANDLE;
}

# convert the input filename into an output filename
if (!defined ($outFileName)) {
    if ($inFileName eq  'STDIN') {
        $outFileName = 'STDOUT';
    } else {
        $outFileName = $inFileName;
        unless ($outFileName =~ s/.sgf$//i) {
            $outFileName =~ s/.mgt$//i;
        }
    }
    $outFileName =~ s/.*\///;   # output into current directory
}
# open the output file handle
if (($outFileName eq 'STDOUT') || ($outFileName eq '-')) {
    $outHandle = \*STDOUT;
} else {
    if (($outFileName ne 'STDOUT') && !($outFileName =~ m/.tex$/i)) {
        $outFileName .= '.tex';         # tack on the TeX extension
    }
    unless (open(\*OUTHANDLE, ">$outFileName")) {
        print(STDERR "Can't open $outFileName for writing\n");
        exit(1);
    }
    $outHandle = \*OUTHANDLE;
}

# clear the board
my ($y, $x);
for ($y = 0; $y < $sgf{BoardSize}; $y++) {
    for ($x = 0; $x < $sgf{BoardSize}; $x++) {
        $board{$y,$x} = -1;
    }
}

# draw the diagrams
SGF_ReadFile($inHandle);       # read the SGF file into the sgf array
close($inHandle);                   # don't need this anymore
print($outHandle "\\bye\n");
close($outHandle);
exit(0);

