Java | Tetris
基于 Java-Swing 实现俄罗斯方块
Preview
普通模式:
加速模式:
设计思路
方块的属性
- 方块表示:
- 由四维数组
SHAPE[][][][]
表示7种方块及每种方块的4种翻转状态。 - 由
SHAPE[type][state]
可以唯一标识一个方块。
- 由四维数组
- 方块属性:
type
:方块类型state
:方块翻转状态(x, y)
:方块坐标nextType
:下一方块类型nextState
:下一方块翻转状态
- 背景块:由
map[ROW][COl]
表示已固定的背景块。
方块的行为
方块翻转:通过改变方块的state属性实现翻转
public void turn() {
int temp = state;
state = (state + 1) % 4;
// 如果旋转后不合法,还原上一状态
if (!check(type, state, x, y)) {
state = temp;
}
}
方块下落:通过改变方块纵坐标实现下落
public void down() {
// 如果下一个下落状态合法,则下落;不合法,则固定,清行,新建块。
if (check(type, state, x, y + 1)) {
y++;
} else {
fix(type, state, x, y);
clearLines();
createShape();
}
this.repaint();
}
方块左移:通过改变方块横坐标实现左移
public void left() {
if (check(type, state, x - 1, y)) {
x--;
}
}
方块右移:通过改变方块横坐标实现右移
public void right() {
if (check(type, state, x + 1, y)) {
x++;
}
}
核心方法实现
方块碰撞检测:
private boolean check(int type, int state, int x, int y) {
for (int i = 0; i < SHAPE[type][state].length; i++) {
for (int j = 0; j < SHAPE[type][state][0].length; j++) {
if (SHAPE[type][state][i][j] == 1) {
// 在坐标系中小方块坐标(x+j,y+i);在背景矩阵中小方块位置map[y+i][x+j];
if ((x + j >= COL) || (x + j < 0) || (y + i >= ROW) || (map[y + i][x + j] == 1)) {
return false;
}
}
}
}
return true;
}
固定当前方块到背景中:
private void fix(int type, int state, int x, int y) {
for (int i = 0; i < SHAPE[type][state].length; i++) {
for (int j = 0; j < SHAPE[type][state][0].length; j++) {
// 在坐标系中小方块坐标(x+j,y+i);在背景矩阵中小方块位置map[y+i][x+j];
if ((y + i < ROW) && (x + j >= 0) && (x + j < COL) && (map[y + i][x + j] == 0)) {
map[y + i][x + j] = SHAPE[type][state][i][j];
mapColor[y + i][x + j] = color[type];
}
}
}
}
清行加分:
private void clearLines() {
int lines = 0;
boolean isFull = true;
for (int i = 0; i < map.length; i++) {
isFull = true;
for (int j = 0; j < map[0].length; j++) {
if (map[i][j] == 0) {
isFull = false;
break;
}
}
if (isFull) {
lines++;
for (int m = i; m > 0; m--) {
for (int n = 0; n < map[0].length; n++) {
map[m][n] = map[m - 1][n];
}
}
}
}
score += lines * lines * 10;
if (isAccelMode) {
up();
}
}
GUI
绘制图形:重写paint()方法,使用Graphics类随意绘制。
Graphics类绘制方法如下:
setColor(Color color); // 设置画笔颜色
drawRect(int x,int y,int width,int height); // 绘制矩形边框
fillRect(int x,int y,int width,int height); // 填充矩形
fill3DRect(int x,int y,int width,int height, boolean raised); // 填充3D矩形。
drawLine(x1,y1,x2,y2); // 绘制线条
setFont(Font font); // 设置字体
drawString(String text, int x, int y); // 绘制字符串
文件目录
com.lizich.tetris下:
.
|——TetrisMain # 游戏窗口,由此运行
|——TetrisCtrl # 绘制游戏面板&游戏逻辑
com.lizi.tetris.singleclass下:
.
|——Tetris # 合并了TetrisMain与TetrisCtrl
源码
TetrisMain
package com.liziczh.tetris;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
public class TetrisMain extends JFrame {
/**
* 版本号
*/
private static final long serialVersionUID = 1L;
// 游戏面板
private TetrisCtrl tCtrl = new TetrisCtrl();
/**
* 游戏窗口初始化
*/
public TetrisMain() {
// 设置标题
this.setTitle("Lizi Tetris");
// 设置大小
this.setSize(tCtrl.getSize());
// 调用方法居中
this.setLocationRelativeTo(null);
// 设置关闭操作:关闭窗口,程序结束运行;
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置窗体大小不改变
this.setResizable(false);
// 添加键盘监听事件
this.addKeyListener(keyListener);
// 菜单栏
JMenuBar menu = new JMenuBar();
this.setJMenuBar(menu);
JMenu gameMenu = new JMenu("游戏");
JMenuItem newGameItem = gameMenu.add("新游戏");
newGameItem.addActionListener(newGameAction);
JMenuItem pauseItem = gameMenu.add("暂停");
pauseItem.addActionListener(pauseAction);
JMenuItem continueItem = gameMenu.add("继续");
continueItem.addActionListener(continueAction);
JMenuItem exitItem = gameMenu.add("退出");
exitItem.addActionListener(exitAction);
JMenu modeMenu = new JMenu("模式");
JMenuItem normalModeItem = modeMenu.add("普通模式");
normalModeItem.addActionListener(normalModeAction);
JMenuItem accelModeItem = modeMenu.add("加速模式");
accelModeItem.addActionListener(accelModeAction);
JMenu helpMenu = new JMenu("帮助");
JMenuItem aboutItem = helpMenu.add("关于");
aboutItem.addActionListener(aboutAction);
menu.add(gameMenu);
menu.add(modeMenu);
menu.add(helpMenu);
// 设置窗口可见
this.setVisible(true);
// 添加TetrisPanel
this.add(tCtrl);
}
// 键盘事件监听
KeyListener keyListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
// ↑:旋转
tCtrl.turn();
tCtrl.repaint();
break;
case KeyEvent.VK_LEFT:
// ←:左移
tCtrl.left();
tCtrl.repaint();
break;
case KeyEvent.VK_RIGHT:
// →:右移
tCtrl.right();
tCtrl.repaint();
break;
case KeyEvent.VK_DOWN:
// ↓:下移
tCtrl.down();
tCtrl.repaint();
break;
}
}
};
// 新游戏
ActionListener newGameAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
tCtrl.init();
}
};
// 暂停
ActionListener pauseAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
tCtrl.setPause();
}
};
// 继续
ActionListener continueAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tCtrl.setContinue();
}
};
// 退出
ActionListener exitAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
};
// 普通模式
ActionListener normalModeAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tCtrl.setNormal();
}
};
// 加速模式
ActionListener accelModeAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tCtrl.setAccel();
}
};
// 关于
ActionListener aboutAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(tCtrl, "Tetris v1.0 from liziczh", "关于", getDefaultCloseOperation());
}
};
public static void main(String[] args) {
new TetrisMain();
}
}
TetrisCtrl:
package com.liziczh.tetris;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.Timer;
public class TetrisCtrl extends JPanel {
/**
* 版本号
*/
private static final long serialVersionUID = 1L;
// 方块边长,单位像素(px)
public final int LEN = 24;
// Panel区域:20行10列
public final int ROW = 20;
public final int COL = 10;
/**
* SHAPE[type][state]:方块形状; type方块类型, state方块旋转状态;
*/
private final int[][][][] SHAPE = new int[][][][] {
// S:
{ { { 0, 1, 1, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
{ { 1, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } },
{ { 0, 1, 1, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
{ { 1, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } } },
// Z:
{ { { 1, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
{ { 0, 1, 0, 0 }, { 1, 1, 0, 0 }, { 1, 0, 0, 0 }, { 0, 0, 0, 0 } },
{ { 1, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
{ { 0, 1, 0, 0 }, { 1, 1, 0, 0 }, { 1, 0, 0, 0 }, { 0, 0, 0, 0 } } },
// L:
{ { { 1, 0, 0, 0 }, { 1, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 } },
{ { 1, 1, 1, 0 }, { 1, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
{ { 1, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } },
{ { 0, 0, 1, 0 }, { 1, 1, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } } },
// J:
{ { { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 } },
{ { 1, 0, 0, 0 }, { 1, 1, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
{ { 1, 1, 0, 0 }, { 1, 0, 0, 0 }, { 1, 0, 0, 0 }, { 0, 0, 0, 0 } },
{ { 1, 1, 1, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } } },
// I:
{ { { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 } },
{ { 0, 0, 0, 0 }, { 1, 1, 1, 1 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
{ { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 } },
{ { 0, 0, 0, 0 }, { 1, 1, 1, 1 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } } },
// O:
{ { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } },
{ { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } },
{ { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } },
{ { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } } },
// T:
{ { { 0, 1, 0, 0 }, { 1, 1, 1, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } },
{ { 0, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } },
{ { 0, 0, 0, 0 }, { 1, 1, 1, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } },
{ { 0, 1, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } } },
};
// 当前方块的四个参数:type方块类型,state方块旋转状态,坐标(x,y)。
private int type, state, x, y;
// 下一个块的参数:nextType方块类型,nextState方块旋转状态
private int nextType, nextState;
// 背景:已固定块
private int[][] map = new int[ROW][COL];
// 已固定块的颜色
private Color[][] mapColor = new Color[ROW][COL];
// 得分
private int score = 0;
// 等级
private int level = 0;
// 下落延时
private int delay = 1000;
// 是否在暂停状态
private boolean isPause = false;
// 是否为加速模式
private boolean isAccelMode = false;
// 方块颜色:color[type]
private Color[] color = new Color[] { Color.green, Color.red, Color.orange, Color.blue, Color.cyan, Color.yellow,
Color.magenta, Color.gray };
public TetrisCtrl() {
// 初始化Panel大小
this.setSize(LEN * 20, LEN * 25);
this.init();
}
/**
* 游戏初始化
*/
public void init() {
// 初始化背景map
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
map[i][j] = 0;
}
}
// 初始化分数
score = 0;
isPause = false;
// 随机生成下一方块
nextType = (int) (Math.random() * 7);
nextState = (int) (Math.random() * 4);
// 生成当前方块
createShape();
// 启动timer
timer.start();
// 绘图
this.repaint();
}
/**
* 创建一个新方块
*/
public void createShape() {
// 当前块
type = nextType;
state = nextState;
x = 3;
y = 0;
// 下一块
nextType = (int) (Math.random() * 7);
nextState = (int) (Math.random() * 4);
// 如果新块不合法,则表示游戏已结束,则重新开始
if (!check(type, state, x, y)) {
JOptionPane.showMessageDialog(this, "GAME OVER!");
init();
}
}
/**
* 判断方块是否合法:true合法,false不合法
*/
private boolean check(int type, int state, int x, int y) {
for (int i = 0; i < SHAPE[type][state].length; i++) {
for (int j = 0; j < SHAPE[type][state][0].length; j++) {
if (SHAPE[type][state][i][j] == 1) {
// 在坐标系中小方块坐标(x+j,y+i);在背景矩阵中小方块位置map[y+i][x+j];
if ((x + j >= COL) || (x + j < 0) || (y + i >= ROW) || (map[y + i][x + j] == 1)) {
return false;
}
}
}
}
return true;
}
/**
* 固定shape到map
*/
private void fix(int type, int state, int x, int y) {
for (int i = 0; i < SHAPE[type][state].length; i++) {
for (int j = 0; j < SHAPE[type][state][0].length; j++) {
// 在坐标系中小方块坐标(x+j,y+i);在背景矩阵中小方块位置map[y+i][x+j];
if ((y + i < ROW) && (x + j >= 0) && (x + j < COL) && (map[y + i][x + j] == 0)) {
map[y + i][x + j] = SHAPE[type][state][i][j];
mapColor[y + i][x + j] = color[type];
}
}
}
}
/**
* 消行加分
*/
private void clearLines() {
int lines = 0;
boolean isFull = true;
for (int i = 0; i < map.length; i++) {
isFull = true;
for (int j = 0; j < map[0].length; j++) {
if (map[i][j] == 0) {
isFull = false;
break;
}
}
if (isFull) {
lines++;
for (int m = i; m > 0; m--) {
for (int n = 0; n < map[0].length; n++) {
map[m][n] = map[m - 1][n];
}
}
}
}
score += lines * lines * 10;
if (isAccelMode) {
up();
}
}
/**
* 升级加速:UP
*/
public void up() {
int limit = 50;
if (score > limit * level) {
level++;
delay /= 1.5;
timer.setDelay(delay);
limit = limit * level;
}
}
public void turn() {
int temp = state;
state = (state + 1) % 4;
// 如果旋转后不合法,还原上一状态
if (!check(type, state, x, y)) {
state = temp;
}
}
public void down() {
// 如果下一个下落状态合法,则下落;不合法,则固定。
if (check(type, state, x, y + 1)) {
y++;
} else {
fix(type, state, x, y);
clearLines();
createShape();
}
this.repaint();
}
public void right() {
if (check(type, state, x + 1, y)) {
x++;
}
}
public void left() {
if (check(type, state, x - 1, y)) {
x--;
}
}
/**
* 绘图:重写paint()方法
*
* @see javax.swing.JComponent#paint(java.awt.Graphics)
*/
@Override
public void paint(Graphics g) {
// 游戏区域的左、上边距
int MARGIN_LEFT = LEN;
int MARGIN_TOP = LEN + 10;
// 边栏的起始坐标
int SIDEBAR_X = LEN * 13;// 文本的横坐标
int SIDEBAR_Y = LEN * 9; // 文本的纵坐标
/**
* 填充背景色
*/
g.setColor(Color.white);
g.fillRect(0, 0, (int) (this.getSize().getWidth()), (int) (this.getSize().getHeight()));
/**
* 画边框
*/
g.setColor(Color.gray);
for (int offset = 1; offset < 2; offset++) {
// 绘制矩形边框:drawRect(int x,int y,int width,int height);
g.drawRect(MARGIN_LEFT - offset, MARGIN_TOP - offset, COL * LEN + offset * 2, ROW * LEN + offset * 2);
}
/**
* 画网状线
*/
g.setColor(Color.gray);
// 11条竖线
for (int i = 0; i < 11; i++) {
// 绘制线条:drawLine(x1,y1,x2,y2);
g.drawLine(MARGIN_LEFT + LEN * i, MARGIN_TOP, MARGIN_LEFT + LEN * i, MARGIN_TOP + ROW * LEN);
}
// 21条横线
for (int i = 0; i < 21; i++) {
g.drawLine(MARGIN_LEFT, MARGIN_TOP + LEN * i, MARGIN_LEFT + COL * LEN, MARGIN_TOP + LEN * i);
}
/**
* 画侧栏
*/
// 画文本:下一个
g.setColor(Color.gray);
g.setFont(new Font("Times", Font.BOLD, 20));
g.drawString("下一个:", SIDEBAR_X, LEN * 2 + 10);
// 画提示方块(下一个方块)
g.setColor(color[nextType]);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (SHAPE[nextType][nextState][i][j] == 1) {
// 填充3D矩形:fill3DRect(int x,int y,int width,int height,boolean raised)
g.fill3DRect(SIDEBAR_X + 20 + j * LEN, LEN * 3 + i * LEN, LEN, LEN, true);
}
}
}
// 画文本:得分
g.setColor(Color.gray);
g.setFont(new Font("Times", Font.BOLD, 24));
g.drawString("等级:" + level, SIDEBAR_X, SIDEBAR_Y);
g.drawString("得分:" + score, SIDEBAR_X, SIDEBAR_Y + 40);
// 画文本:游戏说明
g.setColor(Color.gray);
g.setFont(new Font("Times", Font.BOLD, 15));
g.drawString("玩法:", SIDEBAR_X, SIDEBAR_Y + LEN * 4);
g.drawString("上箭头:旋转", SIDEBAR_X, SIDEBAR_Y + LEN * 5);
g.drawString("左箭头:左移", SIDEBAR_X, SIDEBAR_Y + LEN * 6);
g.drawString("右箭头:右移", SIDEBAR_X, SIDEBAR_Y + LEN * 7);
g.drawString("下箭头:下落", SIDEBAR_X, SIDEBAR_Y + LEN * 8);
g.drawString("@栗子", SIDEBAR_X, SIDEBAR_Y + LEN * 10);
// 画当前下落块
g.setColor(color[type]);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (SHAPE[type][state][i][j] == 1) {
g.fill3DRect(MARGIN_LEFT + (x + j) * LEN, MARGIN_TOP + (y + i) * LEN, LEN, LEN, true);
}
}
}
// 画背景map
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
if (map[i][j] == 1) {
g.setColor(mapColor[i][j]);
g.fill3DRect(MARGIN_LEFT + j * LEN, MARGIN_TOP + i * LEN, LEN, LEN, true);
}
}
}
// 画暂停
if (isPause) {
g.setColor(Color.black);
g.drawString("PAUSE", MARGIN_LEFT + LEN * 8, LEN);
}
// 画普通模式
if (!isAccelMode) {
g.setColor(Color.black);
g.drawString("Normal Mode", LEN, LEN);
}
// 画加速模式
if (isAccelMode) {
g.setColor(Color.black);
g.drawString("Accel Mode", LEN, LEN);
}
}
/**
* 定时器
*/
Timer timer = new Timer(delay, new ActionListener() {
// 定时器任务
@Override
public void actionPerformed(ActionEvent e) {
down();
}
});
// 暂停
public void setPause() {
timer.stop();
isPause = true;
this.repaint();
}
// 继续
public void setContinue() {
timer.restart();
isPause = false;
this.repaint();
}
// 简单模式
public void setNormal() {
isAccelMode = false;
level = 0;
timer.setDelay(1000);
init();
}
// 加速模式
public void setAccel() {
isAccelMode = true;
level = 1;
timer.setDelay(500);
init();
}
}