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 O підряд:
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 хвилини та 800 М для порожньої дошки):
#!/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;
}