MENACE 구현


11

배경

위협 ( M achine E ducable N은 oughts ND C 로시스 E ngine) 1960 년 영국 컴퓨터 과학자 도날드 미치에 의해 생성 된 게임 삼목위한 기초 얕은 기계 학습 알고리즘이다. 원래는 보드 위치로 레이블이 지정되고 색깔의 구슬이 포함 된 304 개의 성냥갑으로 구현되었습니다 (9 가지 색상 중 하나를 사용하여 가능한 움직임을 나타냄). Michie는이 304 개의 성냥갑이 보드의 모든 움직임 조합에 충분하다고 계산했습니다.

더 수학적으로 N & C 보드에 19,683 개의 Noughts, Crosses and Blanks 조합이 있다는 것을 알 수 있습니다. 그러나 그는이 숫자를 줄이는 방법을 계산했습니다 (알고리즘의 속도를 높이고 성냥갑을 줄이는 것입니다!). 첫째, 그는 다음과 같이 불가능한 모든 동작을 제거했습니다.

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

(2 개의 삼목과 4 개의 십자가)

다음으로, 그는 회전을 보상했습니다. 예를 들어 성냥갑에 다음이 표시됩니다.

-------
| |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의 또 다른 단순화는 컴퓨터가 먼저 작동하도록하는 것입니다. 이런 식으로 그는 모든 첫 번째 레벨의 움직임을 제거하고 나머지 상자의 약 5 분의 1을 제거 할 수있었습니다. 마지막으로 그는 모든 게임 종료 상자를 제거했습니다 (이 단계에서 더 이상 '콘텐츠'또는 이동이 필요하지 않기 때문에).

이제 알고리즘 자체에 대해 매우 간단합니다.

  1. 먼저 구슬의 색이 무엇을 나타내는 지 결정하십시오. 보드의 각 공간을 나타내려면 9 가지 색상이 필요합니다.
  2. 게임을 시작할 때, 304 개의 성냥갑 각각에 구슬이 들어 있습니다. 구슬은 임의의 색상이지만 (복제물은 괜찮습니다) 이동할 수 있어야합니다 (보드 상태 이미지가 오른쪽 가운데에 'O'를 표시하면 가운데를 나타내는 구슬을 사용할 수 없습니다- 권리).
  3. MENACE (X) 턴일 때마다 현재 보드 위치 (또는 일부 변형)가 인쇄 된 성냥갑을 찾습니다.
  4. 성냥갑을 열고 무작위로 구슬을 선택하십시오.
  5. 성냥갑의 이미지를 얻기 위해 보드 상태가 어떻게 바뀌 었는지 확인하십시오 (예 : 시계 반대 방향으로 90도 회전). 그런 다음 해당 변형을 비드에 적용합니다 (예 : 왼쪽 상단이 왼쪽이 됨).
  6. 해당 사각형에 X를 놓습니다. 성냥갑에서 선택한 구슬을 제거합니다. 결과적으로 상자가 비어있는 경우 상자에 3 개의 임의 (가능한) 구슬을 넣고 이동을 위해 그 중 하나를 선택하십시오.
  7. 게임이 끝날 때까지 3-6을 반복하십시오.
  8. MENACE가 게임에서 이겼다면 MENACE가 가져간 모든 성냥갑으로 돌아갑니다. 그런 다음 해당 이동에 사용한 색상 비드를 추적하십시오. 박스에 그 비드의 두 가지 색상을 넣으십시오.
  9. MENACE가 게임을 잃어버린 경우, 아무 것도 하지 마십시오 ( 제거한 구슬을 교체 하지 마십시오 ).
  10. MENACE가 게임을 그렸다면 각각의 움직임에 사용 된 구슬을 교체하되 추가로 추가하지 마십시오.

이것은 우리에게 매우 간단하지만 구현하기 어려운 알고리즘을 남깁니다. 이것은 당신의 도전의 기초를 형성합니다.

여전히 혼란 스러우면 http://chalkdustmagazine.com/features/menace-machine-educable-noughts-crosses-engine/을 참조하십시오 . 이 알고리즘에 대해 처음 알게되었을 때 읽은 내용입니다

도전

컴퓨터와 함께 틱택 토 게임을하십시오. 각 단계에서 모든 성냥갑의 내용을 출력하십시오.

입력

  • 프로그램 시작시 MENACE에 대해 몇 개의 게임을하고 싶은지 알려주는 숫자
  • 그런 다음 MENACE의 첫 번째 회전 후 이동을 두 개의 문자열로 입력합니다. 첫 번째 문자는 Y 축을 참조하는 "L", "R"또는 "M"(왼쪽, 오른쪽 또는 가운데)입니다. 그런 다음 X 축을 참조하여 다른 문자 (다시 "L", "R"또는 "M")를 입력합니다. 모든 움직임과 게임에 대해 반복하십시오.

출력

  • 새로운 게임이 시작될 때마다 "새로운 게임"이 출력됩니다.
  • 플레이어가 움직일 때마다 보드를 적절한 형식으로 출력하십시오. 예쁘게 보일 필요는 없습니다 (예를 들어 보드의 위치를 ​​나타내는 배열의 배열이 좋습니다).
  • 플레이어가 움직일 때마다 MENACE가 움직입니다. MENACE 이동 후 보드 출력
  • 각 게임 후에 모든 304 성냥갑의 내용을 출력하십시오. 구슬은 문자, 색상 이름, 문자 또는 원하는 문자열 또는 정수 (포인터, 익명 함수 등)로 표시 될 수 있습니다.

규칙

  1. 이것은 이므로 바이트 단위의 최단 답변이 이깁니다.
  2. MENACE의 응답을 본 후 동작을 입력 할 수 있어야합니다. '이 기능으로 모든 움직임을 전달하고 게임이 어떻게 진행되는지 지켜보십시오'.
  3. 게임 사이에 보드를 비워야합니다.
  4. 게임간에 성냥갑을 비워서는 안됩니다 (기계 학습이 재설정됩니다).
  5. 304 개의 성냥갑이 있어야합니다. 누구나 19,683 개의 모든 성냥갑으로이 알고리즘을 구현할 수 있지만 학습 속도는 느립니다 ( 유용한 내용으로 모든 게임을 얻으 려면 많은 게임이 필요 하기 때문에).
  6. 출력은 합리적인 형식으로 될 수 있으며 PPCG 표준에 따라 입력 할 수 있습니다 (규칙 2를 준수하는 한). 입력 형식을 조정해야하는 경우 ( ' 입력 '섹션에 설명 된대로 ) 의미가있는 한 괜찮습니다.
  7. 플레이어가이기거나 (세로, 가로 또는 세로로 3 개씩 획득) 또는 무승부가있는 경우 (보드가 꽉 차서 승자가없는 경우) 게임이 종료됩니다.
  8. MENACE는 가능한 이동을해야하고 (각 성냥갑 안에 가능한 구슬 만 있어야 함), 도전을 위해 사용자의 입력을 확인할 필요는 없습니다. 그들이 잘못 입력하면 프로그램은 무엇이든 할 수 있습니다 (완전히 미쳤거나 오류가 발생하는 등)-입력이 올바른 것으로 가정 할 수 있습니다.

마틴 가드너 (Martin Gardner)는 단순한 게임 헥사 포른 (Hexapawn)을 사용하여 아이디어를 시연했지만, 그가 만든 "컴퓨터"라는 이름은 잊어 버렸습니다.
Neil



1
큰 도전. 몇 가지 빠른 질문 : 1. 상자의 주어진 공간에 둘 이상의 비드가 있으면 어떻게 출력에 표시해야합니까? 2. 각각의 움직임 후에 모든 304 박스 (2736 셀) 출력을 원하십니까?
Nick Kennedy

@NickKennedy 의견을 보내 주셔서 감사합니다. 비드가 기록 될 때 표시 될 것으로 예상되는 방식은 배열입니다 (예를 들어, 다른 언어를 제한하지 않기 위해 다르게 할 수는 있지만) : 비드를 나타내는 숫자를 선택한 경우 : [[0, 2, 6], [4, 8, 4, 3, 3], [7, 7, 7, 7, 7, 7, 7, 8], [1], ... [3, 3, 5, 4]].
Geza Kerecsenyi

답변:


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 8
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.