#!/usr/bin/perl -wT
# licence: GPL
# author: Ian Hickson Tic Tac Toe
EOF
&play($query->param('move'), $query->param('row'), $query->param('cell'), $query->param('board'));
my $sizes = '';
foreach (2..4) {
$sizes .= "
EOF # end # routines sub play { my($move, $row, $cell, $board) = @_; if (not defined($size)) { $size = 3; } my @board = &parse($board); if ($move) { if ($row and $cell) { @board = &processMove($row, $cell, $youChar, @board); } if (not &done(@board)) { @board = &makeMove(@board); } } my $done = &done(@board); if ($done eq $me) { print "
I win.
\n"; } elsif ($done eq $you) { print "You win.
\n"; } elsif ($done) { print "It's a draw.
\n"; } else { print "Your turn!
\n"; } $board = &encode(@board); &paintBoard($done, $board, @board); } sub makeMove { my(@board) = @_; my($row, $cell) = &findSpot(@board); &processMove($row, $cell, $meChar, @board); return @board; } sub findSpot { if ($algorithm == 1) { return findSpotHard(@_); } else { return findSpotEasy(@_); } } sub processMove { my($row, $cell, $who, @board) = @_; if ($row > $size or $row < 1 or $cell > $size or $cell < 0) { die "row ($row) or cell ($cell) out of range ($size)"; } $board[$row-1]->[$cell-1] = $who; return @board; } sub parse { my($board) = @_; if ((not defined($board)) or (length($board) != $size*$size)) { $board = (($blank)x$size)x$size; } my @board = split(//o, $board); foreach (1..$size) { my @row; foreach (1..$size) { push(@row, shift(@board)); } push(@board, \@row); } return @board; } sub encode { my(@board) = @_; my $board = ''; foreach my $row (@board) { foreach my $cell (@$row) { $board .= $cell; } } return $board; } sub paintBoard { my($done, $board, @board) = @_; my $rowNum = 0; print "| $meChar | \n"; } elsif ($cell eq $you) { print "$youChar | \n"; } else { print "\n"; if (not $done) { &printForm($rowNum, $cellNum, $board); } print " | \n"; } } print "
winning offence...\n"; @attempt = &findObvious($me, @board); return (@attempt) if (@attempt); print "
emergency defense...\n"; @attempt = &findObvious($you, @board); return (@attempt) if (@attempt); print "
meta defense...\n"; @attempt = &findSemiObvious($you, @board); return (@attempt) if (@attempt); print "
meta offense...\n"; @attempt = &findSemiObvious($me, @board); return (@attempt) if (@attempt); my $rowNum; my $cellNum; print "
obscure attacks...\n"; $rowNum = 0; foreach my $row (@board) { $rowNum++; $cellNum = 0; foreach my $cell (@$row) { $cellNum++; @attempt = &checkSquare($rowNum, $cellNum, [$blank], [$blank, $me], [$you], [], $size, 2, @board); if (@attempt) { @attempt = &checkSquare($rowNum, $cellNum, [$blank], [$blank, $me, $you], [], [], $size, 3, @board); return (@attempt) if (@attempt); # .....x... -> O....x... } @attempt = &checkSquare($rowNum, $cellNum, [$blank], [$you, $me], [], [], $size-1, 1, @board); if (@attempt) { @attempt = &checkSquare($rowNum, $cellNum, [$blank], [$blank], [], [], $size, 2, @board); return (@attempt) if (@attempt); # O...X.... -> O...X...O -> O wins vs O...X.... O.O.X... -> draw } } } print "
obscure defences...\n"; # ........X -> ...O....X -> X..O....X -> draw vs ........X ....O...X-> O...O...X -> X wins $rowNum = 0; foreach my $row (@board) { $rowNum++; $cellNum = 0; foreach my $cell (@$row) { $cellNum++; @attempt = &checkSquare($rowNum, $cellNum, [$blank], [$blank], [], [], $size, 2, @board); if (not @attempt) { @attempt = &checkSquare($rowNum, $cellNum, [$blank], [$blank, $you], [], [$me], $size, 1, @board); return (@attempt) if (@attempt); } } } foreach my $possibilities (4, 3, 2, 1) { print "
first square with at least $possibilities possibilities...\n"; $rowNum = 0; foreach my $row (@board) { $rowNum++; $cellNum = 0; foreach my $cell (@$row) { $cellNum++; @attempt = &checkSquare($rowNum, $cellNum, [$blank], [$blank, $me], [], [], $size, $possibilities, @board); return (@attempt) if (@attempt); } } } # cannot win, go anywhere that blocks them print "
Cannot win, looking to block...\n"; $rowNum = 0; foreach my $row (@board) { $rowNum++; $cellNum = 0; foreach my $cell (@$row) { $cellNum++; @attempt = &checkSquare($rowNum, $cellNum, [$blank], [$you], [], [], 1, 1, @board); return (@attempt) if (@attempt); } } # cannot block (?!), go anywhere at all! print "
Cannot even block, going in first free block...\n"; $rowNum = 0; foreach my $row (@board) { $rowNum++; $cellNum = 0; foreach my $cell (@$row) { $cellNum++; return ($rowNum, $cellNum) if $board[$rowNum-1]->[$cellNum-1] eq $blank; } } die "No Spaces Left!"; } sub findObvious { my($target, @board) = @_; my $rowNum; my $cellNum; $rowNum = 0; foreach my $row (@board) { $rowNum++; $cellNum = 0; foreach my $cell (@$row) { $cellNum++; my @result = &checkSquare($rowNum, $cellNum, [$blank], [$target], [], [], $size-1, 1, @board); return @result if @result; } } return; } sub findSemiObvious { my($target, @board) = @_; my $rowNum; my $cellNum; $rowNum = 0; foreach my $row (@board) { $rowNum++; $cellNum = 0; foreach my $cell (@$row) { $cellNum++; my @result = &checkSquare($rowNum, $cellNum, [$blank], [$target, $blank], [], [$target eq $me ? $you : $me], $size, 2, @board); if (@result) { my @result = &checkSquare($rowNum, $cellNum, [$blank], [$target], [], [$target eq $me ? $you : $me], 1, 2, @board); return @result if @result; } } } return; } # HARD MODE #################################################################### sub findSpotHard { my(@board) = @_; my @scores; print "
\ntic-tac-toe algorithm\n";
foreach my $rowNum (1..@board) {
foreach my $cellNum (1..@{$board[$rowNum-1]}) {
if ($board[$rowNum-1]->[$cellNum-1] eq $blank) {
print "placing $me at $rowNum, $cellNum\n";
my $score = &tryMove($rowNum, $cellNum, $me, $you, 1, ' |', &cloneBoard(@board));
print " + sum of scores for $rowNum, $cellNum: $score\n";
push(@scores, [$rowNum, $cellNum, $score]);
}
}
}
@scores = sort { $b->[2] <=> $a->[2] } @scores;
print "\n";
return ($scores[0]->[0], $scores[0]->[1]);
}
# performs a move
sub tryMove {
my($row, $cell, $a, $b, $depth, $prefix, @board) = @_;
return 0 if ($depth > $maxDepth);
my $encoded = &encode(@board);
my $key = "$row|$cell|$a|$depth|$encoded";
if ($cache{$key}) {
print "$prefix + FROM CACHE: $cache{$key} \n" if $debug;
return $cache{$key};
} else {
$board[$row-1]->[$cell-1] = $a;
my $encodedAgain = &encode(@board);
print "$prefix + state: $encodedAgain \n" if $debug;
my $return = 0;
my $result = &tryDone(@board);
if ($result) {
print "$prefix + end of the line, '$result' win " if $debug;
if ($result eq $you) {
# loss.
$return = -1;
} elsif ($result eq $me) {
# win!
$return = $size**($size*$size - $depth);
} else {
# draw...
$return = 0;
}
print "($return)\n" if $debug;
} else {
my $score;
loop: foreach my $rowNum (1..@board) {
foreach my $cellNum (1..@{$board[$rowNum-1]}) {
if ($board[$rowNum-1]->[$cellNum-1] eq $blank) {
print "$prefix + placing $b at $rowNum, $cellNum\n" if $debug;
my $newScore = (&tryMove($rowNum, $cellNum, $b, $a, $depth+1, "$prefix |", &cloneBoard(@board)));
if ($b eq $me) {
if ($newScore >= 1) {
$return = $newScore; # we can win if they do whatever it was we suggested in the previous level
last loop;
} elsif ($newScore == 0) { # if we do this we'll draw
$score = 0 if defined $score and $score < 0;
} elsif ($newScore == -1) {
$score = -1 unless defined($score);
} else {
die "negative score!";
}
} else {
if ($newScore == -1) { # we lose if they do this move
$return = -1; # whatever move we last tried should never be done
last loop;
} else {
if (defined $score) {
$score += $newScore+1;
} else {
$score = $newScore+1;
}
}
}
}
}
}
if ($return == 0 and defined($score)) {
$return = $score;
}
print "$prefix ' ($return)\n" if $debug;
}
print "$prefix\n" if $debug;
$cache{$key} = $return;
return $return;
}
}
sub cloneBoard {
my(@board) = @_;
my @newboard;
foreach my $rowNum (1..@board) {
my @newrow;
foreach my $cellNum (1..@{$board[$rowNum-1]}) {
push(@newrow, $board[$rowNum-1]->[$cellNum-1]);
}
push(@newboard, [@newrow]);
}
return @newboard;
}
sub tryDone {
my(@board) = @_;
# flatten the board
my $flatRows;
my $flatCells;
my $flatDiag1;
my $flatDiag2;
foreach my $a (0..$size-1) {
foreach my $b (0..$size-1) {
$flatRows .= $board[$a]->[$b];
$flatCells .= $board[$b]->[$a];
}
$flatRows .= '|';
$flatCells .= '|';
$flatDiag1 .= $board[$a]->[$a];
$flatDiag2 .= $board[$a]->[$size-$a-1];
}
my $flat = "$flatRows$flatCells$flatDiag1|$flatDiag2";
my $yous = $you x $size;
my $mes = $me x $size;
return $you if $flat =~ /\Q$yous\E/o;
return $me if $flat =~ /\Q$mes\E/o;
return 0 if $flat =~ /[$blank$me]{$size}/o;
return 0 if $flat =~ /[$blank$you]{$size}/o;
return $blank;
}
# HELPER FUNCTIONS #############################################################
sub in {
my($value, @allowedValues) = @_;
foreach (@allowedValues) {
die "no value!" unless $value;
return 1 if $_ eq $value;
}
return 0;
}
sub done {
my(@board) = @_;
my $rowNum;
my $cellNum;
my @result;
# any winners?
$rowNum = 0;
foreach my $row (@board) {
$rowNum++;
$cellNum = 0;
foreach my $cell (@$row) {
$cellNum++;
@result = &checkSquare($rowNum, $cellNum, [$you], [$you], [], [], $size, 1, @board);
return $you if @result;
@result = &checkSquare($rowNum, $cellNum, [$me], [$me], [], [], $size, 1, @board);
return $me if @result;
}
}
# draw?
$rowNum = 0;
foreach my $row (@board) {
$rowNum++;
$cellNum = 0;
foreach my $cell (@$row) {
$cellNum++;
@result = &checkSquare($rowNum, $cellNum, [$blank], [$you, $blank], [], [], $size, 1, @board);
return 0 if @result;
@result = &checkSquare($rowNum, $cellNum, [$blank], [$me, $blank], [], [], $size, 1, @board);
return 0 if @result;
}
}
return $blank;
}
sub getClass {
my($row, $cell, @board) = @_;
my @result;
@result = &checkSquare($row, $cell, [$you], [$you], [], [], $size, 1, @board);
return 'won' if @result;
@result = &checkSquare($row, $cell, [$me], [$me], [], [], $size, 1, @board);
return 'won' if @result;
return '';
}
sub checkSquare {
my($rowNum, $cellNum, $directValues, $allowedValues, $disallowedValuesEverywhere, $disallowedValuesOnMatchingRows,
$requiredCount, $requiredDifferentCount, @board) = @_;
my $differentCount = 0;
my $count;
my $pos;
if (&in($board[$rowNum-1]->[$cellNum-1], @$directValues)) {
# horizontal
$count = 0;
foreach my $otherCell (@{$board[$rowNum-1]}) {
if (&in($otherCell, @$disallowedValuesEverywhere)) {
return;
}
if (&in($otherCell, @$disallowedValuesOnMatchingRows)) {
$count = 0;
last;
}
if (&in($otherCell, @$allowedValues)) {
$count++;
}
}
if ($count >= $requiredCount) {
$differentCount++;
}
# vertical
$count = 0;
foreach my $otherRow (@board) {
if (&in($otherRow->[$cellNum-1], @$disallowedValuesEverywhere)) {
return;
}
if (&in($otherRow->[$cellNum-1], @$disallowedValuesOnMatchingRows)) {
$count = 0;
last;
}
if (&in($otherRow->[$cellNum-1], @$allowedValues)) {
$count++;
}
}
if ($count >= $requiredCount) {
$differentCount++;
}
# diagonal nwse
if ($rowNum == $cellNum) {
$count = 0;
$pos = 0;
foreach my $otherRow (@board) {
$pos++;
if (&in($otherRow->[$pos-1], @$disallowedValuesEverywhere)) {
return;
}
if (&in($otherRow->[$pos-1], @$disallowedValuesOnMatchingRows)) {
$count = 0;
last;
}
if (&in($otherRow->[$pos-1], @$allowedValues)) {
$count++;
}
}
if ($count == $requiredCount) {
$differentCount++;
}
}
# diagonal nesw
if ($rowNum == $size-$cellNum+1) {
$count = 0;
$pos = 0;
foreach my $otherRow (@board) {
$pos++;
if (&in($otherRow->[$size-$pos], @$disallowedValuesEverywhere)) {
return;
}
if (&in($otherRow->[$size-$pos], @$disallowedValuesOnMatchingRows)) {
$count = 0;
last;
}
if (&in($otherRow->[$size-$pos], @$allowedValues)) {
$count++;
}
}
if ($count == $requiredCount) {
$differentCount++;
}
}
}
if ($differentCount >= $requiredDifferentCount) {
return ($rowNum, $cellNum);
} else {
return;
}
}