Java游戏开发——俄罗斯方块

游戏介绍

①由移动的方块和不能动的固定方块组成

②如果一行排满则消除该行

③能够随机产生多种方块

④玩家可以看到游戏分数和下一方块的形状

⑤当前方块可以逆时针旋转

设计思路

Ⅰ俄罗斯方块形状设计

I形方块

倒T形方块

倒Z形方块

Z形方块

J形方块

田字形方块

L形方块

int [] block = new int[]{
1, 0, 0, 0,
1, 0, 0, 0,
1, 1, 0, 0,
0, 0, 0, 0
}

int[][] blocks = new int[][]{
{ 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 } }

Ⅱ 游戏面板屏幕

Ⅲ 游戏运行流程

Ⅳ 游戏核心逻辑

代码实现

public class GamePanel extends JPanel implements KeyListener {

private int blockType;// 方块类型
private int turnState;// 方块状态
private int score = 0;// 得分
private int nextBlockType = -1, nextTurnState = -1;// 下一个方块的类型及状态
private int x, y;// 当前方块位置
private Timer timer;// 定时器
int[][] map = new int[12][21];// 游戏地图，存储方块(1~7)和围墙(-1)及空块(0)的信息
// 方块的形状，有倒Z,Z,L,J,I,田,T七种方块
// 存储七种形状及其旋转变形
private final int shapes[][][] = new int[][][] {
// 长条形I形方块
{ { 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 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0 } },
// 倒Z字形方块
{ { 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 },
{ 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0 } },
// Z形方块
{ { 3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 3, 0, 0, 3, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0 },
{ 3, 3, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 3, 0, 0, 3, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0 } },
// J形方块
{ { 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0 },
{ 4, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 4, 4, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0 },
{ 4, 4, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
// 田字形
{ { 5, 5, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 5, 5, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 5, 5, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 5, 5, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
// L字形
{ { 6, 0, 0, 0, 6, 0, 0, 0, 6, 6, 0, 0, 0, 0, 0, 0 },
{ 6, 6, 6, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 6, 6, 0, 0, 0, 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 6, 0, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
// 倒T字形
{ { 0, 7, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 7, 0, 0, 7, 7, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0 },
{ 7, 7, 7, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 7, 0, 0, 0, 7, 7, 0, 0, 7, 0, 0, 0, 0, 0, 0 } } };

}

// 生成新方块
public void newBlock() {
// 如果当前没有下一方块 (游戏刚开局，生成当前方块和下一方块)
if (nextBlockType == -1 && nextTurnState == -1) {
blockType = (int) (Math.random() * 1000) % 7;
turnState = (int) (Math.random() * 1000) % 4;
nextBlockType = (int) (Math.random() * 1000) % 7;
nextTurnState = (int) (Math.random() * 1000) % 4;
} else {// 已有下一方块
blockType = nextBlockType;
turnState = nextTurnState;
nextBlockType = (int) (Math.random() * 1000) % 7;
nextTurnState = (int) (Math.random() * 1000) % 4;
}
x = 4;
y = 0;// 屏幕上方中央
if (gameover(x, y) == 1) {// 游戏结束
newMap();
drawWall();
score = 0;
JOptionPane.showMessageDialog(null, "GAME OVER!");
}
}

newBlock方法用于随机生成新方块，假如旋转状态为1时，则是逆时针旋转1次的方块。

// 画墙方法
public void drawWall() {
int i, j;
for (i = 0; i < 12; i++) {// 底部第20行画墙
map[i][20] = -1;
}
for (j = 0; j < 21; j++) {// 在第0列和第11列画墙
map[0][j] = -1;
map[11][j] = -1;
}
}

drawWall方法用于地图中围墙的绘画。

// 初始化地图
public void newMap() {
int i, j;
for (i = 0; i < 12; i++) {
for (j = 0; j < 21; j++) {
map[i][j] = 0;
}
}
}

newMap方法初始化地图，将游戏区域清空。

public GamePanel() {
newBlock();
newMap();
drawWall();
timer = new Timer(500, new TimerListener());
timer.start();
}

class TimerListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent arg0) {
if (blow(x, y + 1, blockType, turnState) == 1) {// 可以下落
y += 1;// 当前方块下移
} else {// 不可以下落
delLine();// 消去满行
newBlock();// 产生新的方块
}
repaint();// 屏幕重画
}

}

	// 新游戏
public void newGame() {
newBlock();
newMap();
drawWall();
}

// 暂停游戏
public void pauseGame() {
timer.stop();
}

// 继续游戏
public void continueGame() {
timer.start();
}


public void turn() {
int tempTurnState = turnState;
turnState = (turnState + 1) % 4;
if (blow(x, y, blockType, turnState) == 0) {// 如果不能旋转
turnState = tempTurnState;// 恢复
}
repaint();
}

turn()是旋转当前方块的方法。将turnState加1后，通过blow(x,y,blockType,turnState)来判断当前方块是否可以旋转，如果不能旋转则将turnState恢复为原值。


public void left() {
if (blow(x - 1, y, blockType, turnState) == 1) {// 如果可以左移
x -= 1;
}
repaint();
}

left方法用于左移当前方块。

public void right() {
if (blow(x + 1, y, blockType, turnState) == 1) {// 如果可以右移
x += 1;
}
repaint();
}

right方法用于右移当前方块。

public void down() {
if (blow(x, y + 1, blockType, turnState) == 1) {// 如果可以下落
y += 1;
} else {
newBlock();
delLine();
}
repaint();
}

down方法用于当前方块的下落。通过blow(x,y+1,blockType,turnState)方法判断是否可以下落，如果可以下落，当前方块下落一行；如果不能下落，则固定当前方块，并消去满行，同时产生新的方块。

// 判断移动后当前方块的位置是否合法
// 如果方块围墙或者固定的方块重叠，return 0
public int blow(int x, int y, int blockType, int turnState) {
for (int a = 0; a < 4; a++) {
for (int b = 0; b < 4; b++) {
if (((shapes[blockType][turnState][a * 4 + b] >= 1) && (map[x
+ b + 1][y + a] >= 1))
|| ((shapes[blockType][turnState][a * 4 + b] >= 1) && (map[x
+ b + 1][y + a] == -1))) {
return 0;
}
}
}
return 1;
}

blow方法用于判断当前方块是否与围墙和固定方法重叠，如果是则return 0，如果否则return 1。

public void delLine() {
int c = 0;
for (int b = 0; b < 21; b++) {
for (int a = 0; a < 12; a++) {
if (map[a][b] >= 1) {
c += 1;
if (c == 10) {// 满行消去，得10分
score += 10;
for (int d = b; d > 0; d--) {
for (int e = 0; e < 12; e++) {// 方块下落
map[e][d] = map[e][d - 1];
}
}
}
}
}
c = 0;
}
}

delLine方法用于消去满行方块。

public int gameover(int x, int y) {
if (blow(x, y, blockType, turnState) == 0) {// 如果碰到围墙或固定方块，游戏结束
return 1;
}
return 0;
}

gameover方法用于判断游戏是否结束。

// 将当前方块信息添加到地图数组信息中
public void add(int x, int y, int blockType, int turnState) {
int j = 0;
for (int a = 0; a < 4; a++) {
for (int b = 0; b < 4; b++) {
if (shapes[blockType][turnState][j] >= 1) {
map[x + b + 1][y + a] = shapes[blockType][turnState][j];
}
j++;
}
}
}

public void paint(Graphics g) {
super.paint((g));// 调用父类的paint()方法，实现清屏
int i, j;
// 画当前方块
for (j = 0; j < 16; j++) {
if (shapes[blockType][turnState][j] >= 1) {

switch (shapes[blockType][turnState][j]) {
case 1:
g.setColor(Color.RED);
break;
case 2:
g.setColor(Color.ORANGE);
break;
case 3:
g.setColor(Color.YELLOW);
break;
case 4:
g.setColor(Color.GREEN);
break;
case 5:
g.setColor(Color.BLUE);
break;
case 6:
g.setColor(Color.PINK);
break;
case 7:
g.setColor(Color.MAGENTA);
break;
}
g.fillRect((j % 4 + x + 1) * 15, (j / 4 + y) * 15, 15, 15);
g.setColor(Color.WHITE);
g.drawRect((j % 4 + x + 1) * 15, (j / 4 + y) * 15, 15, 15);
}
}

// 画已经固定的方块和围墙
for (j = 0; j < 21; j++) {
for (i = 0; i < 12; i++) {
if (map[i][j] >= 1) {

switch (map[i][j]) {
case 1:
g.setColor(Color.RED);
break;
case 2:
g.setColor(Color.ORANGE);
break;
case 3:
g.setColor(Color.YELLOW);
break;
case 4:
g.setColor(Color.GREEN);
break;
case 5:
g.setColor(Color.BLUE);
break;
case 6:
g.setColor(Color.PINK);
break;
case 7:
g.setColor(Color.MAGENTA);
break;
}
g.fillRect(i * 15, j * 15, 15, 15);
g.setColor(Color.WHITE);
g.drawRect(i * 15, j * 15, 15, 15);
} else if (map[i][j] == -1) {
g.setColor(Color.gray);
g.fillRect(i * 15, j * 15, 15, 15);
g.setColor(Color.WHITE);
g.drawRect(i * 15, j * 15, 15, 15);
}
}
}
g.setColor(Color.BLACK);
g.drawString("当前分数:" + score, 225, 15);
g.drawString("下一个方块是：", 225, 50);
// 窗口右侧绘制下一方块
for (j = 0; j < 16; j++) {
if (shapes[nextBlockType][nextTurnState][j] >= 1) {
switch (shapes[nextBlockType][nextTurnState][j]) {
case 1:
g.setColor(Color.RED);
break;
case 2:
g.setColor(Color.ORANGE);
break;
case 3:
g.setColor(Color.YELLOW);
break;
case 4:
g.setColor(Color.GREEN);
break;
case 5:
g.setColor(Color.BLUE);
break;
case 6:
g.setColor(Color.PINK);
break;
case 7:
g.setColor(Color.MAGENTA);
break;
}
g.fillRect(225 + (j % 4) * 15, 100 + (j / 4) * 15, 15, 15);
g.setColor(Color.WHITE);
g.drawRect(225 + (j % 4) * 15, 100 + (j / 4) * 15, 15, 15);
}
}
}

paint方法用于屏幕的重画。

// 键盘监听
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_DOWN:
down();
break;
case KeyEvent.VK_UP:
turn();
break;
case KeyEvent.VK_RIGHT:
right();
break;
case KeyEvent.VK_LEFT:
left();
break;
}
}

@Override
public void keyReleased(KeyEvent arg0) {

}

@Override
public void keyTyped(KeyEvent arg0) {

}

package 俄罗斯方块;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JOptionPane;

public class Client extends JFrame implements ActionListener {

GamePanel panel = new GamePanel();

public Client() {
setLocationRelativeTo(null);// 让窗口显示在屏幕正中间
setTitle("俄罗斯方块");
setSize(340, 395);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 当用户点击窗体右上角x时自动退出程序
setResizable(false);
setVisible(true);
}

@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == newGame) {
panel.newGame();
} else if (e.getSource() == pauseGame) {
panel.pauseGame();
} else if (e.getSource() == continueGame) {
panel.continueGame();
} else if (e.getSource() == exitGame) {
System.exit(0);
} else if (e.getSource() == about) {
displayToast("左右键移动，向上键旋转");
}
}

private void displayToast(String string) {
JOptionPane.showMessageDialog(null, string, "提示",
JOptionPane.ERROR_MESSAGE);
}

public static void main(String[] args) {
new Client();
}

}