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