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

19 篇文章 97 订阅

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坦克大战

效果图

在这里插入图片描述

实现思路

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类,窗口建出来了
在这里插入图片描述

创建菜单及菜单选项

创建菜单

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
在这里插入图片描述
此时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);
		}
	}
}

运行一下
在这里插入图片描述

绘制棋盘

重写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);
}

棋盘已经绘制完成
在这里插入图片描述

实现落子指示器

  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);
			}
		}
	}
}

运行如下
在这里插入图片描述

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

  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);
}

再运行

在这里插入图片描述

落子

  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);
	}
}

在这里插入图片描述

加入电脑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);
	}

运行效果:

在这里插入图片描述

  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);
		}
	}
}

在这里插入图片描述
在这里插入图片描述

AI算法

棋子的4个方向

  • 横向
    在这里插入图片描述

  • 竖向
    在这里插入图片描述

  • 右捺
    在这里插入图片描述

  • 左撇
    在这里插入图片描述

权重分

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

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

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

在这里插入图片描述

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

在这里插入图片描述

3子右开就是落子在右边

在这里插入图片描述

只计算3个子以上的分数

类型3子左开3子3子右开
得分323031
类型4子左开4子4子右开
得分424041
类型5子左开5子5子右开
得分525051

通过上述表可以看到,落子权重分顺序: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个情况也差不多,就是注意下标的处理即可。

在这里插入图片描述

最后

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坦克大战

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:撸撸猫 设计师:马嘣嘣 返回首页
评论 164

打赏作者

编程界明世隐

请博主喝瓶水,博主持续输出!

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值