实施MENACE


11

背景

MENACE(中号 achine Ë ducable Ñ oughts ND Ç rosses ë ngine)是用于游戏圈与叉,由英国计算机科学家唐纳德米基在1960创建一个基本的浅的机器学习算法。它最初由304个火柴盒实现,每个火柴盒都标有板子位置并包含彩色珠子(九种颜色之一,代表可能的移动)。Michie计算得出,这304个火柴盒足以满足棋盘上每个动作的组合。

您可能会更数学化,您会发现N&C板上实际上有19,683种Nought,Cross和Blanks的可能组合;但是,他计算了减少此数字的方法(以加快算法的速度,并有可能减少火柴盒!)。首先,他删除了所有不可能的举动,例如:

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

(两个零和四个十字架)

接下来,他补偿了旋转。例如,如果在火柴盒上,我们看到:

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

我们可以使用同一框

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

因此,上述有色珠代表相对位置,而不是绝对位置。例如,如果我们说红色的珠子表示左上角,那么我们将看一下盒子顶部的图像,然后看看:

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

因此,我们知道在这是木板的情况下,红色的珠子表示:

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

但是如果这是董事会:

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

红色的珠子意味着

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

这些变换适用于旋转和反转(在所有方向,包括对角线)。再一次,您只需要按以下方式保存每个火柴盒:不要为每个转换单独创建虚拟盒!

Michie进行的另一个简化是确保计算机先运行。这样,他可以删除所有第一级的动作,从而删除剩余的大约五分之一的盒子。最后,他删除了所有游戏结束框(因为这些步骤不需要进一步的“内容”或移动)。

正确,现在介绍算法本身(非常简单):

  1. 首先,确定珠子代表什么颜色。您需要9种颜色来表示板上的每个空间。
  2. 在游戏开始时,每个304个火柴盒都包含珠子。虽然这些珠子是随机的颜色(因此可以很好地重复),但它们应该可以移动(因此,如果板状态图像在右中角显示一个“ O”,那么您就不能使用代表中间的珠子了,对)。
  3. 每次轮到MENACE(X)时,都要找到刻有当前木板位置(或其某些变形)的火柴盒。
  4. 打开火柴盒,然后在其中随机选择任何珠子。
  5. 查找棋盘的状态如何改变以获取火柴盒上的图像(例如,逆时针旋转90度)。然后,将该变换应用于磁珠(例如,左上角变为左上角)。
  6. 在该正方形中放置一个X。从火柴盒中删除所选的珠子。如果结果是将盒子留空,则将三个随机的(可能的)珠子放入盒子,并选择其中一个进行移动。
  7. 重复3-6,直到游戏结束。
  8. 如果MENACE赢得了比赛,请回顾MENACE拍摄的每个火柴盒。然后,追溯该移动中使用了什么颜色的珠子。将两种颜色的珠子放入盒子中(这样就可以得到原始的珠子,再加上一个),从而增加MENACE的可能性,使它在下次到达该位置时移动。
  9. 如果MENACE输掉了比赛,则什么也不做(不要更换取出的珠子)。
  10. 如果MENACE绘制了游戏,则替换它在每个移动中使用的珠子,但不要添加多余的珠子,这样您就可以开始游戏了。

这给我们留下了非常简单但难以实现的算法。这构成了您挑战的基础。

如果您仍然感到困惑,请访问http://chalkdustmagazine.com/features/menace-machine-educable-noughts-crosses-engine/-这是我第一次学习该算法时所读的内容

挑战

用电脑玩井字游戏。在每一步,输出所有火柴盒的内容。

输入项

  • 在程序开始时有一个数字,表示您想与MENACE进行多少场比赛
  • 然后,在MENACE的第一个回合之后,您将输入的动作作为两个字符串输入,第一个字母是相对于Y轴的“ L”,“ R”或“ M”(左,右或中间)。然后输入另一个字母(再次是“ L”,“ R”或“ M”),这次是指X轴。对所有动作和游戏重复上述步骤。

产出

  • 在每个新游戏开始时,输出“新游戏”。
  • 玩家每次移动后,以任何合理的格式输出棋盘。它不需要看起来很漂亮(例如,代表电路板位置的数组阵列就可以了)。
  • 玩家每次移动后,MENACE应该移动。MENACE移动后输出板
  • 每个游戏结束后,输出所有304个火柴盒的内容。珠子可以由字母,颜色名称,字符或您喜欢的任何字符串或整数(没有指针,匿名函数等)表示。

规则

  1. 这是,因此最短答案以字节为单位。
  2. 看到MENACE的回应后,我必须能够输入动作。没有“将所有动作都传递给该功能,并观察游戏如何进行”。
  3. 游戏之间必须清除董事会。
  4. 比赛之间不得清除火柴盒(这将重置机器学习)
  5. 您必须具有304个火柴盒。任何人都可以在所有19,683个火柴盒中实现此算法,但学习速度很慢(因为要获得具有有用内容的所有游戏,都需要很多游戏)。
  6. 输出可以是任何合理的格式,并且输入可以按照PPCG标准进行(只要符合规则2)。如果您需要调整输入格式(如“ 输入 ”部分中所述),则可以,只要有意义即可。
  7. 当玩家获胜(通过对角线,水平或垂直获得三连胜)或出现平局(棋盘已满且没有获胜者)时,游戏结束
  8. 尽管MENACE需要做出可能的移动(并且每个火柴盒中只能有可能的珠子),但是为了挑战,您不需要验证用户的输入。如果他们输入了错误的内容,则您的程序可以执行任何操作(完全疯狂,抛出错误等)-您可以假定输入正确。

我记得马丁·加德纳(Martin Gardner)使用简单的游戏《六方》(Hexapawn)演示了这个想法,尽管我忘记了他命名的“计算机”。
尼尔,



1
巨大的挑战。几个简单的问题:1.在一个框内的给定空间中,一旦有多个磁珠,应如何在输出中表示?2.您真的希望每次移动后都输出全部304箱(2736个电池)吗?
尼克·肯尼迪

@NickKennedy感谢您的反馈。我希望记录小珠时将其表示为数组(尽管您可以做不同的操作以不限制不同的语言),例如,如果您选择数字来表示小珠:[[0, 2, 6], [4, 8, 4, 3, 3], [7, 7, 7, 7, 7, 7, 7, 8], [1], ... [3, 3, 5, 4]]
Geza Kerecsenyi

Answers:


3

R,839字节

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

在线尝试!

答案很长,但这并不是一个直接的挑战。此处的TIO链接将失败,因为它需要交互输入。这是一个与第二个随机玩家对战的版本,后者只是随机选择一个位置。第二个版本的输出仅是获胜者(平局时为1、2或0。)火柴盒针对所有电路板位置进行初始化,但根据规格仅用于304。它们被实现为带有负数的电路板副本,以指示每个位置的磁珠数​​量。我尝试了按照原始规格创建的矢量列表,但是它不太直观。

这是带有注释(但仍是简短的变量名)的较不流行的版本。请注意,它不会打印出火柴盒,因为它们很长。它可以实现交互式播放器2,随机播放器2或与播放器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]
}

非常聪明!当然,旋转会很困难,但是还要感谢您添加了机器人播放器!
Geza Kerecsenyi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.