Java 拼图小游戏开发全记录:从 0 到 1 实现经典益智项目
个人主页-爱因斯晨
文章专栏-JAVA学习
最近学习人工智能时遇到一个好用的网站分享给大家:
人工智能学习
文章目录
作为 Java 初学者,实战项目是巩固知识的最佳方式。本文将带大家从零开始开发一款拼图小游戏,涵盖界面设计、核心逻辑与交互优化,全程配套可运行代码,适合零基础学习者上手实践。
一、项目设计与准备工作
1.1 功能定位
这款拼图游戏基于经典的数字拼图玩法,将一张图片分割为 N×N 的方块(以 3×3 为例),随机打乱后通过点击或拖拽实现方块移动,最终还原为完整图片。
核心功能包括:
- 图片分割与加载
- 随机打乱算法
- 鼠标交互控制
- 游戏胜利判断
- 计时与步数统计
1.2 开发环境
- JDK 1.8 及以上
- IDE:IntelliJ IDEA(或 Eclipse)
- 技术栈:Swing(Java 自带 GUI 库,无需额外依赖)
1.3 项目结构
PuzzleGame/├─ src/│ ├─ Main.java // 程序入口│ ├─ PuzzleFrame.java // 主窗口类│ └─ ImageUtil.java // 图片处理工具类└─ images/ // 存放游戏图片
二、基础界面搭建(Step 1)
首先创建主窗口框架,使用 Swing 的 JFrame 作为容器,设置基本属性并添加菜单组件。
// PuzzleFrame.javaimport javax.swing.*;import java.awt.*;public class PuzzleFrame extends JFrame { // 游戏参数 private static final int SIZE = 3; // 3×3拼图 private static final int BLOCK_SIZE = 150; // 每个方块大小 private int[][] data = new int[SIZE][SIZE]; // 存储方块编号 public PuzzleFrame() { initFrame(); initMenu(); initData(); setVisible(true); } // 初始化窗口属性 private void initFrame() { setTitle(\"Java拼图游戏\"); setSize(SIZE * BLOCK_SIZE + 50, SIZE * BLOCK_SIZE + 100); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); // 居中显示 setLayout(null); // 绝对布局,方便控制方块位置 } // 初始化菜单 private void initMenu() { JMenuBar menuBar = new JMenuBar(); JMenu gameMenu = new JMenu(\"游戏\"); JMenuItem restartItem = new JMenuItem(\"重新开始\"); JMenuItem exitItem = new JMenuItem(\"退出\"); gameMenu.add(restartItem); gameMenu.add(exitItem); menuBar.add(gameMenu); setJMenuBar(menuBar); // 退出功能 exitItem.addActionListener(e -> System.exit(0)); } // 初始化数据(1-8为方块,0为空位) private void initData() { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { data[i][j] = i * SIZE + j + 1; } } data[SIZE-1][SIZE-1] = 0; // 右下角为空位 } public static void main(String[] args) { new PuzzleFrame(); }}
关键知识点:
- JFrame 作为顶层容器,负责窗口基本属性配置
- JMenuBar、JMenu、JMenuItem 组合实现菜单功能
- 绝对布局(null layout)便于精确控制组件位置
三、图片加载与分割(Step 2)
接下来实现图片处理功能,将原图分割为对应数量的方块并加载显示。
// ImageUtil.javaimport javax.imageio.ImageIO;import java.awt.*;import java.awt.image.BufferedImage;import java.io.File;import java.io.IOException;public class ImageUtil { // 分割图片为SIZE×SIZE的小方块 public static BufferedImage[] splitImage(String path, int size, int blockSize) { try { BufferedImage srcImage = ImageIO.read(new File(path)); // 缩放原图以适应游戏窗口 Image scaledImage = srcImage.getScaledInstance( size * blockSize, size * blockSize, Image.SCALE_SMOOTH ); BufferedImage destImage = new BufferedImage( size * blockSize, size * blockSize, BufferedImage.TYPE_INT_RGB ); destImage.getGraphics().drawImage(scaledImage, 0, 0, null); // 分割图片 BufferedImage[] blocks = new BufferedImage[size * size]; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { int index = i * size + j; blocks[index] = destImage.getSubimage( j * blockSize, i * blockSize, blockSize, blockSize ); } } return blocks; } catch (IOException e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, \"图片加载失败!\"); return null; } }}
在 PuzzleFrame 中添加图片加载与绘制逻辑:
// 在PuzzleFrame中添加成员变量private BufferedImage[] imageBlocks;private int emptyRow = SIZE - 1; // 空位行坐标private int emptyCol = SIZE - 1; // 空位列坐标// 初始化图片private void initImage() { imageBlocks = ImageUtil.splitImage(\"images/pic.jpg\", SIZE, BLOCK_SIZE);}// 重写paint方法绘制界面@Overridepublic void paint(Graphics g) { super.paint(g); // 绘制游戏区域边框 g.setColor(Color.GRAY); g.fillRect(20, 50, SIZE * BLOCK_SIZE, SIZE * BLOCK_SIZE); // 绘制方块 for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { int value = data[i][j]; if (value != 0) { // 非空位绘制图片 g.drawImage( imageBlocks[value - 1], j * BLOCK_SIZE + 20, i * BLOCK_SIZE + 50, BLOCK_SIZE, BLOCK_SIZE, null ); } } } // 绘制网格线 for (int i = 0; i <= SIZE; i++) { g.setColor(Color.WHITE); g.drawLine(20, 50 + i * BLOCK_SIZE, 20 + SIZE * BLOCK_SIZE, 50 + i * BLOCK_SIZE); g.drawLine(20 + i * BLOCK_SIZE, 50, 20 + i * BLOCK_SIZE, 50 + SIZE * BLOCK_SIZE); }}
开发要点:
- 需在项目根目录创建 images 文件夹并放入 pic.jpg 图片
- BufferedImage 类用于图片处理,getSubimage 实现分割
- 重写 paint 方法实现自定义绘制,注意绘制顺序(先背景后元素)
四、核心逻辑实现(Step 3)
4.1 打乱算法
采用随机交换法实现打乱,但需保证拼图可解(3×3 拼图需满足逆序数为偶数):
// 打乱方块private void shuffle() { int count = 0; // 随机交换100次 for (int i = 0; i < 100; i++) { int dir = (int) (Math.random() * 4); // 0-3代表上下左右 switch (dir) { case 0: // 上 if (emptyRow > 0) { swap(emptyRow, emptyCol, emptyRow - 1, emptyCol); emptyRow--; } break; case 1: // 下 if (emptyRow < SIZE - 1) { swap(emptyRow, emptyCol, emptyRow + 1, emptyCol); emptyRow++; } break; case 2: // 左 if (emptyCol > 0) { swap(emptyRow, emptyCol, emptyRow, emptyCol - 1); emptyCol--; } break; case 3: // 右 if (emptyCol < SIZE - 1) { swap(emptyRow, emptyCol, emptyRow, emptyCol + 1); emptyCol++; } break; } }}// 交换两个位置的元素private void swap(int r1, int c1, int r2, int c2) { int temp = data[r1][c1]; data[r1][c1] = data[r2][c2]; data[r2][c2] = temp;}
4.2 鼠标交互
添加鼠标监听器实现点击移动功能:
// 初始化鼠标监听private void initMouseListener() { addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { int x = e.getX(); int y = e.getY(); // 判断点击位置是否在游戏区域内 if (x >= 20 && x <= 20 + SIZE * BLOCK_SIZE && y >= 50 && y <= 50 + SIZE * BLOCK_SIZE) { // 计算点击的方块坐标 int clickRow = (y - 50) / BLOCK_SIZE; int clickCol = (x - 20) / BLOCK_SIZE; // 判断是否可移动(相邻空位) if ((Math.abs(clickRow - emptyRow) == 1 && clickCol == emptyCol) || (Math.abs(clickCol - emptyCol) == 1 && clickRow == emptyRow)) { // 交换位置 swap(clickRow, clickCol, emptyRow, emptyCol); // 更新空位坐标 emptyRow = clickRow; emptyCol = clickCol; // 重绘界面 repaint(); // 判断是否胜利 if (checkWin()) { JOptionPane.showMessageDialog(PuzzleFrame.this, \"恭喜完成拼图!\"); } } } } });}// 胜利判断private boolean checkWin() { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { // 最后一个位置应为0 if (i == SIZE - 1 && j == SIZE - 1) { if (data[i][j] != 0) return false; } else { if (data[i][j] != i * SIZE + j + 1) return false; } } } return true;}
在构造方法中添加初始化调用:
public PuzzleFrame() { initFrame(); initMenu(); initData(); initImage(); initMouseListener(); shuffle(); // 启动时打乱 setVisible(true);}
核心算法解析:
- 打乱采用模拟人玩的随机移动法,保证可解性
- 鼠标点击通过坐标计算确定目标方块,仅允许相邻空位移动
- 胜利判断通过对比当前状态与目标状态实现
五、功能完善与优化(Step 4)
5.1 计时与步数统计
添加计时功能和步数统计,提升游戏体验:
// 添加成员变量private int stepCount = 0; // 步数private long startTime; // 开始时间private JLabel timeLabel = new JLabel(\"时间:0秒\");private JLabel stepLabel = new JLabel(\"步数:0\");// 在initFrame中添加统计标签private void initFrame() { // ... 原有代码 ... // 添加统计面板 JPanel infoPanel = new JPanel(); infoPanel.setBounds(20, 10, SIZE * BLOCK_SIZE, 30); infoPanel.add(timeLabel); infoPanel.add(stepLabel); add(infoPanel); // 初始化计时 startTime = System.currentTimeMillis(); new Timer(1000, e -> { long time = (System.currentTimeMillis() - startTime) / 1000; timeLabel.setText(\"时间:\" + time + \"秒\"); }).start();}// 移动后更新步数(在mouseClicked中)stepCount++;stepLabel.setText(\"步数:\" + stepCount);// 重新开始功能(在菜单监听器中)restartItem.addActionListener(e -> { initData(); shuffle(); stepCount = 0; stepLabel.setText(\"步数:0\"); startTime = System.currentTimeMillis(); repaint();});
5.2 界面美化
优化视觉效果,添加游戏标题和背景:
// 重写paint方法时添加标题绘制g.setColor(Color.BLUE);g.setFont(new Font(\"宋体\", Font.BOLD, 20));g.drawString(\"Java拼图游戏\", 20, 35);// 设置窗口背景setBackground(Color.LIGHT_GRAY);
六、项目总结与拓展方向
6.1 开发收获
通过本项目实践,掌握了:
- Swing 组件的使用与布局管理
- 图片处理与自定义绘制
- 事件驱动编程与用户交互
- 游戏逻辑设计与算法实现
6.2 拓展建议
- 增加难度选择(4×4、5×5)
- 实现拖拽移动功能
- 添加图片选择功能
- 记录最佳成绩排行榜
- 实现动画过渡效果
6.3 完整代码结构
和背景:
// 重写paint方法时添加标题绘制g.setColor(Color.BLUE);g.setFont(new Font(\"宋体\", Font.BOLD, 20));g.drawString(\"Java拼图游戏\", 20, 35);// 设置窗口背景setBackground(Color.LIGHT_GRAY);
六、项目总结与拓展方向
6.1 开发收获
通过本项目实践,掌握了:
- Swing 组件的使用与布局管理
- 图片处理与自定义绘制
- 事件驱动编程与用户交互
- 游戏逻辑设计与算法实现
6.2 拓展建议
- 增加难度选择(4×4、5×5)
- 实现拖拽移动功能
- 添加图片选择功能
- 记录最佳成绩排行榜
- 实现动画过渡效果
6.3 完整代码结构
最终项目包含三个核心类,共约 300 行代码,实现了一个功能完整、交互友好的拼图游戏。通过这个项目,不仅能巩固 Java 基础知识,更能理解小型应用的开发流程。