基于java语言的中国象棋设计与实现毕业设计(编辑修改稿)内容摘要:

于诸如 knight[90]这样不变化的位棋盘的初始化,将在 “伪着法生成 ”章节详述。 此处叙述走棋过程中随棋局变化的诸多位棋盘的初始化及相关操作。 首先,初始化 “BitBoard bitMask[90]”数组: BitBoard b = new BitBoard(0,0,1)。 for (int c = 0。 c 90。 c ++) { mask[c] = (b,c)。 } 其次,用一个叫 ChessPosition 类记录棋盘上某一状态的所有有用信息。 它包含了一个整型数组 int piece_in_square[90],还包含了一些位棋盘。 public class ChessPosition { int piece_in_square[90]。 北京物资学院 2020届毕业论文(设计) 8 int player。 //轮到哪方走棋, 2:红方走, 1:黑方走 BitBoard allPieces。 BitBoard redKing。 //红帅 BitBoard blackKing。 //黑将 BitBoard redRooks。 //红车 BitBoard blackRooks。 //黑车 BitBoard redKnights。 //红马 BitBoard blackKnights。 //黑马 BitBoard redCannon。 //红炮 BitBoard redCannon。 //黑炮 BitBoard redBishops。 //红相 BitBoard blackBishops。 //黑象 BitBoard redAdvisor。 //红仕 BitBoard blackAdvisor。 //黑士 BitBoard redPawns。 //红兵 BitBoard blackPawns。 //黑卒 BitBoard redPieces。 //所有红棋子 BitBoard blackPieces。 //所有黑棋子 }。 初始化 “piece_in_square[]”数组。 piece_in_square[0] = RED_ROOK。 piece_in_square[1] = RED_KNIGHT。 piece_in_square[2] = RED_BISHOP。 … piece_in_square[89] = BLACK_ROOK。 现在初始化其他一些位棋盘 : for (c = 0。 c 90。 c ++) { switch (piece_in_square[c]) { case : RED_ROOK = (,bitMask[c])。 (,bitMask[c])。 break。 … } } 位棋盘的更新 当棋盘局面变动后,某些位棋盘就需要被更新。 例如记录白子所在位置的“WhitePieces”位棋盘。 假如我们把 h2 格的红炮移动到 h9 格(炮二进七),吃掉黑棋的一个马,需要更新如下位棋盘: allPieces 北京物资学院 2020届毕业论文(设计) 9 redPieces redCannons blackpieces blackKnights 首先,要把 redPieces, redCannons 位棋盘的 “h2”位清零,然后把他们的 “h9”位置 1。 /* clear a bit with the XOR operation */ = (,bitMask[h2]。 = (,bitMask[h2])。 = (,bitMask[h2]。 /* set a bit with the OR operation */ = (,bitMask[h9])。 = (,bitMask[h9])。 现在我们要将 blackPieces 和 blackKnights 位棋盘的 h9 位清除,因为那里的黑马被吃掉了。 /* clear the captured piece */ = (,bitMask[h9])。 = (,bitMask[h9] —— Zobrist 键值 比较局面的方法 在写中国象棋程序时,需要比较两个局面看它们是否相同。 如果比较每个棋子的位置,或许不需要花很多时间,但是实战中每秒种需要做成千上万次比较,因此这样会使比较操作变成瓶颈的。 另外,需要比较的局面数量多得惊人,要存储每个棋子的位置,需要占用非常大的空间。 一个解决方案是建立一个标签,通常是 64 位。 由于 64 位不足以区别每个局面,所以仍然存在冲突的标签 , 但实战中这种情况非常罕见。 Zobrist 键值的工作原理 Zobrist 键值的工作原理 用 Zobrist 技术产生的键 值,表面上与局面没什么关系。 如果一个棋子动过了,就会得到完全不同的键值,所以这两个键值不会挤在一块儿或者冲突。 当把它们用作散列表键值的时候会非常有效。 另一个优点在于,键值的产生是可以逐步进行的。 例如,红马在 e5 格,那么键值里一定异或过一个 “zobrist[KNIGHT][RED][E5]”。 如果再次异或这个值,那么根据异或的工作原理,这个 “马 ”就从键值里删除了。 北京物资学院 2020届毕业论文(设计) 10 这就是说,如果有当前局面的键值,并且需要把红马从 e5 移到 f7,你只要异或一个 “红马在 e5”的键值,把马从 e5 格移走,并且异或一个 “红 马在 f7”的键值,把红马放在 f7 上。 比起从头开始一个个棋子去异或,这样做可以得到同样的键值。 如果要改变着子的一方,只要异或一个 “改变着子方 ”的键值就可以了。 用这种方法,可以在搜索根结点的时候构造一个 Zobrist 键值,在搜索时通过走子函数“MakeMove()”来更新键值,一直让它保持和当前局面同步。 Zobrist 键值的实现方法 实现 Zobrist 必须从多维的 64 位数组开始,每个数组含有一个随机数。 在 Java中, “()”函数返回一个 64 位的随机数值。 这个 函数用来填满一个 long 型( 64 位)的三维数组:棋子的类型、棋子的颜色和棋子的位置: long zobrist[pcMAX][coMAX][sqMAX]。 程序启动时就把这个数组用随机数填满。 要为一个局面产生 Zobrist 键值,首先把键值设成零,然后找棋盘上的每个子,并且让键值跟 “zobrist[pc][co][sq]”做异或 (通过 “^”运算符 )运算。 如果局面由红 方走,那么别去动它,如果是黑方走,你还要在键值上异或一个 64 位的随机常数。 Java 中 实现 Zobrist 键值 本系统使用 一个 key 和一个 lock 结合来区分每个局面,这 样 发生冲突(即两个局面对应的 key 和 lock 一样)的概率几乎为 0。 示例代码及相关说明如下 填充数组 上述的三维数组现在改变为二维(将颜色与棋子兵种类型合并) …… public static long ZobristKeyPlayer。 //改变走子方的 key public static long ZobristLockPlayer。 //改变走子方的 lock public static long[][] ZobristKeyTable = new long[14][90]。 public static long[][] ZobristLockTable = new long[14][90]。 …… static{ …… zobristGen()。 } public static void zobristGen() { int i, j。 Random rand = new Random()。 long RandSeed。 RandSeed = 1。 (RandSeed)。 北京物资学院 2020届毕业论文(设计) 11 ZobristKeyPlayer = ()。 for (i = 0。 i 14。 i ++) { //0:红帅 1:红仕 2:红相 3:红马 4:红车 5:红炮 6:红兵 //7:黑将 8:黑士 9:黑象 10:黑马 11:黑车 12:黑炮 13:黑卒 for (j = 0。 j 90。 j ++) { ZobristKeyTable[i][j] = ()。 } } ZobristLockPlayer = ()。 for (i = 0。 i 14。 i ++) { for (j = 0。 j 90。 j ++) { ZobristLockTable[i][j] = ()。 } } } 移子函数 当移动(添加、删除)一个棋子时,将当前局面的 Zobrist 键值与键值表中该棋子的键值进行异或操作,同时也与改变走子方的键值进行异或操作。 public class ChessPosition{ long ZobristKey, ZobristLock。 //当前局面的 zobrist 键值 public ChessPosition{ …… ZobristKey=0。 //初始化为 0 ZobristLock=0。 …… } …… public void makeMove(int Square, int Piece, boolean IsAdd) { …… ZobristKey^=ZobristKeyTable[PieceType][Square]。 ZobristLock^=ZobristLockTable[PieceType][Square]。 ZobristKey ^= ZobristKeyPlayer。 //改变走子方 ZobristLock ^= ZobristLockPlayer。 …… } } 北京物资学院 2020届毕业论文(设计) 12 4 系统实现 着法生成 着法生成在不同的象棋引擎中差异较大。 我选择 使用位棋盘生成着法的基本原理。 之所以 用这种方式生成着法 , 是 因为 生成着法耗费一定的时间。 如果引擎在检查了一部分着法后发现了必须走的棋,那它就无需生成余下的棋步了。 因此,可能先生成所有吃子的着法,如果没有满意的棋再生成余下的着法。 国际象棋引擎 Crafty(其作者是 Robert Hyatt 博士 )使用三个着法生成函数。 一个用来生成所有伪合法吃子着法,一个生成所有伪合法不吃子着法,最后一个生成所有摆脱被将军状态的着法。 注意前两个函数生成的是伪合法的着法。 中国象棋的着法生成与此类似,先生成所有伪合法的着法,存入静态数组中。 在对局中可以用 “查表 ”的方式查找生成的伪着法,并对其合法性作出判断。 这样可以节省大量的时间。 伪合法着法的生成 伪合法着法包含几类: 各兵种的不吃子着法 ; 各兵种的吃子着法 ; “将 ”和摆脱 “将 ”的着法。 其中,马、相(象)、兵、帅(将)、仕(士)的吃子着法与其对应的不吃子着法规则相同。 (伪合法着法并不考虑被吃的棋子的颜色 ——该棋子是对方的棋子 还是己方的棋子,也不考虑该子是否能动,例如动了该子,双方的帅将会面。 )炮和车的不吃子着法规则相同,但分为纵向横向行走两类。 炮的吃子着法分为纵向和横向两类,车的吃子着法也分为纵向和横向两类。 马和象的着法要考虑蹩马腿和塞象眼。 将军的着法单独作为一类。 本程序使用静态数组存储生成的伪 合法着法,先对其作一些说明。 数组及其下标的含义 : 保存帅(将)、仕(士)、相(相)、马、兵的伪合法静态数组如下: public static final int[][] KingMoves=new int[90][8]。 public static final int[][] AdvisorMoves=new int[90][8]。 public static final int[][] BishopMoves=new int[90][8]。 public static final int[][] ElephantEyes=new int[90][4]。 public static final int[][] KnightMoves=new int[90][12]。 public static final int[][] HorseLegs=new int[90][8]。 public static final int[][][] PawnMoves=new int[90][2][4]。 北京物资学院 2020届毕业论文(设计) 13 第一个下标说明棋子所在的格,第二个下标含义不尽相同。 帅(将)在某个位置最多有 4 种走法,例如 KingMoves[13][0]=12 表示帅在 13 格( e1 格)时可以走到 12 格(当然,也可以走到 1 22 格,保存到其他几个数组元素中)。 第 5种(如果前面只有 3 种着法,则此处是第 4 种)保存的是非法着法即KingMoves[13][4]=1,其作用作为查询算法的 “哨兵 ”,提高查询算法的速度。 为了速度(以位移运算取代除法运。
阅读剩余 0%
本站所有文章资讯、展示的图片素材等内容均为注册用户上传(部分报媒/平媒内容转载自网络合作媒体),仅供学习参考。 用户通过本站上传、发布的任何内容的知识产权归属用户或原始著作权人所有。如有侵犯您的版权,请联系我们反馈本站将在三个工作日内改正。