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;
}