Java教程

JAVA五子棋

本文主要是介绍JAVA五子棋,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一个简单的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

这篇关于JAVA五子棋的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!