android小游戏——2048 核心功能的实现与简单的代码重构(2)

上一篇博文,我们实现了初步的左滑,那我们这一篇就把上下右滑的代码给写了,再加一个输赢的判断条件(其实原版2048也就只有输)。

2.右滑(紧接上一篇博文)

由于原理跟左滑一模一样我在这里就直接贴代码了。

private void moveToRight() {//右滑
        for (int i = 0; i < 4; i++) {
            for (int j = 3; j >= 0; j--) {
                int n = 0;//若0后面全为0则下面的while会死循环,设置n表示顶多一行只有4个0
                while (tvNum[i][j] == 0 && n < 4) {//若检测到哪一位为0则把这一位放到最右边那一列
                    n++;
                    for (int k = j; (k - 1) >= 0; k--) {//简单的冒泡,把0浮上来
                        int temp = 0;
                        temp = tvNum[i][k];
                        tvNum[i][k] = tvNum[i][k - 1];
                        tvNum[i][k - 1] = temp;
                    }
                }
            }
        }

        for (int i = 0; i < 4; i++) {//相同数相加
            for (int j = 3; (j - 1) >= 0; j--) {
                if (tvNum[i][j] == tvNum[i][j - 1] && tvNum[i][j] != 0) {//两数相同且不为0
                    tvNum[i][j] *= 2;
                    tvNum[i][j - 1] = 0;
                    for (int k = j - 1; (k - 1) >= 0; k--) {//把第二个数置0冒泡法移到最后
                        int temp = 0;
                        temp = tvNum[i][k];
                        tvNum[i][k] = tvNum[i][k - 1];
                        tvNum[i][k - 1] = temp;
                    }
                }
            }
        }

        boolean ifEmpty = false;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (tvNum[i][j] == 0) {
                    ifEmpty = true;
                }
            }
        }
        if (ifEmpty) {
            int randomXY;
            randomXY = (int) Math.round(Math.random() * 15);
            while (tvNum[randomXY / 4][randomXY % 4] != 0) {
                randomXY = (int) Math.round(Math.random() * 15);
            }
            tvNum[randomXY / 4][randomXY % 4] = ((int) Math.round(Math.random() + 1)) * 2;
        }

        for (int i = 0; i < 4; i++) {//把更新之后的tvNum在游戏界面上显示出来
            for (int j = 0; j < 4; j++) {
                if (tvNum[i][j] == 0) {//0则不显示数值
                    textViews[4 * i + j].setText("");
                } else {
                    textViews[4 * i + j].setText(tvNum[i][j] + "");
                }
            }
        }
    }
3.上滑

上下滑动跟左右滑动有一些区别,主要在于矩阵的遍历方式。左右滑动矩阵是横向遍历,那么我们的上下滑动就应该是纵向遍历。而且由于2048游戏的特性,矩阵必定为方阵,所以不需要担心由于Java的二维数组可以不等长而引起的数组越界。下面就是上滑的代码:

private void moveToTop() {//上滑
        for (int j = 0; j < 4; j++) {
            for (int i = 0; i < 4; i++) {
                int n = 0;//若0后面全为0则下面的while会死循环,设置n表示顶多一行只有4个0
                while (tvNum[i][j] == 0 && n < 4) {//若检测到哪一位为0则把这一位放到最下面那一行
                    n++;
                    for (int k = i; (k + 1) < 4; k++) {//简单的冒泡,把0浮上来
                        int temp = 0;
                        temp = tvNum[k][j];
                        tvNum[k][j] = tvNum[k + 1][j];
                        tvNum[k + 1][j] = temp;
                    }
                }
            }
        }

        for (int j = 0; j < 4; j++) {//相同数相加
            for (int i = 0; (i + 1) < 4; i++) {
                if (tvNum[i][j] == tvNum[i + 1][j] && tvNum[i][j] != 0) {//两数相同且不为0
                    tvNum[i][j] *= 2;
                    tvNum[i + 1][j] = 0;
                    for (int k = i + 1; (k + 1) < 4; k++) {//把第二个数置0冒泡法移到最后
                        int temp = 0;
                        temp = tvNum[k][j];
                        tvNum[k][j] = tvNum[k + 1][j];
                        tvNum[k + 1][j] = temp;
                    }
                }
            }
        }

        boolean ifEmpty = false;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (tvNum[i][j] == 0) {
                    ifEmpty = true;
                }
            }
        }
        if (ifEmpty) {
            int randomXY;
            randomXY = (int) Math.round(Math.random() * 15);
            while (tvNum[randomXY / 4][randomXY % 4] != 0) {
                randomXY = (int) Math.round(Math.random() * 15);
            }
            tvNum[randomXY / 4][randomXY % 4] = ((int) Math.round(Math.random() + 1)) * 2;
        }

        for (int i = 0; i < 4; i++) {//把更新之后的tvNum在游戏界面上显示出来
            for (int j = 0; j < 4; j++) {
                if (tvNum[i][j] == 0) {//0则不显示数值
                    textViews[4 * i + j].setText("");
                } else {
                    textViews[4 * i + j].setText(tvNum[i][j] + "");
                }
            }
        }
    }
4.下滑
 private void moveToBottom() {//下滑
        for (int j = 0; j < 4; j++) {
            for (int i = 3; i >= 0; i--) {
                int n = 0;//若0后面全为0则下面的while会死循环,设置n表示顶多一行只有4个0
                while (tvNum[i][j] == 0 && n < 4) {//若检测到哪一位为0则把这一位放到最上面那一行
                    n++;
                    for (int k = i; (k - 1) >= 0; k--) {//简单的冒泡,把0浮上来
                        int temp = 0;
                        temp = tvNum[k][j];
                        tvNum[k][j] = tvNum[k - 1][j];
                        tvNum[k - 1][j] = temp;
                    }
                }
            }
        }

        for (int j = 0; j < 4; j++) {//相同数相加
            for (int i = 3; (i - 1) >= 0; i--) {
                if (tvNum[i][j] == tvNum[i - 1][j] && tvNum[i][j] != 0) {//两数相同且不为0
                    tvNum[i][j] *= 2;
                    tvNum[i - 1][j] = 0;
                    for (int k = i - 1; (k - 1) >= 0; k--) {//把第二个数置0冒泡法移到最后
                        int temp = 0;
                        temp = tvNum[k][j];
                        tvNum[k][j] = tvNum[k - 1][j];
                        tvNum[k - 1][j] = temp;
                    }
                }
            }
        }

        boolean ifEmpty = false;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (tvNum[i][j] == 0) {
                    ifEmpty = true;
                }
            }
        }
        if (ifEmpty) {
            int randomXY;
            randomXY = (int) Math.round(Math.random() * 15);
            while (tvNum[randomXY / 4][randomXY % 4] != 0) {
                randomXY = (int) Math.round(Math.random() * 15);
            }
            tvNum[randomXY / 4][randomXY % 4] = ((int) Math.round(Math.random() + 1)) * 2;
        }

        for (int i = 0; i < 4; i++) {//把更新之后的tvNum在游戏界面上显示出来
            for (int j = 0; j < 4; j++) {
                if (tvNum[i][j] == 0) {//0则不显示数值
                    textViews[4 * i + j].setText("");
                } else {
                    textViews[4 * i + j].setText(tvNum[i][j] + "");
                }
            }
        }
    }

哇说实话代码写到这里,我是真的忍不住要重构这坨又臭又长的代码了。虽然还有一些功能没实现,但没事,我们重构完再写。

重构垃圾代码

1.分离代码

我写完下滑代码以后,我的GameActivity已经整整322行了!!!真是受不了,于是我决定把跟Activity无关的代码分离开来。新建一个MyOper.java文件,里边用来放我们的上下左右滑动的代码。对了把setXY也放进去吧。
直接剪切所有的滑动和setXY代码到这里面:
android小游戏——2048 核心功能的实现与简单的代码重构(2)_第1张图片
虽然会有很多错误,我们一步一步来解决他们。

  1. 把我们之前定义的数组放进MyOper.java里面
  2. 把GameActivity的onCreate方法里面的对TextView的绑定代码也封装成一个函数init放入MyOper中。
  3. 定义一个MyOper的成员变量private Activity activity;
  4. 然后定义MyOper的构造函数,
    MyOper(Activity activity)
    {
    this.activity = activity;
    }

    用来获取Activity对象。再在init()的findViewById之前加上activity:
    void init()
    {
        for (int i = 0; i < 16; i++) {//绑定所有TextView
            textViews[i] = (TextView) activity.findViewById(tvId[i]);
            textViews[i].setText("");
        }
    }
  1. 接着就只要在GameActvity里初始化一个MyOper对象,然后在各个方法原来的位置调用对象的方法就可以。
    GameActivity
package com.example.a2048;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;


public class GameActivity extends AppCompatActivity {

    int downX = 0;//按下时候的坐标
    int downY = 0;
    MyOper oper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_game);
        oper = new MyOper(this);
        oper.init();
        int initNum1, initNum2;//初始化位置
        initNum1 = (int) Math.round(Math.random() * 15);//生成0~15随机整数
        initNum2 = (int) Math.round(Math.random() * 15);
        while (initNum1 == initNum2) {
            initNum1 = (int) Math.round(Math.random() * 15);
            initNum2 = (int) Math.round(Math.random() * 15);
        }
        oper.setXY(initNum1, 2);
        oper.setXY(initNum2, 2);
    }



    @Override
    public boolean onTouchEvent(MotionEvent event) {//监听触摸事件
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getRawX();
                downY = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                int upX = (int) event.getRawX();
                int upY = (int) event.getRawY();
                if (Math.abs(upX - downX) > Math.max(2 * Math.abs(upY - downY), 100)) {
                    if (upX > downX) {
                        oper.moveToRight();
                    } else {
                        oper.moveToLeft();
                    }
                } else if (Math.abs(upY - downY) > Math.max(2 * Math.abs(upX - downX), 100)) {
                    if (upY > downY) {
                        oper.moveToBottom();
                    } else {
                        oper.moveToTop();
                    }
                } else {
                    Log.d("111", "不滑动");
                }
                break;
        }
        return true;
    }
}

到现在应该已经消除所有的错误了,打开模拟器跑一下我们的游戏,确保我们做的改动没有出错。

确保自己能够继续跑下去,我们就接着重构。如果自己的程序有问题,我建议先解决一下,不要硬着头皮往下改了。接着我们就把上下左右滑动中相同的代码抽离出来封装成方法。那么哪些代码是一样的呢?

  1. 每次平移后随机生成2或者4的代码。
  2. 平移完之后根据tvNum数值矩阵来调整textViews的显示数值的代码。

1的代码抽离出来叫做produce2_4(),2抽离出来叫做showText().

 void produce2_4() {
     boolean ifEmpty = false;
     for (int i = 0; i < 4; i++) {
         for (int j = 0; j < 4; j++) {
             if (tvNum[i][j] == 0) {
                 ifEmpty = true;
             }
         }
     }
     if (ifEmpty) {
         int randomXY;
         randomXY = (int) Math.round(Math.random() * 15);
         while (tvNum[randomXY / 4][randomXY % 4] != 0) {
             randomXY = (int) Math.round(Math.random() * 15);
         }
         tvNum[randomXY / 4][randomXY % 4] = ((int) Math.round(Math.random() + 1)) * 2;
     }
 }

 void showText() {
     for (int i = 0; i < 4; i++) {//把更新之后的tvNum在游戏界面上显示出来
         for (int j = 0; j < 4; j++) {
             if (tvNum[i][j] == 0) {//0则不显示数值
                 textViews[4 * i + j].setText("");
             } else {
                 textViews[4 * i + j].setText(tvNum[i][j] + "");
             }
         }
     }
 }

回过头来看看我们的setXY()方法似乎用处也不大,只在刚开始的时候用了一下。那我们就把这个方法删除掉,然后把GameActivity的生成initNum1, initNum2放在MyOper的init方法里面。
做到现在,我们成功地初步重构了一下我们的代码。虽然还是有很多地方可以再修改,但相比于一开始肯定是简约和清晰了好多。
MyOper.java
android小游戏——2048 核心功能的实现与简单的代码重构(2)_第2张图片
GameActivity.java
android小游戏——2048 核心功能的实现与简单的代码重构(2)_第3张图片

好,我们接着写功能。一个是在每次滑动之后判断当前矩阵能否滑动(即游戏是否结束),我们在MyOper里面新加一个ifLose()方法。
判断能否滑动

boolean ifLose() {
    for (int i = 0; i < 4; i++) {//横向比较是否有邻近的两个数字相同
        for (int j = 0; (j + 1) < 4; j++) {
            if (tvNum[i][j] == tvNum[i][j + 1]) {
                return false;
            }
        }
    }
    for (int j = 0; j < 4; j++) {//纵向比较是否有邻近的两个数字相同
        for (int i = 0; (i + 1) < 4; i++) {
            if (tvNum[i][j] == tvNum[i + 1][j]) {
                return false;
            }
        }
    }
    return true;
}

考虑我们每次判断都是在滑动之后,所以把是否结束游戏的判断放在produce2_4()方法里面

if (ifEmpty) {
...
          if (ifLose()) {
              Toast.makeText(activity, "游戏结束", Toast.LENGTH_SHORT).show();
          }
}

再有一个就是我们随机产生2或者4必须在有效滑动之后,所以在滑动的时候需要加个判断,到底矩阵有没有变换。
以左滑作为例子:

 void moveToLeft() {//左滑
        boolean ifSlide = false;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                int n = 0;//若0后面全为0则下面的while会死循环,设置n表示顶多一行只有4个0
                while (tvNum[i][j] == 0 && n < 4) {//若检测到哪一位为0则把这一位放到最右边那一列
                    n++;
                    for (int k = j; (k + 1) < 4; k++) {//简单的冒泡,把0沉下去
                        if (tvNum[i][k + 1] != 0) {//只要不是都是0则一定进行了有效滑动
                            ifSlide = true;
                        }
                        int temp = 0;
                        temp = tvNum[i][k];
                        tvNum[i][k] = tvNum[i][k + 1];
                        tvNum[i][k + 1] = temp;
                    }
                }
            }
        }

        for (int i = 0; i < 4; i++) {//相同数相加
            for (int j = 0; (j + 1) < 4; j++) {
                if (tvNum[i][j] == tvNum[i][j + 1] && tvNum[i][j] != 0) {//两数相同且不为0
                    ifSlide = true;//有相加操作则一定进行了有效滑动
                    tvNum[i][j] *= 2;
                    tvNum[i][j + 1] = 0;
                    for (int k = j + 1; (k + 1) < 4; k++) {//把第二个数置0冒泡法移到最后
                        int temp = 0;
                        temp = tvNum[i][k];
                        tvNum[i][k] = tvNum[i][k + 1];
                        tvNum[i][k + 1] = temp;
                    }
                }
            }
        }
        if (ifSlide) {
            produce2_4();
            showText();
        }
    }

右滑上滑下滑大家就自己写一下吧。

到现在为止我们可算是真正意义上的写完了2048这个游戏的核心代码。这个游戏已经可以玩了,大家可以自己玩个几盘,看看有没有什么bug。我们下一篇来写得分功能,以及界面的一些美化。

你可能感兴趣的:(android小游戏)