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

青旅半醒 2021-08-11 03:25 439阅读 0赞

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

效果图

在这里插入图片描述

实现思路

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

代码实现

创建窗口

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

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

创建面板容器GamePanel继承至JPanel

  1. import javax.swing.JFrame;
  2. import javax.swing.JPanel;
  3. /* * 画布类 */
  4. public class GamePanel extends JPanel {
  5. private static final long serialVersionUID = 1L;
  6. GamePanel gamePanel=this;
  7. private JFrame mainFrame=null;
  8. //构造里面初始化相关参数
  9. public GamePanel(JFrame frame){
  10. this.setLayout(null);
  11. this.setOpaque(false);
  12. mainFrame = frame;
  13. mainFrame.requestFocus();
  14. mainFrame.setVisible(true);
  15. }
  16. }

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

  1. public class Main {
  2. //主类
  3. public static void main(String[] args) {
  4. GameFrame frame = new GameFrame();
  5. GamePanel gamePanel = new GamePanel(frame);
  6. frame.add(gamePanel);
  7. frame.setVisible(true);//设定显示
  8. }
  9. }

右键执行这个Main类,窗口建出来了
在这里插入图片描述

创建菜单及菜单选项

创建菜单

  1. private void initMenu(){
  2. // 创建菜单及菜单选项
  3. jmb = new JMenuBar();
  4. JMenu jm1 = new JMenu("游戏");
  5. jm1.setFont(new Font("思源宋体", Font.BOLD, 18));// 设置菜单显示的字体
  6. JMenu jm2 = new JMenu("帮助");
  7. jm2.setFont(new Font("思源宋体", Font.BOLD, 18));// 设置菜单显示的字体
  8. JMenuItem jmi1 = new JMenuItem("开始新游戏");
  9. JMenuItem jmi2 = new JMenuItem("退出");
  10. jmi1.setFont(new Font("思源宋体", Font.BOLD, 18));
  11. jmi2.setFont(new Font("思源宋体", Font.BOLD, 18));
  12. JMenuItem jmi3 = new JMenuItem("操作说明");
  13. jmi3.setFont(new Font("思源宋体", Font.BOLD, 18));
  14. JMenuItem jmi4 = new JMenuItem("成功/失败判定");
  15. jmi4.setFont(new Font("思源宋体", Font.BOLD, 18));
  16. jm1.add(jmi1);
  17. jm1.add(jmi2);
  18. jm2.add(jmi3);
  19. jm2.add(jmi4);
  20. jmb.add(jm1);
  21. jmb.add(jm2);
  22. mainFrame.setJMenuBar(jmb);// 菜单Bar放到JFrame上
  23. jmi1.addActionListener(this);
  24. jmi1.setActionCommand("Restart");
  25. jmi2.addActionListener(this);
  26. jmi2.setActionCommand("Exit");
  27. jmi3.addActionListener(this);
  28. jmi3.setActionCommand("help");
  29. jmi4.addActionListener(this);
  30. jmi4.setActionCommand("lost");
  31. }

实现ActionListener并重写方法actionPerformed
在这里插入图片描述
此时GamePanel是报错的,重写actionPerformed方法。

actionPerformed方法的实现

  1. @Override
  2. public void actionPerformed(ActionEvent e) {
  3. String command = e.getActionCommand();
  4. System.out.println(command);
  5. UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));
  6. UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));
  7. if ("Exit".equals(command)) {
  8. Object[] options = { "确定", "取消" };
  9. int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",
  10. JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
  11. options, options[0]);
  12. if (response == 0) {
  13. System.exit(0);
  14. }
  15. }else if("Restart".equals(command)){
  16. if(!"end".equals(gamePanel.gameFlag)){
  17. JOptionPane.showMessageDialog(null, "正在游戏中无法重新开始!",
  18. "提示!", JOptionPane.INFORMATION_MESSAGE);
  19. }else{
  20. if(gamePanel!=null) {
  21. gamePanel.restart();
  22. }
  23. }
  24. }else if("help".equals(command)){
  25. JOptionPane.showMessageDialog(null, "鼠标在指示器位置点下,则落子!",
  26. "提示!", JOptionPane.INFORMATION_MESSAGE);
  27. }else if("lost".equals(command)){
  28. JOptionPane.showMessageDialog(null, "五子连珠方获得胜利!",
  29. "提示!", JOptionPane.INFORMATION_MESSAGE);
  30. }
  31. }

此时的GamePanel代码如下:

  1. package main;
  2. import java.awt.Font;
  3. import java.awt.event.ActionEvent;
  4. import java.awt.event.ActionListener;
  5. import javax.swing.JFrame;
  6. import javax.swing.JMenu;
  7. import javax.swing.JMenuBar;
  8. import javax.swing.JMenuItem;
  9. import javax.swing.JOptionPane;
  10. import javax.swing.JPanel;
  11. import javax.swing.UIManager;
  12. import javax.swing.plaf.FontUIResource;
  13. /* * 画布类 */
  14. public class GamePanel extends JPanel implements ActionListener{
  15. private static final long serialVersionUID = 1L;
  16. GamePanel gamePanel=this;
  17. private JFrame mainFrame=null;
  18. JMenuBar jmb=null;
  19. public String gameFlag="";
  20. //构造里面初始化相关参数
  21. public GamePanel(JFrame frame){
  22. this.setLayout(null);
  23. this.setOpaque(false);
  24. mainFrame = frame;
  25. //创建按钮
  26. initMenu();
  27. mainFrame.requestFocus();
  28. mainFrame.setVisible(true);
  29. }
  30. private void initMenu(){
  31. // 创建菜单及菜单选项
  32. jmb = new JMenuBar();
  33. JMenu jm1 = new JMenu("游戏");
  34. jm1.setFont(new Font("思源宋体", Font.BOLD, 18));// 设置菜单显示的字体
  35. JMenu jm2 = new JMenu("帮助");
  36. jm2.setFont(new Font("思源宋体", Font.BOLD, 18));// 设置菜单显示的字体
  37. JMenuItem jmi1 = new JMenuItem("开始新游戏");
  38. JMenuItem jmi2 = new JMenuItem("退出");
  39. jmi1.setFont(new Font("思源宋体", Font.BOLD, 18));
  40. jmi2.setFont(new Font("思源宋体", Font.BOLD, 18));
  41. JMenuItem jmi3 = new JMenuItem("操作说明");
  42. jmi3.setFont(new Font("思源宋体", Font.BOLD, 18));
  43. JMenuItem jmi4 = new JMenuItem("成功/失败判定");
  44. jmi4.setFont(new Font("思源宋体", Font.BOLD, 18));
  45. jm1.add(jmi1);
  46. jm1.add(jmi2);
  47. jm2.add(jmi3);
  48. jm2.add(jmi4);
  49. jmb.add(jm1);
  50. jmb.add(jm2);
  51. mainFrame.setJMenuBar(jmb);// 菜单Bar放到JFrame上
  52. jmi1.addActionListener(this);
  53. jmi1.setActionCommand("Restart");
  54. jmi2.addActionListener(this);
  55. jmi2.setActionCommand("Exit");
  56. jmi3.addActionListener(this);
  57. jmi3.setActionCommand("help");
  58. jmi4.addActionListener(this);
  59. jmi4.setActionCommand("lost");
  60. }
  61. //重新开始
  62. public void restart() {
  63. //游戏开始标记
  64. gameFlag="start";
  65. }
  66. @Override
  67. public void actionPerformed(ActionEvent e) {
  68. String command = e.getActionCommand();
  69. System.out.println(command);
  70. UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));
  71. UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));
  72. if ("Exit".equals(command)) {
  73. Object[] options = { "确定", "取消" };
  74. int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",
  75. JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
  76. options, options[0]);
  77. if (response == 0) {
  78. System.exit(0);
  79. }
  80. }else if("Restart".equals(command)){
  81. if(!"end".equals(gamePanel.gameFlag)){
  82. JOptionPane.showMessageDialog(null, "正在游戏中无法重新开始!",
  83. "提示!", JOptionPane.INFORMATION_MESSAGE);
  84. }else{
  85. if(gamePanel!=null) {
  86. gamePanel.restart();
  87. }
  88. }
  89. }else if("help".equals(command)){
  90. JOptionPane.showMessageDialog(null, "鼠标在指示器位置点下,则落子!",
  91. "提示!", JOptionPane.INFORMATION_MESSAGE);
  92. }else if("lost".equals(command)){
  93. JOptionPane.showMessageDialog(null, "五子连珠方获得胜利!",
  94. "提示!", JOptionPane.INFORMATION_MESSAGE);
  95. }
  96. }
  97. }

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

绘制棋盘

重写paint方法

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

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

  1. public static final int ROWS=15;
  2. public static final int COLS=15;

绘制网格

  1. //绘制网格
  2. private void drawGrid(Graphics g) {
  3. Graphics2D g_2d=(Graphics2D)g;
  4. int start=26;
  5. int x1=start;
  6. int y1=20;
  7. int x2=586;
  8. int y2=20;
  9. for (int i = 0; i < ROWS; i++) {
  10. y1 = start + 40*i;
  11. y2 = y1;
  12. g_2d.drawLine(x1, y1, x2, y2);
  13. }
  14. y1=start;
  15. y2=586;
  16. for (int i = 0; i < COLS; i++) {
  17. x1 = start + 40*i;
  18. x2 = x1;
  19. g_2d.drawLine(x1, y1, x2, y2);
  20. }
  21. }

绘制5个圆点

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

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

  1. @Override
  2. public void paint(java.awt.Graphics g) {
  3. super.paint(g);
  4. //绘制网格
  5. drawGrid(g);
  6. //绘制5个黑点
  7. draw5Point(g);
  8. }

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

实现落子指示器

  1. 创建指示器类

    1. package main;
    2. import java.awt.Color;
    3. import java.awt.Graphics;
    4. //指示器类
    5. public class Pointer {
    6. private int i=0;//二维下标i
    7. private int j=0;//二维下标j
    8. private int x=0;//坐标X
    9. private int y=0;//坐标Y
    10. private GamePanel panel=null;
    11. private Color color=null;
    12. private int h=40;//指示的大小
    13. private boolean isShow=false;//是否展示
    14. private int qizi = 0 ;//棋子类型 0:无 1:白棋 2:黑棋
    15. public Pointer(int x,int y,int i,int j,Color color,GamePanel panel){
    16. this.x=x;
    17. this.y=y;
    18. this.i=i;
    19. this.j=j;
    20. this.panel=panel;
    21. this.color=color;
    22. }
    23. //绘制
    24. void draw(Graphics g){
    25. Color oColor = g.getColor();
    26. if(color!=null){
    27. g.setColor(color);
    28. }
    29. if(isShow){
    30. //绘制指示器
    31. g.drawRect(x-h/2, y-h/2, h, h);
    32. }
    33. if(color!=null){ //用完设置回去颜色
    34. g.setColor(oColor);
    35. }
    36. }
    37. //判断鼠标是否在指针范围内
    38. boolean isPoint(int x,int y){
    39. //大于左上角,小于右下角的坐标则肯定在范围内
    40. if(x>this.x-h/2 && y >this.y-h/2
    41. && x<this.x+h/2 && y <this.y+h/2){
    42. return true;
    43. }
    44. return false;
    45. }
    46. public boolean isShow() {
    47. return isShow;
    48. }
    49. public void setShow(boolean isShow) {
    50. this.isShow = isShow;
    51. }
    52. public int getQizi() {
    53. return qizi;
    54. }
    55. public void setQizi(int qizi) {
    56. this.qizi = qizi;
    57. }
    58. public int getX() {
    59. return x;
    60. }
    61. public void setX(int x) {
    62. this.x = x;
    63. }
    64. public int getY() {
    65. return y;
    66. }
    67. public void setY(int y) {
    68. this.y = y;
    69. }
    70. public int getI() {
    71. return i;
    72. }
    73. public void setI(int i) {
    74. this.i = i;
    75. }
    76. public int getJ() {
    77. return j;
    78. }
    79. public void setJ(int j) {
    80. this.j = j;
    81. }
    82. }
  2. 初始化二维数组

    public Pointer points[][] = new Pointer[ROWS][COLS];

  3. 创建指示器实例对象

    //创建二维数组
    private void createArr() {

    1. int x=0,y=0;
    2. for (int i = 0; i < ROWS; i++) {
    3. for (int j = 0; j < COLS; j++) {
    4. y = 26 + 40*i;
    5. x = 26 + 40*j;
    6. Pointer pointer = new Pointer(x, y, i,j,new Color(255,0,0), this);
    7. points[i][j] = pointer;
    8. }
    9. }

    }

  4. 初始化调用

    1. //初始化相关对象
    2. private void init() {
    3. createArr();
    4. //游戏开始标记
    5. gameFlag="start";
    6. }

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

  5. paint方法中遍历二维数组并且绘制。

    1. @Override
    2. public void paint(java.awt.Graphics g) {
    3. super.paint(g);
    4. //绘制网格
    5. drawGrid(g);
    6. //绘制5个黑点
    7. draw5Point(g);
    8. //绘制指示器
    9. Pointer pointer = null;
    10. for (int i = 0; i < ROWS; i++) {
    11. for (int j = 0; j < COLS; j++) {
    12. pointer = points[i][j] ;
    13. if(pointer!=null){
    14. pointer.draw(g);
    15. }
    16. }
    17. }
    18. }

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

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

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

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

  1. private void drawPointer(Graphics g) {
  2. Graphics2D g2 = (Graphics2D)g; //g是Graphics对象
  3. g2.setStroke(new BasicStroke(2.0f));
  4. /* * 1.先计算4个顶点 * 2.依次从每个顶点绘制横竖两条线 */
  5. //左上角
  6. int x1 = x-h/2;
  7. int y1 = y-h/2;
  8. //向右画线
  9. int x2 = x1+1*h/4;
  10. int y2 = y1;
  11. g2.drawLine(x1, y1, x2, y2);
  12. //向下画线
  13. x2 = x1;
  14. y2 = y1+1*h/4;
  15. g2.drawLine(x1, y1, x2, y2);
  16. //右上角
  17. x1 = x+h/2;
  18. y1 = y-h/2;
  19. //向左画线
  20. x2 = x1-1*h/4;
  21. y2 = y1;
  22. g2.drawLine(x1, y1, x2, y2);
  23. //向下画线
  24. x2 = x1;
  25. y2 = y1+1*h/4;
  26. g2.drawLine(x1, y1, x2, y2);
  27. //右下角
  28. x1 = x+h/2;
  29. y1 = y+h/2;
  30. //向左画线
  31. x2 = x1-1*h/4;
  32. y2 = y1;
  33. g2.drawLine(x1, y1, x2, y2);
  34. //向上画线
  35. x2 = x1;
  36. y2 = y1-1*h/4;
  37. g2.drawLine(x1, y1, x2, y2);
  38. //左下角
  39. x1 = x-h/2;
  40. y1 = y+h/2;
  41. //向右画线
  42. x2 = x1+1*h/4;
  43. y2 = y1;
  44. g2.drawLine(x1, y1, x2, y2);
  45. //向上画线
  46. x2 = x1;
  47. y2 = y1-1*h/4;
  48. g2.drawLine(x1, y1, x2, y2);
  49. }

再运行

在这里插入图片描述

落子

  1. 创建ImageValue加载类

    1. import java.awt.image.BufferedImage;
    2. import java.io.IOException;
    3. import javax.imageio.ImageIO;
    4. public class ImageValue {
    5. public static BufferedImage whiteQiziImage ;
    6. public static BufferedImage blackQiziImage ;
    7. //路径
    8. public static String ImagePath = "/images/";
    9. //将图片初始化
    10. public static void init(){
    11. String whitePath = ImagePath +"white.png";
    12. String blackPath = ImagePath +"black.png";
    13. try {
    14. whiteQiziImage = ImageIO.read(ImageValue.class.getResource(whitePath));
    15. blackQiziImage = ImageIO.read(ImageValue.class.getResource(blackPath));
    16. } catch (IOException e) {
    17. e.printStackTrace();
    18. }
    19. }
    20. }
  2. 创建落子类

    1. import java.awt.Color;
    2. import java.awt.Graphics;
    3. import common.ImageValue;
    4. public class Qizi {
    5. private int x = 0;
    6. private int y = 0;
    7. private int r = 36;
    8. private GamePanel panel = null;
    9. private Color color = null;
    10. private int type = 1;// 棋子类型 1:白棋 2:黑棋
    11. public Qizi(int x, int y, int type, GamePanel panel) {
    12. this.x = x;
    13. this.y = y;
    14. this.panel = panel;
    15. this.type=type;
    16. }
    17. // 绘制
    18. void draw(Graphics g) {
    19. Color oColor = g.getColor();
    20. if (type == 1) { // 白色
    21. g.drawImage(ImageValue.whiteQiziImage, x - r / 2, y - r / 2,r,r, null);
    22. } else { // 黑色
    23. g.drawImage(ImageValue.blackQiziImage, x - r / 2, y - r / 2,r,r, null);
    24. }
    25. if (color != null) { // 用完设置回去颜色
    26. g.setColor(oColor);
    27. }
    28. }
    29. public int getType() {
    30. return type;
    31. }
    32. public void setType(int type) {
    33. this.type = type;
    34. }
    35. }
  3. 在createMouseListener方法中重写mouseClicked,创建棋子

    1. @Override
    2. public void mouseClicked(MouseEvent e) {
    3. //在合适的位置点击则进行落子操作
    4. if(!"start".equals(gameFlag)) return ;
    5. int x = e.getX();
    6. int y = e.getY();
    7. Pointer pointer;
    8. for (int i = 0; i <ROWS; i++) {
    9. for (int j = 0; j < COLS; j++) {
    10. pointer = points[i][j];
    11. if(pointer==null)continue;
    12. //被点击,且没有棋子,则可以落子
    13. if(pointer.isPoint(x, y) && pointer.getQizi()==0){
    14. Qizi qizi = new Qizi(pointer.getX(), pointer.getY(), 2, gamePanel);
    15. pointer.setQizi(2);
    16. qizis.add(qizi);
    17. //重绘画布
    18. repaint();
    19. return ;
    20. }
    21. }
    22. }
    23. }
  4. 在paint 方法中绘制棋子
    1. @Override
    2. public void paint(java.awt.Graphics g) {
    3. super.paint(g);
    4. //绘制网格
    5. drawGrid(g);
    6. //绘制5个黑点
    7. draw5Point(g);
    8. //绘制指示器
    9. Pointer pointer = null;
    10. for (int i = 0; i < ROWS; i++) {
    11. for (int j = 0; j < COLS; j++) {
    12. pointer = points[i][j] ;
    13. if(pointer!=null){
    14. pointer.draw(g);
    15. }
    16. }
    17. }
    18. //绘制棋子
    19. Qizi qizi=null;
    20. for (int i = 0; i < qizis.size(); i++) {
    21. qizi = (Qizi)qizis.get(i);
    22. qizi.draw(g);
    23. }
    24. }
    在这里插入图片描述

加入电脑AI

  1. 创建AI类
  2. 创建静态方法next(下一步)
  3. 创建静态方法has5(连成5子)

    1. public class AI {
    2. //AI进行下一步
    3. static void next(GamePanel gamePanel){
    4. }
    5. //判断五子连珠
    6. static boolean has5(Pointer pointer1,GamePanel gamePanel){
    7. return false;
    8. }
    9. }
  4. 在你落子后,会先执行has5方法,根据返回来决定走向
  5. has5返回true则表示你获得胜利,否则AI将会走一步棋
    在刚才的mouseClicked修改代码

    1. @Override
    2. public void mouseClicked(MouseEvent e) {
    3. //在合适的位置点击则进行落子操作
    4. if(!"start".equals(gameFlag)) return ;
    5. int x = e.getX();
    6. int y = e.getY();
    7. Pointer pointer;
    8. for (int i = 0; i <ROWS; i++) {
    9. for (int j = 0; j < COLS; j++) {
    10. pointer = points[i][j];
    11. if(pointer==null)continue;
    12. //被点击,且没有棋子,则可以落子
    13. if(pointer.isPoint(x, y) && pointer.getQizi()==0){
    14. Qizi qizi = new Qizi(pointer.getX(), pointer.getY(), 2, gamePanel);
    15. pointer.setQizi(2);
    16. qizis.add(qizi);
    17. //重绘画布
    18. repaint();
    19. //判断有没有五子连珠的情况
    20. if(AI.has5(pointer, gamePanel)){
    21. gamePanel.gameWin();
    22. }else{
    23. AI.next(gamePanel);
    24. }
    25. return ;
    26. }
    27. }
    28. }
    29. }
  6. 在AI类中随机落子(创建方法)

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

  1. Pointer pointer = getRandomPointer(gamePanel);
  2. luozi(pointer, gamePanel,1);
  3. return true;
  4. }
  5. //获取随机下的棋子
  6. static Pointer getRandomPointer(GamePanel gamePanel){
  7. Random random = new Random();
  8. int i = random.nextInt(gamePanel.ROWS);
  9. int j = random.nextInt(gamePanel.COLS);
  10. //取得随机到的格子
  11. Pointer pointer = gamePanel.points[i][j];
  12. if(pointer.getQizi()!=0){ //如果当前格子已经下了棋子,则递归重新取
  13. pointer = getRandomPointer(gamePanel);
  14. }
  15. return pointer;
  16. }
  17. //AI落子操作
  18. static void luozi(Pointer pointer,GamePanel gamePanel,int type){
  19. if(pointer.getQizi()==0){ //如果没有棋子,则落子
  20. Qizi qizi = new Qizi(pointer.getX(), pointer.getY(), type, gamePanel);
  21. qizi.setLast(true);
  22. pointer.setQizi(type);
  23. gamePanel.qizis.add(qizi);
  24. //重绘画布
  25. gamePanel.repaint();
  26. //判断电脑有没有五子连珠的情况
  27. if(AI.has5(pointer, gamePanel)){
  28. gamePanel.gameOver();
  29. }
  30. }
  31. }
  1. 1. AInext方法调用随机落子
  1. //AI进行下一步
  2. static void next(GamePanel gamePanel){
  3. luoziRandom(gamePanel);
  4. }
  1. 运行效果:
  2. ![在这里插入图片描述][20210719215047199.gif_pic_center]
  3. 1. 小红方块一直在,修改代码
  4. **仅需在落子前将其他小红方块清除即可**
  1. //清除电脑棋子的最后一个棋子指示器
  2. private void clearAILast() {
  3. Qizi qizi;
  4. for (int i = 0; i < qizis.size(); i++) {
  5. qizi = (Qizi)qizis.get(i);
  6. if(qizi!=null && qizi.getType()==1){
  7. qizi.setLast(false);
  8. }
  9. }
  10. }

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

AI算法

棋子的4个方向

  • 横向
    在这里插入图片描述
  • 竖向
    在这里插入图片描述
  • 右捺
    在这里插入图片描述
  • 左撇
    在这里插入图片描述

权重分

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

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

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

在这里插入图片描述

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

在这里插入图片描述

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,第二次退出循环。
* 判断左开和右开的状态。
* 根据计数器、左右开的状态,计算出分数和落子的位置。

其实左右还是很相似的

  1. static Data getData(Pointer pointer,int dir,int type,GamePanel gamePanel){
  2. Pointer[][] points = gamePanel.points;
  3. int i = pointer.getI();
  4. int j = pointer.getJ();
  5. Data resData = new Data();
  6. Pointer tempPointer;
  7. int num=1;//默认是1,因为其中自己就是一个子。
  8. int num2=1;//默认是1,用来累加连续的棋子数
  9. int breakNum=0;//默认是0,有一个则不能通过了。
  10. boolean lClosed=false;//左边是否关闭
  11. boolean rClosed=false;//右边是否关闭
  12. if(dir==1){ //横向
  13. //往右循环,判断能与当前pointer 相同的棋子连续多少个。
  14. if(type==1){
  15. for (int k = j+1; k < gamePanel.COLS; k++) {
  16. tempPointer = points[i][k];
  17. if(tempPointer.getQizi()==pointer.getQizi()){ //连续
  18. num++;
  19. num2++;
  20. if(k == gamePanel.COLS-1){ //如果最后一个子也是连续的,则也是右关闭的
  21. rClosed = true;
  22. }
  23. }else if(tempPointer.getQizi()==0){ //空白子
  24. if(breakNum==1){ //有一个则不能通过了
  25. if(points[i][k-1].getQizi()==0){ //如果前一个是空子,要设置成不是中断的
  26. breakNum=0;
  27. }else{
  28. breakNum=2;
  29. }
  30. break;
  31. }
  32. breakNum=1;
  33. num++;
  34. //是中断的那种,这里设定好落子位置
  35. resData.setI(i);
  36. resData.setJ(k);
  37. }else{ //对立子,右关闭
  38. rClosed = true;
  39. break;
  40. }
  41. }
  42. //判断是否左关闭
  43. if(j==0){ //当前子就是最左边的子
  44. lClosed = true;
  45. }else{
  46. tempPointer = points[i][j-1];
  47. if(tempPointer.getQizi()!=0){ //如果当前子的左边有子,则左关闭
  48. lClosed = true;
  49. }
  50. }
  51. }else{ //从右往左
  52. for (int k = j-1; k >=0; k--) {
  53. tempPointer = points[i][k];
  54. if(tempPointer.getQizi()==pointer.getQizi()){ //连续
  55. num++;
  56. num2++;
  57. if(k == 0){ //如果最后一个子也是连续的,则也是左关闭的
  58. lClosed = true;
  59. }
  60. }else if(tempPointer.getQizi()==0){ //空白子
  61. if(breakNum==1){ //有一个则不能通过了。
  62. if(points[i][k+1].getQizi()==0){ //如果前一个是空子,要设置成不是中断的
  63. breakNum=0;
  64. }else{
  65. breakNum=2;
  66. }
  67. break;
  68. }
  69. breakNum=1;
  70. num++;
  71. //是中断的那种,这里设定好落子位置
  72. resData.setI(i);
  73. resData.setJ(k);
  74. }else{ //对立子,左关闭
  75. lClosed = true;
  76. break;
  77. }
  78. }
  79. //判断是否右关闭
  80. if(j==gamePanel.COLS-1){ //当前子就是最右边的子
  81. rClosed = true;
  82. }else{
  83. tempPointer = points[i][j+1];
  84. if(tempPointer.getQizi()!=0){ //如果当前子的右边有子,则右关闭
  85. rClosed = true;
  86. }
  87. }
  88. }
  89. }
  90. setCount(resData, i, j, dir, type, num,num2, breakNum, lClosed, rClosed);
  91. return resData;
  92. }
  93. //计算并设置分数
  94. static void setCount(Data data,int i,int j,int dir,int type,
  95. int num,int num2,int breakNum,boolean lClosed,boolean rClosed){
  96. int count=0;
  97. if(num>2){ //连续3个子以上
  98. if(num==3){ //设定默认分
  99. count=30;
  100. }else if(num==4){
  101. count=40;
  102. }else if(num==5){
  103. count=50;
  104. }
  105. if(num2>=5&&breakNum==0){ //用来判断是否五子或五子以上
  106. count=100;
  107. //设定好权重分
  108. data.setCount(count);
  109. return ;
  110. }
  111. if(breakNum==0){ //如果不是中断的那种
  112. if(lClosed&&rClosed){ //如果没有中断,并且左右都关闭了,则分数为-1,-1表示落子的时候要过滤掉
  113. count = -1;
  114. }else if(!lClosed){ //如果是中断的那种,左边未关闭
  115. count+=2;//加2分
  116. if(dir==1){
  117. if(type==1){
  118. data.setI(i);
  119. data.setJ(j-1);
  120. }else{
  121. data.setI(i);
  122. data.setJ(j-num+1);
  123. }
  124. }else if(dir==2){
  125. if(type==1){
  126. data.setI(i-1);
  127. data.setJ(j);
  128. }else{
  129. data.setI(i-num+1);
  130. data.setJ(j);
  131. }
  132. }else if(dir==3){
  133. if(type==1){
  134. data.setI(i-1);
  135. data.setJ(j-1);
  136. }else{
  137. data.setI(i-num+1);
  138. data.setJ(j-num+1);
  139. }
  140. }else if(dir==4){
  141. if(type==1){
  142. data.setI(i+1);
  143. data.setJ(j-1);
  144. }else{
  145. data.setI(i+num-1);
  146. data.setJ(j-num+1);
  147. }
  148. }
  149. }else if(!rClosed){ //如果是中断的那种,右边未关闭
  150. count+=1;//加1分
  151. if(dir==1){
  152. if(type==1){
  153. data.setI(i);
  154. data.setJ(j+num-1);
  155. }else{
  156. data.setI(i);
  157. data.setJ(j+1);
  158. }
  159. }else if(dir==2){
  160. if(type==1){
  161. data.setI(i+num-1);
  162. data.setJ(j);
  163. }else{
  164. data.setI(i+1);
  165. data.setJ(j);
  166. }
  167. }else if(dir==3){
  168. if(type==1){
  169. data.setI(i+num-1);
  170. data.setJ(j+num-1);
  171. }else{
  172. data.setI(i+1);
  173. data.setJ(j+1);
  174. }
  175. }else if(dir==4){
  176. if(type==1){
  177. data.setI(i-num+1);
  178. data.setJ(j+num-1);
  179. }else{
  180. data.setI(i-1);
  181. data.setJ(j+1);
  182. }
  183. }
  184. }
  185. }else{ //如果中断,
  186. if(num!=5){ //num不是5, 并且左右都关闭,也要过滤
  187. if(lClosed&&rClosed){
  188. count = -1;
  189. }
  190. }
  191. }
  192. //设定好权重分
  193. data.setCount(count);
  194. }
  195. }

AI落子处理

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

  1. //进行下一步
  2. static boolean go(GamePanel gamePanel){
  3. List<Data> datas=new ArrayList<Data>();
  4. //循环找出黑棋,判断此棋子的1横向 2纵向 3右捺 4左撇 是否有4子的情况,
  5. Pointer pointer;
  6. for (int i = 0; i <gamePanel.ROWS; i++) {
  7. for (int j = 0; j < gamePanel.COLS; j++) {
  8. pointer = gamePanel.points[i][j];
  9. if(pointer==null)continue;
  10. if(pointer.getQizi()==0){ //没有棋子则跳过
  11. continue;
  12. }
  13. //循环4个方向
  14. int dir=1;
  15. for (int k = 1; k <= 4; k++) {
  16. dir = k;
  17. Data data = getData(pointer, dir,1, gamePanel);
  18. if(data.getCount()!=-1&&data.getCount()!=0){ //0和-1 的过滤掉
  19. datas.add(data);
  20. }
  21. data = getData(pointer, dir, 2,gamePanel);
  22. if(data.getCount()!=-1&&data.getCount()!=0){ //0和-1 的过滤掉
  23. datas.add(data);
  24. }
  25. }
  26. }
  27. }
  28. //按权重分排序处理,从大到小
  29. Collections.sort(datas, new DataCount());
  30. /*for (int i = 0; i < datas.size(); i++) { System.out.println("----------"+datas.get(i).getCount()); }*/
  31. if(datas.size()>0){ //取第一个位置落子
  32. Data data = datas.get(0);
  33. Pointer p = gamePanel.points[data.getI()][data.getJ()];
  34. luozi(p, gamePanel, 1);
  35. return true;
  36. }
  37. return false;
  38. }

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

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

在这里插入图片描述

最后

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

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

发表评论

表情:
评论列表 (有 0 条评论,439人围观)

还没有评论,来说两句吧...

相关阅读

    相关 五子棋人机对战设计

    一、人机对战算法概述 人机对战属于一种弱人工智能算法,其核心是:当玩家落下一枚棋子后,计算出这枚棋子构成的所有棋型,找出威胁程度最大的棋型,并破解其产生的威胁。 五子棋中所

    相关 昨天今天明天

    在即将毕业之季,写下自己的昨天今天明天。 昨天: 四年凭着自己努力,来到了四川成都一所不被人所赞赏的普通学校西华大学。也许你们会提到,这还需要努力???简直太搞笑了!在大多

    相关 昨天今天,明天

       06年哦,对我来说,真是个动荡的一年,或者也可以说真是个丰富多彩的一年哦, 算算,总共工作过4家公司,当然,里面还包括给人做的兼职,(到现在还没有拿到报酬呢,不知道现在为