说明版权声明环境一、效果二、测试Demo三、静态内容四、主要规则五、棋子移动规则后记 说明 博客日期:2022年2月5日13点14分星期六无界面版中国象棋,是基于JDK 17的版本,虽然说是无界面,但其实还是能看到命令行界面的;本工程尽可能采用模块化编程,并没有一个完全体,如有需要可以自行组装,默认组装了一个简单功能;已有的功能包括棋子移动规则、将军规则、移动棋子等,计时、悔棋、死棋等功能按需自行完善;本工程主要供参考棋子移动规则实现思路,后期可能移植到开发板上打发时间(摸鱼)。项目仓库:ChineseChess ,如果仓库失效,请参考本文代码。 版权声明
该工程的代码均为本文作者撰写,无其他参考,允许使用在任何场景,遵循GPLv3.0开源协议,但转载本文请标注出处。
环境IntelliJ IDEA 2021.3.1 (Ultimate Edition)For educational use only.Runtime version: 11.0.13+7-b1751.21 amd64VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.Windows 10 10.0GC: G1 Young Generation, G1 Old GenerationMemory: 2048MCores: 4Kotlin: 213-1.5.10-release-949-IJ6461.79Java Development Kit 17
一、效果 先手是红方,当红方执棋完毕后,交换显示为黑方,只需要输入起始坐标和目标坐标即可,注意,行坐标优先。
棋子移动不符合移动规范触发规则限制。
将军!当棋子监测到可以吃掉对方将帅即认为被将军。
如果被将军的一方还不肯阻挡被将,那么游戏可能结束,如果按下y或Y则可以重开游戏,因为测试是在一个循环内。
这样就是主函数,没啥好说的,起始相当于一个测试Demo,功能随意拼装,比如符合棋子移动规则后是否需要移动棋子、是否检查将军条件等;
package chess;import java.util.Scanner;public class StartMain { public static void main(String[] args) { while (true) { PieceRule pieceRule = new PieceRule(); Scanner sc = new Scanner(System.in); while (true) { Const.printPiece(pieceRule.chessboard, pieceRule.getFlagPlayer()); if (null == pieceRule.getPosGeneral(pieceRule.getFlagPlayer())) { System.err.println("游戏结束!是否重开?"); String choice = sc.next(); if ("Y".equals(choice) || "y".equals(choice) || "Yes".equals(choice) || "yes".equals(choice)) { break; } System.exit(0); } System.out.format("当前阵营:%s方n", Const.PIECE_FORMAT.getProperty(String.valueOf(pieceRule.getFlagPlayer()))); System.out.print("请选择棋子行坐标X、列坐标Y、落子行坐标X、列坐标Y(空格间隔):"); byte posSrcX = sc.nextByte(); byte posSrcY = sc.nextByte(); byte posDstX = sc.nextByte(); byte posDstY = sc.nextByte(); if (posSrcX < 0 || posSrcY < 0 || posDstX < 0 || posDstY < 0) { System.err.println("输入不合法,请重新输入!"); continue; } if (pieceRule.isPermitRuleMove(posSrcX, posSrcY, posDstX, posDstY, pieceRule.getFlagPlayer())) { pieceRule.moveToPos(posSrcX, posSrcY, posDstX, posDstY); System.out.println("移动成功!"); if (pieceRule.isCheckmate(posDstX, posDstY, pieceRule.getFlagPlayer())) { System.err.println("将军!"); } pieceRule.exchangeFlagPlayer(); } else { System.err.println("无法移动!"); } } } }}
三、静态内容package chess;import java.util.Properties;public class Const { public final static short AMOUNT_PIECE_TYPE = 7; public final static short FLAG_R = 0xFF; public final static short FLAG_B = 0xFE; public final static boolean MOVE_HORIZonTAL = true; public final static boolean MOVE_VERTICAL = false; public final static short BLOCK = 0x00; public final static short[] PIECE_R = new short[]{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; public final static short[] PIECE_B = new short[]{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}; public final static String[] PIECE_NAME = new String[]{"帅", "仕", "相", "車", "馬", "砲", "兵", "将", "士", "象", "车", "马", "炮", "卒"}; public final static Properties PIECE_FORMAT = initPieceCorresponding(); public final static byte[][] RANGE_CAMP = new byte[][]{{0, 4}, {5, 9}}; public final static byte[][] RANGE_CHESSBOARD = new byte[][]{{0, 8}, {0, 9}}; public final static byte[][] RANGE_SUDOKU = new byte[][]{{3, 5}, {0, 2}, {7, 9}}; public final static byte LIMIT_STEP_CAVALRY = 3; public final static byte LIMIT_STEP_PRIME_MINISTER = 4; protected static Properties initPieceCorresponding() { Properties properties = new Properties(); for (int i = 0; i < AMOUNT_PIECE_TYPE; ++i) { properties.setProperty(String.valueOf(PIECE_R[i]), PIECE_NAME[i]); } for (int i = AMOUNT_PIECE_TYPE; i < PIECE_NAME.length; ++i) { properties.setProperty(String.valueOf(PIECE_B[i - AMOUNT_PIECE_TYPE]), PIECE_NAME[i]); } properties.setProperty(String.valueOf(BLOCK), "〇"); properties.setProperty(String.valueOf(FLAG_R), "红"); properties.setProperty(String.valueOf(FLAG_B), "黑"); return properties; } public static void printPiece(short[][] posPiece, short flagPlayer) { String[] numberId = new String[]{"00", "01", "02", "03", "04", "05", "06", "07", "08", "09"}; if (FLAG_R == flagPlayer) { System.out.println("-- 零 一 二 三 四 五 六 七 八"); for (int i = RANGE_CHESSBOARD[1][0]; i < RANGE_CHESSBOARD[1][1] + 1; ++i) { System.out.print(numberId[i] + " "); for (int j = RANGE_CHESSBOARD[0][0]; j < RANGE_CHESSBOARD[0][1] + 1; ++j) { System.out.print(PIECE_FORMAT.getProperty(String.valueOf(posPiece[i][j])) + " "); } System.out.println(); } } else if (FLAG_B == flagPlayer) { System.out.println("-- 八 七 六 五 四 三 二 一 零"); for (int i = RANGE_CHESSBOARD[1][1]; i >= RANGE_CHESSBOARD[1][0]; --i) { System.out.print(numberId[i] + " "); for (int j = RANGE_CHESSBOARD[0][1]; j >= RANGE_CHESSBOARD[0][0]; --j) { System.out.print(PIECE_FORMAT.getProperty(String.valueOf(posPiece[i][j])) + " "); } System.out.println(); } } }}
四、主要规则package chess;import java.util.Arrays;public class RuleDefined { private short flagPlayerCurrent = Const.FLAG_R; public short[][] chessboard = new short[][]{ {0x14, 0x15, 0x13, 0x12, 0x11, 0x12, 0x13, 0x15, 0x14}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00}, {0x17, 0x00, 0x17, 0x00, 0x17, 0x00, 0x17, 0x00, 0x17}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07, 0x00, 0x07}, {0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x04, 0x05, 0x03, 0x02, 0x01, 0x02, 0x03, 0x05, 0x04} }; public short getFlagPlayer() { return this.flagPlayerCurrent; } public void exchangeFlagPlayer() { if (Const.FLAG_R == this.flagPlayerCurrent) { this.flagPlayerCurrent = Const.FLAG_B; } else if (Const.FLAG_B == this.flagPlayerCurrent) { this.flagPlayerCurrent = Const.FLAG_R; } } public byte[] getPosGeneral(short flagPlayer) { if (Const.FLAG_R == flagPlayer) { for (byte i = Const.RANGE_SUDOKU[1 + 1][0]; i < Const.RANGE_SUDOKU[1 + 1][1] + 1; ++i) { for (byte j = Const.RANGE_SUDOKU[0][0]; j < Const.RANGE_SUDOKU[0][1] + 1; ++j) { if (Const.PIECE_R[0] == chessboard[i][j]) { return new byte[]{i, j}; } } } } else if (Const.FLAG_B == flagPlayer) { for (byte i = Const.RANGE_SUDOKU[1][0]; i < Const.RANGE_SUDOKU[1][1] + 1; ++i) { for (byte j = Const.RANGE_SUDOKU[0][0]; j < Const.RANGE_SUDOKU[0][1] + 1; ++j) { if (Const.PIECE_B[0] == chessboard[i][j]) { return new byte[]{i, j}; } } } } return null; } public boolean isCheckmate(byte posX, byte posY, short flagPlayer) { // 优先判断两方将帅是否在同一直线且在两者之间没有棋子阻隔 byte[] posGeneralR = this.getPosGeneral(Const.FLAG_R); byte[] posGeneralB = this.getPosGeneral(Const.FLAG_B); if (null == posGeneralR || null == posGeneralB) { return true; } if (posGeneralR[1] == posGeneralB[1] && 0 == this.getHinderCount(posGeneralB[0], posGeneralR[0], posGeneralB[1], Const.MOVE_VERTICAL)) { return true; } if (Const.FLAG_R == flagPlayer) { byte[] posGeneralOpposite = this.getPosGeneral(Const.FLAG_B); if (null == posGeneralOpposite) { return true; } return this.isPermitRuleMove(posX, posY, posGeneralOpposite[0], posGeneralOpposite[1], Const.FLAG_R); } else if (Const.FLAG_B == flagPlayer) { byte[] posGeneralOpposite = this.getPosGeneral(Const.FLAG_R); if (null == posGeneralOpposite) { return true; } return this.isPermitRuleMove(posX, posY, posGeneralOpposite[0], posGeneralOpposite[1], Const.FLAG_B); } throw new IndexOutOfBoundsException("该阵营标识不存在,请提供正确的阵营标识。"); } public boolean isPermitRuleMove(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY, short flagPlayer) { if (!this.isOverRange(posSrcX, posSrcY, Const.RANGE_CHESSBOARD[1], Const.RANGE_CHESSBOARD[0])) { // 如果初始位置选取超出了棋盘区域则不可移动 return false; } else if (!this.isOverRange(posDstX, posDstY, Const.RANGE_CHESSBOARD[1], Const.RANGE_CHESSBOARD[0])) { // 如果目标位置选取超出了棋盘区域则不可移动 return false; } if (!this.isSelfCamp(flagPlayer, posSrcX, posSrcY) || this.isSelfCamp(flagPlayer, posDstX, posDstY)) { // 如果源起始棋子不是己方,或者目标位置棋子还是己方的,均不可移动 return false; } switch (chessboard[posSrcX][posSrcY]) { case 0x01: case 0x11: return this.ruleGeneral(posSrcX, posSrcY, posDstX, posDstY, flagPlayer); case 0x02: case 0x12: return this.ruleLifeguard(posSrcX, posSrcY, posDstX, posDstY, flagPlayer); case 0x03: case 0x13: return this.rulePrimeMinister(posSrcX, posSrcY, posDstX, posDstY, flagPlayer); case 0x04: case 0x14: return this.ruleChariot(posSrcX, posSrcY, posDstX, posDstY); case 0x05: case 0x15: return this.ruleCavalry(posSrcX, posSrcY, posDstX, posDstY); case 0x06: case 0x16: return this.ruleBattery(posSrcX, posSrcY, posDstX, posDstY); case 0x07: case 0x17: return this.ruleInfantry(posSrcX, posSrcY, posDstX, posDstY, flagPlayer); default: } return false; } public void moveToPos(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY) { chessboard[posDstX][posDstY] = chessboard[posSrcX][posSrcY]; chessboard[posSrcX][posSrcY] = Const.BLOCK; } public byte[] calStepAbs(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY) { return new byte[]{(byte) Math.abs(posDstX - posSrcX), (byte) Math.abs(posDstY - posSrcY)}; } public byte[] calStep(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY) { return new byte[]{(byte) (posDstX - posSrcX), (byte) (posDstY - posSrcY)}; } public boolean isOverRange(byte posX, byte posY, byte[] posLimitX, byte[] posLimitY) { return (posLimitX[0] <= posX && posX <= posLimitX[1] && posLimitY[0] <= posY && posY <= posLimitY[1]); } public boolean isSelfCamp(short flagPlayer, byte posPieceX, byte posPieceY) { if (Const.FLAG_R == flagPlayer) { return Arrays.binarySearch(Const.PIECE_R, this.chessboard[posPieceX][posPieceY]) >= 0; } else if (Const.FLAG_B == flagPlayer) { return Arrays.binarySearch(Const.PIECE_B, this.chessboard[posPieceX][posPieceY]) >= 0; } return false; } public byte getHinderCount(byte posMin, byte posMax, byte posFix, boolean dir) { byte countHinder = 0; for (int i = posMin + 1; i < posMax; ++i) { if (dir && Const.BLOCK != chessboard[posFix][i]) { // 水平移动 ++countHinder; } else if (!dir && Const.BLOCK != chessboard[i][posFix]) { // 垂直移动 ++countHinder; } } return countHinder; } public boolean ruleGeneral(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY, short flagPlayer) { return false; } public boolean ruleLifeguard(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY, short flagPlayer) { return false; } public boolean rulePrimeMinister(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY, short flagPlayer) { return false; } public boolean ruleChariot(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY) { return false; } public boolean ruleCavalry(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY) { return false; } public boolean ruleBattery(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY) { return false; } public boolean ruleInfantry(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY, short flagPlayer) { return false; }}
五、棋子移动规则package chess;public class PieceRule extends RuleDefined { @Override public boolean ruleGeneral(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY, short flagPlayer) { byte[] tmpStepAbs = this.calStepAbs(posSrcX, posSrcY, posDstX, posDstY); if (1 != tmpStepAbs[0] + tmpStepAbs[1]) { return false; } return Const.FLAG_R == flagPlayer && this.isOverRange(posDstX, posDstY, Const.RANGE_SUDOKU[2], Const.RANGE_SUDOKU[0]) || Const.FLAG_B == flagPlayer && this.isOverRange(posDstX, posDstY, Const.RANGE_SUDOKU[1], Const.RANGE_SUDOKU[0]); } @Override public boolean ruleLifeguard(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY, short flagPlayer) { byte[] tmpStepAbs = this.calStepAbs(posSrcX, posSrcY, posDstX, posDstY); if (tmpStepAbs[0] != tmpStepAbs[1]) { return false; } return Const.FLAG_R == flagPlayer && this.isOverRange(posDstX, posDstY, Const.RANGE_SUDOKU[2], Const.RANGE_SUDOKU[0]) || Const.FLAG_B == flagPlayer && this.isOverRange(posDstX, posDstY, Const.RANGE_SUDOKU[1], Const.RANGE_SUDOKU[0]); } @Override public boolean rulePrimeMinister(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY, short flagPlayer) { byte[] tmpStepAbs = this.calStepAbs(posSrcX, posSrcY, posDstX, posDstY); if (Const.LIMIT_STEP_PRIME_MINISTER == tmpStepAbs[0] + tmpStepAbs[1] && tmpStepAbs[0] == tmpStepAbs[1]) { // 步长之和绝对值必须是4,且步长绝对值必须相等 if (Const.BLOCK != chessboard[(posSrcX + posDstX) / (1 + 1)][(posSrcY + posDstY) / (1 + 1)]) { // 判断到目标点的中间点是否有棋子阻碍 return false; } return Const.FLAG_R == flagPlayer && Const.RANGE_CAMP[0][1] < posDstX|| Const.FLAG_B == flagPlayer && posDstX < Const.RANGE_CAMP[1][0]; } return false; } @Override public boolean ruleChariot(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY) { if (posDstX != posSrcX && posDstY == posSrcY) { // 判断是否为垂直移动 if (posSrcX < posDstX) { return 0 == getHinderCount(posSrcX, posDstX, posSrcY, Const.MOVE_VERTICAL); } else { return 0 == getHinderCount(posDstX, posSrcX, posSrcY, Const.MOVE_VERTICAL); } } else if (posDstX == posSrcX && posDstY != posSrcY) { // 判断是否为水平移动 if (posSrcY < posDstY) { return 0 == getHinderCount(posSrcY, posDstY, posSrcX, Const.MOVE_HORIZONTAL); } else { return 0 == getHinderCount(posDstY, posSrcY, posSrcX, Const.MOVE_HORIZONTAL); } } return false; } @Override public boolean ruleCavalry(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY) { byte[] tmpStep = this.calStep(posSrcX, posSrcY, posDstX, posDstY); byte[] tmpStepAbs = new byte[]{(byte) Math.abs(tmpStep[0]), (byte) Math.abs(tmpStep[1])}; if (Const.LIMIT_STEP_CAVALRY != tmpStepAbs[0] + tmpStepAbs[1] || 0 == tmpStepAbs[0] || 0 == tmpStepAbs[1]) { return false; } // 判断棋子目标位置是水平运动还是垂直运动 if (tmpStepAbs[0] < tmpStepAbs[1]) { // 由于calStep()函数是利用目标位置和源位置作差,故大于零的情况说明目标位置在右 if (0 < tmpStep[1]) { // 判断棋子原位置右边是否为空 return Const.BLOCK == chessboard[posSrcX][posSrcY + 1]; } else { return Const.BLOCK == chessboard[posSrcX][posSrcY - 1]; } } else if (tmpStepAbs[1] < tmpStepAbs[0]) { // 由于calStep()函数是利用目标位置和源位置作差,故大于零的情况说明目标位置在下方 if (0 < tmpStep[0]) { return Const.BLOCK == chessboard[posSrcX + 1][posSrcY]; } else { return Const.BLOCK == chessboard[posSrcX - 1][posSrcY]; } } return false; } @Override public boolean ruleBattery(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY) { byte hinderCount = -1; if (posDstX != posSrcX && posDstY == posSrcY) { // 判断是否为垂直移动 if (posSrcX < posDstX) { hinderCount = getHinderCount(posSrcX, posDstX, posSrcY, Const.MOVE_VERTICAL); } else { hinderCount = getHinderCount(posDstX, posSrcX, posSrcY, Const.MOVE_VERTICAL); } } else if (posDstX == posSrcX && posDstY != posSrcY) { // 判断是否为水平移动 if (posSrcY < posDstY) { hinderCount = getHinderCount(posSrcY, posDstY, posSrcX, Const.MOVE_HORIZONTAL); } else { hinderCount = getHinderCount(posDstY, posSrcY, posSrcX, Const.MOVE_HORIZONTAL); } } return 1 == hinderCount && Const.BLOCK != chessboard[posDstX][posDstY] || 0 == hinderCount && Const.BLOCK == chessboard[posDstX][posDstY]; } @Override public boolean ruleInfantry(byte posSrcX, byte posSrcY, byte posDstX, byte posDstY, short flagPlayer) { byte[] tmpStepAbs = this.calStepAbs(posSrcX, posSrcY, posDstX, posDstY); // 步数和必须等于1 if (1 != tmpStepAbs[0] + tmpStepAbs[1]) { return false; } // 如果只是垂直移动是允许的,但是只许进不许退 if (0 == tmpStepAbs[1]) { return Const.FLAG_R == flagPlayer && posDstX < posSrcX || Const.FLAG_B == flagPlayer && posSrcX < posDstX; } // 如果想要水平移动必须跨过楚河汉界 return Const.FLAG_R == flagPlayer && posSrcX < Const.RANGE_CAMP[1][0] || Const.FLAG_B == flagPlayer && Const.RANGE_CAMP[0][1] < posSrcX; }}
后记没啥好说的,注释都写了大部分我想写的内容,便于大家理解,其他不想写的注解我也没写(懒),这个项目其实从2020年8月5日就写了,当然,当时也写的差不多啦,但是代码冗余、耦合性高、乱成一坨xxx,最后因为我去做别的事情,就再也没理过,但是终究是没完成,于是这几天就顺带完成下(当然是重新写了)。
这个工程主要是方便理解思路,可以很便捷的移植到其他编程语言中,不过换句话来说,有谁会没事去理解这思路呢,网上现成的代码包括可视化界面音效一大堆,直接拿来用就好,所以说到底,只是我想做一个属于我自己的项目而已,当然之前还有许多项目没完成,后续可能有机会也会逐个完善。
正值新春佳节,祝大家新春快乐!虎年大吉!
Bye