Perl, 147 байт (не конкурирует, занимает больше 10 секунд за ход)
Включает +4 для -0p
Программа играет X
. Будет играть в идеальную игру.
Введите плату на STDIN, например:
tictaclatin.pl
-X-O
-X--
X-X-
O--O
^D
Выход будет той же платой со всеми X
заменены O
и наоборот. Пустые места будут заполнены числом, указывающим результат, если X сыграет там, а это 1
означает, что результатом будут выигрыш, 2
ничья и 3
поражение. Законченная игра просто возвращает ту же позицию с перевернутыми цветами.
В этом примере вывод будет:
1O1X
1O33
O3O3
X33X
Таким образом, позиция является победой, X
если он играет в 3-х точках по верху и слева. Все остальные ходы проигрывают.
Этот запутанный вывод на самом деле удобен, если вы хотите знать, как продолжается игра после хода. Поскольку программа всегда играет, X
вы должны поменяться местами X
и O
посмотреть ходы O
. Здесь, например, довольно ясно, что X
выигрывает, играя в левом верхнем углу, но что если X
играть в третьей позиции вдоль верха? Просто скопируйте вывод, поместите O
вместо выбранного движения ход и замените все остальные числа -
снова, вот так:
-OOX
-O--
O-O-
X--X
В результате чего:
3XXO
3X33
X3X3
O33O
Очевидно, что каждый ход O
должен проигрывать, так как же он проигрывает, если играет в левом верхнем углу? Снова сделайте это, поместив O
верхний левый угол и заменив цифры на -
:
OXXO
-X--
X-X-
O--O
Предоставление:
XOOX
1O33
O3O3
X33X
Так что у Х есть только один способ победить:
XOOX
OO--
O-O-
X--X
дающий
OXXO
XX33
X3X3
O33O
Ситуация для O
остается безнадежной. Теперь легко увидеть, что каждый ход позволяет X
сразу же выиграть. Давайте хотя бы попробуем пойти на 3 О подряд:
OXXO
XX--
X-X-
O-OO
Предоставление:
XOOX
OO13
O3O3
X3XX
X
играет единственный выигрышный ход (обратите внимание, что это делает XXXO
вдоль третьего столбца:
XOOX
OOO-
O-O-
X-XX
Вот вывод:
OXXO
XXX-
X-X-
O-OO
потому что игра была уже закончена. Вы можете увидеть победу в третьей колонке.
Актуальная программа tictaclatin.pl
:
#!/usr/bin/perl -0p
y/XO/OX/,$@=-$@while$|-=/(@{[map{(O.".{$_}O"x3)=~s%O%Z|$`X$'|Z%gr}0,3..5]})(?{$@++})^|$/sx;$@<=>0||s%-%$_="$`O$'";$$_||=2+do$0%eg&&(/1/||/2/-1)
Применительно к пустой доске это оценивает 9506699 позиций, что занимает 30 Гб и 41 минуту на моем компьютере. Результат:
2222
2222
2222
2222
Таким образом, каждый стартовый ход ничья. Так что игра ничья.
Чрезвычайное использование памяти в основном вызвано использованием рекурсии do$0
. Для использования этой 154-байтовой версии с использованием простой функции требуется 3 Гб и 11 минут:
#!/usr/bin/perl -0p
sub f{y/XO/OX/,$@=-$@while$|-=/(@{[map{(O.".{$_}O"x3)=~s%O%Z|$`X$'|Z%gr}0,3..5]})(?{$@++})^|$/sx;$@<=>0||s%-%$_="$`O$'";$$_||=2+&f%eeg&&(/1/||/2/-1)}f
что более терпимо (но все же слишком много, что-то должно все еще течь память).
Объединение нескольких ускорений приводит к этой 160-байтовой версии (5028168 позиций, 4 минуты и 800M для пустой доски):
#!/usr/bin/perl -0p
sub f{y/XO/OX/,$@=-$@while$|-=/(@{[map{(O.".{$_}O"x3)=~s%O%Z|$`X$'|Z%gr}0,3..5]})(?{$@++})^|$/osx;$@<=>0||s%-%$_="$`O$'";$a{$_}//=&f+1or return 1%eeg&&/1/-1}f
Последний использует 0
для победы (не путайте с O
), 1
для ничьей и 2
для поражения. Вывод этого также более запутанный. Он заполняет выигрышный ход для X в случае выигрыша без замены цвета, но если входная игра уже была выиграна, он все равно выполняет замену цвета и не выполняет ни одного хода.
Все версии, конечно, становятся быстрее и используют меньше памяти по мере заполнения платы. Более быстрые версии должны генерировать ход менее чем за 10 секунд, как только было сделано 2 или 3 хода.
В принципе, эта 146-байтовая версия также должна работать:
#!/usr/bin/perl -0p
y/XO/OX/,$@=-$@while/(@{[map{(O.".{$_}O"x3)=~s%O%Z|$`X$'|Z%gr}0,3..5]})(?{$@++})^/sx,--$|;$@<=>0||s%-%$_="$`O$'";$$_||=2+do$0%eg&&(/1/||/2/-1)
но на моей машине это вызывает ошибку Perl и сбрасывает ядро.
Все версии в принципе все еще будут работать, если $$_||=
удалено 6-байтовое позиционное кэширование, но оно использует столько времени и памяти, что работает только для почти заполненных плат. Но в теории по крайней мере у меня есть 140-байтовое решение.
Если вы положили $\=
(стоимость: 3 байта) непосредственно перед тем, за $@<=>0
каждой выходной платой будет следовать статус всей платы: 1
для X
выигрышей, 0
для розыгрыша и -1
для проигрыша.
Вот интерактивный драйвер, основанный на самой быстрой версии, упомянутой выше. У водителя нет логики, когда игра заканчивается, поэтому вы должны остановить себя. Гольф-код знает, хотя. Если предложенный ход возвращается без -
замены, игра окончена.
#!/usr/bin/perl
sub f{
if ($p++ % 100000 == 0) {
local $| = 1;
print ".";
}
y/XO/OX/,$@=-$@while$|-=/(@{[map{(O.".{$_}O"x3)=~s%O%Z|$`X$'|Z%gr}0,3..5]})(?{$@++})^|$/osx;$@<=>0||s%-%$_="$`O$'";$a{$_}//=&f+1or return 1%eeg&&/1/-1}
# Driver
my $tomove = "X";
my $move = 0;
@board = ("----\n") x 4;
while (1) {
print "Current board after move $move ($tomove to move):\n ABCD\n";
for my $i (1..4) {
print "$i $board[$i-1]";
}
print "Enter a move like B4, PASS (not a valid move, just for setup) or just press enter to let the program make suggestions\n";
my $input = <> // exit;
if ($input eq "\n") {
$_ = join "", @board;
tr/OX/XO/ if $tomove eq "O";
$p = 0;
$@="";
%a = ();
my $start = time();
my $result = f;
if ($result == 1) {
tr/OX/XO/ if $tomove eq "O";
tr/012/-/;
} else {
tr/OX/XO/ if $tomove eq "X";
tr/012/123/;
}
$result = -$result if $tomove eq "O";
my $period = time() - $start;
print "\nSuggested moves (evaluated $p positions in $period seconds, predicted result for X: $result):\n$_";
redo;
} elsif ($input =~ /^pass$/i) {
# Do nothing
} elsif (my ($x, $y) = $input =~ /^([A-D])([1-4])$/) {
$x = ord($x) - ord("A");
--$y;
my $ch = substr($board[$y],$x, 1);
if ($ch ne "-") {
print "Position already has $ch. Try again\n";
redo;
}
substr($board[$y],$x, 1) = $tomove;
} else {
print "Cannot parse move. Try again\n";
redo;
}
$tomove =~ tr/OX/XO/;
++$move;
}