Впроваджуйте MENACE


11

Фон

MENACE ( M achine E ducable N oughts й C Rosses E ngine) є рудиментарним неглибоко машина алгоритм навчання для гри хрестики і нулики, створений британським вчений Дональд Мічі в 1960 - ті роки. Спочатку він був реалізований із 304 коробками сірників, кожен з яких позначений позицією дошки та містить кольорові намистини (з одним із дев'яти кольорів, що представляють можливі рухи). Мічі підрахував, що цих 304 сірникових коробок було достатньо для кожної комбінації ходів на дошці.

Більш математичні серед вас можуть усвідомити, що насправді на дошці N&C є 19 683 можливих комбінацій нут, хрестів та заготовок; однак, він розраховував способи скоротити цю кількість (щоб прискорити алгоритм і, швидше за все, скоротити на сірникові коробки!). По-перше, він видалив усі неможливі рухи, такі як:

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

Ці перетворення застосовуються для обертання та інвертування (у всіх напрямках, включаючи діагональ). Ще раз вам потрібно лише один раз зберегти кожну скриньку таким чином: не робіть окремих віртуальних коробок для кожної трансформації!

Ще одне спрощення, яке зробив Мічі, - переконатися, що комп'ютер працює першим. Таким чином, він міг видалити всі рухи першого рівня, видаливши приблизно п'яту частину залишків. Нарешті, він видалив усі вікна, що закінчуються іграми (оскільки в цьому кроці не потрібно було більше вмісту або рухів).

Право, тепер на самому алгоритмі (це дуже просто):

  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/ - це те, що я прочитав, коли вперше дізнався про цей алгоритм

Виклик

Пограйте в гру Tic-Tac-Toe з комп'ютером. На кожному кроці виведіть вміст усіх сірників.

Вхідні дані

  • На початку програми номер, який говорить, скільки ігор ви хочете зіграти проти MENACE
  • Потім, після першого повороту MENACE, ви вводите свій хід у вигляді двох символьних рядків, перша буква - "L", "R" або "M" (зліва, справа або посередині), що стосується осі Y. Потім ви вводите іншу літеру (знову "L", "R" або "M"), цього разу посилаючись на вісь X. Повторіть всі рухи та ігри.

Виходи

  • На початку кожної нової гри виведіть "нову гру".
  • Після кожного ходу гравця виведіть дошку в будь-якому розумному форматі. Він не повинен виглядати красиво (наприклад, масив масивів, що представляють позиції дошки, добре).
  • Після кожного ходу гравця MENACE повинен зробити хід. Виведіть плату після переміщення MENACE
  • Після кожної гри виведіть вміст усіх 304 сірників. Бісер може бути представлений буквою, назвою кольору, символом або будь-яким рядком або цілим числом, яке вам подобається (без покажчиків, анонімних функцій тощо).

Правила

  1. Це , тому найкоротша відповідь у байтах виграє.
  2. Я повинен мати можливість вводити рухи, побачивши відповідь MENACE. Ні в якому разі не передавайте всі ваші рухи в цю функцію, і дивіться, як грає гра.
  3. Дошка повинна бути очищена між іграми.
  4. У Сірничниці повинні НЕ бути очищені між іграми (це було б скинути машинне навчання)
  5. У вас повинно бути 304 коробки з сірниками. Будь-хто може реалізувати цей алгоритм з усіма 19 683 коробками матчів, але навчання відбувається повільно (оскільки для отримання всіх корисних вмістів потрібно багато ігор).
  6. Вихід може бути у будь-якому розумному форматі, а введення можна приймати відповідно до стандартів PPCG (до тих пір, як це відповідає правилу 2). Якщо вам потрібно відкоригувати формат введення (як описано в розділі « Введення »), тоді це нормально, поки це має сенс.
  7. Гра закінчується, коли гравець виграє (отримуючи три поспіль по діагоналі, горизонталі чи вертикалі) або якщо є нічия (дошка повна, а переможця немає)
  8. У той час як MENACE повинен робити можливі рухи (і мати лише можливі намистини всередині кожної скриньки), задля виклику вам не потрібно перевіряти дані користувача. Якщо вони вводять щось не так, програма може робити все, що завгодно (піти зовсім з розуму, помилка кидання тощо) - ви можете припустити, що введення правильне.

Пам'ятаю, як Мартин Гарднер демонстрував ідею, використовуючи простішу гру 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

Відповіді:


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.