心烦意乱睡不着,随便写点教程吧,不知道这类东西发哪个板块比较合适,先发这吧,哪位管理大大看着不顺眼再移吧。

声明一下啊,本人觉得这个不适合新手看,本人表达能力有限,别把你给误导喽,罪过啊。

象棋的预览地址:http://www.jnzo.com/chess/
代码未压缩,注释写的很清楚了,有兴趣的朋友可以一起改善


制作之前网上搜了一圈资料,关于中国象棋的还真少,不过倒是找到了国际象棋的资料,让我很钦佩的国际同行的专业精神,一个小小的象棋游戏,人家制定一系列标准,还组建了协会,开发了几种不同语言的引擎(可惜没有javascript的),佩服的五体投地。

制作过程先不说了,今儿就说说AI吧,有兴趣想学习html5制作过程的话,请留言,如果需要的话,后面补上。

————————————

言归正传,说到算法,可能许多程序员都会先皱眉头,其实那东西没有那么难,不是数学家也能写算法。

先说说人机对战AI的原理吧

树搜索

其实很简单


1、取得走法很简单,根据规则一步步的去算每个棋子哪个点能走,源文件里面有个 com.bylaw ,他下面是取得每种棋子走点的方法;

//棋子能走的着点 

com.bylaw ={} 

//车 

com.bylaw.c = function (x,y,map,my){ 

 var d=[]; 

 //左侧检索 

 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.m = function (x,y,map,my){ 

 var d=[]; 

 //1点 

 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.x = function (x,y,map,my){ 

 var d=[]; 

 if (my===1){ //红方 

 //4点半 

 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.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.j = function (x,y,map,my){ 

 var d=[]; 

 var isNull=(function (y1,y2){ 

 var y1=com.mans["j0"].y; 

 var x1=com.mans["J0"].x; 

 var y2=com.mans["J0"].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]); 


 }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]); 

 } 

 //右 

 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.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){ 

 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=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.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]); 

 //左 

 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; 

}


复制代码
2、根据各个走法,去生成新的棋谱,然后搜索新的走法
3、评估
评估是个很麻烦的事情,一开始我考虑的是给每个棋子价值,比如
士 20 象 20 马 90 车 200 炮 100 兵 40

不过会有一个问题,每个棋子在不同的局面,不同的位置,其价值是不同的,最明显的就是“士”,过河以后明显价值增加了不少,而且越靠近对方老将价值越大,甚至到最后会超越马或者炮,所以我把价值评估做了改进,详情看源码com.value这块。这里的棋子价值,借鉴了一些其他象棋游戏的经验

com.value = { 


 //车价值 

 c:[ 

 [206, 208, 207, 213, 214, 213, 207, 208, 206], 

 [206, 212, 209, 216, 233, 216, 209, 212, 206], 

 [206, 208, 207, 214, 216, 214, 207, 208, 206], 

 [206, 213, 213, 216, 216, 216, 213, 213, 206], 

 [208, 211, 211, 214, 215, 214, 211, 211, 208], 


 [208, 212, 212, 214, 215, 214, 212, 212, 208], 

 [204, 209, 204, 212, 214, 212, 204, 209, 204], 

 [198, 208, 204, 212, 212, 212, 204, 208, 198], 

 [200, 208, 206, 212, 200, 212, 206, 208, 200], 

 [194, 206, 204, 212, 200, 212, 204, 206, 194] 

 ], 


 //马价值 

 m:[ 

 [90, 90, 90, 96, 90, 96, 90, 90, 90], 

 [90, 96,103, 97, 94, 97,103, 96, 90], 

 [92, 98, 99,103, 99,103, 99, 98, 92], 

 [93,108,100,107,100,107,100,108, 93], 

 [90,100, 99,103,104,103, 99,100, 90], 


 [90, 98,101,102,103,102,101, 98, 90], 

 [92, 94, 98, 95, 98, 95, 98, 94, 92], 

 [93, 92, 94, 95, 92, 95, 94, 92, 93], 

 [85, 90, 92, 93, 78, 93, 92, 90, 85], 

 [88, 85, 90, 88, 90, 88, 90, 85, 88] 

 ], 


 //相价值 

 x:[ 

 [0, 0,20, 0, 0, 0,20, 0, 0], 

 [0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [0, 0, 0, 0,23, 0, 0, 0, 0], 

 [0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [0, 0,20, 0, 0, 0,20, 0, 0], 


 [0, 0,20, 0, 0, 0,20, 0, 0], 

 [0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [18,0, 0, 0,23, 0, 0, 0,18], 

 [0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [0, 0,20, 0, 0, 0,20, 0, 0] 

 ], 


 //士价值 

 s:[ 

 [0, 0, 0,20, 0,20, 0, 0, 0], 

 [0, 0, 0, 0,23, 0, 0, 0, 0], 

 [0, 0, 0,20, 0,20, 0, 0, 0], 

 [0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [0, 0, 0, 0, 0, 0, 0, 0, 0], 


 [0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [0, 0, 0,20, 0,20, 0, 0, 0], 

 [0, 0, 0, 0,23, 0, 0, 0, 0], 

 [0, 0, 0,20, 0,20, 0, 0, 0] 

 ], 


 //奖价值 

 j:[ 

 [0, 0, 0, 8888, 8888, 8888, 0, 0, 0], 

 [0, 0, 0, 8888, 8888, 8888, 0, 0, 0], 

 [0, 0, 0, 8888, 8888, 8888, 0, 0, 0], 

 [0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [0, 0, 0, 0, 0, 0, 0, 0, 0], 


 [0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [0, 0, 0, 8888, 8888, 8888, 0, 0, 0], 

 [0, 0, 0, 8888, 8888, 8888, 0, 0, 0], 

 [0, 0, 0, 8888, 8888, 8888, 0, 0, 0] 

 ], 


 //炮价值 

 p:[ 


 [100, 100, 96, 91, 90, 91, 96, 100, 100], 

 [ 98, 98, 96, 92, 89, 92, 96, 98, 98], 

 [ 97, 97, 96, 91, 92, 91, 96, 97, 97], 

 [ 96, 99, 99, 98, 100, 98, 99, 99, 96], 

 [ 96, 96, 96, 96, 100, 96, 96, 96, 96], 


 [ 95, 96, 99, 96, 100, 96, 99, 96, 95], 

 [ 96, 96, 96, 96, 96, 96, 96, 96, 96], 

 [ 97, 96, 100, 99, 101, 99, 100, 96, 97], 

 [ 96, 97, 98, 98, 98, 98, 98, 97, 96], 

 [ 96, 96, 97, 99, 99, 99, 97, 96, 96] 

 ], 


 //卒价值 

 z:[ 

 [ 9, 9, 9, 11, 13, 11, 9, 9, 9], 

 [19, 24, 34, 42, 44, 42, 34, 24, 19], 

 [19, 24, 32, 37, 37, 37, 32, 24, 19], 

 [19, 23, 27, 29, 30, 29, 27, 23, 19], 

 [14, 18, 20, 27, 29, 27, 20, 18, 14], 


 [ 7, 0, 13, 0, 16, 0, 13, 0, 7], 

 [ 7, 0, 7, 0, 15, 0, 7, 0, 7], 

 [ 0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [ 0, 0, 0, 0, 0, 0, 0, 0, 0], 

 [ 0, 0, 0, 0, 0, 0, 0, 0, 0] 

 ] 

} 


//黑子为红字价值位置的倒置 

com.value.C = com.arr2Clone(com.value.c).reverse(); 

com.value.M = com.arr2Clone(com.value.m).reverse(); 

com.value.X = com.value.x; 

com.value.S = com.value.s; 

com.value.J = com.value.j; 

com.value.P = com.arr2Clone(com.value.p).reverse(); 

com.value.Z = com.arr2Clone(com.value.z).reverse(); 

复制代码 

有了价值,评估就是很简单的事啊,双方健在棋子的价值差,就是这个分支的最后得分。 

//评估棋局 取得棋盘双方棋子价值差 

AI.evaluate = function (map,my){ 

 var val=0; 

 for (var i=0; i<map.length; i++){ 

 for (var n=0; n<map[i].length; n++){ 

 var key = map[i][n]; 

 if (key){ 

 val += play.mans[key].value[i][n] * play.mans[key].my; 

 } 

 } 

 } 

 val+=Math.floor( Math.random() * 10); //让AI走棋增加随机元素 

 AI.number++; 

 return val*my; 

}


复制代码
给我一个指点,我能撬动地球
有了这个算法,我可以骄傲的说只要给我足够强大的计算机,和足够长的计算时间,我就能算出必胜的走棋方法,信不,你信不信反正我是信了。

不过我大概统计了一下,中国象棋平均每步大概有42个走法,如果这盘期走了100个回合的话,那么就是42的200次方个结果,大概需要计算这么多分支,4.4653764701754888195833543328375e+324,大概也就够现在最强大的服务器计算几百年的工作量吧。

看来是不现实的,还是看看现实一点的吧

最大最小搜索

只搜到某个深度,在这个深度来进行评估,决定AI走哪一步棋。
这个时候又有个问题,我走棋以后,对手也不一定选择哪步期来对付我啊,我该怎么判断对手会走哪一步呢,想这个问题之前,看看下面的题目就懂了



现在有30个桔子,有大的有小的,有好的有烂的,我们按照好坏级别给他评分了
随机平分为3堆,两个人抢桔子,规则是,A选择其中的一堆,B在A选的那堆里面,选一个给A,如果你是A,你该怎么选呢?
选NO1很有诱惑力,因为里面有最好的桔子16,不过你选了NO1后,你是得不到16的,因为B会拿-6给你,这样的话,对你最有优势的选择应该是NO3,也就是我要选的是每堆里面最次的那个桔子,里面最好的所在的那一堆,比较绕,不知道你听明白没。

走棋也是一样的,对方肯定是会选择对他损失最小的走法来对付你,是的,这就是最大最小算法的原理,想想如何应用到程序中。

好了,AI的基础算法完成了,依靠这套理论就能写出能够走棋的AI了,欢呼吧,虽然性能很低,虽然走得傻傻的。

不知道你能看明白不,确实比较绕,加上本人表达能力有限,有啥意见多提啊,下次改进

下一讲会说说最大最小搜索的优化算法,单凭最大最小搜索效率很低,具体的下期见吧