Triển khai MENACE


11

Lý lịch

Menace ( M achine E ducable N oughts Một nd C Rosses E ngine) là một thuật toán học máy nông thô sơ cho noughts trò chơi và Crosses, được tạo ra bởi nhà khoa học máy tính người Anh Donald Michie trong những năm 1960. Ban đầu nó được thực hiện với 304 hộp diêm, mỗi hộp được dán nhãn vị trí bảng và chứa các hạt màu (với một trong chín màu, đại diện cho các bước di chuyển có thể). Michie tính toán rằng 304 hộp diêm này là đủ cho mọi sự kết hợp di chuyển trên bảng.

Toán học nhiều hơn trong số bạn có thể nhận ra rằng thực sự có 19.683 kết hợp Noughts, Crosses và Blanks có thể có trên bảng N & C; tuy nhiên, ông đã tính toán các cách để cắt giảm con số này (để tăng tốc thuật toán và có khả năng cắt giảm các hộp diêm!). Đầu tiên, anh ta loại bỏ tất cả các động thái không thể, chẳng hạn như:

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

(hai noughts và bốn cross)

Tiếp theo, anh bù đắp cho vòng quay. Chẳng hạn, nếu trên hộp diêm chúng ta thấy:

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

chúng ta có thể sử dụng cùng một hộp cho

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

Do đó, các hạt màu nói trên đại diện cho các vị trí tương đối, không phải là tuyệt đối. Chẳng hạn, nếu chúng ta nói rằng một hạt màu đỏ có nghĩa là trên cùng bên trái, thì chúng ta sẽ xem hình ảnh trên đỉnh hộp và xem:

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

Vì vậy, chúng tôi biết rằng trong trường hợp đây là bảng, thì hạt màu đỏ có nghĩa là:

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

Nhưng nếu đây là bảng:

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

hạt màu đỏ có nghĩa là

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

Những biến đổi này được áp dụng cho các phép quay và đảo ngược (theo mọi hướng, bao gồm cả đường chéo). Một lần nữa, bạn chỉ cần lưu từng hộp diêm một lần theo cách này: không tạo các hộp ảo riêng cho mỗi lần chuyển đổi!

Một đơn giản hóa khác mà Michie đã thực hiện là đảm bảo máy tính đi trước. Bằng cách này, anh ta có thể loại bỏ tất cả các bước di chuyển cấp một, loại bỏ khoảng một phần năm các hộp còn lại. Cuối cùng, anh ta xóa tất cả các hộp kết thúc trò chơi (vì không cần thêm 'nội dung' hoặc di chuyển trong các bước này).

Phải, bây giờ vào chính thuật toán (nó rất đơn giản):

  1. Đầu tiên, quyết định màu sắc của các hạt thể hiện. Bạn sẽ cần 9 màu để thể hiện từng khoảng trống trên bảng.
  2. Khi bắt đầu trò chơi, mỗi hộp diêm 304 đều chứa các hạt. Mặc dù các hạt có màu ngẫu nhiên (vì vậy các bản sao đều ổn), chúng nên có thể di chuyển (vì vậy nếu hình ảnh trạng thái bảng mô tả chữ 'O' ở giữa bên phải, thì bạn không thể sử dụng hạt đại diện cho phần giữa đúng).
  3. Mỗi khi đến lượt của MENACE (X), hãy tìm hộp diêm với vị trí bảng hiện tại (hoặc một số biến đổi của nó) được in trên đó.
  4. Mở hộp diêm và chọn bất kỳ hạt nào trong đó một cách ngẫu nhiên.
  5. Tìm cách chuyển trạng thái bảng để chuyển sang hình ảnh trên hộp diêm (ví dụ: xoay 90deg ngược chiều kim đồng hồ). Sau đó, áp dụng phép biến đổi đó cho chuỗi hạt (ví dụ: trên cùng bên trái trở thành bên trái).
  6. Đặt dấu X vào hình vuông đó. Loại bỏ các hạt đã chọn từ hộp diêm. Nếu hộp bị bỏ trống do đó, hãy đặt ba hạt ngẫu nhiên (có thể) vào hộp và chọn một trong số chúng để di chuyển.
  7. Lặp lại 3-6 cho đến khi trò chơi kết thúc.
  8. Nếu MENACE thắng trò chơi, hãy quay lại qua mọi hộp diêm mà MENACE đã lấy. Sau đó, theo dõi lại những hạt màu nó đã sử dụng trên di chuyển đó. Đặt hai màu của hạt đó vào hộp (để có hạt ban đầu + thêm một hạt nữa, do đó làm tăng khả năng của MENACE khiến cho lần di chuyển đó đến lần tiếp theo vào vị trí đó)
  9. Nếu MENACE thua trò chơi, không làm gì cả ( đừng thay thế các hạt đã lấy ra).
  10. Nếu MENACE vẽ trò chơi, sau đó thay thế chuỗi hạt được sử dụng trong mỗi lần di chuyển của nó, nhưng không thêm một lần nữa, để bạn còn lại với những gì bạn đã bắt đầu.

Điều này để lại cho chúng tôi một thuật toán rất đơn giản, nhưng khó thực hiện. Điều này tạo thành cơ sở cho thách thức của bạn.

Nếu bạn vẫn còn bối rối, hãy xem http://chalkdustmagazine.com/features/menace-machine-educable-noughts-crosses-engine/ - đó là những gì tôi đọc khi lần đầu tiên biết về thuật toán này

Thử thách

Chơi một trò chơi Tic-Tac-Toe với máy tính. Ở mỗi bước, xuất nội dung của tất cả các hộp diêm.

Đầu vào

  • Khi bắt đầu chương trình, một con số cho biết bạn muốn chơi bao nhiêu game với MENACE
  • Sau đó, sau lượt đầu tiên của MENACE, bạn nhập di chuyển của mình dưới dạng một chuỗi hai ký tự, chữ cái đầu tiên là "L", "R" hoặc "M" (trái, phải hoặc giữa) đề cập đến trục Y. Sau đó, bạn nhập một chữ cái khác (một lần nữa, "L", "R" hoặc "M"), lần này đề cập đến trục X. Lặp lại cho tất cả các di chuyển và trò chơi.

Đầu ra

  • Khi bắt đầu mỗi trò chơi mới, hãy xuất "trò chơi mới".
  • Sau mỗi lần di chuyển của người chơi, hãy xuất bảng theo bất kỳ định dạng hợp lý nào. Nó không cần phải trông đẹp (ví dụ, một mảng các mảng đại diện cho các vị trí của bảng là tốt).
  • Sau mỗi lần di chuyển của người chơi, MENACE nên thực hiện một động tác. Xuất bảng sau khi di chuyển của MENACE
  • Sau mỗi trò chơi, xuất nội dung của tất cả 304 hộp diêm. Các hạt có thể được biểu thị bằng một chữ cái, tên của một màu, ký tự hoặc bất kỳ chuỗi hoặc số nguyên nào bạn thích (không có con trỏ, hàm ẩn danh, v.v.).

Quy tắc

  1. Đây là , vì vậy câu trả lời ngắn nhất bằng byte thắng.
  2. Tôi phải có thể nhập di chuyển sau khi thấy phản hồi của MENACE. Không 'chuyển tất cả các bước di chuyển của bạn vào chức năng này và xem cách trò chơi diễn ra'.
  3. Bảng phải được xóa giữa các trò chơi.
  4. Các hộp diêm không được xóa giữa các trò chơi (điều này sẽ thiết lập lại máy học)
  5. Bạn phải có 304 hộp diêm. Bất cứ ai cũng có thể thực hiện thuật toán này với tất cả 19.683 hộp diêm, nhưng việc học rất chậm (vì nó đòi hỏi rất nhiều trò chơi để có được tất cả chúng với nội dung hữu ích).
  6. Đầu ra có thể ở bất kỳ định dạng hợp lý nào và đầu vào có thể được lấy theo tiêu chuẩn PPCG (miễn là tuân thủ quy tắc 2). Nếu bạn cần điều chỉnh định dạng đầu vào (như được mô tả trong phần ' Đầu vào ') thì miễn là nó hợp lý.
  7. Trò chơi kết thúc khi người chơi thắng (bằng cách xếp ba hàng liên tiếp theo đường chéo, chiều ngang hoặc chiều dọc) hoặc nếu có một trận hòa (bảng đầy đủ và không có người chiến thắng)
  8. Mặc dù MENACE cần thực hiện các động tác có thể (và chỉ có các hạt khả dĩ bên trong mỗi hộp diêm), vì thách thức mà bạn không cần phải xác thực đầu vào của người dùng. Nếu họ gõ sai, chương trình của bạn có thể làm bất cứ điều gì (phát điên hoàn toàn, ném lỗi, v.v.) - bạn có thể cho rằng đầu vào là chính xác.

Tôi nhớ Martin Gardner thể hiện ý tưởng bằng cách sử dụng trò chơi đơn giản Hexapawn, mặc dù tôi quên mất cái mà anh ta đặt tên là "máy tính" mà anh ta xây dựng.
Neil



Thử thách lớn. Một vài câu hỏi nhanh: 1. Một khi có nhiều hơn một hạt trong một không gian nhất định trong một hộp, làm thế nào để được thể hiện trong đầu ra? 2. Bạn có thực sự muốn tất cả đầu ra 304 hộp (2736 ô) sau mỗi lần di chuyển không?
Nick Kennedy

@NickKennedy Cảm ơn bạn đã phản hồi. Cách tôi mong muốn các hạt được biểu diễn khi nó được ghi là một mảng (mặc dù bạn có thể làm khác đi để không hạn chế các ngôn ngữ khác nhau), ví dụ: nếu bạn chọn số để biểu thị các hạt : [[0, 2, 6], [4, 8, 4, 3, 3], [7, 7, 7, 7, 7, 7, 7, 8], [1], ... [3, 3, 5, 4]].
Geza Kerecsenyi

Câu trả lời:


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

Hãy thử trực tuyến!

Một câu trả lời dài, nhưng đây không phải là một thử thách đơn giản. Liên kết TIO ở đây sẽ thất bại vì nó mong đợi đầu vào tương tác. Đây là phiên bản đấu với người chơi ngẫu nhiên thứ hai, người chỉ chọn một điểm ngẫu nhiên. Đầu ra cho phiên bản thứ hai này chỉ là người chiến thắng (1, 2 hoặc 0 cho một trận hòa.) Các hộp diêm được khởi tạo cho tất cả các vị trí bảng, nhưng chỉ được sử dụng cho 304 cho mỗi thông số. Chúng được thực hiện dưới dạng bản sao của bảng với số âm để chỉ số lượng hạt trên mỗi vị trí. Tôi đã thử nghiệm với một danh sách các vectơ cho mỗi thông số ban đầu, nhưng nó ít trực quan hơn.

Đây là một phiên bản ít chơi gôn hơn với các bình luận (nhưng vẫn có tên biến ngắn). Lưu ý rằng nó không in các hộp diêm ra vì chúng rất dài. Nó có thể thực hiện một trình phát tương tác 2, một người chơi ngẫu nhiên 2 hoặc cùng một chiến lược hộp diêm cho người chơi 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]
}

Rất thông minh! Tất nhiên, việc quay vòng gây khó khăn, nhưng cảm ơn vì đã thêm trình phát bot!
Geza Kerecsenyi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.