Html5+javascript中国象棋 制作过程中用到的一些AI算法

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

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

象棋的预览地址: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了,欢呼吧,虽然性能很低,虽然走得傻傻的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值