ใช้ MENACE


11

พื้นหลัง

Menace ( M achine E ducable N oughts ครั้งC Rosses E ngine) เป็นขั้นตอนวิธีการเรียนรู้เครื่องตื้นพื้นฐานสำหรับเอกซ์เกมและไม้กางเขนที่สร้างขึ้นโดยนักวิทยาศาสตร์คอมพิวเตอร์ชาวอังกฤษโดนัลด์ Michie ในปี 1960 แต่เดิมมันถูกนำไปใช้กับ 304 กลักไม้ขีดไฟแต่ละป้ายที่มีตำแหน่งกระดานและมีลูกปัดสี (กับหนึ่งในเก้าสีแสดงถึงการเคลื่อนไหวที่เป็นไปได้) Michie คำนวณว่า 304 matchboxes เหล่านี้เพียงพอสำหรับทุกการเคลื่อนไหวบนกระดาน

ในทางคณิตศาสตร์มากขึ้นในหมู่คุณอาจรู้ว่ามีการรวมกันที่เป็นไปได้ของ Noughts, Crosses และ Blanks บนกระดาน 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|
-------

การแปลงเหล่านี้ใช้สำหรับการหมุนและการกลับด้าน (ในทุกทิศทางรวมถึงแนวทแยงมุม) อีกครั้งคุณจะต้องบันทึกเฉพาะกล่องไม้ขีดแต่ละครั้งด้วยวิธีนี้: อย่าสร้างกล่องเสมือนแยกสำหรับการแปลงแต่ละครั้ง!

ความเรียบง่ายอีกอย่างของ Michie คือการทำให้แน่ใจว่าคอมพิวเตอร์ไปก่อน ด้วยวิธีนี้เขาสามารถลบการเคลื่อนย้ายระดับแรกทั้งหมดลบประมาณหนึ่งในห้าของกล่องที่เหลือ ในที่สุดเขาก็ลบกล่องจบเกมทั้งหมด (เนื่องจาก 'เนื้อหา' หรือการย้ายถูกต้องในขั้นตอนเหล่านี้)

ตอนนี้มาถึงอัลกอริทึม (มันง่ายมาก):

  1. ก่อนอื่นให้ตัดสินใจว่าสีของลูกปัดนั้นหมายถึงอะไร คุณจะต้องมี 9 สีเพื่อเป็นตัวแทนของแต่ละช่องว่างบนกระดาน
  2. ในช่วงเริ่มต้นของการแข่งขัน 304 matchbox แต่ละอันจะมีเม็ดบีด ในขณะที่เม็ดมีสีแบบสุ่ม (ดังนั้นจึงซ้ำกันได้ดี) พวกเขาควรจะเคลื่อนไหวได้ (ดังนั้นหากภาพสถานะของบอร์ดแสดงให้เห็นว่า 'O' อยู่ตรงกลางขวาคุณจะไม่สามารถใช้ลูกปัดที่แทนค่ากลาง - ขวา).
  3. ทุกครั้งที่มีการหมุน (X) ของ MENACE ให้ค้นหา matchbox ที่มีตำแหน่งกระดานปัจจุบัน (หรือการเปลี่ยนแปลงของมัน) ที่พิมพ์ลงบน
  4. เปิดกลักไม้ขีดไฟและเลือกลูกปัดใด ๆ ที่นั่นโดยการสุ่ม
  5. ค้นหาว่าสถานะของบอร์ดได้รับการเปลี่ยนแปลงอย่างไรเพื่อไปยังภาพบนกลักไม้ขีดไฟ (เช่นหมุนทวนเข็มนาฬิกา 90deg) จากนั้นใช้การแปลงนั้นกับลูกปัด (เช่นซ้ายบนกลายเป็นซ้ายซ้าย)
  6. วาง X ในตารางนั้น นำ bead ที่เลือกออกจากกลักไม้ขีดไฟ หากกล่องถูกปล่อยว่างเปล่าให้ใส่ลูกปัดสามเม็ด (เป็นไปได้) ลงในกล่องแล้วเลือกหนึ่งเม็ดเพื่อย้าย
  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 matchboxes ทุกคนสามารถใช้อัลกอริทึมนี้กับกล่องตรงกัน 19,683 ทั้งหมด แต่การเรียนรู้ช้า (เนื่องจากต้องใช้เกมจำนวนมากในการรับเนื้อหาทั้งหมดที่มีประโยชน์)
  6. เอาต์พุตสามารถอยู่ในรูปแบบที่สมเหตุสมผลและอินพุตสามารถใช้ตามมาตรฐาน PPCG (ตราบใดที่เป็นไปตามกฎ 2) หากคุณต้องการปรับรูปแบบการป้อนข้อมูล (ตามที่อธิบายไว้ในส่วน 'การป้อนข้อมูล ') ก็ถือว่าใช้ได้ตราบใดที่เหมาะสม
  7. เกมจะจบลงเมื่อผู้เล่นชนะ (โดยได้สามแถวติดต่อกัน, แนวนอนหรือแนวตั้ง) หรือหากมีการเสมอ (กระดานเต็มและไม่มีผู้ชนะ)
  8. ในขณะที่ MENACE ต้องการให้มีการเคลื่อนไหวที่เป็นไปได้ (และมีเม็ดบีดที่เป็นไปได้ภายในแต่ละกลักไม้ขีดไฟ) เพื่อความท้าทายที่คุณไม่จำเป็นต้องตรวจสอบอินพุตของผู้ใช้ หากพวกเขาพิมพ์สิ่งผิดปกติโปรแกรมของคุณสามารถทำอะไรก็ได้ (ไปอย่างสมบูรณ์บ้าคลั่งโยนความผิดพลาด ฯลฯ ) - คุณสามารถสันนิษฐานได้ว่าอินพุตนั้นถูกต้อง

ฉันจำมาร์ตินการ์ดเนอร์แสดงความคิดโดยใช้เกมที่ง่ายกว่า 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 สำหรับการจับรางวัล) Matchboxes จะถูกกำหนดค่าเริ่มต้นสำหรับทุกตำแหน่งของบอร์ด แต่จะใช้สำหรับ 304 ต่อสเปคเท่านั้น พวกเขากำลังนำมาใช้เป็นสำเนาของคณะกรรมการที่มีตัวเลขติดลบเพื่อระบุจำนวนของลูกปัดในแต่ละตำแหน่ง ฉันทดลองกับรายการเวกเตอร์ตามสเป็คดั้งเดิม แต่มันไม่ง่ายนัก

นี่เป็นเวอร์ชั่น golfed ที่มีความคิดเห็นน้อยกว่า โปรดทราบว่ามันไม่ได้พิมพ์กล่องจับคู่ออกเพราะยาวมาก สามารถใช้ผู้เล่น 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.