文章目录
0.说明1.基本要求2.思路分析(加入核心代码)2.1 游戏初始化局面2.2 两点是否可连2.3 游戏是否结束2.4 判断死局
3.注意事项与全部代码
0.说明
对于数据结构和算法,我并不是很精通(真的很一般),因此在这里只是做一个自己的简单分享,其实从这次数据结构课设中看出了自己存在了许多在这方面的不足。我的代码还存在一些不足,比如得到两个拐点最短路径的方法我并不是采用广度优先算法,而是使用的深度优先。顺便演示一下最终的效果:
1.基本要求
生成游戏初始局面;每次用户选择两个图形,如果图形能满足一定条件(如果两个图形一样,且这个两个图形直接存在少于 3 个弯的路径),则两个图形都能消掉。给定具有相同图形的任意两个格子,我们需要寻找这两个格子之间在转弯最少的情况下,经过格子数目最少的路径。如果这个最优路径的转弯数目少于 3,则这个两个格子可以消去;判断游戏是否结束。如果所有图形全部消去,游戏结束;判断死锁,当游戏玩家不可能消去任意两个图像的时候,游戏进入“死锁”状态。当游戏进入“死锁”状态,可以随机打乱局面,解除“死锁”。
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 = 0 && tx = 0 && ty 重新初始化地图
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 = 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 |