接上一篇,继续完善功能。
两个用户对战 一黑一白
用户可以是人,也可以是AI。对战模式支持人人,人机,机机。
本次比赛执棋颜色
用户名
密码
游戏得分(赢得局数)
下棋
输赢
一黑一白交替轮流下棋
可以决定哪个玩家先手
不可以重复下棋到同一个位置
不可以将棋子下到边界外
可以撤回刚刚下的棋,不可以撤回上一步的棋
哪一方横竖斜到达5个棋子赢一局
用户登录:
用户名、密码输入栏(带提示,用户密码匹配检验),登录按键,注册按键;
登录图像。
选择功能:
新游戏:对战模式 —— 人人,人机,机机
游戏积分 —— 获胜局数
退出游戏:关闭游戏
下棋主界面
设置棋盘、背景板、菜单栏、棋子计数板、计分板、棋子、下棋指示器,刷新窗体后这些都不会消失。
选择游戏先手为黑棋还是白棋。
背景板、棋盘:17*17,棋盘在背景板之上。
菜单栏:撤回、清空、存档、帮助功能——撤回/清空时,计数器要跟着变化。
棋子计数板:记录当前棋盘上黑白棋子个数。(图像不重叠,随撤回清空等操作实时刷新)。
计分板:记录目前双方赢得局数。
棋子:下到交叉线(棋子校准)、不重复、不越出棋盘、刷新保存,可以撤回,可以识别获胜。
当前局数计时器:距离游戏开始的耗时。
当有一方获胜后弹出
显示哪方获胜
显示获胜图片
菜单:
再战一局:触发游戏界面
退出游戏:回到菜单界面
回顾棋局:显示重新下棋步骤(撤回步骤显示?)
存档
读档
软件使用日志
1. 撤销:撤回上一步。除了二维数组,还需要一个ArrayList<chess>来储存棋子放置顺序,设置
棋子类,属性有x、y坐标,棋子颜色,这次修正还增加属性序号以及是否撤回。都设置
构造方法可以直接参数进去,设置 get 方法获得属性值,最好再设置set方法设置属性。
(在下棋的时候,将棋子加入到ArrayList,加入的坐标x,y是棋子的圆心坐标,重画时自
己偏移)。当点击撤销时,改棋盘界面地二维数组,但是不删去棋子数组对象,只是将
删去的属性设为true,(这样就会方便后续的棋子回放功能的扩展。)然后调用界面重写
的paint方法(paint方法实现画出当前棋盘上有的棋子,这样也保证了动棋盘棋子不会消
失)。
结合实际的玩棋体验以及实现效果,撤回只允许撤回刚刚下的一步棋。
ArrayList目前用到的方法 (序号排序从0开始)
.add(chessShape obj);加入一个目标类型对象
.remove(index); 根据序号index移除列表内的对象
.size(); 得出数组目前的长度
.clear(); 清除所有对象
.get(index); 获取序号为index的对象
注意:
当撤销时,ArrayList要将最后一个对象的是否撤销改为撤销,二维数组改棋盘值。
2. 清空:清空棋盘上所有棋子。ArrayList所有对象都删除,调用界面的 repaint 函数。
3. 本次为实现功能清楚分解,还将不同界面的监听器分开单独识别并设置功能。
4. 决定先后手:在进入下棋页面的时候,弹出弹窗,通过确认黑棋是否先手判断下的第一个棋子的
颜色,实现先后手设置。弹窗选择YES_NO_Cancle,分别输出0,1,2(黑、
白、不选<不选会反复跳出弹窗直到选择为止>)标志选择结果,根据这个结果给
controlColor赋值。
5.判赢方代码修正:
上次写的时候将判断方向的flag和在延伸在界限内放到了一起。思路是这样的(放一枚棋子判断是否连成5字有8个方向,然后可以归为4个方向,两两共线,那么就可以先选4个朝一头延伸,到边界或者碰到了不一样的颜色再反向延伸,记总数达到5个就判赢。)原来判断方法是:当方向是1时且不超过边界就继续判断(判断是不是同色,同色计数++,不是同色就反向);否则就判断是不是另一个方向且不超过边界。但其实判断超不超过边界和判断是不是同色是同一级的,这个思路始终没有处理过第一个棋子就还没来得及反向就超出了边界范围,这样就会陷入死循环,最终表现出来就是,点击左上角第一个棋子,就再也无法点击任何棋子,点击棋盘上任何点都没有反应,甚至无法关闭且cpu一直在工作=>陷入了死循环。
解决方法:
只需要在第一级判断中只判断方向,而将判断是否在边界内和棋子是否同色放在一起就可以了。
for(int i=0;i<4;i++) { //左上到右下 竖 右上到左下 横
countNumFive=4;
while (countNumFive > 0) {
if (flag == 1) { //判断方向1
if (currX + changeLocation[i][0] >= 0 && currX + changeLocation[i][0] <= 16 && currY + changeLocation[i][1] >= 0 && currY + changeLocation[i][1] <= 16 && goBangUi.chesses[currX + changeLocation[i][0]][currY + changeLocation[i][1]] == colorNum) {
currX += changeLocation[i][0];
currY += changeLocation[i][1];
countNumFive--;
}
else { //换方向
flag = 2;
currX = x;
currY = y;
}
}
else if (flag == 2 ) { //判断方向2
if (currX + changeLocation[i+4][0] >= 0 && currX + changeLocation[i+4][0] <= 16 && currY + changeLocation[i+4][1] >= 0 && currY + changeLocation[i+4][1] <= 16 && goBangUi.chesses[currX + changeLocation[i+4][0]][currY + changeLocation[i+4][1]] == colorNum) {
currX += changeLocation[i+4][0];
currY += changeLocation[i+4][1];
countNumFive--;
}
else { //这个方向没连够
flag = 1;
currX = x;
currY = y;
break;
}
}
if (countNumFive == 0) //够5个就直接返回结果
return colorNum;
}
}
1.repaint函数好像是调用的paint函数。当想要设置刷新窗口棋子不丢失时,就把棋子重写放在了重
写的paint函数中。但当你想要清空棋盘时,调用了repaint函数,就会让棋子仍然重画无法消掉。
解决办法:
1. 棋子重写仍然写在paint函数里,但是清空的时候先将ArrayList清空,chesses二维数组更
新,再调用repaint方法。
2.棋子重写在撤回方法实现的时候写,不写入paint函数。在清空时,直接调用repaint方法。
综上而言第一个方法更好。
本次解决方案:
repaint再调用了paint方法之后还做了更多的东西。想要实现动棋盘棋子还要保存,那么一定要在paint方法里实现重画棋盘上的棋子的功能,这次通过遍历chesses二维数组实现,保证二维数组一直都显示的是实时棋盘棋子的样子。因此在撤回清空的时候都要及时更新。而在运用到这两个功能的时候,只要保证先改棋盘,再调用paint方法即可。
2.界面布局有点麻烦,想要设置坐标布局好像不太管用,(边框布局只能设置一次,重复只会保留
最后的)(流式布局只会横行排列,然后排到下面,不灵活)。面板、按键大小和布局调整时设
置xy坐标貌似不管用,还未解决,有待多加尝试。以及字体大小设置虽不是问题但有待改进。
本次解决方案:
将窗体布局设置为空,单独给想要设置的模块(包括JLable,JButton,JTextField)设置大小,设置位置即可实现,虽然比较麻烦,但是可以设置到自己想要的位置。
//设置空布局
startjf.setLayout(null);
//设置lable大小和位置
menuTitleLable.setSize(200,100);
menuTitleLable.setLocation(275,20);
3.在设置判断胜方之后弹出winner界面,要实现在这个界面画照片和写字,但是界面无法给监听器
传画笔、监听器传给界面的画笔是画在主界面棋盘格上的。矛盾暂无法解决。有待更新解决方
法。
本次解决方案:
因为每次想要在新界面上画东西时,必须要用这个界面get的画笔,同时这个界面get的画笔只能在这个界面上绘制。绘制实现的顺序时:先生成窗体,然后画笔绘制,否则绘制的内容就会被刷新掉。因此在窗体可视化之后(setVisible)之后,延时500ms,再调用画笔绘制。
但是更好地方法便是重写JFrame类的paint方法,将绘制加到paint方法里,在刷新之后就顺便也把想要的绘制出来,就不会担心生成窗体的时候刷掉之前绘制的内容。
还有一个简便方法就是用JLable,可以显示图片和文字按钮等,非常方便。(提示:越是压在面板底层的Lable要最后加入面板)
用弹窗实现也可以,但是由于那个JOptionPane的按钮键传参设置不太熟练就没用。
设置用户登录之后,显示用户信息,将用户和下棋、棋局结果等绑定,实现赢棋加分,战绩记录。
实现双人单机、人机等分离。
实现棋盘回放等空下来的功能。