当前位置 博文首页 > 十二翼堕落天使:【Swing】——俄罗斯方块
该俄罗斯方块版本是教主第二次写的,也算是一次重构,主要有三个目标:
我们发现大部分俄罗斯方块都是由10x20的小格子组成,然后有7种类型的方块,每种类型方块的对应1~4种可以由旋转而得到的形状。
于是我们可以设置一个10x20的布尔矩阵作为全局矩阵,然后以4x4的布尔矩阵来实现方块。方块的移动即是方块矩阵在全局矩阵上的偏移;方块的旋转即是单位矩阵的旋转。方块的创建可以采用原型模型,创建7种方块的原型,新方块都克隆自这7种原型方块。
综上,俄罗斯方块游戏需要的类有:
BoolMatrix
):将二维数组封装为矩阵,以方便其它类的访问。Tetris
)GameHandler
):定义数据获取和事件处理的接口,目的是为了以接口的方式解开逻辑与渲染的耦合。Game
):继承自GameHandler
,实现具体的游戏逻辑。GameView
):继承自JFrame
,组织各种组件以及负责事件的监听。GamePanel
:继承自JPanel
,负责渲染界面。App
可以发现,俄罗斯方块本身的逻辑其实也就哪些,而这些逻辑好像又的确与视图的渲染没什么直接的关系。
也就是说,只要定义好俄罗斯方块游戏逻辑的访问接口(包括但不限于事件的处理、数据的读取),任何按照一定约定来访问该接口的渲染组件,都能够产生正确的交互并渲染出正确的界面。
GameHandler
)中的全局矩阵的宽高为12x22?大道至简。
如果设置为10x20个小格子,那么除了碰撞问题还要处理移动越界的问题。而如果加一层值为1的边框,那么就可以统一的用碰撞问题去处理。
会。
但即便这样设计会增加不必要的循环和判断,视图约定以0.5秒的间隔去刷新和访问,对于逻辑运算和界面渲染来说也已经绰绰有余。另外Swing中的监听是单独的线程,但是触发的事件会被放入队列中,不存在多线程访问的同步问题。
会。
可以使用享元模式来管理方块。将创建的方块放入享元工厂中就不用一直创建方块了。
Cloneable
接口并重写Clone()
方法,对内置的二维数组进行深克隆。public class BoolMatrix implements Cloneable {
// 内置二维数组
private byte[][] array;
// 矩阵的行数
private int rows;
// 矩阵的列数
private int columns;
public BoolMatrix(int rows, int columns) {
this.rows = rows;
this.columns = columns;
this.array = new byte[rows][columns];
}
/**
* 对于传入的二维数组将会进行复制操作以使得矩阵不依赖实参
* @param array 二维数组
*/
public BoolMatrix(byte[][] array) {
this.rows = array.length;
this.columns = array[0].length;
this.array = new byte[this.rows][this.columns];
for (int i = 0; i < this.rows; i++) {
System.arraycopy(array[i], 0, this.array[i], 0, this.columns);
}
}
/**
* 判断源矩阵经偏移后是否与目标矩阵重合
* @param source 源矩阵
* @param target 目标矩阵
* @param xOffset 源矩阵在目标矩阵上的水平偏移量
* @param yOffset 源矩阵在目标矩阵上的垂直偏移量
* @return 源矩阵经偏移后是否与目标矩阵重合
*/
public static boolean overlapped(BoolMatrix source, BoolMatrix target, int xOffset, int yOffset) {
for (int i = 0; i < source.rows; i++) {
for (int j = 0; j < source.columns; j++) {
if (source.get(i, j) == 1 && target.get(i + yOffset, j + xOffset) == 1) {
return true;
}
}
}
return false;
}
/**
* 将源矩阵经偏移后或运算到目标矩阵上
* 修改的是目标矩阵
* @param source 源矩阵
* @param target 目标矩阵
* @param xOffset 源矩阵在目标矩阵上的水平偏移量
* @param yOffset 源矩阵在目标矩阵上的垂直偏移量
*/
public static void or(BoolMatrix source, BoolMatrix target, int xOffset, int yOffset) {
for (int i = 0; i < source.rows; i++) {
for (int j = 0; j < source.columns; j++) {
if (source.get(i, j) == 1) {
target.set(i + yOffset, j + xOffset, (byte) 1);
}
}
}
}
public byte get(int i, int j) {
return this.array[i][j];
}
public void set(int i, int j, byte value) {
this.array[i][j] = value;
}
/**
* 矩阵右旋
* @throws Exception 行列不相等的矩阵不支持旋转
*/
public void rotateRight() throws Exception {
if (this.rows != this.columns) {
throw new Exception("Rotate not supported");
} else {
int len = this.rows;
for (int i = 0; i < len; i++) {
for (int j = 0; j < len - i; j++) {
byte temp = this.array[i][j];
this.array[i][j] = this.array[len - j - 1][len - i - 1];
this.array[len - j - 1][len - i - 1] = temp;
}
}
for (int i = 0; i < len / 2; i++) {
for (int j = 0; j < len; j++) {
byte temp = this.array[i][j];
this.array[i][j] = this.array[len - i - 1][j];
this.array[len - i - 1][j] = temp;
}
}
}
}
/**
* 矩阵左旋
* @throws Exception 行列不相等的矩阵不支持旋转
*/
public void rotateLeft() throws Exception {
if (this.rows != this.columns) {
throw new Exception("Rotate not supported");
} else {
int len = this.rows;
for (int i = 0; i < len; i++) {
for (int j = i; j < len; j++) {
byte temp = this.array[i][j];
this.array[i][j] = this.array[j][i];
this.array[j][i] = temp;
}
}
for (int i = 0; i < len; i++) {
for (int j = 0; j < len / 2; j++) {
byte temp = this.array[i][j];
this.array[i][j] = this.array[i][len - j - 1];
this.array[i][len - j - 1]