Cruzes, nada


10

Todo mundo percebe que Tic Tac Toe é um jogo resolvido. No entanto, a versão Misère do only-Xs fornece uma alternativa interessante.

Nesta versão do jogo, os dois jogadores jogam Xs no tabuleiro e tentam evitar fazer três seguidas. Se você quiser saber mais sobre isso, o Numberphile tem um bom vídeo sobre esse conceito.

Dado um quadro de Misère Crosses, faça uma jogada ótima.

Um quadro é composto por três linhas de três caracteres cada, que são Xou . Portanto:

X X
X  
 XX

é uma placa válida. Você pode fazer isso em qualquer formato conveniente, desde que sua entrada e saída usem o mesmo formato. Os formatos incluem (mas não estão limitados a): Uma sequência de várias linhas (com nova linha à direita opcional); Uma matriz 2D de caracteres que são Xou ; uma matriz 1D achatada de valores booleanos representando se cada posição foi jogada.

Uma jogada ideal é aquela que garante que você vencerá, continuando a jogar de maneira ideal ou prolongando sua perda pelo maior tempo possível e é definido pelas seguintes regras:

  • Evite fazer três seguidas.
  • Se você for o primeiro, jogue no meio.
  • Se o único espaço ocupado for o meio, toque em qualquer um dos espaços restantes.
  • Se o quadrado do meio não estiver ocupado e um quadrado externo, jogue na frente do último jogo do seu oponente.
  • Se o quadrado do meio estiver ocupado e um quadrado externo, toque um "movimento de cavaleiro" (oposto, um acima) do movimento anterior que não faz com que você perca.
  • Se nenhum quadrado restante for deixado onde você não perderá, jogue em qualquer um dos quadrados restantes.

[OBSERVAÇÃO: isso provou não ser o ideal em um caso, mas você deve usar esse algoritmo de qualquer maneira.]

Você pode supor que todos os seus movimentos anteriores foram ótimos. Portanto, o primeiro quadro de exemplo não é uma entrada válida. Os movimentos do seu oponente podem ou não ser ótimos.

Se o jogo terminou (ou seja, foram feitos três em sequência), o comportamento é indefinido.

Como esse é o , a resposta mais curta em bytes vence!

Um caminho possível, usando apenas movimentos ideais, é o seguinte:

[   ]  [   ]  [X  ]  [X  ]  [X  ]  [X  ]  [XX ]
[   ]->[ X ]->[ X ]->[ XX]->[ XX]->[ XX]->[ XX]
[   ]  [   ]  [   ]  [   ]  [ X ]  [XX ]  [XX ]

Aqui estão possíveis entradas originárias do oponente usando movimentos não ideais:
(Observe que apenas as placas do lado esquerdo nesta lista são entradas válidas.)

[X  ]  [X  ]
[   ]->[   ]
[   ]  [  X]

[XX ]  [XX ]
[   ]->[   ]
[  X]  [ XX]

[XX ]  [XX ]
[X  ]->[X X]
[ XX]  [ XX]


Quais são os formatos de entrada e saída? Estou assumindo uma placa tomada como uma matriz ou seqüência de caracteres? No entanto, isso não fornece informações sobre a última jogada, daí a minha próxima pergunta.
Level River St

11
A estratégia "jogo oposto ao último jogo do seu oponente" pressupõe o conhecimento do histórico de movimentos do seu oponente ou que você seguiu essa estratégia anteriormente, ou seja, não herdou um tabuleiro como .XX\nX..\nX..por exemplo. Temos que considerar a possibilidade de herdar conselhos como este?
Level River St

@LevelRiverSt Como está escrito, "Você pode assumir que todos os seus movimentos anteriores foram ótimos", para que o quadro seja uma entrada inválida. Você pode receber entradas no formato que quiser, mas uma sequência de várias linhas, como o seu exemplo, seria o "padrão": só não quero restringir ninguém a analisar a String quando a lógica de movimentação é o ponto de o desafio.
CAD97

Respostas:


3

Ruby, Rev B 121 bytes

Submissão é a função anônima, menos o f=. Mostrado no programa de teste para ilustrar o uso.

f=->n{["~mK)\7","}uYwQO"][l=n%2].bytes{|t|9.times{|i|(m=n|1<<i)==n||8.times{|j|m/2*257>>j&255==126-t&&t+j%2!=119&&l=m}}}
l}

puts g=f[gets.to_i]
puts
[7,6,5,
 8,0,4,
 1,2,3].each{|i|print g>>i&1; puts if i/3==1}

2 bytes salvos tornando o quadrado do centro o bit menos significativo em vez do bit mais significativo (remova por em /2vez de %256). Economias remanescentes por uma reorganização da tabela de movimentos aceitáveis. Organizar como quadrado central livre / ocupado, em vez de pelo número total de X, permite um teste mais simples. Além disso, agora existem apenas duas cadeias na matriz, portanto a %w{string1 string2}sintaxe é abandonada em favor da ["string1","string2"]sintaxe. Isso permite que um caractere não imprimível \7seja incluído, o que, por sua vez, permite que uma codificação mais simples seja usada: em 126-tvez de (36-t)%120.

Ruby, Rev A 143 bytes

->n{l=r=("%b"%n).sum%8
%w{$ %5 - I+Wy Q S#}[r].bytes{|t|9.times{|i|(m=n|1<<i)==n||8.times{|j|m%256*257>>j&255==(t-36)%120&&t+j%2!=43&&l=m}}}
l}

Esta é uma função anônima. O formato de entrada / saída foi deixado aberto, por isso procurei um número binário de 9 bits. o bit de 512 representa o centro, com os bits restantes em espiral em torno dele (o bit de 1 é considerado um canto).

Existem muito mais entradas possíveis do que saídas aceitáveis; portanto, o algoritmo é tentar todas as jogadas e encontrar uma que se encaixe em um padrão de saída aceitável. Os padrões de saída aceitáveis ​​para cada número de X são codificados.

As informações sobre o quadrado central são removidas e os 8 bits restantes são multiplicados por 257 para duplicá-los. Esse padrão é então rotacionado para além dos padrões aceitáveis ​​pela mudança de direitos.

O loop não é encerrado quando um padrão é encontrado; portanto, o padrão retornado será o ÚLTIMO padrão aceitável encontrado. Por esse motivo, os padrões preferenciais (onde há uma preferência) aparecem mais adiante na lista.

Dada a estratégia 'Cavaleiros se movem', é de pouca importância se um padrão é girado 45 graus ou não. A versão não-gasta segue a estratégia de movimento dos cavaleiros e, portanto, não precisa diferenciar os quadrados dos cantos e dos quadrados das arestas: três de uma vez devem ser evitados de qualquer maneira.

No entanto, descobri que essa nem sempre é a melhor estratégia, pois existe o seguinte truque. Se o seu oponente for o primeiro e tomar o centro, ele deve vencer. Mas em seu segundo movimento, ele comete o erro de permitir que você faça um quadrado de 2x2, você deve aceitá-lo, pois isso permite que você o force a fazer três seguidas. Isso é implementado na versão golfed. É necessário um pouco de código extra nesse caso para distinguir entre três Xs em um canto (forçar o oponente a perder) e 3 Xs em uma borda (suicídio imediato).

Ungolfed in program program

A versão ungolfed segue a lógica expressa na pergunta.

Na versão golfed a tabela é ligeiramente modificado para [[0],[1,17],[9],[37,7,51,85],[45],[47,119]]implementar o comportamento ligeiramente diferente para o caso r=3. Em seguida, é compactado para ASCII imprimível (exigindo decodificação (t-36)%120). É necessário um pouco de lógica adicional para diferenciar entre três Xs em um canto e três Xs ao longo de uma borda no caso da entrada da tabela 7:&&t+j%2!=43

f=->n{l=r=("%b"%n).sum%8                                      #convert input to text, take character checksum to count 1's(ASCII 49.) 
                                                              #0 is ASCII 48, so %8 removes unwanted checksum bloat of 48 per char.
                                                              #l must be initialised here for scoping reasons.
  [[0],[1,17],[9],[11,13,37,51,85],[45],[47,119]][r].each{|t| #according to r, find the list of acceptable perimeter bitmaps, and search for a solution.
    9.times{|i|(m=n|1<<i)==n||                                #OR 1<<i with input. if result == n, existing X overwritten, no good.
                                                              #ELSE new X is in vacant square, good. So.. 
      8.times{|j|m%256*257>>j&255==t&&l=m}}                   #%256 to strip off middle square. *257 to duplicate bitmap.
                                                              #rightshift, see if pattern matches t. If so, write to l
  }
l}                                                            #return l (the last acceptable solution found) as the answer.

#call function and pretty print output (not part of submission)
puts g=f[gets.to_i]
puts
[6,7,0,
 5,8,1,
4,3,2].each{|i|print g>>i&1; puts if i<3}

Saída do programa de teste

É o que acontece quando o computador se reproduz.

C: \ Usuários \ steve> ruby ​​tictac.rb
0 0
256

000
010
000

C: \ Usuários \ steve> ruby ​​tictac.rb
256
384

010
010
000

C: \ Usuários \ steve> ruby ​​tictac.rb
384
400

010
010
100

C: \ Usuários \ steve> ruby ​​tictac.rb
400
404

010
010
101

C: \ Usuários \ steve> ruby ​​tictac.rb
404
436

010
110
101

C: \ Usuários \ steve> ruby ​​tictac.rb
436
444

010
110
111

ANÁLISE DO JOGO QUE JOGA PRIMEIRO

Isso é realmente muito simples e linear.

Ao jogar primeiro, o quadrado do meio será sempre o primeiro quadrado ocupado.

r = 0

...  binary representation 0
.X.
...

r = 2

X..  binary representation 1001=9 
.XX
...

r = 4

X.. binary representation 101101=45
.XX
XX.

Existe apenas uma maneira (até simetria) de ter cinco Xs, incluindo o quadrado do meio no tabuleiro, sem que o jogo termine. Há um X no quadrado do meio, um em cada diagonal (a 90 graus um do outro) e um em cada linha central horizontal / vertical (a 90 graus um do outro). Como uma borda inteira não pode ser ocupada, a única acima é a única arranjo possível. Outro jogador deve perder na próxima jogada.

ANÁLISE DO JOGO EM SEGUNDO JOGO

O jogo é bem diferente dependendo do outro jogador escolher o quadrado do meio.

r = 1

praça do meio ocupada

.X. X..  binary representation 1 
.X. .X.
... ...

praça do meio livre

X.. .X. binary representation 10001=17
... ...
..X .X.

r = 3

Quadrado médio ocupado, se outro jogador jogar ao lado do seu último X. Jogar a jogada de um cavaleiro, como abaixo, é suportado na versão não-gasta

XX. .XX binary representation 1011=11 
.X. XX. or mirror image 1101=13
X.. ...

No entanto, a descrição acima NÃO é a melhor jogada e não é suportada na versão golfed. A melhor jogada é a seguinte, forçando uma vitória no próximo turno:

XX. binary representation 111=7.           XXX
XX. Only to be used where j is odd.        .X.
... Even j would look like image to right. ...

Quadrado médio ocupado, se outro jogador jogar a 90 ou 135 graus até o seu último X (toque o cavaleiro se afastar).

X.X .X. binary representation 100101=37 
.X. .XX
.X. X..

Praça do meio livre

X.X .X. XX. binary representations:
... X.X ...    1010101=85 (first two)
X.X .X. .XX and 110011=51 (last one)

r = 5

praça do meio ocupada. Pelas razões expostas acima em r = 4, há quatro movimentos possíveis, todos perdendo. apenas um é suportado: 101111 = 47.

praça do meio livre. Existe apenas uma placa possível até simetria, como segue. Outro jogador deve perder na próxima jogada, portanto não há necessidade de apoiar r> 5.

XX. binary representation 1110111=119
X.X
.XX

Esta é uma resposta maravilhosa! Eu pensei que tinha verificado todos os casos para o moe ideal, mas acho que perdi um. As especificações permanecerão as mesmas por simplicidade. (Realmente isso é incrível, obrigado por fazer isso e isso é tão bem explicado! Deixei a E / S perdida para que as pessoas pudessem fazer algo incrível como este.) #
3097

11
Obrigado, foi um desafio interessante. Eu consegui jogar um pouco mais com isso agora.
Level River St
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.