使用Java实现小游戏:俄罗斯方块
使用一个二维数组保存游戏的地图:
// 游戏地图格子,每个格子保存一个方块,数组纪录方块的状态
private State map[][] = new State[rows][columns];
- 1
- 2
- 3
游戏前先将所有地图中的格子初始化为空:
/* 初始化所有的方块为空 */
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
map[i][j] = State.EMPTY;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
玩游戏过程中,我们能够看到界面上的方块,那么就得将地图中所有的方块绘制出来,当然,除了需要绘制方块外,游戏积分和游戏结束的字符串在必要的时候也需要绘制:
/**
* 绘制窗体内容,包括游戏方块,游戏积分或结束字符串
*/
@Override
public void paint(Graphics g) {
super.paint(g);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
if (map[i][j] == State.ACTIVE) { // 绘制活动块
g.setColor(activeColor);
g.fillRoundRect(j * BLOCK_SIZE, i * BLOCK_SIZE + 25,
BLOCK_SIZE - 1, BLOCK_SIZE - 1, BLOCK_SIZE / 5,
BLOCK_SIZE / 5);
} else if (map[i][j] == State.STOPED) { // 绘制静止块
g.setColor(stopedColor);
g.fillRoundRect(j * BLOCK_SIZE, i * BLOCK_SIZE + 25,
BLOCK_SIZE - 1, BLOCK_SIZE - 1, BLOCK_SIZE / 5,
BLOCK_SIZE / 5);
}
}
}
/* 打印得分 */
g.setColor(scoreColor);
g.setFont(new Font("Times New Roman", Font.BOLD, 30));
g.drawString("SCORE : " + totalScore, 5, 70);
// 游戏结束,打印结束字符串
if (!isGoingOn) {
g.setColor(Color.RED);
g.setFont(new Font("Times New Roman", Font.BOLD, 40));
g.drawString("GAME OVER !", this.getWidth() / 2 - 140,
this.getHeight() / 2);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
通过随机数的方式产生方块所组成的几种图形,一般七种图形:条形、田形、正7形、反7形、T形、Z形和反Z形,如生成条形:
map[0][randPos] = map[0][randPos - 1] = map[0][randPos + 1]
= map[0][randPos + 2] = State.ACTIVE;
- 1
- 2
- 3
生成图形后,实现下落的操作。如果遇到阻碍,则不能再继续下落:
isFall = true; // 是否能够下落
// 从当前行检查,如果遇到阻碍,则停止下落
for (int i = 0; i < blockRows; i++) {
for (int j = 0; j < columns; j++) {
// 遍历到行中块为活动块,而下一行块为静止块,则遇到阻碍
if (map[rowIndex - i][j] == State.ACTIVE
&& map[rowIndex - i + 1][j] == State.STOPED) {
isFall = false; // 停止下落
break;
}
}
if (!isFall)
break;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
如果未遇到阻碍,则下落的时候,方块图形整体向下移动一行:
// 图形下落一行
for (int i = 0; i < blockRows; i++) {
for (int j = 0; j < columns; j++) {
if (map[rowIndex - i][j] == State.ACTIVE) { // 活动块向下移动一行
map[rowIndex - i][j] = State.EMPTY; // 原活动块变成空块
map[rowIndex - i + 1][j] = State.ACTIVE; // 下一行块变成活动块
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
向左、向右方向移动时是类似的操作:
/**
* 向左走
*/
private void left() {
// 标记左边是否有阻碍
boolean hasBlock = false;
/* 判断是否左边有阻碍 */
for (int i = 0; i < blockRows; i++) {
if (map[rowIndex - i][0] == State.ACTIVE) { // 判断左边是否为墙
hasBlock = true;
break; // 有阻碍,不用再循环判断行
} else {
for (int j = 1; j < columns; j++) { // 判断左边是否有其它块
if (map[rowIndex - i][j] == State.ACTIVE
&& map[rowIndex - i][j - 1] == State.STOPED) {
hasBlock = true;
break; // 有阻碍,不用再循环判断列
}
}
if (hasBlock)
break; // 有阻碍,不用再循环判断行
}
}
/* 左边没有阻碍,则将图形向左移动一个块的距离 */
if (!hasBlock) {
for (int i = 0; i < blockRows; i++) {
for (int j = 1; j < columns; j++) {
if (map[rowIndex - i][j] == State.ACTIVE) {
map[rowIndex - i][j] = State.EMPTY;
map[rowIndex - i][j - 1] = State.ACTIVE;
}
}
}
// 重绘
repaint();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
向下加速移动时,就是减小每次正常状态下落的时间间隔:
/**
* 向下直走
*/
private void down() {
// 标记可以加速下落
immediate = true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
如何变换图形方向,这里仅使用了非常简单的方法来实现方向变换,当然可以有更优的算法实现方向变换操作,大家可以自己研究:
/**
* 旋转方块图形
*/
private void rotate() {
try {
if (shape == 4) { // 方形,旋转前后是同一个形状
return;
} else if (shape == 0) { // 条状
// 临时数组,放置旋转后图形
State[][] tmp = new State[4][4];
int startColumn = 0;
// 找到图形开始的第一个方块位置
for (int i = 0; i < columns; i++) {
if (map[rowIndex][i] == State.ACTIVE) {
startColumn = i;
break;
}
}
// 查找旋转之后是否有阻碍,如果有阻碍,则不旋转
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (map[rowIndex - 3 + i][j + startColumn] == State.STOPED) {
return;
}
}
}
if (map[rowIndex][startColumn + 1] == State.ACTIVE) { // 横向条形,变换为竖立条形
for (int i = 0; i < 4; i++) {
tmp[i][0] = State.ACTIVE;
for (int j = 1; j < 4; j++) {
tmp[i][j] = State.EMPTY;
}
}
blockRows = 4;
} else { // 竖立条形,变换为横向条形
for (int j = 0; j < 4; j++) {
tmp[3][j] = State.ACTIVE;
for (int i = 0; i < 3; i++) {
tmp[i][j] = State.EMPTY;
}
}
blockRows = 1;
}
// 将原地图中图形修改为变换后图形
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
map[rowIndex - 3 + i][startColumn + j] = tmp[i][j];
}
}
} else {
// 临时数组,放置旋转后图形
State[][] tmp = new State[3][3];
int startColumn = columns;
// 找到图形开始的第一个方块位置
for (int j = 0; j < 3; j++) {
for (int i = 0; i < columns; i++) {
if (map[rowIndex - j][i] == State.ACTIVE) {
startColumn = i < startColumn ? i : startColumn;
}
}
}
// 判断变换后是否会遇到阻碍
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (map[rowIndex - 2 + j][startColumn + 2 - i] == State.STOPED)
return;
}
}
// 变换
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
tmp[2 - j][i] = map[rowIndex - 2 + i][startColumn + j];
}
}
// 将原地图中图形修改为变换后图形
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
map[rowIndex - 2 + i][startColumn + j] = tmp[i][j];
}
}
// 重绘
repaint();
// 重新修改行指针
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (map[rowIndex - i][startColumn + j] != null
|| map[rowIndex - i][startColumn + j] != State.EMPTY) {
rowIndex = rowIndex - i;
blockRows = 3;
return;
}
}
}
}
} catch (Exception e) {
// 遇到数组下标越界,说明不能变换图形形状,不作任何处理
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
当图形下落遇到阻碍时停止,我们就需要判断这时是否有某一行或几行可以消除掉,这时可以先获取每行中方块的个数,然后再进行判断:
int[] blocksCount = new int[rows]; // 记录每行有方块的列数
int eliminateRows = 0; // 消除的行数
/* 计算每行方块数量 */
for (int i = 0; i < rows; i++) {
blocksCount[i] = 0;
for (int j = 0; j < columns; j++) {
if (map[i][j] == State.STOPED)
blocksCount[i]++;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
如果有满行的方块,则消除掉该行方块:
/* 实现有满行的方块消除操作 */
for (int i = 0; i < rows; i++) {
if (blocksCount[i] == columns) {
// 清除一行
for (int m = i; m >= 0; m--) {
for (int n = 0; n < columns; n++) {
map[m][n] = (m == 0) ? State.EMPTY : map[m - 1][n];
}
}
eliminateRows++; // 记录消除行数
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
最后我们再重绘显示积分就可以了。
重复以上的生成图形、图形下落、左右下移动、判断消除行的操作,一个简单的俄罗斯方块就完成了。
运行效果: