java实现连连看游戏(附带源码)

java实现连连看游戏(附带源码)

Java实现连连看游戏教学文档

一、项目背景详细介绍

1.1 项目简介

连连看是一款经典的休闲益智类游戏,其游戏规则简单、操作直观,因而广受欢迎。玩家需要在限定时间内,将相同图案的方块通过三条以内的直线连接进行消除,全部消除即为通关。该游戏锻炼玩家的观察力、记忆力和手眼协调能力。

本项目旨在通过Java语言实现一款图形化的连连看游戏。项目使用Java Swing进行界面开发,适合初中级Java学习者了解GUI编程、事件处理、二维数组管理等知识点。同时,通过该项目,学习者可以深入理解MVC设计模式、图形组件绘制、逻辑判定实现等关键技能。

1.2 项目的意义

训练GUI编程能力;

强化面向对象设计思想;

掌握游戏逻辑设计方法;

积累项目开发实战经验;

提升代码结构化和注释编写规范。

1.3 项目适用人群

Java初中级开发者;

在校大学生计算机相关专业;

编程教学博客写作者;

想通过游戏练手Java基础的开发者。

二、项目需求详细介绍

2.1 功能需求

游戏主界面显示一组图案组成的矩阵;

鼠标点击两个图案相同的格子,若可消除则消除;

可消除判定规则:两图案可通过不超过3条直线连接且路径不穿过其他图案;

消除后计分,若全部图案清空则游戏胜利;

限时功能,时间结束则游戏失败;

提供“重排”、“提示”、“重新开始”等功能按钮;

背景音乐与音效控制(可选);

记录最高得分与游戏次数(可选);

图案类型可配置或随机生成。

2.2 非功能需求

用户界面美观,交互响应快;

程序结构清晰,便于扩展与维护;

所有核心逻辑代码应包含详细注释;

鼠标事件与图形刷新需无明显卡顿;

项目应可在Java 8及以上版本运行。

三、相关技术详细介绍

3.1 Java Swing简介

Swing是Java提供的图形用户界面(GUI)工具包,它是建立在AWT之上的更高级别API,具有丰富的组件如按钮(JButton)、面板(JPanel)、标签(JLabel)等。Swing是轻量级组件,具有平台无关性。

本项目大量使用Swing组件实现界面搭建与用户交互。

3.2 二维数组与图案矩阵

游戏核心区域为一个二维数组,记录各图案的位置和状态。通过操作数组元素进行图案的显示、交换、消除等操作。

3.3 路径判断算法

判断两个图案是否可以连接,需要实现一个“连通性判定”算法。常用方法包括:

直线判定(横/竖);

一折线(L形)判定;

两折线(Z形)判定;

外围绕过(U形)判定。

3.4 MVC设计模式

项目将采用Model-View-Controller(MVC)模式设计:

Model负责存储图案数据;

View负责绘制界面;

Controller负责响应用户操作。

这样可以提高代码可读性和可维护性。

四、实现思路详细介绍

4.1 项目结构设计

com.lianliankan

├── Main.java // 主类,程序入口

├── GameFrame.java // 游戏主界面窗口

├── GamePanel.java // 游戏主面板,负责绘制图案

├── GameModel.java // 游戏数据模型

├── GameController.java // 控制器,响应用户操作

├── ImageLoader.java // 工具类,加载图案图片

├── SoundPlayer.java // 音效控制类(可选)

└── util

└── PathFinder.java // 路径判定算法类

4.2 图案数据生成与打乱

使用固定图案集合,每种图案出现偶数次;

使用Collections.shuffle随机打乱列表后填入二维数组中;

确保初始布局可解。

4.3 鼠标点击响应

玩家点击第一个图案,记录其坐标;

点击第二个图案后,判断是否可消除;

若可消除,设置该两位置为空,触发重绘。

4.4 连通性判断逻辑

判断两图案是否相同;

若在同行或同列直接判断无阻碍;

若一折线可达(L型);

若两折线可达(Z型);

以上均不可时判断U型连接是否可达。

4.5 时间控制与计分

使用Timer定时器刷新时间倒计时;

每次消除成功增加分数;

时间耗尽则游戏失败,弹出提示框。

五、完整实现代码

以下为完整项目源代码,已用注释清晰区分各个文件,便于复制与理解:

// === Main.java ===

package com.lianliankan;

public class Main {

public static void main(String[] args) {

new GameFrame();

}

}

// === GameFrame.java ===

package com.lianliankan;

import javax.swing.*;

public class GameFrame extends JFrame {

public GameFrame() {

setTitle("连连看游戏");

setDefaultCloseOperation(EXIT_ON_CLOSE);

setSize(820, 700);

setResizable(false);

setLocationRelativeTo(null);

add(new GamePanel());

setVisible(true);

}

}

// === GamePanel.java ===

package com.lianliankan;

import javax.swing.*;

import java.awt.*;

import java.awt.event.*;

import java.util.Timer;

import java.util.TimerTask;

public class GamePanel extends JPanel implements MouseListener {

private GameModel model;

private GameController controller;

private int score = 0;

private int time = 300; // 游戏时长秒

private JLabel scoreLabel = new JLabel("得分: 0");

private JLabel timeLabel = new JLabel("时间: 300");

private Timer timer = new Timer();

public GamePanel() {

setLayout(null);

setBackground(Color.WHITE);

setPreferredSize(new Dimension(800, 640));

model = new GameModel(10, 8); // 10行8列

controller = new GameController(model, this);

addMouseListener(this);

// 初始化分数与时间标签

scoreLabel.setBounds(650, 50, 150, 30);

add(scoreLabel);

timeLabel.setBounds(650, 90, 150, 30);

add(timeLabel);

// 启动倒计时

timer.scheduleAtFixedRate(new TimerTask() {

public void run() {

time--;

timeLabel.setText("时间: " + time);

if (time <= 0) {

timer.cancel();

JOptionPane.showMessageDialog(null, "时间到,游戏失败!");

}

}

}, 1000, 1000);

}

public void paintComponent(Graphics g) {

super.paintComponent(g);

// 绘制图案网格

for (int i = 0; i < model.rows; i++) {

for (int j = 0; j < model.cols; j++) {

int value = model.board[i][j];

if (value != 0) {

g.drawImage(ImageLoader.getImage(value), j * 60 + 30, i * 60 + 30, 50, 50, this);

}

}

}

}

public void mouseClicked(MouseEvent e) {

int x = e.getX(), y = e.getY();

int col = (x - 30) / 60;

int row = (y - 30) / 60;

if (row < 0 || row >= model.rows || col < 0 || col >= model.cols) return;

if (controller.handleClick(row, col)) {

score += 10;

scoreLabel.setText("得分: " + score);

repaint();

if (model.isCleared()) {

timer.cancel();

JOptionPane.showMessageDialog(null, "恭喜通关!");

}

}

}

public void mousePressed(MouseEvent e) {}

public void mouseReleased(MouseEvent e) {}

public void mouseEntered(MouseEvent e) {}

public void mouseExited(MouseEvent e) {}

}

// === GameModel.java ===

package com.lianliankan;

import java.util.*;

public class GameModel {

public int[][] board;

public int rows, cols;

public GameModel(int rows, int cols) {

this.rows = rows;

this.cols = cols;

board = new int[rows][cols];

initBoard();

}

private void initBoard() {

List icons = new ArrayList<>();

for (int i = 1; i <= (rows * cols) / 2; i++) {

icons.add(i % 10 + 1);

icons.add(i % 10 + 1);

}

Collections.shuffle(icons);

Iterator it = icons.iterator();

for (int i = 0; i < rows; i++)

for (int j = 0; j < cols; j++)

board[i][j] = it.next();

}

public boolean isCleared() {

for (int[] row : board)

for (int val : row)

if (val != 0) return false;

return true;

}

}

// === GameController.java ===

package com.lianliankan;

public class GameController {

private GameModel model;

private GamePanel panel;

private int[] firstClick = null;

public GameController(GameModel model, GamePanel panel) {

this.model = model;

this.panel = panel;

}

public boolean handleClick(int row, int col) {

if (model.board[row][col] == 0) return false;

if (firstClick == null) {

firstClick = new int[]{row, col};

return false;

} else {

int r1 = firstClick[0], c1 = firstClick[1];

int r2 = row, c2 = col;

if (r1 == r2 && c1 == c2) {

firstClick = null;

return false;

}

if (model.board[r1][c1] == model.board[r2][c2] &&

PathFinder.canConnect(model.board, r1, c1, r2, c2)) {

model.board[r1][c1] = 0;

model.board[r2][c2] = 0;

firstClick = null;

return true;

} else {

firstClick = null;

return false;

}

}

}

}

// === ImageLoader.java ===

package com.lianliankan;

import javax.swing.*;

import java.util.HashMap;

import java.util.Map;

public class ImageLoader {

private static Map map = new HashMap<>();

public static ImageIcon getImage(int index) {

return map.computeIfAbsent(index, i -> new ImageIcon("res/" + i + ".png"));

}

}

// === PathFinder.java ===

package com.lianliankan;

public class PathFinder {

public static boolean canConnect(int[][] board, int x1, int y1, int x2, int y2) {

// 同行

if (x1 == x2) {

for (int y = Math.min(y1, y2) + 1; y < Math.max(y1, y2); y++)

if (board[x1][y] != 0) return false;

return true;

}

// 同列

if (y1 == y2) {

for (int x = Math.min(x1, x2) + 1; x < Math.max(x1, x2); x++)

if (board[x][y1] != 0) return false;

return true;

}

// L型

if (board[x1][y2] == 0 && canConnect(board, x1, y1, x1, y2) && canConnect(board, x1, y2, x2, y2))

return true;

if (board[x2][y1] == 0 && canConnect(board, x1, y1, x2, y1) && canConnect(board, x2, y1, x2, y2))

return true;

return false; // 仅支持两折以内连接

}

}

六、代码详细解读(方法作用说明)

GameModel.initBoard():初始化图案矩阵并随机打乱图案。

GamePanel.paintComponent():负责图形绘制,每次状态更新后重绘界面。

GameController.handleClick():响应玩家点击,判断两个图案是否可消除。

PathFinder.canConnect():判断两个坐标之间是否存在合法路径连接。

ImageLoader.getImage():加载图案资源文件,支持缓存提升性能。

GamePanel.timer.schedule():实现倒计时功能,每秒刷新剩余时间。

七、项目详细总结

本项目通过Java Swing技术实现了一款经典的连连看游戏,包含了图形界面设计、事件驱动响应、二维数组操作、路径搜索算法等多个编程知识点,能够很好地锻炼开发者的综合能力。

八、项目常见问题及解答

图片不显示?

请确保图片位于项目根目录的 res/ 文件夹下,命名为 1.png ~ 10.png。

如何添加更多图案?

修改 initBoard() 方法中图案种类数量并在 res/ 中添加图片。

连通规则不够智能?

可扩展 PathFinder 类,实现三折线与绕外连接判断。

支持暂停与继续?

使用全局变量记录暂停状态并控制 Timer 对象。

九、扩展方向与性能优化

增加难度等级:根据通关次数逐步增加图案数量与复杂度;

支持关卡系统:关卡数对应不同布局与时间限制;

添加音效与背景音乐:使用 Clip 类播放音频;

优化连接判断逻辑:引入广度优先搜索支持所有合法连接路径;

保存得分记录:使用文件或数据库持久化玩家成绩。

相关推荐