Java学习笔记(十六)—— 开发个小项目(GoBang4.0)

 接十二,今天重点搞定简单基础版本的AI下棋。

画个大纲(跟随慢慢开发过程不断完善)

1.用户

两个用户对战  一黑一白

用户可以是人,也可以是AI。对战模式支持人人,人机,机机。

  • 属性

本次比赛执棋颜色

用户名

密码

游戏得分(赢得局数)

存档棋盘信息(二维数组,chessShape类型数组,黑、白、总棋子个数,下一次下棋的棋权)

  • 方法

下棋

输赢

设置、获取属性接口

2.比赛规则

一黑一白交替轮流下棋

可以决定哪个玩家先手

不可以重复下棋到同一个位置

不可以将棋子下到边界外

可以撤回刚刚下的棋,不可以撤回上一步的棋

哪一方横竖斜到达5个棋子赢一局

点击存档,当前用户储存当前棋面所有信息

读档读取当前用户保存的棋盘,一个用户只能存一个棋盘(人机模式加进来可以分开储存)

3.界面

  • 登录界面

用户登录:

用户名、密码输入栏(带提示,密码隐藏,用户密码匹配检验),登录按键,注册按键(注册新建user储存到用户txt文档,并弹出储存成功界面选择是否直接登录);

登录图像。     

小logo图标。

  • 菜单界面

选择功能:

新游戏:对战模式 —— 人人,人机,机机

游戏积分 —— 获胜局数

退出游戏:关闭游戏

可以有设置按键:设置游戏背景音乐,音量等,添加用户设置棋子花色功能(现阶段默认登录用户为黑子)

  • 游戏界面

下棋主界面:

设置棋盘、背景板、菜单栏、棋子计数板、计分板、棋子、下棋指示器,刷新窗体后这些都不会消失。

选择游戏先手为黑棋还是白棋。

背景板、棋盘:17*17(16行,17根线,在中间画分界小黑点),棋盘在背景板之上。

菜单栏:撤回、清空、存档、帮助功能——撤回/清空时,计数器要跟着变化。

棋子计数板:记录当前棋盘上黑白棋子个数。(图像不重叠,随撤回清空等操作实时刷新)。

计分板:记录目前双方赢得局数。

棋子:下到交叉线(棋子校准)、不重复、不越出棋盘、刷新保存,可以撤回,可以识别获胜。

当前局数计时器:距离游戏开始的耗时。

  • 获胜界面

当有一方获胜后弹出

显示哪方获胜

显示棋面棋子数

显示获胜图片

菜单:

再战一局:触发游戏界面(棋盘清空)

退出游戏:回到菜单界面(棋盘清空)

回顾棋局:显示重新下棋步骤(撤回步骤显示回顾结束后弹窗返回

乱七八糟的功能

存档

读档

软件使用日志 



第五天 —— 完善AI下棋逻辑,界面功能

实现AI判断合适的下棋位置,完善登录界面,构建储存用户txt。

实现方式以及一些大佬的优秀小tips

1.AI下棋:

计算棋盘上每个空位上的权值,权值越大,越有可能下在这个位置。

权值如何设计:

下棋特点:

棋子最容易下在同色棋子周围,最容易下在连棋长度最长的地方,最容易堵别人的活三连……

一个棋点周围有8路(8个方向)与其有关,列出这8个方向的所有可能,并根据情况赋予不同权值而达到计算权值做出最佳决策的效果。

在初始阶段我们并不考虑垂直、水平这种同一方向(感觉情况会复杂很多),将8个方向先分开考虑,每个方向的思路都一样,所以共用。

从这个要计算权值的点出发朝某一个方向算棋,先分为两类:

ps:从这个点朝某个方向出发,如果没连到棋就不算权值直接为0。

活棋:

从这个点朝某个方向出发,连到几个同色棋子之后是空棋。代码为010,0110,01110,011110等等。

活棋有一、二、三、四连。

死棋:

从这个点朝某个方向出发,连到几个同色棋子之后是空另一方的棋或者边界。代码为01,011,0111,01111等等。

死棋有一、二、三、四连。

图示:

 从左上方向顺时针分别是:活棋一、二、三、四连,死棋一、二、三、四连。

给这8种情况一次赋权值,并以HashMap这种数据结构储存。key为符号编码例如010,011等;value的设计思路:

连数越高,权值应该越大,同等连数活连要大于死连。

又因为8个方向是独立计算的,合并造成的关系应该也有数值大小关系。

为了测试可以先大致设,后续数据关系思路:

棋面权值如何统计:

因为是棋盘上每一个空着的棋点都有可能是放置下一个棋子的位置,因此要遍历棋盘计算权值。

首先这个位置必须是空位置才有可能下棋。

其次这个位置有8个延伸方向,因此需要写8个方向的循环。每个方向最多有四个棋子连棋的延伸。

对某个方向来说周围有棋子才可能连棋,如果周围第一个地方就没有棋子,那么直接认为权值为0,不进入连棋判断的循环;有棋子的话要先记录这颗棋子的颜色,为了后面连棋判断(会突然发现并没有区别己方和敌方,仔细想想好像可以放一起,因为己方、敌方连棋都是我们要放的位置。但再仔细想发现也有区别,可以设置优先级,后面升级再设置)

记录颜色之后进入4个的循环(因为最多四子连棋,否则上次就赢了)——查看棋子颜色计算有多少子连棋,每次循环结果三种情况:

没有棋子:记录连棋记录,break,是活棋,不必再循环下去。

同色:记录连棋记录,继续。

异色:break,直接为死棋。

每个方向的连棋记录生成后(就是key),通过HashMap的据key查值方法,将值叠加起来,8次值的和就是最终这个点的最后权值分数。

代码:

public void fillChessMap(){

        //考虑在最一开始就加入,没用每次计算都重新添加一遍
        this.chessHashMap.addCode();

        //为了检测棋盘输出
        System.out.println("chesses棋盘:");
        for(int i=0;i<=ROW;i++){
            for(int j=0;j<=LINE;j++){
                System.out.print(goBangUI.chesses[i][j]+"\t");
            }
            System.out.println();
        }
        System.out.println();
        //System.out.println(this.chessHashMap.getCodeWeight("010"));

        //算法开始遍历棋盘
        for(int i=0;i<=ROW;i++){
            for(int j=0;j<=LINE;j++){
                if(goBangUI.chesses[i][j]==0){     //这个位置首先必须是个空位子才有可能放棋子
                    int score=0;                   //分数随棋盘算的位置才更新一次
                    for(int l=0;l<8;l++) {         //8个方向循环
                        if(i+this.changeLocation[l][0]>ROW || j+this.changeLocation[l][1]>LINE || i+this.changeLocation[l][0]<0 || j+this.changeLocation[l][1]<0){
                            System.out.println("i:"+i+this.changeLocation[l][0]+" j:"+j+this.changeLocation[l][1]);
                            continue;
                        }                      //先判断是否出了边界,出边界跳过
                        String strCode="0";    //没出就开始记录
                        int chessColor = goBangUI.chesses[i+changeLocation[l][0]][j+changeLocation[l][1]];                      //记录某方向第一个棋子的颜色
                        System.out.println("chessColor:"+chessColor);
                        if(chessColor!=0) {
                            for (int k = 1; k <= 4; k++) {    //连棋循环
                                int nowX=i + k * changeLocation[l][0];   //记录连到的棋子坐标
                                int nowY=j + k * changeLocation[l][1];
                                if (nowX > ROW || nowY > LINE || nowX < 0 || nowY < 0) {   //边界==死棋 break
                                    break;                                
                                }
                                if (goBangUI.chesses[nowX][nowY] == chessColor) { //同色,记录
                                    strCode += chessColor;
                                }
                                else if(goBangUI.chesses[nowX][nowY] == 0){  //空棋 记录,break
                                    strCode += "0";
                                    break;
                                }
                                else           //异色==死棋 break
                                    break; 
                            }
                        }
                        System.out.println("提前str:"+strCode);
                        if(!strCode.equals("0")) {       //因为没有 0 的单独编码所以要避开,否则或中断异常结束
                            System.out.println("str:"+strCode);
                            score += this.chessHashMap.getCodeWeight(strCode);   //8个方向分数相加
                            System.out.println("score:"+score);
                        }
                    }
                    System.out.println("外面要加的score:"+score); 
                    this.chessWeight[i][j]=score;      //最终分数
                }

            }
        }
    }

怎么下棋:

遍历棋盘,权值最大的地方就是本次最下下棋位置。

记录最权值,对应坐标。遍历完之后,下载这个最大位置即可。

代码:

 public void paintChess(){
        int maxWeight=0,maxI=0,maxJ=0;
        for (int i=0;i<=ROW;i++){
            for(int j=0;j<=LINE;j++){
                if(chessWeight[i][j]>maxWeight){
                    maxI=i;
                    maxJ=j;
                }
            }
        }
        //pen.fillOval(X+maxJ*SIZE-SIZE/2,Y+maxI*SIZE-SIZE/2,SIZE,SIZE);
    }

2.一点小tips:

  • 在棋盘上画小点点——类似这种

 实现思路:就是在画完棋盘格之后再像画小棋子一样画上去就可以,位置自行设计,大小控制一下就可以。

  • 当棋盘回放的棋盘有撤回的时候,调用的是paint函数实现的,就会有卡顿的感觉,为了使它更加流畅

实现思路:不要调用paint方法,直接将paint方法内容放在调用的这个位置(我的理解是没有刷新的那个过程?直接摞上去了)

本次待解决的问题

1.因为代码设计问题,想要那个key——value对应的内容值储存一次就够,不用每次遍历都调用,目前设置方法来填充,然后再fill函数之前,调用一次填充函数。不知道还有没有更加自然的方式存疑。

2.用户注册之后可以以流的形式存入txt文档,登录的时候可以读取txt文档并检查用户名密码匹配。

3.设置用户登录之后,显示用户信息,将用户和下棋、棋局结果、存档读档等绑定,实现赢棋加分,战绩记录。

代码

太长啦放github

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值