Android 俄罗斯方块 附源码

思路:

  • 首先要画出游戏背景墙;
  • 其次,要有方块,以及方块单元;
  • 方块的不同形状,颜色随机产生;
  • 游戏的控制面板。

可能会出现的问题或者难点:

  • 边界问题:
    ①处于边界的时候,方块不可以再左右移动;
    ②下降的时候,到达边界即底部,则不可继续下落,此时应该产生一个新的方块;
  • 与其它方块接触问题:
    ①下落的时候,如果碰到其它的方块则停止下落;
    ②左右移动的时候,移动的过程中,如果接触到其他方快,则不可再继续左右移动;
  • 方块的消除:
    ①调用方块消除方法的时间:当方块下落到底部的时候,判断是否有需要消除的行;
    ②消除某一行之后,应该把这一行上面的全部方块下移一行;
  • 方块的旋转:
    在当前项目中,我采用的是顺时针旋转。
    ①当旋转的时候,如果出现方块部分超出了边界,应该对方块进行平移,使其回到边界以内。(曾在网上看到有人做过,判断旋转之后是否会超出边界,如果会超出,则不进行旋转,我觉得不好,方块只要没有下落到底部,我觉得都可以进行旋转,除了没有空间让其旋转外);
    ②如果空间不足以旋转,也不可以旋转。空间不足以旋转的意思是:比如横向方向只有两个的空间,而方块旋转后会占用三个空间,此时也不可进行旋转;
    ③当无法继续下落或者下落到了底部也不可再进行旋转
  • 控制面板:
    ①游戏开始、暂停、继续、结束,这些状态应该怎么去控制,以及游戏与控制台的事件关联。
  • 未发现的问题:
    因为本人能力,只做到这么多,如果有人发现问题,可以留言交流,欢迎挑问题。

游戏的运行界面如下所示,基本的功能以及操作很简单。

这里写图片描述

下面直接看项目代码
项目文件结构

这里写图片描述


下面分别介绍每个类的功能

  • TetrisViewAW.java游戏的主界面,背景墙以及方块都在此TetrisViewAW.Java里面,就是一个自定义的View ,(默认大家对于自定义View是熟悉的),在改类里面,有一个游戏主线程,用于控制游戏的开始,暂停,继续,停止,以及方块下落的速率。代码我加了很多注释,看不懂的可以留言。还有一点需要注意,当停止游戏时,要释放线程,养成好习惯
/**
 * 俄罗斯方块Game主界面
 * 
 * @sign Created by wang.ao on 2017年1月12日
 */
@SuppressLint("DrawAllocation")
public class TetrisViewAW extends View {
    /** 网格开始坐标值,横纵坐标的开始值都是此值 */
    public static final int beginPoint = 10;
    /** 俄罗斯方块的最大坐标 */
    private static int max_x, max_y;
    /** 行数和列数 */
    private static int num_x = 0, num_y = 0;
    /** 背景墙画笔 */
    private static Paint paintWall = null;
    /** 俄罗斯方块的单元块画笔 */
    private static Paint paintBlock = null;
    private static final int BOUND_WIDTH_OF_WALL = 2;
    /** 当前正在下落的方块 */
    private List<BlockUnit> blockUnits = new ArrayList<BlockUnit>();
    /** 下一个要显示的方块 */
    private List<BlockUnit> blockUnitBufs = new ArrayList<BlockUnit>();
    /** 下一个要显示的方块 */
    private List<BlockUnit> routeBlockUnitBufs = new ArrayList<BlockUnit>();
    /** 全部的方块allBlockUnits */
    private List<BlockUnit> allBlockUnits = new ArrayList<BlockUnit>();
    /** 调用此对象的Activity对象 */
    private TetrisActivityAW father = null;
    private int[] map = new int[100]; // 保存每行网格中包含俄罗斯方块单元的个数
    /** 游戏的主线程 */
    private Thread mainThread = null;
    // 游戏的几种状态
    /** 标识游戏是开始还是停止 */
    private boolean gameStatus = false;
    /** 标识游戏是暂停还是运行 */
    private boolean runningStatus = false;
    /** 俄罗斯方块颜色数组 */
    private static final int color[] = { Color.parseColor("#FF6600"), Color.BLUE, Color.RED, Color.GREEN, Color.GRAY };
    /** 方块的中心方块单元的坐标, */
    private int xx, yy;
    /** 方块,用户随机获取各种形状的方块 */
    private TetrisBlock tetrisBlock;
    /** 分数 */
    private int score = 0;
    /** 当前方块的类型 */
    private int blockType = 0;

    public TetrisViewAW(Context context) {
        this(context, null);
    }

    public TetrisViewAW(Context context, AttributeSet attrs) {
        super(context, attrs);
        if (paintWall == null) {// 初始化化背景墙画笔
            paintWall = new Paint();
            paintWall.setColor(Color.LTGRAY);
            paintWall.setStyle(Paint.Style.STROKE);
            paintWall.setStrokeWidth(BOUND_WIDTH_OF_WALL + 1);
        }
        if (paintBlock == null) {// 初始化化背景墙画笔
            paintBlock = new Paint();
            paintBlock.setColor(Color.parseColor("#FF6600"));
        }
        tetrisBlock = new TetrisBlock();
        routeBlockUnitBufs = tetrisBlock.getUnits(beginPoint, beginPoint);
        Arrays.fill(map, 0); // 每行网格中包含俄罗斯方块单元的个数全部初始化为0
        // 绘制方块
    }

    /**
     * 设置当前游戏页面的父类activity
     * 
     * @param tetrisActivityAW
     */
    public void setFather(TetrisActivityAW tetrisActivityAW) {
        father = tetrisActivityAW;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        max_x = getWidth();
        max_y = getHeight();
        RectF rel;
        // 绘制网格
        num_x = 0;
        num_y = 0;
        for (int i = beginPoint; i < max_x - BlockUnit.UNIT_SIZE; i += BlockUnit.UNIT_SIZE) {
            for (int j = beginPoint; j < max_y - BlockUnit.UNIT_SIZE; j += BlockUnit.UNIT_SIZE) {
                rel = new RectF(i, j, i + BlockUnit.UNIT_SIZE, j + BlockUnit.UNIT_SIZE);
                canvas.drawRoundRect(rel, 8, 8, paintWall);
                num_y++;
            }
            num_x++;
        }
        // 随机产生一个俄罗斯方块
        int len = blockUnits.size();
        // 绘制方块
        // Toast.makeText(context, "" + len, Toast.LENGTH_SHORT).show();
        for (int i = 0; i < len; i++) {
            int x = blockUnits.get(i).x;
            int y = blockUnits.get(i).y;
            // 设置当前方块的颜色
            paintBlock.setColor(color[blockUnits.get(i).color]);
            rel = new RectF(x + BOUND_WIDTH_OF_WALL, y + BOUND_WIDTH_OF_WALL,
                    x + BlockUnit.UNIT_SIZE - BOUND_WIDTH_OF_WALL, y + BlockUnit.UNIT_SIZE - BOUND_WIDTH_OF_WALL);
            canvas.drawRoundRect(rel, 8, 8, paintBlock);
        }
        // 随机产生一个俄罗斯方块
        len = allBlockUnits.size();
        // 绘制方块
        // Toast.makeText(context, "" + len, Toast.LENGTH_SHORT).show();
        for (int i = 0; i < len; i++) {
            int x = allBlockUnits.get(i).x;
            int y = allBlockUnits.get(i).y;
            paintBlock.setColor(color[allBlockUnits.get(i).color]);
            rel = new RectF(x + BOUND_WIDTH_OF_WALL, y + BOUND_WIDTH_OF_WALL,
                    x + BlockUnit.UNIT_SIZE - BOUND_WIDTH_OF_WALL, y + BlockUnit.UNIT_SIZE - BOUND_WIDTH_OF_WALL);
            canvas.drawRoundRect(rel, 8, 8, paintBlock);
        }
    }

    /**
     * 开始游戏
     */
    public void startGame() {
        gameStatus = true;
        runningStatus = true;
        if (mainThread == null || !mainThread.isAlive()) {
            getNewBlock();
            mainThread = new Thread(new MainThread());
            mainThread.start();
        }

    }

    /**
     * 暂停游戏
     */
    public void pauseGame() {
        runningStatus = false;
    }

    /**
     * 继续游戏
     */
    public void continueGame() {
        runningStatus = true;
    }

    /**
     * 停止游戏
     */
    public void stopGame() {
        // 停止游戏,释放游戏主线程
        runningStatus = false;
        gameStatus = false;
        mainThread.interrupt();
        blockUnits.clear();
        allBlockUnits.clear();
        score = 0;
        invalidate();
    }

    /**
     * 向左滑动
     */
    public void toLeft() {
        if (BlockUnit.toLeft(blockUnits, max_x, allBlockUnits)) {
            xx = xx - BlockUnit.UNIT_SIZE;
        }
        invalidate();
    }

    /**
     * 向右滑动
     */
    public void toRight() {
        if (BlockUnit.toRight(blockUnits, max_x, allBlockUnits)) {
            xx = xx + BlockUnit.UNIT_SIZE;
        }
        invalidate();
    }

    /**
     * 按顺时针旋转
     */
    public void route() {
        if (blockType == 3) {// 如果当前正在下落的方块为正方形,则不进行旋转
            return;
        }
        if (routeBlockUnitBufs.size() != blockUnits.size()) {
            routeBlockUnitBufs = tetrisBlock.getUnits(xx, yy);
        }
        for (int i = 0; i < blockUnits.size(); i++) {
            routeBlockUnitBufs.get(i).x = blockUnits.get(i).x;
            routeBlockUnitBufs.get(i).y = blockUnits.get(i).y;
        }
        for (BlockUnit blockUnit : routeBlockUnitBufs) {
            int tx = blockUnit.x;
            int ty = blockUnit.y;
            blockUnit.x = -(ty - yy) + xx;
            blockUnit.y = tx - xx + yy;
        }
        routeTran(routeBlockUnitBufs);
        if (!BlockUnit.canRoute(routeBlockUnitBufs, allBlockUnits)) {
            // Toast.makeText(father, "不可旋转", Toast.LENGTH_SHORT).show();
            return;
        }
        for (BlockUnit blockUnit : blockUnits) {
            int tx = blockUnit.x;
            int ty = blockUnit.y;
            blockUnit.x = -(ty - yy) + xx;
            blockUnit.y = tx - xx + yy;
        }
        routeTran(blockUnits);
        invalidate();
    }

    /**
     * 如果方块处于边缘,则翻转过后,会出现方块部分处于边缘之外的情况, 因此,通过递归判断是否有超出边缘的部分,
     * 如果有,则进行左右平移,把处于边缘外的方块移动到边缘内
     */
    public void routeTran(List<BlockUnit> blockUnitsBuf) {
        boolean needLeftTran = false;
        boolean needRightTran = false;
        for (BlockUnit u : blockUnitsBuf) {
            if (u.x < beginPoint) {
                needLeftTran = true;
            }
            if (u.x > max_x - BlockUnit.UNIT_SIZE) {
                needRightTran = true;
            }
        }
        if (needLeftTran || needRightTran) {
            for (BlockUnit u : blockUnitsBuf) {
                if (needLeftTran) {
                    u.x = u.x + BlockUnit.UNIT_SIZE;
                } else if (needRightTran) {
                    u.x = u.x - BlockUnit.UNIT_SIZE;
                }
            }
            routeTran(blockUnitsBuf);
        } else {
            return;
        }

    }

    /**
     * 获取一个新的方块
     */
    private void getNewBlock() {
        // 新的方块的坐标,x坐标位于x轴的中间,y 位于起始位置
        this.xx = beginPoint + (num_x / 2) * BlockUnit.UNIT_SIZE;
        this.yy = beginPoint;
        if (blockUnitBufs.size() == 0) {
            // 当游戏第一次开始的时候,先初始化一个方块
            blockUnitBufs = tetrisBlock.getUnits(xx, yy);
        }
        blockUnits = blockUnitBufs;
        blockType = tetrisBlock.blockType;
        blockUnitBufs = tetrisBlock.getUnits(xx, yy);
        if (father != null) {// 显示出下一个要出现的方块
            father.setNextBlockView(blockUnitBufs, (num_x / 2) * BlockUnit.UNIT_SIZE);
        }
    }

    /**
     * 游戏的主线程
     * 
     * @sign Created by wang.ao on 2017年1月16日
     */
    private class MainThread implements Runnable {

        @Override
        public void run() {
            while (gameStatus) {
                while (runningStatus) {
                    if (BlockUnit.canMoveToDown(blockUnits, max_y, allBlockUnits)) {
                        // 判断是否可以继续下落,如果可以下落,则下落
                        BlockUnit.toDown(blockUnits, max_y, allBlockUnits);
                        yy = yy + BlockUnit.UNIT_SIZE;
                    } else {
                        /**
                         * 当不可以继续下落的时候,把当前的方块添加到allBlockUnits中,
                         * 并且判断是否有需要消除的方块,然后再产生一个新的方块
                         */
                        for (BlockUnit blockUnit : blockUnits) {
                            blockUnit.y = blockUnit.y + BlockUnit.UNIT_SIZE;
                            allBlockUnits.add(blockUnit);
                        }
                        for (BlockUnit u : blockUnits) {
                            // 更新map,即更新每行网格中静止俄罗斯方块单元的个数
                            int index = (int) ((u.y - beginPoint) / 50); // 计算所在行数
                            map[index]++;
                        }
                        // 每行最大个数
                        int end = (int) ((max_y - 50 - beginPoint) / BlockUnit.UNIT_SIZE);
                        int full = (int) ((max_x - 50 - beginPoint) / BlockUnit.UNIT_SIZE) + 1;
                        try {
                            Thread.sleep(GameConfig.SPEED);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        for (int i = 0; i <= end; i++) {
                            /***
                             * 消除需要消除的方块(触发条件,某一行中被塞满了方块,没有空白)
                             * 注意顺序,先消除某一行,再移动这一行上边的方块
                             */
                            if (map[i] >= full) {
                                BlockUnit.remove(allBlockUnits, i);
                                score += 100;
                                map[i] = 0;
                                for (int j = i; j > 0; j--)
                                    map[j] = map[j - 1];
                                map[0] = 0;
                                for (BlockUnit blockUnit : allBlockUnits) {
                                    if (blockUnit.y < (i * BlockUnit.UNIT_SIZE + beginPoint)) {
                                        blockUnit.y = blockUnit.y + BlockUnit.UNIT_SIZE;
                                    }
                                }
                            }
                        }
                        father.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                /**
                                 * 刷新分数
                                 */
                                father.score.setText("" + score);
                                invalidate();
                            }
                        });
                        try {
                            Thread.sleep(GameConfig.SPEED * 3);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        father.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                getNewBlock();
                                score += 10;
                                father.score.setText("" + score);
                            }
                        });
                    }
                    father.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            invalidate();
                        }
                    });
                    try {
                        Thread.sleep(GameConfig.SPEED);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }

        }

    }

}
  • BlockUnit.java方块的单元块,大家都玩过俄罗斯方块,每一个方块由四个单元块组成。单元快应该有以下属性:①大小:单元块的大小决定了主界面的容量(容纳单元块的数量);②颜色:每个单元块都有一个颜色,美化游戏界面(可无);③坐标:包括X轴坐标、Y轴坐标,在绘制方块的时候,以单元块的坐标为起点绘制,即:单元块的坐标值应该为单元块在界面上的左上角的坐标。
  • 此类的主要功能有:方块的下落,左右移动,判断是否可以旋转等功能都在此类中,算是核心类。
/**
 * 俄罗斯方块的单元快
 * 
 * @sign Created by wang.ao on 2017年1月13日
 */
public class BlockUnit {
    public static final int UNIT_SIZE = 50;
    public static final int BEGIN = 10;
    public int color;
    // 单元块 的坐标
    public int x, y;

    public BlockUnit() {
    }

    public BlockUnit(int x, int y, int color) {
        /*
         * @param 单元块横纵坐标 构造函数
         */
        this.x = x;
        this.y = y;
        this.color = color;
    }

    /**
     * 判断方块是否可以向左移动,1是否在边缘,2是否会与其他方块重合
     * @param blockUnits 当前正在下落的方块
     * @param max_x 游戏主界面X轴的最大值 ,下同
     * @param allBlockUnits 所有的方块
     * @return 能移动true;不能移动false
     */
    public static boolean canMoveToLeft(List<BlockUnit> blockUnits, int max_x, List<BlockUnit> allBlockUnits) {
        for (BlockUnit blockUnit : blockUnits) {
            int x = blockUnit.x;
            if (x - UNIT_SIZE < BEGIN) {
                return false;
            }
            int y = blockUnit.y;
            if (isSameUnit(x - UNIT_SIZE, y, allBlockUnits)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断方块是否可以向右移动,1是否在边缘,2是否会与其他方块重合
     * @param blockUnits 当前正在下落的方块
     * @param max_x 游戏主界面X轴的最大值 ,下同
     * @param allBlockUnits 所有的方块
     * @return 能移动true;不能移动false
     */
    public static boolean canMoveToRight(List<BlockUnit> blockUnits, int max_x, List<BlockUnit> allBlockUnits) {
        for (BlockUnit blockUnit : blockUnits) {
            int x = blockUnit.x;
            if (x + UNIT_SIZE > max_x - UNIT_SIZE) {
                return false;
            }
            int y = blockUnit.y;
            if (isSameUnit(x + UNIT_SIZE, y, allBlockUnits)) {
                return false;
            }
        }
        return true;
    }
    /**
     * 判断方块是否可以向下移动,1是否在边缘,2是否会与其他方块重合
     * @param blockUnits 当前正在下落的方块
     * @param max_x 游戏主界面X轴的最大值 ,下同
     * @param allBlockUnits 所有的方块
     * @return 能移动true;不能移动false
     */
    public static boolean canMoveToDown(List<BlockUnit> blockUnits, int max_y, List<BlockUnit> allBlockUnits) {
        for (BlockUnit blockUnit : blockUnits) {
            int x = blockUnit.x;
            int y = blockUnit.y + UNIT_SIZE * 2;
            if (y > max_y - UNIT_SIZE) {
                return false;
            }
            if (isSameUnit(x, y, allBlockUnits)) {
                return false;
            }
        }
        return true;
    }
    public static boolean canRoute(List<BlockUnit> blockUnits, List<BlockUnit> allBlockUnits){
        for (BlockUnit blockUnit: blockUnits) {
            if(isSameUnit(blockUnit.x, blockUnit.y, allBlockUnits)){
                return false;
            }
        }
        return true;
    }

    /**
     * 把当前方块向左移动一格
     * @param blockUnits
     * @param max_x
     * @param allBlockUnits
     * @return 是否成功移动一格,是:true,否:false
     */
    public static boolean toLeft(List<BlockUnit> blockUnits, int max_x, List<BlockUnit> allBlockUnits) {
        if (canMoveToLeft(blockUnits, max_x, allBlockUnits)) {
            for (BlockUnit blockUnit : blockUnits) {
                blockUnit.x = blockUnit.x - UNIT_SIZE;
            }
            return true;
        }
        return false;
    }
    /**
     * 把当前方块向右移动一格
     * @param blockUnits
     * @param max_x
     * @param allBlockUnits
     * @return 是否成功移动一格,是:true,否:false
     */
    public static boolean toRight(List<BlockUnit> blockUnits, int max_x, List<BlockUnit> allBlockUnits) {
        if (canMoveToRight(blockUnits, max_x, allBlockUnits)) {
            for (BlockUnit blockUnit : blockUnits) {
                blockUnit.x = blockUnit.x + UNIT_SIZE;
            }
            return true;
        }
        return false;
    }

    /**
     * 把当前方块下落一格
     * @param blockUnits
     * @param allBlockUnits
     * @return 是否成功移动一格,是:true,否:false
     */
    public static void toDown(List<BlockUnit> blockUnits, int max_Y, List<BlockUnit> allBlockUnits) {
        for (BlockUnit blockUnit : blockUnits) {
            blockUnit.y = blockUnit.y + BlockUnit.UNIT_SIZE;
        }
    }

    /**
     * 判断 方块单元是否和所有方块有重合
     * @param x
     * @param y
     * @param allBlockUnits
     * @return
     */
    public static boolean isSameUnit(int x, int y, List<BlockUnit> allBlockUnits) {
        for (BlockUnit blockUnit : allBlockUnits) {
            if (Math.abs(x - blockUnit.x) < UNIT_SIZE && Math.abs(y - blockUnit.y) < UNIT_SIZE) {
                return true;
            }
        }
        return false;
    }

    /**
     * 删除在第j行上的方块单元
     * @param allBlockUnits
     * @param j 需删除行标
     */
    public static void remove(List<BlockUnit> allBlockUnits, int j) {
        for (int i = allBlockUnits.size() - 1; i >= 0; i--) {
            /*
             * ①逆向遍历 ②根据y坐标计算单元所在行,若为j行则从units中删除
             */
            if ((int) ((allBlockUnits.get(i).y - BEGIN) / 50) == j)
                allBlockUnits.remove(i);
        }
    }
}
  • TetrisBlock.java用于产生不同形状的方块,共有其中类型。
/**
 * 方块
 * 
 * @sign Created by wang.ao on 2017年1月13日
 */
public class TetrisBlock {
    private static final int TYPE_SUM = 7;
    public int blockType, blockDirection; // 方块种类,方块朝向

    private int color; // 方块颜色

    private int x, y; // 方块坐标

    public TetrisBlock() {

    }

    public TetrisBlock(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public List<BlockUnit> getUnits(int x, int y) {
        this.x = x;
        this.y = y;
        return returnUnit();
    }

    /**
     * 随机产生一种方块
     * @return
     */
    public List<BlockUnit> returnUnit() {
        List<BlockUnit> units = new ArrayList<BlockUnit>(); // 方块组成部分
        blockType = (int) (Math.random() * TYPE_SUM) + 1; // 随机生成一个种类
        blockDirection = 1; // 默认初始方向
        color = (int) (Math.random() * 4) + 1; // 随机生成一个颜色
        units.clear();
        switch (blockType) {
        case 1:// 横线
            for (int i = 0; i < 4; i++) {
                units.add(new BlockUnit(x + (-2 + i) * BlockUnit.UNIT_SIZE, y, color));
            }
            break;
        case 2:
            units.add(new BlockUnit(x + (-1 + 1) * BlockUnit.UNIT_SIZE, y - BlockUnit.UNIT_SIZE, color));
            for (int i = 0; i < 3; i++) {
                units.add(new BlockUnit(x + (-1 + i) * BlockUnit.UNIT_SIZE, y, color));
            }
            break;
        case 3:
            for (int i = 0; i < 2; i++) {
                units.add(new BlockUnit(x + (i - 1) * BlockUnit.UNIT_SIZE, y - BlockUnit.UNIT_SIZE, color));
                units.add(new BlockUnit(x + (i - 1) * BlockUnit.UNIT_SIZE, y, color));
            }
            break;
        case 4:
            units.add(new BlockUnit(x + (-1 + 0) * BlockUnit.UNIT_SIZE, y - BlockUnit.UNIT_SIZE, color));
            for (int i = 0; i < 3; i++) {
                units.add(new BlockUnit(x + (-1 + i) * BlockUnit.UNIT_SIZE, y, color));
            }
            break;
        case 5:
            units.add(new BlockUnit(x + (-1 + 2) * BlockUnit.UNIT_SIZE, y - BlockUnit.UNIT_SIZE, color));
            for (int i = 0; i < 3; i++) {
                units.add(new BlockUnit(x + (-1 + i) * BlockUnit.UNIT_SIZE, y, color));
            }
            break;
        case 6:
            for (int i = 0; i < 2; i++) {
                units.add(new BlockUnit(x + (-1 + i) * BlockUnit.UNIT_SIZE, y - BlockUnit.UNIT_SIZE, color));
                units.add(new BlockUnit(x + i * BlockUnit.UNIT_SIZE, y, color));
            }
            break;
        case 7:
            for (int i = 0; i < 2; i++) {
                units.add(new BlockUnit(x + i * BlockUnit.UNIT_SIZE, y - BlockUnit.UNIT_SIZE, color));
                units.add(new BlockUnit(x + (-1 + i) * BlockUnit.UNIT_SIZE, y, color));
            }
            break;
        }
        return units;
    }

  • NextBlockView.java其实就是游戏主界面的一个缩减版,用于显示下一个要出现的方块的,玩家可以明确的知道下一个方块的形状和颜色。
/**
 * 下一个要展示的方块
 * 
 * @sign Created by wang.ao on 2017年1月13日
 */
@SuppressLint("DrawAllocation")
public class NextBlockView extends View {
    /** 网格开始坐标值,横纵坐标的开始值都是此值 */
    public static final int beginPoint = 10;
    /** 俄罗斯方块的最大坐标 */
    private static int max_x, max_y;
    private List<BlockUnit> blockUnits = new ArrayList<BlockUnit>();
    /** 背景墙画笔 */
    private static Paint paintWall = null;
    private static final int BOUND_WIDTH_OF_WALL = 2;
    private static Paint paintBlock = null;
    private int div_x = 0;
    // 俄罗斯方块颜色数组
    private static final int color[] ={ Color.parseColor("#FF6600"), Color.BLUE, Color.RED, Color.GREEN, Color.GRAY };

    public NextBlockView(Context context) {
        this(context, null);
    }

    public NextBlockView(Context context, AttributeSet attrs) {
        super(context, attrs);
        if (paintWall == null) {// 初始化化背景墙画笔
            paintWall = new Paint();
            paintWall.setColor(Color.LTGRAY);
            paintWall.setStyle(Paint.Style.STROKE);
            paintWall.setStrokeWidth(BOUND_WIDTH_OF_WALL + 1);
        }
        if (paintBlock == null) {// 初始化化背景墙画笔
            paintBlock = new Paint();
            paintBlock.setColor(Color.parseColor("#FF6600"));
        }
    }

    public void setBlockUnits(List<BlockUnit> blockUnits, int div_x) {
        this.blockUnits = blockUnits;
        this.div_x = div_x;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        max_x = getWidth();
        max_y = getHeight();
        RectF rel;
        // 绘制网格
        int len = blockUnits.size();
        // 绘制方块
        // Toast.makeText(context, "" + len, Toast.LENGTH_SHORT).show();
        for (int i = 0; i < len; i++) {
            paintBlock.setColor(color[blockUnits.get(i).color]);
            int x = blockUnits.get(i).x - div_x + BlockUnit.UNIT_SIZE * 2;
            int y = blockUnits.get(i).y + BlockUnit.UNIT_SIZE * 2;
            rel = new RectF(x + BOUND_WIDTH_OF_WALL, y + BOUND_WIDTH_OF_WALL,
                    x + BlockUnit.UNIT_SIZE - BOUND_WIDTH_OF_WALL, y + BlockUnit.UNIT_SIZE - BOUND_WIDTH_OF_WALL);
            canvas.drawRoundRect(rel, 8, 8, paintBlock);
            rel = new RectF(x, y, x + BlockUnit.UNIT_SIZE, y + BlockUnit.UNIT_SIZE);
            canvas.drawRoundRect(rel, 8, 8, paintWall);
        }
    }

}
  • GameConfig.java用于配置方块的下落速度
public class GameConfig {
    /**方块下落的速度*/
    public static final int SPEED = 300;
}
  • TetrisActivityAW.java主界面,包括游戏主界面和控制台,很简单,直接贴代码。
public class TetrisActivityAW extends Activity {
    private NextBlockView nextBlockView;
    private TetrisViewAW tetrisViewAW;
    private TextView gameStatusTip;
    public TextView score;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tetris_activity_aw);
        nextBlockView = (NextBlockView) findViewById(R.id.nextBlockView1);
        tetrisViewAW = (TetrisViewAW) findViewById(R.id.tetrisViewAW1);
        tetrisViewAW.setFather(this);
        gameStatusTip = (TextView) findViewById(R.id.game_staus_tip);
        score = (TextView) findViewById(R.id.score);
    }

    public void setNextBlockView(List<BlockUnit> blockUnits, int div_x) {
        nextBlockView.setBlockUnits(blockUnits, div_x);
    }

    /**
     * 开始游戏
     * 
     * @param view
     */
    public void startGame(View view) {
        tetrisViewAW.startGame();
        gameStatusTip.setText("游戏运行中");
    }

    /**
     * 暂停游戏
     */
    public void pauseGame(View view) {
        tetrisViewAW.pauseGame();
        gameStatusTip.setText("游戏已暂停");
    }

    /**
     * 继续游戏
     */
    public void continueGame(View view) {
        tetrisViewAW.continueGame();
        gameStatusTip.setText("游戏运行中");
    }

    /**
     * 停止游戏
     */
    public void stopGame(View view) {
        tetrisViewAW.stopGame();
        score.setText(""+0);
        gameStatusTip.setText("游戏已停止");
    }

    /**
     * 向左滑动
     */
    public void toLeft(View view) {
        tetrisViewAW.toLeft();
    }

    /**
     * 向右滑动
     */
    public void toRight(View view) {
        tetrisViewAW.toRight();
    }
    /**
     * 向右滑动
     */
    public void toRoute(View view) {
        tetrisViewAW.route();
    }
}
  • TetrisActivityAW activity的xml文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}" >

    <com.awang.media.minetetris.TetrisViewAW
        android:id="@+id/tetrisViewAW1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="200dp"
        android:layout_marginRight="120dp" />

    <com.awang.media.minetetris.NextBlockView
        android:id="@+id/nextBlockView1"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_alignParentRight="true" />

    <LinearLayout
        android:layout_width="110dp"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/nextBlockView1"
        android:layout_marginTop="20dp"
        android:orientation="vertical" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="分数" />

            <TextView
                android:id="@+id/score"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:text="1" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:orientation="horizontal" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="等级" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:text="1" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:orientation="horizontal" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="速度" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:text="0" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:orientation="horizontal" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="最高分" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:text="0" />
        </LinearLayout>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:onClick="startGame"
            android:text="开始" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="pauseGame"
            android:text="暂停" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="continueGame"
            android:text="继续" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="stopGame"
            android:text="结束" />
    </LinearLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_alignParentBottom="true" >

        <Button
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:onClick="toLeft"
            android:text="左" />

        <TextView
            android:id="@+id/game_staus_tip"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="5dp"
            android:text="点击开始运行游戏"
            android:textSize="20sp" />
  <Button
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_centerInParent="true"
            android:onClick="toRoute"
            android:text="旋转" />
        <Button
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_alignParentRight="true"
            android:onClick="toRight"
            android:text="右" />
    </RelativeLayout>

</RelativeLayout>

整个项目就是这些,代码已经全部贴出来了。
整个项目写的时候,以为很简单,但是却遇到了很多问题,不过都已解决。欢迎来找bug,大家共同进步。

源码下载地址

http://download.csdn.net/download/waa_0618/9741351

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容