寒假里初步自学了Java。该项目是一个简单的自我检验。编写代码的时候发现游戏实现起来不算太难,所以增加了一些难度让程序能够自己玩俄罗斯方块。
游戏部分
这里部分实现起来实际上并不复杂,实现的时候尽力遵循了MVC模型。
handler
handler
TetrisMove类 监听键盘输入
Game类
Lattices类
Draw类
WDSA控制图形,handler则相当于他们对应的处理码。这里handler作为Game类的一个静态变量存在,这样做的好处是当获取到键盘信息时TetrisMove类可以立即直接改变handler的值。
Game类定时将handler传递给Lattice类,该类储存了格点数组,包含了用处理其变化的函数。获得handler值后,会调用相应的函数,完成移动,变化,加速动作。
实际上还有Pattern类,其代码如下:
package Pattern;
/**
* 有关坐标系的问题,向下向右为正
*/
public class Pattern {
public boolean[][] mat = new boolean[4][4];
public int top,buttom,left,right;
public int type;
public int currtype = 0;
public Pattern(boolean[][] mat, int type) {
super();
this.mat = mat;
this.type = type;
calRegion();
}
private void calRegion() {
top = 3;
left = 3;
right = 0;
buttom = 0;
for(int i = 0;i < 4; i++) {
for(int j = 0;j < 4; j++) {
if(mat[i][j]) {
if(top>i) {
top = i;
}
if(left>j) {
left = j;
}
if(buttom<i) {
buttom = i;
}
if(right<j) {
right = j;
}
}
}
}
}
public void print_info() {
System.out.println("top = " +top);
System.out.println("left = " +left);
System.out.println("buttom = " +buttom);
System.out.println("right = " +right);
System.out.println("currtype = " +currtype);
}
public void rotate() {
boolean[][] tem = new boolean[4][4];
for(int i = 0;i < 4; i++) {
for(int j = 0;j < 4; j++) {
tem[3-j][i] = mat[i][j];
}
}
mat = tem;
currtype = (currtype + 1)%type;
calRegion();
}
public void derotate() {
boolean[][] tem = new boolean[4][4];
for(int i = 0;i < 4; i++) {
for(int j = 0;j < 4; j++) {
tem[j][3-i] = mat[i][j];
}
}
mat = tem;
currtype = (type+(currtype - 1))%type;
calRegion();
}
}
在该类中规定了每个图案的默认大小为4x4,用top,right,buttom,left控制其有效区域,type这个参数则是为了之后AI类使用而保留的。
图案的变化在代码中是利用旋转实现的即tem[3-j][i] = mat[i][j];
和tem[j][3-i] = mat[i][j];
。
AI部分
设计AI的时候,首先想到的方案是图案出现时先进行左右平移,然后下落,在所有结果中找出最优的方案。因为俄罗斯方块的游戏难度实际上不大,该方法的时间复杂度只与格点数组的宽度相关,所以直接就选择了该方法。
(实际上该方法并不能一定找到最优的方案,因为有些区域需要先下落一段距离再左右平移才可以到达,但是考虑到只是实现一个简单的AI,让游戏不死即可,我就没有再做进一步的优化)
然后的问题是怎么在所有结果中找最后解,一个朴素的方法就是计分。利用格点数组对一些方面进行计分,选择分数最高或者最低的结果。
我根据自己对这个游戏的理解,得出以下结论:
- 方块数量应该越少越好
- 方块位置应该越低越好
- 应该避免出现被方块封闭的空格
根据以上三点,定义了三组变量:
private int Scoreblock = 1000;//每一个空格的分数
private int[] ScoreRow ; //方块在某一行的分数
-
private int ScoreSide = 100;//空格左边是方块时的分数
和private int ScoreUP = 5000;//空格上边是方块时的分数
分数的值需要进行尝试。
下面是AImove的完整代码:
package Game;
import java.util.ArrayList;
import Pattern.TetrisLattice;
public class TetrisAI {
private int width;
private int height;
private TetrisLattice AILattice;
private int[] ScoreRow ; //方块在某一行的分数
private int ScoreSide = 100;//空格左边是方块时的分数
private int ScoreUP = 5000;//空格上边是方块时的分数
private int Scoreblock = 1000;//每一个空格的分数
public ArrayList<Integer> HandlerList = new ArrayList<Integer>();
public TetrisAI(int width, int height) {
super();
this.width = width;
this.height = height;
ScoreRow = new int[height];
setScoreRow();
}
public void test_print(int a) {
System.out.println(a);
}
public void calcWay(TetrisLattice lattices) {
HandlerList.clear();
int minScore = 0x7FFFFFFF;
int handler = 0;
int k = 0;
int typetimes = 0;
for(int i= -width+1;i<width;i++) {
AILattice = lattices.copy();
int type = AILattice.getPatternType();
for(int t = 0;t<type;t++) {
k=i;
AILattice = lattices.copy();
for(int kt = 0;kt<t;kt++) {
AILattice.Handler(0);
}
while(k!=0){
if(k<0) {
AILattice.Handler(3);
k++;
}
else {
AILattice.Handler(1);
k--;
}
}
while(!AILattice.blockFall());
int Score = calcScore();
if(minScore>Score) {
minScore = Score;
handler = i;
typetimes = t;
}
}
}
for(int i=0;i<typetimes;i++) {
HandlerList.add(0);
}
if(handler<0) {
HandlerList.add(3);
}
else if(handler>0) {
HandlerList.add(1);
}
}
private int calcScore() {
int score = 0;
for(int i=0;i<height;i++) {
for(int j=0;j<width;j++) {
if(AILattice.Lattices[i][j]==2) {
score+= Scoreblock-ScoreRow[i];
}
else if(AILattice.Lattices[i][j]==0) {
score += getsurroundScore(i, j);
}
}
}
return score;
}
private void setScoreRow() {
//ScoreRow数组的循序与Lattice的循序一致
for(int i=0;i<height;i++) {
ScoreRow[i] = i*10;
}
}
private int getsurroundScore(int y,int x) {
//空格周围的分数
int score = 0;
if(x==0) {
score += ScoreSide;
if(AILattice.Lattices[y][x+1]==2) {
score += ScoreSide;
}
}
else if(x==(width-1)) {
score += ScoreSide;
if(AILattice.Lattices[y][x-1]==2) {
score += ScoreSide;
}
}
else {
if(AILattice.Lattices[y][x-1]==2) {
score += ScoreSide;
}
if(AILattice.Lattices[y][x+1]==2) {
score += ScoreSide;
}
}
if(y!=0&&AILattice.Lattices[y-1][x]==2) {
score += ScoreUP;
}
return score;
}
}
代码