Android 俄羅斯方塊 附源碼

思路:

  • 首先要畫出游戲背景墻;
  • 其次,要有方塊,以及方塊單元;
  • 方塊的不同形狀,顏色隨機產(chǎn)生;
  • 游戲的控制面板。

可能會出現(xiàn)的問題或者難點:

  • 邊界問題:
    ①處于邊界的時候,方塊不可以再左右移動;
    ②下降的時候,到達邊界即底部,則不可繼續(xù)下落,此時應(yīng)該產(chǎn)生一個新的方塊;
  • 與其它方塊接觸問題:
    ①下落的時候,如果碰到其它的方塊則停止下落;
    ②左右移動的時候,移動的過程中,如果接觸到其他方快,則不可再繼續(xù)左右移動;
  • 方塊的消除:
    ①調(diào)用方塊消除方法的時間:當方塊下落到底部的時候,判斷是否有需要消除的行;
    ②消除某一行之后,應(yīng)該把這一行上面的全部方塊下移一行;
  • 方塊的旋轉(zhuǎn):
    在當前項目中,我采用的是順時針旋轉(zhuǎn)。
    ①當旋轉(zhuǎn)的時候,如果出現(xiàn)方塊部分超出了邊界,應(yīng)該對方塊進行平移,使其回到邊界以內(nèi)。(曾在網(wǎng)上看到有人做過,判斷旋轉(zhuǎn)之后是否會超出邊界,如果會超出,則不進行旋轉(zhuǎn),我覺得不好,方塊只要沒有下落到底部,我覺得都可以進行旋轉(zhuǎn),除了沒有空間讓其旋轉(zhuǎn)外);
    ②如果空間不足以旋轉(zhuǎn),也不可以旋轉(zhuǎn)。空間不足以旋轉(zhuǎn)的意思是:比如橫向方向只有兩個的空間,而方塊旋轉(zhuǎn)后會占用三個空間,此時也不可進行旋轉(zhuǎn);
    ③當無法繼續(xù)下落或者下落到了底部也不可再進行旋轉(zhuǎn)
  • 控制面板:
    ①游戲開始、暫停、繼續(xù)、結(jié)束,這些狀態(tài)應(yīng)該怎么去控制,以及游戲與控制臺的事件關(guān)聯(lián)。
  • 未發(fā)現(xiàn)的問題:
    因為本人能力,只做到這么多,如果有人發(fā)現(xiàn)問題,可以留言交流,歡迎挑問題。

游戲的運行界面如下所示,基本的功能以及操作很簡單。

這里寫圖片描述

下面直接看項目代碼
項目文件結(jié)構(gòu)

這里寫圖片描述


下面分別介紹每個類的功能

  • TetrisViewAW.java游戲的主界面,背景墻以及方塊都在此TetrisViewAW.Java里面,就是一個自定義的View ,(默認大家對于自定義View是熟悉的),在改類里面,有一個游戲主線程,用于控制游戲的開始,暫停,繼續(xù),停止,以及方塊下落的速率。代碼我加了很多注釋,看不懂的可以留言。還有一點需要注意,當停止游戲時,要釋放線程,養(yǎng)成好習(xí)慣
/**
 * 俄羅斯方塊Game主界面
 * 
 * @sign Created by wang.ao on 2017年1月12日
 */
@SuppressLint("DrawAllocation")
public class TetrisViewAW extends View {
    /** 網(wǎng)格開始坐標值,橫縱坐標的開始值都是此值 */
    public static final int beginPoint = 10;
    /** 俄羅斯方塊的最大坐標 */
    private static int max_x, max_y;
    /** 行數(shù)和列數(shù) */
    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>();
    /** 調(diào)用此對象的Activity對象 */
    private TetrisActivityAW father = null;
    private int[] map = new int[100]; // 保存每行網(wǎng)格中包含俄羅斯方塊單元的個數(shù)
    /** 游戲的主線程 */
    private Thread mainThread = null;
    // 游戲的幾種狀態(tài)
    /** 標識游戲是開始還是停止 */
    private boolean gameStatus = false;
    /** 標識游戲是暫停還是運行 */
    private boolean runningStatus = false;
    /** 俄羅斯方塊顏色數(shù)組 */
    private static final int color[] = { Color.parseColor("#FF6600"), Color.BLUE, Color.RED, Color.GREEN, Color.GRAY };
    /** 方塊的中心方塊單元的坐標, */
    private int xx, yy;
    /** 方塊,用戶隨機獲取各種形狀的方塊 */
    private TetrisBlock tetrisBlock;
    /** 分數(shù) */
    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); // 每行網(wǎng)格中包含俄羅斯方塊單元的個數(shù)全部初始化為0
        // 繪制方塊
    }

    /**
     * 設(shè)置當前游戲頁面的父類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;
        // 繪制網(wǎng)格
        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++;
        }
        // 隨機產(chǎn)生一個俄羅斯方塊
        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;
            // 設(shè)置當前方塊的顏色
            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);
        }
        // 隨機產(chǎn)生一個俄羅斯方塊
        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;
    }

    /**
     * 繼續(xù)游戲
     */
    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();
    }

    /**
     * 按順時針旋轉(zhuǎn)
     */
    public void route() {
        if (blockType == 3) {// 如果當前正在下落的方塊為正方形,則不進行旋轉(zhuǎn)
            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, "不可旋轉(zhuǎn)", 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();
    }

    /**
     * 如果方塊處于邊緣,則翻轉(zhuǎn)過后,會出現(xiàn)方塊部分處于邊緣之外的情況, 因此,通過遞歸判斷是否有超出邊緣的部分,
     * 如果有,則進行左右平移,把處于邊緣外的方塊移動到邊緣內(nèi)
     */
    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) {// 顯示出下一個要出現(xiàn)的方塊
            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)) {
                        // 判斷是否可以繼續(xù)下落,如果可以下落,則下落
                        BlockUnit.toDown(blockUnits, max_y, allBlockUnits);
                        yy = yy + BlockUnit.UNIT_SIZE;
                    } else {
                        /**
                         * 當不可以繼續(xù)下落的時候,把當前的方塊添加到allBlockUnits中,
                         * 并且判斷是否有需要消除的方塊,然后再產(chǎn)生一個新的方塊
                         */
                        for (BlockUnit blockUnit : blockUnits) {
                            blockUnit.y = blockUnit.y + BlockUnit.UNIT_SIZE;
                            allBlockUnits.add(blockUnit);
                        }
                        for (BlockUnit u : blockUnits) {
                            // 更新map,即更新每行網(wǎng)格中靜止俄羅斯方塊單元的個數(shù)
                            int index = (int) ((u.y - beginPoint) / 50); // 計算所在行數(shù)
                            map[index]++;
                        }
                        // 每行最大個數(shù)
                        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++) {
                            /***
                             * 消除需要消除的方塊(觸發(fā)條件,某一行中被塞滿了方塊,沒有空白)
                             * 注意順序,先消除某一行,再移動這一行上邊的方塊
                             */
                            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() {
                                /**
                                 * 刷新分數(shù)
                                 */
                                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方塊的單元塊,大家都玩過俄羅斯方塊,每一個方塊由四個單元塊組成。單元快應(yīng)該有以下屬性:①大小:單元塊的大小決定了主界面的容量(容納單元塊的數(shù)量);②顏色:每個單元塊都有一個顏色,美化游戲界面(可無);③坐標:包括X軸坐標、Y軸坐標,在繪制方塊的時候,以單元塊的坐標為起點繪制,即:單元塊的坐標值應(yīng)該為單元塊在界面上的左上角的坐標。
  • 此類的主要功能有:方塊的下落,左右移動,判斷是否可以旋轉(zhuǎn)等功能都在此類中,算是核心類。
/**
 * 俄羅斯方塊的單元快
 * 
 * @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 單元塊橫縱坐標 構(gòu)造函數(shù)
         */
        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--) {
            /*
             * ①逆向遍歷 ②根據(jù)y坐標計算單元所在行,若為j行則從units中刪除
             */
            if ((int) ((allBlockUnits.get(i).y - BEGIN) / 50) == j)
                allBlockUnits.remove(i);
        }
    }
}
  • TetrisBlock.java用于產(chǎn)生不同形狀的方塊,共有其中類型。
/**
 * 方塊
 * 
 * @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();
    }

    /**
     * 隨機產(chǎn)生一種方塊
     * @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其實就是游戲主界面的一個縮減版,用于顯示下一個要出現(xiàn)的方塊的,玩家可以明確的知道下一個方塊的形狀和顏色。
/**
 * 下一個要展示的方塊
 * 
 * @sign Created by wang.ao on 2017年1月13日
 */
@SuppressLint("DrawAllocation")
public class NextBlockView extends View {
    /** 網(wǎng)格開始坐標值,橫縱坐標的開始值都是此值 */
    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;
    // 俄羅斯方塊顏色數(shù)組
    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;
        // 繪制網(wǎng)格
        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("游戲已暫停");
    }

    /**
     * 繼續(xù)游戲
     */
    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="分數(shù)" />

            <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="繼續(xù)" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="stopGame"
            android:text="結(jié)束" />
    </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="旋轉(zhuǎn)" />
        <Button
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_alignParentRight="true"
            android:onClick="toRight"
            android:text="右" />
    </RelativeLayout>

</RelativeLayout>

整個項目就是這些,代碼已經(jīng)全部貼出來了。
整個項目寫的時候,以為很簡單,但是卻遇到了很多問題,不過都已解決。歡迎來找bug,大家共同進步。

源碼下載地址

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內(nèi)容