Java实现简单的AI五子棋小游戏(一)

本文详细介绍了如何使用Java实现一个五子棋小游戏,包括创建棋盘、监听鼠标点击落子、判断胜负、悔棋功能,以及初步探讨了AI权值算法的实现思路。通过不断优化,游戏不仅能够记录和重绘棋局,还能判断输赢并支持悔棋操作。后续文章将深入讲解如何实现简单的AI对战功能。
摘要由CSDN通过智能技术生成

一、简介:
本程序功能是实现一个简单的AI五子棋小游戏,大致程序模块如下:

  1. 棋盘界面
  2. 鼠标监听器
  3. 按钮监听器
  4. 悔棋功能
  5. AI权值算法

在开始之前,我们先利用接口定义后面经常用上的几个常量:

/*Config.java*/
public interface Config {
	public static final int X1 = 50;    //棋盘起始点的横坐标
    public static final int Y1 = 50;    //棋盘起始点的纵坐标  
    public static final int LINE = 15;  //行数/列数
    public static final int SIZE = 50;  //线间距
    public static final int CHESS = 30; //棋子直径
}                                       //以上单位均为像素

二、画棋盘
画棋盘就要用到JFrame类了,这个类是窗体控件,可以做出GUI程序。编写ChessUI类继承JFrame类即可。

import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;

public class ChessUI extends JFrame implements Config{
	private static final long serialVersionUID = 1L;
	public ChessUI() {//UI的构造方法
		//窗体的基本组成
		setTitle("五子棋V1.0");
        setSize(800, 800);                       //窗体大小800px*800px
        setDefaultCloseOperation(EXIT_ON_CLOSE); //关闭按钮
        setVisible(true);                        //窗体可视化,将窗体在电脑屏幕上显示
        
	}
	@override
	public void paint(Graphics g) {
		super.paint(g);                 //调用父类方法体内容
		for (int i = 0; i < LINE; i++) {//利用循环逻辑画棋盘线
            g.setColor(Color.black);
            g.drawLine(X1, X1 + SIZE * i, X1 + SIZE * (LINE - 1), Y1 + SIZE * i); 
            g.drawLine(X1 + SIZE * i, X1, Y1 + SIZE * i, Y1 + SIZE * (LINE - 1));
        }
	}
	
	public static void main(String[] args) {
		new ChessUI();//在本类中直接调用构造方法
	}
}
  • 注意:
    这里,棋盘用drawLine方法绘制,并且要重写在paint方法体内,否则,如果在构造方法中画棋盘,窗体界面一旦刷新重绘,棋盘会消失。
  • 运行的结果应该是如下界面:
    初步画好的棋盘

三、实现鼠标点击界面落子(鼠标监听器)

  • 鼠标监听器是干什么的?
    鼠标监听器用来接收鼠标的动作,然后相应的动作会对应实现怎样的功能。
    代码如下,我们以鼠标在窗体中按下时落子:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class ChessListener implements MouseListener,Config{

	public Graphics g = null;//定义一个画笔,用于后面画棋子
	int x , y;               //鼠标点击的点坐标
	
	@Override//鼠标点击时执行的方法
	public void mouseClicked(MouseEvent arg0) {} 

	@Override//鼠标进入组件(窗体)时执行的方法
	public void mouseEntered(MouseEvent arg0) {} 

	@Override//鼠标离开组件时执行的方法
	public void mouseExited(MouseEvent arg0) {} 

	@Override//鼠标按压下去时执行的方法
	public void mousePressed(MouseEvent e) {
		x = e.getX();
		y = e.getY();
		g.setColor(Color.black);                        //设置图形颜色为黑色
		g.fillOval(x-CHESS/2, y-CHESS/2, CHESS, CHESS); //以(x,y)为圆心,CHESS(30px)为直径画圆
	} 
	@Override//鼠标松开时执行的方法
	public void mouseReleased(MouseEvent arg0) {} 
}

效果图如下:
鼠标点击落子
但是,我们的目的是要将棋子下在交叉点处,不能下到交叉点以外对方任何地方,并且要做到黑白交替落子。添加修改后的方法如下:

public void mousePressed(MouseEvent e) {
		x = e.getX();
		y = e.getY();
		//接下来进入坐标校正
		if (x % SIZE < SIZE / 2 && y % SIZE < SIZE / 2) {
            x -= x % SIZE;
            y -= y % SIZE;
        } else if (x % SIZE >= SIZE / 2 && y % SIZE >= SIZE / 2) {
            x = x - x % SIZE + SIZE;
            y = y - y % SIZE + SIZE;
        }
        else if (x % SIZE >= SIZE / 2 && y % SIZE < SIZE / 2) {
            x = x - x % SIZE + SIZE;
            y -= y % SIZE;
        } else if (x % SIZE < SIZE / 2 && y % SIZE >= SIZE / 2) {
            x -= x % SIZE;
            y = y - y % SIZE + SIZE;
        }
        if ((x > X1+SIZE*(LINE-1) || y > Y1+SIZE*(LINE-1))) {     
            return;
        }
        //校正完毕
        if(flag == 1) {
		g.setColor(Color.black);                        //设置图形颜色为黑色
		g.fillOval(x-CHESS/2, y-CHESS/2, CHESS, CHESS); //以(x,y)为圆心,CHESS(30px)为直径画圆
		flag = 2;
        }else if(flag == 2) {
        	g.setColor(Color.white);                        //设置图形颜色为白色
    		g.fillOval(x-CHESS/2, y-CHESS/2, CHESS, CHESS); //以(x,y)为圆心,CHESS(30px)为直径画圆
    		flag = 1;
        }
	} 

效果图如下:
在这里插入图片描述
但是这样,窗体界面一旦刷新,棋子就会消失,并且会出现同一点重复落子的情况。为了不让棋子在窗体刷新时候消失,并且要记住哪个点已经落子了以防下次不能再在同一点落子,我们需要用一种存储结构来存储当前棋盘的落子情况。

  • 利用二维数组来储存落子情况
    定义一个chessArray[][]数组,对应棋盘。落黑子的点在数组相应位置标1,白子标2,未落子为0.

继续修改鼠标监听器的方法和paint方法:

	int[][] chessArray = new int[LINE][LINE]; 
	int xx,yy;               //棋盘坐标(xx,yy)的点所下的棋的情况对应元素chessArray[xx][yy]
	                         //这里xx,yy的取值范围是[0,14],代表棋子所在棋盘的列数和行数。
	@Override//鼠标按压下去时执行的方法
	public void mousePressed(MouseEvent e) {
		x = e.getX();
		y = e.getY();
		//接下来进入坐标校正
		if (x % SIZE < SIZE / 2 && y % SIZE < SIZE / 2) {
            x -= x % SIZE;
            y -= y % SIZE;
        } else if (x % SIZE >= SIZE / 2 && y % SIZE >= SIZE / 2) {
            x = x - x % SIZE + SIZE;
            y = y - y % SIZE + SIZE;
        }
        else if (x % SIZE >= SIZE / 2 && y % SIZE < SIZE / 2) {
            x = x - x % SIZE + SIZE;
            y -= y % SIZE;
        } else if (x % SIZE < SIZE / 2 && y % SIZE >= SIZE / 2) {
            x -= x % SIZE;
            y = y - y % SIZE + SIZE;
        }
        if ((x > X1+SIZE*(LINE-1) || y > Y1+SIZE*(LINE-1))) {     
            return;
        }
        //校正完毕
        xx = x / SIZE - 1;
        yy = y / SIZE - 1;
        if(chessArray[xx][yy] != 0) return;             //防止在同一点重复落子
        if(flag == 1) {
		g.setColor(Color.black);                        //设置图形颜色为黑色
		g.fillOval(x-CHESS/2, y-CHESS/2, CHESS, CHESS); //以(x,y)为圆心,CHESS(30px)为直径画圆
		chessArray[xx][yy] = 1;
		flag = 2;
        }else if(flag == 2) {
        	g.setColor(Color.white);                        //设置图形颜色为白色
    		g.fillOval(x-CHESS/2, y-CHESS/2, CHESS, CHESS); //以(x,y)为圆心,CHESS(30px)为直径画圆
    		chessArray[xx][yy] = 2;
    		flag = 1;
        }
	} 

再修改paint方法:

public void paint(Graphics g) {
		super.paint(g);
		for (int i = 0; i < LINE; i++) {         //画棋盘线
            g.setColor(Color.black);
            g.drawLine(X1, X1 + SIZE * i, X1 + SIZE * (LINE - 1), Y1 + SIZE * i); 
            g.drawLine(X1 + SIZE * i, X1, Y1 + SIZE * i, Y1 + SIZE * (LINE - 1));
        }
		for(int i = 0;i<LINE;i++) {              //重绘棋子
			for(int j = 0;j<LINE;j++) {
				if(cl.chessArray[i][j] == 1) {
					g.setColor(Color.black);
					g.fillOval((i+1)*SIZE-CHESS/2,(j+1)*SIZE-CHESS/2, CHESS, CHESS);
				} else if(cl.chessArray[i][j] == 2) {
					g.setColor(Color.white);
					g.fillOval((i+1)*SIZE-CHESS/2,(j+1)*SIZE-CHESS/2, CHESS, CHESS);
				}
			}
		}
	}

这样,就实现了棋子的重绘和防止重复落子,后面还可以以此实现判断输赢和AI功能。

四、判断胜负
如何判断胜负?思路是,当前所落子的四个方向直线上至少有一组五子同色连线。
在这里插入图片描述
在最新落下一个白子时,其所在水平方向形成同色五子连线。在一方赢了之后,清空棋盘。代码如下:

@Override//鼠标松开时执行的方法
	public void mouseReleased(MouseEvent e) {
		if(ifWin()) {
			if (chessArray[xx][yy] == 1) {
                JOptionPane.showConfirmDialog(null, "黑方胜利", "游戏结束", JOptionPane.WARNING_MESSAGE);
            }
            else {
                JOptionPane.showConfirmDialog(null, "白方胜利", "游戏结束", JOptionPane.WARNING_MESSAGE);
            }
			clear();
			jf.repaint();
		}
	} 
    private boolean ifWin() {
        if (win1(xx, yy) >= 5 || win2(xx, yy) >= 5 || win3(xx, yy) >= 5 || win4(xx, yy) >= 5) {
            return true;
        }
        else
            return false;
    }

    private int win1(int x, int y) {//横向判断输赢
        int count = 0;
        //向右  
        for (int i = x + 1; i < LINE; i++) {
            if (chessArray[x][y] == chessArray[i][y]&&chessArray[x][y]!=0) {
                count++;
            } else
                break;
        }
        //向左
        for (int i = x; i >= 0; i--) {
            if (chessArray[x][y] == chessArray[i][y]&&chessArray[x][y]!=0) {
                count++;
            } else
                break;
        }
        return count;
    }

    private int win2(int x, int y) {//纵向判断输赢
        int count = 0;
        int i = 0;
        for (i = y + 1; i < LINE; i++) {//向下
            if (chessArray[x][y] == chessArray[x][i] && chessArray[x][y] != 0) {
                count++;
            } else
                break;
        }
            for (i = y; i >= 0; i--) {
                if (chessArray[x][y] == chessArray[x][i] && chessArray[x][y] != 0) {//向上
                    count++;
                } else
                    break;
            }
        return count;
    }

    private int win3(int x, int y) {//主对角线判断
        int count = 0;
        int i = 0, j = 0;
        for (i = x + 1, j = y + 1; i < LINE && j < LINE; i++, j++) {
            if (chessArray[x][y] == chessArray[i][j]&&chessArray[x][y]!=0) {
                count++;
            } else
                break;
        }
        for (i = x, j = y; i >= 0 && j >= 0; i--, j--) {
            if (chessArray[x][y] == chessArray[i][j]&&chessArray[x][y]!=0) {
                count++;
            } else
                break;
        }
        return count;
    }

    private int win4(int x, int y) {//副对角线判别
        int count = 0;
        int i = 0, j = 0;
        for (i = x + 1, j = y - 1; i < LINE && j >= 0; i++, j--) {
            if (chessArray[x][y] == chessArray[i][j]&&chessArray[x][y]!=0) {
                count++;
            } else
                break;
        }
        for (i = x, j = y; i >= 0 && j < LINE; i--, j++) {
            if (chessArray[x][y] == chessArray[i][j]&&chessArray[x][y]!=0) {
                count++;
            } else
                break;
        }
        return count;
    }
    void clear() { //矩阵元素清零

        for (int i = 0; i < LINE; i++) {
            for (int j = 0; j < LINE; j++) {
                chessArray[i][j] = 0;
            }
        }
    }

五、实现悔棋
我们利用栈来储存每一步下棋的横坐标xx和纵坐标yy,如要悔棋,对栈进行pop操作,对pop出的点在二维数组上置零然后重绘一次窗体即可。

class UndoData {  //自定义一个二元组
    int x, y;
}

public class ChessStack implements Config{ //自定义一个栈
    UndoData[] Undo = new UndoData[LINE * LINE];
    int Top = -1;

    public UndoData pop() {
        return Undo[Top--];
    }

    public void push(int x, int y) {
        UndoData ud = new UndoData();
        ud.x = x;
        ud.y = y;
        Undo[++Top] = ud;
    }
}

六、添加按钮功能
我们给窗体添加按钮来实现开始游戏和悔棋的功能 。

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JOptionPane;

//按钮事件监听器,先实现开始游戏和悔棋功能!
public class ButtonListener implements ActionListener{
    public ChessListener cl;
    public ChessUI jf;
    UndoData uData = new UndoData();
    
    Object selectedValue1 = "";
    Object selectedValue2 = "";

	@Override
	public void actionPerformed(ActionEvent e) {
        String btnstr = e.getActionCommand();                  //获取按钮的字符串
        Object[] possibleValues1 = { "PVP" };
        Object[] possibleValues2 = { "黑方先手", "白方先手" };    //设定下拉菜单选项
        switch(btnstr) {
        case "开始/重开" :
        	System.out.println("开始按钮被点击了");
        	if(cl.removeFlag == 1) {
        		jf.removeMouseListener(cl);
        	}
        	cl.clear();
        	jf.repaint();                                      //清空,重绘
        	//设置下拉菜单,获取用户选项
        	selectedValue1 = JOptionPane.showInputDialog(null, "选择对战模式", "提示",
                    JOptionPane.INFORMATION_MESSAGE, null, possibleValues1, possibleValues1[0]);
             
            selectedValue2 = JOptionPane.showInputDialog(null, "选择先后手", "提示",
                    JOptionPane.INFORMATION_MESSAGE, null, possibleValues2, possibleValues2[0]);
            switch((String)selectedValue1) {
            case "PVP":
            	System.out.println("人人对战");
            	jf.addMouseListener(cl);
            	cl.removeFlag = 1;
            default:
            	break;
            }
            switch((String)selectedValue2) {
            case "黑方先手":
            	cl.flag = 1;
                break;
            case "白方先手":
            	cl.flag = 2;
            	break;
            default:
            	break;
            }
            break;
        case "悔棋" :
        	if (cl.cStack.Top == -1) {
                JOptionPane.showMessageDialog(null, "不能再悔棋了!", "提示", JOptionPane.ERROR_MESSAGE);
                break;
            }
            uData = cl.cStack.pop();
            if (cl.chessArray[uData.x][uData.y] == 1) {
                cl.flag = 1;                        //这一步悔了黑子,要重下黑子
            } else if (cl.chessArray[uData.x][uData.y] == 2) {
                cl.flag = 2;                        //这一步悔了白子,要重下白子
            }
            cl.chessArray[uData.x][uData.y] = 0;
            jf.repaint();
            break;
        default:
        	break;
        }
	}
}

相应的,也要在其他类修改方法内容:
在ChessListener类中:

	ChessStack cStack = new ChessStack();    //建立悔棋所用的栈
	int removeFlag = 0;  //拿来判断是否要移除鼠标监听器(防止第一次开始游戏时未add监听器就执行remove)
	
	@Override//鼠标按压下去时执行的方法
	public void mousePressed(MouseEvent e) {
		此处略去获取坐标并校正的方法......
        //校正完毕
        xx = x / SIZE - 1;
        yy = y / SIZE - 1;
        if(chessArray[xx][yy] != 0) return;             //防止在同一点重复落子
        cStack.push(xx, yy);                            //每落一个子,把坐标入栈
        //接下来画棋子
        此处略去画棋子的方法......
        }
	} 

在ChessUI类中:

ButtonListener bl = new ButtonListener();
	public ChessUI() {//UI的构造方法
		//窗体的基本组成
		setTitle("五子棋V1.0");
        setSize(1000, 800);
        setDefaultCloseOperation(EXIT_ON_CLOSE); //关闭按钮
        //addMouseListener(cl);                    //给窗体加上鼠标监听器
        setLayout(null);
        JButton btn1 = new JButton("开始/重开");
        JButton btn2 = new JButton("悔棋");
        add(btn1);
        add(btn2);
        btn1.setBounds(800, 100, 150, 40);
        btn2.setBounds(850, 170, 100, 40);       //自定义按钮位置
        setVisible(true);                        //窗体可视化,将窗体在电脑屏幕上显示
        btn1.addActionListener(bl);
        btn2.addActionListener(bl);
        cl.g = getGraphics();                    //把窗体的画笔传给鼠标监听器
        cl.jf = this;
        bl.cl = this.cl;
        bl.jf = this;
	}

这样,我们的五子棋基本功能已经完成!
选择模式,在选择前是无法下棋的
选择先后手
游戏内容
将在(二)中讲述实现简单的AI五子棋功能:
Java实现简单的AI五子棋小游戏(二)
如对此拙作有任何意见或者疑问,欢迎评论或私信提出,批评指正!

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值