统计
  • 建站日期:2021-03-10
  • 文章总数:699 篇
  • 评论总数:733 条
  • 分类总数:10 个
  • 最后更新:12月4日
文章 后端知识

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

在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子右开就是落子在右边


Java五子棋(人机版)
-程序员阿鑫-带你一起秃头!
-第18
张图片

只计算3个子以上的分数

类型 3子左开 3子 3子右开
得分 32 30 31
类型 4子左开 4子 4子右开
得分 42 40 41
类型 5子左开 5子 5子右开
得分 52 50 51
通过上述表可以看到,落子权重分顺序:5>4>3,同时:左>右>中。

计算横向权重分
从左往右判断


* 横向下标 i 是一样的,循环从当前位置 j 加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五子棋(人机版)
-程序员阿鑫-带你一起秃头!
-第19
张图片

最后

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

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

版权说明
文章采用: 《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权。
版权声明:未标注转载均为本站原创,转载时请以链接形式注明文章出处。如有侵权、不妥之处,请联系站长删除。敬请谅解!

-- 展开阅读全文 --
这篇文章最后更新于2021-7-20,已超过 1 年没有更新,如果文章内容或图片资源失效,请留言反馈,我们会及时处理,谢谢!
基础总结篇之一:Activity生命周期
« 上一篇
vue cli3 获取本地json数据的方式
下一篇 »
为了防止灌水评论,登录后即可评论!
注册登录

HI ! 请登录
注册会员,享受下载全站资源特权。
登陆 注册
上号,带你一起秃头!

IP地址

热门文章

1
抖音无限礼物模拟小工具分享
2
QQ假红包引流QQ群教程及代码
4
卡QQ永久大会员方法

最新文章

标签