Croci, niente spille


10

Tutti si rendono conto che Tic Tac Toe è un gioco risolto. Tuttavia, la versione Misère di only-X offre un'alternativa interessante.

In questa versione del gioco, entrambi i giocatori giocano X sul tabellone e cercano di evitare di fare tre di fila. Se vuoi vedere di più su questo, Numberphile ha un bel video su questo concetto.

Data una tavola di Misère Crosses, gioca una mossa ottimale.

Una tavola è composta da tre righe di tre caratteri ciascuna, che sono Xo . Così:

X X
X  
 XX

è una scheda valida. Puoi prenderlo in qualsiasi formato conveniente, a condizione che input e output utilizzino lo stesso formato. I formati includono (ma non sono limitati a): una stringa multilinea (con newline finale opzionale); Una matrice 2D di caratteri che sono Xo ; un array appiattito 1D di valori booleani che rappresentano se ogni posizione è stata giocata.

Una mossa ottimale è quella che ti garantisce di vincere continuando a giocare in modo ottimale o prolunga la perdita il più a lungo possibile ed è definita dalle seguenti regole:

  • Evita di fare tre di fila.
  • Se vai per primo, gioca nel mezzo.
  • Se l'unico spazio occupato è il centro, gioca in uno degli spazi rimanenti.
  • Se la casella centrale non è occupata e una casella esterna lo è, gioca di fronte all'ultima giocata del tuo avversario.
  • Se la casella centrale è occupata e una casella esterna è, gioca una "mossa da cavaliere" (opposta, una sopra) lontano da una mossa precedente che non ti fa perdere.
  • Se non restano quadrati rimanenti dove non perderai, gioca in uno dei quadrati rimanenti.

[NOTA: questo si è dimostrato non ottimale in un caso, ma dovresti comunque utilizzare questo algoritmo.]

Puoi presumere che tutte le tue mosse precedenti fossero ottimali. Pertanto, la prima scheda di esempio non è un input valido. Le mosse del tuo avversario possono o meno essere ottimali.

Se il gioco è terminato (ovvero è stato effettuato un tre di fila), il comportamento non è definito.

Dato che si tratta di , vince la risposta più breve in byte!

Un possibile percorso, utilizzando solo mosse ottimali, è questo:

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

Qui ci sono possibili input che provengono dall'avversario usando mosse non ottimali:
(Nota che solo le schede di sinistra in questo elenco sono input validi).

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

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

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


Quali sono i formati di input e output? Sto assumendo una scheda presa come un array o una stringa? Tuttavia, ciò non fornisce informazioni sull'ultima mossa, quindi la mia prossima domanda.
Level River St

1
La strategia "gioca di fronte all'ultima giocata del tuo avversario" presuppone una conoscenza della cronologia delle mosse del tuo avversario o che tu abbia precedentemente seguito questa strategia, cioè non abbia ereditato una tavola come .XX\nX..\nX..ad esempio. Dobbiamo considerare l'ereditarietà di schede come questa?
Level River St

@LevelRiverSt Come scritto, "Puoi presumere che tutte le tue mosse precedenti fossero ottimali", quindi quella scheda sarebbe stata un input non valido. Puoi prendere l'input in qualunque formato ti piaccia, ma una stringa multi-linea come il tuo esempio ci sarebbe il "default": non voglio limitare nessuno a dover analizzare la stringa quando la logica di spostamento è il punto di la sfida.
CAD97,

Risposte:


3

Rubino, Rev. B 121 byte

L'invio è la funzione anonima, meno il f=. Indicato nel programma di test per illustrare l'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 byte salvati rendendo il quadratino centrale il bit meno significativo anziché il bit più significativo (rimuovi per /2anziché %256.) Risparmi rimanenti mediante una riorganizzazione della tabella delle mosse accettabili. L'organizzazione come quadrato centrale libero / occupato anziché per il numero totale di X consente un test più semplice. Inoltre, ora ci sono solo 2 stringhe nell'array, quindi la %w{string1 string2}sintassi viene abbandonata a favore della ["string1","string2"]sintassi. Ciò consente \7di includere un carattere non stampabile , che a sua volta consente di utilizzare una codifica più semplice: 126-tanziché (36-t)%120.

Rubino, Rev A 143 byte

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

Questa è una funzione anonima. Il formato di input / output è stato lasciato aperto, quindi ho scelto un numero binario a 9 bit. il bit del 512 rappresenta il centro, con i bit rimanenti che lo circondano a spirale (il bit dell'1 è considerato un angolo).

Ci sono molti più input possibili che output accettabili, quindi l'algoritmo è quello di provare tutti i movimenti e trovarne uno che si adatti a un modello di output accettabile. I modelli di output accettabili per ogni numero di X sono codificati.

Le informazioni sul quadrato centrale vengono rimosse e gli 8 bit rimanenti vengono moltiplicati per 257 per duplicarli. Questo modello viene quindi ruotato oltre i modelli accettabili spostando i diritti.

Il loop non viene chiuso quando viene trovato un pattern, quindi il pattern restituito sarà il LAST pattern accettabile trovato. Per questo motivo, i modelli preferibili (dove esiste una preferenza) vengono più avanti nell'elenco.

Data la strategia del "movimento dei cavalieri", è poco importante che un modello venga ruotato di 45 gradi o meno. La versione ungolfed segue la strategia di spostamento dei cavalieri e quindi non ha bisogno di distinguere tra quadrati d'angolo e quadrati di bordo: bisogna comunque evitarne tre di fila.

Tuttavia, ho scoperto che questa non è sempre la strategia migliore, in quanto esiste il seguente trucco. Se il tuo avversario va per primo e prende il centro, dovrebbe vincere. Ma alla sua seconda mossa fa l'errore di permetterti di fare un quadrato 2x2 che dovresti prendere, in quanto ciò ti consente di costringerlo a fare tre di fila. Questo è implementato nella versione golf. In questo caso è necessario un piccolo codice aggiuntivo per distinguere tra tre X in un angolo (forza dell'avversario per perdere) e 3 X lungo un lato (suicidio immediato).

Non registrato nel programma di test

La versione non golfata segue la logica espressa nella domanda.

Nella versione golf la tabella è leggermente modificata [[0],[1,17],[9],[37,7,51,85],[45],[47,119]]per implementare il comportamento leggermente diverso per il caso r=3. Viene quindi compresso in ASCII stampabile (che richiede la decodifica (t-36)%120). È necessario un ulteriore bit di logica per distinguere tra tre X in un angolo e tre X lungo un bordo nel caso della voce di tabella 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}

Uscita del programma di test

Questo succede quando il computer si gioca da solo.

C: \ Users \ steve> ruby ​​tictac.rb
0
256

000
010
000

C: \ Users \ steve> ruby ​​tictac.rb
256
384

010
010
000

C: \ Users \ steve> ruby ​​tictac.rb
384
400

010
010
100

C: \ Users \ steve> ruby ​​tictac.rb
400
404

010
010
101

C: \ Users \ steve> ruby ​​tictac.rb
404
436

010
110
101

C: \ Users \ steve> ruby ​​tictac.rb
436
444

010
110
111

ANALISI DEL GIOCO GIOCANDO PER PRIMO

Questo è in realtà molto semplice e lineare.

Quando si gioca per primo, il quadrato centrale sarà sempre il primo quadrato occupato.

r = 0

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

r = 2

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

r = 4

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

C'è solo un modo (fino alla simmetria) per avere cinque X incluso il quadrato centrale sul tabellone senza che il gioco sia finito. C'è una X nel quadrato centrale, una su ciascuna diagonale (a 90 gradi l'una rispetto all'altra) e una su ciascuna linea centrale orizzontale / verticale (a 90 gradi l'una rispetto all'altra). Poiché un intero bordo non può essere occupato, il precedente è l'unico accordo possibile. L'altro giocatore deve perdere alla prossima mossa.

ANALISI DEL GIOCO CHE GIOCA SECONDO

Il gioco è abbastanza diverso a seconda che l'altro giocatore scelga il quadrato centrale.

r = 1

piazza centrale occupata

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

piazza centrale libera

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

r = 3

Quadrato centrale occupato, se un altro giocatore gioca adiacente alla tua ultima X Giocare una mossa di un cavaliere come di seguito è supportato nella versione non golfata

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

Tuttavia, quanto sopra NON è la mossa migliore e non è supportato nella versione golf. La mossa migliore è la seguente, forzando una vittoria al turno successivo:

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

Quadrato centrale occupato, se un altro giocatore gioca a 90 o 135 gradi rispetto alla tua ultima X (gioca la mossa del cavaliere).

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

Piazza centrale libera

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

r = 5

piazza centrale occupata. Per le ragioni sopra esposte in r = 4, ci sono quattro possibili mosse, che perdono tutte. ne è supportato solo uno: 101111 = 47.

piazza centrale libera. C'è solo una scheda possibile fino alla simmetria, come segue. L'altro giocatore deve perdere alla prossima mossa, quindi non è necessario supportare r> 5.

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

Questa è una risposta meravigliosa! Pensavo di aver controllato tutti i casi per il moe ottimale, ma credo di averne perso uno. Tuttavia, le specifiche rimarranno le stesse per semplicità. (Davvero questo è fantastico, grazie per averlo fatto e questo è così ben spiegato! Ho perso l'I / O in modo che la gente potesse fare qualcosa di straordinario come questo.)
CAD97

1
Grazie, è stata una sfida interessante. Sono riuscito a giocare a golf un po 'di più ora.
Level River St
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.