游戏介绍:

中国象棋是起源于中国的一种棋戏,属于二人对抗性游戏的一种,在中国有着悠久的历史。由于规则简单,趣味性强,成为流行极为广泛的棋类游戏。 中国象棋使用方形格状棋盘及红黑二色圆形棋子进行对弈,棋盘上有十条横线、九条竖线共分成90个交叉点;中国象棋的棋子共有32个,每种颜色16个棋子,分为7个兵种,摆放和活动在交叉点上。双方交替行棋,先把对方的将(帅)“将死”的一方获胜。

本篇博文开发了一个基于UDP通信的中国象棋联机版游戏,游戏符合传统中国象棋规则(包括老将见面判法),具备基本的联机和悔棋功能,悔棋需要得到对方同意。运行效果如下:

使用素材文件夹:

素材及完整源码链接:https://pan.baidu.com/s/1m-z1eDGOabN4iLx5VEcZMw 提取码: rpbr

中国象棋介绍:

棋盘

棋子活动的场所叫做“棋盘”,在长方形的平面上,绘有9条平行的竖线和10条平行的横线相交组成,共90个交叉点,棋子摆在这些交叉点上。中间第5根横线和第6根横线之间未画竖线的空白地带,叫做“楚河 汉界”,整个棋盘就以“楚河 汉界”分为相等的两部分;两方将帅坐阵,画有“米”字方格的地方叫做“九宫”。

Ⅱ棋子

象棋的棋子共32个,分为红黑两组,各16个,由对弈双方各执一组,每组兵种是一样的,各分为七种。

红方:帅、仕、相、车、马、炮、兵;

黑方:将、士、象、车、马、炮、卒。

其中帅和将、士和仕、相和象、兵和卒的作用完全相同,仅仅是为了区分红棋和黑棋。

Ⅲ各棋子走法说明

①将或帅

移动范围:它只能在九宫内移动

移动规则:它每一步只可以水平或垂直移动一点

特殊说明:红方帅和黑方将不能直接见面,即帅和将中间必须有其他棋子间隔,否则构成“老将见面”情况;

假如现在是红方执子,此时构成“老将见面”状况,那么玩家可以移动帅直接吃掉对方的将,红方获胜。

②士或仕

移动范围:它只能在九宫内移动

移动规则:它每一步只能沿对角线方向移动一点

③象或相

移动范围:“楚河 汉界”的一侧

移动规则:它每一步只能沿对角线走两点(走田字),另外,移动的对角线方向上一点不能有其他棋子(不能被堵象眼)。

④马

移动范围:任何位置

移动规则:它每一步只能走曰字并且不能被绊马脚(斜着走,横位移量*纵位移量等于2)。

⑤车

移动范围:任何位置

移动规则:它可以水平或垂直方向移动任意个无阻碍的点。

⑥炮

移动范围:任何位置

移动规则:移动跟车很相似,它既可以像车一样水平或垂直方向移动任意无阻碍的点,也可以通过隔一个棋子吃掉对方一个棋子的方式进行移动。

⑦卒或兵

移动范围:过河后可以走过河后的一侧的任何位置,未过河只能走该棋子向上方向的直线区域

移动规则:它每步只能移动一个点,它未过河时只能向前移动,如果它过河了,增加向左向右移动的能力。

Ⅳ胜负说明

对局中,如果一方玩家认输或者该玩家的“将”或“帅”被对方棋子吃掉,该玩家算输,对方算赢。

UDP通信基础

这里涉及到的UDP通信基础可以参考我之前写的一篇博文:https://blog.csdn.net/A1344714150/article/details/85495088

游戏设计思路

Ⅰ棋盘信息存储及显示

前面说过棋盘是有10条横线和9条竖线组成,一共有90个交叉点,棋子必须放置在这些交叉点上。这里可以使用二维数组对棋盘信息进行存储,每个交叉点存储棋子下标索引,如果交叉点上没有棋子存储-1表示它上面没有棋子。为了更容易地计算坐标,这里直接按照二维数组的分布对棋盘进行分割,声明10行9列数组map,行数从0开始到9为止,列数从0开始到8为止;例如,对方第二个兵或卒位于棋盘第3行第2列,所以map[3][2]存储的是对方第二个兵或卒的棋子的下标索引值;

Ⅱ棋子信息存储及显示

棋子设计成对应的类,每种棋子都有自己对应的棋子图案;

中国象棋一共有32个棋子,这里可以将32个棋子对象数组存储到一个棋子对象数组chess。chess[i]中下标i的值如果是0~15表示黑方棋子,16~31表示的是红方棋子;

具体含义如下:

0将  1~2仕  3~4象  5~6马  7~8车  9~10炮  11~15卒;

16帅  17~18士  19~20相  21~22马  23~24车  25~26炮  27~31兵;

棋子初始化时根据玩家执子颜色和棋子上的字获取指定棋子图案,根据x,y的值,将棋子的位置信息初始化到第x行第y列;

游戏开始时根据双方执子颜色初始化棋盘信息(红方和黑方棋子放置位置会不同);

游戏保证了执子方一定处于棋盘下方,判棋规则目前都是基于我方下棋去判断的,如果我方下棋符合下棋规则,先将下棋信息进行坐标转换,然后发送给对方;接收到对方的下棋信息时,直接按照对方下棋信息对棋盘和棋子信息进行变更即可(发送之前坐标已经转换,无需再次转换)。

Ⅲ走棋规则

对于中国象棋来说,有马走日、象走田等一系列规则。

根据不同的棋子,按不同的规则进行走法判断。

判断是否能走棋的算法如下:

①如果棋子为“帅”或“将”,检查是否走直线并且走一步,以及走一步是否超出范围(老将碰面情况需要提前特殊处理)

②如果棋子为“仕”或“士”,检查是否沿斜对角线走一步,以及走一步是否超过范围

③如果棋子为“象”或“象”,检查是否走“田”字,是否被堵象眼,走一步是否超出范围

④如果棋子为“马”,检查是否走“日”字,是否被绊马脚

⑤如果棋子为“车”,检查是否走直线,移动前的位置和移动后的位置中间是否还有其他子

⑥如果棋子为“炮”,检查是否走直线,判断是否吃子,如果吃子判断中间是否只有一个棋子,否则判断中间是否还有其他子

⑦如果棋子为“卒”或“兵”,检查是否走直线,走一步;如果棋子没有过河,判断棋子是否向前走;如果棋子已经过河,判断是否向前、左、右移动

Ⅳ坐标转换

走棋过程中,需要将鼠标点击的像素坐标转换成棋盘坐标,用到analyse方法;

根据鼠标点击的像素坐标去和每个交叉点的小矩形进行匹配,如果该坐标位于矩形内,说明鼠标点击的是该交叉点。

假设点击的交叉点转换成棋盘坐标是(x,y),如果map[x][y]没有棋子索引,返回空,否则返回该棋子索引对应的棋子对象。

Ⅴ通信信息设计

联机版程序的难度在于对方需要通信,这里使用UDP通信;一方玩家输入对方IP和对方端口,点击开始向对方发送联机请求;

发送的通信信息包括以下功能:

①请求联机

格式:join|

②联机成功

格式:conn|

③认输

格式:lose|

④一方退出游戏

格式:quit|

⑤对方棋子移动信息

格式:move|对方移动的棋子下标|移动后所在行数|移动后所在列数|移动前所在行数|移动前所在列数|被吃掉的棋子索引|

如果该步没有吃掉棋子,被吃掉的棋子索引存储信息为-1

⑥请求悔棋

格式:ask|

⑦同意悔棋

格式:agree|

⑧拒绝悔棋

格式:refuse|

⑨游戏结束

格式:succ|黑方赢了 或者 succ|红方赢了

Ⅵ记录每步棋的信息

自定义数据结构Node类,包含 移动的棋子信息、移动后所在行数、移动后所在列数、移动前所在行数、移动前所在列数、该步棋吃掉了的棋子的索引值;主要作用是为了实现悔棋功能,当然也可以对此进行拓展,完成棋谱记录及按棋谱还原棋局的功能。

游戏具体实现步骤

Ⅰ设计棋子类(Chess.java)

棋子类的成员信息主要包含 棋子所属玩家、棋子类别、棋子所在行数、棋子所在列数、棋子图案的信息;

方法主要包括:

setPos(int x,int y):将棋子放在第x行第y列

ReversePos():将棋子位置进行对调

paint(Graphics g,JPanel i):在指定的JPanel上画棋子

DrawSelectedChess(Graphics g):给选中的棋子画选中框

package 中国象棋;import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.ImageObserver;import javax.swing.JPanel;public class Chess {public static final short REDPLAYER = 1;public static final short BLACKPLAYER = 0;public short player;public String typeName;public int x,y;//网格地图对应的二维数组的下标private Image chessImage;//棋子图案private int leftX=28,leftY=20;public Chess(short player,String typeName,int x,int y){this.player = player;this.typeName = typeName;this.x = x;this.y = y;if(player == REDPLAYER){switch (typeName){case "帅":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess7.png");break;case "仕":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess8.png");break;case "相":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess9.png");break;case "马":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess10.png");break;case "车":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess11.png");break;case "炮":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess12.png");break;case "兵":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess13.png");break;          }}else{switch(typeName){case "将":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess0.png");break;case "士":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess1.png");break;case "象":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess2.png");break;case "马":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess3.png");break;case "车":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess4.png");break;case "炮":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess5.png");break;case "卒":chessImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chess6.png");break;            }}}public void setPos(int x,int y){this.x = x;this.y = y;}public void ReversePos(){x = 9 - x;y = 8 - y;}protected void paint(Graphics g,JPanel i){g.drawImage(chessImage, leftX+y*62, leftY+x*57, 40, 40,(ImageObserver)i);}//绘画选中框public void DrawSelectedChess(Graphics g){g.drawRect(leftX+y*62, leftY+x*57, 40, 40);}}

Ⅱ 设计Node类用于记录每步棋的信息(Node.java)

Node.java成员包括:移动的棋子下标index、棋子移动后位于(x,y)、棋子移动前位于(oldX,oldY)、被吃掉的棋子下标eatChessIndex;

package 中国象棋;//存储棋谱的每一步
public class Node {int index;//移动的棋子下标int x,y;//棋子移动后位于(x,y)int oldX,oldY;//棋子移动前位于(oldX,oldY)int eatChessIndex;//被吃掉的棋子下标//如果棋子移动过程没有吃子,eatChessIndex = -1;public Node(int index,int x,int y,int oldX,int oldY,int eatChessIndex){this.index = index;this.x = x;this.y = y;this.oldX = oldX;this.oldY = oldY;this.eatChessIndex = eatChessIndex;}}

Ⅲ设计棋盘类(ChessBoard.javb)

棋盘类是游戏面板,先定义一个数组chess存放双方32个棋子对象。二维数组map保存了当前棋盘的棋子布局,当map[x][y]=i时说明棋盘第x行第y列是棋子i,否则-1说明此处为空;声明了ArrayList<Node>对象list用于保存每步棋的信息;以下是成员变量定义:

    public static final short REDPLAYER = 1;public static final short BLACKPLAYER = 0;public Chess[] chess = new Chess[32];//棋子数组public int[][] map = new int[10][9];//存储棋盘布局信息数组10行9列public Image bufferImage;private Chess firstChess = null;private Chess secondChess = null;private boolean isFirstClick = true;//标记是否第一次点击private int x1,y1,x2,y2;private int tempX,tempY;private boolean isMyTurn = true;//标记是否自己执子public short LocalPlayer = REDPLAYER;//记录当前执子方private String message = "";//提示信息private boolean flag = false;private int otherPort=3003;//对方端口private int receivePort=3004;//本地端口public ArrayList<Node> list = new ArrayList<Node>();//存储棋谱private String ip = "127.0.0.1";//存储目标IP

对存储当前棋盘布局信息的二维数组map进行初始化,由于棋子索引从0开始到31,所以全部初始化为-1(表示没有棋子)

 //初始化棋盘布局信息为空private void initMap(){int i,j;for(i=0;i<10;i++){for(j=0;j<9;j++){map[i][j]= -1;}}    }

棋盘构造方法主要先对棋盘信息进行初始化,接着为棋盘添加鼠标监听;

监听事件先判断是否是自己执子,如果是自己执子,再判断自己是第几次点击;

⑴如果是第一次点击(isFirstClick为true):

根据点击处的像素坐标转换成自己点击的棋盘坐标,将该棋盘坐标上的Chess对象赋值给firstChess,并用x1和y1对棋盘坐标进行记录;

如果自己选中了棋子(firstChess不为空),判断是否选中的是对方的棋子,

①如果是提示“点击成对方棋子了”,

②如果否,将isFirstClick改为false

⑵如果是第二次点击(isFirstClick为false):

根据点击处的像素坐标转换成自己点击的棋盘坐标,将该棋盘坐标上的Chess对象赋值给secondChess,并用x2和y2对棋盘坐标进行记录;

接着,判断第二次点击的棋子

①如果第二次选中的棋子是自己的,将该棋子对方赋值给firstChess(重新选中棋子),return ;

②如果第二次没有选中任何棋子,判断是否可以走棋

⒈如果isAbleToMove(firstChess,x2,y2)返回true,说明可以走棋,对棋盘信息和棋子信息进行变更,记录棋谱信息,并发送倒置后的下棋信息给对方,重置isFirstClick为true,将isMyTurn改为false

⒉否则说明不符合走棋规则,修改提示信息为“不符合走棋规则”

③如果第二次选中的棋子是对面的,判断是否可以走棋

⒈如果isAbleToMove(firstChess,x2,y2)返回true,说明可以走棋,对棋盘信息和棋子信息进行变更,记录棋谱信息,并发送倒置后的下棋信息给对方,重置isFirstClick为true,同时判断被吃掉的是不是帅或者将,如果是发送输赢信息并结束游戏,最后将isMyTurn改为false

⒉否则说明不能吃子,修改提示信息为“不能吃子”

public ChessBoard(){initMap();//     initChess();message = "程序处于等待联机状态!";addMouseListener(new MouseAdapter(){@Overridepublic void mouseClicked(MouseEvent e){if(isMyTurn == false){message = "现在该对方走棋";repaint();return ;}selectedChess(e);repaint();}private void selectedChess(MouseEvent e) {int index1,index2;//保存第一次和第二次被单击的棋子对应数组下标if(isFirstClick){//第一次点击firstChess = analyse(e.getX(),e.getY());x1 = tempX;y1 = tempY;if(firstChess != null){if(firstChess.player != LocalPlayer){message = "点击成对方棋子了";return ;}isFirstClick = false;}}else{secondChess = analyse(e.getX(), e.getY());x2 = tempX;y2 = tempY;if(secondChess!=null){//如果第二次点击选中了棋子if(secondChess.player == LocalPlayer){//如果第二次点击的棋子是自己的棋子,则对第一次选中的棋子进行更换firstChess = secondChess;x1 = tempX;y1 = tempY;secondChess = null;return ;}}if(secondChess == null){//如果目标处没有棋子,判断是否可以走棋if(IsAbleToMove(firstChess,x2,y2)){index1 = map[x1][y1];map[x1][y1] = -1;map[x2][y2] = index1;chess[index1].setPos(x2, y2);//sendsend("move"+"|"+index1 + "|"+(9-x2)+"|"+(8-y2)+"|"+(9-x1)+"|"+(8-y1)+"|"+"-1|");list.add(new Node(index1,x2,y2,x1,y1,-1));//存储我方下棋信息//置第一次选中标记量为空isFirstClick = true;repaint();SetMyTurn(false);//该对方了}else{message = "不符合走棋规则";}return ;}if(secondChess != null&&IsAbleToMove(firstChess, x2, y2)){//可以吃子isFirstClick = true;index1 = map[x1][y1];index2 = map[x2][y2];map[x1][y1] = -1;map[x2][y2] = index1;chess[index1].setPos(x2, y2);chess[index2] = null;repaint();send("move"+"|"+index1+"|"+(9-x2)+"|"+(8-y2)+"|"+(9-x1)+"|"+(8-y1)+"|"+index2+"|");list.add(new Node(index1,x2,y2,x1,y1,index2));//记录我方下棋信息if(index2 == 0){//被吃掉的是将message = "红方赢了";JOptionPane.showConfirmDialog(null, "红方赢了","提示",JOptionPane.DEFAULT_OPTION);//sendsend("succ"+"|"+"红方赢了"+"|");return ;}if(index2 == 16){//被吃掉的是帅message = "黑方赢了";JOptionPane.showConfirmDialog(null, "黑方赢了","提示",JOptionPane.DEFAULT_OPTION);//sendsend("succ"+"|"+"黑方赢了"+"|");return ;}SetMyTurn(false);//该对方了}else{//不能吃子message = "不能吃子";}}}

使用analyse()方法分析鼠标选中的棋盘坐标

由于棋盘图片大小和棋子间距的关系,这里使用了leftX和leftY表示水平和垂直偏移量;

具体转换过程是使用点击处的像素坐标去和每个棋盘坐标的小矩形进行匹配,如果点位于矩形内,说明点击了该棋盘坐标。

         private Chess analyse(int x, int y) {int leftX = 28,leftY = 20;int index_x=-1,index_y=-1;//记录点击处是第几行第几列for(int i=0;i<=9;i++){for(int j=0;j<=8;j++){Rectangle r = new Rectangle(leftX+j*62, leftY+i*57, 40, 40);if(r.contains(x,y)){index_x = i;index_y = j;break;}}}tempX = index_x;tempY = index_y;if(index_x==-1&&index_y==-1){//没有点击到任何棋盘可点击处return null;}if(map[index_x][index_y]==-1){return null;}else{return chess[map[index_x][index_y]];}}

当玩家输入完对方的IP地址和端口号,点击开始,向对方IP地址的指定端口号发送联机请求,同时自己启动线程开始监听端口。

具体过程可以简述成:

A发送请求联机要求给B的某端口并且开始监听自己的端口,但是此时B是没有启动线程监听端口的,所以收不到A的请求;

接着B输入A的IP地址和端口点击开始发送联机请求,由于刚才A开始监听端口了,这次B发送的联机请求A可以收到,A收到B的联机请求,发送个联机成功的消息回B,双方游戏开始。

//加入对局public void startJoin(String ip,int otherPort,int receivePort){flag = true;this.otherPort = otherPort;this.receivePort = receivePort;this.ip = ip;System.out.println("能帮我连接到"+ip+"吗");send("join|");Thread th = new Thread(this);th.start();//}//联机请求及联机响应相关部分代码@Override    public void run() {System.out.println("我是客户端,我绑定的端口是"+receivePort);DatagramSocket s = new DatagramSocket(receivePort);byte[] data = new byte[100];DatagramPacket dgp = new DatagramPacket(data, data.length);while(flag==true){s.receive(dgp);String strData = new String(data);String[] array = new String[6];array = strData.split("\\|");if(array[0].equals("join")){//对局被加入,我是黑方LocalPlayer = BLACKPLAYER;startNewGame(LocalPlayer);if(LocalPlayer==REDPLAYER){SetMyTurn(true);}else{SetMyTurn(false);}//发送联机成功信息send("conn|");}else if(array[0].equals("conn")){//我成功加入别人的对局,联机成功。我是红方LocalPlayer = REDPLAYER;startNewGame(LocalPlayer);if(LocalPlayer==REDPLAYER){SetMyTurn(true);}else{SetMyTurn(false);}}        其余部分先省略......}}省略......}

当双方联机成功后,startNewGame(short player)根据玩家的执子颜色使用initChess()初始化棋子布局;布局按红下黑上分布,如果玩家执黑子,使用ReverseBoard()方法将棋子位置进行对调,变成黑下红上。布局后将所有棋子和棋盘重画显示。

 public void startNewGame(short player){initMap();initChess();if(player == BLACKPLAYER){reverseBoard();}repaint();}//初始化棋子布局private void initChess(){//布置黑方棋子chess[0] = new Chess(BLACKPLAYER,"将",0,4);//第0行第4列map[0][4] = 0;chess[1] = new Chess(BLACKPLAYER,"士",0,3);//第0行第3列map[0][3] = 1;chess[2] = new Chess(BLACKPLAYER,"士",0,5);//第0行第5列map[0][5] = 2;chess[3] = new Chess(BLACKPLAYER,"象",0,2);//第0行第2列map[0][2] = 3;chess[4] = new Chess(BLACKPLAYER,"象",0,6);//第0行第6列map[0][6] = 4;chess[5] = new Chess(BLACKPLAYER,"马",0,1);//第0行第1列map[0][1] = 5;chess[6] = new Chess(BLACKPLAYER,"马",0,7);//第0行第7列map[0][7] = 6;chess[7] = new Chess(BLACKPLAYER,"车",0,0);//第0行第0列map[0][0] = 7;chess[8] = new Chess(BLACKPLAYER,"车",0,8);//第0行第8列map[0][8] = 8;chess[9] = new Chess(BLACKPLAYER,"炮",2,1);//第2行第1列map[2][1] = 9;chess[10] = new Chess(BLACKPLAYER,"炮",2,7);//第2行第7列map[2][7] = 10;for(int i=0;i<5;i++){//5个黑方卒布局chess[11+i] = new Chess(BLACKPLAYER,"卒",3,i*2);map[3][i*2] = 11+i;}//布置红方棋子chess[16] = new Chess(REDPLAYER,"帅",9,4);//第9行第4列map[9][4] = 16;chess[17] = new Chess(REDPLAYER,"仕",9,3);//第9行第3列map[9][3] = 17;chess[18] = new Chess(REDPLAYER,"仕",9,5);//第9行第5列map[9][5] = 18;chess[19] = new Chess(REDPLAYER,"相",9,2);//第9行第2列map[9][2] = 19;chess[20] = new Chess(REDPLAYER,"相",9,6);//第9行第6列map[9][6] = 20;chess[21] = new Chess(REDPLAYER,"马",9,1);//第9行第1列map[9][1] = 21;chess[22] = new Chess(REDPLAYER,"马",9,7);//第9行第7列map[9][7] = 22;chess[23] = new Chess(REDPLAYER,"车",9,0);//第9行第0列map[9][0] = 23;chess[24] = new Chess(REDPLAYER,"车",9,8);//第9行第8列map[9][8] = 24;chess[25] = new Chess(REDPLAYER,"炮",7,1);//第7行第1列map[7][1] = 25;chess[26] = new Chess(REDPLAYER,"炮",7,7);//第7行第7列map[7][7] = 26;for(int i=0;i<5;i++){//5个红方兵布局chess[27+i] = new Chess(REDPLAYER,"兵",6,i*2);map[6][i*2] = 27+i;}}//翻转所有棋子位置private void reverseBoard(){//对棋子的位置进行互换for(int i=0;i<32;i++){if(chess[i]!=null){chess[i].ReversePos();}}//对两方的棋盘信息进行倒置互换for(int i=0;i<5;i++){for(int j=0;j<9;j++){int temp = map[i][j];map[i][j] = map[9-i][8-j];map[9-i][8-j] = temp;}}  }

使用paint(Graphics g)方法重画游戏中的背景棋盘和所有棋子对象以及提示消息。

//对场景对象进行绘画public void paint(Graphics g){g.clearRect(0, 0, this.getWidth(), this.getHeight());Image backgroundImage = Toolkit.getDefaultToolkit().getImage("D://Game//ChineseChessGame//chessBoard.png");g.drawImage(backgroundImage,0,0,600,600,this);for(int i=0;i<32;i++){if(chess[i]!=null){chess[i].paint(g, this);}}if(firstChess!=null){firstChess.DrawSelectedChess(g);}if(secondChess!=null){secondChess.DrawSelectedChess(g);}g.drawString(message, 0, 620);}

使用IsAbleToMove(firstChess,x,y)判断是否能走棋并返回逻辑值;

传入的参数说明,firstChess是玩家第一次选中的棋子对象,x,y表示该棋子想要移动到第x行第y列;

根据不同的棋子对象有不同的走法规则判断:

①如果移动的棋子是“将”或“帅”

先根据棋子移动的起点和终点,判断是否符合老将碰面的情况;如果玩家尝试用“将”去吃“帅”,并且它们之间没有其他棋子,返回true;玩家尝试用“帅”去吃“将”同理;

如果不符合老将碰面的情况,再判断是否符合只在直线上移动一步并且移动范围没超过超过“九宫”的条件,如果符合返回true

//判断是否可以落子private boolean IsAbleToMove(Chess firstChess, int x, int y) {int oldX,oldY;oldX = firstChess.x;oldY = firstChess.y;String chessName = firstChess.typeName;if(chessName.equals("将")||chessName.equals("帅")){//如果玩家尝试用"将"去吃"帅",或者相反,则判断是否符合两将碰面的情况(必须在同一列,并且中间没有其他子)if(oldY==y&&(map[x][y]==0||map[x][y]==16)){for(int i=x+1;i<oldX;i++){if(map[i][y]!=-1){return false;}}return true;}if((x-oldX)*(y-oldY)!=0){//如果是斜着走return false;}if(Math.abs(x-oldX)>1||Math.abs(y-oldY)>1){//如果横走或竖走超过一格return false;}if((x>2&&x<7)||y<3||y>5){//如果超出九宫格区域return false;}return true;}...}

②如果移动的棋子是“仕”或“士”

根据棋子移动的起点坐标(oldX,oldY)和棋子移动的终点坐标(x,y),通过判断是否沿直线走,判断斜走的时候横位移量和纵位移量是否有一个大于1,判断移动后的位置是否超过九宫 三个条件来筛选出 只能在九宫内斜走一步的情况。

//判断是否可以落子private boolean IsAbleToMove(Chess firstChess, int x, int y) {int oldX,oldY;oldX = firstChess.x;oldY = firstChess.y;String chessName = firstChess.typeName;...if(chessName.equals("士")||chessName.equals("仕")){if((x-oldX)*(y-oldY)==0){//如果横走或者竖走return false;}if(Math.abs(x-oldX)>1||Math.abs(y-oldY)>1){//如果横向或者纵向的位移量大于1,即不是斜走一格return false;}if((x>2&&x<7)||y<3||y>5){//如果超出九宫格区域return false;}return true;}...
}

③如果移动的棋子是“象”或“相”

根据棋子移动的起点坐标(oldX,oldY)和棋子移动的终点坐标(x,y),通过判断是否直线行走、判断是否走田字、判断是否越过“楚河-汉界”、判断是否被堵象眼 四个判断条件筛选出 棋子走田字并且不越界的情况。

//判断是否可以落子private boolean IsAbleToMove(Chess firstChess, int x, int y) {int oldX,oldY;oldX = firstChess.x;oldY = firstChess.y;String chessName = firstChess.typeName;...if(chessName.equals("相")||chessName.equals("象")){if((x-oldX)*(y-oldY)==0){//如果横走或者竖走return false;}if(Math.abs(x-oldX)!=2||Math.abs(y-oldY)!=2){//如果横向或者纵向的位移量不同时为2,即不是走田字return false;}if(x<5){//如果象越过“楚河-汉界”return false;}int i=0,j=0;//记录象眼位置if(x-oldX==2){//象向下跳i=oldX+1;}if(x-oldX==-2){//象向上跳i=oldX-1;}if(y-oldY==2){//象向右跳j=oldY+1;}if(y-oldY==-2){//象向左跳j=oldY-1;}if(map[i][j]!=-1){//被堵象眼return false;}return true;}...}

④如果移动的棋子是“马”

根据棋子移动的起点坐标(oldX,oldY)和棋子移动的终点坐标(x,y),通过判断马是否走“曰”字、判断马是否被绊马脚两个判断条件筛选出 棋子走“曰”字并不被绊马脚的情况。补充:熟悉象棋的童鞋应该知道,马踏八方,但是实际上被绊马脚的情况只有四种。

 //判断是否可以落子private boolean IsAbleToMove(Chess firstChess, int x, int y) {int oldX,oldY;oldX = firstChess.x;oldY = firstChess.y;String chessName = firstChess.typeName;...if(chessName.equals("马")){if(Math.abs(x-oldX)*Math.abs(y-oldY)!=2){//如果横向位移量乘以竖向位移量不等于2,即如果马不是走日字return false;}if(x-oldX==2){//如果马向下跳,并且横向位移量为1,纵向位移量为2if(map[oldX+1][oldY]!=-1){//如果被绊马脚return false;}             }if(x-oldX==-2){//如果马向上跳,并且横向位移量为1,纵向位移量为2if(map[oldX-1][oldY]!=-1){//如果被绊马脚return false;}             }if(y-oldY==2){//如果马向右跳,并且横向位移量为2,纵向位移量为1if(map[oldX][oldY+1]!=-1){//如果被绊马脚return false;}}if(y-oldY==-2){//如果马向左跳,并且横向位移量为2,纵向位移量为1if(map[oldX][oldY-1]!=-1){//如果被绊马脚return false;}}return true; }...}

⑤如果移动的棋子是“车”

根据棋子移动的起点坐标(oldX,oldY)和棋子移动的终点坐标(x,y),通过判断是否斜走、判断两坐标中间是否还有其他子 两个判断条件 筛选出正确的车的走法。

//判断是否可以落子private boolean IsAbleToMove(Chess firstChess, int x, int y) {int oldX,oldY;oldX = firstChess.x;oldY = firstChess.y;String chessName = firstChess.typeName;
...if(chessName.equals("车")){if((x-oldX)*(y-oldY)!=0){//如果横向位移量和纵向位移量同时都不为0,说明车在斜走,故return falsereturn false;}if(x!=oldX){//如果车纵向移动if(oldX>x){//将判断过程简化为纵向从上往下查找中间是否有其他子int t = x;x = oldX;oldX = t;}for(int i=oldX+1;i<x;i++){if(map[i][oldY]!=-1){//如果中间有其他子return false;}}}if(y!=oldY){//如果车横向移动if(oldY>y){//将判断过程简化为横向从左到右查找中间是否有其他子int t = y;y = oldY;oldY = t;}for(int i=oldY+1;i<y;i++){if(map[oldX][i]!=-1){//如果中间有其他子return false;}}}return true;}
...
}

⑥如果移动的棋子是“炮”

先使用swapFlagX和swapFlagY表示x和y值是否交换过,如果棋子斜走返回false;使用变量c记录棋子移动前和移动后的位置中间有几个棋子;根据横走和纵走的情况分别计算两个位置中间的棋子数目;如果c>1说明两个位置中间的棋子超过1个,所以不能移动;如果c==0说明两个位置中间没有棋子,如果之前交换过x或者y需要先交换回来,再判断移动的终点是否有其他棋子,如果有棋子占位,则不能移动;如果c==1说明两个位置中间有一个棋子,如果之前交换过x或者y需要先交换回来,再判断移动的终点是否有其他棋子,如果没有其他棋子,则不能移动(不能打空炮)

 //判断是否可以落子private boolean IsAbleToMove(Chess firstChess, int x, int y) {System.out.println("判断前:"+x+":"+y);int oldX,oldY;oldX = firstChess.x;oldY = firstChess.y;String chessName = firstChess.typeName;
...if(chessName.equals("炮")){boolean swapFlagX = false;//记录纵向棋子是否交换过boolean swapFlagY = false;//记录横向棋子是否交换过if((x-oldX)*(y-oldY)!=0){//如果棋子斜走return false;}int c = 0;//记录两子中间有多少个子if(x!=oldX){//如果炮是纵向移动if(oldX>x){//简化后续判断int t = x;x = oldX;oldX = t;swapFlagX = true;}for(int i=oldX+1;i<x;i++){if(map[i][oldY]!=-1){//如果中间有子c += 1;}}}if(y!=oldY){//如果炮是横向 移动if(oldY>y){//简化后续判断int t = y;y = oldY;oldY = t;swapFlagY = true;}for(int i=oldY+1;i<y;i++){if(map[oldX][i]!=-1){//如果中间有子c += 1;}}}if(c>1){//中间超过一个子return false;}if(c==0){//如果中间没有子if(swapFlagX==true){//如果之间交换过,需要重新交换回来int t = x;x = oldX;oldX = t;}if(swapFlagY==true){int t = y;y = oldY;oldY = t;}if(map[x][y]!=-1){//如果目标处有子存在,则不能移动return false;}}if(c==1){//如果中间只有一个子if(swapFlagX==true){//如果之间交换过,需要重新交换回来int t = x;x = oldX;oldX = t;}if(swapFlagY==true){int t = y;y = oldY;oldY = t;}if(map[x][y]==-1){//如果目标处没有棋子,即不能打空炮return false;}}return true;}...}

⑦如果移动的棋子是“兵”或“卒”

根据棋子移动的起点坐标(oldX,oldY)和棋子移动的终点坐标(x,y),通过判断棋子是否斜走、判断棋子一次移动是否超过一步、根据棋子是否过河分别判断移动方向是否符合规则(过河前只许前走,过河后可以前、左、右走)这几个判断条件筛选出正确的走法情况。

 //判断是否可以落子private boolean IsAbleToMove(Chess firstChess, int x, int y) {int oldX,oldY;oldX = firstChess.x;oldY = firstChess.y;String chessName = firstChess.typeName;...if(chessName.equals("卒")||chessName.equals("兵")){if((x-oldX)*(y-oldY)!=0){//如果斜走return false;}if(Math.abs(x-oldX)>1||Math.abs(y-oldY)>1){//如果一次移动了一格以上return false;}if(oldX>=5){//如果兵未过河,则只能向上移动,不能左右移动if(Math.abs(y-oldY)>0){//没过河尝试左右移动return false;}if(x-oldX==1){//兵向下移动return false;}}else{//如果已经过河,可以进行上左右移动,但不能进行向下移动if(x-oldX==1){//兵向下移动return false;}}return true;}...}

使用rebackChess(int index,int x,int y,int oldX,int oldY)方法将棋子进行位置的回退

//将棋子回退到上一步的位置,并把棋子未回退前的棋盘位置信息清空private void rebackChess(int index,int x,int y,int oldX,int oldY){chess[index].setPos(oldX, oldY);map[oldX][oldY] = index;//棋子放回到(oldX,oldY)map[x][y] = -1;//棋盘里原有棋子位置信息清除}

使用resetChess(int index,int x,int y)方法将一个被吃了的棋子放回到棋盘

//将一个被吃了的子重新放回到棋盘,传入参数说明:index棋子数组下标,第x行,第y列private void resetChess(int index,int x,int y){short temp = index<16?BLACKPLAYER:REDPLAYER;//存储是哪方的棋子String name = null;//存储棋子上的字switch(index){//根据棋子索引,得到棋子上面的字case 0:name="将";break;case 1:;case 2:name="士";break;case 3:;case 4:name="象";break;case 5:;case 6:name="马";break;case 7:;case 8:name="车";break;case 9:;case 10:name="炮";break;case 11:;case 12:;case 13:;case 14:;case 15:name="卒";break;case 16:name="帅";break;case 17:;case 18:name="仕";break;case 19:;case 20:name="相";break;case 21:;case 22:name="马";break;case 23:;case 24:name="车";break;case 25:;case 26:name="炮";break;case 27:;case 28:;case 29:;case 30:;case 31:name="兵";break;}chess[index] = new Chess(temp,name,x,y);map[x][y] = index;//将棋子放回到棋盘}

使用SetMyTurn(boolean b)控制执子权利,b为true时我方下棋,否则对方下棋

 private void SetMyTurn(boolean b) {isMyTurn = b;if(b){message = "请您开始走棋";}else{message = "对方正在思考";}                                                                                                                                                                          }

发送信息是使用send(String s)方法,主要实现创建UDP网络服务,传送信息到指定计算机的指定端口号后,关闭UDP套接字。

 public void send(String str) {DatagramSocket s = null;try{s = new DatagramSocket();byte[] buffer;buffer = new String(str).getBytes();
//          InetAddress ia = InetAddress.getLocalHost();//获取本机地址InetAddress ia = InetAddress.getByName(ip );//获取目标地址      System.out.println("请求连接的ip是"+ip);DatagramPacket dgp = new DatagramPacket(buffer, buffer.length,ia,otherPort);s.send(dgp);System.out.println("发送信息:"+str);}catch(Exception e){e.printStackTrace();}finally{if(s!=null){s.close();}}}

使用run()方法不断侦听本地设定的端口,得到对方的信息根据自己定义的通信信息设计规则,解析成不同的指令,并分别处理:

@Override    public void run() {try{System.out.println("我是客户端,我绑定的端口是"+receivePort);DatagramSocket s = new DatagramSocket(receivePort);byte[] data = new byte[100];DatagramPacket dgp = new DatagramPacket(data, data.length);while(flag==true){s.receive(dgp);String strData = new String(data);String[] array = new String[6];array = strData.split("\\|");if(array[0].equals("join")){//对局被加入,我是黑方LocalPlayer = BLACKPLAYER;startNewGame(LocalPlayer);if(LocalPlayer==REDPLAYER){SetMyTurn(true);}else{SetMyTurn(false);}//发送联机成功信息send("conn|");}else if(array[0].equals("conn")){//我成功加入别人的对局,联机成功。我是红方LocalPlayer = REDPLAYER;startNewGame(LocalPlayer);if(LocalPlayer==REDPLAYER){SetMyTurn(true);}else{SetMyTurn(false);}}else if(array[0].equals("succ")){if(array[1].equals("黑方赢了")){if(LocalPlayer==REDPLAYER)JOptionPane.showConfirmDialog(null, "黑方赢了,你可以重新开始","你输了",JOptionPane.DEFAULT_OPTION);elseJOptionPane.showConfirmDialog(null, "黑方赢了,你可以重新开始","你赢了",JOptionPane.DEFAULT_OPTION);                       }if(array[1].equals("红方赢了")){if(LocalPlayer==REDPLAYER)JOptionPane.showConfirmDialog(null, "红方赢了,你可以重新开始","你赢了",JOptionPane.DEFAULT_OPTION);elseJOptionPane.showConfirmDialog(null, "红方赢了,你可以重新开始","你输了",JOptionPane.DEFAULT_OPTION);                     }message = "你可以重新开局";GameClient.buttonStart.setEnabled(true);//可以点击开始按钮了//}else if(array[0].equals("move")){//对方的走棋信息,move|棋子索引号|x|y|oldX|oldY|背驰棋子索引System.out.println("接受信息:"+array[0]+"|"+array[1]+"|"+array[2]+"|"+array[3]+"|"+array[4]+"|"+array[5]+"|"+array[6]+"|");int index = Short.parseShort(array[1]);x2 = Short.parseShort(array[2]);y2 = Short.parseShort(array[3]);//                  String z = array[4];//对方上步走棋的棋谱信息//                    message = x2 + ":" +y2;int oldX = Short.parseShort(array[4]);//棋子移动前所在行数int oldY = Short.parseShort(array[5]);//棋子移动前所在列数int eatChessIndex = Short.parseShort(array[6]);//被吃掉的棋子索引list.add(new Node(index,x2,y2,oldX,oldY,eatChessIndex));//记录下棋信息message = "对方将棋子\""+chess[index].typeName+"\"移动到了("+x2+","+y2+")\n现在该你走棋";Chess c = chess[index];x1 = c.x;y1 = c.y;index = map[x1][y1];int index2 = map[x2][y2];map[x1][y1] = -1;map[x2][y2] = index;chess[index].setPos(x2, y2);if(index2!=-1){// 如果吃了子,则取下被吃掉的棋子chess[index2] = null;}repaint();isMyTurn = true;}else if(array[0].equals("quit")){JOptionPane.showConfirmDialog(null, "对方退出了,游戏结束!","提示",JOptionPane.DEFAULT_OPTION);message = "对方退出了,游戏结束!";GameClient.buttonStart.setEnabled(true);//可以点击开始按钮了}else if(array[0].equals("lose")){JOptionPane.showConfirmDialog(null, "恭喜你,对方认输了!","你赢了",JOptionPane.DEFAULT_OPTION);SetMyTurn(false);GameClient.buttonStart.setEnabled(true);//可以点击开始按钮了}else if(array[0].equals("ask")){//对方请求悔棋String msg = "对方请求悔棋,是否同意?";int type = JOptionPane.YES_NO_OPTION;String title = "请求悔棋";int choice = 0;choice = JOptionPane.showConfirmDialog(null, msg,title,type);if(choice==1){//否,拒绝悔棋send("refuse|");}else if(choice == 0){//是,同意悔棋send("agree|");message = "同意了对方的悔棋,对方正在思考";SetMyTurn(false);//对方下棋Node temp = list.get(list.size()-1);//获取棋谱最后一步棋的信息list.remove(list.size()-1);//移除if(LocalPlayer==REDPLAYER){//假如我是红方if(temp.index>=16){//上一步是我下的,需要回退两步rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}temp = list.get(list.size()-1);list.remove(list.size()-1);rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}}else{//上一步是对方下的,需要回退一步rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}}}else{//假如我是黑方if(temp.index<16){//上一步是我下的,需要回退两步rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}temp = list.get(list.size()-1);list.remove(list.size()-1);rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}}else{//上一步是对方下的,需要回退一步rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}}}repaint();}}else if(array[0].equals("agree")){//对方同意悔棋JOptionPane.showMessageDialog(null, "对方同意了你的悔棋请求");Node temp = list.get(list.size()-1);//获取棋谱最后一步棋的信息list.remove(list.size()-1);//移除if(LocalPlayer==REDPLAYER){//假如我是红方if(temp.index>=16){//上一步是我下的,回退一步即可rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}}else{//上一步是对方下的,需要回退两步//第一次回退,此时回退到的状态是我刚下完棋轮到对方下棋的状态rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}temp = list.get(list.size()-1);list.remove(list.size()-1);//第二次回退,此时回退到的状态是我上一次刚轮到我下棋的状态rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}}}else{//假如我是黑方if(temp.index<16){//上一步是我下的,回退一步即可rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}}else{//上一步是对方下的,需要回退两步//第一次回退,此时回退到的状态是我刚下完棋轮到对方下棋的状态rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}temp = list.get(list.size()-1);list.remove(list.size()-1);//第二次回退,此时回退到的状态是我上一次刚轮到我下棋的状态rebackChess(temp.index, temp.x, temp.y, temp.oldX, temp.oldY);//回退棋子if(temp.eatChessIndex!=-1){//如果上一步吃了子,将被吃的子重新放回到棋盘resetChess(temp.eatChessIndex, temp.x, temp.y);//将被吃的棋子放回棋盘}}}SetMyTurn(true);repaint();}else if(array[0].equals("refuse")){//对方拒绝悔棋JOptionPane.showMessageDialog(null, "对方拒绝了你的悔棋请求");                   }//             System.out.println(new String(data));//s.send(dgp);}}catch(Exception e){e.printStackTrace();}}

游戏窗口类(GameClient.java)

游戏窗口类实现整体界面的组装,包括认输,请求悔棋,开始三个按钮的点击事件处理;

需要注意的是,这里监听实现的比较简单,假如A向3003端口发送联机请求,那么A就监听3004端口;假如A向3004端口发送联机请求,那么A就监听3003端口;

package 中国象棋;import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;import javax.swing.*;public class GameClient extends JFrame{static ChessBoard gamePanel = new ChessBoard();static JButton buttonGiveIn = new JButton("认输");static JButton buttonStart = new JButton("开始");JButton buttonAskRegret = new JButton("请求悔棋");JTextField textIp = new JTextField("127.0.0.1");//IPJTextField textPort = new JTextField("3004");//对方端口public static final short REDPLAYER = 1;public static final short BLACKPLAYER = 0;public GameClient(){JPanel panelBottom = new JPanel(new FlowLayout());panelBottom.add(new JLabel("输入对方IP:"));panelBottom.add(textIp);panelBottom.add(new JLabel("输入对方端口:"));panelBottom.add(textPort);panelBottom.add(buttonGiveIn);panelBottom.add(buttonAskRegret);panelBottom.add(buttonStart);this.getContentPane().setLayout(new BorderLayout());this.getContentPane().add(gamePanel,BorderLayout.CENTER);this.getContentPane().add(panelBottom,BorderLayout.SOUTH);this.setSize(610,730);this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.setTitle("中国象棋客户端");this.setVisible(true);buttonGiveIn.setEnabled(false);buttonAskRegret.setEnabled(false);buttonStart.setEnabled(true);setVisible(true);this.addWindowListener(new WindowAdapter() {//窗口关闭事件public void windowClosing(WindowEvent e){try{gamePanel.send("quit|");System.exit(0);}catch(Exception ex){ex.printStackTrace();}}});buttonGiveIn.addMouseListener(new MouseAdapter() {//认输事件public void mouseClicked(MouseEvent e){try{gamePanel.send("lose|");//发送认输信息}catch(Exception ex){ex.printStackTrace();}}});buttonAskRegret.addMouseListener(new MouseAdapter() {public void mouseClicked(MouseEvent e){if(gamePanel.list.size()==0){JOptionPane.showMessageDialog(null, "不能悔棋");return ;}if(gamePanel.list.size()==1){int flag = gamePanel.LocalPlayer==REDPLAYER?REDPLAYER:BLACKPLAYER;if(flag==REDPLAYER){//如果我是红方,判断上一步是不是对方下的,如果是,不能悔棋if(gamePanel.list.get(0).index<16){JOptionPane.showMessageDialog(null, "不能悔棋");return ;}}else{if(gamePanel.list.get(0).index>=16){JOptionPane.showMessageDialog(null, "不能悔棋");return ;}}}gamePanel.send("ask|");//发送请求悔棋请求}});buttonStart.addMouseListener(new MouseAdapter() {public void mouseClicked(MouseEvent e){String ip = textIp.getText();int otherPort = Integer.parseInt(textPort.getText());int receivePort;if(otherPort == 3003){receivePort = 3004;                            }else{receivePort = 3003;}gamePanel.startJoin(ip, otherPort, receivePort);buttonGiveIn.setEnabled(true);buttonAskRegret.setEnabled(true);buttonStart.setEnabled(false);}});}public static void main(String[] args) {new GameClient();}}

后续说明

经过博主一顿瞎搞,程序总算可以真正地进行联机了,真不容易~哈哈,需要注意的是IP地址不能搞错了,得到本机IP地址可以使用cmd的ipconfig命令获取,在这里我是使用手机热点形式,让两台电脑同时连接一个手机热点,然后再输入命令得到本机IP,实现连接的;

具体如下:

①使用ipconfig命令查询本机IP

②在游戏界面输入对方IP地址,点击开始(一定要双方都输入对方的IP地址,并且一个端口为3003,另一个端口为3004,具体原因前面关于联机实现的时候说过了)进行联机

③联机成功

emmmmm...上图吧

这是博主电脑运行效果

这是博主室友电脑运行效果

到这里整个游戏的开发过程已经详尽介绍完毕了;

不过这个游戏依旧存在很多可以优化的地方,但是真正要做好一件事情其实是很繁琐的,这里我想简单地谈下我的解决思路:

Q:我希望对方将我军时,提示音效,我该怎么做?

A:在收到对方发送过来的走棋信息并更新完棋局信息的时候,调用自定义的一个方法,方法的功能是判断对方是否在将我方军,具体实现就是遍历对方的棋子数组,用每个对方的棋子去尝试移动到我方“将”或“帅”的位置,如果某个棋子按照象棋规则可以移动到我方“将”或“帅”的位置(即对方下一步可以获胜),返回true;如果对方将我方军,那么播放音效;音效的播放后续博文将进行播放音效的代码模板补充;

Q:可以记录棋谱信息到文件里,然后根据棋谱文件还原对局信息吗?

A:可以的,前面说到过棋盘类有个存储Node对象的ArrayList对象list用于我们对每一步棋的信息进行存储,我们可以在一方获胜的时候,将list对象以某种自定义的形式(或者直接用JSON去封装)进行重组,变成一个特殊的字符串,使用文件IO存储到电脑内存中,需要注意的就是存储的时候需要另外记录下自己这方的执子颜色;根据棋谱文件还原棋局的话,需要加些UI方面的工作,在窗口类新加按钮和文本框用于读取指定名称的棋谱,解析后根据棋谱中我方执子颜色初始化棋盘信息,然后可以点击下一步按钮,一步一步地对棋谱信息进行棋局还原。游戏常用的历史文件的IO以及UI组件的使用后续博文也会补充相关模板;

Q:为什么我游戏结束后,不能重新点击开始重开一局?

A:emmm...博主尝试实现过,但是效果不理想(重新让开始按钮变成可点击状态时,只有一个游戏窗口的开始可以点击),我摸不着头脑,索性没去优化了。也许可以另创一个类,将窗口对象实例化为一个类静态成员,然后类似“工厂模式”那样,进行工厂类A.gameClient.button.setEnable(true)的操作;暂时不想去尝试了。

如果各位童鞋还有其他的什么疑问。欢迎大家在评论区一起沟通。创作不易,感谢支持~

Java游戏开发——中国象棋联机版相关推荐

  1. Java实现中国象棋(联机版)

    Java实现中国象棋(联机版) 该版本的中国象棋,程序有点复杂,是基于网络通信的基础上实现的.由于代码带太长,我这里就只做简单的演示,下面会给出链接地址的. 一.程序结构: 客户端: 服务端: 二.操 ...

  2. Java+Swing实现中国象棋游戏

    目录 一.系统介绍 1.开发环境 2.技术选型 3.系统功能 二.系统展示 1.首页 2.黑棋走 3.红旗走 三.部分代码 ChineseCheseRule.java 四.其他 1.更多系统 Java ...

  3. 基于Java+Swing实现中国象棋游戏

    基于Java+Swing实现中国象棋游戏 一.系统介绍 二.功能展示 三.其他系统 四.获取源码 前言 中国象棋是起源于中国的一种棋,属于二人对抗性游戏的一种,在中国有着悠久的历史.由于用具简单,趣味 ...

  4. iOS cocos2d 2游戏开发实战(第3版)

    <iOS cocos2d 2游戏开发实战(第3版)> 基本信息 原书名:Learn cocos2d 2: Game Development for iOS 作者: (美)Steffen I ...

  5. iOS 5 cocos2d游戏开发实战(第2版)

    <iOS 5 cocos2d游戏开发实战(第2版)> 基本信息 作者: [美]Steffen Itterheim Andreas Low [作译者介绍] 译者: 同济大学苹果俱乐部 丛书名 ...

  6. 基于Java EE的中国象棋网上对战的设计与实现

    技术:Java.JSP等 摘要:中国象棋是一种起源于中国古代的双人对战棋类游戏,广泛的流行在全国各地,是中华文化的典型代表之一,体现着中华文化的智慧和蕴含.为了传承这一重要的传统,理解其中的精髓,以及 ...

  7. java中国象棋网络对弈,java课程设计---中国象棋对弈系统

    java课程设计---中国象棋对弈系统 1 目目 录录 摘要 1 关键字 1 正文 2 1.程序设计说明. 2 1.1 程序的设计及实现 2 1.1.1搜索引擎的实现(engine包) . 2 1.1 ...

  8. Java游戏开发框架LGame-0 2 8版发布(含JavaSE及Android版,已有文档)

    LGame是LoonFramework框架的一部分,也可简称做"LF"或"Loon". LGame框架的创立初衷在于,构建一个高效且完善的Java游戏开发体系, ...

  9. Java游戏开发组件LGame简易测试版发布(版本号 0 1 5)

    LGame-Simple-0.1.5组件下载地址:http://code.google.com/p/loon-simple/downloads/list 2009-09-13 更新内容: Java游戏 ...

最新文章

  1. SysTick定时器的一个简单应用
  2. [通告]Nuget服务宕机,出现 503 Server Unavailable 错误无法编译及解决方法
  3. leetcode 81 Search in Rotated Sorted Array II ----- java
  4. [2019CSP多校联赛普及组第五周] 调度CPU (贪心)
  5. 适用于高级Java开发人员的十大书籍
  6. 概率论和数理统计 - 03 - 多维随机变量及其分布
  7. 对网站商城源码的研究分析 分享大量源码下载
  8. De Casteljau算法
  9. Easy AR初级开发教程
  10. Program E的Flash前端
  11. php 禁止外链,php伪造referer突破网盘禁止外链
  12. IntelliJ IDEA 设置快捷键(Keymap)
  13. Mathtype安装教程(mathpage.wll文件未找到)
  14. 尤里复仇退出界面_win10运行红警2尤里复仇的解决方法
  15. 跨界融合,共创智能汽车研发新生态(技术大会诚邀您的莅临)
  16. 简单粗暴的EMC设计指南
  17. 容联云发送手机短信验证码
  18. html5中float的用法,float的用法总结大全
  19. 【素描基础】大师素描及素描抽象明…
  20. 跨境独立站MaTaCart教你怎么查谷歌排名

热门文章

  1. 边玩游戏边学Python,原来编程如此有趣!
  2. Proteus使用和下载
  3. 金链盟中国区块链应用大赛广泛征集应用案例
  4. FS2956A 输入8-120V 用于液晶仪表5V-USB 充电口方案
  5. 编程练习,简单练习,图形打印
  6. 产品经理必备的5个办公软件, 你学会几个?
  7. 金山携手PICC探索杀毒软件服务全新模式
  8. 我对所谓电信运营商重组和3G发牌的看法
  9. java多线程相关问题汇总
  10. 4.3nbsp;科斯定理分析