Java俄罗斯方块源码解析:游戏主循环、碰撞检测与消行实现

简介:这是一份面向Java初学者与游戏编程爱好者的俄罗斯方块完整源码,以Java语言实现,适合用来理解经典游戏的核心逻辑与面向对象设计。源码围绕游戏主面板、方块形状、流程控制器与程序入口等关键类展开,涵盖游戏循环、方块生成与移动、碰撞检测、得分计算、结束判定及键盘交互等要点,可帮助读者掌握一个简单游戏如何用代码落地。压缩包共38个文件,约170KB,包含18个png图片资源、14个class字节码、3个java源文件,以及prefs、project、classpath等IDE配置,便于在Eclipse等环境中直接导入与复用。目前已有2785人学习下载,读者可对照源码梳理模块职责,尝试修改参数、添加新功能或优化性能,在实战中提升Java编程与代码组织能力。

1. 从一份俄罗斯方块源码说起:为什么我建议你从它开始读

很多人第一次想读游戏源码,都会挑一个“看起来简单”的项目。俄罗斯方块往往第一个被想到,然后被迅速放弃——因为网上流传的版本要么是几百行塞进一个文件的面条代码,要么是套了引擎之后只剩胶水逻辑,读完什么也学不到。我最近拿到一份 Java 实现的俄罗斯方块源码,结构干净,没有依赖任何游戏引擎,纯 Swing 绘制,把“游戏主循环、方块旋转、碰撞检测、消行计分”这四件事拆得清清楚楚。它适合两类人:一是刚学完 Java 基础、想找一个能跑起来又能看懂的项目练手的新手;二是想自己写个小游戏、但不知道状态管理和渲染循环怎么组织的开发者。这份源码不炫技,但每一块都能对应到实际工程里的真实问题。

2. 项目结构与技术选型:一个不依赖引擎的 Java 桌面游戏怎么搭

2.1 目录结构与模块划分

拿到源码后先别急着跑,把目录扫一遍,心里有个地图。这份俄罗斯方块源码的典型结构如下:

tetris/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── tetris/ │ │ │ ├── Main.java │ │ │ ├── GamePanel.java │ │ │ ├── GameThread.java │ │ │ ├── Tetromino.java │ │ │ ├── Board.java │ │ │ └── ScorePanel.java │ │ └── resources/ │ │ └── block.png └── pom.xml

这个划分对应了五个职责:Main负责启动窗口,GamePanel是主画布,GameThread是游戏循环,Tetromino管方块形状和旋转,Board管网格状态和消行,ScorePanel单独显示分数。每个类只做一件事,读起来不会迷路。

2.2 技术选型:为什么是 Swing 而不是 JavaFX

源码用的是 Swing,不是 JavaFX。这不是偷懒,而是有实际考虑的。Swing 从 JDK 1.2 就内置,不需要额外依赖,pom.xml里除了 JUnit 几乎没有第三方库。对于一份教学性质的源码来说,少一层依赖就少一层理解成本。Swing 的JPanel.paintComponent配合Timer或独立线程,足够撑起一个 60 帧的方块游戏。常见做法是:主循环用Thread.sleep控制节奏,渲染统一走repaint(),逻辑更新和绘制分离。这份源码就是这么干的。

2.3 核心类职责一览

类名职责关键方法
Main创建 JFrame,装配面板main
GamePanel绘制网格、当前方块、下一个方块paintComponent
GameThread控制下落速度、游戏状态run
Tetromino定义 7 种方块形状与旋转rotate
Board维护 20x10 网格,检测碰撞与消行checkCollision, clearLines
ScorePanel显示分数、等级、行数updateScore

这张表建议你贴在编辑器旁边,读代码时随时对照。

3. 核心模块拆解:方块旋转、碰撞检测与消行的代码实现

3.1 方块的数据结构:用二维数组表示形状

俄罗斯方块有 7 种基本形状(I、O、T、S、Z、J、L),每种形状在不同旋转状态下有不同的占位。源码用二维数组表示:

public class Tetromino { // 每种方块用 4x4 矩阵表示,1 表示有方块,0 表示空 private int[][] shape; private int x, y; // 当前方块在棋盘上的左上角坐标 // I 型方块的初始形状 private static final int[][] I_SHAPE = { {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0} }; public Tetromino(int[][] shape) { this.shape = shape; this.x = 3; // 从中间开始 this.y = 0; } // 顺时针旋转 90 度:转置后左右翻转 public void rotate() { int n = shape.length; int[][] rotated = new int[n][n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { rotated[j][n - 1 - i] = shape[i][j]; } } shape = rotated; } }

这里的关键是旋转算法:先转置再水平翻转,等价于顺时针旋转 90 度。用 4x4 矩阵而不是紧凑数组,是为了旋转时不用重新计算边界,代价是多了几个空位。参数x和y是方块在棋盘坐标系里的位置,旋转只改shape,不改x、y,位置调整交给碰撞检测后的回退逻辑。

3.2 碰撞检测:判断能不能移动或旋转

碰撞检测是俄罗斯方块里最容易写错的地方。源码的做法是:先假设移动,再检查是否越界或重叠,如果冲突就回退。

public class Board { private int[][] grid; // 20 行 10 列,0 为空,非 0 为已固定方块 private static final int ROWS = 20; private static final int COLS = 10; // 检查当前方块在指定位置是否合法 public boolean isValidPosition(Tetromino piece, int newX, int newY) { int[][] shape = piece.getShape(); for (int i = 0; i < shape.length; i++) { for (int j = 0; j < shape[i].length; j++) { if (shape[i][j] == 0) continue; int boardX = newX + j; int boardY = newY + i; // 左右越界、底部越界 if (boardX < 0 || boardX >= COLS || boardY >= ROWS) { return false; } // 与已固定方块重叠 if (boardY >= 0 && grid[boardY][boardX] != 0) { return false; } } } return true; } }

逻辑说明:遍历方块的 4x4 矩阵,只检查值为 1 的格子。boardY >= 0的判断是为了允许方块在顶部部分露出时仍然合法。如果任何一格越界或与grid中已有方块重叠,返回false。调用方在移动前先试算新坐标,合法才真正更新x、y。

3.3 消行与计分:从底部往上扫描

消行的逻辑不复杂,但顺序很重要。源码从底部往上扫描,遇到满行就删除,并把上面的行整体下移。

public int clearLines() { int linesCleared = 0; for (int row = ROWS - 1; row >= 0; row--) { boolean full = true; for (int col = 0; col < COLS; col++) { if (grid[row][col] == 0) { full = false; break; } } if (full) { // 将 row 以上的所有行下移一行 for (int r = row; r > 0; r--) { System.arraycopy(grid[r - 1], 0, grid[r], 0, COLS); } // 顶部行清空 Arrays.fill(grid[0], 0); linesCleared++; row++; // 重新检查当前行,因为上面的行已经下移 } } return linesCleared; }

参数说明:grid是 20x10 的二维数组。System.arraycopy做整行复制,比嵌套循环快。row++是为了抵消循环的row--,让下一次迭代仍然检查同一行位置,因为下移后这里可能是新的满行。计分规则通常按一次消除的行数给不同分数,源码里是 1 行 100 分、2 行 300 分、3 行 500 分、4 行 800 分。

3.4 游戏主循环:线程控制下落节奏

游戏循环用独立线程,而不是 Swing 的Timer,因为下落速度需要动态调整(等级越高越快)。

public class GameThread extends Thread { private GamePanel panel; private boolean running = true; private int dropInterval = 500; // 毫秒 public void run() { while (running) { try { Thread.sleep(dropInterval); } catch (InterruptedException e) { break; } // 尝试下落一格,失败则固定方块并生成新方块 if (!panel.moveDown()) { panel.lockPiece(); panel.spawnNewPiece(); panel.clearLines(); } panel.repaint(); } } public void setDropInterval(int interval) { this.dropInterval = interval; } }

逻辑说明:dropInterval控制下落速度,初始 500ms,每消 10 行减 50ms,最低 100ms。moveDown()返回false表示无法继续下落,此时把当前方块固定到grid,生成新方块,再检查消行。repaint()触发GamePanel重绘。注意running标志用于退出线程,避免窗口关闭后线程还在跑。

4. 跑起来与改起来:编译、运行和常见改造点

4.1 环境准备与编译命令

这份源码是标准 Maven 项目,需要 JDK 8 或以上。如果你没有 Maven,也可以直接用javac编译。

# 方式一:Maven mvn clean compile mvn exec:java -Dexec.mainClass="com.tetris.Main" # 方式二:手动编译 cd src/main/java javac com/tetris/*.java java com.tetris.Main

注意:手动编译时资源文件block.png需要复制到com/tetris/目录下,否则图片加载会报空指针。Maven 会自动处理资源路径。

4.2 运行效果与操作说明

启动后会出现一个 300x600 的窗口,左边是 10x20 的游戏区,右边是分数面板。操作键:左右箭头移动,上箭头旋转,下箭头加速下落,空格直接落底。源码里键盘监听在GamePanel的KeyAdapter中实现,每个按键对应调用moveLeft()、moveRight()、rotate()、hardDrop()。

4.3 常见改造点:从能跑到好用

拿到源码后,最常改的几个地方:

  • 改下落速度曲线:在GameThread里调整dropInterval的递减逻辑,比如每消 5 行减 30ms。
  • 加暂停功能:在GamePanel里加一个paused标志,GameThread循环里判断,暂停时Thread.sleep(100)空转。
  • 加下一个方块预览:GamePanel里多画一个 4x4 区域,从Tetromino工厂里提前取一个形状。
  • 改配色:paintComponent里每个方块的颜色由grid里的值决定,改颜色映射表即可。

这些改动都不需要动核心逻辑,适合作为二次开发的起点。

5. 避坑与排查:跑这份源码时容易翻车的几个地方

5.1 窗口一闪而过或白屏

现象:运行后窗口出现但立刻消失,或者一片白。原因通常是Main里没有设置setDefaultCloseOperation,或者GamePanel的paintComponent里抛了异常被吞掉。解决:在Main里加frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE),并在paintComponent开头加super.paintComponent(g),否则 Swing 不会清空背景。

5.2 方块旋转后卡在墙里

现象:按上箭头旋转,方块直接嵌进左右边界或底部。原因:旋转后没有做碰撞检测回退。解决:在rotate()调用后,立即用isValidPosition检查,不合法就旋转回去。常见做法是尝试左右微移(wall kick),但这份源码只做了简单回退,够用。

5.3 消行后分数不更新

现象:行消了,但分数面板没变。原因:clearLines()返回消除行数,但调用方忘了把返回值传给ScorePanel.updateScore()。解决:在GameThread里把panel.clearLines()的返回值接住,调用scorePanel.addScore(lines)。

5.4 线程退出后报 IllegalThreadStateException

现象:关闭窗口后控制台报错。原因:GameThread的running标志没有在窗口关闭时置为false,线程还在跑,但panel已经销毁。解决:在Main里加WindowListener,windowClosing时调用gameThread.stopRunning(),把running设为false并interrupt()。

5.5 图片资源加载失败

现象:方块显示为黑色或报NullPointerException。原因:getClass().getResource("block.png")的路径不对。解决:确认block.png在 classpath 根目录或与GamePanel同包。Maven 项目放在src/main/resources/下,手动编译时复制到com/tetris/。

6. 二次开发技巧:把这份源码改成你自己的游戏

这份源码最值钱的地方不是俄罗斯方块本身,而是它把“网格状态 + 实时循环 + 输入响应”这套模式写清楚了。你完全可以拿它当模板,改成贪吃蛇、推箱子、甚至一个简单的战棋 demo。我自己的习惯是:先不动核心逻辑,只改Tetromino的形状定义和Board的网格尺寸,跑通一个“变体俄罗斯方块”,比如 15x15 的方形棋盘、方块形状改成不规则多边形。这一步能帮你确认自己真的理解了坐标系统和碰撞检测。

具体改造时,我一般会先抽一个ShapeFactory接口,把Tetromino的创建逻辑从GamePanel里解耦出来。这样加新形状不用改渲染代码。然后给Board加一个reset()方法,方便重开一局。最后把GameThread里的dropInterval抽成配置项,从config.properties读,改速度不用重新编译。

验证方法很简单:每改一个模块,先跑一局看有没有崩溃,再用System.out.println打印关键状态(当前方块坐标、网格快照、分数),确认逻辑符合预期。别一上来就大改,俄罗斯方块的边界条件比看起来多,尤其是旋转和消行的交互。

从那以后我每次拿到一份小游戏源码,都强制自己先跑通、再改一个参数、最后加一个小功能,三步走完才敢说“看懂了”。希望这份俄罗斯方块的拆解能帮你省下自己摸索的时间,直接上手改出点自己的东西。

本文还有配套的精品资源,点击获取

相关新闻

NLP与无人船:从自然语言指令到自主靠泊的完整技术链路

NLP与无人船:从自然语言指令到自主靠泊的完整技术链路

去年我带着组里的几位同学把一个挺有意思的项目从零做到了仿真验证——给一艘无人船加一套自然语言操控链路&#xff0c;让操作员可以直接说“慢速驶向测试场北侧码头&#xff0c;左舷靠泊”或者“绕开右前方漂浮物&#xff0c;停到A区泊位”&#xff0c;船载系统自己去完成意图…

2026/10/21 5:11:04 阅读更多 →
鸿蒙+Flutter跨平台实战:桌面图标焕新方案全解析

鸿蒙+Flutter跨平台实战:桌面图标焕新方案全解析

桌面图标焕新大师这个名字&#xff0c;听起来像某个手机美化App的宣传语&#xff0c;但它本质上是一个很典型的跨平台实战项目&#xff1a;把视觉设计、资源管理、系统桌面机制和Flutter渲染能力串在一起&#xff0c;最终在鸿蒙设备上完成“一键切换桌面图标风格”的完整闭环。…

2026/10/21 5:11:04 阅读更多 →
从ReAct到龙虾架构:AI Agent编排演进与工程实践

从ReAct到龙虾架构:AI Agent编排演进与工程实践

这两年AI Agent架构的文章我看了不下几十篇&#xff0c;名字一个比一个唬人&#xff1a;ReAct、Plan-and-Execute、Reflexion、多Agent系统、图式编排、龙虾架构……但真能把“从A到B是怎么一步步走出来”讲明白的&#xff0c;确实不多。尤其对小程序员来说&#xff0c;最痛苦的…

2026/10/21 5:11:04 阅读更多 →

最新新闻

AI信息挖掘实战:从提问到交叉验证,把碎片信息串成认知

AI信息挖掘实战:从提问到交叉验证,把碎片信息串成认知

元旦的钟声刚敲过&#xff0c;我猜不少人正把“新的一年多学点东西、把手头的事做精”写进计划表。这几天我发现一个很有意思的现象&#xff1a;大家不是不想查资料&#xff0c;而是被资料淹没了。打开浏览器想看某个行业的最新动态&#xff0c;结果十条链接里八条是去年的&…

2026/10/21 5:48:18 阅读更多 →
从SLAM到AMCL:自制差速机器人地图导航模块实战解析

从SLAM到AMCL:自制差速机器人地图导航模块实战解析

lingbot-map 这个名字听起来像某个开源仓库的代号&#xff0c;其实是我给自制的差速移动机器人开发的一套完整地图与导航模块。项目核心就一句话&#xff1a;让一台只会上电转圈的底盘&#xff0c;变成能在办公室和家里自主巡航的机器人。它由三个子功能组成——离线建图、在线…

2026/10/21 5:48:18 阅读更多 →
Python语法基础精讲:变量、缩进与运算符优先级实战避坑指南

Python语法基础精讲:变量、缩进与运算符优先级实战避坑指南

简介&#xff1a;这份PPT课件面向Python零基础学习者与编程入门教学场景&#xff0c;系统梳理第2章Python语法基础的核心知识框架。内容围绕变量与数据类型展开&#xff0c;涵盖整型、浮点型、复数、布尔、字符串、列表、元组、字典、集合等类型&#xff0c;并延伸至标识符命名…

2026/10/21 5:48:18 阅读更多 →
互联网医院管理制度文档编写指南:从制度条款到系统代码的落地实践

互联网医院管理制度文档编写指南:从制度条款到系统代码的落地实践

简介&#xff1a;这份文档面向医院管理者、信息科人员及互联网医疗从业者&#xff0c;系统梳理了互联网医院从组织架构到具体业务规范的完整制度框架&#xff0c;可用于制度建设参考、合规自查或岗位培训。压缩包内为1个doc文件&#xff0c;约176KB&#xff0c;内容以制度条文与…

2026/10/21 5:48:18 阅读更多 →
kubeadm 安装 Kubernetes 集群完整指南:初始化、加入节点与排障

kubeadm 安装 Kubernetes 集群完整指南:初始化、加入节点与排障

经常有人问我&#xff1a;“想自己搭一套 Kubernetes 集群&#xff0c;到底从哪下手&#xff1f;”我一般都会回复一句话&#xff1a;先把 kubeadm 用熟&#xff0c;再谈别的。kubeadm 是 Kubernetes 官方维护的集群引导工具&#xff0c;它的目标就是让你别在节点初始化、证书签…

2026/10/21 5:48:18 阅读更多 →
MediaSoup SDP协商详解:ICE、DTLS与RTP参数三层拆解

MediaSoup SDP协商详解:ICE、DTLS与RTP参数三层拆解

做WebRTC服务端的时间长了&#xff0c;你会发现SDP协商是所有“看起来简单、跑起来折磨”的问题里最典型的一个。文本就那么几行&#xff0c;但每一行都对应着一段连接的生命周期。MediaSoup的SDP协商尤其有意思——它并没有像传统服务器那样去解析和重写SDP文本&#xff0c;而…

2026/10/21 5:47:17 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →