2026/1/12 20:28:37
网站建设
项目流程
快递建站收费标准,做私活的网站,机关门户网站 建设 方案,pageadmin的最新版本1、演示视频 基于Java Swing的拼图小游戏2、项目截图 三、设计说明
3.1 整体架构设计
项目采用单窗体架构#xff0c;核心类为PuzzleGame#xff08;继承JFrame#xff09;#xff0c;包含以下核心模块#xff1a;
窗口初始化模块#xff1a;设置窗体大小、标题、布局…1、演示视频基于Java Swing的拼图小游戏2、项目截图三、设计说明3.1 整体架构设计项目采用单窗体架构核心类为PuzzleGame继承JFrame包含以下核心模块窗口初始化模块设置窗体大小、标题、布局、关闭行为等基础属性图片处理模块负责加载默认图片/自定义图片并对图片进行缩放、裁剪处理拼图数据模块维护拼图块的存储、面板位置映射、空位索引等核心数据界面组件模块构建拼图绘制面板、按钮面板、提示标签等界面元素事件处理模块处理鼠标点击事件拼图移动、按钮点击事件功能触发算法模块实现拼图打乱、可解性保证、胜利判断等核心算法。3.2 数据结构设计BufferedImage[] blockImages按原始位置存储切割后的拼图块图片索引对应拼图块的原始位置int[] panelBlockMap面板位置到原始块索引的映射数组panelBlockMap[i]表示面板第i个位置显示的是第panelBlockMap[i]个原始拼图块int emptyPanelIndex记录空位在面板中的索引位置默认值为拼图总块数-1最后一个位置BufferedImage originalImage存储原始的完整图片作为拼图块切割的数据源。3.3 界面布局设计采用BorderLayout布局管理器划分窗体区域NORTH区域放置操作提示标签JLabel显示核心操作提示CENTER区域放置拼图绘制面板自定义JPanel作为核心游戏区域SOUTH区域放置按钮面板JPanel采用FlowLayout布局排列功能按钮。3.4 图片处理设计为避免图片变形采用“保持宽高比居中裁剪”的处理策略计算图片缩放比例取宽、高分别与目标尺寸的比值中的较大值确保缩放后的图片能覆盖目标尺寸按比例缩放图片得到缩放后的图片从缩放后的图片中心裁剪出与目标尺寸一致的正方形区域作为拼图的原始图片。四、算法说明4.1 拼图可解性算法核心拼图的可解性是游戏的核心前提本项目采用“从胜利状态出发通过合法移动打乱”的策略确保拼图始终可解具体原理如下合法移动的可逆性拼图的合法移动块与空位交换是可逆的。若从胜利状态S0执行一系列合法移动得到状态S1则从S1执行反向合法移动可回到S0打乱逻辑从胜利状态开始重复1000次“随机获取空位的相邻索引交换该索引与空位的拼图块”操作最终得到的拼图状态必然可解。补充3×3拼图的可解性数学判定条件备用参考将拼图块除空位外按行优先顺序排列统计逆序数前面的数比当前数大的情况总数当拼图行数为奇数时逆序数为偶数的拼图状态可解当拼图行数为偶数时逆序数与空位所在行从下往上数的奇偶性相同则可解。4.2 拼图移动算法处理用户点击拼图块的移动逻辑步骤如下边界校验判断用户点击的坐标是否在拼图面板区域内若不在则直接返回索引计算根据点击坐标计算对应的面板索引clickPanelIndex 行号×列数 列号空位过滤若点击的索引是空位索引则直接返回不处理相邻判断获取点击索引的上下左右相邻索引判断空位索引是否在其中块交换若空位在相邻索引中交换点击索引与空位索引在panelBlockMap中的值并更新空位索引重绘面板刷新拼图面板显示移动后的状态并执行胜利判断。4.3 胜利判断算法为确保胜利判断的准确性采用双重校验逻辑遍历panelBlockMap数组验证每个位置的索引值是否等于该位置的面板索引即panelBlockMap[i] i验证空位索引是否等于拼图总块数-1即空位处于最后一个位置若上述两个条件均满足则判定为胜利弹出胜利提示框。4.4 一键还原算法直接恢复拼图的初始状态步骤如下遍历panelBlockMap数组将每个位置的值设置为其面板索引panelBlockMap[i] i将空位索引重置为拼图总块数-1重绘面板显示胜利状态不弹出胜利提示框区分手动还原与一键还原。五、测试说明5.1 测试环境操作系统Windows 10/11、Linux Ubuntu 20.04、Mac OS 12JDK版本JDK 8、JDK 11、JDK 17兼容所有JDK8及以上版本开发工具IntelliJ IDEA 2022、Eclipse 2022。5.2 功能测试用例测试项测试步骤预期结果默认图片加载1. 在程序运行路径下放default.jpg2. 启动程序。程序加载default.jpg作为拼图背景无报错。自定义图片选择1. 点击“选择图片”按钮2. 选择本地一张PNG图片3. 确认选择。程序加载所选图片裁剪缩放后作为拼图背景。拼图移动1. 启动程序拼图处于打乱状态2. 点击空位相邻的拼图块。点击的拼图块移动到空位空位更新到原块位置。胜利判断1. 逐步移动拼图块到原始位置2. 所有块归位且空位在最后。弹出“恭喜你拼图完成”提示框。重置拼图1. 点击“重置拼图”按钮。拼图被重新打乱保持可解性。一键还原1. 拼图处于打乱状态2. 点击“一键还原”按钮。拼图直接恢复到胜利状态无提示框。异常处理1. 删除运行路径下的default.jpg2. 启动程序。程序加载系统图标作为背景无报错。5.3 性能测试拼图打乱性能执行1000次随机合法移动耗时小于100ms无明显卡顿图片处理性能处理1920×1080分辨率的图片耗时小于500ms界面无卡顿界面刷新性能拼图移动后面板重绘耗时小于10ms视觉流畅。六、关键代码6.1 图片加载与处理核心代码负责加载默认图片优先default.jpg并进行缩放裁剪处理/** * 初始化默认图片优先加载当前路径下的default.jpg失败则使用兜底方案 */ private void initDefaultImage() { // 1. 尝试加载当前路径下的default.jpg File defaultImageFile new File(default.jpg); if (defaultImageFile.exists() !defaultImageFile.isDirectory()) { try { BufferedImage temp ImageIO.read(defaultImageFile); // 缩放并裁剪图片到面板尺寸保持宽高比 originalImage resizeAndCropImage(temp, PANEL_SIZE, PANEL_SIZE); return; // 加载成功直接返回 } catch (IOException e) { // 图片读取失败如损坏执行兜底逻辑 JOptionPane.showMessageDialog(this, default.jpg图片损坏将使用默认图标替代, 提示, JOptionPane.INFORMATION_MESSAGE); } } // 2. 兜底方案使用系统图标或纯色图片 try { // 获取系统信息图标 Icon icon UIManager.getIcon(OptionPane.informationIcon); // 将Icon绘制到BufferedImage BufferedImage temp new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g2d temp.createGraphics(); icon.paintIcon(null, g2d, 0, 0); g2d.dispose(); // 缩放并裁剪图片到面板尺寸保持宽高比 originalImage resizeAndCropImage(temp, PANEL_SIZE, PANEL_SIZE); } catch (Exception e) { // 极端情况系统图标也获取失败生成纯色默认图片 originalImage new BufferedImage(PANEL_SIZE, PANEL_SIZE, BufferedImage.TYPE_INT_RGB); Graphics g originalImage.getGraphics(); g.setColor(new Color(150, 200, 250)); g.fillRect(0, 0, PANEL_SIZE, PANEL_SIZE); g.dispose(); } } /** * 缩放并裁剪图片保持宽高比裁剪中心正方形区域 * param image 原始图片 * param targetSize 目标尺寸正方形的边长 * return 处理后的图片 */ private BufferedImage resizeAndCropImage(BufferedImage image, int targetSize, int targetSize2) { int width image.getWidth(); int height image.getHeight(); // 1. 计算缩放比例取宽和高中的较大比例确保图片能覆盖目标尺寸 double scale Math.max((double) targetSize / width, (double) targetSize / height); int scaledWidth (int) (width * scale); int scaledHeight (int) (height * scale); // 2. 缩放图片 BufferedImage scaledImage new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_RGB); Graphics2D g2d scaledImage.createGraphics(); g2d.drawImage(image, 0, 0, scaledWidth, scaledHeight, null); g2d.dispose(); // 3. 裁剪中心的正方形区域 int cropX (scaledWidth - targetSize) / 2; int cropY (scaledHeight - targetSize) / 2; // 安全裁剪避免坐标为负数 cropX Math.max(cropX, 0); cropY Math.max(cropY, 0); // 确保裁剪区域不超出图片边界 int cropWidth Math.min(targetSize, scaledWidth - cropX); int cropHeight Math.min(targetSize, scaledHeight - cropY); return scaledImage.getSubimage(cropX, cropY, cropWidth, cropHeight); }6.2 拼图打乱与可解性保证核心代码从胜利状态出发通过随机合法移动打乱拼图确保可解性/** * 打乱拼图顺序通过随机移动空位相邻块实现确保有解 */ private void shufflePuzzle() { Random random new Random(); // 执行1000次随机移动充分打乱 for (int i 0; i 1000; i) { int[] adjacentIndices getAdjacentIndices(emptyPanelIndex); if (adjacentIndices.length 0) { // 随机选择一个相邻块与空位交换 int randomIndex adjacentIndices[random.nextInt(adjacentIndices.length)]; swapBlocks(randomIndex, emptyPanelIndex, false); } } } /** * 获取指定面板索引的相邻索引上下左右 * param panelIndex 面板位置索引 * return 相邻的面板索引数组 */ private int[] getAdjacentIndices(int panelIndex) { int row panelIndex / COLS; int col panelIndex % COLS; java.util.List adjacentList new java.util.ArrayList(); // 上行号大于0 if (row 0) { adjacentList.add(panelIndex - COLS); } // 下行号小于总行数-1 if (row ROWS - 1) { adjacentList.add(panelIndex COLS); } // 左列号大于0 if (col 0) { adjacentList.add(panelIndex - 1); } // 右列号小于总列数-1 if (col COLS - 1) { adjacentList.add(panelIndex 1); } // 转换为数组返回 int[] result new int[adjacentList.size()]; for (int i 0; i result.length; i) { result[i] adjacentList.get(i); } return result; } /** * 交换两个面板位置的块 * param index1 第一个面板索引 * param index2 第二个面板索引 * param needCheckWin 是否需要检查胜利 */ private void swapBlocks(int index1, int index2, boolean needCheckWin) { // 交换面板映射的块索引 int temp panelBlockMap[index1]; panelBlockMap[index1] panelBlockMap[index2]; panelBlockMap[index2] temp; // 更新空位索引 if (index2 emptyPanelIndex) { emptyPanelIndex index1; } else if (index1 emptyPanelIndex) { emptyPanelIndex index2; } // 重绘面板始终执行除了打乱时的内部交换 if (needCheckWin) { puzzlePanel.repaint(); checkWin(); } }6.3 拼图移动与胜利判断核心代码处理鼠标点击事件实现拼图移动并进行胜利判断// 鼠标点击事件处理拼图移动 puzzlePanel.addMouseListener(new MouseAdapter() { Override public void mouseClicked(MouseEvent e) { // 获取点击的坐标 int clickX e.getX(); int clickY e.getY(); // 1. 边界校验判断点击是否在拼图区域内 if (clickX 0 || clickX PANEL_SIZE || clickY 0 || clickY PANEL_SIZE) { return; // 点击区域外直接返回 } // 2. 计算点击的面板索引 int clickCol clickX / BLOCK_SIZE; int clickRow clickY / BLOCK_SIZE; int clickPanelIndex clickRow * COLS clickCol; // 3. 过滤点击空位的情况 if (clickPanelIndex emptyPanelIndex) { return; // 点击空位不处理 } // 4. 获取点击块的相邻索引判断空位是否在其中 int[] adjacentIndices getAdjacentIndices(clickPanelIndex); for (int adjIndex : adjacentIndices) { if (adjIndex emptyPanelIndex) { // 空位在相邻位置交换块 swapBlocks(clickPanelIndex, emptyPanelIndex); break; } } } }); /** * 检查是否拼图完成面板每个位置的块索引等于其位置索引且空位在最后 */ private void checkWin() { boolean isWin true; int totalBlocks ROWS * COLS; // 遍历所有面板位置验证块索引是否匹配 for (int i 0; i totalBlocks; i) { if (panelBlockMap[i] ! i) { isWin false; break; } } // 额外验证空位是否在最后一个位置双重保险 if (isWin emptyPanelIndex totalBlocks - 1) { JOptionPane.showMessageDialog(this, 恭喜你拼图完成, 胜利, JOptionPane.INFORMATION_MESSAGE); } }6.4 一键还原核心代码/** * 一键还原拼图到胜利状态 */ private void restorePuzzle() { int totalBlocks ROWS * COLS; // 恢复面板映射每个位置对应相同索引的原始块 for (int i 0; i totalBlocks; i) { panelBlockMap[i] i; } // 重置空位到最后一个位置 emptyPanelIndex totalBlocks - 1; // 重绘面板 puzzlePanel.repaint(); // 还原后不弹出胜利提示手动拼出才提示 }七、总结与扩展本项目基于Java Swing实现了一款功能完整、体验流畅的3×3拼图小游戏核心亮点在于通过合法移动打乱保证拼图可解性以及对图片的自适应处理。项目代码结构清晰注释完善便于维护和扩展。可扩展方向支持自定义拼图行列数如4×4、5×5动态调整块尺寸和面板大小添加计时功能记录用户还原拼图的时间增加游戏趣味性实现拼图块拖动功能替代点击移动提升操作体验添加分数系统根据移动步数计算分数支持保存/加载拼图状态方便用户继续游戏