View difference between Paste ID: QeQkquta and zd9xtdDp
SHOW: | | - or go back to the newest paste.
1
--[[
2
  แปลงมาจาก OX.py ในหนังสือ Artificial Intelligence
3
  with Machine Learning ของ รศ.ดร.ปริญญา สงวนสัตย์
4
]]
5-
--1) แก้ครั้งที่ 1 ลืมตรวจโค้ด function all() ปัญหา checkWin() แล้วได้ false ตลอด 5-12-18 23:12
5+
--version 2 ปรับปรุงใหม่ให้สั้นขึ้นนิดนึง แก้ส่วน ai 
6-
--2) แก้ครั้งที่ 2
6+
7-
-- 2.1) แก้ function all() เพราะจำนวนสมาชิกใน t1 กับ t2 ไม่เท่ากัน
7+
  เปลี่ยน table O กับ X ให้เก็บ boolean แทนเลขช่อง
8-
-- 2.2) เพิ่มส่วนตรวจ X ชนะกรณีที่มี criticalMove 
8+
  แล้วเรียกใช้ index แทนเพื่อความสะดวกการใช้งาน
9-
--3) แก้ครั้งที่ 3 
9+
  ลดการใช้ลูปลง
10-
-- 3.1) เอา 2.2 ออกเพิ่มการตรวจแถวที่มี X สองตำแหน่งและมีตำแหน่งว่างในแถวแทน
10+
11-
-- 3.2) เปลี่ยนจากสุ่มเลือกตำแหน่งว่างใน criticalMove เป็นเอาตำแหน่งที่ยังว่างมาใช้เลยเพราะใน criticalMove มีตำแหน่งว่างค่าเดียว
11+
--แก้เพิ่มเติม v1
12-
-- 3.3) เพิ่มการแปลงค่าที่ผู้เล่นป้อนให้เป็นจำนวนเต็มด้วยการปัดเศษ
12+
local O = {false,false,false,false,false,false,false,false,false}
13-
local O, X = {}, {}
13+
local X = {false,false,false,false,false,false,false,false,false}
14
local win = {{1,2,3},
15
            {4,5,6},
16
            {7,8,9},
17
            {1,4,7},
18
            {2,5,8},
19
            {3,6,9},
20
            {1,5,9},
21
            {3,5,7}}
22
--[[
23
เอาออก
24
local map = {"[_]","[_]","[_]",
25
            "[_]","[_]","[_]",
26
            "[_]","[_]","[_]"}
27-
--3) เพิ่มตัวย่อฟังก์ชั่นปัดเศษ
27+
28
local rnd = math.random
29
local max = math.max
30
31-
  --table.sort(t1) --1) เพิ่มบรรทัดนี้ 2) แก้ออก
31+
--เพิ่ม function การปัดเศษ
32-
  local t = 0 --2 เพิ่มบรรทัดนี้ เก็บผลรวม
32+
33-
  --for i = 1, #t1 do --1) แก้ออก
33+
34-
    for j = 1, #t2 do
34+
--ทำเรียกใช้ table function สะดวกขึ้น
35-
    for i = 1, #t1 do --2) เพิ่มบรรทัดนี้
35+
local add = table.insert
36-
      --if t1[j] ~= t2[j] then  --1) เปลี่ยน t1[i] เป็น t1[j] 2) แก้ออก
36+
local unpak = table.unpack
37-
      if t1[i] == t2[j] then  --2) เปลี่ยน t1[j] เป็น t1[i] เปลี่ยนเป็นตรวจเท่ากัน
37+
local concat = table.concat
38-
        t = t + 1  --2) เพิ่มบรรทัดนี้ เก็บจำนวนสมาชิกที่ตรงกัน
38+
39-
        --return false  --2) แก้ออก
39+
--แก้ bug ของรุ่น 1 ปรับให้สั้นลงไม่ต้องใช้ลูป
40-
        if t == 3 then return true end --2) เพิ่มบรรทัดนี้ ถ้าสมาชิกใน t2 (table ของแถวที่สามารถชนะ) ทุกตัว(3)เป็นสมาชิกใน t1
40+
41
  --ให้ t1 เป็น O หรือ X
42-
    end  --2) เพิ่ม
42+
  return t1[t2[1]] and t1[t2[2]] and t1[t2[3]]
43
end
44-
  --end --1) แก้ออก
44+
45-
  return false --2) เปลี่ยน true เป็น false
45+
--แก้ให้ code สั้นลงไม่ต้องใช้ลูป
46
function any(t1, t2)
47
  --ให้ t1 เป็น O หรือ X
48
  local t = 0
49
  --(เงื่อนไข and จริง or เท็จ) เหมือน (เงื่อนไข?จริง:เท็จ) ใน C
50-
  for i = 1, #t1 do
50+
  t = t + (t1[t2[1]] and 1 or 0)
51-
    for j = 1, #t2 do
51+
  t = t + (t1[t2[2]] and 1 or 0)
52-
      if t1[i] == t2[j] then
52+
  t = t + (t1[t2[3]] and 1 or 0)
53-
        t = t + 1
53+
  return t  --คืนค่าเป็นจำนวนของต่าที่เป็นจริงของ t1 ที่มี index เป็นสมาชิกของ t2 
54
end
55
56
--เอาส่วนของการสุ่มเลือกใน string ออกเพราะไม่ได้ใช้
57-
  return t
57+
58
  --if type(args) == "table" then
59
    return args[rnd(#args)]
60
  --elseif type(args) == "string" then
61-
  if type(args) == "table" then
61+
    --local p = rnd(#args)
62
    --return args:sub(p,p)
63-
  elseif type(args) == "string" then
63+
64-
    local p = rnd(#args)
64+
65-
    return args:sub(p,p)
65+
66
function checkWin(P)
67
  for _, w in pairs(win) do
68
    if all(P, w) then
69
      return true
70
    end
71
  end
72
  return false
73
end
74
75
--ลดเหลือลูปเดียวสร้าง map สด
76
function displayOX()
77
  local newMap = {}
78
  for i = 1, 9, 3 do
79-
  local newMap = {table.unpack(map)}
79+
    newMap[i] = O[i] and "[O]" or (X[i] and "[X]" or "[_]")
80-
  if #O > 0 then
80+
    newMap[i+1] = O[i+1] and "[O]" or (X[i+1] and "[X]" or "[_]")
81-
    for o = 1, #O do
81+
    newMap[i+2] = O[i+2] and "[O]" or (X[i+2] and "[X]" or "[_]")
82-
      newMap[O[o]] = "[O]"
82+
83
  end
84
end
85-
  if #X > 0 then
85+
86-
    for x = 1, #X do
86+
87-
      newMap[X[x]] = "[X]"
87+
  --เปลี่ยน validMove เป็นแบบเดียวกับ O กับ X แต่เก็บค่าเริ่มต้นเป็น true
88
  local validMove = {true,true,true,true,true,true,true,true,true}
89
  for i = 1, 9 do
90-
  for i = 1, #newMap, 3 do
90+
    validMove[i] = not (O[i] or X[i]) and validMove[i]
91
  end
92
--เพิ่ม v1 หาแถวที่มี X สองตำแหน่งและไม่มี O คืนค่าเป็นตำแหน่งที่ว่างในแถวนั้น
93
  for _, w in pairs(win) do
94
    local cX = any(X,w)
95
    for _, v in pairs(w) do
96-
  local validMove = {1,2,3,4,5,6,7,8,9}
96+
      if cX == 2 and validMove[v] then
97-
  --ลบตาเดินใน O กับ X ออกจาก validMove
97+
        return v
98-
  --0) เดิมใช้เปลี่ยนค่าใน validMove ที่ตรงกับ O และ X เป็น 0 แล้วค่อยมาลบออก
98+
99-
  --for o = 1, #O do
99+
100-
    --validMove[O[o]] = 0
100+
101
--##########
102-
  --for x = 1, #X do
102+
103-
    --validMove[X[x]] = 0
103+
  for i, v in ipairs(validMove) do
104
    --validMove มีสมาชิก 9 ตัวเลยเพิ่มเงื่อนไขให้ทำเฉพาะช่องที่มีค่าเป็น true
105-
  --for i = #validMove, 1, -1 do
105+
    if v then
106-
    --if validMove[i] == 0 then
106+
      local tempX, criticalMove = {unpak(X)}, {}  --ให้ tempX เป็น clone ของ X
107-
      --table.remove(validMove,i)
107+
      tempX[i] = v                                --เพิ่มตำแหน่งเดินของ X โดยเปลี่ยน tempX[i] ด้วยค่าใน validMove[i]
108
      V[i], criticalMove = evalOX(O,tempX)        --หา critiMove และคำนวณทางเลือกในตาเดินต่างๆ (V[i])
109
      if #criticalMove > 0 then                   --ถ้า O ใกล้ชนะ (มีสองตัวในชุดใดชุดหนึ่งของ table win)
110-
  --0.5) ปรับเป็นรวม O กับ X เป็น moved ก่อนแล้วลบออกจาก validMove 
110+
        --แก้ v1 เอาออกไปใช้ส่วนข้างบน
111-
  --เร็วขึ้นกว่าเดิมหน่อยโค้ดสั้นลงลดไปหนึ่งลูป
111+
        --ส่วนที่เพิ่มให้ ai ถึง O จะใกล้ชนะแต่เป็นตาของ X ถ้าเลือกแล้วชนะเกมก็ให้ใช้ค่านี้ไม่ต้องไปเลือกใน criticalMove อีก
112-
  local moved = {table.unpack(O)}
112+
        --if checkWin(tempX) then
113-
  for _, v in pairs(X) do
113+
          --return i
114-
    table.insert(moved,v)
114+
        --end
115
        --จบส่วนที่เพิ่ม
116-
  table.sort(moved, function(a,b) return a>b end)
116+
        --##########
117-
  for _, v in pairs(moved) do
117+
        --ส่วนข้างล่างนี้น่าจะเผื่อสำหรับตารางเกิน 3x3 หรือเปล่าเพราะ
118-
    table.remove(validMove,v)
118+
        --criticalMove จะมี O อยู่สองตำแหน่งว่างหนึ่งไม่ต้องจับใส่ list 
119
        --แล้วสุ่มเลือกก็ได้มั๊งแต่ช่างเถอะเอาตามตัวอย่าง
120-
  --3) เพิ่มส่วน ai เลือกตำแหน่งว่างในแถวที่มี X อยู่สองตำแหน่ง
120+
        local move = {}
121
        for _, c in pairs(criticalMove) do
122
          --เปลี่ยนการตรวจสอบ validMove
123-
    for _, i in pairs(w) do
123+
          if validMove[c] then                    --ถ้าตำแหน่งใน criticalMove อยู่ในตาที่สามารถเดินได้ให้เพิ่มตำแหน่งนั้นใน move
124-
      for _, j in pairs(validMove) do
124+
            --แก้ v1 เอาออก
125-
        if cX == 2 and i == j then
125+
            --add(move,c)
126-
          return i
126+
            --แก้ v1 เปลี่ยนเป็นคืนค่าตำแหน่งที่ว่างในแถวของ criticalMove เพราะมีตัวเดียว
127
            return c
128
            --##########
129
          end
130
        end
131
        --แก้ v1 เอาออก
132-
  for _, v in pairs(validMove) do
132+
        --return randomChoice(move)
133-
    local tempX, criticalMove = {table.unpack(X)}, {}
133+
        --##########
134-
    table.insert(tempX,v)
134+
135-
    V[v], criticalMove = evalOX(O,tempX)
135+
136-
    if #criticalMove > 0 then
136+
137-
      --3) เอาส่วนนี้ออก
137+
  --นี่เป็นส่วนคำนวณ โดยสุ่มจากตัวเลือกที่ดีที่สุด (ตำแหน่งที่มีค่ามากที่สุดใน V)
138-
      --2) เพิ่มส่วน ai ถึง O จะใกล้ชนะแต่เป็นตาของ X ถ้าเลือกแล้วชนะเกมก็ให้ใช้ค่านี้ไม่ต้องไปเลือกใน criticalMove อีก
138+
  local maxV = max(unpak(V))
139-
      --if checkWin(tempX) then
139+
140-
        --return v
140+
141-
      --end
141+
142-
      --3) เอา move ออก
142+
      add(imaxV,i)
143-
      local move = {}
143+
144-
      for _, i in pairs(validMove) do
144+
145-
        for _, j in pairs(criticalMove) do
145+
146-
          if j == i then
146+
147-
            --3) เปลี่ยนจากเอาค่าใส่ table เป็นคืนค่านั้นกลับ
147+
148-
            table.insert(move,j)
148+
149-
            return j
149+
150
  --คำนวณโอกาสในตำแหน่งเดินนั้น
151
  --(1 + (จำนวนรวมของ X ในทุกแถวที่ X มีโอกาสชนะ) - (จำนวนรวมของ O ในทุกแถวที่ O มีโอกาสชนะ))
152
  return (1 + SX - SO), criticalMove
153-
      --3) เอาออก
153+
154-
      return randomChoice(move)
154+
155
function calSOX(o,x)
156
  local SO, SX = 0, 0
157-
  local maxV = max(table.unpack(V))
157+
158
  for _, w in pairs(win) do
159
    local cO = any(o,w)          --หาจำนวนของ O ที่อยู่ใน w (table ย่อยใน Win)
160
    local cX = any(x,w)          --หาจำนวนของ X ที่อยู่ใน w (table ย่อยใน Win)
161-
      table.insert(imaxV,i)
161+
    if cX == 0 then              --ถ้าไม่มี X ในแถวนั้น (แปลว่ามีโอกาสที่ O จะชนะในแถวนั้น)
162
      SO = SO + cO               --เก็บค่าจำนวนรวมของ O ในแต่ละแถว
163
      if cO == 2 then            --ถ้าจำนวนของ O ในแถวนั้นเป็น 2
164
        print("critical", "{".. concat(w,",").."}")
165
        criticalMove = w         --แสดงว่าแถวนั้นเป็น critical ที่ X ต้องกันเพื่อไม่ให้ O ชนะ
166
      end
167
    end
168
    if cO == 0 then              --ถ้าไม่มี O ในแถวนั้น (แปลว่ามีโอกาสที่ X จะชนะในแถวนั้น)
169
      SX = SX + cX               --เก็บค่าจำนวนรวมของ X ในแต่ละแถว
170
    end
171
  end
172
  return SO, SX, criticalMove
173
end
174
175
while true do
176-
    local cO = any(o,w)
176+
177-
    local cX = any(x,w)
177+
178-
    if cX == 0 then
178+
179-
      SO = SO + cO
179+
  local validMove = true
180-
      if cO == 2 then
180+
181-
        print("critical", "{".. table.concat(w,",").."}")
181+
182-
        criticalMove = w
182+
183
  move = tonumber(io.read())
184
  print("")
185-
    if cO == 0 then
185+
186-
      SX = SX + cX
186+
187
    goto chooseAgain
188
  --elseif move > 9 or move < 1 then
189
    --goto chooseAgain
190
  --else
191
    --เพิ่มการปัดเศษทศนิยมกรณีป้อนค่าเป็นเลขทศนิยม
192
    --move = floor(move)
193
  --end
194
  --แก้ v1 ปัดเศษก่อนแล้วค่อยเทียบค่า
195
  else
196
    move == floor(move)
197
    if move > 9 or move < 1 then
198
      goto chooseAgain
199
    end
200
  end
201
  --##########
202
  --แก้ให้เทียบค่า O[move] แทน
203
  --for _, v in pairs(O) do
204-
  --3) เปลี่ยนให้แปลง move แป็นจำนวนเต็มด้วยการปัดเศษก่อนตรวจหาช่วงของ move
204+
    --if move == v then
205
    --if O[move] then
206
      --goto chooseAgain
207
    --end
208-
    move = floor(move)
208+
209
  --แก้ให้เทียบค่า X[move] แทน
210
  --for _, v in pairs(X) do
211
    --if move == v then
212
    --if X[move] then
213-
  for _, v in pairs(O) do
213+
      --goto chooseAgain
214-
    if move == v then
214+
215
  --end
216
  --แก้ v1 เอาลูปออกแล้วปรับเป็นเงื่อนไขเดียว
217
  if O[move] or X[move] then
218-
  for _, v in pairs(X) do
218+
219-
    if move == v then
219+
220
  --##########
221
  --เปลี่ยนจาก insert table O เป็นเลือกตำแหน่งที่ O[move] ให้เป็น true แทน
222
  O[move] = true
223-
  table.insert(O,move)
223+
224
  if checkWin(O) then
225
    print("O win")
226
    break
227
  end
228
  --เปลี่ยนจากนับจำนวนสมาชิกใน O กับ X เป้นการตรวจ true/false แทนตามการเปลี่ยน O กับ X
229-
  if #O + #X == 9 then
229+
  local d = true
230
  for i = 1, 9 do
231
    d = (O[i] or X[i]) and d
232
  end
233-
  table.insert(X,ai())
233+
  if d then
234
    print("Draw")
235
    break
236
  end
237
  --จบส่วนที่เปลี่ยนกรณีเสมอ
238
  --เปลี่ยนจาก insert table X เป็นเลือกตำแหน่งที่ X[ai()] ให้เป็น true แทน
239-
  --2) เอาออกไม่ต้องใช้ตรวจเฉพาะในตาของ O ก็พอ
239+
  X[ai()] = true
240
  displayOX()
241
  if checkWin(X) then
242
    print("X win")
243
    break
244
  end
245
  --ตัดส่วนนี้ออกเพราะ O เริ่มก่อน O ลงสุดท้ายถึงจะเสมอได้ไม่ต้องตรวจหลังตาเดินของ X
246
  --if #O + #X == 9 then
247
    --print("Draw")
248
    --break
249
  --end
250
end