MENACEを実装する


11

バックグラウンド

MENACE(M achine E ducable Nは oughts A ND C rosses E ngine)1960年代に英国のコンピュータ科学者ドナルド・ミッチーが作成したゲームNoughtsと十字のための初歩的な浅いの機械学習アルゴリズムです。元々は304個のマッチボックスで実装され、各マッチボックスにはボードの位置がラベル付けされており、色付きのビーズが含まれています(9色のいずれかで、可能な動きを表します)。Michieは、ボード上の動きのあらゆる組み合わせに対して、これらの304個のマッチボックスで十分であると計算しました。

あなたの中のより数学的な人は、実際にはN&Cボード上​​にNoughts、Cross、Blanksの19,683の可能な組み合わせがあることに気付くかもしれません。しかし、彼はこの数を減らす方法を計算しました(アルゴリズムを高速化し、マッチボックスを減らす可能性が高い!)。最初に、彼は次のような可能性のない動きをすべて削除しました。

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

(2つのoughtと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が行った別の単純化は、コンピューターが最初に行くことを確認することでした。この方法で、彼はすべての第1レベルの移動を削除し、残りの約5分の1のボックスを削除できました。最後に、彼はすべてのゲーム終了ボックスを削除しました(これらのステップでそれ以上の「コンテンツ」や移動は必要ありませんでした)。

右、今アルゴリズム自体に(それは非常に簡単です):

  1. まず、ビーズの色が何を表すかを決めます。ボード上の各スペースを表すには9色が必要です。
  2. ゲームの開始時に、304個のマッチボックスのそれぞれにビーズが含まれています。ビーズはランダムな色です(複製は問題ありません)が、移動できるはずです(そのため、ボードの状態の画像が右中央に「O」を描いている場合、中央を表すビーズは使用できません。正しい)。
  3. MENACEの(X)ターンになるたびに、現在のボード位置(またはその変形)が印刷されたマッチボックスを見つけます。
  4. マッチ箱を開き、そこにあるビーズをランダムに選択します。
  5. ボードの状態がどのように変換されてマッチボックスの画像に到達するかを確認します(反時計回りに90度回転します)。次に、その変換をビーズに適用します(左上が左になります)。
  6. その正方形にXを配置します。選択したビーズをマッチ箱から削除します。その結果、ボックスが空のままになっている場合は、3つのランダムな(可能性のある)ビーズをボックスに入れ、移動のためにそれらの1つを選択します。
  7. ゲームが終了するまで3〜6を繰り返します。
  8. MENACEがゲームに勝った場合は、MENACEが取ったすべてのマッチボックスに戻ります。次に、その移動で使用した色のビーズをトレースバックします。その色のビーズの2つをボックスに入れます(元のビーズともう1つがあり、それによってMENACEの可能性が高くなり、次にその位置に移動したときに動きます)
  9. MENACEがゲームに負けた場合は、何もしません(取り出したビーズを交換しないでください)。
  10. MENACEがゲームを描いた場合、各ムーブで使用したビーズを交換しますが、余分なビーズを追加しないでください。

これにより、アルゴリズムは非常にシンプルですが、実装が困難になります。これが課題の基礎となります。

まだ混乱している場合は、http://chalkdustmagazine.com/features/menace-machine-educable-noughts-crosses-engine/を参照してください -このアルゴリズムについて最初に学んだときに読んだものです

チャレンジ

コンピューターで三目並べのゲームをプレイします。各ステップで、すべてのマッチボックスの内容を出力します。

入力

  • プログラムの開始時に、MENACEと対戦するゲームの数を示す数字
  • 次に、MENACEの最初のターンの後、動きを2文字の文字列として入力します。最初の文字はY軸を指す「L」、「R」、または「M」(左、右、または中央)です。次に、別の文字(もう一度、「L」、「R」、または「M」)を入力します。今回はX軸を参照します。すべての動きとゲームで繰り返します。

出力

  • 各新しいゲームの開始時に、「新しいゲーム」を出力します。
  • プレーヤーによる各移動の後、適切なフォーマットでボードを出力します。きれいに見える必要はありません(例えば、ボードの位置を表す配列の配列は問題ありません)。
  • プレイヤーによる各移動の後、MENACEは移動を行う必要があります。MENACEの移動後にボードを出力します
  • 各ゲームの後に、304個すべてのマッチボックスの内容を出力します。ビーズは、文字、色の名前、文字、または任意の文字列または整数で表すことができます(ポインター、匿名関数などはありません)。

ルール

  1. これはであるため、バイト単位の最短回答が優先されます。
  2. MENACEの応答を見た後、動きを入力できる必要があります。「すべての動きをこの機能に渡して、ゲームがどのようにプレイされるかを見る」ことはできません。
  3. ボードはゲーム間でクリアする必要があります。
  4. ゲーム間でマッチボックスをクリアしてはいけませ(これにより機械学習がリセットされます)
  5. 304個のマッチボックスが必要です。誰でもこのアルゴリズムを19,683個のすべてのマッチボックスで実装できますが、学習には時間がかかります(すべてのマッチボックスを使用するには、多くのゲームが必要です)。
  6. 出力は任意の妥当な形式にすることができ、PPCG標準に従って入力を取得できます(ルール2に準拠している限り)。(「入力」セクションで説明されているように)入力形式を調整する必要がある場合は、意味があれば問題ありません。
  7. ゲームは、プレイヤーが勝つ(斜め、水平、または垂直に3つ連続して)または引き分けがある(ボードがいっぱいで勝者がいない)ときに終了します
  8. MENACEは可能な移動を行う必要があります(各マッチボックス内に可能なビーズのみがあります)が、チャレンジのために、ユーザーの入力を検証する必要はありません。間違ったタイプを入力した場合、プログラムは何でもできます(完全にクレイジーになり、エラーをスローするなど)。入力が正しいと想定できます。

Martin Gardnerが、よりシンプルなゲームHexapawnを使用してアイデアを実証したことを覚えていますが、彼が構築した「コンピューター」と名付けたものは忘れています。
ニール



大きな挑戦。いくつかの簡単な質問: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]]
ゲザ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リンクは、インタラクティブな入力を想定しているため失敗します。これは、ランダムにスポットを選んだ2番目のランダムプレーヤーと対戦するバージョンです。この2番目のバージョンの出力は、勝者(ドローの場合は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]
}

非常に賢い!もちろん、ローテーションは難しくなりますが、ボットプレーヤーも追加してくれてありがとう!
ゲザKerecsenyi
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.