Games-Go-Image2SGF
view release on metacpan or search on metacpan
Image2SGF.pm view on Meta::CPAN
for (my $k = ($x_vertex - $radius); $k <= ($x_vertex + $radius); $k++) {
for (my $l = ($y_vertex - $radius); $l <= ($y_vertex + $radius); $l++) {
if (($x_vertex - $k)**2 + ($y_vertex - $l)**2 <= ($radius**2)) {
# If this is true, then the point ($k, $l) is in our circle.
# Now we sample at it.
my $gp = $self->{img}->getpixel('x' => $k, 'y' => $l);
next if $gp == undef;
if (_color_cmp($gp, $black) == 1) { $blackcount++; }
if (_color_cmp($gp, $board) == 1) { $boardcount++; }
if (_color_cmp($gp, $white) == 1) { $whitecount++; }
}
}
}
# Finished sampling. Use a simple majority to work out which colour
# wins. TODO -- there are better ways of doing this. For example,
# if we determine one stone to be white or black, we could afterwards
# set its radius _in our quantized image_ back to the board colour;
# this "explaining away" would alleviate cases where the grid is
# slightly off and we're catching pixels of an already-recorded
# stone on the edges.
if (($whitecount > $blackcount) and ($whitecount > $boardcount)) {
$stone = WHITE;
} elsif ($blackcount > $boardcount) {
$stone = BLACK;
} else {
$stone = BOARD;
}
my @letters = qw/z a b c d e f g h i j k l m n o p q r s/;
if ($stone == WHITE or $stone == BLACK) {
$self->update_sgf($stone, $letters[$i], $letters[$j], $stone);
}
return $stone;
}
sub invert_coords {
my $self = shift;
# Because the origin (0,0) in the inputed coordinates is in the
# upper left instead of the intuitive-for-geometry bottom left,
# we want to call this the "fourth quadrant". That means all the
# y values are treated as negative numbers, so we convert:
for (qw(tl tr bl br)) { $self->{$_}[Y] = -$self->{$_}[Y]; }
}
sub start_sgf {
my $self = shift;
my $time = scalar localtime;
$self->{sgf} .= <<ENDSTARTSGF;
(;GM[1]FF[4]SZ[19]
GN[Image2SGF conversion of $time.]
AP[Image2SGF by Chris Ball.]
PL[B]
ENDSTARTSGF
}
sub update_sgf {
my $self = shift;
my ($stone, $x, $y) = @_;
if ($stone == BLACK) {
push @{$self->{blackstones}}, "$y$x";
}
elsif ($stone == WHITE) {
push @{$self->{whitestones}}, "$y$x";
}
}
sub finish_sgf {
my $self = shift;
$self->{sgf} .= "\nAB";
$self->{sgf} .= "[$_]" foreach (@{$self->{blackstones}});
$self->{sgf} .= "\nAW";
$self->{sgf} .= "[$_]" foreach (@{$self->{whitestones}});
$self->{sgf} .= ")\n\n";
}
sub _color_cmp {
my ($l, $r) = @_;
my @l = $l->rgba;
my @r = $r->rgba;
return ($l[0] == $r[0] and $l[1] == $r[1] and $l[2] == $r[2]);
}
sub _to_coords {
# Example: "cd" => "C16".
my ($x, $y) = @_;
return chr(64 + $y + ($y > 9 && 1)) . (20 - $x);
}
sub _from_coords {
# Example: "C16" => "cd".
my $move = shift;
/(.)(\d+)/;
return ($2, ord($1) - 65);
}
sub to_sgf {
my $self = shift;
# The only user-visible method right now. Runs the conversion functions.
# (Which are separate methods so that we can keep track of a live game
# efficiently -- if the camera is stationary above the board, we only
# have to find the grid location once, and can just repeatedly call
# read_image/quantize/sample, reusing the coordinates.)
$self->find_intersections;
$self->start_sgf;
$self->read_image;
$self->quantize;
for my $i (1 .. BOARDSIZE) {
for my $j (1 .. BOARDSIZE) {
my $stone = $self->sample($i, $j, $self->{sample_radius});
}
}
( run in 0.718 second using v1.01-cache-2.11-cpan-39bf76dae61 )