Java 拼图小游戏开发全记录:从 0 到 1 实现经典益智项目

发布于:2025-08-11 ⋅ 阅读:(14) ⋅ 点赞:(0)

个人主页-爱因斯晨

文章专栏-JAVA学习

最近学习人工智能时遇到一个好用的网站分享给大家:
人工智能学习

文章目录

    • 个人主页-爱因斯晨
    • 文章专栏-JAVA学习
    • 一、项目设计与准备工作
      • 1.1 功能定位
      • 1.2 开发环境
      • 1.3 项目结构
    • 二、基础界面搭建(Step 1)
    • 三、图片加载与分割(Step 2)
    • 四、核心逻辑实现(Step 3)
      • 4.1 打乱算法
      • 4.2 鼠标交互
    • 五、功能完善与优化(Step 4)
      • 5.1 计时与步数统计
      • 5.2 界面美化
    • 六、项目总结与拓展方向
      • 6.1 开发收获
      • 6.2 拓展建议
      • 6.3 完整代码结构
    • 六、项目总结与拓展方向
      • 6.1 开发收获
      • 6.2 拓展建议
      • 6.3 完整代码结构

作为 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.java
import 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.java
import 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方法绘制界面
@Override
public 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);
    }
}

开发要点

  1. 需在项目根目录创建 images 文件夹并放入 pic.jpg 图片
  2. BufferedImage 类用于图片处理,getSubimage 实现分割
  3. 重写 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 拓展建议

  1. 增加难度选择(4×4、5×5)
  2. 实现拖拽移动功能
  3. 添加图片选择功能
  4. 记录最佳成绩排行榜
  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 拓展建议

  1. 增加难度选择(4×4、5×5)
  2. 实现拖拽移动功能
  3. 添加图片选择功能
  4. 记录最佳成绩排行榜
  5. 实现动画过渡效果

6.3 完整代码结构

最终项目包含三个核心类,共约 300 行代码,实现了一个功能完整、交互友好的拼图游戏。通过这个项目,不仅能巩固 Java 基础知识,更能理解小型应用的开发流程。


网站公告

今日签到

点亮在社区的每一天
去签到