(Ideally this message would be a reply to the thread about printing
diagrams, but I'm in the middle of switching mail to my new laptop and
that thread is on the old machine, so the easy thing is just to start
a new thread. For that matter, this is my first outgoing message from 
my new laptop, so if it comes out in EBCDIC ALL CAPS or something then
it will be A Learning Experience For Me.)

Years ago I worked on a sort of design document which contained many
trivial Go diagrams embedded in LaTeX source. I wanted some way to
be able to read and edit those diagrams as ASCII art while editing the
LaTeX source in a text editor. I hacked up the following Perl script
to support this. It's not a particularly full-featured solution, but
in case it might be useful to someone else (if only as a starting point
for a more complete solution) I will offer it up here.

To use the script in an environment where the relationship of LaTeX
source to .dvi output is controlled by a Makefile, you can pick a file
extension other than .tex for the to-be-preprocessed-LaTeX source (I 
use ".ptx"), give the script a name (I use "gopicture"), then put a 
rule like 
  %.tex: %.ptx
        gopicture < $< > $@
in your Makefile. (Or, of course, if you use more preprocessing hacks, 
then some variant like "cat ... | otherhack1 | gopicture | otherhack2 ..."
in place of the "gopicture ..." command.)

I am releasing this code into the public domain. It has no warranty
of any kind. (After all, had high reliability in all circumstances
been a design goal, would I have hacked it out in Perl?) 
--
#!/usr/bin/perl -w

# a hack to munge LaTeX source so that human-readable ASCII diagrams 
# of Go board positions, e.g.
#   \begin{figure}\label{figure:gopicture}
#     \begin{center}
#       %gopicture   A B C D E
#       %gopicture 5 . . . . . 5
#       %gopicture 4 . . . . . 4
#       %gopicture 3 . . . . . 3
#       %gopicture 2 . . . . . 2
#       %gopicture 1 . . . . . 1
#       %gopicture   A B C D E
#     \end{center}
#     \caption{Exercising GoPicture}
#   \end{figure}
# are transformed into LaTeX graphics, e.g.
#   \begin{figure}\label{figure:gopicture}
#     \begin{center}
#       \begin{picture}(200,200)(0,0)
#         ...
#       \end{picture}
#     \end{center}
#     \caption{Exercising GoPicture}
#   \end{figure}
#
# The initial implementation used the LaTeX \picture environment,
# but eventually it'd probably be nice to use something prettier,
# like sgf2tex fonts. (I gave up on them for the first pass because
# my diagrams often really really want board edge labels, and 
# intermingling edge labels with sgf2tex fonts seemed to require
# more TeX knowledge than I wanted to acquire.)

# TODO:
#   * (stuff doable within LaTeX \picture environment)
#     ** hoshi
#   * (stuff doable in PostScript or sgf2tex)
#     ** triangle and square marks on stones

use strict;
use Carp qw(carp confess croak);

use POSIX;
use GameOfGo::Basic qw(color_from_char
                       is_color
                       trad_column_label
                       trad_column_label_to_x);

# All of the input lines we handle (instead of just copying) begin
# with this prefix.
use vars qw($lineprefix);
$lineprefix = "%gopicture";
#print STDERR "initialized lineprefix=$lineprefix\n";

# All of our input lines begin with the $lineprefix and end with
# newline. Get rid of that stuff and return what's left.
sub chop_as_input_line {
    my ($line) = @_;
    #print STDERR "chopping as input line: $line";
    if ($line =~ /^\s*$lineprefix\s+(.*)$/) {
        return $1;
    } else {
        die "missing \"$lineprefix\" or newline in input: $line";
    }
}

# Parse a label line in the style " A B C D E F G H J" and
# return the implied size of the board.
sub parse_label_line {
    my ($prefixed_line) = @_;
    #print STDERR "in parse_label_line, prefixed_line=$prefixed_line";
    my $line = chop_as_input_line($prefixed_line);
    #print STDERR "in parse_label_line, line=\"$line\"\n";
    my $edge = 0;
    my $line_index;
    #print STDERR "parsing label line \"$line\"\n";
    for ($line_index = 0; $line_index < length($line); ++$line_index) {
        my $char = substr($line, $line_index, 1);
        if ($char =~ /^\s$/) {
            # Just skip it.
        } elsif ($char = trad_column_label($edge)) {
            ++$edge;
        } else {
            die "bad label line: $line";
        }
    }
    $edge >= 3 or die "edge=$edge too small (almost certainly)\n";
    return $edge;
}

# Parse a board row in the style " 4 . . . O O # . O . 4"
# and return the corresponding row of the board (as a list).
sub parse_board_row {
    my ($y, $edge, $prefixed_line) = @_;
    my $line = chop_as_input_line($prefixed_line);
    #print STDERR "doing parse_board_row $y, $edge, \"$line\"\n";
    my @result;
    $line =~ /^(\d+)\s+(.*)$/ or die "no leading row index: $line";
    my $leading_row_index = $1;
    my $remaining_line = $2;
    $leading_row_index == 1 + $y or die "unexpected row index in $line";
    my $char_index = 0;
    #print STDERR "remaining_line=\"$remaining_line\"\n";
    while ($char_index < length($remaining_line)) {
        my $char = substr($remaining_line, $char_index++, 1);
        if ($char =~ /^\s$/) {
            # Just skip it.
        } else {
            #print STDERR "char_index=$char_index, char='$char'\n";
            my $color = color_from_char($char);
            push @result, $color;
            #print STDERR "grew result to @result\n";
            last if (@result == $edge);
        } 
    }
    #print STDERR "final char_index=$char_index\n";
    @result == $edge or die "not enough squares in row: $line";
    my $trailing_row_index = substr($remaining_line, $char_index);
    $trailing_row_index =~ /^\s*\d+\s*$/ or
        die "bad trailing row index: \"$trailing_row_index\"";
    int $trailing_row_index == 1 + $y or die "unexpected row index in $line";
    return [EMAIL PROTECTED];
}

# fundamental dimensions for drawing
use vars qw($stone_diameter $grid_size $row_label_width $whitespace_width);
$stone_diameter = 8;
$grid_size = 1.1 * $stone_diameter;
$row_label_width = 1.5 * $grid_size;
# ($column_label_width is implicitly assumed to be $grid_size.)
# ($label_height is implicitly assumed to be $grid_size.)
$whitespace_width = 0.0 * $grid_size;
#print STDERR "stone_diameter=$stone_diameter\n";
#print STDERR "grid_size=$grid_size\n";
#print STDERR "row_label_width=$row_label_width\n";
#print STDERR "whitespace_sidth=$whitespace_width\n";

# mapping from logical board positions to the center of associated
# pixel regions
#
# Note that off-board locations are explicitly OK, since we use them
# to place row and column labels.
sub xpixel {
    my ($xboard) = @_;
    return $whitespace_width + $row_label_width + ($xboard+0.5)*$grid_size;
}
sub ypixel {
    my ($xboard) = @_;
    return $whitespace_width + $grid_size + ($xboard+0.5)*$grid_size;
}

sub draw_square {
    my ($xboard, $yboard, $color, $edge) = @_;
    my $xpixel = xpixel($xboard);
    my $ypixel = ypixel($yboard);
    if ($color eq "black") {
        print "\\put($xpixel,$ypixel){\\circle*{$stone_diameter}}\n";
    } elsif ($color eq "white") {
        print "\\put($xpixel,$ypixel){\\circle{$stone_diameter}}\n";
    } elsif ($color eq "empty") {
        my $stone_radius = 0.5 * $stone_diameter;
        $xboard == 0 or
            print "\\put($xpixel,$ypixel){\\line(-1,0){$stone_radius}}\n";
        $xboard == $edge - 1 or
            print "\\put($xpixel,$ypixel){\\line(1,0){$stone_radius}}\n";
        $yboard == 0 or
            print "\\put($xpixel,$ypixel){\\line(0,-1){$stone_radius}}\n";
        $yboard == $edge - 1 or
            print "\\put($xpixel,$ypixel){\\line(0,1){$stone_radius}}\n";
    } else {
        die "unexpected color=\"$color\"";
    }
}

sub draw_row_label {
    my ($xboard, $yboard) = @_;
    #print STDERR "draw_row_label $xboard, $yboard\n";
    # As per p. 104 of original _LaTeX User's Guide and Reference Manual_,
    # a \makebox(0,0) command with no positioning argument puts the
    # center of the text on the reference point -- just what we want.
    printf "\\put(%f,%f){\\makebox(0,0){\\small %d}}\n",
        xpixel($xboard), ypixel($yboard),
        1+$yboard;
}

sub draw_column_label {
    my ($xboard, $yboard) = @_;
    #print STDERR "draw_column_label $xboard, $yboard\n";
    # As per p. 104 of original _LaTeX User's Guide and Reference Manual_,
    # a \makebox(0,0) command with no positioning argument puts the
    # center of the text on the reference point -- just what we want.
    printf "\\put(%f,%f){\\makebox(0,0){\\small %s}}\n",
        xpixel($xboard), ypixel($yboard),
        POSIX::toupper(trad_column_label($xboard));
}

# Print a board (represented as a list of lists of colors)
# as commands to create a graphical representation embedded in
# a LaTeX document.
sub draw_board {
    my @board = @_;
    my $edge = @board;

    #print STDERR "in draw_board, edge=$edge\n";
    #print STDERR "[EMAIL PROTECTED]";

    my $box_x =
        $edge * $grid_size + 2 * $row_label_width + 2 * $whitespace_width;
    my $box_y =
        $edge * $grid_size + 2 * $grid_size + 2 * $whitespace_width;
    my $stone_radius = 0.5 * $stone_diameter;

    # preamble
    print <<"EOF";
\\begin{picture}($box_x,$box_y)(0,0)
  % only for exploratory programming -- box essentially as big as \\picture
  %\\put(0,0){\\framebox($box_x,$box_y)}
EOF

    my ($xboard, $yboard);

    # Draw squares.
    for $xboard (0 .. $edge-1) {
        for $yboard (0 .. $edge-1) {
            draw_square($xboard, $yboard, $board[$xboard][$yboard], $edge);
        my $xpixel = xpixel($xboard);
            my $ypixel = ypixel($yboard);
        }
    }

    # Draw interstial lines.
    my $interstitial_length = $grid_size - $stone_diameter;
    for $xboard (0 .. $edge-2) {
        for $yboard (0 .. $edge-1) {
            # horizontal line
            printf "\\put(%f,%f){\\line(1,0){%f}}\n",
                xpixel($xboard)+$stone_radius, ypixel($yboard),
                $interstitial_length;
        }
    }
    for $xboard (0 .. $edge-1) {
        for $yboard (0 .. $edge-2) {
            # vertical line
            printf "\\put(%f,%f){\\line(0,1){%f}}\n",
                xpixel($xboard), ypixel($yboard)+$stone_radius,
                $interstitial_length;
        }
    }

    # Draw column labels.
    for $xboard (0 .. $edge-1) {
        draw_column_label($xboard, -1);
        draw_column_label($xboard, $edge);
    }

    # Draw row labels.
    for $yboard (0 .. $edge-1) {
        draw_row_label(-1, $yboard);
        draw_row_label($edge, $yboard);
    }

    # postamble
    print "\\end{picture}\n";
}

while (<>) {
    #print STDERR "read _=$_";
    if (/^\s*%gopicture/) {
        #print STDERR "leading gopicture line=$_";
        my $edge = parse_label_line($_);
        #print STDERR "edge=$edge\n";
        my @mirrorboard = (); # list of lists representing board
                              # mirrored across SW-to-NE axis 
        my $ycomplement;
        for $ycomplement (0 .. ($edge-1)) {
            #print STDERR "ycomplement=$ycomplement\n";
            my $y = ($edge-1) - $ycomplement;
            my $line = <>;
            $mirrorboard[$y] = parse_board_row($y, $edge, $line);
        }
        my $trailing_label_line = <>;
        #print STDERR "trailing label line=$trailing_label_line";
        parse_label_line($trailing_label_line) == $edge or
            die "mismatch between leading and trailing label lines";
        my ($x, $y);
        my @board = ();
        for $x (0 .. $edge-1) {
            for $y (0 .. $edge-1) {
                $board[$x][$y] = $mirrorboard[$y][$x];
            }
        }
        draw_board(@board);
        #print STDERR "done printing board as picture\n";
    } else {
        print;
    }
}
--

Enjoy.
_______________________________________________
computer-go mailing list
[email protected]
http://www.computer-go.org/mailman/listinfo/computer-go/

Reply via email to