吴昊品游戏核心算法 Round 5 ——(转载)关于无禁手下先手必胜的证明
關(guān)于五子棋先手必勝的證明,用人工的方式過于復(fù)雜,其難度相當(dāng)于證明四色定理的正確性或者是若兒當(dāng)定理的正確性。但是,如果采用計(jì)算機(jī)來解決,則復(fù)雜程度 會(huì)降低許多。由于很難地毯式地枚舉到所有可能的情形,這一款五子棋終結(jié)者的算法(最新的應(yīng)該是1.22版本,網(wǎng)絡(luò)上流傳的2.0和4.0版都是子虛烏有) 幾乎可以達(dá)到先手必勝了。作者甚至開出了懸賞92萬美元的獎(jiǎng)勵(lì),就是說如果你用他的程序戰(zhàn)勝了執(zhí)黑的他,他就會(huì)給你這么多錢作為補(bǔ)償,但是,到目前為止該 算法還是幾乎可以實(shí)現(xiàn)先手必勝的!
?
?? 五子棋終結(jié)者算法 ???????
??
?? 任何一種棋類游戲其關(guān)鍵是對(duì)當(dāng)前棋局是否有正確的評(píng)分,評(píng)分越準(zhǔn)確則電腦的AI越高。五子棋游戲也是
?
如此,但在打分之前,我們先掃描整個(gè)棋盤,把每個(gè)空位從八個(gè)方向上的棋型填入數(shù)組gStyle(2, 15, 15,?
?
8, 2),其中第一個(gè)下標(biāo)為1時(shí)表示黑棋,為2時(shí)表示白棋,第二和第三個(gè)下標(biāo)表示(x,y),第四個(gè)下標(biāo)表示8個(gè)
?
方向,最后一個(gè)下標(biāo)為1時(shí)表示棋子數(shù),為2時(shí)表示空格數(shù),如:
?
gStyle(1,2,2,1,1)=3表示與坐標(biāo)(2,2)在第1個(gè)方向上相鄰的黑棋棋子數(shù)為3?
gstyle(1,2,2,1,2)=4表示與坐標(biāo)(2,2)在第1個(gè)方向上的最近的空格數(shù)為4?
在定義方向時(shí),也應(yīng)該注意一定的技巧,表示兩個(gè)相反的方向的數(shù)應(yīng)該差4,在程序中我是這樣定義的:?
Const DIR_UP = 1?
Const DIR_UPRIGHT = 2?
Const DIR_RIGHT = 3?
Const DIR_RIGHTDOWN = 4?
Const DIR_DOWN = 5?
Const DIR_DOWNLEFT = 6?
Const DIR_LEFT = 7?
Const DIR_LEFTUP = 8?
這樣我們前四個(gè)方向可以通過加四得到另一個(gè)方向的值。請(qǐng)看下面的圖:?
---------?
---------?
---oo----?
-ox*xx---?
---------?
---------?
圖中的*點(diǎn)從標(biāo)為(4,4),(打*的位置是空位),則:?
gStyle(2,4,4,1,1)=1在(4,4)點(diǎn)相鄰的上方白棋數(shù)為1?
gStyle(2,4,4,1,2)=2在(4,4)點(diǎn)的上方距上方白棋最近的空格數(shù)為2?
gStyle(1,4,4,3,1)=2在(4,4)點(diǎn)相鄰的右方黑棋數(shù)為2?
gStyle(1,4,4,3,2)=1在(4,4)點(diǎn)的右方距右方黑棋最近的空格數(shù)為3?
...
?
一旦把所有空點(diǎn)的棋型值填完,我們很容易地得出黑棋水平方向上點(diǎn)(4,4)的價(jià)值,由一個(gè)沖1(我把有
?
界的棋稱為沖)和活2(兩邊無界的?
棋稱為活)組成的。對(duì)于而白棋在垂直方向上點(diǎn)(4,4)的價(jià)值是一個(gè)活1,而在/方向也是活1所以,只要我們
?
把該點(diǎn)的對(duì)于黑棋和白棋的價(jià)值算出?
來,然后我們就取棋盤上各個(gè)空點(diǎn)的這兩個(gè)值的和的最大一點(diǎn)作為下棋的點(diǎn)。然而,對(duì)各種棋型應(yīng)該取什么
?
值呢?我們可以先作如下假設(shè):?
Fn 表示先手n個(gè)棋子的活棋型,如:F4表示先手活四?
Fn'表示先手n個(gè)棋子的沖棋型,如:F4'表示先手沖四?
Ln 表示后手n個(gè)棋子的活棋型,如:L3表示后手活三?
Ln'表示后手n個(gè)棋子的沖棋型,如:L3'表示后手沖三?
.?
. .?
根據(jù)在一行中的棋型分析,得到如下關(guān)系:?
L1'<=F1'<L2'<=F2'<=L1<F1<L2<F2<L3'<=F3'<L4'<F4'=F4從這個(gè)關(guān)系包含了進(jìn)攻和防守的關(guān)系(當(dāng)然,這個(gè)
?
關(guān)系是由我定的,你可以自己定義這些關(guān)系)。對(duì)這些關(guān)系再進(jìn)一步細(xì)化,如在一個(gè)可下棋的點(diǎn),其四個(gè)方
?
向上都有活三,也比不上一個(gè)沖四,所以我們可以又得到4*F3<L4'這個(gè)關(guān)系,同樣,我們還可以得到其它的
?
關(guān)系,如:4*F2<L3、4*L3<F3...,這些的關(guān)系由于你的定法和我的定法制可能不一樣,這樣計(jì)算機(jī)的AI也就
?
不一樣,最后我們把分值最小的L1'值定為1,則我們就得到了下面各種棋型的分值,由C語言表示為:?
F[2][5]={{0,2,5,50,16000},{0,10,30,750,16000}};?
L[2][5]={{0,1,5,50,3750},{0,10,30,150,4000}};?
F數(shù)組表示先手,第一個(gè)下標(biāo)為0時(shí)表示沖型,第二個(gè)下標(biāo)表示棋子數(shù),則F2'對(duì)應(yīng)F[0][2]L數(shù)組表示后手
?
,第一個(gè)下標(biāo)為0時(shí)表示沖型,第二個(gè)下標(biāo)表示棋子數(shù),則L2對(duì)應(yīng)F[1][2]Ok,棋型的分值關(guān)系確定好了以后
?
,我們把每一個(gè)可下點(diǎn)的四個(gè)方向的棋型值相加(包括先手和后手的分值),最后選擇一個(gè)最大值,并把這
?
一點(diǎn)作為計(jì)算機(jī)要下的點(diǎn)就OK了:)。
?
后話:?
1、得到最大值也許不止一個(gè)點(diǎn),但在我的程序中只選擇第一個(gè)最大點(diǎn),當(dāng)然你可以用于個(gè)隨機(jī)數(shù)來決定?
選擇那一個(gè)最大值點(diǎn),也可以對(duì)這些最大值點(diǎn)再作進(jìn)一步的分析。?
2、在這個(gè)算法中我只考慮了周圍有棋子的點(diǎn),而其它點(diǎn)我沒有考慮。?
3、可以再更進(jìn)一步,用這個(gè)算法來預(yù)測(cè)以后的幾步棋,再選擇預(yù)測(cè)值最好的一步,這樣電腦的AI就更高了?
4、這個(gè)算法沒有考慮黑棋的禁手(雙3、雙四和多于五子的連棋)。因?yàn)樵谄綍r(shí)我下的五子棋是沒有這些?
禁手的
?
五子棋終結(jié)者的算法求解過程2008-11-14 15:23
終 結(jié)五子棋不是一個(gè)很難的問題,和普通的遍歷求解問題沒多大區(qū)別。只是計(jì)算量稍微大點(diǎn),設(shè)計(jì)的時(shí)候需要考慮系統(tǒng)性層次性逐步發(fā)展的觀點(diǎn) ,不可能三兩天之內(nèi)完成很精妙的算法。因此終結(jié)五子棋算不上卓有成效的工作,只是解了一個(gè)狀態(tài)空間稍微大點(diǎn)的遍歷題目而已,所需要的全部知識(shí)只是C語言、 二叉樹和對(duì)五子棋規(guī)則的了解,并不需要多么好的棋力才可以寫程序,只是將想法賦予機(jī)器。
五子棋終結(jié)者終結(jié)五子棋的計(jì)算引擎分為三層,上層調(diào)用下層,從上至下依次為
>>目標(biāo)過程層。
受限于計(jì)算機(jī)能力,五子棋終結(jié)者是不可能一次搜索就被全面終結(jié)的,而是不斷地從主要干支到次要枝葉到全面終結(jié)的一個(gè)目標(biāo)漸進(jìn)過程。此層引擎只是一個(gè)for循環(huán),逐步放大終結(jié)目標(biāo)的寬度,從5到10到30到50到225,當(dāng)?shù)?25時(shí),五子棋就被完全地終結(jié)了。
>>策略引擎層。
最佳優(yōu)先與或求解樹引擎。不要看到名字這么復(fù)雜,其實(shí)就是一個(gè)不斷擴(kuò)展發(fā)展M叉樹。讓最優(yōu)的棋子獲得CPU,棋子的優(yōu)先度根據(jù)下層的結(jié)果動(dòng)態(tài)計(jì)算調(diào)整,也稱作反饋,直到分支在當(dāng)前的目標(biāo)寬度下被終結(jié)。
>>VC攻擊引擎層。
VCF和VCT,簡(jiǎn)稱VC,也就是連續(xù)攻擊取勝引擎。求解速度和求解嚴(yán)格精確至關(guān)重要。
如何在0.01S內(nèi)進(jìn)行深度為幾十步的攻擊?計(jì)算攻防時(shí)黑與白之間的無關(guān)性以及各自的相關(guān)性。
無關(guān)性的考慮可以化解對(duì)方的隨意反攻,相關(guān)性的考慮可以使自己的進(jìn)攻關(guān)聯(lián),保持節(jié)奏和組織性,不至盲目,二者結(jié)合使自己的進(jìn)攻如行云流水,長驅(qū)直入,勢(shì)如破竹。
1.00 版本的vc設(shè)計(jì)是近似的,因?yàn)樵赩C中考慮了寬度估計(jì),而不是全面地推理。因此終結(jié)也是近似的,很容易打敗。1.20版本vc設(shè)計(jì)是嚴(yán)格的,但是程序中 “相鄰三子形成的連活三只需防守與兩邊的兩個(gè)空白位置”是一條有漏洞的攻防邏輯,因此1.20版本也在出來一個(gè)月后被打敗。1.22版本改正了1.20版 本的上述漏洞,只改動(dòng)了一個(gè)字符,也就是緊鄰活三的防守掩碼。以前我所認(rèn)為的很嚴(yán)格的推理邏輯在后來仍然被發(fā)現(xiàn)了意外漏洞的存在,因此對(duì)于1.22的VC 引擎,我也不知道是否還有邏輯漏洞存在,暫時(shí)沒人打敗1.22并不能說明漏洞不存在。
以上算法實(shí)現(xiàn)后,執(zhí)行終結(jié)命令,經(jīng)過半個(gè)月的連續(xù)計(jì)算后,會(huì)生成一個(gè)完整的地毯必勝樹,包含大約百萬個(gè)棋子節(jié)點(diǎn),樹的所有葉子都是可以VC求解的,如果VC求解引擎不存在漏洞,那么五子棋是必勝的。
終結(jié)者程序很小,算法部分的代碼只有一萬多行,編譯結(jié)果只有一百多K,而且運(yùn)行只需要幾M的內(nèi)存,必勝樹只有百來萬個(gè)節(jié)點(diǎn)。
下面是讀取必勝樹的代碼,你可以用下面的函數(shù)操作終結(jié)者資源里面的必勝樹:
?2?//int?key=1;//!!!!!!!
?3?//傳入當(dāng)前key和指向子孫的指針root,將root指向的樹生長完整
?4?//上層調(diào)用此函數(shù)之時(shí)key已經(jīng)指向[的下一個(gè)字符(,我的處理:
?5?//(--添加root的孩子
?6?//)--添加孩子完畢,可有可無
?7?//[--為孩子添加子孫
?8?//]--root樹生長完成
?9?//參數(shù)為長子的指針的指針和父親的指針
10?void?make(NODEOFTREE?**?root,NODEOFTREE?*parent)
11?{
12?/*?*root指向節(jié)點(diǎn),我們要給*root賦值?*/
13?int?rootOK=0;
14?NODEOFTREE?*rightC=NULL;?/*?rightC總是指向剛剛加入的兄弟?*/
15?NODEOFTREE?*rightT=NULL;?/*?rightT指向新開辟兄弟?*/
16?
17?if(book[key]!='(')
18?{
19????F5_PRINT(1,"?make?book[key]!='('?");
20????return;
21?}
22?
23?do
24?{???
25?????switch(book[key])
26?????{
27?case?'['?:?//fprintf(outcourse,"?開始生孩子...?\n");
28????????????????key++;
29????????????????make(&(rightC->down),rightC);
30????????????????break;
31?case?']'?:?//fprintf(outcourse,"?孩子完成...?\n");
32????????????????key++;
33????????????????return?;
34?
35??????????case?'('?:?/*?加一個(gè)兄弟?*/
36?????????????????
37?????????????????rightT=(NODEOFTREE?*)malloc(sizeof(NODEDATA));
38?????????????????if(rightT==NULL)
39?????????????????F5_PRINT(1,"?make?rightT==NULL");
40?????node_mum++;
41?????????????????memset(rightT,0,sizeof(NODEDATA));
42?????????????????rightT->data[0]=cover(book[key+1],1);
43?????????????????rightT->data[1]=cover(book[key+2],2);
44?????????????????if(key==1)
45?????????????????rightT->data[2]=1;/*第一個(gè)子是黑子*/
46?????????????????else
47?????????????????{/*和父親的顏色相反*/
48?????????????????if(parent->data[2]==1)rightT->data[2]=2;
49?????????????????if(parent->data[2]==2)rightT->data[2]=1;
50?????????????????}
51?????????????????rightT->right=NULL;
52?????????????????rightT->down=NULL;
53?????????????????
54?????????????????if(rightC!=NULL)
55?????????????????{
56?????????????????rightC->right=rightT;
57?????????????????}????????????????
58?????????????????rightC=rightT;
59?????????????????
60?????????????????
61?????????????????if(rootOK!=1)/*兄弟的一個(gè),即老大*/
62?????????????????{*root=rightT;?rootOK=1;
63?????????????????}
64?????????????????
65?????????????????key=key+3;
66?????????????????//?fprintf(outcourse,"加一個(gè)兄弟(%c-%c-%c)\n",rightC->data[0],rightC->data[1],rightC->data[2]);
67?????????????????
68?????????????????break;
69??????case?')'?:
70?????????????????key++;
71?????????????????break;
72?????????????????
73??????case?'\0':?//fprintf(outcourse,"不可能有此一字,在最后一個(gè)]的時(shí)候返回!\n");
74?????????????????break;??????
75?
76??????default?:?key++;
77?????????????????//fprintf(outcourse,"字符串中有錯(cuò)誤的字符!\n");
78?????????????????break;
79?}
80?}?while(book[key]!='\0');
81?return;
82?}
?
轉(zhuǎn)載于:https://www.cnblogs.com/tuanzang/archive/2013/02/27/2935772.html
總結(jié)
以上是生活随笔為你收集整理的吴昊品游戏核心算法 Round 5 ——(转载)关于无禁手下先手必胜的证明的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。