最近在学Android开发,一直想找个项目来练练手,前段时间在考试也没有时间,不过那时候就有点想法,就是想做个俄罗斯方块或者贪吃蛇什么的。然后一直到这几天才有时间来写这个项目。
完成这个项目主要有几个问题要处理:
- 边界问题,即如何判断俄罗斯方块是否已经到达边界,主要是在左右移动和下降过程中,判断俄罗斯方块是否已经抵达边界,使其不超越边界。
- 接触问题,即如何判断俄罗斯方块已经与其他俄罗斯方块接触,此时应该停止方块的下落,或者避免方块间重合。
- 旋转问题,俄罗斯方块要旋转很简单,只要用转换公式即可,但问题是如何判断旋转后的位置是否不合法,即有没有可能触及边界,或者与其他俄罗斯方块重合。
- 消去问题,当网格中有一行填满了方块,需要消去此行,并将在其上的所有方块均向下移动一行,更新分数等相关信息。
- 界面显示问题,如何显示下落的俄罗斯方块和静止的俄罗斯方块组,以及下一个即将下落的俄罗斯方块。
- 还有一些比较细节的问题,只要有耐心还是很容易可以解决的,这里就不再赘述了。
下面给出我相应的解决方案:
- 第① 和第②个问题类似,每个俄罗斯方块(TetrisBlock)对象包含四个更小的块单元(BlockUnit), 在处理这两个问题的时候只要在块单元类当中添加判断块单元对象是否接触边界或者其他俄罗斯方块的块单元的方法,然后俄罗斯方块类的判断接触边界或其他俄罗斯方块的方法,只需依次调用该俄罗斯方块对象的所有块单元对象的判断方法,若其中一个块单元接触边界,则该俄罗斯方块接触边界。而边界和方块接触问题都可以大致分为两类,a. 接触两侧边界或在两侧接触其他俄罗斯方块,b. 接触网格底部边界或者方块接触其下的其他俄罗斯方块。这两类问题需要分开处理,因为方块的下落和左右移动是分开进行的,当方块左右移动时不能穿过两侧边界或与其他俄罗斯方块重合,当方块下落时,则不能穿过底部边界或穿过其下的俄罗斯方块。
- 第③个问题,先克隆一个下落的俄罗斯方块,对其进行旋转操作,然后判断其是否超越边界或者与其他俄罗斯方块重合,若其状态合法则将下落的俄罗斯方块进行旋转操作,否则结束操作并返回。
- 第④个问题,用一个数组标记每行块单元个数,并计算每行最多可填入多少个块单元,若某一行已经填满则,删除每个俄罗斯方块中在该行上的块单元,并且将在该行之上的块单元全部向下移动一行。
代码如下:
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<BlockUnit> units = new ArrayList<>(); //方块组成部分
private ArrayList<TetrisBlock> 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){
//遍历俄罗斯方块所有单元,是否均能翻转,若其中一个单元不能,则俄罗斯方块也不能翻转
if(canRotate(b)==false){
return false;
}
}
return true;
}
public boolean canRotate(TetrisBlock other){
/*
* 判断方块是否能够翻转
* @return 若能翻转返回true
* @param 另一俄罗斯方块
*/
for(BlockUnit i:units){
//遍历俄罗斯方块所有单元,是否均能翻转,若其中一个单元不能,则俄罗斯方块也不能翻转
for(BlockUnit j:other.getUnits() ){
if(i.canRotate(j) == false){
return false;
}
}
}
return true;
}
public void move(int x){
/*
* 俄罗斯方块左右移动
*/
// 检查是否接触边界,若接触边界,并且往接触边界移动会超界,故返回
if(checkCollision_X() <0 && x<0||checkCollision_X()>0&&x>0)
return;
//更新移动后的坐标
if(x > 0)
setX(getX() + BlockUnit.UNITSIZE);
else
setX(getX() - BlockUnit.UNITSIZE);
}
public boolean checkCollision_Y() {
/*
* 判断俄罗斯方块是否与其他俄罗斯方块或者边界(底部)接触
* @return 若接触返回true
*/
for(BlockUnit u:units){
//遍历所有单元块判断是否接触底部
if(u.checkOutOfBoundary_Y())
return true;
}
for(TetrisBlock block:blocks){
//判断是否与其他俄罗斯方块接触
if(this == block) {
continue;
}
//判断俄罗斯方块底部是否接触其他俄罗斯方块
if(checkCollision_Y(block))
return true;
}
return false;
}
public int checkCollision_X() {
/*
* 判断俄罗斯方块是否与其他俄罗斯方块或者边界(两侧)接触
* @return 若接触返回true
*/
for(BlockUnit u:units){
//遍历所有单元块判断是否接触两侧
if(u.checkOutOfBoundary_X() != 0)
return u.checkOutOfBoundary_X();
}
for(TetrisBlock block:blocks){
if(this == block)
continue;
//判断俄罗斯方块两侧是否接触其他俄罗斯方块
if(checkCollision_X(block) != 0)
return checkCollision_X(block);
}
return 0;
}
public boolean checkCollision_Y(TetrisBlock other){
/*
* 判断俄罗斯方块是否与其他俄罗斯方块或者边界(底部)接触
* @return 若接触返回true
*/
for(BlockUnit i: units){
//遍历所有单元块判断是否接触底部
for(BlockUnit j:other.units){
if(i == j) {
continue;
}
//判断俄罗斯方块底部是否接触其他俄罗斯方块
if(i.checkVerticalCollision(j))
return true;
}
}
return false;
}
public int checkCollision_X(TetrisBlock other){
/*
* 判断俄罗斯方块是否与其他俄罗斯方块或者边界(两侧)接触
* @return 若接触返回true
*/
for(BlockUnit i: units){
//遍历所有单元块判断是否接触两侧
for(BlockUnit j:other.units){
if(i == j)
continue;
//判断俄罗斯方块两侧是否接触其他俄罗斯方块
if(i.checkHorizontalCollision(j)!=0)
return i.checkHorizontalCollision(j);
}
}
return 0;
}
public TetrisBlock(float x,float y){
/*
* 构造函数
*/
this.x = x;
this.y = y;
blockType = (int)(Math.random() * TYPESUM) + 1; //随机生成一个种类
blockDirection = 1; //默认初始方向
color = (int)(Math.random() * 5) + 1; //随机生成一个颜色
switch(blockType){
case 1:
for(int i=0;i<4;i++){
units.add(new BlockUnit(x + (-2 + i ) * BlockUnit.UNITSIZE , y));
}
break;
case 2:
units.add(new BlockUnit(x + (-1 + 1 ) * BlockUnit.UNITSIZE , y - BlockUnit.UNITSIZE));
for(int i=0;i<3;i++){
units.add(new BlockUnit(x + (-1 + i ) * BlockUnit.UNITSIZE , y ));
}
break;
case 3:
for(int i=0;i<2;i++){
units.add(new BlockUnit(x + (i-1) * BlockUnit.UNITSIZE,y - BlockUnit.UNITSIZE ));
units.add(new BlockUnit(x + (i-1) * BlockUnit.UNITSIZE,y ));
}
break;
case 4:
units.add(new BlockUnit(x + (-1 + 0 ) * BlockUnit.UNITSIZE , y - BlockUnit.UNITSIZE));
for(int i=0;i<3;i++){
units.add(new BlockUnit(x + (-1 + i ) * BlockUnit.UNITSIZE , y ));
}
break;
case 5:
units.add(new BlockUnit(x + (-1 + 2 ) * BlockUnit.UNITSIZE , y - BlockUnit.UNITSIZE));
for(int i=0;i<3;i++){
units.add(new BlockUnit(x + (-1 + i ) * BlockUnit.UNITSIZE , y ));
}
break;
case 6:
for(int i=0;i<2;i++){
units.add(new BlockUnit(x + (-1+i) * BlockUnit.UNITSIZE,y - BlockUnit.UNITSIZE ));
units.add(new BlockUnit(x + i * BlockUnit.UNITSIZE,y ));
}
break;
case 7:
for(int i=0;i<2;i++){
units.add(new BlockUnit(x + i * BlockUnit.UNITSIZE,y - BlockUnit.UNITSIZE ));
units.add(new BlockUnit(x + ( -1 + i )* BlockUnit.UNITSIZE,y ));
}
break;
}
}
public void setX(float x) {
/*
* 设置俄罗斯方块坐标
* @param 新坐标值
*/
float dif_x = x - this.x; //x增量
for (BlockUnit u:units){
//根据增量更新方块单元坐标
u.setX(u.getX() + dif_x);
}
this.x = x;
}
public void setY(float y) {
/*
* 设置俄罗斯方块坐标
* @param 新坐标值
*/
//若纵坐标超界则返回
if(checkCollision_Y())
return;
float dif_y = y - this.y;//y增量
for (BlockUnit u:units){
//根据增量更新方块单元坐标
u.setY(u.getY() + dif_y);
}
this.y = y;
}
public TetrisBlock(TetrisBlock other){
x = other.x;
y = other.y;
color = other.color;
blockDirection = other.blockDirection;
blockType = other.blockType;
blocks = other.blocks;
}
@Override
public TetrisBlock clone(){
TetrisBlock block = new TetrisBlock(this);
for(BlockUnit u:getUnits()){
block.units.add(u.clone());
}
return block;
}
public ArrayList<TetrisBlock> getBlocks() {
return blocks;
}
public void setBlocks(ArrayList<TetrisBlock> blocks) {
this.blocks = blocks;
}
public ArrayList<BlockUnit> getUnits() {
return units;
}
public void setUnits(ArrayList<BlockUnit> units) {
this.units = units;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public int getBlockDirection() {
return blockDirection;
}
public void setBlockDirection(int blockDirection) {
this.blockDirection = blockDirection;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public void rotate(){
if(checkCollision_X()!=0 && checkCollision_Y() || blockType == 3)
return;
//按顺时针旋转
for(BlockUnit u:units){
float tx = u.getX();
float ty = u.getY();
u.setX(-(ty - y) + x);
u.setY( tx - x + y) ;
}
}
}
package cn.jczhuang.tetris2.model;
import cn.jczhuang.tetris2.view.TetrisView;
/**
* Created by Terence on 2016/2/3.
*/
public class BlockUnit implements Cloneable{
/*
* @class 俄罗斯方块单元块,每个俄罗斯方块包含四个单元块
*
*/
public final static float UNITSIZE = 50; //单元块的大小
public static float max_x, max_y;//单元块的最大横纵坐标
private float x,y; //单元块的横纵坐标
public BlockUnit(float x,float y){
/*
* @param 单元块横纵坐标
* 构造函数
*/
this.x = x;
this.y = y;
}
public boolean canRotate(BlockUnit other){
/*
* 判断是否适合进行旋转
* @param 另一单元块对象
* @return 若能旋转返回true,否则false
*/
//超出边界
if(x<TetrisView.beginPoint/2 || x>= TetrisView.max_x - UNITSIZE||y >= TetrisView.max_y - UNITSIZE)
return false;
//与其他单元块重合
if(Math.abs(x-other.x)<=UNITSIZE/2 && Math.abs(y-other.y)<=UNITSIZE/2)
return false;
return true;
}
public boolean checkOutOfBoundary_Y(){
/*
* 判断单元块是否在纵向刚好踩到边界,即y坐标是否刚好踩界,
* 用以判断俄罗斯方块是否能向下移动
* @return 若超界返回true,否则false
*/
if(y >= TetrisView.max_y - UNITSIZE * 2 )
return true;
else if( y - TetrisView.max_y - UNITSIZE * 2 <= 1e-5 && y - TetrisView.max_y - UNITSIZE * 2 >= -1e-5)
return true;
else
return false;
}
public int checkOutOfBoundary_X(){
/*
* 判断单元块是否在横向刚好踩到边界,即x坐标是否刚好踩界,
* 用以判断俄罗斯方块是否能左右移动
* @return 若超界返回true,否则false
*/
if(x<=50 )
return -1;
else if(x >= TetrisView.max_x - UNITSIZE * 2)
return 1;
else
return 0;
}
public boolean checkVerticalCollision(BlockUnit other){
/*
* 判断单元块是否在横向与边界或其他单元块接触
* @param 另一单元块
* @return 若接触返回true
*/
if(y >= TetrisView.max_y - UNITSIZE )
return true;
else if( y - TetrisView.max_y - UNITSIZE >= 1e-5 && y - TetrisView.max_y - UNITSIZE <= -1e-5)
return true;
if(Math.abs(x - other.x) >= UNITSIZE)
return false;
else{
if(Math.abs(y - other.y) > UNITSIZE)
return false;
else if( y- other.y - UNITSIZE < 1e-5 && y- other.y - UNITSIZE > -1e-5)
return true;
return true;
}
}
public int checkHorizontalCollision(BlockUnit other){
/*
* 判断单元块是否在纵向与边界或其他单元块接触
* @param 另一单元块
* @return 若接触返回true
*/
if(x <= 50 || x > TetrisView.max_x - UNITSIZE * 2)
return checkOutOfBoundary_X();
if(Math.abs(y - other.y )>= UNITSIZE)
return 0;
else{
if(Math.abs(x - other.x) > UNITSIZE)
return 0;
else if(x - other.x - UNITSIZE <= 1e-5 && x - other.x - UNITSIZE >= -1e-5)
return -1;
else if(other.x - x - UNITSIZE <= 1e-5 && other.x - x - UNITSIZE >= -1e-5)
return 1;
}
return 0;
}
@Override
public BlockUnit clone(){
return new BlockUnit(getX(),getY());
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
}
package cn.jczhuang.tetris2.view;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Arrays;
import cn.jczhuang.tetris2.Main;
import cn.jczhuang.tetris2.model.BlockUnit;
import cn.jczhuang.tetris2.model.TetrisBlock;
/**
* Created by Terence on 2016/2/3.
*/
public class TetrisView extends View {
/*
* 主画板,用于显示游戏主要部分,即活动部分,俄罗斯方块下降,消去等
*
*/
boolean flag;
public static int beginPoint = 10; //网格开始坐标值,横纵坐标的开始值都是此值
public static int max_x, max_y; //保存俄罗斯方块单元的最大横纵坐标
public static float beginX; //俄罗斯方块初始x坐标
public int dropSpeed = 300; //俄罗斯方块下落线程默认休眠时间
public int currentSpeed = 300; //俄罗斯方块下落线程当前休眠时间
public boolean isRun = true; //标识游戏是否正在进行
public boolean canMove = false; //标识此时俄罗斯方块是否能左右移动
public Thread dropThread ; //游戏主线程
private int[] map = new int[100]; //保存每行网格中包含俄罗斯方块单元的个数
private Main father; //调用此对象的Activity对象
private TetrisBlock fallingBlock; //正在下落的俄罗斯方块
private Thread thread = new Thread();//俄罗斯方块下落线程
private float x1, y1, x2, y2; //保存onTouchEvent中的起始坐标和结束坐标
private ArrayList<TetrisBlock> blocks = new ArrayList<>();
private float h,w;//保存TetrisView的宽和高
public void clear(){
//清空游戏状态
isRun = false;
blocks.clear();
thread = new Thread();
fallingBlock = null;
}
@Override
public boolean onTouchEvent(MotionEvent event){
/*
* 触摸事件,根据滑动方向左右移动俄罗斯方块,向上滑动则旋转俄罗斯方块
*/
if(event.getAction() == MotionEvent.ACTION_DOWN){
//记录触摸滑动事件起始位置坐标
x1 = event.getX();
y1 = event.getY();
}
if(event.getAction() == MotionEvent.ACTION_UP ){
//记录触摸庝事件结束位置坐标
//若此时俄罗斯方块不能左右移动,即不在下落过程,则返回
if(canMove == false)
return false;
x2 = event.getX();
y2 = event.getY();
float tx = fallingBlock.getX();
float ty = fallingBlock.getY();
if(x1 - x2 > 50){
//向左滑动
fallingBlock.move(-1);
//更新界面
father.runOnUiThread(new Runnable() {
@Override
public void run() {
TetrisView.this.invalidate();
}
});
}else if(x2 - x1 > 50){
//向右滑动
fallingBlock.move(1);
//更新界面
father.runOnUiThread(new Runnable() {
@Override
public void run() {
TetrisView.this.invalidate();
}
});
}else if(y1 - y2 > 50){
//向上滑动,旋转俄罗斯方块
fallingBlock.rotate();
//更新界面
father.runOnUiThread(new Runnable() {
@Override
public void run() {
TetrisView.this.invalidate();
}
});
}
}
return true;
}
public void init(){
/*
* 游戏设置初始化
*/
dropSpeed = 300; //俄罗斯方块下落线程默认休眠时间
currentSpeed = 300; //俄罗斯方块下落线程当前休眠时间
Arrays.fill(map, 0); //每行网格中包含俄罗斯方块单元的个数全部初始化为0
flag = true; //第一次进入线程循环
isRun = true; //游戏正在运行
dropThread=new Thread(new Runnable() {
/*
* 游戏主线程
*/
@Override
public void run() {
while(isRun) {
try {
//初始化各参数
Thread.sleep(3000);
h = getHeight();
w = getWidth();
beginX = (int)((w - beginPoint)/100) * 50 + beginPoint;
} catch (Exception e) {
e.printStackTrace();
}
if(flag) {
//第一次进入线程循环,创建第一个俄罗斯方块
father.nextBlockView.createNextBlock();
father.runOnUiThread(new Runnable() {
//更新ui
@Override
public void run() {
father.nextBlockView.invalidate();
}
});
flag = false; //下次循环不在执行此块操作
}
if(thread.getState() == Thread.State.TERMINATED || thread.getState() == Thread.State.NEW) {
//如果线程新创建或者已经结束,则重新创建新线程
thread = new Thread(new Runnable() {
@Override
public void run() {
h = getHeight();
w = getWidth();
for(TetrisBlock b:blocks){
//判断游戏是否应该结束
if(b.getY()<=BlockUnit.UNITSIZE)
isRun = false;
}
fallingBlock = father.nextBlockView.nextBlock; //跟新当前线程
father.nextBlockView.createNextBlock(); //创建新的后备线程
father.runOnUiThread(new Runnable() {
//更新ui
@Override
public void run() {
father.nextBlockView.invalidate();
}
});
fallingBlock.setBlocks(blocks); //设置全局俄罗斯方块
float ty;
int end = (int) ((h - 50 - beginPoint) / BlockUnit.UNITSIZE);
float dropCount = fallingBlock.getY(); //用以记录正常下落情况下y坐标的值,即不考虑碰撞情况
canMove = true; //俄罗斯方块开始下落
while (dropCount-fallingBlock.getY()<=2 * BlockUnit.UNITSIZE) {
//若dropCount即y坐标的理想值与y坐标的准确值相差不到两个方块的大小,
// 说明俄罗斯方块仍在下落,否则说明发生触碰事件,停止下落,跳出循环
try {
Thread.sleep(currentSpeed);
//更新相应坐标值
ty = fallingBlock.getY();
ty = ty + BlockUnit.UNITSIZE;
dropCount += BlockUnit.UNITSIZE;
fallingBlock.setY(ty);
father.runOnUiThread(new Runnable() {
//更新ui
@Override
public void run() {
TetrisView.this.invalidate();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
canMove = false;//俄罗斯方块结束下落
blocks.add(fallingBlock); //讲俄罗斯方块加入静止的俄罗斯方块数组中
TetrisBlock temp = fallingBlock;
for (BlockUnit u : temp.getUnits()) {
//更新map,即更新每行网格中静止俄罗斯方块单元的个数
int index = (int) ((u.getY() - beginPoint) / 50); //计算所在行数
map[index]++;
}
//每行最大个数
int full = (int) ((w - 50 - beginPoint) / BlockUnit.UNITSIZE) + 1;
for (int i = 0; i <= end; i++) {
if (map[i] >= full) {
//若某行达到最大个数则消去此行并更新分数等级等信息
father.scoreValue += 100;
if(father.scoreValue > 1000) {
father.speedValue += 1;
father.levelValue += 1;
}
if(father.scoreValue>father.maxScoreValue){
father.maxScoreValue = father.scoreValue;
}
//将被消去行上的所有俄罗斯方块向下移动一行
map[i] = 0;
for (int j = i; j > 0; j--)
map[j] = map[j - 1];
map[0] = 0;
//消去此行
for (TetrisBlock b : blocks)
b.remove(i);
for (int j = blocks.size()-1; j>=0; j--) {
if (blocks.get(j).getUnits().isEmpty()) {
blocks.remove(j);
continue;
}
for (BlockUnit u : blocks.get(j).getUnits()) {
if ((int) ((u.getY() - beginPoint) / 50) < i)
u.setY(u.getY() + BlockUnit.UNITSIZE);
}
}
father.runOnUiThread(new Runnable() {
@Override
public void run() {
//更新ui
father.score.setText(father.scoreString + father.scoreValue);
father.maxScore.setText(father.maxScoreString + father.maxScoreValue);
father.speed.setText(father.speedString + father.speedValue);
father.level.setText(father.levelString + father.levelValue);
TetrisView.this.invalidate();
}
});
}
}
}
});
thread.start();
}
}
if(isRun == false){
//游戏结束
father.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(father,"game over",Toast.LENGTH_SHORT).show();
}
});
}
}
});
dropThread.start();
}
@Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
max_x = getWidth();
max_y = getHeight();
canvas.drawColor(Color.WHITE);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
RectF rel;
float size = BlockUnit.UNITSIZE;
//俄罗斯方块颜色数组
int color[] = {0,Color.BLUE,Color.RED,Color.YELLOW,Color.GREEN,Color.GRAY};
if(!blocks.isEmpty()){
//绘出所有静止的俄罗斯方块
for(TetrisBlock block:blocks){
paint.setColor(color[block.getColor()]); //设置画笔为俄罗斯方块的颜色
for(BlockUnit u:block.getUnits()){
float tx = u.getX();
float ty = u.getY();
rel = new RectF(tx, ty, tx + size, ty + size); //将方块画成圆角矩形的形式
canvas.drawRoundRect(rel, 8, 8, paint);
}
}
}
if(fallingBlock!=null) {
//绘制正在下落的俄罗斯方块
paint.setColor(color[fallingBlock.getColor()]);
for (BlockUnit u : fallingBlock.getUnits()) {
float tx = u.getX();
float ty = u.getY();
rel = new RectF(tx, ty, tx + size, ty + size);
canvas.drawRoundRect(rel, 8, 8, paint);
}
}
paint.setColor(Color.LTGRAY);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
//绘制网格
for(int i=beginPoint; i<max_x-50; i+= 50){
for(int j=beginPoint; j<max_y-50; j+= 50) {
rel = new RectF(i, j, i + 50, j + 50);
canvas.drawRoundRect(rel, 8, 8, paint);
}
}
}
public TetrisBlock getFallingBlock() {
return fallingBlock;
}
public float getH() {
return h;
}
public void setH(float h) {
this.h = h;
}
public void setFallingBlock(TetrisBlock fallingBlock) {
this.fallingBlock = fallingBlock;
}
public Activity getFather() {
return father;
}
public void setFather(Main father) {
this.father = father;
}
public TetrisView(Context context) {
super(context);
}
public TetrisView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
package cn.jczhuang.tetris2.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import cn.jczhuang.tetris2.model.BlockUnit;
import cn.jczhuang.tetris2.model.TetrisBlock;
/**
* Created by Terence on 2016/2/5.
*/
public class ShowNextBlockView extends View {
/*
* 用以显示下一个俄罗斯方块
*/
public TetrisBlock nextBlock = null; //保存下一个俄罗斯方块
public TetrisBlock createNextBlock(){
/*
* 创建下一个俄罗斯方块
*/
nextBlock = new TetrisBlock(TetrisView.beginX,TetrisView.beginPoint);
return nextBlock;
}
@Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
//画布画笔初始化
canvas.drawColor(Color.WHITE);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
RectF rel;
float size = BlockUnit.UNITSIZE;
//方块颜色
int color[] = {0,Color.BLUE,Color.RED,Color.YELLOW,Color.GREEN,Color.GRAY};
if(nextBlock!=null) {
//画出下一个俄罗斯方块
paint.setColor(color[nextBlock.getColor()]);
for (BlockUnit u : nextBlock.getUnits()) {
//设置填充风格
paint.setStyle(Paint.Style.FILL);
paint.setColor(color[nextBlock.getColor()]);
//获取每个方块的横纵坐标
float tx = (float)(u.getX() - TetrisView.beginX + BlockUnit.UNITSIZE * 1.5);
float ty = u.getY() + BlockUnit.UNITSIZE ;
//创建圆角矩形
rel = new RectF(tx, ty, tx + size, ty + size);
canvas.drawRoundRect(rel, 8, 8, paint);
//画出边界
paint.setColor(Color.LTGRAY);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
canvas.drawRoundRect(rel, 8, 8, paint);
}
}
}
public ShowNextBlockView(Context context) {
/*
* 构造函数
*/
super(context);
}
public ShowNextBlockView(Context context, AttributeSet attrs) {
/*
* 构造函数
*/
super(context, attrs);
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#1a98df"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="right"
android:layout_margin="3dp"
android:background="@drawable/ic_reply_white_48dp"
android:gravity="right" />
<TextView
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_weight="1"
android:background="#1a98df"
android:gravity="center"
android:text="俄罗斯方块"
android:textColor="#ffffff"
android:textSize="20dp" />
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="right"
android:layout_margin="3dp"
android:background="@drawable/ic_send_white_48dp"
android:gravity="right" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#ffffff"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<cn.jczhuang.tetris2.view.TetrisView
android:id="@+id/tetrisView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:orientation="vertical"
android:layout_height="wrap_content">
<cn.jczhuang.tetris2.view.ShowNextBlockView
android:id="@+id/nextBlockView"
android:layout_width="250px"
android:layout_height="250px"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/score"
android:textSize="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="分数:" />
<TextView
android:id="@+id/level"
android:textSize="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="等级:level " />
<TextView
android:id="@+id/speed"
android:textSize="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="速度:" />
<TextView
android:id="@+id/maxScore"
android:textSize="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="最高分:" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<Button
android:id="@+id/left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="left" />
<Button
android:id="@+id/right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="right" />
<Button
android:id="@+id/rotate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="rotate" />
<Button
android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="start" />
</LinearLayout>
<Button
android:id="@+id/speedUp"
android:text="speedUp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</LinearLayout>
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();
}
}