电脑智能升级难度更大, 采用隔空跳的规则,这样变化更多一些,可玩性更好, 是一个eclipse的工程,
现在是最新版本:
1.修改电脑的智力,难度更高,要战胜电脑?你有10%的机会
2.现在支持多个玩家游戏(将来支持网络游戏)
3.下一步设想移植到手机,PPC上;
4.增加声音效果;
一、 概述:
跳棋在我国是一项老少皆宜、流传广泛的益智型棋类游戏。由于其规则简单,一玩就懂,一辈子都不会忘,所以几乎每个人从小到大都下过跳棋。如果您不愿陷入激烈的思考和竞争,那么无疑本游戏可以使您回到一种更平和、产生美好回忆的心情之中。
在此我把近十天的成果做一个简要的介绍,尽量把问题说得清楚细致。希望与对跳棋编程有同样兴趣的伙伴们好好讨论一下,也希望下一版本的跳棋做得更好。
这个跳棋游戏主要有下几个特点:界面漂亮,操作简单,功能简洁,老少兼宜。我爸爸都很喜欢下。整个跳棋的源码你可以下载。
二、 跳棋整体设计
UML类图:
先给跳棋整个代码做一个整体介绍,说明每个包,每个类具体作用:
1. org.yushang.jumpchess.app
AppJumpChess.java实现的漂亮的窗体界面,标题栏和各种按钮,设置玩家个数及类型(人,或是计算机)。
2. org.yushang.jumpchess.audio
WavePlayer.java主要用于播放游戏中各种声音效果,比较简单。
3. org.yushang.jumpchess.image
ImageLoader.java主要用于从资源文件中导入各种图片背景,比较简单。
4. org.yushang.jumpchess.Interface
Animation.java是用于播放动画的超类
AnimationGO.java是用于播放棋子走动的动画
AnimationSelect.java是用于播放棋子被选中后,“抖动”动画效果
Drawer.java主要是静态地显示棋盘、棋子、下棋位置等图片
JumpChessControl.java是一个很复杂的类,控制玩家下棋顺序、棋子棋盘的显示、动画显示、以及响应玩家的下棋的鼠标事件。
5. org.yushang.jumpchess.pkg
Chess.java棋子类,说明棋子的颜色以及索引号等信息。
ChessBoard.java棋盘类:
索引功能:为棋子和棋盘坐标建立索引关系,根据棋子的可快速检索出该棋子的坐标或根据坐标快速检索出该位置是否有棋子。由于这两项功能在下棋过程中使用频率很高,因此对性能要求也很高。
下棋规则:设定了下棋规则,可以是传统的规则,也可以隔空跳规则。这是一个麻烦的工作。涉及数据结构方面的知识,有图的生成和最短路径搜索两个难点。
RealChessBoard和VirtualChessBoard是ChessBoard派生类: RealChessBoard中建立了棋子和棋盘坐标建立真正的索引关系。而VirtualChessBoard是建立在RealChessBoard基础之上的,VirtualChessBoard只是保存当已走动的棋子坐标变化情况,这样VirtualChessBoard的数据量很小,提高计算机下棋智能计算速度,这一优点在介绍计算机下棋策略详细讨论。RealChessBoard和VirtualChessBoard只是创建方式有点区别,使用上没有任何差别,这也是多态性的一个运用。
Player.java玩家类
保存了玩家棋子颜色、昵称、敌对玩家,棋子摆放初始区域等信息。同时输出下棋路径,为播放动画作准备。
有两个Play和Man两个派生类,计算机玩家的下棋由org.yushang.jumpchess.strategy控制,而人玩家的下棋要由用户鼠标事件来驱动。这两个类实现这两个不同的功能。
6. org.yushang.jumpchess.pub
BoardArea.java这把棋盘各玩家所处的位置编号成6个区域。该类可得到自己区域包含哪些坐标(getAreaPositions)。也可得到和自己相对区域(getOppsiteArea)。
Color.java是棋子颜色类
Director.java棋盘任何一坐标位置(非棋盘边界),在放六个方向上都有相邻的坐标位置,
Map.java、Node.java、Nodes.java三个文件是和数据结构“图”相关的三个类。
Position.java棋盘坐标类,有三个功能:设定了棋盘边界、给定一方向可以得到该方向上相邻的坐标、计算与另一坐标的距离。
7. org.yushang.jumpchess.strategy
计算机下棋策略主要有以下几个方面:
- 计算出己方所有可能走法
- 选出一个跳得最远(尽快让自己获胜)的棋子和走法。
- 选择一个最可能阻碍对方前行的棋子和走法。
- 尽可能选择可为下一步提供最有利条件的棋子和走法
- 对以上几项做综合分析,得出较好的走法。
Analyser.java:策略分析类,运用对各种策略进行综合分析, 得出较好的下棋位置。
Strategy.java:策略基类。
MainStrategy.java:让棋子跳得最远的策略
DoubleStrategy.java: 为下一步提供最有利条件的策略
OppsiteScore.java:尽可能阻碍对方前行的策略
三、 界面设计:
因为这是一个游戏,应该在界面上下点功夫,早就不是DOS时代了,应该不会有人喜欢玩一个界面很难看的游戏。同时界面要简洁,功能也不必太多,但基本功能却一定要做好,让用户玩得痛快。下图是界面:
界面看起来很复杂其实很简单,棋盘是图片,按钮也是图片,都是贴上去的。这里有两个难点:
1. 标题栏制作:
去掉原有的标题栏:
创建Shell的时候加上SWT.NO_TRIM参数就没有边框和标题栏了
sShell = new Shell(SWT.NO_TRIM);
生成自己的标题栏
创建一个Label,放到Shell的(0,0)位置,设置一下图片就可以了
Title.jpg是一个图片:
final ImageLoader imageTitle = new ImageLoader(sShell.getDisplay(), "Title.jpg"); // ImageLoader是自己创建的类,用于载入Title.jpg,
lblTitle = new Label(parent, SWT.NO_BACKGROUND);
lblTitle.setImage(imageTitle.getImage());//设置Image属性
lblTitle.setBounds(0, 0, 800, 28);//设置位置和大小
如何移动Shell
标题栏做好了,问题成于这个Label假标题栏无法移动Shell,怎么办呢?只要当鼠标在lblTitle上拖动时,程序就要调整Shell的位置,才能完成原有标题栏的效果。
代码图如下:
Static boolean mouseDown = false; //鼠标是否按下的标记
Static Point lastMousePoint = null; //按下鼠标时的位置
lblTitle.addMouseListener(new MouseAdapter( {
//按下鼠标时
public void mouseDown(MouseEvent e) {
mouseDown = true; //设置按下标记
lastMousePoint = new Point(e.x, e.y); //记录按下的位置
}
//鼠标释放时
public void mouseUp(MouseEvent e) {
mouseDown = false; //清除按下标记
}
});
lblTitle.addMouseMoveListener(new MouseMoveListener() {
public void mouseMove(MouseEvent e) {
if (mouseDown) { //鼠标在lblTitle上移动,且鼠标已按下
Point nowMousePoint = new Point(e.x, e.y);//鼠标的新位置
Point point = sShell.getLocation();//当前Shell的位置
//计算Shell的新位置
point.x += (nowMousePoint.x - lastMousePoint.x);
point.y += (nowMousePoint.y - lastMousePoint.y);
sShell.setLocation(point);//设置Shell的位置
}
});
2. 按钮制作:
先导入三个按钮图片
正常显示的图片"Begin1.jpg"
鼠标移动到按钮上时显示的图片"Begin2.jpg"
按下鼠标时显示的图片"Begin1.jpg"
final ImageLoader imageBegin1 = new ImageLoader(sShell.getDisplay(), "Begin1.jpg");
final ImageLoader imageBegin2 = new ImageLoader(sShell.getDisplay(), "Begin2.jpg");
final ImageLoader imageBegin3 = new ImageLoader(sShell.getDisplay(),"Begin1.jpg");
创建按钮
lblBegin = new Label(parent, SWT.NO_BACKGROUND);
lblBegin.setImage(imageBegin1.getImage());
lblBegin.setBounds(70, 40, 75, 38);
为按钮各事件写入代码
lblBegin.addMouseTrackListener(new MouseTrackAdapter() {
public void mouseEnter(MouseEvent e) { //鼠标移动到按钮上方
lblBegin.setImage(imageBegin2.getImage());
}
public void mouseExit(MouseEvent e) { //鼠标从按钮上方移开
lblBegin.setImage(imageBegin1.getImage());
}
});
lblBegin.addMouseListener(new MouseAdapter() {
public void mouseDown(MouseEvent e) {
if (e.button == 1) {//按下鼠标左键
lblBegin.setImage(imageBegin3.getImage()); //在这里写入单击事件代码
}
}
public void mouseUp(MouseEvent e) {
if (e.button == 1) {//释放鼠标左键
lblBegin.setImage(imageBegin2.getImage());
}
}
});
3. 界面控件创建序列图:
4. 按钮的功能代码:
开始按钮:(BeginButton)setCanvas.setVisible(true);//显示玩家设置界面
退出按钮:(ExitButton) System.exit(0);//退出游戏
关闭按钮:(CloseButton) System.exit(0);//退出游戏
确定按钮:(OKButton) Initialize();//初始化玩家,同时调用JumpChessControl.Initialize();
取消按钮:(CancelButton) setCanvas.setVisible(false);//关闭玩家设置界面
由于专门有一个类来导入图片(ImageLoader),好处是界面换肤很容易做到,把图片替换就可以了。如何显示棋盘棋子,坐标转换,棋子动画,声音等遇到什么问题(不能退出,尝试解办法失则)
四、 跳棋算法:
1. 棋盘坐标定义
坐标定义如图所示,红色数字标识棋盘的区域.
2. 边界限制
如图所示,当X坐标为1时,Y的坐标只能为5,当X坐标为2时,Y的坐标可以5或6。于是我们建立一个数组:
final static private int[][] pos = {
{5,5}, //X坐标为1,Y的上限是5,下限是5
{5,6}, //X坐标为2,Y的上限是5,下限是6
{5,7}, //X坐标为3,Y的上限是5,下限是7
{5,8}, //X坐标为4,Y的上限是5,下限是8
{1,13}, //X坐标为5,Y的上限是1,下限是13
{2,13}, //6
{3,13}, //7
{4,13}, //8
{5,13}, //9
{5,14}, //10
{5,15}, //11
{5,16}, //12
{5,17}, //13
{10,13}, //14
{11,13}, //15
{12,13}, //16
{13,13}, //17
};
在Position类中IsLegalPosition函数可以确定一个坐标是否合法
public static boolean IsLegalPosition(int x, int y) {
if ((x < 1) || (x > 17)) {
return false;
}
if ((y < pos[x - 1][0]) || (y > pos[x - 1][1])) {
return false;
}
return true;
}
3. 棋盘类(ChessBoard)中棋子和坐标的索引关系
棋盘中所有Chess集合
private Chess[] chesses = null;//所有棋子对象都保存这个数组当中
下面函数可以根据索引号返回棋子对象
public Chess getChess(int index) {
return chesses[index];
}
棋子和坐标的对应关系
private Position[] chessesPosition = null;//所有棋子坐标都保存在这个数组当中
下面函数可以根据棋子对象或棋子索引号返回坐标
public Position getPosition(Chess chess) {
return chessesPosition[chess.getindex()];
}
public Position getPosition(int index) {
return chessesPosition[index];
}
坐标和棋子的对应关系
private Chess[][] chessesIndex = new Chess[17][17];//数组保存了17*17个棋子对象指针
下面函数可以根据棋子坐标返回该位置上的棋子,如果没有棋子返回Null
public Chess getChess(Position position) {
if (position == null){
return null;
}
return chessesIndex[position.getx() - 1][position.gety() - 1];
}
4. 下棋规则的算法
坐标的六个方向及相邻坐标的关系
在任一个坐标位置上(非边界位置)有相邻六个坐标位置,我们称六个方向分别为:UpLeft、UpRight、Left、Right、DownLeft、DownRight。相邻坐标的关系如下图:
下面的代码可以得到相邻坐标:(在Position类实现的)
public Position getJoint(Director director) {
int x = 0;
int y = 0;
if (director == Director.UpLeft) {
x = this.x;
y = this.y + 1;
} else if (director == Director.UpRight) {
x = this.x + 1;
y = this.y + 1;
} else if (director == Director.Left) {
x = this.x - 1;
y = this.y;
} else if (director == Director.Right) {
x = this.x + 1;
y = this.y;
} else if (director == Director.DownLeft) {
x = this.x - 1;
y = this.y - 1;
} else if (director == Director.DownRight) {
x = this.x;
y = this.y - 1;
}
if (IsLegalPosition(x, y)) {//判断坐标是出边界了
return new Position(x, y);
} else {
return null;
}
}
棋子跳动算法
该算法在ChessBoard.CanJumpTo函数中实现
找出一个棋子所有可走位置,并建成数据结构“图”的形式
这个算法有点象图的广度优先遍历算法。文字说明如下:
1.把当前棋子坐标加入“图”中。
2.从当前棋子位置出发,从六个方向查找棋子可跳的位置,
3.如果第2步找到坐标没有加入“图”中,就把这些坐标加入“图”中去。
4.从第3步刚加入“图”中的坐标出发,继续执行第2步。一直到没有坐标加入“图”中为止。
最短路径搜索
当用户指定下棋位置以后,还要计算出棋子从当前位置到目标位置的最短路径,为游戏的播放棋子走动动画作准备。这个算法还是有点象图的广度优先遍历算法,文字说明如下:
1.把所有坐标的“权值”设为-1
2.把当前棋子坐标的“权值”设为0。
3.从当前棋子位置出发,查找棋子可跳的位置。
3.如果第3步找到坐标没有“权值”为-1,就把这些坐标的“权值”在上一坐标的基础上加1。
4.从第4步刚设定“权值”的坐标出发,继续执行第2步。一直到所有坐标都设定“权值”为止。
5.从目标位置开始,倒退着开始查找,每次查找的位置应该是“权值”比当前位置减小1的坐标。一直找到起始位置。这样就找到最短路径了。
这些的都是数据结构基础知识,有点抽象,只要理解加想象就没有问题了。哦,上次“平安”考试中有一个题目:什么是前序遍厉,什么是后序遍厉,我真的记不住。数据结构的知识能理解能运用,考试却不能得分,冤呀。
5. 计算机下棋策略
这是最难设计的部分,人与计算机的思维方式完全不同,如何把人的下棋思路表示计算机可运算的数据结构,这是比较困难的,要不然人工智能这么多年一直没有突破性进展呢?
两坐标间距离计算方法
没有写完
五、 跳棋控制类
所有的工具都做好了,现在要把它组装起来,做好了我们就可以完工了,这个组装没有UML设计,写得有点乱,但是思路是很清晰的,下面画出流程:
另外需要说明的是:由于程序是事件驱动的,而且动画显示用记时器实现,在编码上实现上面流程图的思路,还是比较困难的。
六、 改进计划
1.支持网络对战(坐标旋转的问题)
2.移植到手机PALM、PPC上
3.实现换肤功能,用QQ的图片总是不大好吧。
4.提高计算机智能,目前的只能达到QQ中“高级棋师水平”,我在QQ中胜率是70%。
七、 总结
我写跳棋游戏,不是为了玩游戏,只想从中体验编程的快乐。另一方面也看看自己学习能力到底怎样。因为我这是第一次用eclipse做开发,第一次用Java写程序,第一次用UML做设计。花了十来天时间边学边做居然做出来,还比较满意。虽然大大小小项目做过不少,但我从来没有在算法上觉得有难度,跳棋算法是比较复杂的。
做完“我的跳棋”在以下几个方面得到锻炼:
- 学习Java和eclipse,十天时间基本掌握一门全新编程工具应该没有问题。我对一直很欣赏Java,其中有很多先进的编程理念,比如GC,完善的SDK,其中源码写得十分漂亮。Eclipse真是优秀的IDE是我目前觉得最好的一个编程环境。VB,VC,Delphi比起它来是垃圾了。
- 用了一点UML方面的知识,感觉编程难度不在于某一个函数该怎么写,而在于一个类,一个系统接口如何设计,每个类的功能如何分配,把它们合理的组合关联,从而实现复杂的功能。
- 另外也决定写一个文档,也该加强这方面的能力。世界上90%的代码都不会被作者以外的人阅读,要与别人交流,表达自己的思路只能借助于文档。中国决不缺少程序员,缺少的文档员。