个人主页-爱因斯晨
文章专栏-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 项目结构
plaintext
PuzzleGame/
├─ src/
│ ├─ Main.java // 程序入口
│ ├─ PuzzleFrame.java // 主窗口类
│ └─ ImageUtil.java // 图片处理工具类
└─ images/ // 存放游戏图片
二、基础界面搭建(Step 1)
首先创建主窗口框架,使用 Swing 的 JFrame 作为容器,设置基本属性并添加菜单组件。
java
// 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)
接下来实现图片处理功能,将原图分割为对应数量的方块并加载显示。
java
// 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 中添加图片加载与绘制逻辑:
java
// 在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);
}
}
开发要点:
- 需在项目根目录创建 images 文件夹并放入 pic.jpg 图片
- BufferedImage 类用于图片处理,getSubimage 实现分割
- 重写 paint 方法实现自定义绘制,注意绘制顺序(先背景后元素)
四、核心逻辑实现(Step 3)
4.1 打乱算法
采用随机交换法实现打乱,但需保证拼图可解(3×3 拼图需满足逆序数为偶数):
java
// 打乱方块
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 鼠标交互
添加鼠标监听器实现点击移动功能:
java
// 初始化鼠标监听
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;
}
在构造方法中添加初始化调用:
java
public PuzzleFrame() {
initFrame();
initMenu();
initData();
initImage();
initMouseListener();
shuffle(); // 启动时打乱
setVisible(true);
}
核心算法解析:
- 打乱采用模拟人玩的随机移动法,保证可解性
- 鼠标点击通过坐标计算确定目标方块,仅允许相邻空位移动
- 胜利判断通过对比当前状态与目标状态实现
五、功能完善与优化(Step 4)
5.1 计时与步数统计
添加计时功能和步数统计,提升游戏体验:
java
// 添加成员变量
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 界面美化
优化视觉效果,添加游戏标题和背景:
java
// 重写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 完整代码结构
和背景:
java
// 重写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 基础知识,更能理解小型应用的开发流程。