1.设计内容
设计一个常规的俄罗斯方块游戏,方块形状有7个形状,如果在一条水平线上所有位置都填满,就可消去一行,得10分,如果某一列高度超过上限,游戏结束。
效果如图:俄罗斯方块界面
2.设计要求
界面美观,键盘的上下左右键好用。上箭头:出现的方块顺时针旋转。下箭头:加速下落。左键头,向左平移。右箭头:向右平移。颜色鲜艳,用随机数产生。
3.设计思想
定义砖块类Brick,定义数字代表砖块形状
:0,:1,:2,:3,:4,:5,:6;定义旋转角度:旋转90度:0,旋转180度:1,旋转270度:2,旋转360:3;定义砖块的宽度和高度; 初始化所有可能出现的砖块的模型:定义一个四维数组,第一维代表7种不同的图案,第二维代表每个团旋转的4个角度,第三维和第四维定义行列,代表每个图形的样子。如
:四个角度都一样
:四个角度分别是
:四个角度分别是
定义一个Color数组,是砖块可能出现的所有颜色;定义变量记录当前砖块的形状、颜色、和颜色数组的索引、旋转角度、当前砖块所在的坐标值。用构造函数初始化砖块的有关属性(形状、角度、颜色、坐标)
- 搭建界面,响应键盘时间,上下左右四个方向键头,分别调用旋转,下落,向左移动,向右移动方法。主函数显示界面,界面上显示的是M类对象,启动游戏。
- 定义一个Panel的子类M,实现多线程接口。
A.构造函数M(){1. 整个界面包含边界,边界内算作一个二维数组,清空二维数组中的砖块信息;2. 产生当前砖块和下一个提示的砖块;3.计算提示的砖块的位置}
B.显示界面方法 Paint(){1.画得分; 2.画边界; 3. 画当前下落的砖块; 4.画下一个砖块}
C.线程方法Run(){砖块继续下落,只要当前砖块到达界面底部,判定是否需要消掉一行,是否得分。初始化相关变量信息。将原来下一个砖块的信息赋值给当前砖块,产生一个新砖块,新生成的砖块信息赋值给下一个提示砖块}
D.下落函数drop(){将当前砖块位置全部清空,纵坐标+1,重新画当前下落砖块形状,如果y不能再次+1,y==0,说明砖块落不下去了,游戏结束,如果y不能再次+1,但y!=0,说明到达底部。}
E.旋转砖块函数(){当前砖块角度顺时针旋转90度。即角度的索引下标+1;}
F.向左移动函数(){当前砖块x值-1;}
G.向右移动函数(){当前砖块x值+1;}
H.消行函数(){如果某一行全部有颜色,说明这一行满了,将上面的砖块全体下移,并将最上面一行全部置空}
其中我分割成三个文件,代码如下:
砖块类 Brick.java
package sqare;
import java.awt.*;
/**
* 该类为砖块类 定义了游戏中可能出现的所有砖块的形状索引、角度索引、颜色索引、
* 以及其模型各种情况下的模型,规定了游戏中可能出现的所有颜色,定义了获取当前
* 砖块的形状的方法getCurrentShape(),获取角度的方法getCurrentAngle(),获取颜色
* 的方法getCurrentColor(),重置砖块各属性的方法resetBrick()和根据给定的坐标值绘
* 制当前砖块的方法drawCurrentBrick(Graphics g,int begin_x,int begin_y)
*/
public class Brick {
//定义砖块形状索引
public static final int ZERO_SHAPE = 0;
public static final int I_SHAPE = 1;
public static final int T_SHAPE = 2;
public static final int Z_SHAPE = 3;
public static final int S_SHAPE = 4;
public static final int L_SHAPE = 5;
public static final int REVERSE_L_SHAPE = 6;
//定义砖块所处的角度(顺时针旋转)
public static final int ANGLE_90 = 0;
public static final int ANGLE_180 = 1;
public static final int ANGLE_270 = 2;
public static final int ANGLE_360 = 3;
//定义每一个砖块的宽和高
public static final int BRICK_WIDTH = 10;//占10个像素
public static final int BRICK_HEIGHT = 10;
//初始化所有可能出现的砖块的模型
public static final int[][][][] BRICK_MODEL = {
//ZERO_SHAPE型砖块处于不同角度时的形状模型
{
{
{1, 1, 0, 0},
{1, 1, 0, 0}, //ANGLE_90时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{
{1, 1, 0, 0},
{1, 1, 0, 0},
{0, 0, 0, 0}, //ANGLE_180时的模型
{0, 0, 0, 0}
},
{
{1, 1, 0, 0},
{1, 1, 0, 0},
{0, 0, 0, 0}, //ANGLE_270时的模型
{0, 0, 0, 0}
},
{
{1, 1, 0, 0},
{1, 1, 0, 0},
{0, 0, 0, 0}, //ANGLE_360时的模型
{0, 0, 0, 0}
}
},
//I_SHAPE型的砖块处于不同角度时的形状模型
{
{
{1, 0, 0, 0},
{1, 0, 0, 0}, //ANGLE_90时的模型
{1, 0, 0, 0},
{1, 0, 0, 0}
},
{
{1, 1, 1, 1},
{0, 0, 0, 0}, //ANGLE_180时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{
{1, 0, 0, 0},
{1, 0, 0, 0}, //ANGLE_270时的模型
{1, 0, 0, 0},
{1, 0, 0, 0}
},
{
{1, 1, 1, 1},
{0, 0, 0, 0}, //ANGLE_360时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
}
},
//T_SHAPE型的砖块处于不同角度时的形状模型
{
{
{1, 1, 1, 0},
{0, 1, 0, 0}, //ANGLE_90时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{
{0, 1, 0, 0},
{1, 1, 0, 0}, //ANGLE_180时的模型
{0, 1, 0, 0},
{0, 0, 0, 0}
},
{
{0, 1, 0, 0},
{1, 1, 1, 0}, //ANGLE_270时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{
{1, 0, 0, 0},
{1, 1, 0, 0}, //ANGLE_360时的模型
{1, 0, 0, 0},
{0, 0, 0, 0}
}
},
//Z_SHAPE型的砖块处于不同角度时的形状模型
{
{
{1, 1, 0, 0},
{0, 1, 1, 0}, //ANGLE_90时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{
{0, 1, 0, 0},
{1, 1, 0, 0}, //ANGLE_180时的模型
{1, 0, 0, 0},
{0, 0, 0, 0}
},
{
{1, 1, 0, 0},
{0, 1, 1, 0}, //ANGLE_270时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{
{0, 1, 0, 0},
{1, 1, 0, 0}, //ANGLE_360时的模型
{1, 0, 0, 0},
{0, 0, 0, 0}
}
},
//S_SHAPE型的砖块处于不同角度时的形状模型
{
{
{0, 1, 1, 0},
{1, 1, 0, 0}, //ANGLE_90时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{
{1, 0, 0, 0},
{1, 1, 0, 0}, //ANGLE_180时的模型
{0, 1, 0, 0},
{0, 0, 0, 0}
},
{
{0, 1, 1, 0},
{1, 1, 0, 0}, //ANGLE_270时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{
{1, 0, 0, 0},
{1, 1, 0, 0}, //ANGLE_360时的模型
{0, 1, 0, 0},
{0, 0, 0, 0}
}
},
//L_SHAPE型的砖块处于不同角度时的形状模型
{
{
{1, 0, 0, 0},
{1, 0, 0, 0}, //ANGLE_90时的模型
{1, 1, 0, 0},
{0, 0, 0, 0}
},
{
{1, 1, 1, 0},
{1, 0, 0, 0}, //ANGLE_180时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{
{1, 1, 0, 0},
{0, 1, 0, 0}, //ANGLE_270时的模型
{0, 1, 0, 0},
{0, 0, 0, 0}
},
{
{0, 0, 1, 0},
{1, 1, 1, 0}, //ANGLE_360时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
}
},
//REVERSE_L_SHAPE型的砖块处于不同角度时的形状模型
{
{
{0, 1, 0, 0},
{0, 1, 0, 0}, //ANGLE_90时的模型
{1, 1, 0, 0},
{0, 0, 0, 0}
},
{
{1, 0, 0, 0},
{1, 1, 1, 0}, //ANGLE_180时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
},
{
{1, 1, 0, 0},
{1, 0, 0, 0}, //ANGLE_180时的模型
{1, 0, 0, 0},
{0, 0, 0, 0}
},
{
{1, 1, 1, 0},
{0, 0, 1, 0}, //ANGLE_360时的模型
{0, 0, 0, 0},
{0, 0, 0, 0}
}
}
};
//初始化可能出现的砖块的所有颜色
public static final Color[] ALL_COLOR = {
new Color(21, 170, 122), new Color(138, 87, 53), new Color(187, 18, 4), new Color(103, 57, 134),
new Color(17, 174, 45), new Color(158, 33, 139)
};
//当前砖块的形状
public int current_shape;
//当前砖块的颜色
public Color current_color;
//当前砖块颜色对应颜色数组中的索引
public int current_color_index;
//当前砖块的角度
public int current_angle;
//当前砖块所在的坐标值
public int x, y;
//构造函数 初始化砖块的有关属性
public Brick(int shape, int angle, int color_index) {
current_shape = shape % 7;
current_angle = angle % 4;
current_color_index = color_index % 6;
x = -1;
y = -1;
}
//返回当前砖块的形状
public int getCurrentBrickShape() {
return current_shape;
}
//返回当前砖块的角度
public int getCurrentBrickAngle() {
return current_angle;
}
//返回当前砖块的颜色的索引值
public int getCurrentBrickColor() {
return current_color_index;
}
//返回当前砖块的模型
public int[][] getCurrentBrickModle(int shape, int angle) {
return BRICK_MODEL[shape][angle];
}
//重新设置一个砖块
public void resetBrick(int shape, int angle, int color_index) {
current_shape = shape % 7;
current_angle = angle % 4;
current_color_index = color_index % 6;
}
//根据给定的角度旋转砖块
public void turnAngle(int angle) {
current_angle += angle;
current_angle = (current_angle >= 4 ? current_angle % 4 : current_angle);
}
}
砖块形状类 BrickUI.java
package sqare;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class BrickUI extends KeyAdapter {
private JFrame frame;
private JButton startButton;
private Map m;
public void init() {
m = new Map(); //线程实现类
frame = new JFrame("俄罗斯方块");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addKeyListener(this); //界面相应键盘事件
frame.setSize(500, 500);
frame.setLocation(200, 200); //界面大小位置
frame.add(m); //添加游戏界面Panel
frame.setVisible(true);
m.startGame(); //线程开始
}
public void keyPressed(KeyEvent e) { //键盘事件
int keyNum = e.getKeyCode(); //获取键盘代码
switch (keyNum) {
case KeyEvent.VK_LEFT: //左箭头,向左平移
m.moveLeftOrRight(-1);
break;
case KeyEvent.VK_RIGHT: //右箭头,向右平移
m.moveLeftOrRight(1);
break;
case KeyEvent.VK_UP: //上箭头,旋转
m.turnAngle();
break;
case KeyEvent.VK_DOWN: //下箭头,下落
m.drop();
break;
}
}
public static void main(String[] args) {
BrickUI bu = new BrickUI();//创建对象
bu.init(); //初始化;
}
}
地图类 Map.java
package sqare;
import javax.swing.*;
import java.awt.*;
import java.util.Random;
public class Map extends JPanel implements Runnable {
private boolean flag = false;
private boolean flagother = false;
private boolean flagtwo = false;
//定义游戏中最多出现多少行和列
private static final int MAX_ROWS = 24;
private static final int MAX_COLS = 16;
//定义延迟时间 决定游戏的速度
private int delay = 1000;
//该值表示此处为空 没有任何砖块
private static final int NULL_BRICK = -1;
//定义边界
private static final int BORDER = 20;
/**
* 此数组表示游戏进行时的整个面板,当某位置无砖块时数组的值为NULL_BRICK,当有砖块
* 时数组的值为此处砖块颜色的索引值
*/
private int[][] brick_info;
//记录游戏得分
private static int score;
//标识游戏是否结束,true为结束false为未结束
private boolean gameOver = true;
//标识砖块是否已经到达底部,投入额为达到false为未到达
private boolean reachBottom = true;
//显示下一个砖块的横坐标和纵坐标
private int msgX, msgY;
//当前砖块和预览砖块
private Brick brick, next_brick;
private Random ran;
//构造方法,初始化游戏中的一些参数
public Map() {
brick_info = new int[MAX_ROWS][MAX_COLS];//游戏区域
resetBrick_info(); //清空二维数组中的砖块信息
brick = new Brick(0, 0, 0); //产生当前砖块和下一个提示的砖块
next_brick = new Brick(0, 0, 0);
msgX = BORDER + (MAX_COLS + 1) * Brick.BRICK_WIDTH + BORDER;
msgY = BORDER * 3; //提示的下一个砖块的显示位置
ran = new Random();
score = 0;
}
/**
* 重置brick_info数组
*/
private void resetBrick_info() { //清空二维数组中的砖块信息
for (int i = 0; i < MAX_ROWS; i++) {
for (int j = 0; j < MAX_COLS; j++) {
brick_info[i][j] = NULL_BRICK;
}
}
}
/**
* 游戏开始时调用此方法,初始化一些有戏中必须的参数
*/
public void startGame() {
gameOver = false;
reachBottom = false;
score = 0; //变量赋初值
(new Thread(this)).start();
repaint();
}
/**
*此方法用来结束游戏
*/
/**
* 进行游戏的线程,游戏开始时会被调用。当有戏结束标志为false时则继续游戏,若砖块的
* x坐标为-1或到达底部标志为true则重新创建一个砖块并将reachBottom置为false,清空brick_info
* 数组中当前位置,将当前砖块的信息填充到brick_info数组的当前位置。若x坐标不为-1且reachBottom
* 不为true则向下移动砖块,若到达底部则继续下一次循环,否则延时delay毫秒继续循环
*/
public void run() {
while (!gameOver) { //只要游戏没有结束,继续
if (brick.x == -1 || reachBottom) { //只要当前砖块到达界面底部,
createNewBrick(); //产生一个新砖块
reachBottom = false; //将到达底部的变量重置为假
//updataBrick_info(true); //将当前砖块区域清空
//updataBrick_info(false); //根据当前砖块位置重画
} else {
drop(); //砖块下落
}
try {
Thread.sleep(delay); //线程休眠一会
} catch (InterruptedException e) {
}
}
}
/**
* 创建砖块函数,利用下一个砖块的信息重置当前砖块的信息,同时重新生成下一个砖块的信息
* 以备下一次创建砖块使用
*/
synchronized private void createNewBrick() {
if (next_brick.x == -1) {
int shape = ran.nextInt(200);
int angle = ran.nextInt(200);
int color_index = ran.nextInt(200);
next_brick.resetBrick(shape, angle, color_index);
next_brick.x = 0;
} //将下一个砖块的信息赋给当前信息,将下一个砖块变成正在进行的砖块
brick.resetBrick(next_brick.current_shape, next_brick.current_angle, next_brick.current_color_index);
brick.x = 8;
brick.y = 0;
flagother = true;
brick.current_color = Brick.ALL_COLOR[next_brick.current_color_index];//下一砖块颜色赋给当前砖块
int shape1 = ran.nextInt(200);
int angle1 = ran.nextInt(200);
int color_index1 = ran.nextInt(200);//使用随机数,生成形状,角度,颜色
next_brick.resetBrick(shape1, angle1, color_index1); //下一个砖块生成
}
/**
* 清空或填充brick_info数组中当前砖块位置的信息,当清空时,将该处信息全部至为NULL_BRICK
* 当时填充时将当前砖块的颜色索引值填入相应的位置
*/
private void updataBrick_info(boolean clear) {
//clear为真,将临时区域全部清空;
//clear为假,将当前砖块填充到临时区域
int[][] tempBrick = brick.getCurrentBrickModle(brick.current_shape, brick.current_angle);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (tempBrick[i][j] != 0) {
brick_info[brick.y + i][brick.x + j] = clear ? NULL_BRICK : brick.current_color_index;
}
}
}
if (!clear) {
repaint();
}
}
/**
* 定义砖块向下移动的方法,先清空当前砖块在brick_info数组中的信息,然后判断是否可以
* 移动到brick.y+1的位置,如果可以则将brick.y+1,brick.x不变,
* 如果不可以移动,判断brick.y的值是否为0,为0说明游戏结束,将gameOver置为true否则是
* 到达底部,将reachBottom置为true。最后填充brick_info数组检查是否可以消行
*/
public void drop() {
if (brick.x == -1) {
return;
}
updataBrick_info(true);
if (canMove(brick.x, brick.y + 1)) {
brick.y += 1;
} else {
if (brick.y == 0) {
gameOver = true;
} else {
reachBottom = true;
}
}
updataBrick_info(false);
if (reachBottom) {
checkRowAndGetScore();
}
}
/**
* 检查砖块是否可以移动,当x<0时超过左边界返回false。当重叠或者超过右边界或底边界
* 时返回false。不超出任何边界也不重叠返回true
*/
private boolean canMove(int x, int y) {
//超过左边界返回false
if ((x < 0) || (y >= MAX_ROWS)) {
return false;
}
//不能重叠和超过下边界和右边界否则返回false
int[][] tempBrick = brick.getCurrentBrickModle(brick.current_shape, brick.current_angle);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (tempBrick[i][j] != 0) {
if (x + j >= MAX_COLS || y + i >= MAX_ROWS || brick_info[y + i][x + j] != NULL_BRICK) {
return false;
}
}
}
}
return true;
}
/**
* 实现左右移动,当传递参数为-1时代表左移一个单位,参数为1时代表右移一个单位
*/
public void moveLeftOrRight(int left) {
if (brick.x != -1) {
updataBrick_info(true);
if (canMove(brick.x + left, brick.y)) {
updataBrick_info(true);
brick.x += left;
}
}
updataBrick_info(false);
}
/**
* 检查是否有满行,并记录有几行满行,跟据一次削去行数的不同相应的得分也不同,并记录
* 相同颜色的行数,颜色相同的行数不同相应的得分也不同。最后绘制得分削去符合条件的行
*/
public void checkRowAndGetScore() {
int s = 0;
int full_lines_score = 50; //满一行的基础得分
int color_score = 200; //一行颜色相同的基础得分
int full_rows_lines = 0; //满的行数
int same_colors_lines = 0; //颜色相同的行数
boolean full_rows; //标记是否满行
boolean same_colors; //标记是否颜色相同
for (int i = MAX_ROWS - 1; i >= 0; i--) {
full_rows = true;
same_colors = true;
//如果此行内brick_info数组中有NULL_BRICK说明该行不可消除则跳出本行进行下一行检验
for (int j = 0; j < MAX_COLS; j++) {
if (brick_info[i][j] == NULL_BRICK) {
full_rows = false;
break;
}
if (j + 1 <= MAX_COLS - 1 && brick_info[i][j] != brick_info[i][j + 1]) {
same_colors = false;
}
}
if (full_rows == true) { //如果满行,满行数加一,如果颜色相同,颜色数加一并削去该行
full_rows_lines++;
if (same_colors == true) {
same_colors_lines++;
}
for (int z = i; z > 0; z--) { //将上面的砖块全体下移,并将最上面一行全部置空
//brick_info[z]= brick_info[z-1];
System.arraycopy(brick_info[z - 1], 0, brick_info[z], 0, MAX_COLS);
}
for (int n = 0; n < MAX_COLS; n++) {
brick_info[0][n] = NULL_BRICK;
flag = true;
}
i++;
}
}
//记算得分并根据得分更改游戏速度
s += (full_lines_score + full_rows_lines % 4 * 50) * full_rows_lines;
s += (color_score + same_colors_lines % 4 * 100) * same_colors_lines;
this.score += s;
delay -= s / 50;
}
/**
* 绘制游戏边界
*/
private void drawBorder(Graphics g) {
int rightX = BORDER + (MAX_COLS + 1) * Brick.BRICK_WIDTH;//右边界横坐标
int bottomY = BORDER + MAX_ROWS * Brick.BRICK_HEIGHT; //底部边界的纵坐标
int x, y;
g.setColor(Color.BLACK); //设定边界颜色
//绘制左右边界
for (int i = 0; i < MAX_ROWS; i++) {
y = BORDER + i * Brick.BRICK_WIDTH; //一个边界砖块的纵坐标
g.fill3DRect(BORDER, y, Brick.BRICK_WIDTH, Brick.BRICK_HEIGHT, true);
g.fill3DRect(rightX, y, Brick.BRICK_WIDTH, Brick.BRICK_HEIGHT, true);
}
//绘制底部边界
for (int j = 0; j <= MAX_COLS + 1; j++) {
x = BORDER + j * Brick.BRICK_WIDTH; //每一个底部边界砖块的横坐标
g.fill3DRect(x, bottomY, Brick.BRICK_WIDTH, Brick.BRICK_HEIGHT, true);
}
}
/**
* 根据brick_info数组绘制游戏面板中的所有砖块
*/
private void drawBrick(Graphics g) {
int index, x, y;
for (int i = 0; i < MAX_ROWS; i++) {
for (int j = 0; j < MAX_COLS; j++) {
index = brick_info[i][j];
if (index != NULL_BRICK) {
x = BORDER + (j + 1) * Brick.BRICK_WIDTH; //该砖块的横坐标
y = BORDER + i * Brick.BRICK_HEIGHT; //该砖块的纵坐标
g.setColor(Brick.ALL_COLOR[index]);
g.fill3DRect(x, y, Brick.BRICK_WIDTH, Brick.BRICK_HEIGHT, true);
}
}
}
}
/**
* 根据next_brick中的信息绘制出下一个砖块
*/
private void drawNextBrick(Graphics g) {
int[][] msgBrick = next_brick.getCurrentBrickModle(next_brick.current_shape, next_brick.current_angle);
int index = next_brick.current_color_index;
int x, y;
g.setColor(Brick.ALL_COLOR[index]);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (msgBrick[i][j] != 0) {
x = msgX + j * Brick.BRICK_WIDTH;
y = msgY + i * Brick.BRICK_HEIGHT;
g.fill3DRect(x, y, Brick.BRICK_WIDTH, Brick.BRICK_HEIGHT, true);
}
}
}
repaint();
}
/**
* 用来旋转砖块,每次旋转90度,
*/
public void turnAngle() {
if (brick.x != -1) {
updataBrick_info(true);
brick.turnAngle(1);
if (!canMove(brick.x, brick.y)) {
brick.turnAngle(3);
}
}
updataBrick_info(false);
}
/**
* 绘制得分
*/
private void drawScore(Graphics g, int score) {
String str = "得分:" + score;
int x = BORDER + (MAX_COLS + 1) * Brick.BRICK_WIDTH + BORDER;
int y = BORDER + BORDER / 2;
g.setColor(Color.RED);
g.drawString(str, x, y);
}
/**
* 重载JPanel的paint()方法
*/
public void paint(Graphics g) {
super.paint(g);
drawBorder(g);
if (brick.x == -1) {
return;
}
drawBrick(g);
drawNextBrick(g);
drawScore(g, this.score);
}
}