Perl, 147 bytes (não concorrente, leva mais de 10 segundos por movimento)
Inclui +4 para -0p
O programa é reproduzido X
. Jogará um jogo perfeito.
Insira a placa no STDIN, por exemplo:
tictaclatin.pl
-X-O
-X--
X-X-
O--O
^D
A saída será a mesma placa, com todas X
substituídas por O
e vice-versa. As vagas vazias serão preenchidas com um número indicando o resultado se X for jogado lá, o que 1
significa que o resultado será uma vitória, 2
um empate e 3
uma perda. Um jogo terminado retorna a mesma posição com as cores invertidas.
Neste exemplo, a saída seria:
1O1X
1O33
O3O3
X33X
Portanto, a posição é uma vitória, X
se ele jogar nos 3 lugares ao longo do topo e da esquerda. Todos os outros movimentos perdem.
Essa saída confusa é realmente conveniente se você quiser saber como o jogo continua após uma jogada. Como o programa sempre é reproduzido, X
você deve trocar X
e O
ver os movimentos O
. Aqui, por exemplo, é bem claro que X
ganha ao jogar no canto superior esquerdo, mas e se X
jogar na terceira posição ao longo do topo? Basta copiar a saída, colocar um O
lugar no movimento que você selecionar e substituir todos os outros números -
novamente, então aqui:
-OOX
-O--
O-O-
X--X
Resultando em:
3XXO
3X33
X3X3
O33O
Obviamente, todo movimento O
deve perder, então como ele perde se jogar no canto superior esquerdo? Novamente, faça isso colocando O
no canto superior esquerdo e substituindo os dígitos por -
:
OXXO
-X--
X-X-
O--O
Dando:
XOOX
1O33
O3O3
X33X
Então X tem apenas um caminho a percorrer para sua vitória:
XOOX
OO--
O-O-
X--X
Dando
OXXO
XX33
X3X3
O33O
A situação para O
permanece sem esperança. É fácil ver agora que cada movimento permite X
ganhar imediatamente. Vamos pelo menos tentar fazer 3 O's seguidos:
OXXO
XX--
X-X-
O-OO
Dando:
XOOX
OO13
O3O3
X3XX
X
joga a única jogada vencedora (observe que isso ocorre XXXO
na terceira coluna:
XOOX
OOO-
O-O-
X-XX
Aqui a saída é:
OXXO
XXX-
X-X-
O-OO
porque o jogo já estava terminado. Você pode ver a vitória na terceira coluna.
O programa atual 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)
Aplicado ao tabuleiro vazio, ele avalia 9506699 posições, o que leva 30 GB e 41 minutos no meu computador. O resultado é:
2222
2222
2222
2222
Assim, cada movimento inicial é empatado. Então o jogo é um empate.
O uso extremo da memória é causado principalmente pela recursão em uso do$0
. O uso desta versão de 154 bytes usando uma função comum precisa de 3Gb e 11 minutos:
#!/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
o que é mais suportável (mas ainda é demais, algo ainda deve estar vazando de memória).
A combinação de várias acelerações leva a esta versão de 160 bytes (5028168 posições, 4 minutos e 800M para o tabuleiro vazio):
#!/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
Esse último usa 0
para uma vitória (não confunda O
), 1
para um empate e 2
para uma derrota. A saída deste também é mais confusa. Ele preenche a jogada vencedora para X no caso de uma vitória sem troca de cores, mas se o jogo de entrada já foi ganho, ainda assim a troca de cores e não preenche nenhuma jogada.
É claro que todas as versões ficam mais rápidas e usam menos memória à medida que a placa se enche. As versões mais rápidas devem gerar um movimento em menos de 10 segundos assim que 2 ou 3 movimentos forem feitos.
Em princípio, esta versão de 146 bytes também deve funcionar:
#!/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)
mas na minha máquina ele aciona um bug perl e despeja o núcleo.
Todas as versões, em princípio, ainda funcionarão se o cache da posição de 6 bytes feito por $$_||=
for removido, mas isso usa tanto tempo e memória que funciona apenas para placas quase cheias. Mas, em teoria, pelo menos eu tenho uma solução de 140 bytes.
Se você colocar $\=
(custo: 3 bytes) imediatamente antes do $@<=>0
painel, cada placa de saída será seguida pelo status de todo o painel: 1
para X
vitórias, 0
empates e -1
perdas.
Aqui está um driver interativo baseado na versão mais rápida mencionada acima. O motorista não tem lógica para quando o jogo terminar, então você deve se parar. O código golfado sabe embora. Se a jogada sugerida retornar sem -
substituir por nada, o jogo acabou.
#!/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;
}