Gioca a tic-tac-toe e non perdere mai


14

(Esistono alcune sfide che richiedono l'uso della migliore strategia, ma qui non lo facciamo. Anche se sei in grado di vincere, ti è permesso fare un pareggio)

Sfida

Scrivi un programma che riproduca il gioco tic-tac-toe. Non deve perdere (quindi, dovrebbe terminare il gioco con un pareggio o vincendo).

Metodi di I / O consentiti

  1. L'input potrebbe essere la scheda corrente. Puoi presumere che tutte le mosse precedenti del 2 ° giocatore siano state giocate dal tuo motore.
  2. L'input può essere la mossa del primo giocatore e la tua funzione memorizza le mosse avvenute in passato. In questo caso la funzione viene chiamata più volte, una volta per ogni mossa; o l'inserimento del prompt di funzione / programma per più volte.
  3. Puoi prendere un input aggiuntivo dicendo se sei il primo giocatore o scrivere due funzioni (possibilmente correlate) per risolvere il problema del primo giocatore e quello del secondo giocatore. Se il programma deve utilizzare il metodo di input 2 (chiamata multipla), è possibile decidere cosa viene passato nella prima chiamata.
  4. L'output può essere la tavola dopo il tuo turno.
  5. L'output potrebbe essere la tua mossa.
  6. Una mossa può essere rappresentata come una coppia di numeri (può essere 0-indicizzazione o 1-indicizzazione), un numero nell'intervallo 0 ~ 8 o un numero nell'intervallo 1 ~ 9.
  7. La scheda può essere rappresentata come un array 3 × 3 o un array di lunghezza 9. Anche se la lingua ha un array con indicizzazione 0, è possibile utilizzare 1-indicizzazione.
  8. Le celle della griglia può utilizzare qualsiasi 3 valori diversi per indicare X, Oe svuotare.

Criteri vincenti

Vincono il codice più corto in ogni lingua.


Se ti viene data una perdita, la soluzione non è valida. Stai giocando con altri, quindi la scacchiera non cambierà all'istante, quindiwe can assume that all previous moves of the 2nd player were also played by our engine
l4m2


1
@ l4m2 Basta riavviare l'interprete. Fatto. Perché preoccuparsene? Aumenta inutilmente il conteggio dei byte per nulla.
user202729


4
Non fare il bonus. Richiedilo o rimuovilo, non renderlo facoltativo. Il bonus rovina la sfida ..
Rɪᴋᴇʀ

Risposte:


4

Befunge, 181 168 byte

>>4&5pp20555>>03>16p\::5g8%6p5v
 ^p5.:g605$_ #!<^_|#:-1g61+%8g<
543217539511|:_^#->#g0<>8+00p3+5%09638527419876
v<304p$_v#:->#$$:2`#3_:^
>#\3#13#<111v124236478689189378

Le posizioni sul tabellone sono numerate da 1 a 9. Per impostazione predefinita, ottieni la prima mossa, ma se vuoi consentire al computer di andare per primo, puoi semplicemente inserire 0 per la tua prima mossa. Quando hai effettuato una mossa, il computer risponderà con un numero che indica la loro mossa.

Non ci sono controlli per assicurarti di non inserire una mossa valida e non ci sono nemmeno controlli per vedere se qualcuno ha vinto o perso. Una volta che non ci sono più mosse da fare, il programma passa in un ciclo infinito.

È un po 'difficile testarlo online, poiché non ci sono interpreti online con input interattivi. Tuttavia, se sai quali mosse farai in anticipo (il che presuppone che tu sappia come risponderà il computer), puoi in qualche modo testare su TIO con quelle mosse preprogrammate.

L'utente gioca per primo: provalo online!
Il computer gioca per primo: provalo online!

Per rendere più facile vedere cosa sta succedendo, ho anche una versione che emette il board tra le mosse.

L'utente gioca per primo: provalo online!
Il computer gioca per primo: provalo online!

Nota che dovrai attendere il timeout di TIO prima di poter vedere i risultati.

Spiegazione

La scheda è memorizzata nell'area di memoria di Befunge come un array piatto di 9 valori, indicizzati da 1 a 9. Questo ci consente di utilizzare l'offset zero come caso speciale "nessuna mossa" quando vogliamo che il computer giochi per primo. Le mosse del giocatore sono memorizzate come 4 e il computer si muove come 5. Per iniziare con tutte le posizioni sono inizializzate su 32 (impostazione predefinita della memoria di Befunge), quindi ogni volta che accediamo alla scheda modifichiamo con 8, quindi torneremo indietro 0, 4 o 5.

Dato tale accordo, se sommiamo i valori di una qualsiasi delle tre posizioni sul tabellone, sappiamo che il computer è a una mossa dalla vincita se il totale è 10, il giocatore è a una mossa dalla vincita se il totale è 8, e il le posizioni sono condivise tra computer e giocatore (ma una posizione è ancora libera) se il totale è 9.

Tutta la nostra strategia si basa su questo concetto. Abbiamo una routine che prende un elenco di triple che indicano gruppi di tre posizioni sul tabellone, calcoliamo la somma di quelle posizioni e se la somma è uguale a un certo totale, il computer si sposta su qualsiasi delle posizioni nell'insieme è libera.

L'elenco principale delle triple che testiamo sono le combinazioni vincenti (1/2/3, 1/5/9, 1/4/7, ecc.). Prima cerchiamo un totale di 10 (il computer sta per vincere), quindi un totale di 8 (il giocatore sta per vincere e dobbiamo bloccare quella mossa). Meno ovviamente, controlliamo anche un totale di 9 (se il giocatore e il computer hanno una delle posizioni, è una buona strategia per il computer prendere la terza).

Prima di quest'ultimo scenario, l'altra mossa strategica che facciamo è quella di controllare tutti i set di angoli (1/2/4, 2/3/6, ecc.) E due combinazioni di angoli opposti (1/8/9 e 3 / 7/8). Se una qualsiasi di queste combinazioni ammonta a 8, ovvero il giocatore ha preso due posizioni, è una buona strategia per il computer prendere la posizione libera rimanente.

Infine, ci sono due mosse di casi speciali. Innanzitutto, cerchiamo sempre di prendere la posizione centrale prima di qualsiasi altra mossa. Ciò si ottiene con la stessa routine di tutte le altre nostre mosse, passando semplicemente in una tripla singola, 5/5/5 e una somma target di 0. Inoltre, se tutti gli altri test non sono riusciti a trovare una mossa, proviamo a prendere uno degli angoli più importanti come ultima risorsa. Ancora una volta questo è semplicemente ottenuto testando le triple 1/1/1 e 3/3/3, con una somma target di 0.

Non penso che questa sia necessariamente una strategia perfetta - potrebbero esserci dei giochi che il computer disegna che potrebbe essere stato potenzialmente vinto - ma è abbastanza buono da non perdere mai una partita. Ho eseguito uno script di prova che ha cercato di eseguire tutte le mosse possibili contro il computer e, per ogni sequenza valida di mosse, il computer ha vinto o disegnato il gioco.


Non conosco bene Befunge, ma forse puoi andare a testare tutti i possibili input ( campione )
l4m2

@ l4m2 FYI, ora ho eseguito uno script di test che ha provato ogni possibile mossa contro il computer e posso confermare che non perde mai.
James Holderness,

2

Python 2: 399 401 349 333 317 370 byte

2x Bug Fix: credito a l4m2

-52 caratteri: credito alla metropolitana sotterranea

-16 caratteri: credito a Jonathan Frech

-26 caratteri: credito all'utente202729

def f(b):
 t=4,9,2,3,5,7,8,1,6;n=lambda k:[t[i]for i,j in enumerate(b)if j==k];p,o,a,I=n(2),n(1),n(0),t.index
 for i in p:
    for j in p:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in o:
    for j in o:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in 9,3,7,1:
    if i in a and 5 in p:return I(i)
 for i in 5,4,2,8,6:
    if i in a:return I(i)
 return I(a[0])

Provalo online!

Il primo giorno di un corso di algebra lineare che ho seguito lo scorso semestre, il mio astuto istruttore studente laureato ha proposto che se rappresenti la tavola tic-tac-toe come matrice:

4 | 9 | 2
--+---+--
3 | 5 | 7
--+---+--
8 | 1 | 6

quindi ottenere tre di fila equivale a scegliere tre numeri nell'intervallo [1,9] che si sommano a 15. Questa risposta sfrutta questa idea. La funzione prende un elenco contenente nove numeri che rappresentano la scheda. 0 indica uno spazio vuoto, 1 è occupato dall'avversario e 2 rappresenta una giocata precedente effettuata dal programma. Le prime 3 righe indicano quali numeri ha scelto il programma (p), l'opposizione ha scelto (o) e sono ancora disponibili (a). Quindi controlla i numeri disponibili e vede se uno di essi, combinato con due numeri che ha già selezionato, aggiunge a quindici. Se lo fa, sceglierà quel quadrato e vincerà. Se non ci sono mosse vincenti immediate, verificherà se l'avversario può vincere usando lo stesso metodo. Se possono, ci vorrà il loro quadrato vincente. Se non è disponibile una mossa vincente o bloccante, si sposterà in un angolo. Questo impedisce a un matto amico:

- - - 
- X -
- - -

- O -             # Bad Move
- X -
- - -

- O X
- X -
- - -

- O X
- X -
O - -

- O X
- X -
O - X

Se non si verifica nessuna di queste situazioni, sceglierà un quadrato arbitrariamente. La funzione genera un numero [0,8] che rappresenta il quadrato indicizzato 0 scelto dall'algoritmo.

Modifica: l'algoritmo ora dà la priorità al centro rispetto alla diagonale, il che impedirà un'altra possibilità di accoppiamento idiota evidenziata da l4m2 e strategie correlate.

Modifica: per chiarire, la funzione accetta una scheda sotto forma di un array e genera uno spostamento come numero intero su [0,8]. Poiché questa strategia di I / O è così goffa, ecco uno script wrapper che la rende più interattiva. Prende un singolo argomento della riga di comando, che dovrebbe essere 1 se il giocatore inizia per primo e 0 se il programma inizia per primo.

import sys

def f(b):
 t=4,9,2,3,5,7,8,1,6;n=lambda k:[t[i]for i,j in enumerate(b)if j==k];p,o,a,I=n(2),n(1),n(0),t.index
 for i in p:
    for j in p:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in o:
    for j in o:
     for k in a:
        if i+j+k==15and-j+i:return I(k)
 for i in 9,3,7,1:
    if i in a and 5 in p:return I(i)
     for i in 5,4,2,8,6:
        if i in a:return I(i)
 return I(a[0])

board = [0,0,0,0,0,0,0,0,0]
rep = {0:"-",1:"X",2:"O"}

turn = int(sys.argv[1])
while True:
    for i in range(3):
        print rep[board[i*3]]+" "+rep[board[i*3+1]]+" "+rep[board[i*3+2]]
        print
    if turn:
        move = int(raw_input("Enter Move [0-8]: "))
    else:
        move = f(board)
    board[move] = turn+1
    turn = (turn+1)%2 


1
Tutte le tue returnlinee tranne l'ultima possono essere messe sulla linea prima di loro, risparmiando spazi bianchi
undergroundmonorail

1
Inoltre non posso fare a meno di chiedersi se avrebbe salvato byte, invece di fare e=enumerate, fare f=lambda n:[t[i]for i,j in enumerate(b)if j==n]e assegnare p, oe ausando la funzione. Non l'ho contato però
undergroundmonorail

3
Ancora hackerato . xkcd.com/832 aiuta davvero
l4m2 il

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.