JAVA项目-AI五子棋(续)

文章详细介绍了棋盘游戏中人机对战的实现思路,包括使用布尔变量标记对战类型,通过遍历棋盘寻找活连和眠连,并为不同情况分配权重来决定AI的落子策略。同时,文章还讨论了悔棋、回放和历史棋局记录的功能实现,以及结束游戏的处理方法。
摘要由CSDN通过智能技术生成

上一篇讲到人人下棋功能,下面讲解其他功能的实现思路。


人机对战

首先使用布尔变量isAI标记当前是人人对战还是人机对战。在点击人机对战按钮后,设置isAI为true。

人机对战的思路:在玩家落子后,遍历整个棋盘,判断每个可落子的位置,以当前位置为原点,分别向八个方向探索,是否有活连或眠连(分一连二连三连四连,眠连表示同色连子后是异色棋子或边界)

如图所示以红圈为原点向八个方向探索,发现有四个方向达成活连,顺时针看分别达成了一连、二连、三连和四连。很显然如果达成了四连,在该点落子就能胜利,因此如果为这四种情况分配权重,应该是依次增加的,且三连四连的权重要比一连二连权重大的多。眠连因为有一头被堵死,所以权重是要比活连整体都小。

我们用字符串表示活连和眠连的几种情况,活连有010,0110,01110,011110,眠连有01,011,0111,01111,同时也考虑到为了不让玩家赢,防止玩家一连二连三连四连,因此要为020,0220,02220,022220,02,022,0222,02222也分配权重,这里用到HashMap存储字符串,并为每个字符串分配合适的分数。

import java.util.HashMap;

public class AIplay {
//    活连:
//    从这个点朝某个方向出发,连到几个同色棋子之后是空棋。代码为010,0110,01110,011110等等。
//    活棋有一、二、三、四连。

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

     HashMap<String,Integer> CodeWeight = new HashMap<String,Integer>();
     
     public void addCode(){
        //活连
        CodeWeight.put("010",10);
        CodeWeight.put("0110",50);
        CodeWeight.put("01110",2000);
        CodeWeight.put("011110",5000);

        CodeWeight.put("020",10);
        CodeWeight.put("0220",50);
        CodeWeight.put("02220",2000);
        CodeWeight.put("022220",5000);

        //死连
        CodeWeight.put("01",5);
        CodeWeight.put("011",25);
        CodeWeight.put("0111",1000);
        CodeWeight.put("01111",2500);

        CodeWeight.put("02",5);
        CodeWeight.put("022",25);
        CodeWeight.put("0222",1000);
        CodeWeight.put("02222",2500);
     }
      public int getCodeWeight(String code){

          return CodeWeight.get(code);
        }
}

最终是否落子要通过对该点四周八个方向的查找,如果找到匹配我们存入的字符串(符合活连或眠连或玩家达成活连眠连)则加分。

//算法开始遍历棋盘
for(int i=0;i<=ROW;i++){
  for(int j=0;j<=COL;j++){
    if(chessArr[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]>COL || i+this.changeLocation[l][0]<0 || j+this.changeLocation[l][1]<0){
                            continue;//如果下一颗棋子落在边界外,直接进入下一次循环
                       }
           String strCode="0";    //没出就开始记录
           int chessColor = chessArr[i+changeLocation[l][0]][j+changeLocation[l][1]];
           if(chessColor!=0) {
               for(int k=1;k<=4;k++) {
                   int R=i+k * changeLocation[l][0]; 
                   int C=j + k * changeLocation[l][1];
                   if (R > ROW || C > COL || R < 0 || C < 0) {   //边界==死棋 break
                         break;                                
                        }
                    if (chessArr[R][C] == chessColor) { //同色,记录
                         strCode += chessColor;
                        }
                    else if(chessArr[R][C] == 0){  //空棋 记录,break
                        strCode += "0";
                         break;
                        }
                     else 
                         break;
                     }                           
                 }
             if(!strCode.equals("0")) {
                  score += chessHashMap.getCodeWeight(strCode);
                 }        
             }
         chessWeight[i][j]=score; 
         }
     }
 }

评估整个棋盘的空位后,最后分数最大的点被认为是最值得落的点。

int maxWeight=0,maxI=0,maxJ=0;
        for (int i=0;i<=ROW;i++){
            for(int j=0;j<=COL;j++){
                if(chessWeight[i][j]>maxWeight){
                    maxI=i;
                    maxJ=j;
                    maxWeight=chessWeight[i][j];
                }
            }
        }  
        int[] resArr = new int[2];
        resArr[0]=maxI;
        resArr[1]=maxJ;
        System.out.println("最佳坐标:"+maxI+","+maxJ);
        System.out.println("最佳分数:"+maxWeight);
        return resArr;

那么如何实现在玩家下棋后自动计算落点落子呢?与人人对战不同的是,人机对战是监听到鼠标按下的动作后做两个事情,一个是在鼠标点击的地方画玩家的棋子,然后调用计算落点的方法,返回最适合落点的坐标,画机器的棋子,相当于点一下画两个棋子。

            g.setColor(Color.BLACK);      
            // 根据行列值 还原 标准坐标
            int cx = c * SIZE + X - SIZE / 2;
            int cy = r * SIZE + Y - SIZE / 2;
    
            // 绘制圆形
            g.fillOval (cx, cy, SIZE, SIZE);
           
            // 绘制完棋子后判断输赢
    
            if(IsWin.isWin (chessArr,r,c)){
                msgFrame ( "玩家胜利!");
                chessFlag = 0;
                return;
            }
 //根据黑子落点计算白子落点
            int[] resArr=fillChesses();
            int r1=resArr[0];
            int c1=resArr[1];    

            g.setColor (Color.WHITE);
            // 根据行列值 还原 标准坐标
            int cx1 = c1 * SIZE + X - SIZE / 2;
            int cy1 = r1 * SIZE + Y - SIZE / 2;
            
            // 绘制圆形
            g.fillOval (cx1, cy1, SIZE, SIZE);
           
            // 绘制完棋子后判断输赢

            if(IsWin.isWin (chessArr,r1,c1)){
                msgFrame ("人机胜利!");
                chessFlag = 0;
                return
            }

悔棋

悔棋的实现主要靠一个chess类型的数组,chess是一个具有r,c,chessFlag,index属性的方法,也就是用chess类型的数组记录每个棋子的坐标,棋子颜色,棋子顺序。

public class Chess {
    int r, c;
    int chessFlag;
    int index;

    public Chess(int r, int c, int chessFlag, int index){
        this.r = r;
        this.c = c;
        this.chessFlag = chessFlag;
        this.index = index;
        
    }
}
    Chess[] chessIndexArr = new Chess[(ROW+1)*(COL+1)];
    int chessIndex=0;

每下一个棋子都创建一个chess对象存到这个数组中,这样如果悔棋,只要把数组最后一个棋子取出来,根据坐标把chessArr棋盘数组中该点赋值为0表示这个点为空棋,再重新刷新棋盘。注意在人机对战模式下,要一下取出两颗棋子,并且因为在下棋时会将当前落点的权重分数设置为0,所以悔棋后要重新统计并填上该点的权值分数(使用reFillWeight方法实现)。

if(btnstr.equals("悔棋")) {
          if(!isAI) {
              if(chessIndex<=0||chessFlag==0) {
                  msgFrame("不能再悔棋了");
                  return;
              }
              Chess chess=chessIndexArr[chessIndex-1];//把最后一个棋子取出来
              chessArr[chess.r][chess.c]=0;
              chessFlag=chess.chessFlag;
              chessIndex--;
          }else {
              if(chessIndex<=0||chessFlag==0) {
                  msgFrame("不能再悔棋了");
                  return;
              }
              Chess chess1=chessIndexArr[chessIndex-1];//把最后两颗棋子取出来
              chessArr[chess1.r][chess1.c]=0;
              reFillWeight(chess1.r,chess1.c);
              chessIndex--;
              Chess chess2=chessIndexArr[chessIndex-1];
              chessArr[chess2.r][chess2.c]=0;
              reFillWeight(chess2.r,chess2.c);
              chessFlag=chess2.chessFlag;
              chessIndex--;
           }
// 刷新
            ui.drawBack (g);
            ui.drawChess (g);
            ui.rfBtn ();
            }

回放

棋局回放实现思路:首先把棋盘清空,这一步主要依靠将chessArr棋盘数组清空,然后刷新界面实现。遍历chessIndexArr数组,按顺序取出每一个chess,根据chess的坐标和棋子颜色画出棋子,每画一个停顿1s,直到chess为null说明遍历完,退出循环。

            if(btnstr.equals("回放")) {
                for(int i=0;i<chessArr.length;i++) {
                    for(int j=0;j<chessArr[i].length;j++) {
                        chessArr[i][j]=0;
                    }
                }
                // 刷新界面
                ui.drawBack (g);
                ui.rfBtn ();
                for(int i=0;i<chessIndexArr.length;i++) {
                    Chess chess=chessIndexArr[i];             
                    if(chess==null) {
                        break;
 //chess数组包括棋盘上所有棋子的信息,包括还没下的,如果棋盘没下满,数组就会有空值,遇到空值说明棋盘已经遍历完
                    }
                    g.setColor(chess.chessFlag==1?Color.BLACK:Color.WHITE);
                    //根据行列,还原坐标
                    int cx = chess.c * SIZE + X - SIZE / 2;
                    int cy = chess.r * SIZE + Y - SIZE / 2;
                    g.fillOval(cx, cy, SIZE, SIZE);
                    try {
                        Thread.sleep (1000);
                    } catch (InterruptedException ex) {
                        throw new RuntimeException (ex);
                    }
                }

            }

历史棋局

历史棋局的实现思路:当某一方胜利代表棋局的终结,创建fileInput方法用来将这一局的信息写入本地文件中。

    public void fileInput() {
        try {
            if(!file.exists())
            file.createNewFile(); 
            FileWriter  out=new FileWriter (file);
            BufferedWriter bw= new BufferedWriter(out);
            str+="第"+times+"局"+":"+"\r\n";
            for(int i=0;i<chessIndexArr.length;i++) {
                Chess chess=chessIndexArr[i];       
                if(chess==null) {
                    break;
                }                                    
                str +=chess.toString();              
            }
            str+="\r\n";
            bw.write(str);
            bw.flush();
            bw.close();
            times++;
        }catch (IOException e1) {
            e1.printStackTrace();
        } 
    }
            if(IsWin.isWin (chessArr,r,c)){
                msgFrame (chessFlag == 1 ? "白棋胜利!!" : "黑棋胜利!!");
                
                chessFlag = 0;
                fileInput();
            }

当点击历史棋局时读取文件并输出

if(btnstr.equals("历史棋局")) {
                if(!file.exists()) {
                    msgFrame("还没有历史棋局!");
                }else {                    
                    try {
                        BufferedReader in=new BufferedReader(new FileReader(file));
                        line=in.readLine();//一次读取文件中的一行数据
                        
                        while (line!=null) {
                            str1+=line+"\r\n";
                            line=in.readLine();
                        }
                        System.out.println(str1);

                    } catch (IOException e2) {
                        e2.printStackTrace();
                  }
 
                }
                
            }

结束游戏

结束游戏主要实现思路:点击结束游戏会清屏,将chessArr数组清空,重新创建chessIndex对象,chessIndex赋值为0,重新刷新界面。

if(btnstr.equals("结束游戏")) {
                isAI=false;
                chessFlag=0;
                //清屏
                for(int i=0;i<chessArr.length;i++) {
                    for(int j=0;j<chessArr[i].length;j++) {
                        chessArr[i][j]=0;
                    }
                }
                chessIndexArr = new Chess[(ROW+1)*(COL+1)];//每次开始一局必须清空上一局的棋盘
                chessIndex=0;                
                //刷新界面,只要刷新就会重新执行paint函数重置棋盘
            
                ui.repaint ();
            }

完整代码在下载资源里~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值