接上一篇,看了别的优秀作品有点感触,给自己一点灵感,完善&优化项目。
画个大纲(跟随慢慢开发过程不断完善)
1.用户
两个用户对战 一黑一白
用户可以是人,也可以是AI。对战模式支持人人,人机,机机。
- 属性
本次比赛执棋颜色
用户名
密码
游戏得分(赢得局数)
存档棋盘信息(二维数组,chessShape类型数组,黑、白、总棋子个数,下一次下棋的棋权)
-
方法
下棋
输赢
设置、获取属性接口
2.比赛规则
一黑一白交替轮流下棋
可以决定哪个玩家先手
不可以重复下棋到同一个位置
不可以将棋子下到边界外
可以撤回刚刚下的棋,不可以撤回上一步的棋
哪一方横竖斜到达5个棋子赢一局
点击存档,当前用户储存当前棋面所有信息
读档读取当前用户保存的棋盘,一个用户只能存一个棋盘(人机模式加进来可以分开储存)
3.界面
- 登录界面
用户登录:
用户名、密码输入栏(带提示,密码隐藏,用户密码匹配检验),登录按键,注册按键(注册新建user储存到用户txt文档);
登录图像。
- 菜单界面
选择功能:
新游戏:对战模式 —— 人人,人机,机机
游戏积分 —— 获胜局数
退出游戏:关闭游戏
可以有设置按键:设置游戏背景音乐,音量等,添加用户设置棋子花色功能(现阶段默认登录用户为黑子)。
- 游戏界面
下棋主界面:
设置棋盘、背景板、菜单栏、棋子计数板、计分板、棋子、下棋指示器,刷新窗体后这些都不会消失。
选择游戏先手为黑棋还是白棋。
背景板、棋盘:17*17(16行,17根线,在中间画分界小黑点),棋盘在背景板之上。
菜单栏:撤回、清空、存档、帮助功能——撤回/清空时,计数器要跟着变化。
棋子计数板:记录当前棋盘上黑白棋子个数。(图像不重叠,随撤回清空等操作实时刷新)。
计分板:记录目前双方赢得局数。
棋子:下到交叉线(棋子校准)、不重复、不越出棋盘、刷新保存,可以撤回,可以识别获胜。
当前局数计时器:距离游戏开始的耗时。
- 获胜界面
当有一方获胜后弹出
显示哪方获胜
显示棋面棋子数
显示获胜图片
菜单:
再战一局:触发游戏界面(棋盘清空)
退出游戏:回到菜单界面(棋盘清空)
回顾棋局:显示重新下棋步骤(撤回步骤显示,回顾结束后弹窗返回)
乱七八糟的功能
存档
读档
软件使用日志
第四天 —— 完善优化菜单界面、获胜界面功能
实现棋子回放、退出游戏、存档方法,完善一步撤销提示、赢棋之后界面跳转逻辑。
实现方式以及一些修正
1. 撤销:设置撤销标志,当点击撤销时,判断标志——撤销是否在上一步发生(发生为1,否则为
0),如果发生则,弹窗提示已经撤销一步,不可以再次撤销;如果未发生,则是标志置
为1,实现撤销功能。当每次下棋的时候都讲撤销标志置为0。
2. 退出游戏:弹窗确认是否离开,是:判断没有存档标志——则提示没有存档:是否离开,是则清
空数据,跳转菜单页面,否则都保留原棋盘页面。
3. 存档:将当前棋盘所有数据存入用户信息中。
4. 读档:判断用户是否有存档的棋盘,有则弹窗提示,并初始化棋盘;否则弹窗提示没有存档棋
盘。
5. 回顾棋盘:先将棋盘清空,然后调用界面的paint方法重画棋盘,根据当前储存的chessIndex开
始绘制棋子,每次下棋之后隔1.5s判断这个棋子是否被撤回,若撤回就重新绘制前面
的所有棋子并将当前棋子跳过,继续绘制记录的下一个棋子。
棋子回顾结束后弹窗提示回顾结束,回到赢棋页面。
6.判赢方代码再学习:
- 上次自己实现的可能是一种看起来比较聪明的穷举法。
思路是这样的:
(放一枚棋子判断是否连成5字有8个方向,然后可以归为4个方向,两两共线,那么就可以先选4个朝一头延伸,到边界或者碰到了不一样的颜色再反向延伸,记总数达到4个就判赢,return棋子颜色,否则4个大方向都结束还没有数到4就返回0。数4个是因为当前棋子默认记录)
设置了8个方向坐标,用于坐标移动:
int[][] changeLocation =new int[][]{{-1,-1},{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1}};//04 15 26 37 是一个方向
具体代码:
public int ifWin(int x,int y,int colorNum){
int countNumFive = 4;
int currX=x,currY = y;
int flag = 1;
for(int i=0;i<4;i++) { //左上到右下 竖 右上到左下 横 4个方向判断
countNumFive=4;
while (countNumFive > 0) {//如果没数到4就继续数
if (flag == 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 ) {
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) //如果连够4个+默认当前,则直接结束方法,返回颜色值
return colorNum;
}
}
//如果4个大方向都不够5个,则返回0,表示没有连够
return 0;
}
- 还学习到里一种听起来比较莽的穷举法:五元组法
思路:
一个16行*16列的棋盘上共有17*17个点,五子连棋的情况共有17*13*2(横纵)+13*13*2(左右斜)种。
只要判断当前是否有棋子连成这个局势就能判断是否有赢棋。
- 听闻大佬是用动态规划实现的:
思路:
4个二维数组模拟棋盘上某个位置在4个方向分别可以连成的最长棋子数,每放一个棋子,其向四周扩展,半径为4,该方向上连续不空棋,或者不遇到异色的棋子的最长记录都++,当有某条记录等于5时,直接return获胜的颜色。
7.发现一个小问题
上次不是将repaint方法是调用了paint方法吗,除此之外还做了点别的事情。如今调用的时候发现了二者的表面区别:当调用repaint方法是,他是先实现之后的代码,再调用paint刷新窗体。比如你想要刷新窗体,重画棋盘,然后棋盘上面画存档的棋子;这时候如果调用repaint,就会先画棋子,然后paint画棋盘,就把你刚刚画好的东西刷新掉了,这并不是我们想要实现的功能,就需要调用paint方法。
解决遇到的问题
1.上次问题解决放到功能实现里了,其他还没解决完。
本次待解决的问题
设置用户登录之后,显示用户信息,将用户和下棋、棋局结果、存档读档等绑定,实现赢棋加分,战绩记录。
实现双人单机、人机等分离。