Mettre en œuvre MENACE


11

Contexte

MENACE ( M achine E ducable N oughts A nd C rosses E ngine) est un algorithme rudimentaire d'apprentissage automatique peu profond pour le jeu Noughts and Crosses, créé par l'informaticien britannique Donald Michie dans les années 1960. Il a été initialement mis en œuvre avec 304 boîtes d'allumettes, chacune étiquetée avec une position de planche et contenant des perles colorées (avec l'une des neuf couleurs, représentant les mouvements possibles). Michie a calculé que ces 304 boîtes d'allumettes étaient suffisantes pour chaque combinaison de mouvements sur le plateau.

Les plus mathématiques d'entre vous peuvent se rendre compte qu'il existe en réalité 19 683 combinaisons possibles de Noughts, Crosses et Blanks sur une carte N&C; cependant, il a calculé des façons de réduire ce nombre (pour accélérer l'algorithme, et probablement pour réduire les boîtes d'allumettes!). Tout d'abord, il a supprimé tous les mouvements non possibles, tels que:

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

(deux noughts et quatre croix)

Ensuite, il a compensé les rotations. Par exemple, si sur la boîte d'allumettes, nous voyons:

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

nous pouvons utiliser la même boîte pour

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

Par conséquent, les perles colorées susmentionnées représentent des positions relatives et non absolues. Par exemple, si nous disons qu'une perle rouge signifie en haut à gauche, nous regardons l'image en haut de la boîte et voyons:

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

donc nous saurions que dans le cas où c'est la planche, alors la perle rouge signifierait:

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

Mais si c'est le conseil:

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

la perle rouge signifierait

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

Ces transformations s'appliquaient aux rotations et inversions (dans toutes les directions, y compris en diagonale). Encore une fois, vous n'avez qu'à enregistrer chaque boîte d'allumettes une fois de cette façon: ne faites pas de boîtes virtuelles séparées pour chaque transformation!

Une autre simplification que Michie a faite a été de s'assurer que l'ordinateur démarre en premier. De cette façon, il pourrait supprimer tous les mouvements de premier niveau, supprimant environ un cinquième des cases restantes. Enfin, il a supprimé toutes les cases de fin de jeu (car aucun autre `` contenu '' ou mouvement n'était requis pour ces étapes).

Bon, maintenant sur l'algorithme lui-même (c'est très simple):

  1. Décidez d'abord de ce que représentent les couleurs des perles. Vous aurez besoin de 9 couleurs pour représenter chacun des espaces sur le plateau.
  2. Au début du jeu, chacune des 304 boîtes d'allumettes contient des perles. Bien que les perles soient de couleur aléatoire (les doublons sont donc bien), ils devraient être des mouvements possibles (donc si l'image de l'état du tableau représente un `` O '' au milieu à droite, vous ne pouvez pas utiliser la perle qui représente le milieu) droite).
  3. Chaque fois que c'est le tour de MENACE (X), trouvez la boîte d'allumettes avec la position actuelle du plateau (ou une transformation de celle-ci) imprimée dessus.
  4. Ouvrez la boîte d'allumettes et choisissez n'importe quelle perle dedans au hasard.
  5. Découvrez comment l'état du tableau a été transformé pour accéder à l'image sur la boîte d'allumettes (par exemple, pivoté de 90 degrés dans le sens inverse des aiguilles d'une montre). Ensuite, appliquez cette transformation à la perle (par exemple en haut à gauche devient à gauche à gauche).
  6. Placez un X dans ce carré. Retirez la perle sélectionnée de la boîte d'allumettes. Si la boîte reste vide, placez trois perles aléatoires (possibles) dans la boîte et choisissez-en une pour le déplacement.
  7. Répétez 3-6 jusqu'à la fin de la partie.
  8. Si MENACE a gagné le match, revenez dans chaque boîte d'allumettes prise par MENACE. Ensuite, retracez la couleur de la perle utilisée lors de ce mouvement. Mettez deux de cette couleur de perle dans la boîte (afin qu'il y ait la perle d'origine + une de plus, augmentant ainsi la probabilité que MENACE fasse ce mouvement la prochaine fois qu'elle arrivera à cette position)
  9. Si MENACE a perdu le jeu, ne faites rien ( ne remplacez pas les billes qu'il a enlevées).
  10. Si MENACE a dessiné le jeu, remplacez la perle qu'il a utilisée dans chacun de ses mouvements, mais n'en ajoutez pas un de plus, de sorte que vous vous retrouvez avec ce que vous avez commencé.

Cela nous laisse un algorithme très simple, mais difficile à mettre en œuvre. Cela constitue la base de votre défi.

Si vous êtes toujours confus, consultez http://chalkdustmagazine.com/features/menace-machine-educable-noughts-crosses-engine/ - c'est ce que j'ai lu lorsque j'ai découvert cet algorithme pour la première fois

Défi

Faites une partie de Tic-Tac-Toe avec l'ordinateur. À chaque étape, affichez le contenu de toutes les boîtes d'allumettes.

Contributions

  • Au début du programme, un nombre indiquant le nombre de matchs que vous souhaitez jouer contre MENACE
  • Ensuite, après le premier tour de MENACE, vous entrez votre mouvement sous la forme d'une chaîne de deux caractères, la première lettre étant "L", "R" ou "M" (gauche, droite ou milieu) se référant à l'axe Y. Ensuite, vous entrez une autre lettre (encore une fois, "L", "R" ou "M"), cette fois en référence à l'axe X. Répétez pour tous les mouvements et jeux.

Les sorties

  • Au début de chaque nouveau jeu, sortez "nouveau jeu".
  • Après chaque mouvement du joueur, sortez le tableau dans n'importe quel format raisonnable. Il n'a pas besoin d'être joli (par exemple, un tableau de tableaux représentant les positions de la carte est très bien).
  • Après chaque mouvement du joueur, MENACE devrait faire un mouvement. Sortie de la carte après le mouvement de MENACE
  • Après chaque partie, affichez le contenu des 304 boîtes d'allumettes. Les perles peuvent être représentées par une lettre, le nom d'une couleur, un caractère ou n'importe quelle chaîne ou entier que vous aimez (pas de pointeurs, fonctions anonymes, etc.).

Règles

  1. C'est le , donc la réponse la plus courte en octets l'emporte.
  2. Je dois pouvoir saisir des mouvements après avoir vu la réponse de MENACE. Non, passez tous vos mouvements dans cette fonction et regardez comment le jeu se déroule.
  3. Le plateau doit être vidé entre les matchs.
  4. Les boîtes d'allumettes ne doivent pas être effacées entre les jeux (cela réinitialiserait l'apprentissage automatique)
  5. Vous devez avoir 304 boîtes d'allumettes. Tout le monde peut implémenter cet algorithme avec les 19 683 boîtes d'allumettes, mais l'apprentissage est lent (car il nécessite beaucoup de jeux pour les obtenir tous avec un contenu utile).
  6. La sortie peut être dans n'importe quel format raisonnable et l'entrée peut être prise selon les normes PPCG (tant qu'elle est conforme à la règle 2). Si vous avez besoin d'ajuster le format d'entrée (comme décrit dans la section « Entrée »), c'est OK tant que cela a du sens.
  7. Une partie se termine lorsqu'un joueur gagne (en obtenant trois d'affilée en diagonale, horizontalement ou verticalement) ou en cas d'égalité (le plateau est plein et il n'y a pas de gagnant)
  8. Bien que MENACE doive faire des mouvements possibles (et avoir seulement des perles possibles à l'intérieur de chaque boîte d'allumettes), pour le défi, vous n'avez pas besoin de valider l'entrée de l'utilisateur. S'ils tapent quelque chose de mal, votre programme peut faire n'importe quoi (devenir complètement fou, jeter une erreur, etc.) - vous pouvez supposer que l'entrée est correcte.

Je me souviens que Martin Gardner avait démontré l'idée en utilisant le jeu plus simple Hexapawn, bien que j'oublie ce qu'il a appelé "l'ordinateur" qu'il a construit.
Neil



1
Grand défi. Quelques questions rapides: 1. Une fois qu'il y a plus d'une perle dans un espace donné dans une boîte, comment cela devrait-il être représenté dans la sortie? 2. Voulez-vous vraiment que les 304 boîtes (2736 cellules) soient sorties après chaque mouvement?
Nick Kennedy

@NickKennedy Merci pour la rétroaction. La façon dont je vous attendriez les perles à représenter quand il est connecté est comme un tableau (bien que vous pouvez le faire différemment pour ne pas restreindre différentes langues), par exemple si vous avez choisi les numéros pour représenter les perles: [[0, 2, 6], [4, 8, 4, 3, 3], [7, 7, 7, 7, 7, 7, 7, 8], [1], ... [3, 3, 5, 4]].
Geza Kerecsenyi

Réponses:


3

R , 839 octets

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

Essayez-le en ligne!

Une réponse assez longue, mais ce n'était pas un défi simple. Le lien TIO ici échouera car il attend une entrée interactive. Voici une version qui joue contre un deuxième joueur aléatoire qui choisit simplement une place au hasard. La sortie de cette deuxième version n'est que le gagnant (1, 2 ou 0 pour un match nul.) Les boîtes d'allumettes sont initialisées pour toutes les positions du plateau, mais uniquement utilisées pour les 304 selon la spécification. Ils sont implémentés sous forme de copies du tableau avec des nombres négatifs pour indiquer le nombre de perles sur chaque position. J'ai expérimenté une liste de vecteurs selon les spécifications d'origine, mais c'était moins intuitif.

Il s'agit d'une version moins golfée avec des commentaires (mais toujours des noms de variables courts). Notez qu'il n'imprime pas les boîtes d'allumettes car elles sont très longues. Il peut implémenter un joueur interactif 2, un joueur aléatoire 2 ou la même stratégie de matchbox pour le joueur 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]
}

Très intelligent! Bien sûr, les rotations rendent la tâche difficile, mais merci également d'avoir ajouté le joueur du bot!
Geza Kerecsenyi
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.