[深度学习]实现一个博弈型的AI,从五子棋开始(1)
好久沒有寫過博客了,多久,大概8年???最近重新把寫作這事兒撿起來……最近在折騰AI,寫個AI相關(guān)的給團(tuán)隊的小伙伴們看吧。
?
搞了這么多年的機(jī)器學(xué)習(xí),從分類到聚類,從樸素貝葉斯到SVM,從神經(jīng)網(wǎng)絡(luò)到深度學(xué)習(xí),各種神秘的項目里用了無數(shù)次,但是感覺干的各種事情離我們生活還是太遠(yuǎn)了。最近AlphaGo Zero的發(fā)布,深度學(xué)習(xí)又火了一把,小伙伴們按捺不住內(nèi)心的躁動,要搞一個游戲AI,好吧,那就從規(guī)則簡單、老少皆宜的五子棋開始講起。
?
好了,廢話就說這么多,下面進(jìn)入第一講,實現(xiàn)一個五子棋。
?
小伙伴:此處省去吐槽一萬字,說好的講深度學(xué)習(xí),怎么開始扯實現(xiàn)一個五子棋程序了,大哥你不按套路出牌啊……
我:工欲善其事必先利其器,要實現(xiàn)五子棋的AI,連棋都沒有,AI個錘子!
老羅:什么事?
……
?
五子棋分為有禁手和無禁手,我們先實現(xiàn)一個普通版本的無禁手版本作為例子,因為這個不影響我們實現(xiàn)一個AI。補(bǔ)充說明一下,無禁手黑棋必勝,經(jīng)過比賽和各種研究,人們逐漸知道了這個事實就開始想辦法來限制黑棋先手優(yōu)勢。于是出現(xiàn)了有禁手規(guī)則,規(guī)定黑棋不能下三三,四四和長連。但隨著比賽的結(jié)果的研究的繼續(xù)進(jìn)行,發(fā)現(xiàn)其實即使是對黑棋有禁手限制,還是不能阻止黑棋開局必勝的事實,像直指開局中花月,山月,云月,溪月,寒星等,斜指開局中的名月,浦月,恒星,峽月,嵐月都是黑棋必勝。于是日本人繼續(xù)提出了交換和換打的思想,到了后來發(fā)展成了國際比賽中三手交換和五手二打規(guī)則,防止執(zhí)黑者下出必勝開局或者在第五手下出必勝打。所以結(jié)論是,在不正規(guī)的比賽規(guī)則或者無禁手情況下,黑棋必勝是存在的。
?
(1)五子棋下棋邏輯實現(xiàn)
這里用Python來實現(xiàn),因為之后的機(jī)器學(xué)習(xí)庫也是Python的,方便一點。
界面和邏輯要分開,解耦合,這個是毋庸置疑的,并且之后還要訓(xùn)練AI,分離這是必須的。所以我們先來實現(xiàn)一個五子棋的邏輯。
我們先來考慮五子棋是一個15*15的棋盤,棋盤上的每一個交叉點(或格子)上一共會有3種狀態(tài):空白、黑棋、白棋,所以先建個文件?consts.py
做如下定義:
from enum import EnumN = 15class ChessboardState(Enum):EMPTY = 0BLACK = 1WHITE = 2?
?
棋盤的狀態(tài),我們先用一個15*15的二維數(shù)組chessMap來表示,建一個類 gobang.py
currentI、currentJ、currentState 分別表示當(dāng)前這步著棋的坐標(biāo)和顏色,再定義一個get和set函數(shù),最基本的框架就出來了,代碼如下:
from enum import Enum from consts import *class GoBang(object):def __init__(self):self.__chessMap = [[ChessboardState.EMPTY for j in range(N)] for i in range(N)]self.__currentI = -1self.__currentJ = -1self.__currentState = ChessboardState.EMPTYdef get_chessMap(self):return self.__chessMapdef get_chessboard_state(self, i, j):return self.__chessMap[i][j]def set_chessboard_state(self, i, j, state):self.__chessMap[i][j] = stateself.__currentI = iself.__currentJ = jself.__currentState = state?
?
這樣界面端可以調(diào)用get函數(shù)來獲取各個格子的狀態(tài)來決定是否繪制棋子,以及繪制什么樣的棋子;每次下棋的時候呢,在對應(yīng)的格子上,通過坐標(biāo)來設(shè)置棋盤Map的狀態(tài)。
所以最基本的展示和下棋,上面的邏輯就夠了,接下來干什么呢,得考慮每次下棋之后,set了對應(yīng)格子的狀態(tài),是不是需要判斷當(dāng)前有沒有獲勝。所以還需要再加兩個函數(shù)來干這個事情,思路就是從當(dāng)前位置從東、南、西、北、東南、西南、西北、東北8個方向,4根軸,看是否有連續(xù)的大于5顆相同顏色的棋子出現(xiàn)。假設(shè)我們目前落子在棋盤正中,需要判斷的位置如下圖所示的米字形。
?
?
那代碼怎么寫呢,最最笨的辦法,按照字面意思來翻譯咯,比如橫軸,先看當(dāng)前位置左邊有多少顆連續(xù)同色的,再看右邊有多少顆連續(xù)同色的,左邊加右邊,就是當(dāng)前橫軸上的連續(xù)數(shù),如果大于5,則勝利。
def have_five(self, current_i, current_j):#四個方向計數(shù) 豎 橫 左斜 右斜hcount = 1temp = ChessboardState.EMPTY#H-左for j in range(current_j - 1, -1, -1): #橫向往左 from (current_j - 1) to 0temp = self.__chessMap[current_i][j]if temp == ChessboardState.EMPTY or temp != self.__currentState:breakhcount = hcount + 1#H-右for j in range(current_j + 1, N): #橫向往右 from (current_j + 1) to Ntemp = self.__chessMap[current_i][j]if temp == ChessboardState.EMPTY or temp != self.__currentState:breakhcount = hcount + 1
#H-結(jié)果if hcount >= 5:return True
?
?
以此類推,再看豎軸、再看左斜、再看右斜。于是,have_five函數(shù)變成這樣了:
def have_five(self, current_i, current_j):#四個方向計數(shù) 橫 豎 左斜 右斜hcount = 1vcount = 1lbhcount = 1rbhcount = 1temp = ChessboardState.EMPTY#H-左for j in range(current_j - 1, -1, -1): #橫向往左 from (current_j - 1) to 0temp = self.__chessMap[current_i][j]if temp == ChessboardState.EMPTY or temp != self.__currentState:breakhcount = hcount + 1#H-右for j in range(current_j + 1, N): #橫向往右 from (current_j + 1) to Ntemp = self.__chessMap[current_i][j]if temp == ChessboardState.EMPTY or temp != self.__currentState:breakhcount = hcount + 1#H-結(jié)果if hcount >= 5:return True#V-上for i in range(current_i - 1, -1, -1): # from (current_i - 1) to 0temp = self.__chessMap[i][current_j]if temp == ChessboardState.EMPTY or temp != self.__currentState:breakvcount = vcount + 1#V-下for i in range(current_i + 1, N): # from (current_i + 1) to Ntemp = self.__chessMap[i][current_j]if temp == ChessboardState.EMPTY or temp != self.__currentState:breakvcount = vcount + 1#V-結(jié)果if vcount >= 5:return True
#LB-上for i, j in zip(range(current_i - 1, -1, -1), range(current_j - 1, -1, -1)): temp = self.__chessMap[i][j]if temp == ChessboardState.EMPTY or temp != self.__currentState:breaklbhcount = lbhcount + 1#LB-下for i, j in zip(range(current_i + 1, N), range(current_j + 1, N)): temp = self.__chessMap[i][j]if temp == ChessboardState.EMPTY or temp != self.__currentState:breaklbhcount = lbhcount + 1#LB-結(jié)果if lbhcount >= 5:return True
#RB-上for i, j in zip(range(current_i - 1, -1, -1), range(current_j + 1, N)): temp = self.__chessMap[i][j]if temp == ChessboardState.EMPTY or temp != self.__currentState:breakrbhcount = rbhcount + 1#RB-下for i, j in zip(range(current_i + 1, N), range(current_j - 1, -1, -1)): temp = self.__chessMap[i][j]if temp == ChessboardState.EMPTY or temp != self.__currentState:breakrbhcount = rbhcount + 1#LB-結(jié)果if rbhcount >= 5:return True
?
?
這樣是不是就寫完了,五子棋的邏輯全部實現(xiàn)~?
NO,別高興得太早,我想說,我好惡心,上面那個代碼,簡直丑爆了,再看一眼,重復(fù)的寫了這么多for,這么多if,這么多重復(fù)的代碼塊,讓我先去吐會兒……
好了,想想辦法怎么改,至少分了4根軸,是重復(fù)的對不對,然后每根軸分別從正負(fù)兩個方向去統(tǒng)計,最后加起來,兩個方向,也是重復(fù)的對不對。
于是我們能不能只寫一個方向的代碼,分別調(diào)2次,然后4根軸,分別再調(diào)4次,2*4=8,一共8行代碼搞定試試。
因為有45°和135°這兩根斜軸的存在,所以方向上應(yīng)該分別從x和y兩個軸來控制正負(fù),于是可以這樣,先寫一個函數(shù),按照方向來統(tǒng)計:
xdirection=0,ydirection=1 ? ? ? 表示從y軸正向數(shù);
xdirection=0,ydirection=-1 ? ??表示從y軸負(fù)向數(shù);
xdirection=1,ydirection=1 ? ? ? 表示從45°斜軸正向數(shù);
……
不一一列舉了,再加上邊界條件的判斷,于是有了以下函數(shù):
def count_on_direction(self, i, j, xdirection, ydirection, color):count = 0for step in range(1, 5): #除當(dāng)前位置外,朝對應(yīng)方向再看4步if xdirection != 0 and (j + xdirection * step < 0 or j + xdirection * step >= N):breakif ydirection != 0 and (i + ydirection * step < 0 or i + ydirection * step >= N):breakif self.__chessMap[i + ydirection * step][j + xdirection * step] == color:count += 1else:breakreturn count?
?
于是乎,前面的have_five稍微長的好看了一點,可以變成這樣:
def have_five(self, i, j, color):#四個方向計數(shù) 橫 豎 左斜 右斜hcount = 1vcount = 1lbhcount = 1rbhcount = 1hcount += self.count_on_direction(i, j, -1, 0, color)hcount += self.count_on_direction(i, j, 1, 0, color)if hcount >= 5:return Truevcount += self.count_on_direction(i, j, 0, -1, color)vcount += self.count_on_direction(i, j, 0, 1, color)if vcount >= 5:return Truelbhcount += self.count_on_direction(i, j, -1, 1, color)lbhcount += self.count_on_direction(i, j, 1, -1, color)if lbhcount >= 5:return Truerbhcount += self.count_on_direction(i, j, -1, -1, color)rbhcount += self.count_on_direction(i, j, 1, 1, color)if rbhcount >= 5:return True?
?
還是一大排重復(fù)的代碼呀,我還是覺得它丑啊,我真的不是處女座,但是這個函數(shù)是真丑啊,能不能讓它再帥一點,當(dāng)然可以,4個重復(fù)塊再收成一個函數(shù),循環(huán)調(diào)4次,是不是可以,好,就這么干,于是have_five就又漂亮了一點點:
def have_five(self, i, j, color):#四個方向計數(shù) 橫 豎 左斜 右斜directions = [[(-1, 0), (1, 0)], \[(0, -1), (0, 1)], \[(-1, 1), (1, -1)], \[(-1, -1), (1, 1)]]for axis in directions:axis_count = 1for (xdirection, ydirection) in axis:axis_count += self.count_on_direction(i, j, xdirection, ydirection, color)if axis_count >= 5:return Truereturn False?
?
嗯,感覺好多了,這下判斷是否有5顆相同顏色棋子的邏輯也有了,再加一個函數(shù)來給界面層返回結(jié)果,邏輯部分的代碼就差不多了:
def get_chess_result(self):if self.have_five(self.__currentI, self.__currentJ, self.__currentState):return self.__currentStateelse:return ChessboardState.EMPTY?
?
于是,五子棋邏輯代碼就寫完了,完整代碼 gobang.py 如下:
#coding:utf-8from enum import Enum from consts import *class GoBang(object):def __init__(self):self.__chessMap = [[ChessboardState.EMPTY for j in range(N)] for i in range(N)]self.__currentI = -1self.__currentJ = -1self.__currentState = ChessboardState.EMPTYdef get_chessMap(self):return self.__chessMapdef get_chessboard_state(self, i, j):return self.__chessMap[i][j]def set_chessboard_state(self, i, j, state):self.__chessMap[i][j] = stateself.__currentI = iself.__currentJ = jself.__currentState = statedef get_chess_result(self):if self.have_five(self.__currentI, self.__currentJ, self.__currentState):return self.__currentStateelse:return ChessboardState.EMPTYdef count_on_direction(self, i, j, xdirection, ydirection, color):count = 0for step in range(1, 5): #除當(dāng)前位置外,朝對應(yīng)方向再看4步if xdirection != 0 and (j + xdirection * step < 0 or j + xdirection * step >= N):breakif ydirection != 0 and (i + ydirection * step < 0 or i + ydirection * step >= N):breakif self.__chessMap[i + ydirection * step][j + xdirection * step] == color:count += 1else:breakreturn countdef have_five(self, i, j, color):#四個方向計數(shù) 橫 豎 左斜 右斜directions = [[(-1, 0), (1, 0)], \[(0, -1), (0, 1)], \[(-1, 1), (1, -1)], \[(-1, -1), (1, 1)]]for axis in directions:axis_count = 1for (xdirection, ydirection) in axis:axis_count += self.count_on_direction(i, j, xdirection, ydirection, color)if axis_count >= 5:return Truereturn False?
小伙伴:大哥,憋了半天,就憋出這么不到60行代碼?
我:代碼不在多,實現(xiàn)則靈……
?
明天來給它加個render,前端界面就有了,就是一個簡單的完整游戲了,至于AI,別急嘛。
好吧,就這樣…
?
UI部分在這里:
[深度學(xué)習(xí)]實現(xiàn)一個博弈型的AI,從五子棋開始(2)
轉(zhuǎn)載于:https://www.cnblogs.com/erwin/p/7828956.html
總結(jié)
以上是生活随笔為你收集整理的[深度学习]实现一个博弈型的AI,从五子棋开始(1)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。