最近在学Android开发,一直想找个项目来练练手,前段时间在考试也没有时间,不过那时候就有点想法,就是想做个俄罗斯方块或者贪吃蛇什么的。然后一直到这几天才有时间来写这个项目。
完成这个项目主要有几个问题要处理:
① 边界问题,即如何判断俄罗斯方块是否已经到达边界,主要是在左右移动和下降过程中,判断俄罗斯方块是否已经抵达边界,使其不超越边界。
② 接触问题,即如何判断俄罗斯方块已经与其他俄罗斯方块接触,此时应该停止方块的下落,或者避免方块间重合。
③ 旋转问题,俄罗斯方块要旋转很简单,只要用转换公式即可,但问题是如何判断旋转后的位置是否不合法,即有没有可能触及边界,或者与其他俄罗斯方块重合。
④ 消去问题,当网格中有一行填满了方块,需要消去此行,并将在其上的所有方块均向下移动一行,更新分数等相关信息。
⑤ 界面显示问题,如何显示下落的俄罗斯方块和静止的俄罗斯方块组,以及下一个即将下落的俄罗斯方块。
⑥ 还有一些比较细节的问题,只要有耐心还是很容易可以解决的,这里就不再赘述了。
** 下面给出我相应的解决方案:**
** ** 首先,看下我的项目文件框架图:
然后,我在解释下每个类的作用:
第① 和第②个问题类似,每个俄罗斯方块(TetrisBlock)对象包含四个更小的块单元(BlockUnit), 在处理这两个问题的时候只要在块单元类当中添加判断块单元对象是否接触边界或者其他俄罗斯方块的块单元的方法,然后俄罗斯方块类的判断接触边界或其他俄罗斯方块的方法,只需依次调用该俄罗斯方块对象的所有块单元对象的判断方法,若其中一个块单元接触边界,则该俄罗斯方块接触边界。而边界和方块接触问题都可以大致分为两类,a. 接触两侧边界或在两侧接触其他俄罗斯方块,b. 接触网格底部边界或者方块接触其下的其他俄罗斯方块。这两类问题需要分开处理,因为方块的下落和左右移动是分开进行的,当方块左右移动时不能穿过两侧边界或与其他俄罗斯方块重合,当方块下落时,则不能穿过底部边界或穿过其下的俄罗斯方块。
第③个问题,先克隆一个下落的俄罗斯方块,对其进行旋转操作,然后判断其是否超越边界或者与其他俄罗斯方块重合,若其状态合法则将下落的俄罗斯方块进行旋转操作,否则结束操作并返回。
第④个问题,用一个数组标记每行块单元个数,并计算每行最多可填入多少个块单元,若某一行已经填满则,删除每个俄罗斯方块中在该行上的块单元,并且将在该行之上的块单元全部向下移动一行。
最后的效果大概这样
Paste_Image.png
能想到这个问题的解决方案就可以很轻松的开始敲代码了。
package cn.jczhuang.tetris2;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import cn.jczhuang.tetris2.model.BlockUnit;
import cn.jczhuang.tetris2.model.TetrisBlock;
import cn.jczhuang.tetris2.view.TetrisView;
import cn.jczhuang.tetris2.view.ShowNextBlockView;
public class Main extends Activity {
public Button left, right, rotate, start, speedUp; //按钮
public TextView score, maxScore, level, speed; //标签
public int scoreValue,maxScoreValue,levelValue,speedValue; //标签值
public String scoreString = "分数:",maxScoreString = "最高分:",levelString = "等级:",speedString = "速度:";
public TetrisView view;
public ShowNextBlockView nextBlockView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 获取各组件和标签值
view = (TetrisView)findViewById(R.id.tetrisView);
left = (Button)findViewById(R.id.left);
right = (Button)findViewById(R.id.right);
rotate = (Button)findViewById(R.id.rotate);
start = (Button)findViewById(R.id.start);
speedUp = (Button)findViewById(R.id.speedUp);
nextBlockView = (ShowNextBlockView)findViewById(R.id.nextBlockView);
nextBlockView.invalidate();
score = (TextView)findViewById(R.id.score);
maxScore = (TextView)findViewById(R.id.maxScore);
level = (TextView)findViewById(R.id.level);
speed = (TextView)findViewById(R.id.speed);
scoreValue = maxScoreValue =0;
levelValue = speedValue = 1;
score.setText(scoreString + scoreValue);
level.setText(levelString + levelValue);
speed.setText(speedString + speedValue);
maxScore.setText(maxScoreString + maxScoreValue);
//设置各按钮的监听器
left.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (view.canMove)
view.getFallingBlock().move(-1);
runOnUiThread(new Runnable() {
@Override
public void run() {
view.invalidate();
}
});
}
});
right.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (view.canMove)
view.getFallingBlock().move(1);
runOnUiThread(new Runnable() {
@Override
public void run() {
view.invalidate();
}
});
}
});
rotate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (view.canMove == false)
return;
TetrisBlock copyOfFallingBlock = view.getFallingBlock().clone();
copyOfFallingBlock.rotate();
if (copyOfFallingBlock.canRotate()) {
TetrisBlock fallinBlock = view.getFallingBlock();
fallinBlock.rotate();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
view.invalidate();
}
});
}
});
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
view.init();
}
});
speedUp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (view.canMove) {
view.getFallingBlock().setY(view.getFallingBlock().getY() + BlockUnit.UNITSIZE);
runOnUiThread(new Runnable() {
@Override
public void run() {
view.invalidate();
}
});
}
}
});
view.setFather(this);
view.invalidate();
}
}
package cn.jczhuang.tetris2.model;
import java.util.ArrayList;
import cn.jczhuang.tetris2.view.TetrisView;
/**
* Created by Terence on 2016/2/3.
*/
public class TetrisBlock implements Cloneable{
/*
* 俄罗斯方块
*/
public final static int TYPESUM = 7; //方块种类总数
public final static int DIRECTIONSUM = 4; //每个方块有四个方向
private int blockType,blockDirection; //方块种类,方块朝向
private int color; //方块颜色
private float x, y; //方块坐标
private ArrayList units = new ArrayList<>(); //方块组成部分
private ArrayList blocks = new ArrayList<>(); //所有俄罗斯方块
public void remove(int j){
/*
* 删除在第j行上的方块单元
* @param 需删除行标
*/
for(int i=units.size()-1;i>=0;i--){
/*
* ①逆向遍历
* ②根据y坐标计算单元所在行,若为j行则从units中删除
*/
if((int)((units.get(i).getY()- TetrisView.beginPoint)/50) == j)
units.remove(i);
}
}
public boolean canRotate(){
/*
* 判断方块是否能够翻转
* @return 若能翻转返回true
*/
for(TetrisBlock b:blocks){
//遍历俄罗斯方块所有单元,是否均能翻转,若其中一个单元不能,则俄罗斯方块