Implementa MENACE


11

sfondo

MINACCIA ( M achine E ducable N oughts Una ° C rosses E ngine) è un algoritmo di apprendimento automatico superficiale rudimentale per le Noughts gioco e Croci, creato da scienziato informatico britannico Donald Michie nel 1960. È stato originariamente implementato con 304 scatole di fiammiferi, ciascuna etichettata con una posizione sulla scacchiera e contenente perline colorate (con uno dei nove colori, che rappresentano possibili mosse). Michie calcolò che queste 304 scatole di fiammiferi erano sufficienti per ogni combinazione di mosse sul tabellone.

Quanto più matematico tra voi può rendersi conto che in realtà ci sono 19.683 possibili combinazioni di Noughts, Crosses e Blanks su una tavola N&C; tuttavia, ha calcolato i modi per ridurre questo numero (per accelerare l'algoritmo e probabilmente per ridurre le scatole di fiammiferi!). In primo luogo, ha rimosso tutte le mosse impossibili, come:

-------
|X|0|X|
| |0| |
|X|X| |
-------

(due bocconcini e quattro croci)

Successivamente, ha compensato le rotazioni. Ad esempio, se sulla scatola di fiammiferi vediamo:

-------
| |0|0|
|X| |X|
| |0| |
-------

possiamo usare la stessa scatola per

-------
| |X| |
|0| |0|
| |X|0|
-------

Pertanto, le perline colorate di cui sopra rappresentano posizioni relative, non assolute. Ad esempio, se dicessimo che una perlina rossa significava in alto a sinistra, daremmo un'occhiata all'immagine in cima alla scatola e vedremmo:

-------
| |0|0|
|X| |X|
| |0| |
-------

quindi sapremmo che nel caso in cui questa sia la tavola, allora la perlina rossa significherebbe:

-------
|R|0|0|
|X| |X|
| |0| |
-------

Ma se questo è il consiglio:

-------
| |X| |
|0| |0|
| |X|0|
-------

il tallone rosso significherebbe

-------
| |X|R|
|0| |0|
| |X|0|
-------

Queste trasformazioni si applicavano alle rotazioni e all'inversione (in tutte le direzioni, compresa la diagonale). Ancora una volta, devi solo salvare ogni scatola di fiammiferi una volta in questo modo: non creare scatole virtuali separate per ogni trasformazione!

Un'altra semplificazione fatta da Michie è stata quella di assicurarsi che il computer andasse per primo. In questo modo, poteva rimuovere tutte le mosse di primo livello, rimuovendo circa un quinto delle caselle rimanenti. Alla fine, ha rimosso tutte le caselle di fine gioco (poiché non sono stati richiesti ulteriori "contenuti" o mosse in questi passaggi).

Bene, ora sull'algoritmo stesso (è molto semplice):

  1. Prima di tutto, decidi cosa rappresentano i colori delle perle. Avrai bisogno di 9 colori per rappresentare ciascuno degli spazi sul tabellone.
  2. All'inizio del gioco, ciascuna delle 304 scatole di fiammiferi contiene perline. Mentre le perline sono di colore casuale (quindi i duplicati vanno bene), dovrebbero essere possibili mosse (quindi se l'immagine dello stato della scheda raffigura una 'O' in mezzo a destra, quindi non puoi usare la perlina che rappresenta il mezzo- destra).
  3. Ogni volta che è il turno di MENACE (X), trova la scatola di fiammiferi con l'attuale posizione del tabellone (o una sua trasformazione) stampata su di essa.
  4. Apri la scatola di fiammiferi e scegli qualsiasi tallone lì a caso.
  5. Scopri come lo stato della scheda è stato trasformato per arrivare all'immagine sulla scatola di fiammiferi (ad es. Ruotata di 90 gradi in senso antiorario). Quindi, applica quella trasformazione al tallone (es. In alto a sinistra diventa sinistra-sinistra).
  6. Posiziona una X in quel quadrato. Rimuovi il tallone selezionato dalla scatola di fiammiferi. Se la scatola viene lasciata vuota di conseguenza, metti tre perline (possibili) casuali nella scatola e scegline una per la mossa.
  7. Ripeti 3-6 fino alla fine del gioco.
  8. Se MENACE ha vinto il gioco, torna indietro attraverso ogni scatola di fiammiferi che MENACE ha preso. Quindi, rintraccia di quale colore perlina ha usato in quella mossa. Metti due di quel colore di tallone nella scatola (in modo che ci sia il tallone originale + uno in più, aumentando così la probabilità che MENACE faccia quella mossa la prossima volta che arriva in quella posizione)
  9. Se MENACE ha perso il gioco, non fare nulla ( non sostituire le perline che ha tolto).
  10. Se MENACE ha disegnato il gioco, quindi sostituisci il tallone che ha usato in ciascuna delle sue mosse, ma non aggiungerne uno in più, in modo da rimanere con ciò che hai iniziato.

Questo ci lascia con un algoritmo che è molto semplice, ma difficile da implementare. Questo costituisce la base della tua sfida.

Se sei ancora confuso, vedi http://chalkdustmagazine.com/features/menace-machine-educable-noughts-crosses-engine/ - è quello che ho letto quando ho appreso per la prima volta di questo algoritmo

Sfida

Gioca una partita a Tic-Tac-Toe con il computer. Ad ogni passaggio, visualizza il contenuto di tutte le scatole di fiammiferi.

ingressi

  • All'inizio del programma un numero, che indica quante partite vuoi giocare con MENACE
  • Quindi, dopo il primo turno di MENACE, inserisci la tua mossa come una stringa di due caratteri, la prima lettera che è "L", "R" o "M" (sinistra, destra o centro) in riferimento all'asse Y. Quindi inserisci un'altra lettera (di nuovo "L", "R" o "M"), questa volta facendo riferimento all'asse X. Ripeti l'operazione per tutte le mosse e i giochi.

Uscite

  • All'inizio di ogni nuovo gioco, genera "nuovo gioco".
  • Dopo ogni mossa da parte del giocatore, esponi la scacchiera in qualsiasi formato ragionevole. Non ha bisogno di avere un bell'aspetto (es. Una serie di array che rappresentano le posizioni della scheda va bene).
  • Dopo ogni mossa del giocatore, MENACE dovrebbe fare una mossa. Emetti il ​​tabellone dopo la mossa di MENACE
  • Dopo ogni partita, pubblica il contenuto di tutte le 304 scatole di fiammiferi. Le perle possono essere rappresentate da una lettera, nome di un colore, carattere o qualsiasi stringa o numero intero che ti piace (senza puntatori, funzioni anonime, ecc.).

Regole

  1. Questo è , quindi vince la risposta più breve in byte.
  2. Devo essere in grado di inserire le mosse dopo aver visto la risposta di MENACE. No "passa tutte le tue mosse in questa funzione e guarda come si svolge il gioco".
  3. Il tabellone deve essere eliminato tra una partita e l'altra.
  4. Le scatole di fiammiferi non devono essere cancellate tra i giochi (questo ripristinerebbe l'apprendimento automatico)
  5. Devi avere 304 scatole di fiammiferi. Chiunque può implementare questo algoritmo con tutte le 19.683 scatole di fiammiferi, ma l'apprendimento è lento (poiché richiede molti giochi per ottenerle tutte con contenuti utili).
  6. L'output può essere in qualsiasi formato ragionevole e l'input può essere preso secondo gli standard PPCG (purché conforme alla regola 2). Se è necessario regolare il formato di input (come descritto nella sezione " Input "), va bene finché ha senso.
  7. Una partita termina quando un giocatore vince (ottenendo tre di fila in diagonale, in orizzontale o in verticale) o se c'è un pareggio (il tavolo è pieno e non c'è vincitore)
  8. Mentre MENACE deve fare possibili mosse (e avere solo possibili perline all'interno di ogni scatola di fiammiferi), per il bene della sfida non è necessario convalidare l'input dell'utente. Se digitano qualcosa di sbagliato, il tuo programma può fare qualunque cosa (impazzire, lanciare errori, ecc.) - puoi presumere che l'input sia corretto.

Ricordo Martin Gardner mentre dimostrava l'idea usando il gioco più semplice Hexapawn, anche se dimentico quello che ha chiamato il "computer" che ha costruito.
Neil,



1
Grande sfida. Un paio di domande veloci: 1. Una volta che c'è più di un tallone in un dato spazio in una scatola, come dovrebbe essere rappresentato nell'output? 2. Vuoi davvero tutte le 304 scatole (2736 celle) in uscita dopo ogni mossa?
Nick Kennedy,

@NickKennedy Grazie per il feedback. Il modo in cui ci si aspetta le perline per essere rappresentati quando è registrato è come un array (anche se è possibile farlo in modo diverso per non limitare le lingue diverse), ad esempio, se si è scelto i numeri per rappresentare le perline: [[0, 2, 6], [4, 8, 4, 3, 3], [7, 7, 7, 7, 7, 7, 7, 8], [1], ... [3, 3, 5, 4]].
Geza Kerecsenyi,

Risposte:


3

R , 839 byte

options(max.print=1e5)
s=colSums
r=rowSums
m=matrix
a=array
y=apply
S=sum
p=sample
b=m(rep(i<-1:(K=3^9),e=9)%/%(E=3^(8:0))%%3,c(9,K))
V=a(1:9,c(3,3,8))
V[,,2:4]=c(V[x<-3:1,,1],V[,x,1],V[x,x,1])
V[,,5:8]=y(V[,,1:4],3,t)
d=aperm(a(b[c(V),],c(9,8,K)),c(1,3,2))
v=m(V,9)
g=y(m(match(e<-y(d*E,2:3,S),i),,8),1,min)
g[K]=K
G=9-y(t(e==g)*8:1,2,max)
h=s(a(c(b,d[,,5],b[c(1,5,9,3,5,7,1:3),]),c(3,3,K,3))*3^(0:2))
k=r(s(h==13))>0
l=r(s(h==26))>0
o=s(b>0)
M=b
M[M==0]=-1
repeat{A=b[,t<-K]
z=j=c();B=1
repeat{if(S(pmax(-M[,t],0))<1)M[p(9,pmin(3,S(x)),,x<-M[,t]<1),t]=-1
z=c(z,u<-p(9,1,,pmax(-M[,t],0)))
j=c(j,t)
A[v[,G[B]][u]]=1
print(m(A,3))
if(k[B<-S(A*E)]||o[B]==9)break
A[ceiling((utf8ToInt(readline())-76)/5)%*%c(1,3)+1]=2
if(l[B<-S(A*E)])break
t=g[B]}
M[x]=M[x<-cbind(z,j)]-k[B]+l[B]
print(a(M[,g==seq(g)&!k&!l&s(b==1)==s(b==2)&o<8],c(3,3,304)))}

Provalo online!

Una risposta abbastanza lunga, ma questa non è stata una sfida semplice. Il collegamento TIO qui fallirà perché si aspetta un input interattivo. Ecco una versione che gioca contro un secondo giocatore a caso che sceglie un posto a caso. L'output per questa seconda versione è solo il vincitore (1, 2 o 0 per un pareggio.) Le scatole di fiammiferi vengono inizializzate per tutte le posizioni della scacchiera, ma utilizzate solo per i 304 secondo le specifiche. Sono implementate come copie del tabellone con numeri negativi per indicare il numero di perline su ciascuna posizione. Ho sperimentato un elenco di vettori secondo le specifiche originali, ma era meno intuitivo.

Questa è una versione meno giocosa con commenti (ma ancora nomi di variabili brevi). Nota che non stampa le scatole di fiammiferi perché sono molto lunghe. Può implementare un giocatore interattivo 2, un giocatore casuale 2 o la stessa strategia di scatola di fiammiferi per il giocatore 2.

auto = 1 # 1 = Random player 2, 2 = Player 2 uses learning strategy
         # 0 for interactive
print_board <- function(board) {
  cat(apply(matrix(c(".", "X", "O")[board + 1], 3), 1, paste, collapse = ""), "", sep = "\n")
}
E = 3 ^ (8:0) # Number of possible arrangements of board
              # ignoring rotations etc.
# Make all possible boards
b = matrix(rep(1:3 ^ 9, e = 9) %/% E %% 3, c(9, 3 ^ 9))
# Define the eight possible rotation/inversion matrices
V = array(1:9, c(3, 3, 8))
V[, , 2:4] = c(V[x <- 3:1, , 1], V[, x, 1], V[x, x, 1])
V[, , 5:8] = apply(V[, , 1:4], 3, t)
# Create eight copies of the 19683 boards with each transformation
d = aperm(array(b[c(V), ], c(9, 8, 3 ^ 9)), c(1, 3, 2))
v = matrix(V, 9)
# Create reverse transformations (which are the same except for rotation)
w = v[, c(1:5, 7, 6, 8)]
# Find the sums of each transformation using base 3
e = apply(d * E, 2:3, sum)
# Find the lowest possible sum for each board's transformed versions
# This will be the one used for the matchboxes
f = matrix(match(e, 1:3 ^ 9), , 8)
g = apply(f, 1, min)
# Store which transformation was necessary to convert the lowest board
# into this one
G = 9 - apply(t(e == g) * 8:1, 2, max)
# Work out which boards have 3-in-a-row
h = colSums(array(c(b, d[, , 5], b[c(1, 5, 9, 3, 5, 7, 1:3), ]), c(3, 3, 3 ^ 9, 3)) * 3 ^ (0:2))
k = rowSums(colSums(h == 13)) > 0 # player 1 wins
l = rowSums(colSums(h == 26)) > 0 # player 2 wins
# Store how many cells are filled
o = colSums(b > 0)
# Create matchboxes. These contain the actual board configuration, but
# instead of zeroes for blanks have a minus number. This is initially -1,
# but will ultimately represent the number of beads for that spot on the
# board.
M = b
M[M == 0] = -1
repeat {
  # Initialise board and storage of moves and intermediate board positions
  A = b[, t <- 3 ^ 9]
  z = j = c()
  C = 1
  # If we're automating player 2 also, initialise its storage
  if (auto) {
    Z = J = c()
  }
  repeat {
    # If the current board's matchbox is empty, put up to three more beads
    # back in
    if (sum(pmax(-M[, t], 0)) == 0) {
      M[sample(9, pmin(3, sum(x)), , x <- M[, t] == 0), t] = -1
    }
    # Take out a bead from the matchbox
    u = sample(9, 1, , pmax(-M[, t], 0))
    # Mark the bead as taken out
    M[u, t] = M[u, t] + 1
    # Store the bead and board position in the chain for this game
    z = c(z, u)
    j = c(j, t)
    # Mark the spot on the board
    A[v[, C][u]] = 1
    # Print the board
    if (!auto) print_board(matrix(A, 3))
    # Check if  player 1 has won or board is full
    if (k[B <- sum(A * E)] || o[B] == 9) break
    if (auto) {
      # Repeat for player 2 if we're automating its moves
      # Note if auto == 1 then we pick at random
      # If auto == 2 we use the same algorithm as player 1
      D = g[B]
      if (sum(pmax(-M[, D], 0)) == 0) {
        M[sample(9, pmin(3, sum(x)), , x <- M[, D] == 0), D] = -1
      }
      U = sample(9, 1, , if (auto == 1) M[, D] <= 0 else pmax(-M[, D], 0))
      Z = c(Z, U)
      J = c(J, D)
      A[v[, G[B]][U]] = 2
    } else {
      cat(
        "Please enter move (LMR for top/middle/bottom row and\nLMR for left/middle/right column, e.g. MR:"
      )
      repeat {
        # Convert LMR into numbers
        q = ceiling((utf8ToInt(readline()) - 76) / 5)
        if (length(q) != 2)
          stop("Finished")
        if (all(q %in% 0:2) && A[q %*% c(1, 3) + 1] == 0) {
          break
        } else {
          message("Invalid input, please try again")
        }
      }
      A[q %*% c(1, 3) + 1] = 2
    }
    if (l[B <- sum(A * E)])
      break
    # Player 2 has won
    t = g[B]
    C = G[B]
  }
  if (auto) {
    cat(c("D", 1:2)[1 + k[B] + 2 * l[B]])
  } else {
    cat("Outcome:", c("Draw", sprintf("Player %d wins", 1:2))[1 + k[B] + 2 * l[B]], "\n")
  }
  # Add beads back to matchbox
  M[x] = M[x <- cbind(z, j)] - k[B] - 1 + l[B]
  if (auto)
    M[x] = M[x <- cbind(Z, J)] - l[B] - 1 + k[B]
}

Molto intelligente! Certo, le rotazioni lo rendono difficile, ma grazie per aver aggiunto anche il lettore bot!
Geza Kerecsenyi,
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.