新手立體四子棋AI教程(1)——基礎掃盲

1、引言

最近身邊好幾個朋友開始玩立體四子棋,激起了個人好奇心。那麼首先來講什麼是【立體四子棋】,規則又是如何呢?html

上圖即爲立體四子棋,規則相似於五子棋四子連在一塊兒,可是四子棋更加多樣、豐富。不只能夠在平面內橫豎斜四子連在一塊兒,還能夠在不一樣平面內四子斜着連在一塊兒、同一根柱子上四子連在一塊兒,可謂十分有趣。算法

2、規則介紹

那麼咱們用科學的方法總結一下規則:網絡

狀況一:xy平面內橫豎斜四子連成ide

上圖中的三種狀況,能夠推廣到任意z平面函數

狀況二:立體中四子斜着連成優化

以上狀況一樣能夠在同一x軸上、同一y軸上成立spa

狀況三:.net

這種狀況在任意位置均成立,即z軸上四子連成翻譯

3、設計思路

鑑於立體四子棋脫胎於五子棋,那麼咱們能夠在傳統五子棋的AI算法上進行咱們的創做。首先對於棋盤來講,五子棋可下的位置一共有15*15=225個,立體四子棋可下的位置有4*4*4=64個,而且因爲空間限制,任何一步內的可下位置不會超過16個。這是由於不能違揹物理限制,落子時棋子的下方必須不爲空。設計

那麼咱們能夠簡單的創建一個int[4][4][4]來表示當前棋盤狀態,此處不須要過多解釋。對於棋盤的各類狀態,咱們能夠經過一個enum chessPicesStatus{empty, black, white}來描述一個位置的狀況。一樣咱們創建一個棋子位置的結構體來提供更多的信息:(value的做用後續會在提到)

enum chessPicesStatus{empty, black, white}; struct PicesPos{ int x; int y; int z; chessPicesStatus type; int value; };

 

說完這些,咱們再來聊聊真正的算法部分。立體四子棋和其餘棋類如五子棋、圍棋、國際象棋同樣,均爲【零和博弈】,如下爲維基百科中的定義

在零和屬性(若是一方得益,另外一方必然損失)下,是指結果是零和的狀況下會出現帕累托最優的現象。反過來講,全體參加者可得益或受損的狀況被稱爲非零和博弈。若是一個國家利用其過剩的香蕉與另外一國家剩餘的蘋果進行貿易,由於兩方都從交易中受惠,這是一個非零和的例子。

這個概念最先是在博弈論(game theory)上發展,所以零和狀況一般被稱爲零和遊戲(zero-sum game)。

翻譯一下,棋類零和遊戲必須知足的幾點:

  1. 整個棋盤必須是透明的,即遊戲雙方均可以瞭解到棋盤全部的信息
  2. 雙方輪流出棋
  3. 在規則下一定出現一方勝利,一方失敗的局面,或者產生和棋

目前在這類博弈中算法大部分已經至關成熟,基本都採用暴力搜索,模擬將來n步走子產生最優位置。可是鑑於棋類巨大的可能性,模擬出將來的每種可能性是不現實的,所以,模擬出儘量多的步數是你們努力的目標。舉個例子,著名圍棋Ai Alpha Go,核心的地方在於在突破傳統的蒙特卡洛搜索樹,採用預先訓練好的【價值評估網絡】和【走子網絡】兩個神經網絡來評估,縮小了每層的搜索量,而且配以Google的TPU,實現超越人類棋手。

在咱們此次的程序中,將採用極值(極大極小)算法,以及啓發式搜索,並配以alpha-beta剪枝來優化搜索量。鑑於立體四子棋每層16個位置的搜索,咱們暫時還用不到蒙特卡洛搜索樹和神經網絡。

 

4、基本功能

好了,既然理論知識已經掃盲完成(沒明白也不要緊,後續還會再詳細介紹),咱們快速構建一下以後會用到的各個功能。

首先定義chessBoard類,提供以下方法:

 

typedef std::vector<PicesPos> PicesPosList; class ChessBoard { public: int chessBoard[4][4][4]; void init(); bool insertPices(int x,int y,int type); void printBoard(); PicesPosList getAvailablePos(int board[4][4][4],chessPicesStatus side); }

 

接着咱們來依次實現:

首先是初始化,鑑於將來咱們會重複使用棋盤,這個方法仍是有必定必要性的。

 

void ChessBoard::init() { for(int z = 0;z < 4;z++) { for(int y = 0;y < 4;y++) { for(int x = 0;x < 4;x++) { chessBoard[x][y][z] = chessPicesStatus::empty; } } } }

 

 很簡單,而後咱們實現落子的方法。在此須要保證幾點:

  1. 這個位置是存在的
  2. 這個位置並無落滿
  3. 插入中不能出現懸空的狀態
bool ChessBoard::insertPices(int x, int y, int type) { if(x < 0 || x >3 || y < 0 || y > 3) return false; //check 0-3th floor
    for(int z = 0;z < 4;z++) { if(chessBoard[x][y][z] == chessPicesStatus::empty) { chessBoard[x][y][z] = chessPicesStatus(type); return true; } } return false; }

 

一樣沒什麼難度,接着咱們來看打印函數,這個函數只是讓咱們能方便的看到棋盤狀況,真正項目中還須要用更友好的UI來替代。

void ChessBoard::printBoard() { for(int z = 0;z < 4;z++) { cout<<"The "<<z<<"th floor"<<endl; for(int y = 0;y < 4;y++) { for(int x = 0;x < 4;x++) { cout<<chessBoard[x][y][z]<<" "; } cout<<endl; } cout<<"\\\\\\\\\\\\\\"<<endl; } }

 

打印效果以下:

The 0th floor 0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0 \\\\\\\ The 1th floor 0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0 \\\\\\\ The 2th floor 0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0 \\\\\\\ The 3th floor 0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0 \\\\\\\

 

最後咱們來看下稍有難度的獲取當前可下位置的方法,其實也很簡單:

PicesPosList ChessBoard::getAvailablePos(int board[4][4][4], chessPicesStatus side) { PicesPosList availablePos; for(int y = 0;y < 4;y++) { for(int x = 0;x < 4;x++) { for(int z = 0; z < 4;z++) { if(board[x][y][z] == chessPicesStatus::empty) { PicesPos pos; pos.x = x; pos.y = y; pos.z = z; pos.type = chessPicesStatus::empty; pos.value = getPosValue(board,&pos,side); availablePos.push_back(pos); break; } } } } return availablePos; }

 

基本就是對於每一個x,y,從下往上找可用位置,找到後退出。其中side參數和getPosValue(board,&pos,side)是咱們以後會提到的啓發式搜索所用到的價值評估函數,能夠先行略過。

至此,咱們已經構建了一個基本的棋盤,配上簡單的io交互就能夠在這上面下棋了!

在以後的幾篇文章,我會講到勝負判斷、當前局勢分評估、搜索樹的構建,剪枝算法優化,啓發式搜索等內容,敬請期待!

 

參考博文:

http://blog.csdn.net/lihongxun945/article/details/50625267

https://www.cnblogs.com/pangxiaodong/archive/2011/05/26/2058864.html

https://www.zhihu.com/question/27221568

致謝!

相關文章
相關標籤/搜索