Terapkan MENACE

11

Latar Belakang

MENACE ( M esin E ducable N hendaknya A nd C Rosses E ngine) adalah mesin dangkal algoritma pembelajaran dasar untuk Noughts permainan dan Crosses, yang diciptakan oleh ilmuwan komputer Inggris Donald Michie pada 1960-an. Awalnya diterapkan dengan 304 kotak korek api, masing-masing dilabeli dengan posisi papan dan berisi manik-manik berwarna (dengan salah satu dari sembilan warna, mewakili kemungkinan gerakan). Michie menghitung bahwa 304 kotak korek api ini cukup untuk setiap kombinasi gerakan di papan tulis.

Semakin matematis di antara Anda mungkin menyadari bahwa sebenarnya ada 19.683 kemungkinan kombinasi Noughts, Crosses dan Blanks pada papan N&C; namun, dia menghitung cara untuk mengurangi angka ini (untuk mempercepat algoritme, dan kemungkinan mengurangi pada kotak korek api!). Pertama, ia menghapus semua gerakan yang tidak mungkin dilakukan, seperti:

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

(dua noughts dan empat salib)

Selanjutnya, ia mengganti rotasi. Misalnya, jika di kotak korek api kita melihat:

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

kita bisa menggunakan kotak yang sama untuk

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

Oleh karena itu, manik-manik berwarna tersebut mewakili posisi relatif, bukan yang absolut. Misalnya, jika kami mengatakan bahwa manik merah berarti kiri atas, maka kami akan melihat gambar di bagian atas kotak dan melihat:

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

jadi kita akan tahu bahwa jika ini adalah papan, maka manik merah berarti:

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

Tetapi jika ini adalah papan:

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

manik merah artinya

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

Transformasi ini diterapkan untuk rotasi dan pembalik (di semua arah, termasuk diagonal). Sekali lagi, Anda hanya perlu menyimpan setiap kotak korek api dengan cara ini: jangan membuat kotak virtual terpisah untuk setiap transformasi!

Penyederhanaan lain yang dibuat Michie adalah memastikan komputer berjalan lebih dulu. Dengan cara ini, dia bisa menghapus semua gerakan tingkat pertama, menghilangkan sekitar seperlima dari kotak yang tersisa. Akhirnya, ia menghapus semua kotak yang mengakhiri permainan (karena tidak ada 'isi' atau langkah lebih lanjut diperlukan pada langkah-langkah ini).

Benar, sekarang ke algoritme itu sendiri (sangat sederhana):

  1. Pertama, tentukan apa yang mewakili warna manik-manik. Anda membutuhkan 9 warna untuk mewakili setiap ruang di papan tulis.
  2. Di awal permainan, masing-masing dari 304 kotak korek api berisi manik-manik. Sementara manik-manik berwarna acak (jadi duplikatnya baik-baik saja), manik-manik tersebut harus merupakan gerakan yang mungkin (jadi jika gambar status papan menggambarkan 'O' di kanan-tengah, maka Anda tidak dapat menggunakan manik yang mewakili tengah-tengah). Baik).
  3. Setiap kali giliran MENACE (X), cari kotak korek api dengan posisi papan saat ini (atau beberapa transformasi itu) dicetak di atasnya.
  4. Buka kotak korek api, dan pilih manik di sana secara acak.
  5. Temukan bagaimana status papan telah diubah untuk mendapatkan gambar pada kotak korek api (mis. Diputar 90 derajat berlawanan arah jarum jam). Kemudian, terapkan transformasi itu ke manik (mis. Kiri atas menjadi kiri-kiri).
  6. Tempatkan tanda X di kotak itu. Hapus manik yang dipilih dari kotak korek api. Jika kotak dibiarkan kosong sebagai hasilnya, masukkan tiga manik-manik acak (mungkin) ke dalam kotak, dan pilih salah satu dari mereka untuk dipindahkan.
  7. Ulangi 3-6 hingga game berakhir.
  8. Jika MENACE memenangkan permainan, kembali ke setiap kotak korek api yang diambil MENACE. Kemudian, telusuri kembali manik warna apa yang digunakannya pada gerakan itu. Masukkan dua warna manik ke dalam kotak (sehingga ada manik asli + satu lagi, sehingga meningkatkan kemungkinan MENACE membuat gerakan yang lain kali sampai ke posisi itu)
  9. Jika MENACE kehilangan permainan, jangan lakukan apa pun ( jangan ganti manik-manik yang dikeluarkannya).
  10. Jika MENACE membuat permainan, maka ganti manik yang digunakannya di setiap gerakannya, tetapi jangan menambahkan yang lain, sehingga Anda memiliki apa yang Anda mulai.

Ini membuat kita dengan algoritma yang sangat sederhana, tetapi sulit untuk diterapkan. Ini membentuk dasar dari tantangan Anda.

Jika Anda masih bingung, lihat http://chalkdustmagazine.com/features/menace-machine-educable-noughts-crosses-engine/ - itulah yang saya baca ketika saya pertama kali belajar tentang algoritma ini

Tantangan

Mainkan game Tic-Tac-Toe dengan komputer. Di setiap langkah, keluarkan konten semua kotak korek api.

Input

  • Di awal program, ucapkan berapa banyak game yang ingin Anda mainkan melawan MENACE
  • Kemudian, setelah giliran pertama MENACE, Anda memasukkan gerakan Anda sebagai string dua karakter, huruf pertama adalah "L", "R", atau "M" (kiri, kanan atau tengah) mengacu pada sumbu Y. Kemudian Anda memasukkan huruf lain (lagi, "L", "R", atau "M"), kali ini mengacu pada sumbu X. Ulangi untuk semua gerakan dan permainan.

Keluaran

  • Pada awal setiap game baru, output "game baru".
  • Setelah setiap gerakan oleh pemain, output papan dalam format yang masuk akal. Itu tidak perlu terlihat cantik (misalnya array array yang mewakili posisi papan baik-baik saja).
  • Setelah setiap gerakan oleh pemain, MENACE harus bergerak. Keluarkan papan setelah gerakan MENACE
  • Setelah setiap pertandingan, keluarkan konten dari semua 304 kotak korek api. Manik-manik dapat diwakili oleh huruf, nama warna, karakter atau string atau integer apa pun yang Anda suka (tidak ada pointer, fungsi anonim, dll).

Aturan

  1. Ini adalah , jadi jawaban tersingkat dalam byte menang.
  2. Saya harus dapat memasukkan gerakan setelah melihat respons MENACE. Tidak 'serahkan semua gerakan Anda ke fungsi ini, dan saksikan bagaimana permainan dimainkan'.
  3. Papan harus dibersihkan di antara game.
  4. Kotak korek api tidak boleh dihapus di antara game (ini akan mengatur ulang pembelajaran mesin)
  5. Anda harus memiliki 304 kotak korek api. Siapa pun dapat menerapkan algoritme ini dengan semua 19.683 kotak korek api, tetapi pembelajarannya lambat (karena membutuhkan banyak permainan untuk mendapatkan semuanya dengan konten yang bermanfaat).
  6. Outputnya bisa dalam format apa pun yang masuk akal, dan input dapat diambil sesuai standar PPCG (asalkan sesuai dengan aturan 2). Jika Anda perlu menyesuaikan format input (seperti yang dijelaskan di bagian ' Input ') maka tidak apa-apa asalkan masuk akal.
  7. Sebuah permainan berakhir ketika seorang pemain menang (dengan mendapatkan tiga berturut-turut secara diagonal, horizontal atau vertikal) atau jika ada hasil seri (papan penuh dan tidak ada pemenang)
  8. Sementara MENACE perlu membuat gerakan yang mungkin (dan hanya memiliki manik-manik yang mungkin di dalam setiap kotak korek api), demi tantangan, Anda tidak perlu memvalidasi input pengguna. Jika mereka mengetik sesuatu yang salah, program Anda dapat melakukan apa saja (menjadi sangat gila, melempar kesalahan, dll.) - Anda dapat mengasumsikan bahwa inputnya benar.
Geza Kerecsenyi
sumber
Saya ingat Martin Gardner mendemonstrasikan ide tersebut menggunakan permainan Hexapawn yang lebih sederhana, meskipun saya lupa apa yang ia namakan "komputer" yang ia buat.
Neil
1
Tantangan besar. Beberapa pertanyaan cepat: 1. Setelah ada lebih dari satu manik dalam ruang yang diberikan dalam sebuah kotak, bagaimana seharusnya itu diwakili dalam output? 2. Apakah Anda benar-benar ingin semua 304 kotak (2736 sel) keluaran setelah setiap gerakan?
Nick Kennedy
@NickKennedy Terima kasih atas umpan baliknya. Cara saya harapkan manik-manik untuk diwakili ketika itu login adalah sebagai array (meskipun Anda dapat melakukannya secara berbeda untuk tidak membatasi bahasa yang berbeda), misalnya jika Anda memilih nomor untuk mewakili manik-manik: [[0, 2, 6], [4, 8, 4, 3, 3], [7, 7, 7, 7, 7, 7, 7, 8], [1], ... [3, 3, 5, 4]].
Geza Kerecsenyi

Jawaban:

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

Cobalah online!

Jawaban yang cukup panjang, tetapi ini bukan tantangan langsung. TIO link di sini akan gagal karena mengharapkan input interaktif. Berikut adalah versi yang dimainkan melawan pemain acak kedua yang hanya mengambil tempat secara acak. Output untuk versi kedua ini hanyalah pemenang (1, 2 atau 0 untuk seri.) Kotak korek api diinisialisasi untuk semua posisi papan, tetapi hanya digunakan untuk 304 per spec. Mereka diimplementasikan sebagai salinan papan dengan angka negatif untuk menunjukkan jumlah manik-manik di setiap posisi. Saya bereksperimen dengan daftar vektor per spesifikasi asli, tetapi kurang intuitif.

Ini adalah versi yang kurang golf dengan komentar (tapi masih nama variabel pendek). Perhatikan bahwa itu tidak mencetak kotak korek api karena sangat panjang. Itu dapat menerapkan pemain interaktif 2, pemain acak 2 atau strategi kotak korek api yang sama untuk pemain 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]
}
Nick Kennedy
sumber
Sangat pintar! Tentu saja, rotasi menyulitkan, tetapi terima kasih telah menambahkan pemain bot juga!
Geza Kerecsenyi