Java五子棋(人机版),昨天买的棋子今天就用不上了

Java五子棋,老程序员也花了3天

作者简介

作者名:编程界明世隐
简介:CSDN博客专家,从事软件开发多年,精通Java、JavaScript,博主也是从零开始一步步把学习成长、深知学习和积累的重要性,喜欢跟广大ADC一起打野升级,欢迎您关注,期待与您一起学习、成长、起飞!

热门专栏推荐

【1】Java小游戏(俄罗斯方块、飞机大战、植物大战僵尸等)
【2】JavaWeb项目实战(图书管理、在线考试、宿舍管理等)
【3】JavaScript精彩实例(飞机大战、贪吃蛇、验证码等)
【4】Java小白入门200例
【5】从零学Java、趣学Java
【6】Idea从零到精通

系列目录

1. Java俄罗斯方块
2. 老Java程序员花2天写了个连连看
3. 老Java程序员花一天时间写了个飞机大战
4. Java植物大战僵尸
5. Java消消乐(天天爱消除)
6. Java贪吃蛇小游戏
7. Java扫雷小游戏
8. Java坦克大战

效果图

Java五子棋(人机版),昨天买的棋子今天就用不上了_第1张图片

实现思路

1.创建运行窗口并添加背景色。
2.绘制棋盘。
3.用二维数组来控制起码落子位置、绘制指示器。
4.鼠标在落子位置处点击可落子。
5.落子后检查是否获得胜利。
6.机器判断下一步,并落子。
7.机器判断是否获得胜利。

代码实现

创建窗口

首先创建一个游戏窗体类GameFrame,继承至JFrame,用来显示在屏幕上(window的对象),每个游戏都有一个窗口,设置好窗口标题、尺寸、布局等就可以。

/*
 * 游戏窗体类
 */
public class GameFrame extends JFrame {
     
	
	public GameFrame() {
     
		setTitle("五子棋");//设置标题
		setSize(620, 670);//设定尺寸
		getContentPane().setBackground(new Color(209,146,17));//添加背景色
		setLayout(new BorderLayout());
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点击关闭按钮是关闭程序
        setLocationRelativeTo(null);   //设置居中
    	setResizable(false); //不允许修改界面大小
	}
}

创建面板容器GamePanel继承至JPanel


import javax.swing.JFrame;
import javax.swing.JPanel;

/*
 * 画布类
 */
public class GamePanel extends JPanel {
     
	private static final long serialVersionUID = 1L;
	GamePanel gamePanel=this;
	private JFrame mainFrame=null;
	
	//构造里面初始化相关参数
	public GamePanel(JFrame frame){
     
		this.setLayout(null);
		this.setOpaque(false);
		mainFrame = frame;
		
		mainFrame.requestFocus();
		mainFrame.setVisible(true);
	}
	
}

再创建一个Main类,来启动这个窗口。

public class Main {
     
	//主类
	public static void main(String[] args) {
     
		GameFrame frame = new GameFrame();
		GamePanel gamePanel = new GamePanel(frame);
		frame.add(gamePanel);
		frame.setVisible(true);//设定显示
	}
}

右键执行这个Main类,窗口建出来了
Java五子棋(人机版),昨天买的棋子今天就用不上了_第2张图片

创建菜单及菜单选项

创建菜单

private void  initMenu(){
     
	// 创建菜单及菜单选项
	jmb = new JMenuBar();
	JMenu jm1 = new JMenu("游戏");
	jm1.setFont(new Font("思源宋体", Font.BOLD, 18));// 设置菜单显示的字体
	JMenu jm2 = new JMenu("帮助");
	jm2.setFont(new Font("思源宋体", Font.BOLD, 18));// 设置菜单显示的字体
	
	JMenuItem jmi1 = new JMenuItem("开始新游戏");
	JMenuItem jmi2 = new JMenuItem("退出");
	jmi1.setFont(new Font("思源宋体", Font.BOLD, 18));
	jmi2.setFont(new Font("思源宋体", Font.BOLD, 18));
	
	JMenuItem jmi3 = new JMenuItem("操作说明");
	jmi3.setFont(new Font("思源宋体", Font.BOLD, 18));
	JMenuItem jmi4 = new JMenuItem("成功/失败判定");
	jmi4.setFont(new Font("思源宋体", Font.BOLD, 18));
	
	jm1.add(jmi1);
	jm1.add(jmi2);
	
	jm2.add(jmi3);
	jm2.add(jmi4);
	
	jmb.add(jm1);
	jmb.add(jm2);
	mainFrame.setJMenuBar(jmb);// 菜单Bar放到JFrame上
	jmi1.addActionListener(this);
	jmi1.setActionCommand("Restart");
	jmi2.addActionListener(this);
	jmi2.setActionCommand("Exit");
	
	jmi3.addActionListener(this);
	jmi3.setActionCommand("help");
	jmi4.addActionListener(this);
	jmi4.setActionCommand("lost");
}

实现ActionListener并重写方法actionPerformed
Java五子棋(人机版),昨天买的棋子今天就用不上了_第3张图片
此时GamePanel是报错的,重写actionPerformed方法。

actionPerformed方法的实现

@Override
public void actionPerformed(ActionEvent e) {
     
	String command = e.getActionCommand();
	System.out.println(command);
	UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));
	UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));
	if ("Exit".equals(command)) {
     
		Object[] options = {
      "确定", "取消" };
		int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",
				JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
				options, options[0]);
		if (response == 0) {
     
			System.exit(0);
		} 
	}else if("Restart".equals(command)){
     
		if(!"end".equals(gamePanel.gameFlag)){
     
			JOptionPane.showMessageDialog(null, "正在游戏中无法重新开始!",
					"提示!", JOptionPane.INFORMATION_MESSAGE); 
		}else{
     
			if(gamePanel!=null) {
     
				gamePanel.restart();
			}
		}
	}else if("help".equals(command)){
     
		JOptionPane.showMessageDialog(null, "鼠标在指示器位置点下,则落子!",
				"提示!", JOptionPane.INFORMATION_MESSAGE);
	}else if("lost".equals(command)){
     
		JOptionPane.showMessageDialog(null, "五子连珠方获得胜利!",
				"提示!", JOptionPane.INFORMATION_MESSAGE);
	}
}

此时的GamePanel代码如下:

package main;

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.FontUIResource;

/*
 * 画布类
 */
public class GamePanel extends JPanel implements ActionListener{
     
	private static final long serialVersionUID = 1L;
	GamePanel gamePanel=this;
	private JFrame mainFrame=null;
	JMenuBar jmb=null;
	public String gameFlag="";
	
	//构造里面初始化相关参数
	public GamePanel(JFrame frame){
     
		this.setLayout(null);
		this.setOpaque(false);
		mainFrame = frame;
		
		//创建按钮
		initMenu();
		
		mainFrame.requestFocus();
		mainFrame.setVisible(true);
	}
	
	private void  initMenu(){
     
		// 创建菜单及菜单选项
		jmb = new JMenuBar();
		JMenu jm1 = new JMenu("游戏");
		jm1.setFont(new Font("思源宋体", Font.BOLD, 18));// 设置菜单显示的字体
		JMenu jm2 = new JMenu("帮助");
		jm2.setFont(new Font("思源宋体", Font.BOLD, 18));// 设置菜单显示的字体
		
		JMenuItem jmi1 = new JMenuItem("开始新游戏");
		JMenuItem jmi2 = new JMenuItem("退出");
		jmi1.setFont(new Font("思源宋体", Font.BOLD, 18));
		jmi2.setFont(new Font("思源宋体", Font.BOLD, 18));
		
		JMenuItem jmi3 = new JMenuItem("操作说明");
		jmi3.setFont(new Font("思源宋体", Font.BOLD, 18));
		JMenuItem jmi4 = new JMenuItem("成功/失败判定");
		jmi4.setFont(new Font("思源宋体", Font.BOLD, 18));
		
		jm1.add(jmi1);
		jm1.add(jmi2);
		
		jm2.add(jmi3);
		jm2.add(jmi4);
		
		jmb.add(jm1);
		jmb.add(jm2);
		mainFrame.setJMenuBar(jmb);// 菜单Bar放到JFrame上
		jmi1.addActionListener(this);
		jmi1.setActionCommand("Restart");
		jmi2.addActionListener(this);
		jmi2.setActionCommand("Exit");
		
		jmi3.addActionListener(this);
		jmi3.setActionCommand("help");
		jmi4.addActionListener(this);
		jmi4.setActionCommand("lost");
	}
	
	//重新开始
	public void restart() {
     
		//游戏开始标记
		gameFlag="start";
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
     
		String command = e.getActionCommand();
		System.out.println(command);
		UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));
		UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));
		if ("Exit".equals(command)) {
     
			Object[] options = {
      "确定", "取消" };
			int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",
					JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
					options, options[0]);
			if (response == 0) {
     
				System.exit(0);
			} 
		}else if("Restart".equals(command)){
     
			if(!"end".equals(gamePanel.gameFlag)){
     
				JOptionPane.showMessageDialog(null, "正在游戏中无法重新开始!",
						"提示!", JOptionPane.INFORMATION_MESSAGE); 
			}else{
     
				if(gamePanel!=null) {
     
					gamePanel.restart();
				}
			}
		}else if("help".equals(command)){
     
			JOptionPane.showMessageDialog(null, "鼠标在指示器位置点下,则落子!",
					"提示!", JOptionPane.INFORMATION_MESSAGE);
		}else if("lost".equals(command)){
     
			JOptionPane.showMessageDialog(null, "五子连珠方获得胜利!",
					"提示!", JOptionPane.INFORMATION_MESSAGE);
		}
	}
}

运行一下
Java五子棋(人机版),昨天买的棋子今天就用不上了_第4张图片

绘制棋盘

重写paint方法

@Override
public void paint(java.awt.Graphics g) {
     
	super.paint(g);
}

绘制横竖相交接的线
定义15行、15列

public static final int ROWS=15;
public static final int COLS=15;

绘制网格

//绘制网格
private void drawGrid(Graphics g) {
     
	Graphics2D g_2d=(Graphics2D)g;
	int start=26;
	int x1=start;
	int y1=20;
	int x2=586;
	int y2=20;
	for (int i = 0; i < ROWS; i++) {
     
		y1 = start + 40*i;
		y2 = y1;
		g_2d.drawLine(x1, y1, x2, y2);		
	}
	
	y1=start;
	y2=586;
	for (int i = 0; i < COLS; i++) {
     
		x1 = start + 40*i;
		x2 = x1;
		g_2d.drawLine(x1, y1, x2, y2);		
	}
}

绘制5个圆点

//绘制5个黑点
private void draw5Point(Graphics g) {
     
	//第1个点
	g.fillArc(142, 142, 8, 8, 0, 360);
	//第2个点
	g.fillArc(462, 142, 8, 8, 0, 360);
	
	//第3个点
	g.fillArc(142, 462, 8, 8, 0, 360);
	//第4个点
	g.fillArc(462, 462, 8, 8, 0, 360);
	
	//中心点
	g.fillArc(302, 302, 8, 8, 0, 360);
}

在paint方法里面调用以上2个方法

@Override
public void paint(java.awt.Graphics g) {
     
	super.paint(g);
	//绘制网格
	drawGrid(g);
	//绘制5个黑点
	draw5Point(g);
}

棋盘已经绘制完成
Java五子棋(人机版),昨天买的棋子今天就用不上了_第5张图片

实现落子指示器

  1. 创建指示器类
package main;

import java.awt.Color;
import java.awt.Graphics;

//指示器类
public class Pointer {
     
	private int i=0;//二维下标i
	private int j=0;//二维下标j
	private int x=0;//坐标X
	private int y=0;//坐标Y
	private GamePanel panel=null;
	private Color color=null;
	private int h=40;//指示的大小
	private boolean isShow=false;//是否展示
	private int qizi = 0 ;//棋子类型 0:无  1:白棋  2:黑棋
	
	public Pointer(int x,int y,int i,int j,Color color,GamePanel panel){
     
		this.x=x;
		this.y=y;
		this.i=i;
		this.j=j;
		this.panel=panel;
		this.color=color;
	}
	//绘制
	void draw(Graphics g){
     
		Color oColor = g.getColor();
		if(color!=null){
     
			g.setColor(color);
		}
		if(isShow){
     
			//绘制指示器
			g.drawRect(x-h/2, y-h/2, h, h);
		}
		if(color!=null){
     //用完设置回去颜色
			g.setColor(oColor);
		}
	}
	//判断鼠标是否在指针范围内
	boolean isPoint(int x,int y){
     
		//大于左上角,小于右下角的坐标则肯定在范围内
		if(x>this.x-h/2 && y >this.y-h/2
			&& x<this.x+h/2 && y <this.y+h/2){
     
			return  true;
		}
		return false;
	}
	public boolean isShow() {
     
		return isShow;
	}
	public void setShow(boolean isShow) {
     
		this.isShow = isShow;
	}
	public int getQizi() {
     
		return qizi;
	}
	public void setQizi(int qizi) {
     
		this.qizi = qizi;
	}
	public int getX() {
     
		return x;
	}
	public void setX(int x) {
     
		this.x = x;
	}
	public int getY() {
     
		return y;
	}
	public void setY(int y) {
     
		this.y = y;
	}
	public int getI() {
     
		return i;
	}
	public void setI(int i) {
     
		this.i = i;
	}
	public int getJ() {
     
		return j;
	}
	public void setJ(int j) {
     
		this.j = j;
	}
}
  1. 初始化二维数组
public Pointer points[][] =  new Pointer[ROWS][COLS];
  1. 创建指示器实例对象
//创建二维数组
private void createArr() {
     
	int x=0,y=0;
	for (int i = 0; i < ROWS; i++) {
     
		for (int j = 0; j < COLS; j++) {
     
			y = 26 + 40*i;
			x = 26 + 40*j;
			Pointer pointer = new Pointer(x, y, i,j,new Color(255,0,0), this);
			points[i][j] = pointer;
		}
	}		
}
  1. 初始化调用
//初始化相关对象
private void init() {
     
	createArr();
	//游戏开始标记
	gameFlag="start";
}

同时 init方法 在GamePanel 的构造方法调用。

  1. paint方法中遍历二维数组并且绘制。
@Override
public void paint(java.awt.Graphics g) {
     
	super.paint(g);
	//绘制网格
	drawGrid(g);
	//绘制5个黑点
	draw5Point(g);
	//绘制指示器
	Pointer pointer = null;
	for (int i = 0; i < ROWS; i++) {
     
		for (int j = 0; j < COLS; j++) {
     
			pointer = points[i][j] ;
			if(pointer!=null){
     
				pointer.draw(g);
			}
		}
	}
}

运行如下
Java五子棋(人机版),昨天买的棋子今天就用不上了_第6张图片

但是这个指示器方块不是我们想要的,需要改一下

  1. 修改指示器类的绘图代码

在Pointer方法中新增方法,并在指示器绘制的时候调用此方法,而不是之前的drawRect。

private void drawPointer(Graphics g) {
     
	
	Graphics2D g2 = (Graphics2D)g;  //g是Graphics对象
	g2.setStroke(new BasicStroke(2.0f));

	/*
	 * 1.先计算4个顶点
	 * 2.依次从每个顶点绘制横竖两条线
	 */
	
	//左上角
	int x1 = x-h/2;
	int y1 = y-h/2;
	//向右画线
	int x2 = x1+1*h/4;
	int y2 = y1;
	g2.drawLine(x1, y1, x2, y2);
	
	//向下画线
	x2 = x1;
	y2 = y1+1*h/4;
	g2.drawLine(x1, y1, x2, y2);
	
	//右上角
	x1 = x+h/2;
	y1 = y-h/2;
	//向左画线
	x2 = x1-1*h/4;
	y2 = y1;
	g2.drawLine(x1, y1, x2, y2);
	
	//向下画线
	x2 = x1;
	y2 = y1+1*h/4;
	g2.drawLine(x1, y1, x2, y2);
	
	//右下角
	x1 = x+h/2;
	y1 = y+h/2;
	//向左画线
	x2 = x1-1*h/4;
	y2 = y1;
	g2.drawLine(x1, y1, x2, y2);
	
	//向上画线
	x2 = x1;
	y2 = y1-1*h/4;
	g2.drawLine(x1, y1, x2, y2);
	
	//左下角
	x1 = x-h/2;
	y1 = y+h/2;
	//向右画线
	x2 = x1+1*h/4;
	y2 = y1;
	g2.drawLine(x1, y1, x2, y2);
	
	//向上画线
	x2 = x1;
	y2 = y1-1*h/4;
	g2.drawLine(x1, y1, x2, y2);
}

再运行

Java五子棋(人机版),昨天买的棋子今天就用不上了_第7张图片

落子

  1. 创建ImageValue加载类
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;

public class ImageValue {
     
    public static BufferedImage whiteQiziImage  ;
    
    public static BufferedImage blackQiziImage  ;
    //路径
    public static String ImagePath = "/images/";
    //将图片初始化
    public static void init(){
     
    	String whitePath = ImagePath +"white.png";
    	String blackPath = ImagePath +"black.png";
    	try {
     
			whiteQiziImage = ImageIO.read(ImageValue.class.getResource(whitePath));
			blackQiziImage = ImageIO.read(ImageValue.class.getResource(blackPath));
		} catch (IOException e) {
     
			e.printStackTrace();
		}
    }
}
  1. 创建落子类

import java.awt.Color;
import java.awt.Graphics;
import common.ImageValue;

public class Qizi {
     

	private int x = 0;
	private int y = 0;
	private int r = 36;
	private GamePanel panel = null;
	private Color color = null;
	private int type = 1;// 棋子类型 1:白棋 2:黑棋

	public Qizi(int x, int y, int type, GamePanel panel) {
     
		this.x = x;
		this.y = y;
		this.panel = panel;
		this.type=type;
	}

	// 绘制
	void draw(Graphics g) {
     
		Color oColor = g.getColor();
		if (type == 1) {
     // 白色
			g.drawImage(ImageValue.whiteQiziImage, x - r / 2, y - r / 2,r,r, null);
		} else {
     // 黑色
			g.drawImage(ImageValue.blackQiziImage, x - r / 2, y - r / 2,r,r, null);
		}

		if (color != null) {
     // 用完设置回去颜色
			g.setColor(oColor);
		}
	}
	
	public int getType() {
     
		return type;
	}

	public void setType(int type) {
     
		this.type = type;
	}
}
  1. 在createMouseListener方法中重写mouseClicked,创建棋子
@Override
public void mouseClicked(MouseEvent e) {
     
	//在合适的位置点击则进行落子操作
	if(!"start".equals(gameFlag)) return ;
	
	int x = e.getX();
	int y = e.getY();
	
	Pointer pointer;
	for (int i = 0; i <ROWS; i++) {
     
		for (int j = 0; j < COLS; j++) {
     
			pointer = points[i][j];
			if(pointer==null)continue;
			//被点击,且没有棋子,则可以落子
			if(pointer.isPoint(x, y) && pointer.getQizi()==0){
     
				Qizi qizi = new Qizi(pointer.getX(), pointer.getY(), 2, gamePanel);
				pointer.setQizi(2);
				qizis.add(qizi);
			    //重绘画布
				repaint();
				return ;
			}
		}
	}
}
  1. 在paint 方法中绘制棋子
@Override
public void paint(java.awt.Graphics g) {
     
	super.paint(g);
	//绘制网格
	drawGrid(g);
	//绘制5个黑点
	draw5Point(g);
	//绘制指示器
	Pointer pointer = null;
	for (int i = 0; i < ROWS; i++) {
     
		for (int j = 0; j < COLS; j++) {
     
			pointer = points[i][j] ;
			if(pointer!=null){
     
				pointer.draw(g);
			}
		}
	}
	//绘制棋子
	Qizi qizi=null;
	for (int i = 0; i < qizis.size(); i++) {
     
		qizi = (Qizi)qizis.get(i);
		qizi.draw(g);
	}
}

Java五子棋(人机版),昨天买的棋子今天就用不上了_第8张图片

加入电脑AI

  1. 创建AI类
  2. 创建静态方法next(下一步)
  3. 创建静态方法has5(连成5子)
public class AI {
     
	//AI进行下一步
	static void next(GamePanel gamePanel){
     
		
	}
	//判断五子连珠
	static boolean has5(Pointer pointer1,GamePanel gamePanel){
     
		
		return false;
	}
}
  1. 在你落子后,会先执行has5方法,根据返回来决定走向
  2. has5返回true则表示你获得胜利,否则AI将会走一步棋
    在刚才的mouseClicked修改代码
@Override
public void mouseClicked(MouseEvent e) {
     
	//在合适的位置点击则进行落子操作
	if(!"start".equals(gameFlag)) return ;
	
	int x = e.getX();
	int y = e.getY();
	
	Pointer pointer;
	for (int i = 0; i <ROWS; i++) {
     
		for (int j = 0; j < COLS; j++) {
     
			pointer = points[i][j];
			if(pointer==null)continue;
			//被点击,且没有棋子,则可以落子
			if(pointer.isPoint(x, y) && pointer.getQizi()==0){
     
				Qizi qizi = new Qizi(pointer.getX(), pointer.getY(), 2, gamePanel);
				pointer.setQizi(2);
				qizis.add(qizi);
				//重绘画布
				repaint();
				//判断有没有五子连珠的情况
				if(AI.has5(pointer, gamePanel)){
     
					gamePanel.gameWin();
				}else{
     
					AI.next(gamePanel);
				}
				return ;
			}
		}
	}
}
  1. 在AI类中随机落子(创建方法)

- 随机获取下标 i 和 j。
- 通过下标从二维数组取到指示器对象。
- 如果此指示器被占用则再次随机(递归),直到正确获取到指示器对象。
- 在此指示器位置,创建棋子对象并更新指示器的棋子信息。
- Qizi类中加入last属性,表示AI下的最后一个棋子。
- 根据last在对应的位置创建一个小红方块标示AI的最后落子。

//随机落子
static boolean luoziRandom(GamePanel gamePanel){
     
	
	Pointer pointer = getRandomPointer(gamePanel);
	
	luozi(pointer, gamePanel,1);
	
	return true;
}

//获取随机下的棋子
static Pointer getRandomPointer(GamePanel gamePanel){
     
	Random random = new Random();
	int i = random.nextInt(gamePanel.ROWS);
	int j = random.nextInt(gamePanel.COLS);
	//取得随机到的格子
	Pointer pointer = gamePanel.points[i][j];
	if(pointer.getQizi()!=0){
     //如果当前格子已经下了棋子,则递归重新取
		pointer = getRandomPointer(gamePanel);
	}
	return pointer;
}

//AI落子操作
static void luozi(Pointer pointer,GamePanel gamePanel,int type){
     
	if(pointer.getQizi()==0){
     //如果没有棋子,则落子
		Qizi qizi = new Qizi(pointer.getX(), pointer.getY(), type, gamePanel);
		qizi.setLast(true);
		pointer.setQizi(type);
		gamePanel.qizis.add(qizi);
		
		//重绘画布
		gamePanel.repaint();
		
		//判断电脑有没有五子连珠的情况
		if(AI.has5(pointer, gamePanel)){
     
			gamePanel.gameOver();
		}
	}
}
  1. AI的next方法调用随机落子
//AI进行下一步
	static void next(GamePanel gamePanel){
     
		luoziRandom(gamePanel);
	}

运行效果:

Java五子棋(人机版),昨天买的棋子今天就用不上了_第9张图片

  1. 小红方块一直在,修改代码
    仅需在落子前将其他小红方块清除即可
//清除电脑棋子的最后一个棋子指示器
private void clearAILast() {
     
	Qizi qizi;
	for (int i = 0; i < qizis.size(); i++) {
     
		qizi = (Qizi)qizis.get(i);
		if(qizi!=null && qizi.getType()==1){
     
			qizi.setLast(false);
		}
	}
}

Java五子棋(人机版),昨天买的棋子今天就用不上了_第10张图片
Java五子棋(人机版),昨天买的棋子今天就用不上了_第11张图片

AI算法

棋子的4个方向

  • 横向
    Java五子棋(人机版),昨天买的棋子今天就用不上了_第12张图片

  • 竖向
    Java五子棋(人机版),昨天买的棋子今天就用不上了_第13张图片

  • 右捺
    Java五子棋(人机版),昨天买的棋子今天就用不上了_第14张图片

  • 左撇
    Java五子棋(人机版),昨天买的棋子今天就用不上了_第15张图片

权重分

描述:计算出分数,最高的分数来决定下一步的落子。
左开:就是说左边可落子
右开:右边可落子

3子的相关定义(4、5子类似)

什么是3子左开,就是目前有2个子,下一个子可以落子左边

Java五子棋(人机版),昨天买的棋子今天就用不上了_第16张图片

3子只能落子在中间,图示:

Java五子棋(人机版),昨天买的棋子今天就用不上了_第17张图片

3子右开就是落子在右边

在这里插入图片描述

只计算3个子以上的分数

类型 3子左开 3子 3子右开
得分 32 30 31
类型 4子左开 4子 4子右开
得分 42 40 41
类型 5子左开 5子 5子右开
得分 52 50 51

通过上述表可以看到,落子权重分顺序:5>4>3,同时:左>右>中。

计算横向权重分

  1. 从左往右判断

* 横向下标 i 是一样的,循环从当前位置 j 加1开始。
* 当碰到和当前子一样的就计数器 +1。
* 当碰到不一样的就退出循环,表示堵住 。
* 如果碰到空子,是第一次计数器 +1,第二次退出循环。
* 判断左开和右开的状态。
* 根据计数器和左右开的状态,计算出分数

  1. 从右往左判断

* 横向下标 i 是一样的,循环从当前位置 j 减1开始。
* 当碰到和当前子一样的就计数器 +1。
* 当碰到不一样的就退出循环,表示堵住 。
* 如果碰到空子,是第一次计数器 +1,第二次退出循环。
* 判断左开和右开的状态。
* 根据计数器、左右开的状态,计算出分数和落子的位置。

其实左右还是很相似的

static Data getData(Pointer pointer,int dir,int type,GamePanel gamePanel){
     
	Pointer[][] points = gamePanel.points;
	int i = pointer.getI();
	int j = pointer.getJ();
	
	Data resData = new Data();
	Pointer tempPointer;
	int num=1;//默认是1,因为其中自己就是一个子。
	int num2=1;//默认是1,用来累加连续的棋子数
	int breakNum=0;//默认是0,有一个则不能通过了。
	
	boolean lClosed=false;//左边是否关闭
	boolean rClosed=false;//右边是否关闭
	if(dir==1){
     //横向
		//往右循环,判断能与当前pointer 相同的棋子连续多少个。
		if(type==1){
     
			for (int k = j+1; k < gamePanel.COLS; k++) {
     
				tempPointer = points[i][k];
				if(tempPointer.getQizi()==pointer.getQizi()){
     //连续
					num++;
					num2++;
					if(k == gamePanel.COLS-1){
     //如果最后一个子也是连续的,则也是右关闭的
						rClosed = true;
					}
				}else if(tempPointer.getQizi()==0){
     //空白子
					if(breakNum==1){
     //有一个则不能通过了
						if(points[i][k-1].getQizi()==0){
     //如果前一个是空子,要设置成不是中断的
							breakNum=0;
						}else{
     
							breakNum=2;
						}
						break;
					}
					breakNum=1;
					num++;
					//是中断的那种,这里设定好落子位置
					resData.setI(i);
					resData.setJ(k);
				}else{
     //对立子,右关闭
					rClosed = true;
					break;
				}
			}
			//判断是否左关闭
			if(j==0){
     //当前子就是最左边的子
				lClosed = true;
			}else{
     
				tempPointer = points[i][j-1];
				if(tempPointer.getQizi()!=0){
     //如果当前子的左边有子,则左关闭
					lClosed = true;
				}
			}
		}else{
     //从右往左
			for (int k = j-1; k >=0; k--) {
     
				tempPointer = points[i][k];
				if(tempPointer.getQizi()==pointer.getQizi()){
     //连续
					num++;
					num2++;
					if(k == 0){
     //如果最后一个子也是连续的,则也是左关闭的
						lClosed = true;
					}
				}else if(tempPointer.getQizi()==0){
     //空白子
					if(breakNum==1){
     //有一个则不能通过了。
						if(points[i][k+1].getQizi()==0){
     //如果前一个是空子,要设置成不是中断的
							breakNum=0;
						}else{
     
							breakNum=2;
						}
						break;
					}
					breakNum=1;
					num++;
					//是中断的那种,这里设定好落子位置
					resData.setI(i);
					resData.setJ(k);
				}else{
     //对立子,左关闭
					lClosed = true;
					break;
				}
			}
			//判断是否右关闭
			if(j==gamePanel.COLS-1){
     //当前子就是最右边的子
				rClosed = true;
			}else{
     
				tempPointer = points[i][j+1];
				if(tempPointer.getQizi()!=0){
     //如果当前子的右边有子,则右关闭
					rClosed = true;
				}
			}
		}
	}
	setCount(resData, i, j, dir, type, num,num2, breakNum, lClosed, rClosed);
	
	return resData;
}

//计算并设置分数
static void setCount(Data data,int i,int j,int dir,int type,
		int num,int num2,int breakNum,boolean lClosed,boolean rClosed){
     
	int count=0;
	if(num>2){
     //连续3个子以上
		if(num==3){
     //设定默认分
			count=30;
		}else if(num==4){
     
			count=40;
		}else if(num==5){
     
			count=50;
		}
		if(num2>=5&&breakNum==0){
     //用来判断是否五子或五子以上
			count=100;
			//设定好权重分
			data.setCount(count);
			return ;
		}
		if(breakNum==0){
     //如果不是中断的那种
			if(lClosed&&rClosed){
     //如果没有中断,并且左右都关闭了,则分数为-1,-1表示落子的时候要过滤掉
				count = -1;
			}else if(!lClosed){
     //如果是中断的那种,左边未关闭
				count+=2;//加2分
				if(dir==1){
     
					if(type==1){
     
						data.setI(i);
						data.setJ(j-1);
					}else{
     
						data.setI(i);
						data.setJ(j-num+1);
					}
				}else if(dir==2){
     
					if(type==1){
     
						data.setI(i-1);
						data.setJ(j);
					}else{
     
						data.setI(i-num+1);
						data.setJ(j);
					}
				}else if(dir==3){
     
					if(type==1){
     
						data.setI(i-1);
						data.setJ(j-1);
					}else{
     
						data.setI(i-num+1);
						data.setJ(j-num+1);
					}
				}else if(dir==4){
     
					if(type==1){
     
						data.setI(i+1);
						data.setJ(j-1);
					}else{
     
						data.setI(i+num-1);
						data.setJ(j-num+1);
					}
				}
			}else if(!rClosed){
     //如果是中断的那种,右边未关闭
				count+=1;//加1分
				if(dir==1){
     
					if(type==1){
     
						data.setI(i);
						data.setJ(j+num-1);
					}else{
     
						data.setI(i);
						data.setJ(j+1);
					}
				}else if(dir==2){
     
					if(type==1){
     
						data.setI(i+num-1);
						data.setJ(j);
					}else{
     
						data.setI(i+1);
						data.setJ(j);
					}
				}else if(dir==3){
     
					if(type==1){
     
						data.setI(i+num-1);
						data.setJ(j+num-1);
					}else{
     
						data.setI(i+1);
						data.setJ(j+1);
					}
				}else if(dir==4){
     
					if(type==1){
     
						data.setI(i-num+1);
						data.setJ(j+num-1);
					}else{
     
						data.setI(i-1);
						data.setJ(j+1);
					}
				}
			}
		}else{
     //如果中断,
			if(num!=5){
     //num不是5, 并且左右都关闭,也要过滤
				if(lClosed&&rClosed){
     
					count = -1;
				}
			}
		}
		//设定好权重分
		data.setCount(count);
	}
}

AI落子处理

- 循环取横向、竖向、右捺、左撇 4种分数,放到List集合中
- 对集合进行排序(分数从高到底)
- 第一个分数值作为下一步落子的位置
- 落子操作(如果集合没有值,则进行随机落子)

//进行下一步
static boolean go(GamePanel gamePanel){
     
	
	List<Data> datas=new ArrayList<Data>();
	//循环找出黑棋,判断此棋子的1横向  2纵向  3右捺  4左撇 是否有4子的情况,
	Pointer pointer;
	for (int i = 0; i <gamePanel.ROWS; i++) {
     
		for (int j = 0; j < gamePanel.COLS; j++) {
     
			pointer = gamePanel.points[i][j];
			if(pointer==null)continue;
			if(pointer.getQizi()==0){
     //没有棋子则跳过
				continue;
			}
			//循环4个方向
			int dir=1;
			for (int k = 1; k <= 4; k++) {
     
				dir = k;
				Data data = getData(pointer, dir,1, gamePanel);
				if(data.getCount()!=-1&&data.getCount()!=0){
     //0和-1 的过滤掉
					datas.add(data);
				}
				data = getData(pointer, dir, 2,gamePanel);
				if(data.getCount()!=-1&&data.getCount()!=0){
     //0和-1 的过滤掉
					datas.add(data);
				}
			}
		}
	}
	//按权重分排序处理,从大到小
	Collections.sort(datas, new DataCount());
	
	/*for (int i = 0; i < datas.size(); i++) {
		System.out.println("----------"+datas.get(i).getCount());
	}*/
	if(datas.size()>0){
     //取第一个位置落子
		Data data = datas.get(0);
		Pointer p = gamePanel.points[data.getI()][data.getJ()];
		luozi(p, gamePanel, 1);
		return true;
	}
	
	return false;
}

五子或者以上的判断就很简单了,当棋子是连续的并且计数器大于5就成功了!

这里只介绍了横向的,另外3个情况也差不多,就是注意下标的处理即可。

Java五子棋(人机版),昨天买的棋子今天就用不上了_第18张图片

最后

1. AI还不是特别智能,应该算简单版吧,赢的难度不大.
2. 可能会有我没发现的bug吧,望理解!

看到这里的大佬,动动发财的小手 点赞 + 回复 + 收藏,能【 关注 】一波就更好了。

代码获取方式
订阅我的专栏《Java游戏大全》后,可以查看专栏内所有的文章,并且联系博主免费获取其中1-3份你心仪的源代码,专栏的文章都是上过csdn热榜的,值得信赖!专栏内目前有[13]篇实例,未来2个月内专栏会更新到15篇以上,一般2周一更,了解一下我的专栏

热门专栏推荐

【1】Java小游戏(俄罗斯方块、飞机大战、植物大战僵尸等)
【2】JavaWeb项目实战(图书管理、在线考试、宿舍管理等)
【3】JavaScript精彩实例(飞机大战、贪吃蛇、验证码等)
【4】Java小白入门200例
【5】从零学Java、趣学Java
【6】Idea从零到精通

更多精彩

1. Java俄罗斯方块
2. 老Java程序员花2天写了个连连看
3. 老Java程序员花一天时间写了个飞机大战
4. Java五子棋人机版
5. Java植物大战僵尸
6. Java消消乐(天天爱消除)
7. Java贪吃蛇小游戏
8. Java扫雷小游戏
9. Java坦克大战

你可能感兴趣的:(java项目实战,Java游戏实例大全,Java,五子棋,小游戏,五子棋人机版)