創作內容

7 GP

【JavaScript】如何製作純網頁遊戲5(end)

作者:巨龍-埃特│2014-07-06 20:14:03│巴幣:14│人氣:3172


接續上篇
這邊主要探討五子棋AI的寫法

先從基本棋類遊戲的AI寫法來看
我們可以把棋盤上每個可下的棋格做為AI的行動選擇之一
而讓AI在所有行動中挑出一個最佳的選擇

可以用行動價值做判別
每個棋格都個別評估行動價值的高低
再從中取出最高的做為AI的動作

首先,我們要先決定AI的棋色
設定變數
var ai=2; //AI設為白棋

然後設定一個方法做為更換回合使用,以及一個方法處裡AI
//開始回合
//傳入值為下回合行動方
//1為黑棋,2為白棋
function startTurn(nextTurn){
turn=nextTurn;
//下回合為ai回合
if(turn==ai){
//處理AI
aiAction();
}
}
function aiAction(){
}


然後將上篇教學中的
putChess方法(下棋動作)
最後面的這段

//回合更換
turn=3-turn;

將他更改成

//回合更換
startTurn(3-turn);


如此一來就能在玩家下棋後進行AI處理
接下來只要把重點放在aiAction()這個方法
我的思路是
再用一個方法回傳所有棋格評價值中的最大值
然後讓ai也進行和玩家點及棋格一樣的putChess動作

因此aiAction()會這樣寫
function aiAction(){
//取得評價最大的索引
var aiPickIndex=getMaxEvaluateIndex(turn);
//取得索引相對的棋格物件
var aiPickPiece=boardPiece.childNodes[aiPickIndex+18+Math.floor(aiPickIndex/15)*2];
//ai放棋
putChess(aiPickPiece,aiPickIndex);
}


再把getMaxEvaluateIndex的方法寫出來
//取得最大評價索引
function getMaxEvaluateIndex(){
//回傳index為1的棋格
return 1;
}


現在我們試試是否有動作


OK,確認出來的效果是你想要的後
我們繼續寫getMaxEvaluateIndex這個方法
不過在這之前
要先知道,如何取的一個陣列中最大值的索引
最大值也可能同時很多個索引

so
我的作法是
1.先取得最大值
2.從陣列中挑出所有和該最大值一樣索引
3.再從所有索引中隨機挑選一個做為行動

//取得最大評價索引
function getMaxEvaluateIndex(){
var evaluate=[];
for(var y=0; y<15; y++){
for(var x=0; x<15; x++){
//依序評價每個座標點
evaluate.push(evaluatePoint(x,y));
}
}
//取得最大值
var max = Math.max.apply(Math,evaluate);
//在等同最大值的評價中隨機取一索引
return selectRandomIndexByValue(evaluate,max);
}


selectRandomIndexByValue 的方法內容
//在同值的陣列成員中隨機取一索引
function selectRandomIndexByValue(arr,value){
var indexs=[];
for(var i=0;i<arr.length;i++){
if(arr==value){
indexs.push(i);
}
}
return indexs[Math.floor(Math.random()*indexs.length)];
}


再來我們先讓評價點evaluatePoint(x,y)方法一律回傳1

function evaluatePoint(x,y){
return 1;
}



確認沒問題,AI隨機從一樣的值中挑選索引後
接下來要處理到核心問題
到底,要以哪種思路來評斷一個棋格的價值?

回想教學2中
我們知道五子棋的贏棋規則是在
橫:,
直:,
斜1:,
斜2:,
等4種直線上,連續5個己方棋贏得勝利

那麼如果是以點的觀點來看
會分得更細如下
反橫:
正橫:
反直:
正直:
反橫+反直:
正橫+正直:
反橫+正直:
正橫+反直:
等8個方向

那麼,我的做法是
1.分別評估該點對每個方向的放棋價值
2.評估正方向對應反方向的放棋價值
3.加成總值
4.回傳總值


五子棋的技巧中
防禦面來看
我們知道要是讓對手形成活4就代表無法挽回,必定輸棋了

以黑棋為例,粗紅為估值點
活4在直線畫面上看起來會是
畫面:+●●●●+
資料:011110

死4則還有救
畫面:+●●●●○
資料:011112

因此我們知道
雖然對手活4已經無藥可救,但還是必須放棋,看看對手是否是n00b沒注意到
如果對手是死4也是必須放的,你必須要尊敬自己對手,認為他不會白癡到沒看到那條路
so
防禦活4優先度同於防禦死4

那麼換個方面想
如果今天是己方活4己方死4
不論哪個絕對都是優先選擇
因為無論下哪個最後結果都直接贏
因此我們可以得知敵我活死4的優先排序如下:
己方活死4>>>>>>敵方活死4

那麼接下來換成思考活3與死3
活3在敵我優先的方面就不同於活4
如果你不阻礙對手的活3
而讓活3變成活4
你幾乎就等於輸了這盤棋
但是
相對的
如果你發現自己有活3
絕對也是優先於發現對手的活3
因為只要完成活4
你就幾乎等於贏了這盤棋
至於死3則無論敵我,優先都低於活3
但己方死3稍大於敵方死3一些
只不過敵方死3暫時無必要進行阻擋,因為擋了意義不大

所以我們可以知道敵我活死3的優先是
(>Φ<符號代表依照AI設定攻擊防守優先高低有變化)
己方活3>>>>>>敵方活3>>>>>>己方死3>Φ<敵方活3

而活死2則沒必要做區分
但是我方的部分會依AI的攻擊防禦優先度設定做微調
我認為和活死2的優先高低是
(己方死3>己方活2)>Φ<(敵方活3>敵方活2>敵方死2)>我方死2

最後把所有估值加上活死1後,全體會變成
己方活死4>>>敵方活死4>>>己方活3>>>敵方活3>>>
(己方死3>己方活2)>Φ<(敵方活3>敵方活2>敵方死2)>
(我方活1>我方死2>我方死1)>Φ<(敵方活1>敵方死1)>
空棋格>已經放棋的棋格


ok
分析結束
接下來讓我們設計優先權吧
將其設計為常數
為求方便
因此設計兩個優先權常數陣列
引索值和評價值成正比
並且死後活前
0則為沒有價值

const defValues=[0,1,2,3,4,5,21,341,341];  //AI評價防守價值[沒價值,死1,活1,死2....死4,活4]
const atkValues=[0,1,3,2,4,5,85,1365,1365];  //AI評價進攻價值[沒價值,死1,活1,死2....死4,活4]

至於為什麼數值比這樣設計
恩...以後有機會再說明現在我們先繼續

最後我們設計一個常數代表攻防比重
//AI攻擊權重
//-1=積極防守
// 0=隨機進攻防守
// 1=積極進攻
var aiActive=0;

OK
一切都準備完成
now,回到Javascript
evaluatePoint的方法
我們要依序計算出每條線每個方向的攻擊/防禦權重後
再評估正反兩方向的總值做加成

Follow by me:
//評價座標點
function evaluatePoint(x,y){
//該點非空棋格
if(board[y*15+x] > 0){
//回傳-1權重
return -1;
}
//總評價值
var evaluateValue=0;
//評價橫←,→
evaluateValue+=evaluateLine(x,y,1,0);
//評價直↑,↓
evaluateValue+=evaluateLine(x,y,0,-1);
//評價左上斜↖,↘
evaluateValue+=evaluateLine(x,y,-1,-1);
//評價左下斜↙,↗
evaluateValue+=evaluateLine(x,y,1,-1);
//回傳評價
return evaluateValue;
}

//評價通過某點直線的攻防比重
function evaluateLine(x,y,dx,dy){
var atkValue=0;//攻擊權重
var defValue=0;//防禦權重
//正方向估值
atkValue+=evaluateVector(x,y,dx,dy,atkValues,turn);
defValue+=evaluateVector(x,y,dx,dy,defValues,3-turn);
//反方向估值
atkValue+=evaluateVector(x,y,dx*-1,dy*-1,atkValues,turn);
defValue+=evaluateVector(x,y,dx*-1,dy*-1,defValues,3-turn);
//總和評估
//如果攻擊權重大於防禦權重
if(atkValue>defValue){
//回傳攻擊權重
return atkValue;
}else{
//回傳防禦權重
return defValue;
}
}

//向量權重
function evaluateVector(x,y,dx,dy,evaluateValues,evaluateType){
//權重初始為0
var valueIndex=0;
//一路判定原點往外整條線共4格
for(var i=0;i<4;i++){
//切換到下一點連線
x+=dx;
y+=dy;
//超出棋盤範圍則回傳該向量沒價值
if(!valid(x,y)){
return evaluateValues[0];
}
//該連線上為判定棋則權重+2
if(board[y*15+x] == evaluateType){
valueIndex+=2;
//若不是判定棋
}else{
//是判定棋的對手 且 權重索引指向活1以上
if(board[y*15+x]==3-evaluateType && valueIndex>1){
//權重-1指向死路
valueIndex-=1;
}
//離開迴圈
break;
}
}
//回傳權重相對索引
return evaluateValues[valueIndex];
}


OK,now let's test


先別問除錯Log怎麼做
待會有興趣可以自行看原始碼
首先,我們發現ai第一步棋下再棋盤索引66(x=6,y=4)
權重為2(對造上面解讀為防守黑棋的活1)

第二步棋下再索引82(x=7,y=5)
權重7(對造上面無法做直覺解讀,因為這是3條線的加總)
分別為(防守2個黑棋的活1)+(補強自己的1個活1)=>2*2+3*1=7

接著下到第5步棋時,權重值突然從個為數變成十位數
因為黑棋已經完成一個活3(權重21),白棋必須阻擋否則會輸棋

第6步,黑棋繼續進攻,下了死4
白棋必須阻擋,因此權重達到341

這個時候你想想看,要是白棋剛好發現下某點,4條線都會成為活4
那權重將會是85*4=340
而防守1個死4的權重就剛好高於完成4個活4的值1點(341>340)
這麼一來應該可以知道為何之前的權重常數要這樣設定了吧
雖然這樣的情形幾乎不太可能,但還是保守起見稍微防止一下


由圖可知一點最多就是4條線(藍、橘、紅、直)的估值

但是......還有一個小問題沒處裡
看下圖的狀況

圖中,白棋下了索引80(x=5,y=5)
權重為6(補強兩個活1,因為補強活1權重比防禦活1大,所以防禦活1不計算)
但如過這樣下的話得到的應該是2個死2(2)才對啊?
而且黑棋已經在上一步中完成了活3,為什麼最大不是活3權重
因為......



只有加總正負向量的值是不夠的
還必須對兩向量的連線數進一步補正(也就是中空狀況)

因此,先看上面的問題
我寫了這個打算做為判斷正確的活死數用
//正反兩路結合條件
const combineIf=[
[[1,1],[3,3],[5,5],[7,7]],//雙死沒價值
[[0,1]],//沒價值+死1=死1
[[0,2]],//沒價值+活1=活1
[[0,3],[1,2]],//沒價值+死2or死1+死1=死2
[[0,4],[2,2]],//沒價值+活2or活1+活1=活2
[[0,5],[1,4],[2,3]],//沒價值+死3or死1+活2or死2+活1=死3
[[0,6],[2,4]],//沒價值+活3or活1+活2=活3
[[0,7],[1,6],[3,4],[5,2]],//沒價值+死4or死1+活3or死2+活2or死3+活1=死4
[[0,8],[2,6],[4,4]],//沒價值+活4or活1+活3or活2+活2=活4
];

只是這樣讓人感覺有點土法煉鋼
有沒有更好的辦法?

答案是有的
觀察上圖的條件

可得知下面規則
(正向索引數+反向索引數)(不是兩個都基數)=正確索引

以及
(正向索引數 && 反向索引數)(兩個都基數)=雙死

所以解決第一個問題就變簡單了
現在我們來修改evaluateLine的方法內容
改成以下
//評價通過某點直線的攻防比重
function evaluateLine(x,y,dx,dy){
var atkValuePosIndex=0;//正向攻擊權重索引
var atkValueNegIndex=0;//反向攻擊權重索引
var defValuePosIndex=0;//正向防禦權重索引
var defValueNegIndex=0;//反向防禦權重索引
var atkValueAll=0;//攻擊權重總和
var defValueAll=0;//防禦權重總和
//正方向估值索引
atkValuePosIndex=evaluateVector(x,y,dx,dy,turn);
defValuePosIndex=evaluateVector(x,y,dx,dy,3-turn);
//反方向估值索引
atkValueNegIndex=evaluateVector(x,y,dx*-1,dy*-1,turn);
defValueNegIndex=evaluateVector(x,y,dx*-1,dy*-1,3-turn);
//攻擊總和評估
//兩個都基數為雙死
if(atkValuePosIndex%2==1 && atkValueNegIndex%2==1){
atkValueAll=0;
//其餘
}else{
atkValueAll=atkValues[atkValuePosIndex+atkValueNegIndex];
}
//防禦總和評估
//兩個都基數為雙死
if(defValuePosIndex%2==1 && defValueNegIndex%2==1){
defValueAll=0;
//其餘
}else{
defValueAll=defValues[defValuePosIndex+defValueNegIndex];
}
//如果攻擊權重大於防禦權重
if(atkValueAll>defValueAll){
//回傳攻擊權重
return atkValueAll;
}else{
//回傳防禦權重
return defValueAll;
}
}

然後evaluateVector方法只改成回傳索引值
function evaluateVector(x,y,dx,dy,evaluateType){
//權重初始為0
var valueIndex=0;
//一路判定原點往外整條線共4格
for(var i=0;i<4;i++){
//切換到下一點連線
x+=dx;
y+=dy;
//超出棋盤範圍則回傳該向量沒價值
if(!valid(x,y)){
return 0;
}
//該連線上為判定棋則權重+2
if(board[y*15+x] == evaluateType){
valueIndex+=2;
//若不是判定棋
}else{
//是判定棋的對手 且 權重索引指向活1以上
if(board[y*15+x]==3-evaluateType && valueIndex>1){
//權重-1指向死路
valueIndex-=1;
}
//離開迴圈
break;
}
}
//回傳權重相對索引
return valueIndex;
}


so 成功了嗎?


答案是肯定的
那麼本教學到此結束
你已經寫出了以個基本的五子棋AI
當然還有更多方法
像是讀入五子棋高手的棋譜來強化AI
但本教學不會講到那麼深
畢竟這是編成技巧教學不是五子棋技巧教學啊

好啦,完解散花
所以,編成難嗎?
是的,非常難。
而且我討厭巴哈的小屋文章系統
因為不能Tab導致我程式碼看起來很亂

have a nice night,and good bye

喔對了
評論開放投票下次的教學主題
有興趣就流個言吧
也許會選你的Idea來做

原碼按此下載
線上玩
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=2502193
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:程式|編成|html|javascript|五子棋|AI設計

留言共 8 篇留言

深海異音
如果是使用Eclipse的話,可以把程式碼貼到Word,然後再把【一個空白】取代成【2個空白】就能貼到巴哈了。
(不取代直接貼到巴哈的話會出問題)

07-06 20:44

巨龍-埃特
確實.Eclipse是初學者的救星
回想以前大二學java是被老師逼著用記事本一個字一個字打的07-06 20:49
深海異音
漏打[e8],是可以把有縮排&顏色的程式碼貼到巴哈

07-06 20:56

巨龍-埃特
可是用記事本打似乎沒有顏色呢 只有Tab
當然用Google瀏覽器附帶的js控制台 是可以看顏色啦07-06 20:59
隱弩弦音
http://www.et99.net/topic-t60197.html

07-06 21:07

巨龍-埃特
看不懂是甚麼 我不教網遊07-06 21:12
巨龍-埃特
感覺好像在某K島看過...你該不會是07-06 21:14
隱弩弦音
棋子這東西是死的 你應該做個資料庫
記下所有的走法 這樣人類就不可能贏過'資料庫'了

07-06 21:17

巨龍-埃特
這是個好主意沒錯 但這樣就沒有研究AI思路的必要了
本教學只是討論AI思路07-06 21:23
巨龍-埃特
隨便問問 你有聽過學飛之前要先學走嗎?07-06 22:08
Hua
資料庫是個好方法, 但代價太大.

07-06 22:40

巨龍-埃特
而且如果不做正確過濾 難以避免垃圾資訊被存入07-07 07:21
巨龍-埃特
而過濾判斷到頭來還是AI估值07-07 07:22
隱弩弦音
會游泳的兔子,會唱歌的鴨子,會跑步的小鳥。

07-07 13:22

Hua
也許貼在外部的blogger上, 然後再放連結到巴哈, 這樣就可以避開程式排版的問題了.

07-07 13:45

巨龍-埃特
恩...不知有沒有推薦功能簡單不花俏的 張貼類似這類解說文容易的blogger呢07-07 22:59
霧窩
請問 線上玩的連結掛了 可以補嗎

06-20 20:27

巨龍-埃特
https://www.dropbox.com/s/ohn8d55zlhd0bmu/Game.rar?dl=006-20 20:32
我要留言提醒:您尚未登入,請先登入再留言

7喜歡★a0878787878 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:【JavaScript】... 後一篇:【神寶幻想】安迪的三種型...

追蹤私訊切換新版閱覽

作品資料夾

lin881205大家
小屋不定期更新冷門西洋歌曲推廣與Reddit鬼故事翻譯唷!看更多我要大聲說昨天21:34


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】