中国象棋AI实现——alpha-beta剪枝

中国象棋AI实现——alpha-beta剪枝

一、简介

这是基于alpha-bata剪枝算法实现的一个中国象棋博弈程序,可以实现人机交互,AI具有初级的智能,可以应对一般的象棋新手。
界面用最基本的html+css+js实现,参考自中国象棋界面素材

AI逻辑同样用js实现,项目地址

在这里插入图片描述

二、算法实现

在让AI自动做出落子决定前,必须提供一个方法让其判断哪种走法较优,也即需要一个局面评估函数,能够返回一个表示局面好坏的分数,对于AI方,这个分数越大越好。

//有一点需要强调:alpha-beta剪枝过程中走棋的双方可能会变化,但估分的标准一直是以黑方即AI方为准,黑方希望估值最大,红方希望估值最小。
function evaluate() {
    var score = 0;
    score += SingleChessScore();
    score += ChouJu();
    score += ShuangPao();
    return score;
}

目前的局面评估只包括一个主要部分和两个分支部分,分别为单棋子棋力判断、抽车将军判断和双炮将军判断,后续可能会在github仓库上更新更多的局面评估方法。
主要部分SingleChessScore依次考虑棋面上每个单独的子,由它们本身的属性和所在位置决定它们的分数,黑方取正值,红方取负值,并累加起来。

棋力表部分参考自eleeye。
以马的分数表为例,马的分数表如下:

var Ma = [
    [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, 50, 90, 88, 90, 88, 90, 50, 88]//马的两个初始位置权值设小一点,防止AI的炮“盲目攻击马”
];

有了棋面评估函数,AI现在可以遍历每一个己方棋子的每一种走法,并判断该走法的分数,基于这个,已经可以实现一个贪心算法,该算法只考虑当前的一步,并选择对自己最有利的走法。

V1.0 贪心算法,AI是黑方
function AImove() {
    var max_score = -100000000;
    var from = [];
    var to = [];
    var can_eat = false;
    for (var j = 0; j < 10; ++j) {
        for (var i = 0; i < 9; ++i) {
            if (map[j][i] < 0) {
                var t = WhatSpace(j, i);//返回棋子的属性
                var tmap = WhereCan(j,i,t);//返回能走的位置,分为能吃和不能吃(即目的棋子或空)
                if(tmap!=null && tmap.length>0) {
                    for(var q=0;q<tmap.length;q++){
                        var dest = tmap[q];
                        var tmp = map[dest[0]][dest[1]];
                        map[dest[0]][dest[1]] = map[j][i];
                        map[j][i] = 0;
                        var score = evaluate(map);
                        if (score > max_score) {
                            from[0] = j;
                            from[1] = i;
                            to[0] = dest[0];
                            to[1] = dest[1];
                            max_score = score;
                            can_eat = tmp == 0 ? false : true;
                        }
                        map[j][i] = map[dest[0]][dest[1]];
                        map[dest[0]][dest[1]] = tmp;
                    }
                }

            }
        }
    }
    console.log("now best: " + max_score);
    if (can_eat) {
        eat(from[0], from[1], to[0], to[1]);
    }
    else {
        move(from[0], from[1], to[0], to[1]);
    }
    
}


显然,这样的AI智能还不够,所以下一步我们用alpha-beta对其进行优化,使其能考虑接下来几步内的较优走法。

算法框架如下:

function alpha-beta(depth, alpha, beta) {
	对于每一个棋子的每一种走法
		修改局面为该走法落子后的局面
		ret = alpha-beta(depth + 1, alpha, beta)
		修改局面回落子前的局面
		ifMAX, alpha = max(alpha, ret)
		else ifMIN, beta = min(beta, ret)
		if beta <= alpha return (MAX? alpha : beta)
	return (MAX? alpha : beta)
}

具体代码如下:

//V2.0 alpha-beta算法,仍然假设AI是黑方
function AImove() {
    console.log("best: " + alpha_beta(1, -1e9, 1e9));
    if (AIcan_eat) {
        eat(AIfrom[0], AIfrom[1], AIto[0], AIto[1]);
    }
    else {
        move(AIfrom[0], AIfrom[1], AIto[0], AIto[1]);
    }
    
}
var AIfrom = [];
var AIto = [];
var AIcan_eat = false;
function alpha_beta(depth, alpha, beta) {
    if (depth >= 5) return evaluate(map);
    for (var j = 0; j < 10; ++j) {
        for (var i = 0; i < 9; ++i) {
            if ((depth & 1) == 1 && map[j][i] < 0 || (depth & 1) == 0 && map[j][i] > 0) {//哪些棋子能走
                var t = WhatSpace(j, i);//返回棋子的属性
                var tmap = WhereCan(j,i,t);//返回能走的位置,分为能吃和不能吃(即目的棋子或空)
                if(tmap!=null && tmap.length>0) {
                    for(var q=0;q<tmap.length;q++){
                        var dest = tmap[q];
                        var tmp = map[dest[0]][dest[1]];
                        map[dest[0]][dest[1]] = map[j][i];
                        map[j][i] = 0;
                        ret = alpha_beta(depth + 1, alpha, beta);
                        if (depth & 1 == 1) {
                            if (ret > alpha) {
                                alpha = ret;
                                if (depth == 1) {
                                    AIfrom[0] = j;
                                    AIfrom[1] = i;
                                    AIto[0] = dest[0];
                                    AIto[1] = dest[1];
                                    AIcan_eat = !(tmp == 0);
                                }
                            }
                            
                        }
                        else {
                            beta = Math.min(beta, ret);
                        }
                        map[j][i] = map[dest[0]][dest[1]];
                        map[dest[0]][dest[1]] = tmp;
                        if (beta <= alpha) return (depth & 1 == 1 ? alpha : beta);     
                    }
                }

            }
        }
    }
    return (depth & 1 == 1 ? alpha : beta);

}

可能每次都走同一种走法会使AI显得过于死板,所以在返回到第一层的所有结果中加入一点随机性

if (depth & 1 == 1) {
    if (ret > alpha) {
        if (depth == 1) {
            console.log(j + '-' + i + getCText(dest[0],dest[1])[0] + "移动到 " + dest[0] + '-' + dest[1])
            //如果新的最好结果比原最好结果只大了5分以内,以某种概率保持原最好结果,以提高随机性
            if (ret - alpha < 5 && Math.random() > 0.8) {
                console.log("跨过最优解法");
                map[j][i] = map[dest[0]][dest[1]];
                map[dest[0]][dest[1]] = tmp;
                continue;
            }
            AIfrom[0] = j;
            AIfrom[1] = i;
            AIto[0] = dest[0];
            AIto[1] = dest[1];
            AIcan_eat = !(tmp == 0);
        }
        alpha = ret;
    }
    
    //console.log(j + " " + i + " " + "alpha: " + alpha);
}

为了提高玩家体验,增加了悔棋功能。
用一个栈保存之前走过的所有步数,每次悔棋从栈中弹出并恢复。

三、结果分析

alpha-beta剪枝层数大于4时,AI表现出较好的智能,能够与普通人类进行对战且有较大的胜算。由于象棋游戏本身有较大的不确定性,因此以下结果仅供参考。

对战局数AI获胜局数胜率
10880%
搜索深度每步用时
4<0.1s
5<0.5s
61~3s

AI程序与成熟的象棋AI程序相比仍然有很多不足,主要体现在以下方面:

  • 搜索层数过低
    成熟的象棋程序中的AI算法一般能搜索到数十层的深度,这是我的算法和硬件设备所远远达不到的。
  • 套路不足
    尽管我已经为重炮将军和将军抽车这些套路加到棋力评估当中,仍然有很多危险的套路是AI所不能识别和避免的。
  • 多棋子联动性差
    棋面评估目前只限制在单子棋力评估,所以不能很好地联动多个棋子,造成较大的杀伤。但由于alpha-beta剪枝本身的特性,即使专门写两个或两个以上棋子的联动也不能有很大的改进,所以我放弃了优化这个方向。

一些可能起到优化作用的改进:

  • 完善棋力表
    在目前的程序中自始至终每个棋子都只有一个表,但一个表只能提供最粗略的估计,显然适应不了变化莫测的局面。为了提供更详细可靠的评估标准,可以将棋局分为三个阶段:开局、中局和残局,每个局面对应一个棋力评估表。
  • 增加搜索深度
  • 历史记录
    很多同学都有提到这个点,不过我很好奇他们是怎么实现的。记住一个好的走法是需要记住整个局面吗,这样的代价似乎有点大,一局下来也不一定能遇到几次重复的局面,而你还要从出现过这个局面的历史记录里找出分数最高的走法,听起来很好,但我认为很难实现。如果不记住整个局面而记下局部区域的局面,那开销可能小一点,重复出现该局面的可能性也比较高,但算法就更复杂了。
  • 11
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值