Внедрить 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 спичечных коробок содержит бусы. В то время как бусины имеют произвольный цвет (поэтому дубликаты в порядке), они должны быть возможными ходами (поэтому, если изображение состояния доски изображает букву «О» в середине справа, то вы не можете использовать бусину, которая представляет середину верно).
  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, вы вводите свой ход в виде двухсимвольной строки, первая буква которой «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]].
Геза

Ответы:

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]
}
Ник Кеннеди
источник
Очень умный! Конечно, ротации затрудняют, но спасибо, что добавили и игрока с ботом!
Геза