本文轉自:http://blog.csdn.net/u012723995/article/details/47143569
參考文獻:http://bbs.blueidea.com/thread-3047030-1-1.html
前言:
原文大神是用html5+js寫的關於象棋AI的博客,里面重點講了棋子的着法,自己設計的評估函數和簡單的Minmax理論,沒有具體的講搜索算法,本文是對原文的學習和分析補充
一,棋子的着法 com.bylaw ={} 首先創建一個數組,用於存儲該棋子處於某一點時所能走到着點
(1)車:
- com.bylaw.c = function (x,y,map,my){
- var d=[];
- //左側檢索 若存在棋子且顏色不同則push過去並結束循環,否則一步步push <span style="color:#ff0000;"> </span>
- for (var i=x-1; i>= 0; i--){
- if (map[y][i]) {
- if (com.mans[map[y][i]].my!=my) d.push([i,y]);
- break
- }else{
- d.push([i,y])
- }
- }
- //右側檢索
- for (var i=x+1; i <= 8; i++){
- if (map[y][i]) {
- if (com.mans[map[y][i]].my!=my) d.push([i,y]);
- break
- }else{
- d.push([i,y])
- }
- }
- //上檢索
- for (var i = y-1 ; i >= 0; i--){
- if (map[i][x]) {
- if (com.mans[map[i][x]].my!=my) d.push([x,i]);
- break
- }else{
- d.push([x,i])
- }
- }
- //下檢索
- for (var i = y+1 ; i<= 9; i++){
- if (map[i][x]) {
- if (com.mans[map[i][x]].my!=my) d.push([x,i]);
- break
- }else{
- d.push([x,i])
- }
- }
- return d;
- }
com.bylaw.c = function (x,y,map,my){ var d=[]; //左側檢索 若存在棋子且顏色不同則push過去並結束循環,否則一步步push <span style="color:#ff0000;"> </span> for (var i=x-1; i>= 0; i--){ if (map[y][i]) { if (com.mans[map[y][i]].my!=my) d.push([i,y]); break }else{ d.push([i,y]) } } //右側檢索 for (var i=x+1; i <= 8; i++){ if (map[y][i]) { if (com.mans[map[y][i]].my!=my) d.push([i,y]); break }else{ d.push([i,y]) } } //上檢索 for (var i = y-1 ; i >= 0; i--){ if (map[i][x]) { if (com.mans[map[i][x]].my!=my) d.push([x,i]); break }else{ d.push([x,i]) } } //下檢索 for (var i = y+1 ; i<= 9; i++){ if (map[i][x]) { if (com.mans[map[i][x]].my!=my) d.push([x,i]); break }else{ d.push([x,i]) } } return d; }
算法分析:
分別向上,下,左,右四個方向搜索,若找到一個點且顏色與該棋子不同(敵對棋子),則將該點坐標記錄在d數組中,若某一方向上沒有其他棋子,將這一方向上所有坐標都記錄在d數組中。簡單來講:就是將以車這個棋子為中心的十字上的坐標都記錄在d數組中(你早這樣說多好~,開始說那么多)
前提補充:
1,代碼中的map:
- com.initMap = [
- ['C0','M0','X0','S0','J0','S1','X1','M1','C1'],
- [ , , , , , , , , ],
- [ ,'P0', , , , , ,'P1', ],
- ['Z0', ,'Z1', ,'Z2', ,'Z3', ,'Z4'],
- [ , , , , , , , , ],
- [ , , , , , , , , ],
- ['z0', ,'z1', ,'z2', ,'z3', ,'z4'],
- [ ,'p0', , , , , ,'p1', ],
- [ , , , , , , , , ],
- ['c0','m0','x0','s0','j0','s1','x1','m1','c1']
- ];
com.initMap = [ ['C0','M0','X0','S0','J0','S1','X1','M1','C1'], [ , , , , , , , , ], [ ,'P0', , , , , ,'P1', ], ['Z0', ,'Z1', ,'Z2', ,'Z3', ,'Z4'], [ , , , , , , , , ], [ , , , , , , , , ], ['z0', ,'z1', ,'z2', ,'z3', ,'z4'], [ ,'p0', , , , , ,'p1', ], [ , , , , , , , , ], ['c0','m0','x0','s0','j0','s1','x1','m1','c1'] ];
這里的字符串代表每個棋子的key值:
- com.keys = { //設定每類棋子的key值
- "c0":"c","c1":"c",
- "m0":"m","m1":"m",
- "x0":"x","x1":"x",
- "s0":"s","s1":"s",
- "j0":"j",
- "p0":"p","p1":"p",
- "z0":"z","z1":"z","z2":"z","z3":"z","z4":"z","z5":"z",
- "C0":"C","C1":"C",
- "M0":"M","M1":"M",
- "X0":"X","X1":"X",
- "S0":"S","S1":"S",
- "J0":"J",
- "P0":"P","P1":"P",
- "Z0":"Z","Z1":"Z","Z2":"Z","Z3":"Z","Z4":"Z","Z5":"Z",
- }
com.keys = { //設定每類棋子的key值 "c0":"c","c1":"c", "m0":"m","m1":"m", "x0":"x","x1":"x", "s0":"s","s1":"s", "j0":"j", "p0":"p","p1":"p", "z0":"z","z1":"z","z2":"z","z3":"z","z4":"z","z5":"z", "C0":"C","C1":"C", "M0":"M","M1":"M", "X0":"X","X1":"X", "S0":"S","S1":"S", "J0":"J", "P0":"P","P1":"P", "Z0":"Z","Z1":"Z","Z2":"Z","Z3":"Z","Z4":"Z","Z5":"Z", }
2,my:
標記值:1代表紅色方(這里指人。玩家永遠操縱紅色) ; -1代表AI
3,map[y][i]與d.push([i][y])
左方向上搜索,y坐標不變,x坐標遍歷,而體現在map當中(向上翻第一點),仔細看就會發現:第一個下標代表y值,第二個下標代表x值,其與坐標值正好相反
其他方向上以此類推。。。
(2)馬
- com.bylaw.m = function (x,y,map,my){
- var d=[];
- //1點鍾方向 不絆馬腳 1點不存在棋子或1點棋子顏色不同 push
- if ( y-2>= 0 && x+1<= 8 && !play.map[y-1][x] &&(!com.mans[map[y-2][x+1]] || com.mans[map[y-2][x+1]].my!=my)) d.push([x+1,y-2]);
- //2點
- if ( y-1>= 0 && x+2<= 8 && !play.map[y][x+1] &&(!com.mans[map[y-1][x+2]] || com.mans[map[y-1][x+2]].my!=my)) d.push([x+2,y-1]);
- //4點
- if ( y+1<= 9 && x+2<= 8 && !play.map[y][x+1] &&(!com.mans[map[y+1][x+2]] || com.mans[map[y+1][x+2]].my!=my)) d.push([x+2,y+1]);
- //5點
- if ( y+2<= 9 && x+1<= 8 && !play.map[y+1][x] &&(!com.mans[map[y+2][x+1]] || com.mans[map[y+2][x+1]].my!=my)) d.push([x+1,y+2]);
- //7點
- if ( y+2<= 9 && x-1>= 0 && !play.map[y+1][x] &&(!com.mans[map[y+2][x-1]] || com.mans[map[y+2][x-1]].my!=my)) d.push([x-1,y+2]);
- //8點
- if ( y+1<= 9 && x-2>= 0 && !play.map[y][x-1] &&(!com.mans[map[y+1][x-2]] || com.mans[map[y+1][x-2]].my!=my)) d.push([x-2,y+1]);
- //10點
- if ( y-1>= 0 && x-2>= 0 && !play.map[y][x-1] &&(!com.mans[map[y-1][x-2]] || com.mans[map[y-1][x-2]].my!=my)) d.push([x-2,y-1]);
- //11點
- if ( y-2>= 0 && x-1>= 0 && !play.map[y-1][x] &&(!com.mans[map[y-2][x-1]] || com.mans[map[y-2][x-1]].my!=my)) d.push([x-1,y-2]);
- return d;
- }
com.bylaw.m = function (x,y,map,my){ var d=[]; //1點鍾方向 不絆馬腳 1點不存在棋子或1點棋子顏色不同 push if ( y-2>= 0 && x+1<= 8 && !play.map[y-1][x] &&(!com.mans[map[y-2][x+1]] || com.mans[map[y-2][x+1]].my!=my)) d.push([x+1,y-2]); //2點 if ( y-1>= 0 && x+2<= 8 && !play.map[y][x+1] &&(!com.mans[map[y-1][x+2]] || com.mans[map[y-1][x+2]].my!=my)) d.push([x+2,y-1]); //4點 if ( y+1<= 9 && x+2<= 8 && !play.map[y][x+1] &&(!com.mans[map[y+1][x+2]] || com.mans[map[y+1][x+2]].my!=my)) d.push([x+2,y+1]); //5點 if ( y+2<= 9 && x+1<= 8 && !play.map[y+1][x] &&(!com.mans[map[y+2][x+1]] || com.mans[map[y+2][x+1]].my!=my)) d.push([x+1,y+2]); //7點 if ( y+2<= 9 && x-1>= 0 && !play.map[y+1][x] &&(!com.mans[map[y+2][x-1]] || com.mans[map[y+2][x-1]].my!=my)) d.push([x-1,y+2]); //8點 if ( y+1<= 9 && x-2>= 0 && !play.map[y][x-1] &&(!com.mans[map[y+1][x-2]] || com.mans[map[y+1][x-2]].my!=my)) d.push([x-2,y+1]); //10點 if ( y-1>= 0 && x-2>= 0 && !play.map[y][x-1] &&(!com.mans[map[y-1][x-2]] || com.mans[map[y-1][x-2]].my!=my)) d.push([x-2,y-1]); //11點 if ( y-2>= 0 && x-1>= 0 && !play.map[y-1][x] &&(!com.mans[map[y-2][x-1]] || com.mans[map[y-2][x-1]].my!=my)) d.push([x-1,y-2]); return d; }
算法分析:
當馬處於一點時,可以走的最多情況有8種方向,分別討論每個方向:如果不絆馬腳,且該方向上那着點沒有棋子或棋子顏色不同,則記錄該着點
圖例分析:
有點丑,用畫圖做的,不要在意這些細節
(三)相
- com.bylaw.x = function (x,y,map,my){
- var d=[];
- if (my===1){ //紅方 顏色不同,y的取值范圍不同,且不能過河
- //4點半 不絆象腳 4.5位置沒子或棋子顏色不同 push
- if ( y+2<= 9 && x+2<= 8 && !play.map[y+1][x+1] && (!com.mans[map[y+2][x+2]] || com.mans[map[y+2][x+2]].my!=my)) d.push([x+2,y+2]);
- //7點半
- if ( y+2<= 9 && x-2>= 0 && !play.map[y+1][x-1] && (!com.mans[map[y+2][x-2]] || com.mans[map[y+2][x-2]].my!=my)) d.push([x-2,y+2]);
- //1點半
- if ( y-2>= 5 && x+2<= 8 && !play.map[y-1][x+1] && (!com.mans[map[y-2][x+2]] || com.mans[map[y-2][x+2]].my!=my)) d.push([x+2,y-2]);
- //10點半
- if ( y-2>= 5 && x-2>= 0 && !play.map[y-1][x-1] && (!com.mans[map[y-2][x-2]] || com.mans[map[y-2][x-2]].my!=my)) d.push([x-2,y-2]);
- }else{
- //4點半
- if ( y+2<= 4 && x+2<= 8 && !play.map[y+1][x+1] && (!com.mans[map[y+2][x+2]] || com.mans[map[y+2][x+2]].my!=my)) d.push([x+2,y+2]);
- //7點半
- if ( y+2<= 4 && x-2>= 0 && !play.map[y+1][x-1] && (!com.mans[map[y+2][x-2]] || com.mans[map[y+2][x-2]].my!=my)) d.push([x-2,y+2]);
- //1點半
- if ( y-2>= 0 && x+2<= 8 && !play.map[y-1][x+1] && (!com.mans[map[y-2][x+2]] || com.mans[map[y-2][x+2]].my!=my)) d.push([x+2,y-2]);
- //10點半
- if ( y-2>= 0 && x-2>= 0 && !play.map[y-1][x-1] && (!com.mans[map[y-2][x-2]] || com.mans[map[y-2][x-2]].my!=my)) d.push([x-2,y-2]);
- }
- return d;
- }
com.bylaw.x = function (x,y,map,my){ var d=[]; if (my===1){ //紅方 顏色不同,y的取值范圍不同,且不能過河 //4點半 不絆象腳 4.5位置沒子或棋子顏色不同 push if ( y+2<= 9 && x+2<= 8 && !play.map[y+1][x+1] && (!com.mans[map[y+2][x+2]] || com.mans[map[y+2][x+2]].my!=my)) d.push([x+2,y+2]); //7點半 if ( y+2<= 9 && x-2>= 0 && !play.map[y+1][x-1] && (!com.mans[map[y+2][x-2]] || com.mans[map[y+2][x-2]].my!=my)) d.push([x-2,y+2]); //1點半 if ( y-2>= 5 && x+2<= 8 && !play.map[y-1][x+1] && (!com.mans[map[y-2][x+2]] || com.mans[map[y-2][x+2]].my!=my)) d.push([x+2,y-2]); //10點半 if ( y-2>= 5 && x-2>= 0 && !play.map[y-1][x-1] && (!com.mans[map[y-2][x-2]] || com.mans[map[y-2][x-2]].my!=my)) d.push([x-2,y-2]); }else{ //4點半 if ( y+2<= 4 && x+2<= 8 && !play.map[y+1][x+1] && (!com.mans[map[y+2][x+2]] || com.mans[map[y+2][x+2]].my!=my)) d.push([x+2,y+2]); //7點半 if ( y+2<= 4 && x-2>= 0 && !play.map[y+1][x-1] && (!com.mans[map[y+2][x-2]] || com.mans[map[y+2][x-2]].my!=my)) d.push([x-2,y+2]); //1點半 if ( y-2>= 0 && x+2<= 8 && !play.map[y-1][x+1] && (!com.mans[map[y-2][x+2]] || com.mans[map[y-2][x+2]].my!=my)) d.push([x+2,y-2]); //10點半 if ( y-2>= 0 && x-2>= 0 && !play.map[y-1][x-1] && (!com.mans[map[y-2][x-2]] || com.mans[map[y-2][x-2]].my!=my)) d.push([x-2,y-2]); } return d; }
算法分析:
因為相不能過河,所以要按顏色分情況討論(不同顏色,y坐標不同)
而每種顏色的相都有四種可能着法,與馬類似:如果不絆象腳, 着點沒有棋子或棋子顏色不同,記錄
圖例分析:
(四)士
- com.bylaw.s = function (x,y,map,my){
- var d=[];
- if (my===1){ //紅方
- //4點半
- if ( y+1<= 9 && x+1<= 5 && (!com.mans[map[y+1][x+1]] || com.mans[map[y+1][x+1]].my!=my)) d.push([x+1,y+1]);
- //7點半
- if ( y+1<= 9 && x-1>= 3 && (!com.mans[map[y+1][x-1]] || com.mans[map[y+1][x-1]].my!=my)) d.push([x-1,y+1]);
- //1點半
- if ( y-1>= 7 && x+1<= 5 && (!com.mans[map[y-1][x+1]] || com.mans[map[y-1][x+1]].my!=my)) d.push([x+1,y-1]);
- //10點半
- if ( y-1>= 7 && x-1>= 3 && (!com.mans[map[y-1][x-1]] || com.mans[map[y-1][x-1]].my!=my)) d.push([x-1,y-1]);
- }else{
- //4點半
- if ( y+1<= 2 && x+1<= 5 && (!com.mans[map[y+1][x+1]] || com.mans[map[y+1][x+1]].my!=my)) d.push([x+1,y+1]);
- //7點半
- if ( y+1<= 2 && x-1>= 3 && (!com.mans[map[y+1][x-1]] || com.mans[map[y+1][x-1]].my!=my)) d.push([x-1,y+1]);
- //1點半
- if ( y-1>= 0 && x+1<= 5 && (!com.mans[map[y-1][x+1]] || com.mans[map[y-1][x+1]].my!=my)) d.push([x+1,y-1]);
- //10點半
- if ( y-1>= 0 && x-1>= 3 && (!com.mans[map[y-1][x-1]] || com.mans[map[y-1][x-1]].my!=my)) d.push([x-1,y-1]);
- }
- return d;
- }
com.bylaw.s = function (x,y,map,my){ var d=[]; if (my===1){ //紅方 //4點半 if ( y+1<= 9 && x+1<= 5 && (!com.mans[map[y+1][x+1]] || com.mans[map[y+1][x+1]].my!=my)) d.push([x+1,y+1]); //7點半 if ( y+1<= 9 && x-1>= 3 && (!com.mans[map[y+1][x-1]] || com.mans[map[y+1][x-1]].my!=my)) d.push([x-1,y+1]); //1點半 if ( y-1>= 7 && x+1<= 5 && (!com.mans[map[y-1][x+1]] || com.mans[map[y-1][x+1]].my!=my)) d.push([x+1,y-1]); //10點半 if ( y-1>= 7 && x-1>= 3 && (!com.mans[map[y-1][x-1]] || com.mans[map[y-1][x-1]].my!=my)) d.push([x-1,y-1]); }else{ //4點半 if ( y+1<= 2 && x+1<= 5 && (!com.mans[map[y+1][x+1]] || com.mans[map[y+1][x+1]].my!=my)) d.push([x+1,y+1]); //7點半 if ( y+1<= 2 && x-1>= 3 && (!com.mans[map[y+1][x-1]] || com.mans[map[y+1][x-1]].my!=my)) d.push([x-1,y+1]); //1點半 if ( y-1>= 0 && x+1<= 5 && (!com.mans[map[y-1][x+1]] || com.mans[map[y-1][x+1]].my!=my)) d.push([x+1,y-1]); //10點半 if ( y-1>= 0 && x-1>= 3 && (!com.mans[map[y-1][x-1]] || com.mans[map[y-1][x-1]].my!=my)) d.push([x-1,y-1]); } return d; }
算法分析:
士不能出九宮格,x,y值都有限制。按顏色分情況討論。每種顏色各有4中可能着法:如果該着點沒棋子或棋子顏色不同,記錄
圖例分析:
這個簡單了,就不畫圖了~ ~ ~ ~
(五)將
- com.bylaw.j = function (x,y,map,my){
- var d=[];
- var isNull=(function (y1,y2){
- var y1=com.mans["j0"].y; //紅帥的y
- var x1=com.mans["J0"].x; //黑將的x
- var y2=com.mans["J0"].y; //黑將的y
- for (var i=y1-1; i>y2; i--){
- if (map[i][x1]) return false; //將與將之間非空,有子
- }
- return true;
- })();
- if (my===1){ //紅方
- //下
- if ( y+1<= 9 && (!com.mans[map[y+1][x]] || com.mans[map[y+1][x]].my!=my)) d.push([x,y+1]);
- //上
- if ( y-1>= 7 && (!com.mans[map[y-1][x]] || com.mans[map[y-1][x]].my!=my)) d.push([x,y-1]);
- //老將對老將的情況
- if ( com.mans["j0"].x == com.mans["J0"].x &&isNull) d.push([com.mans["J0"].x,com.mans["J0"].y]); //x相等,且中間為空,push黑將的坐標
- }else{
- //下
- if ( y+1<= 2 && (!com.mans[map[y+1][x]] || com.mans[map[y+1][x]].my!=my)) d.push([x,y+1]);
- //上
- if ( y-1>= 0 && (!com.mans[map[y-1][x]] || com.mans[map[y-1][x]].my!=my)) d.push([x,y-1]);
- //老將對老將的情況
- if ( com.mans["j0"].x == com.mans["J0"].x &&isNull) d.push([com.mans["j0"].x,com.mans["j0"].y]); //push紅帥的坐標
- }
- //右
- if ( x+1<= 5 && (!com.mans[map[y][x+1]] || com.mans[map[y][x+1]].my!=my)) d.push([x+1,y]);
- //左
- if ( x-1>= 3 && (!com.mans[map[y][x-1]] || com.mans[map[y][x-1]].my!=my))d.push([x-1,y]);
- return d;
- }
com.bylaw.j = function (x,y,map,my){ var d=[]; var isNull=(function (y1,y2){ var y1=com.mans["j0"].y; //紅帥的y var x1=com.mans["J0"].x; //黑將的x var y2=com.mans["J0"].y; //黑將的y for (var i=y1-1; i>y2; i--){ if (map[i][x1]) return false; //將與將之間非空,有子 } return true; })(); if (my===1){ //紅方 //下 if ( y+1<= 9 && (!com.mans[map[y+1][x]] || com.mans[map[y+1][x]].my!=my)) d.push([x,y+1]); //上 if ( y-1>= 7 && (!com.mans[map[y-1][x]] || com.mans[map[y-1][x]].my!=my)) d.push([x,y-1]); //老將對老將的情況 if ( com.mans["j0"].x == com.mans["J0"].x &&isNull) d.push([com.mans["J0"].x,com.mans["J0"].y]); //x相等,且中間為空,push黑將的坐標 }else{ //下 if ( y+1<= 2 && (!com.mans[map[y+1][x]] || com.mans[map[y+1][x]].my!=my)) d.push([x,y+1]); //上 if ( y-1>= 0 && (!com.mans[map[y-1][x]] || com.mans[map[y-1][x]].my!=my)) d.push([x,y-1]); //老將對老將的情況 if ( com.mans["j0"].x == com.mans["J0"].x &&isNull) d.push([com.mans["j0"].x,com.mans["j0"].y]); //push紅帥的坐標 } //右 if ( x+1<= 5 && (!com.mans[map[y][x+1]] || com.mans[map[y][x+1]].my!=my)) d.push([x+1,y]); //左 if ( x-1>= 3 && (!com.mans[map[y][x-1]] || com.mans[map[y][x-1]].my!=my))d.push([x-1,y]); return d; }
算法分析:
將除了顏色不同導致y值不同外,還有種特殊情況:即老將見面。所以開始先寫個函數,判斷將與帥之間是否有其他棋子
接下來按顏色不同分情況討論上下兩種着法:重點 是y值的界定。以帥為例:帥在棋盤下方,y坐標只能取7,8,9.如果向下走,則取7,8,所以y值最大為8.上與其類似。而判斷完着法之后還要判斷是否老將見面的特殊情況:如果兩者x坐標相等且中間沒其他棋子,之間閃現過去搶人頭~ ~ ~然后victory
(六),炮
- com.bylaw.p = function (x,y,map,my){
- var d=[];
- //左側檢索
- var n=0;
- for (var i=x-1; i>= 0; i--){
- if (map[y][i]) { //碰到子
- if (n==0){ //若是第一個子,不用管,跳出本次循環,標記位加1
- n++;
- continue;
- }else{ //若不是第一個子,判斷顏色若不同,push過去並結束循環
- if (com.mans[map[y][i]].my!=my) d.push([i,y]);
- break
- }
- }else{ //若一直碰不到子,將子走到最左
- if(n==0) d.push([i,y])
- }
- }
- //右側檢索
- var n=0;
- for (var i=x+1; i <= 8; i++){
- if (map[y][i]) {
- if (n==0){
- n++;
- continue;
- }else{
- if (com.mans[map[y][i]].my!=my) d.push([i,y]);
- break
- }
- }else{
- if(n==0) d.push([i,y])
- }
- }
- //上檢索
- var n=0;
- for (var i = y-1 ; i >= 0; i--){
- if (map[i][x]) {
- if (n==0){
- n++;
- continue;
- }else{
- if (com.mans[map[i][x]].my!=my) d.push([x,i]);
- break
- }
- }else{
- if(n==0) d.push([x,i])
- }
- }
- //下檢索
- var n=0;
- for (var i = y+1 ; i<= 9; i++){
- if (map[i][x]) {
- if (n==0){
- n++;
- continue;
- }else{
- if (com.mans[map[i][x]].my!=my) d.push([x,i]);
- break
- }
- }else{
- if(n==0) d.push([x,i])
- }
- }
- return d;
- }
com.bylaw.p = function (x,y,map,my){ var d=[]; //左側檢索 var n=0; for (var i=x-1; i>= 0; i--){ if (map[y][i]) { //碰到子 if (n==0){ //若是第一個子,不用管,跳出本次循環,標記位加1 n++; continue; }else{ //若不是第一個子,判斷顏色若不同,push過去並結束循環 if (com.mans[map[y][i]].my!=my) d.push([i,y]); break } }else{ //若一直碰不到子,將子走到最左 if(n==0) d.push([i,y]) } } //右側檢索 var n=0; for (var i=x+1; i <= 8; i++){ if (map[y][i]) { if (n==0){ n++; continue; }else{ if (com.mans[map[y][i]].my!=my) d.push([i,y]); break } }else{ if(n==0) d.push([i,y]) } } //上檢索 var n=0; for (var i = y-1 ; i >= 0; i--){ if (map[i][x]) { if (n==0){ n++; continue; }else{ if (com.mans[map[i][x]].my!=my) d.push([x,i]); break } }else{ if(n==0) d.push([x,i]) } } //下檢索 var n=0; for (var i = y+1 ; i<= 9; i++){ if (map[i][x]) { if (n==0){ n++; continue; }else{ if (com.mans[map[i][x]].my!=my) d.push([x,i]); break } }else{ if(n==0) d.push([x,i]) } } return d; }
算法分析:
跟車一樣,需要向4個方向上搜索
若該方向上沒棋子,則記錄該方向所有點坐標
若走着走着發現一個棋子,先冷靜一下(跳出本次循環),偷偷地看接下來該方向上有沒有敵方棋子,有,就可以越塔gank了。然后把敵方死的位置記錄下來留作紀念~ ~ ~
(七)卒
- com.bylaw.z = function (x,y,map,my){
- var d=[];
- if (my===1){ //紅方
- //上
- if ( y-1>= 0 && (!com.mans[map[y-1][x]] || com.mans[map[y-1][x]].my!=my)) d.push([x,y-1]);
- //右
- if ( x+1<= 8 && y<=4 && (!com.mans[map[y][x+1]] || com.mans[map[y][x+1]].my!=my)) d.push([x+1,y]); //y<4,即過河之后,才能左右移動
- //左
- if ( x-1>= 0 && y<=4 && (!com.mans[map[y][x-1]] || com.mans[map[y][x-1]].my!=my))d.push([x-1,y]);
- }else{
- //下
- if ( y+1<= 9 && (!com.mans[map[y+1][x]] || com.mans[map[y+1][x]].my!=my)) d.push([x,y+1]);
- //右
- if ( x+1<= 8 && y>=6 && (!com.mans[map[y][x+1]] || com.mans[map[y][x+1]].my!=my)) d.push([x+1,y]);
- //左
- if ( x-1>= 0 && y>=6 && (!com.mans[map[y][x-1]] || com.mans[map[y][x-1]].my!=my))d.push([x-1,y]);
- }
- return d;
- }
com.bylaw.z = function (x,y,map,my){ var d=[]; if (my===1){ //紅方 //上 if ( y-1>= 0 && (!com.mans[map[y-1][x]] || com.mans[map[y-1][x]].my!=my)) d.push([x,y-1]); //右 if ( x+1<= 8 && y<=4 && (!com.mans[map[y][x+1]] || com.mans[map[y][x+1]].my!=my)) d.push([x+1,y]); //y<4,即過河之后,才能左右移動 //左 if ( x-1>= 0 && y<=4 && (!com.mans[map[y][x-1]] || com.mans[map[y][x-1]].my!=my))d.push([x-1,y]); }else{ //下 if ( y+1<= 9 && (!com.mans[map[y+1][x]] || com.mans[map[y+1][x]].my!=my)) d.push([x,y+1]); //右 if ( x+1<= 8 && y>=6 && (!com.mans[map[y][x+1]] || com.mans[map[y][x+1]].my!=my)) d.push([x+1,y]); //左 if ( x-1>= 0 && y>=6 && (!com.mans[map[y][x-1]] || com.mans[map[y][x-1]].my!=my))d.push([x-1,y]); } return d; }
算法分析:
同樣分情況討論。且由於卒不能后退所以只用判斷上,左,右三種情況。而卒由於過河后才能左右移動,所以左右的判斷除了x的界定還有y值的界定。最后跟車一樣如果該着點沒有棋子或該棋子顏色不同,記錄該點
二 ,使用alpha-beta在所有着法當中搜索最佳着法
- AI.getAlphaBeta = function (A, B, depth, map ,my) {
- if (depth == 0) {
- return {"value":AI.evaluate(map , my)}; //當搜索深度為0是時調用局面評價函數;
- }
- var moves = AI.getMoves(map , my ); //生成全部走法;
- <span style="color:#ff0000;">//這里排序以后會增加效率
- for (var i=0; i < moves.length; i++) {</span>
- //走這個走法;
- var move= moves[i];
- var key = move[4];
- var oldX= move[0];
- var oldY= move[1];
- var newX= move[2];
- var newY= move[3];
- var clearKey = map[ newY ][ newX ]||"";
- map[ newY ][ newX ] = key; //走,賦新值,刪除舊值
- delete map[ oldY ][ oldX ];
- play.mans[key].x = newX;
- play.mans[key].y = newY;
- <span style="color:#ff0000;">if (clearKey=="j0"||clearKey=="J0") { //被吃老將
- play.mans[key] .x = oldX;
- play.mans[key] .y = oldY;
- map[ oldY ][ oldX ] = key;
- delete map[ newY ][ newX ]; //並不是真的走,所以這里要撤銷
- if (clearKey){
- map[ newY ][ newX ] = clearKey;
- }
- return {"key":key,"x":newX,"y":newY,"value":8888};
- </span>
- }else {
- var val = -AI.getAlphaBeta(-B, -A, depth - 1, map , -my).value; //上面代表AI,這里倒置,-my,代表人的着法,然后再從上面開始執行
- //val = val || val.value;
- //<span style="color:#ff0000;">撤消這個走法;
- play.mans[key] .x = oldX;
- play.mans[key] .y = oldY;
- map[ oldY ][ oldX ] = key;
- delete map[ newY ][ newX ];
- if (clearKey){
- map[ newY ][ newX ] = clearKey;
- //play.mans[ clearKey ].isShow = true;
- }</span>
- if (val >= B) {
- //將這個走法記錄到歷史表中;
- //AI.setHistoryTable(txtMap,AI.treeDepth-depth+1,B,my);
- return {"key":key,"x":newX,"y":newY,"value":B};
- }
- <span style="color:#ff0000;">if (val > A) {
- A = val; //設置最佳走法,
- if (AI.treeDepth == depth) var rootKey={"key":key,"x":newX,"y":newY,"value":A};
- } </span>
- }
- }
- if (AI.treeDepth == depth) {//已經遞歸回根了
- if (!rootKey){
- //AI沒有最佳走法,說明AI被將死了,返回false
- return false;
- }else{
- //這個就是最佳走法;
- return rootKey;
- }
- }
- return {"key":key,"x":newX,"y":newY,"value":A};
- }
AI.getAlphaBeta = function (A, B, depth, map ,my) { if (depth == 0) { return {"value":AI.evaluate(map , my)}; //當搜索深度為0是時調用局面評價函數; } var moves = AI.getMoves(map , my ); //生成全部走法; <span style="color:#ff0000;">//這里排序以后會增加效率 for (var i=0; i < moves.length; i++) {</span> //走這個走法; var move= moves[i]; var key = move[4]; var oldX= move[0]; var oldY= move[1]; var newX= move[2]; var newY= move[3]; var clearKey = map[ newY ][ newX ]||""; map[ newY ][ newX ] = key; //走,賦新值,刪除舊值 delete map[ oldY ][ oldX ]; play.mans[key].x = newX; play.mans[key].y = newY; <span style="color:#ff0000;">if (clearKey=="j0"||clearKey=="J0") { //被吃老將 play.mans[key] .x = oldX; play.mans[key] .y = oldY; map[ oldY ][ oldX ] = key; delete map[ newY ][ newX ]; //並不是真的走,所以這里要撤銷 if (clearKey){ map[ newY ][ newX ] = clearKey; } return {"key":key,"x":newX,"y":newY,"value":8888}; </span> }else { var val = -AI.getAlphaBeta(-B, -A, depth - 1, map , -my).value; //上面代表AI,這里倒置,-my,代表人的着法,然后再從上面開始執行 //val = val || val.value; //<span style="color:#ff0000;">撤消這個走法; play.mans[key] .x = oldX; play.mans[key] .y = oldY; map[ oldY ][ oldX ] = key; delete map[ newY ][ newX ]; if (clearKey){ map[ newY ][ newX ] = clearKey; //play.mans[ clearKey ].isShow = true; }</span> if (val >= B) { //將這個走法記錄到歷史表中; //AI.setHistoryTable(txtMap,AI.treeDepth-depth+1,B,my); return {"key":key,"x":newX,"y":newY,"value":B}; } <span style="color:#ff0000;">if (val > A) { A = val; //設置最佳走法, if (AI.treeDepth == depth) var rootKey={"key":key,"x":newX,"y":newY,"value":A}; } </span> } } if (AI.treeDepth == depth) {//已經遞歸回根了 if (!rootKey){ //AI沒有最佳走法,說明AI被將死了,返回false return false; }else{ //這個就是最佳走法; return rootKey; } } return {"key":key,"x":newX,"y":newY,"value":A}; }
簡化后的偽代碼(與上面代碼一一對應):
- int AlphaBeta(int vlAlpha, int vlBeta, int nDepth) {
- if (nDepth == 0) {
- return 局面評價函數;
- }
- 生成全部走法;
- <span style="color:#ff0000;">按歷史表排序全部走法;</span>
- for (每個生成的走法) {
- 走這個走法;
- <span style="color:#ff0000;">if (被將軍) {
- 撤消這個走法;
- } else</span> {
- int vl = -AlphaBeta(-vlBeta, -vlAlpha, nDepth - 1);
- <span style="color:#ff0000;">撤消這個走法;</span>
- if (vl >= vlBeta) {
- <span style="color:#ff0000;">將這個走法記錄到歷史表中;</span>
- return vlBeta;
- }
- if (vl > vlAlpha) {
- <span style="color:#ff0000;">設置最佳走法;</span>
- vlAlpha = vl;
- }
- }
- }
- if (沒有走過任何走法) { //AI被將死
- return 殺棋的分數;
- }
- 將最佳走法記錄到歷史表中;
- if (根節點) {
- 最佳走法就是電腦要走的棋;
- }
- return vlAlpha;
- }
int AlphaBeta(int vlAlpha, int vlBeta, int nDepth) { if (nDepth == 0) { return 局面評價函數; } 生成全部走法; <span style="color:#ff0000;">按歷史表排序全部走法;</span> for (每個生成的走法) { 走這個走法; <span style="color:#ff0000;">if (被將軍) { 撤消這個走法; } else</span> { int vl = -AlphaBeta(-vlBeta, -vlAlpha, nDepth - 1); <span style="color:#ff0000;">撤消這個走法;</span> if (vl >= vlBeta) { <span style="color:#ff0000;">將這個走法記錄到歷史表中;</span> return vlBeta; } if (vl > vlAlpha) { <span style="color:#ff0000;">設置最佳走法;</span> vlAlpha = vl; } } } if (沒有走過任何走法) { //AI被將死 return 殺棋的分數; } 將最佳走法記錄到歷史表中; if (根節點) { 最佳走法就是電腦要走的棋; } return vlAlpha; }
這樣,簡單套用上一講講過的alpha-beta算法,就能搜索出相對來說最佳路徑來~ ~ ~
最后設置坐標就可以實現AI自動走棋或吃子了