中国象棋博弈

news/2024/11/8 0:08:35/

文章目录

    • 棋盘表示
    • 着法生成
    • 搜索算法
      • 最小值-最大值搜索搜索
      • alpha-beta剪枝优化
    • 棋局评估
      • 棋子子力
      • 棋子位置
    • 棋盘UI
    • 不足
    • 参考文献

棋盘表示

中国象棋的棋盘为10*9的矩形,一般采用10*9的二维数组来表示。

chessBoard: [["BR1","BN1","BB1","BA1","BK","BA2","BB2","BN2","BR2"],[0, 0, 0, 0, 0, 0, 0, 0, 0],[0,"BC1", 0, 0, 0, 0, 0,"BC2", 0],["BP1", 0,"BP2", 0,"BP3", 0,"BP4", 0,"BP5"],[0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0],["RP1", 0,"RP2", 0,"RP3", 0,"RP4", 0,"RP5"],[0,"RC1", 0, 0, 0, 0, 0,"RC2", 0],[0, 0, 0, 0, 0, 0, 0, 0, 0],["RR1","RN1","RB1","RA1","RK","RA2","RB2","RN2","RR2"]
],

我使用javascript语言实现,在棋盘表示中,直接使用各个棋子的id,空位则使用数字0表示。棋盘大小为10*9的二维数组,边界判断并未使用扩展棋盘的方法,使用通过棋子坐标来判断该棋子是否位于棋盘内。

(y >= 0 && y < 10) && (x >= 0 && x < 9) //图形化界面中x,y与二维数组x,y刚好相反

着法生成

着法生成主要是用来判断用户的着法石佛u正确,以及生成的计算机的所有着法,并从中选择最好的着法。下边代码展示了判断每个棋子走位是否符合要求的代码(主要展示了兵、炮)的着法,其他棋子按照各自的规则即可。

validMove: function(chessBoard, id, from, to) {switch(id.slice(0, 2)) {// 兵case "RP":if(to.x === from.x && from.y - to.y === 1) {return true} else if (to.y === from.y && Math.abs(to.x - from.x) === 1) {if(from.y < this.redRiverDimen) {return true}}return falsecase "BP":if(to.x === from.x && from.y - to.y === -1) {return true} else if (to.y === from.y && Math.abs(to.x - from.x) === 1) {if(from.y > this.blackRiverDimen) {return true}}return false// 砲case "RC":case "BC":if(to.x == from.x || to.y == from.y) {let count = 0if(to.x == from.x) {let res = this.min_max(from.y, to.y)for(let i=res.min + 1; i<res.max; i++) {if(chessBoard[i][to.x] != 0) {count++}}} else {let res = this.min_max(from.x, to.x)for(let i=res.min + 1; i<res.max; i++) {if(chessBoard[from.y][i] != 0) {count++}}}if(this.getTarget(chessBoard, to) != null) {if(this.getTarget(chessBoard, to).id[0] === this.opponent[id[0]]) {return count == 1} else {return false}} else {return count == 0}}return false// 車case "RR":case "BR":// 同行、同列// 馬case "RN":case "BN":if(this.getTarget(chessBoard, to)!=null) {if(this.getTarget(chessBoard, to).id[0] === id[0]) {return false}}// 顺时针:上(上左、上右)、右(右上、右下)、下(下右、下左)、左(左下、左上)...// 相case "RB":case "BB":// 不能过河// 顺时针: 右上、右下、左下、左上...// 仕case "RA":case "BA":// 不能出九宫// 顺时针:右上、右下、左下、左上...// 帥case "RK":case "BK":// 不能出九宫// 顺时针:上、右、下、左...}return false
},

搜索算法

最小值-最大值搜索搜索

对于博弈的双方,我们每走一步棋自然会选择对自己最有利而对对方最不利的着法。以黑方为例,假设黑方走一步之后的黑方棋局局势估计值为blackValue,而此时红方棋局局势估计值为redValue,此时整个棋局局势估计值对黑方而言可以为blackValue-redValue,黑方自然希望这个值最大,而对于红方而言,自然希望选择这个值最小的走法。如此交替,便是最小-最大搜索。以下图为例:正方形为黑方,圆形为红方,图形下方数字即为blackValue-redValue,图形中A、B等字母表示当前的棋盘局势标识或选择的着法。
在这里插入图片描述
上图中,当前棋盘局势为A,黑方有B、C两种着法,其自然是选择B、C中棋局局势对自己最有利的,及balckValue-redValue最大;假设已知走法B的棋局估计值为7,现在进行走法C的计算,对于红方,当前棋局局势为C,由三种可选走法D、E、F,其自然选择balckValue-redValue最小的为自己的走法,所以选择-5,也就是走法D。此时棋局局势C的估计值为-5。至此对于黑方而言,肯定选择走法B,因为棋局对自己更加有利。

通过以上分析可知,黑方每走一步,都需要将红方可能走的所有情况进行计算求局势估计值,相同的,红方每走一步,都需要将黑方可能走的所有情况进行计算求局势估计值,所以,随着搜索深度的增加,棋盘的局势个数呈指数级的增长,在有限的时间内,我们只能够搜索有限的步骤数。

此时,我们可以利用alpha-beta剪枝进行优化。

alpha-beta剪枝优化

在这里插入图片描述
在上述黑方走动的过程中,我们计算了红方所有可能的着法,事实上,当我们求得了局势B的估计值7之后,我们便可以进行一些剪枝,比如:对于局势C,红方有三种不同的选择,并且我们知道红方的选择是min(D,E,F),在我们搜索到D之后,我们发现D的局势估计值为-5,此时我们还有必要获得E和F的局势估计值吗?答案是否定的,因为此时C的选择最大不会大于-5,而-5<7,我们已经可以知道A的选择一定时C。E、F被我们进行了“剪枝”,即为alpha剪枝
在这里插入图片描述
下面来看一下beta剪枝,对于上图所示的情况,红方当前处于棋局局势为A情况,红方与有B与C两种着法,已经计算着法B的的局势估计值为-10,接下来计算着法C的局势估计值:对于黑方,会选择二D、E、F中最大的局势估计值,但是,当我们得到局势E的估计值2之后,便会知道C的选择最小不会小于2,而2>-10,我们已经可以知道A的选择一定是B。F被我们进行了“剪枝”,即为beta剪枝

alpha-beta剪枝代码如下:

robotNextStep: function(chessBoard, turn, alpha, beta, step) {// 当搜索深度大于预设的深度是退出搜索if(step >= this.depth) {return this.evaluation(chessBoard)}let value, nextStep = {}, fromfor(let i=0; i<10; i++) {for(let j=0; j<9; j++) {if(chessBoard[i][j] == 0) {continue}if(chessBoard[i][j].indexOf(turn) === 0) {from = {"x": j, "y": i}let [...tos] = this.generateNextStep(chessBoard, from), tempfor(let k=0, len = tos.length; k<len; k++) {temp = chessBoard[tos[k].y][tos[k].x]this.swap(chessBoard, from, tos[k])// alpha剪枝if(turn == this.black) {value = this.robotNextStep(this.getCopy(chessBoard), this.red, alpha, beta, step+1)if(value > alpha) {alpha = valuenextStep.id = chessBoard[tos[k].y][tos[k].x]nextStep.from = fromnextStep.to = tos[k]}if(alpha > beta) {return beta}} else {// beta剪枝value = this.robotNextStep(this.getCopy(chessBoard), this.black, alpha, beta, step+1)if(value < beta) {beta = valuenextStep.id = chessBoard[tos[k].y][tos[k].x]nextStep.from = fromnextStep.to = tos[k]}if(beta < alpha) {return alpha}}// 注意递归结束之后进行回溯操作this.swap(chessBoard, tos[k], from)chessBoard[tos[k].y][tos[k].x] = temp}}}}this.bestStep = nextStepreturn value
}

搜索深度定义如下:

// depth初始值为4
updateDepth: function() {let count = 0for(let i=0; i<10; i++) {for(let j=0; j<9; j++) {if(this.chessBoard[i][j] != 0) {count++}}}if(count < 8) {this.depth = 6} else if(count < 15) {this.depth = 5}return count
}

棋局评估

  • 棋子棋力
  • 棋子位置
  • 棋子机动性(控制力)
    机动性值是棋子的活动范围的度量,通 常棋子的活动范围越大,其机动性也越好,而 对棋盘的控制范围则是一个棋子的合法着法的 范围内,可以通过双方控制该位置的棋子数量 及棋子价值来决定哪方的机动性占优。
  • 棋子相互关系
    棋子相互关系是指在当前棋盘局势中,存在某个棋子可以吃掉对方棋子,以及某个棋子可以保护另外一个棋子的现象。
    在此我们只实现了棋子棋力和棋子位置相关的棋局评估,棋子控制力和棋子相互关系有些复杂并未实现:

棋子子力

在设置棋子子力时,将和帅的棋力设为一个较大的值,因为将和帅两方中有一个被吃则游戏结束。

// 棋子子力表
chessValue: {"K": 100000,  // 将、帅"A": 110,     // 仕、士"B": 110,     // 相、象"N": 300,     // 马、馬"R": 500,     // 车、車"C": 300,     // 炮、砲"P": 100      // 兵、卒
}

棋子位置

对于同一个棋子,位置不同,其价值也有所不同,比方说,过河卒子要比未过河的价值高得多。可以给每个不同的兵种在棋盘上的各个位置给出经验评估值,以确保评估的准确性。

chessPosValue: {'P': [[0, 3, 6, 9, 12, 9, 6, 3, 0],[18, 36, 56, 80, 120, 80, 56, 36, 18],[14, 26, 42, 60, 80, 60, 42, 26, 14],[10, 20, 30, 34, 40, 34, 30, 20, 10],[6, 12, 18, 18, 20, 18, 18, 12, 6],[2, 0, 8, 0, 8, 0, 8, 0, 2],[0, 0, -2, 0, 4, 0, -2, 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],],'R': [[14, 14, 12, 18, 16, 18, 12, 14, 14],[16, 20, 18, 24, 26, 24, 18, 20, 16],[12, 12, 12, 18, 18, 18, 12, 12, 12],[12, 18, 16, 22, 22, 22, 16, 18, 12],[12, 14, 12, 18, 18, 18, 12, 14, 12],[12, 16, 14, 20, 20, 20, 14, 16, 12],[6, 10, 8, 14, 14, 14, 8, 10, 6],[4, 8, 6, 14, 12, 14, 6, 8, 4],[8, 4, 8, 16, 8, 16, 8, 4, 8],[-1, 10, 6, 14, 12, 14, 6, 10, -2],],'N': [[4, 8, 16, 12, 4, 12, 16, 8, 4],[4, 10, 28, 16, 8, 16, 28, 10, 4],[12, 14, 16, 20, 18, 20, 16, 14, 12],[8, 24, 18, 24, 20, 24, 18, 24, 8],[6, 16, 14, 18, 16, 18, 14, 16, 6],[4, 12, 16, 14, 12, 14, 16, 12, 4],[2, 6, 8, 6, 10, 6, 8, 6, 2],[4, 2, 8, 8, 4, 8, 8, 2, 4],[0, 2, 4, 4, -2, 4, 4, 2, 0],[0, -4, 0, 0, 0, 0, 0, -4, 0],],'C': [[6, 4, 0, -10, -12, -10, 0, 4, 6],[2, 2, 0, -4, -14, -4, 0, 2, 2],[2, 2, 0, -10, -8, -10, 0, 2, 2],[0, 0, -2, 4, 10, 4, -2, 0, 0],[0, 0, 0, 2, 8, 2, 0, 0, 0],[-2, 0, 4, 2, 6, 2, 4, 0, -2],[0, 0, 0, 2, 4, 2, 0, 0, 0],[4, 0, 8, 6, 10, 6, 8, 0, 4],[0, 2, 4, 6, 6, 6, 4, 2, 0],[0, 0, 2, 6, 6, 6, 2, 0, 0],],
}

之后将二者加权综合计算得到总的棋局估计值:
b l a c k V a l u e = c h e s s V a l u e + c h e s s P o s V a l u e ∗ 8 blackValue = chessValue + chessPosValue*8 blackValue=chessValue+chessPosValue8
r e d V a l u e = c h e s s V a l u e + c h e s s P o s V a l u e ∗ 8 redValue = chessValue + chessPosValue*8 redValue=chessValue+chessPosValue8
棋 局 估 计 值 = b l a c k V a l u e − r e d V a l u e 棋局估计值 = blackValue - redValue =blackValueredValue

评估函数如下:

evaluation: function(chessBoard) {let blackValue =0, redValue=0, tos=[], targetfor(let i=0;i<10;i++) {for(let j=0;j<9;j++) {if(chessBoard[i][j]!=0) {if(chessBoard[i][j][0] == this.black) {blackValue += this.chessValue[chessBoard[i][j][1]]if(this.chessPosValue[chessBoard[i][j][1]] != undefined) {blackValue += this.chessPosValue[chessBoard[i][j][1]][9-i][j] * 8}} else {redValue += this.chessValue[chessBoard[i][j][1]]if(this.chessPosValue[chessBoard[i][j][1]] != undefined) {redValue += this.chessPosValue[chessBoard[i][j][1]][i][j] * 8}tos = this.generateNextStep(chessBoard, {"x": j, "y": i})}}}}return blackValue - redValue
}

棋盘UI

使用web技术来实现棋盘设计,如下图所示:
在这里插入图片描述
失败时状态:
在这里插入图片描述

不足

棋局评估函数还有很大的改进空间,有时会发生红方已经将军,但是黑方不保护“将”的情况。

参考文献

1.中国象棋博弈系统实现的关键技术探索_肖秀春
2.基于博弈树搜索算法的中国象棋游戏的设计与实现_刘淑琴


http://www.ppmy.cn/news/430067.html

相关文章

java实现中国象棋 源代码

java实现中国象棋 在网上找了很久中国象棋实现的源代码&#xff0c;终于找到了&#xff0c;下面就是源代码。 /**中国象棋Java版V3.0*源文件:Chess.java*添加功能:实现了当前棋局的保存*/import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.uti…

中国象棋,博大精深

文章目录 象棋简介象棋玩法棋子分数 象棋简介 象棋&#xff0c;是汉族棋类益智游戏&#xff0c;中国象棋在中国有着三千多年的历史&#xff0c;属于二人对抗性游戏的一种。由于用具简单、趣味性强&#xff0c;成为流行广泛的棋艺活动。是我国正式开展的78个体育项目之一。 象…

中国象棋详细设计分析

目 录 第一章 引言&#xff08;概述&#xff09; - 1 - 第二章 可行性分析 - 2 - 2.1 总体分析 - 2 - 2.2 开发环境介绍 - 2 - 2.2.1 软件开发环境 - 2 - 第三章 需求设计 - 2 - 第四章 详细设计 - 3 - 4.1 功能设计 - 3 - 4.1.1 功能说明 - 3- 4.1.2 对弈规则 - 4 - 4.1.3 相…

【180629】C++版智商超高的中国象棋游戏源码

这个中国象棋游戏可谓智商比较高&#xff0c;有时候你就是比不过电脑&#xff0c;哈&#xff0c;不服气不行&#xff0c; 试着玩了一局&#xff0c;没有赢电脑&#xff0c;因时间关系没有下第二局。不过&#xff0c;程序中还是有一点点缺憾&#xff0c;希望高人能够修正&#x…

C++实现双人中国象棋(一)——算法篇(附完整代码)

一、简介 最近突发奇想&#xff0c;要使用C做一个双人象棋的程序&#xff0c;昨天肝了一天&#xff0c;终于把算法部分完成了&#xff0c;下面把开发过程中的经验分享一下。 开发环境&#xff1a;Visual Studio 2019 语言标准&#xff1a;C11及以上 纠错&#xff1a;暂无 二、…

Java游戏开发——中国象棋联机版

游戏介绍&#xff1a; 中国象棋是起源于中国的一种棋戏&#xff0c;属于二人对抗性游戏的一种&#xff0c;在中国有着悠久的历史。由于规则简单&#xff0c;趣味性强&#xff0c;成为流行极为广泛的棋类游戏。 中国象棋使用方形格状棋盘及红黑二色圆形棋子进行对弈&#xff0c…

程序员开发工具

文章目录 redis redis brew install --cask another-redis-desktop-manager 安装地址 https://github.com/qishibo/AnotherRedisDesktopManager/

盘点目前初学者适合用的C语言编程工具!C语言初学者必看!

手机软件 1.C语言编译器&#xff1a;这是手机上的一个C语言编程软件&#xff0c;可以直接在手机上编译运行baiC语言程序&#xff0c; 如果你在学习C/C的过程中遇到了问题&#xff0c;可以来加入小编的企鹅圈问小编哦~小编很热情的(●’◡’●) 2.C编译器&#xff1a;也即C4d…