连连看核心算法与基本思想(附全部项目代码链接与代码详细注释)

简介: 连连看核心算法与基本思想(附全部项目代码链接与代码详细注释)

说明

对于数据结构和算法,我并不是很精通(真的很一般),因此在这里只是做一个自己的简单分享,其实从这次数据结构课设中看出了自己存在了许多在这方面的不足。我的代码还存在一些不足,比如得到两个拐点最短路径的方法我并不是采用广度优先算法,而是使用的深度优先。顺便演示一下最终的效果:

1.基本要求

  1. 生成游戏初始局面;
  2. 每次用户选择两个图形,如果图形能满足一定条件(如果两个图形一样,且这个两个图形直接存在少于 3 个弯的路径),则两个图形都能消掉。给定具有相同图形的任意两个格子,我们需要寻找这两个格子之间在转弯最少的情况下,经过格子数目最少的路径。如果这个最优路径的转弯数目少于 3,则这个两个格子可以消去;
  3. 判断游戏是否结束。如果所有图形全部消去,游戏结束;
  4. 判断死锁,当游戏玩家不可能消去任意两个图像的时候,游戏进入“死锁”状态。当游戏进入“死锁”状态,可以随机打乱局面,解除“死锁”。

2.思路分析(加入核心代码)

以下仅是我个人的思路

2.1 游戏初始化局面

  • 加载图片
  • 生成随机成对布局

2.2 两点是否可连

  • 先判断直连
/**
     * 判断是否可以直连
     *
     * @param x1 当前位置点的横坐标
     * @param y1 当前位置点的纵坐标
     * @param x2 目标位置点的横坐标
     * @param y2 目标位置点的纵坐标
     * @return 是否可连
     */
    public boolean isLineLink(int x1, int y1, int x2, int y2) {
        if (x1 == x2) {
            int minY = Math.min(y1, y2) + 1;
            int maxY = Math.max(y1, y2);
            while (minY < maxY) {
                if (map[minY][x1] != 0) {
                    return false;
                }
                minY++;
            }
            return true;
        } else if (y1 == y2) {
            int minX = Math.min(x1, x2) + 1;
            int maxX = Math.max(x1, x2);
            while (minX < maxX) {
                if (map[y2][minX] != 0) {
                    return false;
                }
                minX++;
            }
            return true;
        } else {
            return false;
        }
    }
  • 再判断是否折一次可连
/**
     * 判断是否可以通过一次折点连通
     *
     * @param x1 当前位置点的横坐标
     * @param y1 当前位置点的纵坐标
     * @param x2 目标位置点的横坐标
     * @param y2 目标位置点的纵坐标
     * @param g 画笔
     * @return 是否可连
     */
    public boolean isLinkByOne(int x1, int y1, int x2, int y2, Graphics g) {
        g.setColor(Color.RED);
        if (isLineLink(x1, y1, x1, y2) && map[y2][x1] == 0 && isLineLink(x1, y2, x2, y2)) {
            g.drawLine(x1 * 50 + 25, y1 * 50 + 25, x1 * 50 + 25, y2 * 50 + 25);
            g.drawLine(x1 * 50 + 25, y2 * 50 + 25, x2 * 50 + 25, y2 * 50 + 25);
            return true;
        } else if (isLineLink(x1, y1, x2, y1) && map[y1][x2] == 0 && isLineLink(x2, y1, x2, y2)) {
            g.drawLine(x1 * 50 + 25, y1 * 50 + 25, x2 * 50 + 25, y1 * 50 + 25);
            g.drawLine(x2 * 50 + 25, y1 * 50 + 25, x2 * 50 + 25, y2 * 50 + 25);
            return true;
        }
        return false;
    }
  • 最后判断图片是否可以两次可连 --> 深度优先搜索
/**
     * 深度优先搜索
     * 判断是否可以通过两次折点连通 - 如果存在,在此基础上得到最短路径
     *
     * @param x1 当前位置点的横坐标
     * @param y1 当前位置点的纵坐标
     * @param x2 目标位置点的横坐标
     * @param y2 目标位置点的纵坐标
     * @param inflectionPointNum 当前折点的次数
     * @param direction 当前方向:无(-1) 右(0) 下(1) 左(2) 右(3)
     * @param step 当前走的步数
     */
    public void isLinkByTwo(int x1, int y1, int x2, int y2, int inflectionPointNum, int direction, int step) {
        //发现折点次数达到三次就返回,表示路径不行
        if (inflectionPointNum == 3) {
            return;
        }
        //记录该点位置到路径数组中
        tempPath[step][0] = y1;
        tempPath[step][1] = x1;
        //查看是否到达目标点
        if (x1 == x2 && y1 == y2) {
            //说明找到了更短的,就将这个更短路径放入到最短路径数组中存储
            if (step < minStep) {
                minStep = step;
                for (int i = 0; i <= minStep; i++) {
                    minPath[i][0] = tempPath[i][0];
                    minPath[i][1] = tempPath[i][1];
                }
            }
            return;
        }
        //标记当前点已经访问
        isVisited[y1][x1] = 1;
        int tx;
        int ty;
        //尝试不同的方向
        for (int i = 0; i < 4; i++) {
            tx = x1 + dx[i];
            ty = y1 + dy[i];
            /*
                1.
                    tx >= 0 && tx < LENGTH && ty >= 0 && ty < LENGTH 保证不越界
                    map[ty][tx] == 0 保证是空白区
                    isVisited[ty][tx] == 0 保证是未访问的
                2.
                    tx == x2 && ty == y2 说明是目标点
             */
            if (tx >= 0 && tx < LENGTH && ty >= 0 && ty < LENGTH && map[ty][tx] == 0 && isVisited[ty][tx] == 0 || tx == x2 && ty == y2) {
                //标记即将查找的点为 已访问
                isVisited[ty][tx] = 1;
                if (direction == -1) {
                    //如果无方向,这是第一次深度搜索的状态
                    isLinkByTwo(tx, ty, x2, y2, inflectionPointNum, i, step + 1);
                } else if (i != direction) {
                    //如果即将走的点的方向与原来行走的点方向不一致
                    isLinkByTwo(tx, ty, x2, y2, inflectionPointNum + 1, i, step + 1);
                } else {
                    //如果方向一致:可以与第一个if的代码合并,但为了层次更为清晰,这里分开写
                    isLinkByTwo(tx, ty, x2, y2, inflectionPointNum, i, step + 1);
                }
                //访问完毕,回溯回来就标记为未访问,给后面的点来查找
                isVisited[ty][tx] = 0;
            }
        }
    }

广度优先搜索其实更好,但我有特别纠结却又不太好描述的问题导致我写到一般就没再继续用广度优先,总感觉对于 ”寻找这两个格子之间在转弯最少的情况下,经过格子数目最少的路径“ 有点问题,脑子绕不过来。

2.3 游戏是否结束

  • 用一个count全局变量来记录当前剩余的数目

2.4 判断死局

  • 对每个不为空的点进行深度优先搜索,如果该点返回true即找到有可连,就不再继续继续其它点的搜索
  • 判断一个地图是否为死局需要时间,而在这个过程中如果进行其它操作比如“重新开始游戏”就可能会弹出对话框上一张地图是死锁,而此时对于上一张是否为死锁是毫无意义的,因此我加入了一个地图版本号nowVersion,在每次更新地图时都会改变,值为当前系统时间 System.currentTimeMillis()
/**
     * 深度优先搜索
     * 搜索有没有值为value并与最初点可连的点
     *
     * @param x1 当前点的横坐标
     * @param y1 当前点的纵坐标
     * @param value 最初点的值
     * @param inflectionPointNum 拐点个数
     * @param direction 当前方向
     * @param step 当前步数
     * @return 是否找到
     */
    public boolean search(int x1, int y1, int value, int inflectionPointNum, int direction, int step) {
        if (inflectionPointNum == 3) {
            return false;
        }
        /*
            步数不为0是保证与刚开始的搜索不会冲突,否则刚已进入最初的搜索就会表明找到
         */
        if (value == runMap[y1][x1] && step != 0) {
            //说明找到了一条
            return true;
        }
        isVisitedForThread[y1][x1] = 1;
        int tx;
        int ty;
        boolean isFind = false;
        //尝试不同的方向
        for (int i = 0; i < 4 && !isFind; i++) {
            tx = x1 + dx[i];
            ty = y1 + dy[i];
            /*
                tx >= 0 && tx < LENGTH && ty >= 0 && ty < LENGTH && isVisitedForThread[ty][tx] == 0 不越界并且未被访问
                runMap[ty][tx] == 0 临时地图的该坐标为null则可走
                value == runMap[ty][tx] 临时地图的该坐标就为目标相等值则可走
             */
            if (tx >= 0 && tx < LENGTH && ty >= 0 && ty < LENGTH && isVisitedForThread[ty][tx] == 0 && (runMap[ty][tx] == 0 || value == runMap[ty][tx])) {
                isVisitedForThread[ty][tx] = 1;
                if (direction == -1) {
                    isFind = search(tx, ty, value, inflectionPointNum, i, step + 1);
                } else if (i != direction) {
                    isFind = search(tx, ty, value, inflectionPointNum + 1, i, step + 1);
                } else {
                    isFind = search(tx, ty, value, inflectionPointNum, i, step + 1);
                }
                isVisitedForThread[ty][tx] = 0;
            }
        }
        return isFind;
    }

3.注意事项与全部代码

  • 我会把项目(包括代码,图片以及背景音乐代码)放在我的百度网盘:https://pan.baidu.com/s/1ykr53VP1zvfvk9VHG413yA?pwd=1314
  • 图片你可以自定义,但需要是 50*50 大小的图片

  • 代码注释比较详细,可以明确知道某段代码是在做什么

👇 不包括音乐代码的全部代码

package com.fox.link;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Random;
/**
 * @author 狐狸半面添
 * @create 2022-12-02 1:18
 */
public class Game {
    private static GamePanel gamePanel;
    public static void main(String[] args) throws IOException {
        //画一个窗体
        JFrame gameFrame = new JFrame("连连看");
        //设置宽和高
        gameFrame.setSize(GamePanel.LENGTH * 51, GamePanel.LENGTH * 54);
        //设置一个菜单条
        JMenuBar menuBar = new JMenuBar();
        //设置一个菜单组件
        JMenu jMenu = new JMenu("设置");
        //设置菜单项
        JMenuItem restartItem = new JMenuItem("重新开始");
        //增加重新开始监听事件 --> 重新初始化地图
        restartItem.addActionListener(e-> {
                //初始化地图
                gamePanel.initMap();
                //刷新版本号
                gamePanel.nowVersion = System.currentTimeMillis();
                //刷新页面
                gamePanel.repaint();
        });
        //设置菜单项 - 退出
        JMenuItem exitItem = new JMenuItem("退出");
        exitItem.addActionListener(e-> {
                int option = JOptionPane.showConfirmDialog(gameFrame, "您确认退出吗?", "确认框", JOptionPane.YES_NO_OPTION);
                if (option == JOptionPane.YES_OPTION) {
                    //选择了确定则退出程序
                    System.exit(0);
                }
        });
        //将组件整合
        jMenu.add(restartItem);
        jMenu.add(exitItem);
        menuBar.add(jMenu);
        gameFrame.setJMenuBar(menuBar);
        //往窗口添加一个自定义容器
        gamePanel = new GamePanel(gameFrame);
        gameFrame.add(gamePanel);
        //开启线程监听当前是否死局
        new Thread(gamePanel).start();
        //设置窗口的图标/logo
        gameFrame.setIconImage(ImageIO.read(new File(Game.class.getResource("/").getPath()+"img/logo.png")));
        //展示窗体
        gameFrame.setVisible(true);
        //点击 x 即可退出程序
        gameFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //设置窗口大小不可改变
        gameFrame.setResizable(false);
        //设置窗口位置到屏幕中间
        gameFrame.setLocationRelativeTo(null);
    }
}
class GamePanel extends JPanel implements Runnable {
    /**
     * 设置地图长度
     */
    public static final int LENGTH = 14;
    /**
     * 设置随机范围 --> 随机的图片数目
     */
    private final int RANDOM_NUM = 8;
    /**
     * 存储加载的RANDOM_NUM张图片
     */
    private Image[] images;
    /**
     * 地图
     */
    private int[][] map;
    /**
     * 临时地图去判断死局
     */
    private int[][] runMap;
    /**
     * 记录被选中的点的坐标
     */
    private Point selectedPoint;
    /**
     * 是否被选中,默认为 false
     */
    private boolean isSelected;
    /**
     * 保存当前地图的访问信息
     */
    private int[][] isVisited;
    /**
     * 保存临时地图的访问信息
     */
    private int[][] isVisitedForThread;
    /**
     * 保存访问路径
     * [i][0] 是横坐标
     * [i][1] 是纵坐标
     */
    private final int[][] tempPath = new int[100][2];
    /**
     * 保存最短的访问路径
     * [i][0] 是纵坐标
     * [i][1] 是横坐标
     */
    private final int[][] minPath = new int[100][2];
    /**
     * dx是保存四个方向在横坐标上的位置
     * 方向数组:对应 右 下 左 上
     */
    private final int[] dx = {1, 0, -1, 0};
    /**
     * dy是保存四个方向在横坐标上的位置
     * 方向数组:对应 右 下 左 上
     */
    private final int[] dy = {0, 1, 0, -1};
    /**
     * 记录最小步数
     */
    private int minStep;
    /**
     * 临时变量
     */
    private int temp;
    /**
     * 当前地图上剩余的图片数目
     */
    private int count;
    private final JFrame parentFrame;
    /**
     * 当前版本号
     * 判断一个地图是否为死局需要时间,而在这个过程中如果进行其它操作比如“重新开始游戏”就可能会显示上一张地图是死锁
     * 这是没有必要的,因此加个版本号进行判断
     */
    public long nowVersion;
    /**
     * 初始化画板
     *
     * @param parentFrame 父窗口
     * @throws IOException 异常
     */
    public GamePanel(JFrame parentFrame) throws IOException {
        //设置父节点
        this.parentFrame = parentFrame;
        //获取类路径
        String imgBasePath = this.getClass().getResource("/").getPath() + "img/";
        //加载图片存入数组
        images = new Image[RANDOM_NUM + 1];
        for (int i = 0; i <= RANDOM_NUM; i++) {
            images[i] = ImageIO.read(new File(imgBasePath + i + ".jpg"));
        }
        //初始化地图
        initMap();
        //创建选择点
        selectedPoint = new Point();
        //连连看监听
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                //获取当前的点击坐标
                int x = e.getX() / 50;
                int y = e.getY() / 50;
                //如果超出真实地图范围则取消下一步操作
                if (x >= LENGTH || y >= LENGTH) {
                    return;
                }
                Graphics g = getGraphics();
                g.setColor(Color.RED);
                //如果不是空,就可以 选中/连接 判断
                if (map[y][x] != 0) {
                    //如果当前没有选中的图像
                    if (!isSelected) {
                        //就设置当前点击到的图像为选中图像
                        isSelected = true;
                        selectedPoint.x = x;
                        selectedPoint.y = y;
                        g.drawRect(50 * x, 50 * y, 50, 50);
                    } else if (selectedPoint.x == x && selectedPoint.y == y) {
                        //如果之前选中了图片,现在是重复点击,就取消选中图片的选中
                        isSelected = false;
                        repaint();
                    } else {
                        //进入else说明选中了两张不同位置的图片
                        //如果两张图片不相同
                        if (map[selectedPoint.y][selectedPoint.x] != map[y][x]) {
                            //就取消已选中的图片的选中
                            isSelected = false;
                            repaint();
                        } else {
                            //进入else说明要去真正判断是否可连了
                            //如果可以直连,即无折点
                            if (isLineLink(x, y, selectedPoint.x, selectedPoint.y)) {
                                //就画线连接
                                g.drawRect(50 * x, 50 * y, 50, 50);
                                g.drawLine(50 * x + 25, 50 * y + 25, selectedPoint.x * 50 + 25, selectedPoint.y * 50 + 25);
                                try {
                                    //暂停1s,主要是为了能看清画线
                                    Thread.sleep(1000);
                                } catch (InterruptedException ex) {
                                    ex.printStackTrace();
                                }
                                //将两个点的坐标标记为0即空白了
                                map[y][x] = 0;
                                map[selectedPoint.y][selectedPoint.x] = 0;
                                //减少当前地图剩余图片的数量
                                count -= 2;
                                //修改版本号
                                nowVersion = System.currentTimeMillis();
                            } else if (isLinkByOne(x, y, selectedPoint.x, selectedPoint.y, g)) {
                                //如果可以在只经过一个折点的情况下直连
                                g.drawRect(50 * x, 50 * y, 50, 50);
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException ex) {
                                    throw new RuntimeException(ex);
                                }
                                map[y][x] = 0;
                                map[selectedPoint.y][selectedPoint.x] = 0;
                                count -= 2;
                                nowVersion = System.currentTimeMillis();
                            } else {
                                //初始化深度优先搜索需要用到的访问情况数组与最短路径
                                fillZeroAndSetMinPath();
                                //进行深度优先搜索  --> 找到了基于两个折点的最短路径就会改变 minStep
                                isLinkByTwo(x, y, selectedPoint.x, selectedPoint.y, 0, -1, 0);
                                //如果不是 Integer.MAX_VALUE,说明找到了最大值
                                if (minStep != Integer.MAX_VALUE) {
                                    int ky = minPath[0][0], kx = minPath[0][1];
                                    //进行路径的显示连接在窗口上
                                    for (int i = 1; i <= minStep; i++) {
                                        g.drawLine(50 * kx + 25, 50 * ky + 25, minPath[i][1] * 50 + 25, minPath[i][0] * 50 + 25);
                                        ky = minPath[i][0];
                                        kx = minPath[i][1];
                                    }
                                    try {
                                        Thread.sleep(1000);
                                    } catch (InterruptedException ex) {
                                        throw new RuntimeException(ex);
                                    }
                                    map[y][x] = 0;
                                    map[selectedPoint.y][selectedPoint.x] = 0;
                                    count -= 2;
                                    nowVersion = System.currentTimeMillis();
                                }
                            }
                            //不管是否可连,都需要取消当前选中
                            isSelected = false;
                            //重绘画板
                            repaint();
                            //发现当前无剩余图片就弹出提醒:游戏结束
                            if (count == 0) {
                                JOptionPane.showMessageDialog(parentFrame, "游戏结束!", "success", JOptionPane.INFORMATION_MESSAGE);
                            }
                        }
                    }
                }
            }
        });
    }
    /**
     * 绘制地图
     *
     * @param g  the <code>Graphics</code> context in which to paint
     */
    @Override
    public void paint(Graphics g) {
        for (int i = 0; i < LENGTH; i++) {
            for (int j = 0; j < LENGTH; j++) {
                g.drawImage(images[map[i][j]], 50 * j, 50 * i, 50, 50, null);
            }
        }
    }
    /**
     * 判断是否可以直连
     *
     * @param x1 当前位置点的横坐标
     * @param y1 当前位置点的纵坐标
     * @param x2 目标位置点的横坐标
     * @param y2 目标位置点的纵坐标
     * @return 是否可连
     */
    public boolean isLineLink(int x1, int y1, int x2, int y2) {
        if (x1 == x2) {
            int minY = Math.min(y1, y2) + 1;
            int maxY = Math.max(y1, y2);
            while (minY < maxY) {
                if (map[minY][x1] != 0) {
                    return false;
                }
                minY++;
            }
            return true;
        } else if (y1 == y2) {
            int minX = Math.min(x1, x2) + 1;
            int maxX = Math.max(x1, x2);
            while (minX < maxX) {
                if (map[y2][minX] != 0) {
                    return false;
                }
                minX++;
            }
            return true;
        } else {
            return false;
        }
    }
    /**
     * 判断是否可以通过一次折点连通
     *
     * @param x1 当前位置点的横坐标
     * @param y1 当前位置点的纵坐标
     * @param x2 目标位置点的横坐标
     * @param y2 目标位置点的纵坐标
     * @param g 画笔
     * @return 是否可连
     */
    public boolean isLinkByOne(int x1, int y1, int x2, int y2, Graphics g) {
        g.setColor(Color.RED);
        if (isLineLink(x1, y1, x1, y2) && map[y2][x1] == 0 && isLineLink(x1, y2, x2, y2)) {
            g.drawLine(x1 * 50 + 25, y1 * 50 + 25, x1 * 50 + 25, y2 * 50 + 25);
            g.drawLine(x1 * 50 + 25, y2 * 50 + 25, x2 * 50 + 25, y2 * 50 + 25);
            return true;
        } else if (isLineLink(x1, y1, x2, y1) && map[y1][x2] == 0 && isLineLink(x2, y1, x2, y2)) {
            g.drawLine(x1 * 50 + 25, y1 * 50 + 25, x2 * 50 + 25, y1 * 50 + 25);
            g.drawLine(x2 * 50 + 25, y1 * 50 + 25, x2 * 50 + 25, y2 * 50 + 25);
            return true;
        }
        return false;
    }
    /**
     * 深度优先搜索
     * 判断是否可以通过两次折点连通 - 如果存在,在此基础上得到最短路径
     *
     * @param x1 当前位置点的横坐标
     * @param y1 当前位置点的纵坐标
     * @param x2 目标位置点的横坐标
     * @param y2 目标位置点的纵坐标
     * @param inflectionPointNum 当前折点的次数
     * @param direction 当前方向:无(-1) 右(0) 下(1) 左(2) 右(3)
     * @param step 当前走的步数
     */
    public void isLinkByTwo(int x1, int y1, int x2, int y2, int inflectionPointNum, int direction, int step) {
        //发现折点次数达到三次就返回,表示路径不行
        if (inflectionPointNum == 3) {
            return;
        }
        //记录该点位置到路径数组中
        tempPath[step][0] = y1;
        tempPath[step][1] = x1;
        //查看是否到达目标点
        if (x1 == x2 && y1 == y2) {
            //说明找到了更短的,就将这个更短路径放入到最短路径数组中存储
            if (step < minStep) {
                minStep = step;
                for (int i = 0; i <= minStep; i++) {
                    minPath[i][0] = tempPath[i][0];
                    minPath[i][1] = tempPath[i][1];
                }
            }
            return;
        }
        //标记当前点已经访问
        isVisited[y1][x1] = 1;
        int tx;
        int ty;
        //尝试不同的方向
        for (int i = 0; i < 4; i++) {
            tx = x1 + dx[i];
            ty = y1 + dy[i];
            /*
                1.
                    tx >= 0 && tx < LENGTH && ty >= 0 && ty < LENGTH 保证不越界
                    map[ty][tx] == 0 保证是空白区
                    isVisited[ty][tx] == 0 保证是未访问的
                2.
                    tx == x2 && ty == y2 说明是目标点
             */
            if (tx >= 0 && tx < LENGTH && ty >= 0 && ty < LENGTH && map[ty][tx] == 0 && isVisited[ty][tx] == 0 || tx == x2 && ty == y2) {
                //标记即将查找的点为 已访问
                isVisited[ty][tx] = 1;
                if (direction == -1) {
                    //如果无方向,这是第一次深度搜索的状态
                    isLinkByTwo(tx, ty, x2, y2, inflectionPointNum, i, step + 1);
                } else if (i != direction) {
                    //如果即将走的点的方向与原来行走的点方向不一致
                    isLinkByTwo(tx, ty, x2, y2, inflectionPointNum + 1, i, step + 1);
                } else {
                    //如果方向一致:可以与第一个if的代码合并,但为了层次更为清晰,这里分开写
                    isLinkByTwo(tx, ty, x2, y2, inflectionPointNum, i, step + 1);
                }
                //访问完毕,回溯回来就标记为未访问,给后面的点来查找
                isVisited[ty][tx] = 0;
            }
        }
    }
    /**
     * 初始化最短步数和访问数组(都标记为未访问)
     */
    public void fillZeroAndSetMinPath() {
        for (int i = 0; i < LENGTH; i++) {
            Arrays.fill(isVisited[i], 0);
        }
        minStep = Integer.MAX_VALUE;
    }
    /**
     * 初始化判断死局的临时地图的访问数组中的位置都为未访问
     */
    public void fillZeroForThread() {
        for (int i = 0; i < LENGTH; i++) {
            Arrays.fill(isVisitedForThread[i], 0);
        }
    }
    /**
     * 交换地图上的两个点的图片
     *
     * @param x1 第一个点的横坐标
     * @param y1 第一个点的纵坐标
     * @param x2 第二个点的横坐标
     * @param y2 第二个点的横坐标
     * @param map 地图
     */
    public void swap(int x1, int y1, int x2, int y2, int[][] map) {
        temp = map[y1][x1];
        map[y1][x1] = map[y2][x2];
        map[y2][x2] = temp;
    }
    /**
     * 初始化地图
     */
    public void initMap() {
        //初始化 10 * 10 的游戏界面,初始值为 0
        map = new int[LENGTH][LENGTH];
        runMap = new int[LENGTH][LENGTH];
        isVisited = new int[LENGTH][LENGTH];
        isVisitedForThread = new int[LENGTH][LENGTH];
        Random r = new Random();
        int randomNum;
        //随机数成对连续布局
        for (int i = 1; i <= LENGTH - 2; i++) {
            for (int j = 1; j <= LENGTH - 2; j++) {
                randomNum = r.nextInt(RANDOM_NUM) + 1;
                map[i][j] = randomNum;
                map[i][++j] = randomNum;
            }
        }
        //打乱图片
        shuffle(map);
        //重置当前剩余图片数量
        count = (LENGTH - 2) * (LENGTH - 2);
        //取消选中
        isSelected = false;
        //修改版本号
        nowVersion = System.currentTimeMillis();
    }
    /**
     * 用于监听地图是否死局
     */
    @Override
    public void run() {
        //死循环判断,除非程序退出
        while (true) {
            try {
                //每隔五秒判断一次地图状态
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //获取到当前的版本号
            long version = nowVersion;
            //拷贝一份当前地图放入到临时地图
            for (int i = 1; i <= LENGTH - 2; i++) {
                for (int j = 1; j <= LENGTH - 2; j++) {
                    runMap[i][j] = map[i][j];
                }
            }
            //查看是否有可连点
            boolean isFind = pointSearch();
            //如果没有找到并且地图上还有点并且版本号还是原来的说明是原地图,就显示死局
            if (!isFind && count != 0 && version == nowVersion) {
                JOptionPane.showMessageDialog(parentFrame, "当前死局,将进行洗牌", "提醒", JOptionPane.PLAIN_MESSAGE);
                //进行洗牌,直到有可连点
                while (!isFind&& version == nowVersion) {
                    //洗牌
                    shuffle(map);
                    //获取临时地图
                    for (int i = 1; i <= LENGTH - 2; i++) {
                        for (int j = 1; j <= LENGTH - 2; j++) {
                            runMap[i][j] = map[i][j];
                        }
                    }
                    isFind = pointSearch();
                }
                //洗牌完毕,重绘地图
                if(version == nowVersion) {
                    repaint();
                    nowVersion = System.currentTimeMillis();
                }
            }
        }
    }
    /**
     * 对 map 地图进行洗牌,随机交换打乱位置
     * @param map 地图
     */
    public void shuffle(int[][] map) {
        int tx, ty;
        int num = 0;
        Random r = new Random();
        while (num++ < LENGTH) {
            for (int y = 1; y <= LENGTH - 2; y++) {
                for (int x = 1; x <= LENGTH - 2; x++) {
                    if(map[y][x]!=0) {
                        tx = r.nextInt(LENGTH - 2) + 1;
                        ty = r.nextInt(LENGTH - 2) + 1;
                        if(map[ty][tx]!=0) {
                            swap(tx, ty, x, y, map);
                        }
                    }
                }
            }
        }
    }
    /**
     * 搜索是否存在点可连
     *
     * @return 为true说明存在,false不存在
     */
    public boolean pointSearch() {
        boolean isFind = false;
        for (int i = 1; i <= LENGTH - 2; i++) {
            for (int j = 1; j <= LENGTH - 2; j++) {
                if (runMap[i][j] != 0) {
                    fillZeroForThread();
                    isFind = search(j, i, runMap[i][j], 0, -1, 0);
                }
                if (isFind) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * 深度优先搜索
     * 搜索有没有值为value并与最初点可连的点
     *
     * @param x1 当前点的横坐标
     * @param y1 当前点的纵坐标
     * @param value 最初点的值
     * @param inflectionPointNum 拐点个数
     * @param direction 当前方向
     * @param step 当前步数
     * @return 是否找到
     */
    public boolean search(int x1, int y1, int value, int inflectionPointNum, int direction, int step) {
        if (inflectionPointNum == 3) {
            return false;
        }
        /*
            步数不为0是保证与刚开始的搜索不会冲突,否则刚已进入最初的搜索就会表明找到
         */
        if (value == runMap[y1][x1] && step != 0) {
            //说明找到了一条
            return true;
        }
        isVisitedForThread[y1][x1] = 1;
        int tx;
        int ty;
        boolean isFind = false;
        //尝试不同的方向
        for (int i = 0; i < 4 && !isFind; i++) {
            tx = x1 + dx[i];
            ty = y1 + dy[i];
            /*
                tx >= 0 && tx < LENGTH && ty >= 0 && ty < LENGTH && isVisitedForThread[ty][tx] == 0 不越界并且未被访问
                runMap[ty][tx] == 0 临时地图的该坐标为null则可走
                value == runMap[ty][tx] 临时地图的该坐标就为目标相等值则可走
             */
            if (tx >= 0 && tx < LENGTH && ty >= 0 && ty < LENGTH && isVisitedForThread[ty][tx] == 0 && (runMap[ty][tx] == 0 || value == runMap[ty][tx])) {
                isVisitedForThread[ty][tx] = 1;
                if (direction == -1) {
                    isFind = search(tx, ty, value, inflectionPointNum, i, step + 1);
                } else if (i != direction) {
                    isFind = search(tx, ty, value, inflectionPointNum + 1, i, step + 1);
                } else {
                    isFind = search(tx, ty, value, inflectionPointNum, i, step + 1);
                }
                isVisitedForThread[ty][tx] = 0;
            }
        }
        return isFind;
    }
}


相关文章
|
1月前
|
存储 算法 程序员
C 语言递归算法:以简洁代码驾驭复杂逻辑
C语言递归算法简介:通过简洁的代码实现复杂的逻辑处理,递归函数自我调用解决分层问题,高效而优雅。适用于树形结构遍历、数学计算等领域。
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
67 1
|
2月前
|
存储 缓存 算法
通过优化算法和代码结构来提升易语言程序的执行效率
通过优化算法和代码结构来提升易语言程序的执行效率
|
2月前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
2月前
|
算法 测试技术 开发者
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗;代码审查通过检查源代码发现潜在问题,提高代码质量和团队协作效率。本文介绍了一些实用的技巧和工具,帮助开发者提升开发效率。
53 3
|
2月前
|
分布式计算 Java 开发工具
阿里云MaxCompute-XGBoost on Spark 极限梯度提升算法的分布式训练与模型持久化oss的实现与代码浅析
本文介绍了XGBoost在MaxCompute+OSS架构下模型持久化遇到的问题及其解决方案。首先简要介绍了XGBoost的特点和应用场景,随后详细描述了客户在将XGBoost on Spark任务从HDFS迁移到OSS时遇到的异常情况。通过分析异常堆栈和源代码,发现使用的`nativeBooster.saveModel`方法不支持OSS路径,而使用`write.overwrite().save`方法则能成功保存模型。最后提供了完整的Scala代码示例、Maven配置和提交命令,帮助用户顺利迁移模型存储路径。
|
3月前
|
缓存 分布式计算 监控
优化算法和代码需要注意什么
【10月更文挑战第20天】优化算法和代码需要注意什么
36 0
|
13天前
|
机器学习/深度学习 算法
基于改进遗传优化的BP神经网络金融序列预测算法matlab仿真
本项目基于改进遗传优化的BP神经网络进行金融序列预测,使用MATLAB2022A实现。通过对比BP神经网络、遗传优化BP神经网络及改进遗传优化BP神经网络,展示了三者的误差和预测曲线差异。核心程序结合遗传算法(GA)与BP神经网络,利用GA优化BP网络的初始权重和阈值,提高预测精度。GA通过选择、交叉、变异操作迭代优化,防止局部收敛,增强模型对金融市场复杂性和不确定性的适应能力。
146 80
|
1天前
|
机器学习/深度学习 数据采集 算法
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
本项目基于MATLAB2022a实现时间序列预测,采用CNN-GRU-SAM网络结构。卷积层提取局部特征,GRU层处理长期依赖,自注意力机制捕捉全局特征。完整代码含中文注释和操作视频,运行效果无水印展示。算法通过数据归一化、种群初始化、适应度计算、个体更新等步骤优化网络参数,最终输出预测结果。适用于金融市场、气象预报等领域。
基于GA遗传优化的CNN-GRU-SAM网络时间序列回归预测算法matlab仿真
|
1天前
|
算法
基于龙格库塔算法的锅炉单相受热管建模与matlab数值仿真
本设计基于龙格库塔算法对锅炉单相受热管进行建模与MATLAB数值仿真,简化为喷水减温器和末级过热器组合,考虑均匀传热及静态烟气处理。使用MATLAB2022A版本运行,展示自编与内置四阶龙格库塔法的精度对比及误差分析。模型涉及热传递和流体动力学原理,适用于优化锅炉效率。

热门文章

最新文章