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 Xsubstituídas por Oe vice-versa. As vagas vazias serão preenchidas com um número indicando o resultado se X for jogado lá, o que 1significa que o resultado será uma vitória, 2um empate e 3uma 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, Xse 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, Xvocê deve trocar Xe Over os movimentos O. Aqui, por exemplo, é bem claro que Xganha ao jogar no canto superior esquerdo, mas e se Xjogar na terceira posição ao longo do topo? Basta copiar a saída, colocar um Olugar 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 Odeve perder, então como ele perde se jogar no canto superior esquerdo? Novamente, faça isso colocando Ono 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 Opermanece sem esperança. É fácil ver agora que cada movimento permite Xganhar imediatamente. Vamos pelo menos tentar fazer 3 O's seguidos:
OXXO
XX--
X-X-
O-OO
Dando:
XOOX
OO13
O3O3
X3XX
Xjoga a única jogada vencedora (observe que isso ocorre XXXOna 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 0para uma vitória (não confunda O), 1para um empate e 2para 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 $@<=>0painel, cada placa de saída será seguida pelo status de todo o painel: 1para Xvitórias, 0empates e -1perdas.
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;
}