Implementar MENACE


11

fundo

AMEAÇA ( M achine E ducable N deveres A nd C rosses E ngine) é um algoritmo de aprendizado de máquina rasa rudimentar para os Noughts jogo e cruzes, criados pela British computador cientista Donald Michie na década de 1960. Foi originalmente implementado com 304 caixas de fósforos, cada uma rotulada com uma posição no tabuleiro e contendo miçangas coloridas (com uma das nove cores, representando possíveis movimentos). Michie calculou que essas 304 caixas de fósforos eram suficientes para todas as combinações de jogadas no tabuleiro.

Os mais matemáticos entre vocês podem perceber que, na verdade, existem 19.683 combinações possíveis de nada, cruzes e espaços em branco em um quadro da N&C; no entanto, ele calculou maneiras de reduzir esse número (para acelerar o algoritmo e provavelmente reduzir as caixas de fósforos!). Primeiro, ele removeu todos os movimentos não possíveis, como:

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

(dois zeros e quatro cruzamentos)

Em seguida, ele compensou as rotações. Por exemplo, se na caixa de fósforos, vemos:

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

podemos usar a mesma caixa para

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

Portanto, as contas coloridas acima mencionadas representam posições relativas, não absolutas. Por exemplo, se disséssemos que um cordão vermelho significava o canto superior esquerdo, veríamos a imagem na parte superior da caixa e veríamos:

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

então saberíamos que, no caso de ser esse o quadro, a conta vermelha significaria:

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

Mas se este for o quadro:

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

a conta vermelha significaria

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

Essas transformações se aplicavam a rotações e inversões (em todas as direções, inclusive na diagonal). Mais uma vez, você só precisa salvar cada caixa de fósforos desta maneira: não faça caixas virtuais separadas para cada transformação!

Outra simplificação que Michie fez foi garantir que o computador funcione primeiro. Dessa forma, ele poderia remover todos os movimentos de primeiro nível, removendo cerca de um quinto das caixas restantes. Por fim, ele removeu todas as caixas de final de jogo (pois não foram necessários mais 'conteúdos' ou movimentos nessas etapas).

Certo, agora no próprio algoritmo (é muito simples):

  1. Primeiro, decida o que as cores das contas representam. Você precisará de 9 cores para representar cada um dos espaços no quadro.
  2. No início do jogo, cada uma das 304 caixas de fósforos contém miçangas. Embora as contas tenham cores aleatórias (portanto, duplicatas são ótimas), elas devem ser possíveis movimentos (por isso, se a imagem do estado da placa exibir um 'O' no meio direito, não será possível usar a conta que representa o centro. certo).
  3. Toda vez que for a vez do MENACE (X), encontre a caixa de fósforos com a posição atual do tabuleiro (ou alguma transformação dela) impressa nela.
  4. Abra a caixa de fósforos e escolha qualquer conta ali aleatoriamente.
  5. Descubra como o status do quadro foi transformado para chegar à imagem na caixa de fósforos (por exemplo, girado 90 graus no sentido anti-horário). Em seguida, aplique essa transformação ao cordão (por exemplo, a parte superior esquerda se torna a esquerda).
  6. Coloque um X nesse quadrado. Remova o cordão selecionado da caixa de fósforos. Se a caixa for deixada vazia como resultado, coloque três contas aleatórias (possíveis) na caixa e escolha uma delas para a mudança.
  7. Repita 3-6 até o jogo terminar.
  8. Se o MENACE ganhou o jogo, volte a todas as caixas de fósforos do MENACE. Em seguida, rastreie de que cor o cordão usou nesse movimento. Coloque duas dessa cor de conta na caixa (para que haja a conta original + mais uma, aumentando assim a probabilidade de MENACE fazer esse movimento na próxima vez que chegar a essa posição)
  9. Se MENACE perder o jogo, não faça nada ( não substitua as contas que ele tirou).
  10. Se MENACE desenhou o jogo, substitua o cordão usado em cada um dos seus movimentos, mas não adicione um extra, para que você fique com o que começou.

Isso nos deixa com um algoritmo muito simples, mas difícil de implementar. Isso forma a base do seu desafio.

Se você ainda está confuso, consulte http://chalkdustmagazine.com/features/menace-machine-educable-noughts-crosses-engine/ - é o que li quando soube pela primeira vez sobre esse algoritmo

Desafio

Jogue um jogo de Tic-Tac-Toe com o computador. Em cada etapa, imprima o conteúdo de todas as caixas de fósforos.

Entradas

  • No início do programa, um número dizendo quantos jogos você deseja jogar contra o MENACE
  • Depois, após o primeiro turno do MENACE, você insere seu movimento como uma sequência de dois caracteres, sendo a primeira letra "L", "R" ou "M" (esquerda, direita ou meio) referente ao eixo Y. Em seguida, insira outra letra (novamente, "L", "R" ou "M"), desta vez referindo-se ao eixo X. Repita para todos os movimentos e jogos.

Saídas

  • No início de cada novo jogo, produza "novo jogo".
  • Após cada jogada do jogador, produza o tabuleiro em qualquer formato razoável. Não precisa parecer bonito (por exemplo, uma matriz de matrizes representando as posições da placa é boa).
  • Após cada jogada do jogador, MENACE deve fazer uma jogada. Saída do quadro após a mudança da MENACE
  • Após cada jogo, produza o conteúdo de todas as 304 caixas de fósforos. As contas podem ser representadas por uma letra, nome de uma cor, caractere ou qualquer sequência ou número inteiro que você desejar (sem ponteiros, funções anônimas, etc.).

Regras

  1. Isso é , então a resposta mais curta em bytes vence.
  2. Devo ser capaz de inserir movimentos depois de ver a resposta da MENACE. Não 'passe todos os seus movimentos para essa função e observe como o jogo se desenrola'.
  3. O tabuleiro deve ser limpo entre os jogos.
  4. As caixas de fósforos não devem ser limpas entre os jogos (isso redefiniria o aprendizado de máquina)
  5. Você deve ter 304 caixas de fósforos. Qualquer um pode implementar esse algoritmo com todas as 19.683 caixas de fósforos, mas o aprendizado é lento (pois exige muitos jogos para obter todos eles com conteúdo útil).
  6. A saída pode estar em qualquer formato razoável e a entrada pode ser obtida conforme os padrões PPCG (desde que esteja em conformidade com a regra 2). Se você precisar ajustar o formato de entrada (conforme descrito na seção ' Entrada '), tudo bem, desde que faça sentido.
  7. Um jogo termina quando um jogador vence (fazendo três em sequência na diagonal, horizontal ou vertical) ou se houver um empate (o tabuleiro está cheio e não há vencedor)
  8. Embora o MENACE precise fazer possíveis movimentos (e só tenha contas possíveis dentro de cada caixa de fósforos), para o desafio, você não precisa validar a entrada do usuário. Se eles digitarem algo errado, seu programa poderá fazer o que for (enlouquecer, lançar erro etc.) - você pode assumir que a entrada está correta.

Lembro-me de Martin Gardner demonstrando a idéia usando o jogo mais simples Hexapawn, embora eu esqueça o que ele chamou de "computador" que ele construiu.
Neil



Grande desafio. Algumas perguntas rápidas: 1. Depois que houver mais de uma conta em um determinado espaço em uma caixa, como isso deve ser representado na saída? 2. Deseja realmente que todas as 304 caixas (2736 células) saiam após cada movimento?
Nick Kennedy

@NickKennedy Obrigado pelo feedback. A maneira que eu esperaria as contas para ser representado quando está conectado é como uma matriz (embora você pode fazê-lo de maneira diferente para não restringir diferentes línguas), por exemplo, se você escolheu os números para representar as contas: [[0, 2, 6], [4, 8, 4, 3, 3], [7, 7, 7, 7, 7, 7, 7, 8], [1], ... [3, 3, 5, 4]].
Geza Kerecsenyi 01/03/19

Respostas:


3

R , 839 bytes

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

Experimente online!

Uma resposta bastante longa, mas este não foi um desafio direto. O link do TIO aqui falhará porque espera uma entrada interativa. Aqui está uma versão que joga contra um segundo jogador aleatório que escolhe um lugar aleatoriamente. A saída para esta segunda versão é apenas a vencedora (1, 2 ou 0 para um empate.) As caixas de fósforos são inicializadas para todas as posições do tabuleiro, mas usadas apenas para o 304 conforme a especificação. Eles são implementados como cópias do quadro com números negativos para indicar o número de contas em cada posição. Eu experimentei uma lista de vetores de acordo com a especificação original, mas era menos intuitiva.

Esta é uma versão menos golfe com comentários (mas nomes curtos de variáveis). Observe que ele não imprime as caixas de fósforos porque são muito longas. Ele pode implementar um jogador interativo 2, um jogador aleatório 2 ou a mesma estratégia de caixa de fósforos do jogador 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]
}

Muito esperto! Claro, as rotações dificultam, mas obrigado por adicionar o bot player também!
Geza Kerecsenyi 7/03/19
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.