ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

JAVA五子棋

2021-09-24 20:29:52  阅读:215  来源: 互联网

标签:map JAVA temp gp int 五子棋 gm public


一个简单的JAVA小项目,主要实现的功能有:

一、下棋功能,在棋盘的交点处落子。

二、悔棋功能,取消最后一颗下的棋子。

三、简单人机对战功能。

1.窗体实现。主要使用了JFrame类

public class GameUI {

    public static void main(String[] args) {
        GameUI ui = new GameUI();
        ui.showUI();
    }

    //显示游戏界面
    public void showUI() {
        //窗体
        JFrame jf = new JFrame();
        //自定义的面板和鼠标监听器实现类
        GamePanel gp = new GamePanel(jf);
        GameMouse mouse = new GameMouse();

        //将面板添加进窗体
        jf.add(gp);
        //像素点>分辨率,设置窗体大小
        jf.setSize(800, 800);
        jf.setTitle("五子棋游戏");
        jf.getContentPane().setBackground(Color.pink);
        //居中显示
        jf.setLocationRelativeTo(null);
        //设置退出进程
        jf.setDefaultCloseOperation(3);
        //设置可见
        jf.setVisible(true);
        
        //从面板上获取画笔,一定要在窗体显示可见之后
        Graphics g = gp.getGraphics();
        
        //绑定事件处理类
        mouse.setG(g);
        mouse.setGP(gp);
        gp.setGM(mouse);
        //给面板添加鼠标监听器方法
        gp.addMouseListener(mouse);

    }
}

 2.绘制棋盘和棋子。

定义一个画板类对象,基础结构如下,其中定义了一个二维数组,用于存储每个位置棋子的情况。

public class GamePanel extends JPanel implements ActionListener,ChessConfig {
    private JFrame jf ;
    private int[][] map = new int[11][11];
    public GameMouse gm;

    public int[][] getMap(){
        return map;
    }
    public int getChess(int[][]map,int x,int y){
        return map[x][y];
    }
    public void setGM(GameMouse gm){this.gm=gm;}
    public void setMap(int x,int y,int z){
        map[x][y]=z;
    }


    //构造方法
    public GamePanel(JFrame jf) {
        this.jf = jf;
        this.setOpaque(false);
        createMenu();
    }

    //重写paint方法,其目的是在每次需要重绘时(如窗口最小化)保持相同的情况
    @Override
    public void paint(Graphics g) {
        super.paint(g); 
        drawGrid(g);
        drawChess(g);
        g.setColor(Color.PINK); //用于保持落子顺序指示器
        g.fillRect(680,25,100,50);
        g.setColor(Color.black);
        g.setFont(createFont());
        g.drawString("当前:"+gm.message,680,50);
    }

 
    //获取字体
    public Font createFont(){
        return new Font(LETTER,Font.BOLD,LETTER_SIZE);
    }
}

2.1 绘制棋盘和棋子。

绘制棋盘:此处有多种方法,我采用的是循环画正方形的方法来绘制棋盘:

public void drawGrid(Graphics g){
    for (int i = 0; i < SQUARE_NUM; i++) {
        for (int j = 0; j <SQUARE_NUM ; j++) {
            g.drawRect((X0+SQUARE*j),(Y0+SQUARE*i),SQUARE,SQUARE);
        }
    }
}

绘制棋子:我采用的是Graphics类自带的fillOval方法,以黑棋为例:

public void drawBlack(Graphics g,int x,int y ){
    g.setColor(Color.BLACK);
    g.fillOval(x*SQUARE+X0-CHESS/2,y*SQUARE+Y0-CHESS/2,CHESS,CHESS);
}

关于fillOval方法:fillOval(int x,int y ,int width ,int  height)

前两个参数x和y是椭圆外接正切矩形的左上角顶点,width和height代表了矩形的宽和高,在画圆时要注意坐标的处理,使得棋子的圆心落在十字线交叉处。

此外要注意画笔获取的先后顺序,Graphics类方法调用时出现的空指针异常。

2.2 重写actionPerformed方法。

根据对应指令实现不同操作,以认输为例:

if(command.equals("surrender")){
    JOptionPane.showMessageDialog(null, gm.message + "认输");
    newGame();
    repaint();
        }

repaint()的目的是手动重新绘制所有组件。

3.实现落子。

落子的实现主要依靠鼠标监听器的实现类,关键要素有:鼠标点击处的坐标与棋子坐标的转换,防止在同一处重复落子,黑白交替,输赢判断。

public class GameMouse implements MouseListener,ChessConfig {
    private Graphics g;  //定义变量,保存传递过来的画笔对象
    private JFrame jf;
    public GamePanel gp;
    public int turn; //指示黑白顺序
    public int flag ; //指示获胜方
    public int xc ,yc; //棋子的交点值
    public boolean start; //游戏开始判断
    public String message; //落子顺序判断
    public Stack<Integer> stckx; //用于保存坐标 实现悔棋功能
    public Stack<Integer> stcky;
    public boolean AI;//人机对战判断
    ChessAI ai = new ChessAI();
    //set方法,初始化棋盘和画笔
    public void setG(Graphics g) {
        this.g = g;
    }

    public void setGP(GamePanel gp){
        this.gp = gp;
    }

    public GameMouse(){
        this.stckx=new Stack<>();
        this.stcky=new Stack<>();
        this.turn=1;
        this.flag=0;
        this.start=true;
        this.message="";
        this.AI= false;
    }

3.1 棋子坐标处理

以x坐标为例:
public void mouseClicked(MouseEvent e) {
    //获取当前坐标值
    int x = e.getX();

    //超出棋盘的点落在边界点上

    if(x>XX){
        x=XX;
    }
    if(x<X0){
        x=X0;
    }

    //计算棋子坐标
    xc = positionX(x);

//计算棋子位置,在判定范围内落子都在同一处
public int positionX(int s){
    int res=0;
    if((s-X0)%SQUARE<=SQUARE/2){
        res = (s-X0)/SQUARE;
    }if((s-X0)%SQUARE>SQUARE/2){
        res = (s-X0)/SQUARE+1;
    }
    return res;
}
//判断是否已经有棋子
public boolean isFree(int x,int y){
    int [][] map = gp.getMap();
    return gp.getChess(map, x, y) == 0;
}

3.2 落子功能

依靠每次绘制棋子后改变对应变量值来实现黑白交替

    //绘制棋子
    if (&isFree(xc,yc)&&start){
        if (turn==1){
            drawBlack(g,xc,yc);
            gp.setMap(xc,yc,1);
            message="白棋";
            turn=2; //实现黑白交替
        }else if(turn==2){
            drawWhite(g,xc,yc);
            gp.setMap(xc,yc,2);
            message="黑棋";
            turn=1;
        }
    }
    stckx.push(xc); //将棋子坐标入栈
    stcky.push(yc);
}

3.3 输赢判断

 

根据落子位置,判断落子位置上下左右,左上右下,右上左下的棋盘情况,之后对应方向相加。

要注意判断循环的边界,否则容易出现数组越界情况。

以右上为例:

 //右上相同棋子统计
    public int count_RightUp(int[][]map,int i,int j){

        int count = 0;
        int x=i;
        int y=j;
        while (x>=0&&y>0&&x<SQUARE_NUM&&y<=SQUARE_NUM){
            if (map[x][y]!=0){
                if (map[x][y]==map[x+1][y-1]){
                    count++;
                    x++;y--;
                }else
                    return count;
            }
            else
                break;
        }
        return count;
    }

注意要处理已有棋子时的情况 用break跳出循环,否则会在获胜判断后,再次点击则进入死循环。

对落子位置的四个方向求和,判断输赢。

//全向统计
    public void sumAll(int[][]map,int x,int y){
        int sum1 = count_LeftUp(map,x,y)+count_RightDown(map,x,y);
        int sum2 = count_LeftDown(map,x,y)+count_RightUp(map,x,y);
        int sum3 = count_Up(map,x,y)+count_Down(map,x,y);
        int sum4 = count_Left(map,x,y)+count_Right(map,x,y);
        if(map[x][y]==1&&((sum1>=4)||(sum2>=4)||(sum3>=4)||(sum4>=4))){
            flag=1;
        }else if(map[x][y]==2&&((sum1>=4)||(sum2>=4)||(sum3>=4)||(sum4>=4))){
            flag=2;
        }else
            flag=0;
    }

    //判断获胜方
    public void isWinner(int flag){
        if (flag == 1){
           JOptionPane.showMessageDialog(null,"黑棋胜利");
           this.start=false;
        }else if(flag==2){
            JOptionPane.showMessageDialog(null,"白棋胜利");
            this.start=false;
        }else{
            System.out.println("暂无获胜者");
        }
    }

3.4 悔棋功能

悔棋方法定义在GamePanel类重写的actionPerformed方法下,从栈中获得最后一颗棋子坐标,取消该处棋子,之后重绘棋盘。

if (command.equals("back")) { //悔棋
            if (!gm.stckx.isEmpty()) {
                System.out.println("悔棋,返回上一步状态");
                Integer temp_x = gm.stckx.pop();
                Integer temp_y = gm.stcky.pop();
                if (map[temp_x][temp_y] == 1) {
                    setMap(temp_x,temp_y,0);
                    gm.message = "黑棋";
                    gm.turn = 1;
                    repaint();
                } else if (map[temp_x][temp_y] == 2) {
                    gm.message = "白棋";
                    gm.turn = 2;
                    setMap(temp_x,temp_y,0);
                    repaint();
                } else if (gm.stckx.size() == 1) {  //悔第一颗黑子
                    gm.message = " ";
                    gm.turn = 1;
                    setMap(temp_x,temp_y,0);
                    repaint();
                }
            }else
                System.out.println("无法悔棋");
        }

4.简单人机对战功能

使用权值法决定落子位置:在落子前对所有空位遍历,由周围棋子情况计算该处权值,之后在权值最大处落子。

首先利用哈希表保存棋子情况和设置的权重:此处只考虑人机下白棋情况

public class ChessAI implements ChessConfig{
    public GamePanel gp;
    public GameMouse gm;
    public static HashMap<String,Integer> Weight = new HashMap<>();
    int[][] value = new int[11][11];

    public ChessAI() {
        Weight.put("1", 20);
        Weight.put("11",200);
        Weight.put("111",800);
        Weight.put("1111",2000);
        Weight.put("2",20);
        Weight.put("22",300);
        Weight.put("222",900);
        Weight.put("2222",4000); 
    }

遍历棋盘,对空位置分析其所有方向的权重,之后相加。 

 public int[] ai(int [][]map){

        for (int i =0;i<=SQUARE_NUM;i++){
            for (int j = 0;j<=SQUARE_NUM;j++){
                if (map[i][j]==0){
                    //向右
                    String zeroPoint ="";
                    for(int temp=1;i+temp<=SQUARE_NUM;temp++){
                        int first = map[i+1][j];
                        if (map[i+temp][j]!=0&&map[i+temp][j]==first){
                            zeroPoint+=map[i+temp][j]+"";
                        }else
                            break; //碰到空位置就结束
                    }if (Weight.get(zeroPoint)!=null){
                        value[i][j]+=Weight.get(zeroPoint);
                    }
                    //向左
                    zeroPoint ="";
                    for(int temp=1;i-temp>=0;temp++){
                        int first = map[i-1][j];
                        if (map[i-temp][j]!=0&&map[i-temp][j]==first){
                            zeroPoint+=map[i-temp][j]+"";
                        }else
                            break; //碰到空位置就结束
                    }if (Weight.get(zeroPoint)!=null){
                        value[i][j]+=Weight.get(zeroPoint);
                    }
                    //向上
                    zeroPoint ="";
                    for(int temp=1;j-temp>=0;temp++){
                        int first = map[i][j-1];
                        if (map[i][j-temp]!=0&&map[i][j-temp]==first){
                            zeroPoint+=map[i][j-temp]+"";
                        }else
                            break; //碰到空位置就结束
                    }if (Weight.get(zeroPoint)!=null){
                        value[i][j]+=Weight.get(zeroPoint);
                    }
                    //向下
                    zeroPoint ="";
                    for(int temp=1;j+temp<=SQUARE_NUM;temp++){
                        int first = map[i][j+1];
                        if (map[i][j+temp]!=0&&map[i][j+temp]==first){
                            zeroPoint+=map[i][j+temp]+"";
                        }else
                            break; //碰到空位置就结束
                    }if (Weight.get(zeroPoint)!=null){
                        value[i][j]+=Weight.get(zeroPoint);
                    }
                    //向左上
                    zeroPoint ="";
                    for(int temp=1;i-temp>=0&&j-temp>=0;temp++){
                        int first = map[i-1][j-1];
                        if (map[i-temp][j-temp]!=0&&map[i-temp][j-temp]==first){
                            zeroPoint+=map[i-temp][j-temp]+"";
                        }else
                            break; //碰到空位置就结束
                    }if (Weight.get(zeroPoint)!=null){
                        value[i][j]+=Weight.get(zeroPoint);
                    }
                    //右下
                    zeroPoint ="";
                    for(int temp=1;i+temp<=SQUARE_NUM&&j+temp<=SQUARE_NUM;temp++){
                        int first = map[i+1][j+1];
                        if (map[i+temp][j+temp]!=0&&map[i+temp][j+temp]==first){
                            zeroPoint+=map[i+temp][j+temp]+"";
                        }else
                            break; //碰到空位置就结束
                    }if (Weight.get(zeroPoint)!=null){
                        value[i][j]+=Weight.get(zeroPoint);
                    }
                    //右上
                    zeroPoint ="";
                    for(int temp=1;i+temp<=SQUARE_NUM&&j-temp>=0;temp++){
                        int first = map[i+1][j-1];
                        if (map[i+temp][j-temp]!=0&&map[i+temp][j-temp]==first){
                            zeroPoint+=map[i+temp][j-temp]+"";
                        }else
                            break; //碰到空位置就结束
                    }if (Weight.get(zeroPoint)!=null){
                        value[i][j]+=Weight.get(zeroPoint);
                    }

                    //左下
                    zeroPoint ="";
                    for(int temp=1;i-temp>=0&&j+temp<=SQUARE_NUM;temp++){
                        int first = map[i-1][j+1];
                        if (map[i-temp][j+temp]!=0&&map[i-temp][j+temp]==first){
                            zeroPoint+=map[i-temp][j+temp]+"";
                        }else
                            break; //碰到空位置就结束
                    }if (Weight.get(zeroPoint)!=null){
                        value[i][j]+=Weight.get(zeroPoint);
                    }
                }
                else
                    value[i][j]=0;
            }
        }
        int max = 0;
        int xx=0,yy=0;
        for (int i=0;i<=SQUARE_NUM;i++){
            for (int j=0;j<=SQUARE_NUM;j++){
                if (max<value[i][j]){
                    max = value[i][j];
                    xx=i;
                    yy=j;
                }
            }
        }
        clearValue(value);
    return new int[]{xx, yy};
    }

对每个位置考虑周围相/不同子相连情况,这种算法较为简单,没有将棋子两个对应方向(如左方和右方)的情况共同考虑。容易出现下图情况:

 4.1 对落子功能的修改

 加入人机对战功能后,将人机落子放在玩家落子后,只考虑人机执白子情况:

if (isFree(xc,yc)&&start){
            if (turn==1){
                drawBlack(g,xc,yc);
                gp.setMap(xc,yc,1);
                message="白棋";
                turn=2;
                sumAll(gp.getMap(),xc,yc);
                if (AI&&flag!=1){
                    xc=ai.ai(gp.getMap())[0];
                    yc=ai.ai(gp.getMap())[1];
                    drawWhite(g,xc,yc);
                    gp.setMap(xc,yc,2);
                    message="黑棋";
                    turn=1;
                }

            }else if(turn==2&&!AI){
                drawWhite(g,xc,yc);
                gp.setMap(xc,yc,2);
                message="黑棋";
                turn=1;
            }
        }

 此时下棋黑棋后需要单独使用进行判断输赢的sumAll()方法,否则会出现黑棋五子却没有提示胜利,没有结束游戏的情况。

5.小结

作为第一个java项目,本项目改进之处还有很多,例如算法和人机执黑子功能。

完整项目代码:https://github.com/smdnzhan/FiveChess

标签:map,JAVA,temp,gp,int,五子棋,gm,public
来源: https://blog.csdn.net/smdnzhan/article/details/120460322

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有