SHOW:
|
|
- or go back to the newest paste.
| 1 | --[[ | |
| 2 | แปลงมาจาก OX.py ในหนังสือ Artificial Intelligence | |
| 3 | with Machine Learning ของ รศ.ดร.ปริญญา สงวนสัตย์ | |
| 4 | ]] | |
| 5 | - | --version 2 ปรับปรุงใหม่ให้สั้นขึ้นนิดนึง แก้ส่วน ai |
| 5 | + | --version 3 ใช้ penlight module ช่วยจัดการเรื่อง list และ list comprehension |
| 6 | --ทำให้เขียนโค้ดสั้นลงใกล้เคียง python ต้้นฉบับมากขึ้น แต่ประสิทธิภาพยังไม่ได้ตรวจ | |
| 7 | - | เปลี่ยน table O กับ X ให้เก็บ boolean แทนเลขช่อง |
| 7 | + | -- #1.1 แก้ไขเปลี่ยนส่วนของ ai() และการตรวจสอบตอนผู้เล่นพิมพ์ตำแหน่ง |
| 8 | - | แล้วเรียกใช้ index แทนเพื่อความสะดวกการใช้งาน |
| 8 | + | local comp = require("pl.comprehension").new()
|
| 9 | - | ลดการใช้ลูปลง |
| 9 | + | local ls = require("pl.List")
|
| 10 | local O, X = ls{}, ls{}
| |
| 11 | - | --แก้เพิ่มเติม v1 |
| 11 | + | |
| 12 | - | local O = {false,false,false,false,false,false,false,false,false}
|
| 12 | + | |
| 13 | - | local X = {false,false,false,false,false,false,false,false,false}
|
| 13 | + | |
| 14 | {1,4,7},
| |
| 15 | {2,5,8},
| |
| 16 | {3,6,9},
| |
| 17 | {1,5,9},
| |
| 18 | {3,5,7}}
| |
| 19 | local rnd = math.random | |
| 20 | local max = math.max | |
| 21 | local floor = math.floor | |
| 22 | local concat = table.concat | |
| 23 | - | เอาออก |
| 23 | + | local unpack = table.unpack |
| 24 | - | local map = {"[_]","[_]","[_]",
|
| 24 | + | |
| 25 | - | "[_]","[_]","[_]", |
| 25 | + | --function all(t1, t2) |
| 26 | - | "[_]","[_]","[_]"} |
| 26 | + | --local t = true |
| 27 | --for _, v in pairs(t2) do | |
| 28 | --t = t and t1:contains(v) | |
| 29 | --end | |
| 30 | --return t | |
| 31 | - | --เพิ่ม function การปัดเศษ |
| 31 | + | --end |
| 32 | ||
| 33 | --function any(t1, t2) | |
| 34 | - | --ทำเรียกใช้ table function สะดวกขึ้น |
| 34 | + | --local t = 0 |
| 35 | - | local add = table.insert |
| 35 | + | --for _, v in pairs(t2) do |
| 36 | - | local unpak = table.unpack |
| 36 | + | --if t1:contains(v) then |
| 37 | --t = t + 1 | |
| 38 | --end | |
| 39 | - | --แก้ bug ของรุ่น 1 ปรับให้สั้นลงไม่ต้องใช้ลูป |
| 39 | + | |
| 40 | - | function all(t1, t2) |
| 40 | + | --return t |
| 41 | - | --ให้ t1 เป็น O หรือ X |
| 41 | + | --end |
| 42 | - | return t1[t2[1]] and t1[t2[2]] and t1[t2[3]] |
| 42 | + | |
| 43 | function randomChoice(args) | |
| 44 | return args[rnd(#args)] | |
| 45 | - | --แก้ให้ code สั้นลงไม่ต้องใช้ลูป |
| 45 | + | |
| 46 | - | function any(t1, t2) |
| 46 | + | |
| 47 | - | --ให้ t1 เป็น O หรือ X |
| 47 | + | |
| 48 | - | local t = 0 |
| 48 | + | |
| 49 | - | --(เงื่อนไข and จริง or เท็จ) เหมือน (เงื่อนไข?จริง:เท็จ) ใน C |
| 49 | + | --if all(P, w) then |
| 50 | - | t = t + (t1[t2[1]] and 1 or 0) |
| 50 | + | --return true |
| 51 | - | t = t + (t1[t2[2]] and 1 or 0) |
| 51 | + | |
| 52 | - | t = t + (t1[t2[3]] and 1 or 0) |
| 52 | + | local all = ls(comp "(_1):contains(v) for _,v in pairs(_2)" (P,w)):reduce(function(x,y) return x and y end) |
| 53 | - | return t --คืนค่าเป็นจำนวนของต่าที่เป็นจริงของ t1 ที่มี index เป็นสมาชิกของ t2 |
| 53 | + | if all then |
| 54 | return true | |
| 55 | end | |
| 56 | - | --เอาส่วนของการสุ่มเลือกใน string ออกเพราะไม่ได้ใช้ |
| 56 | + | |
| 57 | return false | |
| 58 | - | --if type(args) == "table" then |
| 58 | + | |
| 59 | - | return args[rnd(#args)] |
| 59 | + | |
| 60 | - | --elseif type(args) == "string" then |
| 60 | + | |
| 61 | - | --local p = rnd(#args) |
| 61 | + | |
| 62 | - | --return args:sub(p,p) |
| 62 | + | |
| 63 | newMap[i] = O:contains(i) and "[O]" or (X:contains(i) and "[X]" or "[_]") | |
| 64 | newMap[i+1] = O:contains(i+1) and "[O]" or (X:contains(i+1) and "[X]" or "[_]") | |
| 65 | newMap[i+2] = O:contains(i+2) and "[O]" or (X:contains(i+2) and "[X]" or "[_]") | |
| 66 | print(newMap[i]..newMap[i+1]..newMap[i+2]) | |
| 67 | end | |
| 68 | - | if all(P, w) then |
| 68 | + | |
| 69 | ||
| 70 | function ai() | |
| 71 | local validMove = ls.range(1,9) | |
| 72 | local moved = O:clone():extend(X) | |
| 73 | for _, v in pairs(moved) do | |
| 74 | validMove:remove_value(v) | |
| 75 | - | --ลดเหลือลูปเดียวสร้าง map สด |
| 75 | + | |
| 76 | -- #1.1 แก้ไขตรวจหาแถวที่มี X สองตำแหน่งและไม่มี O ให้เลือกตำแหน่งที่ว่างในแถวนั้น | |
| 77 | for _, w in pairs(win) do | |
| 78 | local xl = ls(comp "(_1):contains(v) for _,v in pairs(_2)" (X,w)) | |
| 79 | - | newMap[i] = O[i] and "[O]" or (X[i] and "[X]" or "[_]") |
| 79 | + | local vl = ls(comp "(_1):contains(v) for _,v in pairs(_2)" (validMove,w)) |
| 80 | - | newMap[i+1] = O[i+1] and "[O]" or (X[i+1] and "[X]" or "[_]") |
| 80 | + | if xl:count(true)==2 and vl:reduce(function(x,y) return x or y end) then |
| 81 | - | newMap[i+2] = O[i+2] and "[O]" or (X[i+2] and "[X]" or "[_]") |
| 81 | + | return comp "v for _,v in pairs(_1) if (_2):contains(v)"(w,validMove)[1] |
| 82 | end | |
| 83 | end | |
| 84 | --########## | |
| 85 | local V = {-100,-100,-100,-100,-100,-100,-100,-100,-100}
| |
| 86 | for _, v in pairs(validMove) do | |
| 87 | - | --เปลี่ยน validMove เป็นแบบเดียวกับ O กับ X แต่เก็บค่าเริ่มต้นเป็น true |
| 87 | + | local tempX, criticalMove = X:clone(), {}
|
| 88 | - | local validMove = {true,true,true,true,true,true,true,true,true}
|
| 88 | + | tempX:append(v) |
| 89 | - | for i = 1, 9 do |
| 89 | + | V[v], criticalMove = evalOX(O,tempX) |
| 90 | - | validMove[i] = not (O[i] or X[i]) and validMove[i] |
| 90 | + | if #criticalMove > 0 then |
| 91 | -- #1.1 เอาออก | |
| 92 | - | --เพิ่ม v1 หาแถวที่มี X สองตำแหน่งและไม่มี O คืนค่าเป็นตำแหน่งที่ว่างในแถวนั้น |
| 92 | + | --if checkWin(tempX) then |
| 93 | --return v | |
| 94 | - | local cX = any(X,w) |
| 94 | + | --end |
| 95 | - | for _, v in pairs(w) do |
| 95 | + | --########## |
| 96 | - | if cX == 2 and validMove[v] then |
| 96 | + | -- #1.1 เปลี่ยนจากใช้ลูปเป็น list comprehention แทนและ return ค่าเดียวไม่ต้องใส่ table แล้วสุ่มเลือก |
| 97 | - | return v |
| 97 | + | --local move = ls{}
|
| 98 | --for _, i in pairs(criticalMove) do | |
| 99 | --if validMove:contains(i) then | |
| 100 | --move:append(i) | |
| 101 | - | --########## |
| 101 | + | |
| 102 | --end | |
| 103 | - | for i, v in ipairs(validMove) do |
| 103 | + | --return randomChoice(move) |
| 104 | - | --validMove มีสมาชิก 9 ตัวเลยเพิ่มเงื่อนไขให้ทำเฉพาะช่องที่มีค่าเป็น true |
| 104 | + | --########## |
| 105 | - | if v then |
| 105 | + | return comp "v for _,v in pairs(_1) if (_2):contains(v)" (criticalMove,validMove)[1] |
| 106 | - | local tempX, criticalMove = {unpak(X)}, {} --ให้ tempX เป็น clone ของ X
|
| 106 | + | |
| 107 | - | tempX[i] = v --เพิ่มตำแหน่งเดินของ X โดยเปลี่ยน tempX[i] ด้วยค่าใน validMove[i] |
| 107 | + | |
| 108 | - | V[i], criticalMove = evalOX(O,tempX) --หา critiMove และคำนวณทางเลือกในตาเดินต่างๆ (V[i]) |
| 108 | + | local maxV = max(unpack(V)) |
| 109 | - | if #criticalMove > 0 then --ถ้า O ใกล้ชนะ (มีสองตัวในชุดใดชุดหนึ่งของ table win) |
| 109 | + | --local imaxV = ls{}
|
| 110 | - | --แก้ v1 เอาออกไปใช้ส่วนข้างบน |
| 110 | + | --for i, v in pairs(V) do |
| 111 | - | --ส่วนที่เพิ่มให้ ai ถึง O จะใกล้ชนะแต่เป็นตาของ X ถ้าเลือกแล้วชนะเกมก็ให้ใช้ค่านี้ไม่ต้องไปเลือกใน criticalMove อีก |
| 111 | + | --if v == maxV then |
| 112 | - | --if checkWin(tempX) then |
| 112 | + | --imaxV:append(i) |
| 113 | - | --return i |
| 113 | + | |
| 114 | --end | |
| 115 | - | --จบส่วนที่เพิ่ม |
| 115 | + | local imaxV = comp "i for i,v in pairs(_1) if v == (_2)" (V,maxV) |
| 116 | - | --########## |
| 116 | + | |
| 117 | - | --ส่วนข้างล่างนี้น่าจะเผื่อสำหรับตารางเกิน 3x3 หรือเปล่าเพราะ |
| 117 | + | |
| 118 | - | --criticalMove จะมี O อยู่สองตำแหน่งว่างหนึ่งไม่ต้องจับใส่ list |
| 118 | + | |
| 119 | - | --แล้วสุ่มเลือกก็ได้มั๊งแต่ช่างเถอะเอาตามตัวอย่าง |
| 119 | + | |
| 120 | - | local move = {}
|
| 120 | + | local SO, SX, criticalMove = calSOX(o,x) |
| 121 | - | for _, c in pairs(criticalMove) do |
| 121 | + | |
| 122 | - | --เปลี่ยนการตรวจสอบ validMove |
| 122 | + | |
| 123 | - | if validMove[c] then --ถ้าตำแหน่งใน criticalMove อยู่ในตาที่สามารถเดินได้ให้เพิ่มตำแหน่งนั้นใน move |
| 123 | + | |
| 124 | - | --แก้ v1 เอาออก |
| 124 | + | |
| 125 | - | --add(move,c) |
| 125 | + | |
| 126 | - | --แก้ v1 เปลี่ยนเป็นคืนค่าตำแหน่งที่ว่างในแถวของ criticalMove เพราะมีตัวเดียว |
| 126 | + | |
| 127 | - | return c |
| 127 | + | |
| 128 | - | --########## |
| 128 | + | --local cO = any(o,w) |
| 129 | - | end |
| 129 | + | --local cX = any(x,w) |
| 130 | - | end |
| 130 | + | local cO = ls(comp "(_1):contains(v) for _,v in pairs(_2)" (o,w)) |
| 131 | - | --แก้ v1 เอาออก |
| 131 | + | local cX = ls(comp "(_1):contains(v) for _,v in pairs(_2)" (x,w)) |
| 132 | - | --return randomChoice(move) |
| 132 | + | if not cX:reduce(function(x,y) return x or y end) then |
| 133 | - | --########## |
| 133 | + | local nO = cO:count(true) |
| 134 | SO = SO + nO | |
| 135 | if nO == 2 then | |
| 136 | print("critical", "{".. concat(w,",").."}")
| |
| 137 | - | --นี่เป็นส่วนคำนวณ โดยสุ่มจากตัวเลือกที่ดีที่สุด (ตำแหน่งที่มีค่ามากที่สุดใน V) |
| 137 | + | criticalMove = w |
| 138 | - | local maxV = max(unpak(V)) |
| 138 | + | |
| 139 | - | local imaxV = {}
|
| 139 | + | |
| 140 | - | for i, v in pairs(V) do |
| 140 | + | if not cO:reduce(function(x,y) return x or y end) then |
| 141 | - | if v == maxV then |
| 141 | + | SX = SX + cX:count(true) |
| 142 | - | add(imaxV,i) |
| 142 | + | |
| 143 | end | |
| 144 | return SO, SX, criticalMove | |
| 145 | end | |
| 146 | ||
| 147 | while true do | |
| 148 | io.write("Choose position [1-9]: ")
| |
| 149 | - | local SO, SX, criticalMove =calSOX(o,x) |
| 149 | + | |
| 150 | - | --คำนวณโอกาสในตำแหน่งเดินนั้น |
| 150 | + | |
| 151 | - | --(1 + (จำนวนรวมของ X ในทุกแถวที่ X มีโอกาสชนะ) - (จำนวนรวมของ O ในทุกแถวที่ O มีโอกาสชนะ)) |
| 151 | + | |
| 152 | ::chooseAgain:: | |
| 153 | io.write("Bad move: choose position [1-9]: ")
| |
| 154 | move = tonumber(io.read()) | |
| 155 | print("")
| |
| 156 | ::checkMove:: | |
| 157 | if not move then | |
| 158 | goto chooseAgain | |
| 159 | - | local cO = any(o,w) --หาจำนวนของ O ที่อยู่ใน w (table ย่อยใน Win) |
| 159 | + | -- #1.1 เอาออก |
| 160 | - | local cX = any(x,w) --หาจำนวนของ X ที่อยู่ใน w (table ย่อยใน Win) |
| 160 | + | |
| 161 | - | if cX == 0 then --ถ้าไม่มี X ในแถวนั้น (แปลว่ามีโอกาสที่ O จะชนะในแถวนั้น) |
| 161 | + | |
| 162 | - | SO = SO + cO --เก็บค่าจำนวนรวมของ O ในแต่ละแถว |
| 162 | + | |
| 163 | - | if cO == 2 then --ถ้าจำนวนของ O ในแถวนั้นเป็น 2 |
| 163 | + | |
| 164 | move = floor(move) | |
| 165 | - | criticalMove = w --แสดงว่าแถวนั้นเป็น critical ที่ X ต้องกันเพื่อไม่ให้ O ชนะ |
| 165 | + | -- #1.1 ย้ายมาตรวจหลังจากปัดเศษแล้ว |
| 166 | if move > 9 or move < 1 then | |
| 167 | goto chooseAgain | |
| 168 | - | if cO == 0 then --ถ้าไม่มี O ในแถวนั้น (แปลว่ามีโอกาสที่ X จะชนะในแถวนั้น) |
| 168 | + | |
| 169 | - | SX = SX + cX --เก็บค่าจำนวนรวมของ X ในแต่ละแถว |
| 169 | + | --########## |
| 170 | end | |
| 171 | if O:contains(move) or X:contains(move) then | |
| 172 | goto chooseAgain | |
| 173 | end | |
| 174 | O:append(move) | |
| 175 | displayOX() | |
| 176 | if checkWin(O) then | |
| 177 | print("O win")
| |
| 178 | break | |
| 179 | - | local validMove = true |
| 179 | + | |
| 180 | if #O + #X == 9 then | |
| 181 | print("Draw")
| |
| 182 | break | |
| 183 | end | |
| 184 | X:append(ai()) | |
| 185 | displayOX() | |
| 186 | if checkWin(X) then | |
| 187 | print("X win")
| |
| 188 | break | |
| 189 | end | |
| 190 | - | --else |
| 190 | + |