5.主要算法和实现
5.1核心算法
5.1.1绘制棋盘
算法伪码
输入:棋子点2维数组,炮、兵、卒位置坐标。
输出:显示棋盘。
初始化棋子点坐标;
绘制10条横线;//for循环
绘制9条纵线;//for循环
绘制2个九宫格;//2*2共5条斜线;
绘制楚河汉界;//2个字符串;
绘制炮、兵、卒的位置标记;
//中间的6个兵和5个炮,标记需要画8条短直线;其它5个兵、卒都画
5条短直线。
画竖线标记;
// 红方从右到左画中文简体汉字一到九,黑方从右到左画阿拉伯数字1到9。
程序代码
Graphics2D g2 = (Graphics2D) g;
// 兵、卒、炮标记笔画
BasicStroke bsFlag = new BasicStroke(2);
// 楚河汉界、棋盘边框笔画
BasicStroke bsLine = new BasicStroke(2);
// 棋盘线笔画
BasicStroke bs1 = new BasicStroke(1);
//绘制直线
drawLines(g2, bsLine, bs1);
//绘制九宫格
drawJiuGongLines(g2, bs1);
//绘制楚河漢界
drawChuheHanjieString(g2);
//绘制炮和兵标记
drawPaoBingFlag(g2, bsFlag);
// 如果有棋子移动,画出2个提示框,每个提示框由8条线组成
drawMoveFlag(g2);
drawWillMoveFlag(g2);
5.1.2绘制棋子
算法伪码
使用图片标签;
程序代码
public class ChessPiece extends JLabel {
}
setIcon(PieceUtil.getImageIcon("hongju.png"));
5.1.3记录棋谱
算法伪码
第1字是棋子的名称。如“马”或“车”。
第2字是棋子所在纵线的数码。
第3字表示棋子移动的方向:横走用“平”,向前走用“进”或“上”,向后走用“退”或“下”。
第4字是棋子进退的格数,或者到达纵线的数码。
举例
“炮二平五”,表示红炮从纵线二平移到纵线五。
“马8进7”,表示黑马从纵线8向前走到纵线7。
“车2退3”,表示黑车沿纵线2向后移动3格
当一方有2个以上名称相同的棋子位于同一纵线时,需要用“前”或“后”来加以区别。例如,“前马退六”(表示前面的红马退到直线六)、“后炮平5”(表示后面的黑炮平移到直线5)
程序代码
int startX = (int) pStart.getX();
int startY = (int) pStart.getY();
int endX = (int) pEnd.getX();
int endY = (int) pEnd.getY();
5.1.4游戏规则
5.1.4.1棋子移动
算法伪码
输入:移动棋子的起点和终点坐标,棋子点2维数组
输出:棋子能否移动;
移动棋子需要考虑的共同问题:
起点和终点的位置的坐标是否越界;
起点和终点位置的棋子,是否是同一方的;
棋子移动后,己方是否会被将军;
棋子移动后,将帅是否会对脸。
移动棋子需要考虑的特殊问题:
將(帥)、仕(士)是否走出九宫;
象是否过河,象眼是否被堵;
馬是否别腿;
炮要翻山吃子;
兵过河前只能前进,过河后可以前进或左右移动;
車的移动规则:
如果起点和终点横坐标相同,2点之间的竖线上没有其它的棋子。
否则,如果起点和终点纵坐标相同,2点之间的横线上没有其它的棋子。
馬的移动规则:
水平方向移动2步,垂直方向移动1步;
水平方向移动1步,垂直方向移动2步。
炮的移动规则:
如果起点和终点横坐标相同,2点之间的竖线上没有其它的棋子,或者有且只有1个棋子。
否则,如果起点和终点纵坐标相同,2点之间的横线上没有其它的棋子或者有且只有1个棋子。
象的移动规则:
起点和终点的横坐标和纵坐标之差都是2.
仕的移动规则:
起点和终点的横坐标和纵坐标之差都是1.
帥的移动规则:
起点和终点的横坐标差1或纵坐标差1.
兵的移动规则:
起点和终点的纵坐标差1;横坐标差1。
public static boolean allRule(ChessPiece piece, int startX,
int startY, int endX, int endY, ChessPoint[][]
chessPoints) {
// 重用車、馬、炮的移动规则
if (category == PieceCategory.JU || category ==
PieceCategory.MA
|| category == PieceCategory.PAO) {
return jmpRule(category, startX, startY, endX,
endY, chessPoints);
}
// 基本打谱、高级打谱共用的帥、仕、兵、相的移动规则
if (category.equals(PieceCategory.SHUAI)
|| category.equals
(PieceCategory.HONGSHI)
|| category.equals(PieceCategory.BING)
|| category.equals
(PieceCategory.HONGXIANG)
|| category.equals(PieceCategory.JIANG)
|| category.equals(PieceCategory.HEISHI)
|| category.equals(PieceCategory.ZU)
|| (category.equals
(PieceCategory.HEIXIANG))) {
return ssxbRule(piece, startX, startY, endX,
endY, chessPoints);
}
5.1.4.2将帅是否对脸检测算法:
获得將帥的坐标;
如果移动的棋子是將或帥
將移动,终点和帥之间是否没有棋子。
帥移动,终点和將之间是否没有棋子。
否则,
將和帥之间是否有且只有一个棋子,此棋子是正在移动的棋子。
5.1.4.3己方是否被将军算法:
获取己方將帥的位置;
获取对方車馬炮兵的位置;(車馬炮可能有2个,兵可能有5个)
如果車将军,返回true;
否则,如果馬将军,返回true;
否则,如果炮将军,返回true;
否则,如果兵将军,则返回true。
注:判断一个棋子是否将军,只需要判断一个棋子移动到將的位置,是否符合游戏规则就可以了。
返回false;
程序代码
public static boolean isDuiLian(ChessPiece piece, int endX, int endY,
ChessPoint[][] chessPoints) ;
5.2.5走棋算法
算法伪码
棋手通过鼠标点击来走棋。先单击要走的棋子,然后在目的位置上再单击一次,就实现一步走棋。正规比赛中,棋手拿起棋子,就必须走该棋子,除非该棋子没有合理走法。在这里,没有必要严格执行这条规则。棋手单击本方棋子之后,又再次单击本方另一个棋子,则说明放弃原有的选择,新的棋子为即将走的棋子。
为了在棋盘上有明显的视觉效果,棋手每次选中己方棋子都要突出显示,发出声音并让棋子闪烁。棋子移动后,移动的起点和终点也突出显示。
如果,第一次点击本方棋子
保存棋子的起点坐标,选中棋子闪烁。
//点击棋盘和对方棋子忽略不计
否则,
点击本方棋子,则更新选中的棋子,更新起点坐标。
点击对方棋子,吃子是否合法,合法则移动。
点击棋盘,移动是否合法,合法则移动。
程序代码
public void mousePressed(MouseEvent e) ;
5.1.6 棋子闪烁
算法伪码
算法用途:红方或黑方选中棋子时,选中的棋子应该闪烁,提示用户。
算法描述:
用一个ChessPiece类型的变量 winkPiece保存需要闪烁的棋子,用一个boolean类型的变量needWink标志棋子是否需要闪烁,然后新增一个线程,控制棋子的闪烁。
程序代码
while (true) {
try {
if (needWink) {//棋子需要闪烁
winkPiece.setVisible(false);//隐藏棋子
Thread.sleep(600);
winkPiece.setVisible(true);//显示棋子
Thread.sleep(600);
} else {//棋子不需要闪烁,则
Thread.sleep(500);
}
} catch (InterruptedException ex) {
break;//退出线程
} catch (Exception e) {
e.printStackTrace();
}
5.1.7 保存和读取棋谱
算法伪码
棋谱格式:
对象格式,二进制流,使用Java序列化机制;
文本格式,文本字符串,使用文字表示的棋谱
程序代码
GameRecord gameRecord = owner.getGameRecord();
gameRecord.setDesc(desc);
ArrayList<String> paths = owner.getSavePaths();
boolean flag = ManualUtil.saveManual(paths.get(0) + name
+ EXTENSION_NAME, paths.get(1) + name + ".txt", gameRecord);
5.1.8 逆向解析棋谱
算法伪码
棋谱生成的逆向解析过程
解析第一个字
解析第二个字
解析第三个字
解析第四个字
程序代码
private void movePieceByManual(String manual);
5.2网络通信
5.2.1 通信协议
public static enum MsgType {
GAME_EXIT, GAME_PAUSE,
GAME_CONTINUE, PIECE_MOVING,
GAME_UNDO, GAME_BACK_YES,
GAME_BACK_NO, PLAYER_GIVEIN,
ROOM_MESSAGE, GAME_MESSAGE,
GROUP_MESSAGE, PLAYER_CREATE_GAME, PLAYER_JOIN_CREATED_GAME, PLAYER_EXIT_CREATED_GAME, CREATOR_START_CREATED_GAME, PLAYER_LIST, CREATOR_LIST, ADD_PLAYER, SUB_PLAYER, ADD_CREATOR, SUB_CREATOR, PLAYER_LOGIN, CREATOR_AND_MEMBER, CHANGE_ROLE, CHANGE_GAME_STATUS, BYE_BYE
};
房间内的消息
编号 | 类型 | 名字 | 描述 | 消息内容 |
001 | PLAYER_LOGIN | 玩家登录 | 玩家使用用户名和密码登录到服务器 | 用户名、密码 |
002 | ROOM_MESSAGE | 房间聊天消息 | 玩家在房间内发表消息 | 用户ID、消息内容 |
003 | PLAYER_CREATE_GAME | 创建游戏 | 创建一个游戏 | 用户ID、玩家组ID |
004 | PLAYER_JOIN_CREATED_GAME | 加入游戏 | 加入一个游戏 | 用户ID、玩家组ID |
005 | BYE_BYE | 退出房间 | 退出房间 | 彻底退出游戏 |
小组内的消息
编号 | 类型 | 名字 | 描述 | 消息内容 |
101 | PLAYER_LIST | 所有玩家的列表 | 玩家列表 | 所有玩家的列表 |
102 | CREATOR_LIST | 创建者列表 | 创建者列表 | 创建者的列表 |
103 | ADD_PLAYER | 增加一个玩家 | 新增加了一个玩家 | 玩家ID、新增玩家的信息 |
105 | SUB_PLAYER | 减少一个玩家 | 减少了一个玩家 | 玩家ID、退出玩家的ID |
105 | ADD_CREATOR | 增加创建者 | 增加了一个创建者 | 创建者信息 |
106 | SUB_CREATOR | 减少创建者 | 减少了一个创建者 | 创建者ID |
107 | CREATOR_AND_MEMBER | 创建者和玩家列表 | 创建者和该组玩家的列表 | 该组所有玩家的信息 |
108 | CHANGE_ROLE | 改变角色 | 玩家改变角色 | 玩家ID、新的角色 |
109 | CHANGE_GAME_STATUS | 改变游戏状态 | 更新游戏的状态 | 玩家ID、游戏状态 |
110 | GROUP_MESSAGE | 组内消息 | 小组内的消息 | 玩家组ID、消息详情 |
111 | CREATOR_EXIT_CREATED_GAME | 退出游戏 | 小组内有人退出了游戏 | 退出游戏的人的ID |
112 | PLAYER_EXIT_CREATED_GAME | 加入者退出某个创建者创建的游戏 | 加入者退出某个创建者创建的游戏 | 创建者ID,加入者ID |
游戏进行过程中的消息
类型 | 名字 | 描述 | 消息内容 | |
201 | GAME_EXIT | 玩家退出游戏 | 观察者、红方、黑方退出了刚刚或正在进行的一场游戏 | 玩家的ID、玩家的角色 |
202 | CREATOR_START_CREATED_GAME | 创建者开始游戏 | 创建游戏的人开始了游戏 | 创建者开始游戏 |
203 | GAME_PAUSE | 暂停游戏 | 玩家暂停了游戏 | 暂停游戏 |
205 | GAME_CONTINU | 继续游戏 | 玩家继续了游戏 | |
205 | GAME_UNDO | 请求悔棋 | 玩家请求悔棋 | |
206 | GAME_BACK_YES | 同意悔棋 | 玩家同意了对手的悔棋请求 | |
207 | GAME_BACK_NO | 拒绝悔棋 | 玩家拒绝了对手的悔棋请求 | |
208 | PLAYER_GIVEIN | 认输 | 对方认输 | |
209 | PIECE_MOVING | 棋子移动 | 对方移动棋子 | |
210 | GAME_QIUHE | 求和 | 玩家求和 | |
211 | GAME_MESSAGE | 比赛中聊天消息 | 游戏进行过程中,玩家发表聊天信息 |
5.2.2消息处理过程
5.2.2.1房间内消息
PLAYER_LOGIN:客户端发送登录所需的信息,服务器端验证,验证通过则向其它所有玩家发送新玩家的信息。
ROOM_MESSAGE:客户端发送聊天信息到服务器,服务器转发该消息给所有人。
PLAYER_CREATE_GAME:客户端发送创建游戏的消息,服务器端转发该消息给所有人。
PLAYER_JOIN_CREATED_GAME:客户端发送加入游戏的消息到服务器,服务器转发该消息给所有人。
BYE_BYE:玩家彻底退出游戏,服务器转发该消息给所有人,所有玩家删除该玩家的信息。
5.2.2.2小组内消息
PLAYER_LIST:服务器发送玩家列表消息给某个组的所有人。
CREATOR_LIST:服务器发送创建者列表给所有人。更新创建者列表。
ADD_PLAYER:服务器发送增加玩家消息给此玩家之外的所有人,接收消息的客户端更新玩家列表。
SUB_PLAYER:服务器发送减少玩家消息给此玩家之外的所有人,接收消息的客户端更新玩家列表。
ADD_CREATOR:服务器发送增加创建者消息给此玩家之外的所有人,接收消息的客户端更新创建者列表。
SUB_CREATOR:服务器发送减少创建者消息给此玩家之外的所有人,接收消息的客户端更新创建者列表。
CREATOR_AND_MEMBER:服务器发送创建者和成员列表消息消息给此玩家之外的所有人,接收消息的客户端更新玩家列表。
CHANGE_ROLE:服务器发送改变角色消息给此玩家之外的所有人,接收消息的客户端更新该玩家的角色信息。
CHANGE_GAME_STATUS:更改游戏状态。
GROUP_MESSAGE:玩家组的聊天消息。
CREATOR_EXIT_CREATED_GAME:创建者退出创建的游戏。所有玩家都将退出该组,玩家组被删除。
PLAYER_EXIT_CREATED_GAME:加入游戏的人退出创建者创建的游戏,其它玩家收到消息后,更新成员列表。
CREATOR_START_CREATED_GAME:创建者开始游戏。其它玩家都开始游戏。
5.2.2.3 玩家游戏消息
GAME_EXIT:玩家退出正在进行的比赛。
GAME_PAUSE:玩家暂停游戏。
GAME_CONTINU:玩家继续游戏。
GAME_UNDO:玩家请求悔棋。
GAME_BACK_YES:对方同意悔棋。
GAME_BACK_NO:对方拒绝悔棋。
PLAYER_GIVEIN:玩家认输。
PIECE_MOVING:棋子移动消息。
GAME_QIUHE:玩家求和。
GAME_MESSAGE:玩家组内的聊天。
5.2.3 Socket实现
Java.net.ServerSocket
// 打开端口号PORT,使客户能够连接上
ServerSocket serverSocket = new ServerSocket
(NetworkConstants.PORT);
String time = ChessUtil.getTime();
msgArea.append(SERVER_NAME + ":开始监听用户请求
--" + time + "\n");
// 不间断地监听用户请求
while (true) {
// 等待客户进程发出连接请求
Socket playerSocket = serverSocket.accept();
ObjectOutputStream oos = new ObjectOutputStream(playerSocket.getOutputStream());
ObjectInputStream ois = new
ObjectInputStream(playerSocket.getInputStream());
Java.net.Socket
/** 从服务器接收消息 */
private void receiveInfoFromServer() throws Exception {
MsgPacket serverPacket = new MsgPacket();
if (fromServer != null) {
serverPacket = (MsgPacket) fromServer.readObject();
}
5.3人工智能
5.3.1 局面表示
使用2维数组,
// 棋子点,共90个,横9*纵10
public ChessPoint chessPoints[][];
5.3.2 走法表示
class ManualItem {
private PieceId eatedPieceId;// 被吃棋子的ID,悔棋时使用
private MoveStep moveStep;// 从哪移动到哪
}
5.3.3 生成走法
先找出本方棋子,然后分别生成每个棋子的走法。每个棋子的走法,根据中国象棋的游戏规则来生成。
方法一:程序简单,但效率不高。
使用2个for循环,从所有位置中找出可走位置,来生成走法。
方法二:程序复杂,效率高。
针对每个棋子的特点,只选择可走位置,来生成走法。
5.3.4 局面评估
参考因素:棋子的价值、位置、灵活性等。
5.3.5 搜索算法
5.3.5.1 深度优先
算法伪码
输入:树根结点root
输出:无
如果结点root非空
访问结点root
从左到右,依次深度优先搜索结点root的每一个子结点。
否则,直接返回
5.3.5.2 广度优先
算法伪码
输入:树根节点root
输出:无
1如果结点root为空
直接返回
2.否则
3.将结点root放入队列
5.如果队列不为空
5.队首结点出队,赋给root
6.访问该结点
7.将root的子结点进队
8.返回第5步。
程序代码
5.3.6人机博弈流程
初始化棋盘:显示棋盘,棋子按棋局开始位置摆放好。
棋手行棋: 棋手思考走法,并且鼠标在界面上走出一步棋。
合理性判断:对棋手走出的每一步棋,进行合理性判断,是否符合游戏规则。
棋手思考不周全,下棋时手误,或者鼠标点的位置有误差,都可能导致走棋不符合规则。对棋手下棋进行合理性判断是非常必要的。为了简化程序,并不判断是否有长将、长捉、长杀等情况。
棋局结束判断:当一方无棋可走,或者被吃掉將,棋局就结束了。程序必须判断棋局是否结束。为了降低程序难度,不考虑不变作和。
5.4其它算法
5.4.1 棋谱的自动演示
For循环
5.4.2 音乐的播放与停止
标记播放状态,根据状态来决定是播放、停止还是暂停。