快乐编程吧之2048小游戏

当你走进这欢乐场,

一杯敬明天,一杯敬过往......

学校开课为周共九周的课结束了,要上交一个作品,想了许久准备开发了一个小游戏2048。既然决定了,就要认真做,把它当做一门艺术品来看待。

艺术就要精雕细琢,下面就开始雕琢这件艺术品。

第一部分:完成布局以及它的初始化

                                    布局效果

1.思考主布局的布局方式,确定用何种布局方式来更简单的实现这种布局。

(1)为了更美观,提升游戏的体验,把按钮和一些控件进行美化,比如这里的扁平化设计风格就很不错。

这里我们可以在drawable目录里新建xml文件设计这种扁平化风格

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
    <shape android:shape="rectangle">
        <solid android:color="#33444444"/>
        <corners android:radius="10dip"/>
        <padding android:bottom="1dp" android:left="1dp" android:right="1dp" android:top="1dp"/>
    </shape>
</item>
    <item >
        <shape android:shape="rectangle">
            <solid android:color="#BFBFBF"/>
            <corners android:radius="10dip"/>
            <padding android:bottom="1dp" android:left="1dp" android:right="1dp" android:top="1dp"/>
        </shape>
    </item>
</selector>

我们定义一些属性的值,让风格更具特色:

shape 所要画形状

solid 填充颜色属性

corners 弧形的半径

padding 控件里的文字与控件边界的间隔

新建多个这样的文件,只需改变里面的solid属性值即可改变颜色,让风格多样化。

(2)☆☆☆下面我们就开始对整个页面进行布局了☆☆☆

这个布局很简单 ,整体是线性布局,里面嵌套线性布局和FrameLayout布局,这里用framelayout是游戏主体部分的布局块。为什么用这种布局后面会有详细的解释。

通过下面这行代码即可引用上面定义的扁平化风格按钮

 android:background="@drawable/gray_button"

布局代码块如下:

<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:padding="3dp"
      android:id="@+id/ll"
      android:layout_width="match_parent"
      android:layout_height="65dp"
      android:orientation="horizontal">
      <LinearLayout
          android:layout_width="0dp"
          android:layout_height="60dp"
          android:layout_weight="1">
          <TextView
              android:id="@+id/tv_goal"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:text="2048"
              android:textSize="36sp"
              android:gravity="center"
              android:background="@drawable/gray_button"/>
      </LinearLayout>

      <LinearLayout
          android:layout_width="0dp"
          android:layout_height="60dp"
          android:layout_weight="1"
          android:orientation="vertical">

          <TextView
              android:layout_width="match_parent"
              android:layout_height="30dp"
              android:gravity="center"
              android:background="@drawable/gray_button"
              android:text="Score" />

          <TextView
              android:id="@+id/score"
              android:layout_width="match_parent"
              android:layout_height="30dp"
              android:text="Score"
              android:gravity="center"
              android:background="@drawable/light_orange_button"/>
      </LinearLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="60dp"
        android:layout_weight="1"
        android:orientation="vertical">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:text="Record"
            android:gravity="center"
            android:background="@drawable/gray_button"/>
        <TextView
            android:id="@+id/record"
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:gravity="center"
            android:text="Record"
            android:background="@drawable/light_orange_button"/>
    </LinearLayout>
  </LinearLayout>

    <FrameLayout
        android:id="@+id/game_panel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
<RelativeLayout
    android:id="@+id/game_panel_rl"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">
</RelativeLayout>
    </FrameLayout>

    <!--下面的三个按钮-->
    <LinearLayout
        style="?android:attr/buttonBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:padding="3dp"
        android:background="@drawable/choose">

        <Button
            android:id="@+id/btn_revert"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="90dp"
            android:layout_height="30dp"
            android:layout_marginLeft="20dp"
            android:background="@drawable/white_button"
            android:textColor="@android:color/black"
            android:text="后退一步"
            />
        <Button
            android:id="@+id/btn_restart"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="100dp"
            android:layout_height="30dp"
            android:layout_margin="3dp"
            android:background="@drawable/white_button"
            android:text="重新开始"
            android:textColor="@android:color/black" />
        <Button
            android:id="@+id/btn_option"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="100dp"
            android:layout_height="30dp"
            android:layout_margin="3dp"
            android:background="@drawable/white_button"
            android:text="难度设置"
            android:textColor="@android:color/black" />
    </LinearLayout>
</LinearLayout>
2.小方块的设计
(1)2048游戏滑动的小方块是游戏最终操作的最小单元。游戏面板中使用的是GridLayout,在设计好小方块之后,只要将不同属性的小方块加入到GridLayout中即可。

上面我们布局时提出问题为什么要用FrameLayout?

基于效率的考虑,让小方块--GridItem继承自FrameLayout。FrameLayout是几大布局中最轻量级的布局,并给在GameItem中给小方块设置了要显示的数字,并根据要显示的数字来设置相应的颜色,这些都通过GameItem的构造方法实现。(

(2)在此类里面写一个方法来设定小方块代表的颜色和数值

public class GameItem extends FrameLayout {
    private int mCardShowNum;  //获取卡片的数字
    private TextView mTvNum;
    private LayoutParams mParams;
    public GameItem(Context context,int cardShowNum){
        super(context);
        this.mCardShowNum=cardShowNum;
        initCardItem();
    }
     .......
  //为每一个数字设置不同的颜色
    public void setNum(int num) {
        this.mCardShowNum = num;
        if (num == 0) {
            mTvNum.setText("");
        } else {
            mTvNum.setText("" + num);
        }
        // 设置背景颜色
        switch (num) {
            case 0:
                mTvNum.setBackgroundColor(0x00000000);
                break;
            case 2:
                mTvNum.setBackgroundColor(0xffeee5db);
                break;
            case 4:
                mTvNum.setBackgroundColor(0xffeee0ca);
                break;
            case 8:
                mTvNum.setBackgroundColor(0xfff2c17a);
                break;
            case 16:
                mTvNum.setBackgroundColor(0xfff59667);
                break;
            case 32:
                mTvNum.setBackgroundColor(0xfff68c6f);
                break;
            case 64:
                mTvNum.setBackgroundColor(0xfff66e3c);
                break;
            case 128:
                mTvNum.setBackgroundColor(0xffedcf74);
                break;
            case 256:
                mTvNum.setBackgroundColor(0xffedcc64);
                break;
            case 512:
                mTvNum.setBackgroundColor(0xffedc854);
                break;
            case 1024:
                mTvNum.setBackgroundColor(0xffedc54f);
                break;
            case 2048:
                mTvNum.setBackgroundColor(0xffedc32e);
                break;
            default:
                mTvNum.setBackgroundColor(0xff3c4a34);
                break;
        }
    }

(3)根据传入的cardShowNum,确定了小方块应该显示的数字和颜色。初始化Item方法中首先将颜色都设置为灰色,从而将面板变成一个大的灰色布局,这是由Frame拼接起来的,再将其中用于显示Item的TextView设置对应数字的背景颜色和边距,来展现一个游戏方块。

 private void initCardItem() {
        setBackgroundColor(Color.GRAY);
        mTvNum=new TextView(getContext());
        setNum(mCardShowNum);
        //修改5*5时字体太大
        int gameLines= Config.mSp.getInt(Config.KEY_GAME_LINES,4);
        if(gameLines==4){
            mTvNum.setTextSize(35);
        }else if (gameLines==5){
            mTvNum.setTextSize(25);
        }else {
            mTvNum.setTextSize(20);
        }
        TextPaint tp=mTvNum.getPaint();
        tp.setFakeBoldText(true);  //true为粗体,false为非粗体
        mTvNum.setGravity(Gravity.CENTER);
        mParams=new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        addView(mTvNum,mParams);
    }

☆☆☆☆☆☆☆代码重点解析☆☆☆☆☆☆☆

这里的gameLine表示游戏中方格总数,即4*4或5*5。这个变量值通过我们自定义Config类获取(这个类我们下面会详细说),这个类的作用是保存游戏数据:游戏历史高分,游戏行数等,下面我们会详细介绍。

根据gameLine的大小我们要改变方块的大小,避免太大影响美观。

setFakeBoldText(true);   表示设置字体为粗体,false不加粗

 mParams=new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
 addView(mTvNum,mParams);
设定此处布局样式mParams,利用addView()把布局数据及样式添加到view中完成此处的自定义布局。

3.为游戏提供一个游戏配置保存类

上面我们在设置放置小方块面板时提到了一个类Config,因为我们每次进入时都希望读取上次退出游戏时保存的配置,所以我们需要这个类来保存信息


因为保存的是一些简单的数据类型,使用这里我们可以简单的使用借助数据持久化SharePreferences类,注意要在Mainifest文件中设置程序的入口。

 <application
        android:name=".config.Config"
...

我们需要保存哪些信息:历史分数,游戏面板规格,目标分数,历史高分

public class Config extends Application {
    //SP对象
    public static SharedPreferences mSp;
    //Item的宽高
    public static int mItemSize;
    //游戏目标分数
    public int mGameGoal;
    //GameView行列数
    public static int mGameLines;
    //记录分数
    public static int SCORE=0;
    public static String KEY_HIGH_SCORE ="KEY_HIGHSCORE" ;
    public static String KEY_GAME_LINES = "KEY_GAMELINES";
    public static String KEY_GAME_GOAL = "KEY_GAMEGOAL ";
    public static String SP_HIGH_SCORE="SP_HIGHSCORE";

    @Override
    public void onCreate() {
        super.onCreate();
        mSp=getSharedPreferences(SP_HIGH_SCORE,0);   //设置文件名及存储方式
        mGameLines=mSp.getInt(KEY_GAME_LINES,4);     //默认为4*4
        mGameGoal=mSp.getInt(KEY_GAME_GOAL,2048);    //默认为2048
        mItemSize=0;
    }
}

4.游戏面板设计

游戏布局及全局配置(游戏参数配置)已完成,现在要进入这个游戏的重头戏,也是难点。这件是艺术品,我们刚刚只玩成大体的设置。下面就让我们一起慢慢的对其雕琢。

(1)初始化游戏矩阵

首先要移除之前游戏的矩阵,并通过配置文件读取出来的配置值生成游戏的布局。而且还这里还要计算每个小方块的具体宽度。

public class GameView extends GridLayout implements View.OnTouchListener {

    private int mTarget;            //目标分数
    private int mScoreHistory;      //历史分数
    private int mGameLines;         //矩阵规格参数
    private GameItem[][] mGameMatrix; //游戏1矩阵
    private int[][] mGameMatrixHistory; //游戏历史矩阵
    private ArrayList<Integer> mCalList;  //存储方块的数组
    private ArrayList<Point> mBlanks;       //存储空格的数组
    //历史高分记录
    private int mHighScore;
    private int mStartX;
    private int mStartY;
    private int mEndX;
    private int mEndY;
    private int mKeyItemNum = -1;
...
 private void initGameMatrix() {
        removeAllViews();
        mScoreHistory = 0;
        Config.mGameLines = Config.mSp.getInt(Config.KEY_GAME_LINES, 4);
        mGameLines = Config.mGameLines;
        mGameMatrix = new GameItem[mGameLines][mGameLines];
        mGameMatrixHistory = new int[mGameLines][mGameLines];
        mCalList = new ArrayList<Integer>();
        mBlanks = new ArrayList<Point>();
        mHighScore = Config.mSp.getInt(Config.KEY_HIGH_SCORE, 0);
        setColumnCount(mGameLines);
        setRowCount(mGameLines);
        //添加触模
        setOnTouchListener(this);
        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();   // DisplayMetrics metrics = new DisplayMetrics();getWindowManager().getDefaultDisplay().getMetrics(metrics);
        display.getMetrics(metrics);
        Config.mItemSize = metrics.widthPixels/Config.mGameLines;
        Log.d("syso", "手机分辨率: ="+metrics.widthPixels+"*"+metrics.heightPixels);
        initGameView(Config.mItemSize);
    }
☆初始化代码很简单,数据初始化直接读取Config里的数据。因为继承了GridLayout布局所以需要设置行列数,通过下面两个方法直接设置。

setColumnCount(mGameLines);
setRowCount(mGameLines);
☆这里需要注意的是获取每个小方块的具体宽度。
☆先获取屏幕的分辨率,然后通过计算获得每个小方块具体的宽度
display.getMetrics(metrics);
Config.mItemSize = metrics.widthPixels/Config.mGameLines;
☆完成了面板的初始化后,需要根据游戏的难度进行矩阵的初始化,即填充初始化数据(小方块)。开始时将所有的小方块都设置成0,然后在为0的小方块上随机生成两个数(2或4)。

private void initGameView(int cardSize) {
        removeAllViews();
        GameItem card;
        for (int i = 0; i < mGameLines; i++) {
            for (int j = 0; j < mGameLines; j++) {
                card = new GameItem(getContext(), 0);
                addView(card, cardSize, cardSize);
                mGameMatrix[i][j] = card;
                mBlanks.add(new Point(i, j));
            }
        }
        //添加随机数字
        addRandomNum();
        addRandomNum();
    }
☆这里用到了一个Point类,这个类可以理解为一个二维数组,用来存储一个点的x,y坐标。

添加随机数字的方法很简单,先获取空格的位置,放入一个新的集合里,直接调用随机函数,通过设置一个比例来设定出现2和4的概率

 private void getBlanks() {
        mBlanks.clear();
        for (int i = 0; i < mGameLines; i++) {
            for (int j = 0; j < mGameLines; j++) {
                if (mGameMatrix[i][j].getNum() == 0) {
                    mBlanks.add(new Point(i, j));
                }
            }
        }
    }

  private void addRandomNum() {
        getBlanks();
        if (mBlanks.size() > 0) {
            int randomNum = (int) (Math.random() * mBlanks.size());
            Point randomPoint = mBlanks.get(randomNum);
            mGameMatrix[randomPoint.x][randomPoint.y].setNum(Math.random() > 0.2d ? 2 : 4);
            animCreate(mGameMatrix[randomPoint.x][randomPoint.y]);
        }
    }

(2)滑动事件和算法实现

面板的初始化我们一步步已经实现了,运行之后我们效果是面板随机生成两个数。这样我们的艺术品已经完成一半了,下面我们要开始我们滑动产生效果部分实现。这也是我们这个游戏最难和复杂的部分。主要分为两大部分,滑动事件和算法。下面我们一个一个来实现。

由于我们要先看到效果,就要先实现滑动事件,产生滑动事件我们首先要写一个方法,用来判断我们往哪个方向滑动。

 private void judgeDirection(int offsetX, int offsetY) {
        int density = getDeviceDensity();      //获取设备屏幕密度,像素的比例
        int slideDis = 5 * density;              //滑动边界
        int maxDis = 200 * density;                //滑动限制
        boolean flagNormal = (Math.abs(offsetX) > slideDis
                || Math.abs(offsetY) > slideDis)
                && (Math.abs(offsetX) < maxDis
                && Math.abs(offsetY) < maxDis);
        if (flagNormal) {
            if (Math.abs(offsetX) > Math.abs(offsetY)) {
                if (offsetX > slideDis) {
                    swipeRight();
                } else {
                    swipeLeft();
                }
            } else {
                if (offsetY > slideDis) {
                    swipeDown();
                } else {
                    swipeUp();
                }
            }
}
☆这个方法还是比较简单的,先获取屏幕响度密度,通过两个像素来确定一个滑动产生效果的范围。
如果在这个范围内,在通过判断确定往哪个方向滑动,并为其写入方法,有上下左右共四个方法。

这样我们每次有效滑动就会分别调用这四个方法的其中一个,之后,就是我们的重点:实现这四个方法,显而易见,这四个方法主要就是算法实现,而且这四个方法会很类似。

我们先举一个向左滑动的例子来详细说一下算法原理。

首先对这个矩阵(二维数组)进行遍历,每遍历一行,把相邻且相同的两个数相加,并把得到的结果存入集合,如果遇到0,那么就直接跳过。要到不同的相邻不同的数,依次加入集合中。比如第一行遍历数是2 2 0 4,那么加入集合的结果应该是4 4。第一行遍历完之后直接在对原矩阵进行更新你保存的集合数据。


   private void swipeLeft() {
        for (int i = 0; i < mGameLines; i++) {
            for (int j = 0; j < mGameLines; j++) {
                int currentNum = mGameMatrix[i][j].getNum();
                if (currentNum != 0) {
                    if (mKeyItemNum == -1) {
                        mKeyItemNum = currentNum;
                    } else {
                        if (mKeyItemNum == currentNum) {
                            mCalList.add(mKeyItemNum * 2);
                            Config.SCORE += mKeyItemNum * 2;
                            mKeyItemNum = -1;
                        } else {
                            mCalList.add(mKeyItemNum);
                            mKeyItemNum = currentNum;
                        }
                    }
                } else {
                    continue;
                }
            }
            if (mKeyItemNum != -1) {
                mCalList.add(mKeyItemNum);
            }
            for (int j = 0; j < mCalList.size(); j++) {
                mGameMatrix[i][j].setNum(mCalList.get(j));
            }
            for (int m = mCalList.size(); m < mGameLines; m++) {
                mGameMatrix[i][m].setNum(0);
            }
            mKeyItemNum = -1;
            mCalList.clear();
        }
    }

★★★★★★注意的点★★★★★★

这里我们要设置一个标志点,也就是代码中的 mKeyItemNum ,而且初始设为-1。目的是什么呢。这里是为了实现如果相加一次后能够不再对这个已经操作过了的数进行操作了。具体是怎么实现的呢:我们在刚开始先把每次遍历的值先赋值给mKeyItemNum,让它和之后获取的数进行判断是否相等,如果相等,则相加结果放入集合,并把mKeyItemNum再次赋值为-1;这样这个已经被加过的数就不会被带入下一次循环当中了。

这里还要注意的是:我们结束第一行时,在更新矩阵时要注意将空格的位置设为0。


其余的滑动方法实现和此向左滑动方法类似(主要在循环路线不一样,个人觉得用“整体法替代”理解比较简单)

 
 
private void swipeUp() {
        for (int i = 0; i < mGameLines; i++) {
            for (int j = 0; j < mGameLines; j++) {
                int currentNum = mGameMatrix[j][i].getNum();
                if (currentNum != 0) {
                    if (mKeyItemNum == -1) {
                        mKeyItemNum = currentNum;
                    } else {
                        if (mKeyItemNum == currentNum) {
                            mCalList.add(mKeyItemNum * 2);
                            Config.SCORE += mKeyItemNum * 2;
                            mKeyItemNum = -1;
                        } else {
                            mCalList.add(mKeyItemNum);
                            mKeyItemNum = currentNum;
                        }
                    }
                } else {
                    continue;
                }
            }
            if (mKeyItemNum != -1) {
                mCalList.add(mKeyItemNum);
            }
            // 改变Item值
            for (int j = 0; j < mCalList.size(); j++) {
                mGameMatrix[j][i].setNum(mCalList.get(j));
            }
            for (int m = mCalList.size(); m < mGameLines; m++) {
                mGameMatrix[m][i].setNum(0);
            }
            // 重置行参数
            mKeyItemNum = -1;
            mCalList.clear();
        }
    }

    /**
     * 向下滑动
     */
    private void swipeDown() {
        for (int i = mGameLines - 1; i >= 0; i--) {
            for (int j = mGameLines - 1; j >= 0; j--) {
                int currentNum = mGameMatrix[j][i].getNum();
                if (currentNum != 0) {
                    if (mKeyItemNum == -1) {
                        mKeyItemNum = currentNum;
                    } else {
                        if (mKeyItemNum == currentNum) {
                            mCalList.add(mKeyItemNum * 2);
                            Config.SCORE += mKeyItemNum * 2;
                            mKeyItemNum = -1;
                        } else {
                            mCalList.add(mKeyItemNum);
                            mKeyItemNum = currentNum;
                        }
                    }
                } else {
                    continue;
                }
            }
            if (mKeyItemNum != -1) {
                mCalList.add(mKeyItemNum);
            }
            // 改变Item值
            for (int j = 0; j < mGameLines - mCalList.size(); j++) {
                mGameMatrix[j][i].setNum(0);
            }
            int index = mCalList.size() - 1;
            for (int m = mGameLines - mCalList.size(); m < mGameLines; m++) {
                mGameMatrix[m][i].setNum(mCalList.get(index));
                index--;
            }
            // 重置行参数
            mKeyItemNum = -1;
            mCalList.clear();
            index = 0;
        }
    }

    /**
     * 向右滑动
     */
    private void swipeRight() {
        for (int i = mGameLines - 1; i >= 0; i--) {
            for (int j = mGameLines - 1; j >= 0; j--) {
                int currentNum = mGameMatrix[i][j].getNum();
                if (currentNum != 0) {
                    if (mKeyItemNum == -1) {
                        mKeyItemNum = currentNum;
                    } else {
                        if (mKeyItemNum == currentNum) {
                            mCalList.add(mKeyItemNum * 2);
                            Config.SCORE += mKeyItemNum * 2;
                            mKeyItemNum = -1;
                        } else {
                            mCalList.add(mKeyItemNum);
                            mKeyItemNum = currentNum;
                        }
                    }
                } else {
                    continue;
                }
            }
            if (mKeyItemNum != -1) {
                mCalList.add(mKeyItemNum);
            }
            // 改变Item值
            for (int j = 0; j < mGameLines - mCalList.size(); j++) {
                mGameMatrix[i][j].setNum(0);
            }
            int index = mCalList.size() - 1;
            for (int m = mGameLines - mCalList.size(); m < mGameLines; m++) {
                mGameMatrix[i][m].setNum(mCalList.get(index));
                index--;
            }
            // 重置行参数
            mKeyItemNum = -1;
            mCalList.clear();
            index = 0;
        }
    } 
 
 

 
 

(3)算法设计完成后,我们就可以进行游戏了,但我们在玩的时候肯定要知道什么时候结束以及结束的结果。所以我们还需要添加两个方法来实现。

第一个方法(检查数字):先检查所有小方格是否被填充满,如果填充满再判断每个小方块周围有没有相同小方块,结果返回不同的值。

 /**
     *  检测所有数字 看是否有满足条件的
     * @return 0:结束 1:正常 2:成功
     */
    private int checkNums() {
        getBlanks();
        if (mBlanks.size() == 0) {
            for (int i = 0; i < mGameLines; i++) {
                for (int j = 0; j < mGameLines; j++) {
                    if (j < mGameLines - 1) {
                        if (mGameMatrix[i][j].getNum() == mGameMatrix[i][j + 1]
                                .getNum()) {
                            return 1;
                        }
                    }
                    if (i < mGameLines - 1) {
                        if (mGameMatrix[i][j].getNum() == mGameMatrix[i + 1][j]
                                .getNum()) {
                            return 1;
                        }
                    }
                }
            }
            return 0;
        }
        for (int i = 0; i < mGameLines; i++) {
            for (int j = 0; j < mGameLines; j++) {
                if (mGameMatrix[i][j].getNum() == mTarget) {
                    return 2;
                }
            }
        }
        return 1;
    }

第二个方法:检查是否完成游戏及完成游戏后产生的效果事件。

在上面第一个方法的获得的返回值进行选择判断,判断后弹出相应的对话框显示提示信息,并保存这次游戏的分数配置。

这里我们还修改了目标分数来提升游戏难度。

 private void checkCompleted() {
        int result=checkNums();
        if (result==0){
            if (Config.SCORE>mHighScore){
                SharedPreferences.Editor edit=Config.mSp.edit();
                edit.putInt(Config.SP_HIGH_SCORE,Config.SCORE);
                edit.apply();
                Game.getGameActivity().setScore(Config.SCORE,1);
                Config.SCORE=0;
            }
            AlertDialog.Builder builder=new AlertDialog.Builder(getContext());
            builder.setTitle("您输了哦,请重新开始吧").setPositiveButton("重新开始", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    startGame();
                }
            }).create().show();
            Config.SCORE=0;
        }else if (result==2){
            AlertDialog.Builder builder=new AlertDialog.Builder(getContext());
            builder.setTitle("完成此难度,双击666").setPositiveButton("重新游戏", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    startGame();
                }
            }).setNegativeButton("继续游戏", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    SharedPreferences.Editor edit = Config.mSp.edit();
                    if (mTarget == 1024) {
                        edit.putInt(Config.KEY_GAME_GOAL, 2048);
                        mTarget = 2048;
                        Game.getGameActivity().setGoal(2048);
                    } else if (mTarget == 2048) {
                        edit.putInt(Config.KEY_GAME_GOAL, 4096);
                        mTarget = 4096;
                        Game.getGameActivity().setGoal(4096);
                    } else {
                        edit.putInt(Config.KEY_GAME_GOAL, 4096);
                        mTarget = 4096;
                        Game.getGameActivity().setGoal(4096);
                    }
                    edit.apply();
                }
            }).create().show();
            Config.SCORE=0;
        }
    }
到这里我们的基本功能已经实现了,之后我们就要做的事情就比较轻松了,建立一个类(主活动)来使用这些方法。

5.游戏类+设置类

(1)游戏类

public class Game extends Activity implements OnClickListener {

    // Activity的引用
    private static Game mGame;
    // 记录分数
    private TextView mTvScore;
    // 历史记录分数
    private TextView mTvHighScore;
    private int mHighScore;
    // 目标分数
    private TextView mTvGoal;
    private int mGoal;
    // 重新开始按钮
    private Button mBtnRestart;
    // 撤销按钮
    private Button mBtnRevert;
    // 选项按钮
    private Button mBtnOptions;
    // 游戏面板
    private GameView mGameView;

    public Game() {
        mGame = this;
    }

    /**
     * 获取当前Activity的引用
     *
     * @return Activity.this
     */
    public static Game getGameActivity() {
        return mGame;
    }

    @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化View
        initView();
        mGameView = new GameView(this);
        FrameLayout frameLayout = (FrameLayout)findViewById(R.id.game_panel);
        // 为了GameView能居中
        RelativeLayout relativeLayout = (RelativeLayout)findViewById(R.id.game_panel_rl);
        relativeLayout.addView(mGameView);

    }

    /**
     * 初始化View
     */
    private void initView() {
        mTvScore = (TextView)findViewById(R.id.score);
        mTvGoal = (TextView)findViewById(R.id.tv_goal);
        mTvHighScore = (TextView) findViewById(R.id.record);
        mBtnRestart = (Button) findViewById(R.id.btn_restart);
        mBtnRevert = (Button) findViewById(R.id.btn_revert);
        mBtnOptions = (Button) findViewById(R.id.btn_option);
        mBtnRestart.setOnClickListener(this);
        mBtnRevert.setOnClickListener(this);
        mBtnOptions.setOnClickListener(this);
        mHighScore = Config.mSp.getInt(Config.KEY_HIGH_SCORE, 0);
        mGoal = Config.mSp.getInt(Config.KEY_GAME_GOAL, 2048);
        mTvHighScore.setText("" + mHighScore);
        mTvGoal.setText("" + mGoal);
        mTvScore.setText("0");
        setScore(0, 0);
        setScore(mHighScore,1);
    }

    public void setGoal(int num) {
        mTvGoal.setText(String.valueOf(num));
    }
    /**
     * 修改得分
     *
     * @param score score
     * @param flag  0 : score 1 : high score
     */
    public void setScore(int score, int flag) {
        switch (flag) {
            case 0:
                mTvScore.setText("" + score);
                break;
            case 1:
                mTvHighScore.setText("" + score);
                break;
            default:
                break;
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_restart:
                mGameView.startGame();
                setScore(0, 0);
                break;
            case R.id.btn_revert:
                mGameView.revertGame();
                break;
            case R.id.btn_option:
                Intent intent = new Intent(Game.this, ConfigPreference.class);
                startActivityForResult(intent, 0);
                break;
            default:
                break;
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            mGoal = Config.mSp.getInt(Config.KEY_GAME_GOAL, 2048);
            setScore(0,0);
            mTvGoal.setText("" + mGoal);
            getHighScore();
            mGameView.startGame();
        }
    }

    /**
     * 获取最高记录
     */
    private void getHighScore() {
        int score = Config.mSp.getInt(Config.KEY_HIGH_SCORE, 0);
        setScore(score, 1);
    }

    //返回键点击事件
    @Override
    public void onBackPressed() {
        AlertDialog.Builder builder=new AlertDialog.Builder(this);
        builder.setTitle("确定退出游戏")
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish();
                    }
                })
                .setNegativeButton("返回游戏", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                }).create().show();


    }
}

(2)设置类

在前面弄清楚的情况下,这部分还是很容易弄懂的。这里我们发现有一个类叫 ConfigPreference.class

这个就是设置类,之前我们设置了游戏难度,那么在这个类里面我们就能实现游戏难度的更换。我们也把代码贴出来:

public class ConfigPreference extends AppCompatActivity implements View.OnClickListener {
    private Button mBtnGameLines;
    private TextView mTvGoal;
    private Button mBtnBack;
    private Button mBtnDone;
    private String[] mGameLinesList;
    private String[] mGameGoalList;
    private AlertDialog.Builder mBuilder;
    private String[] mGameStandrand;      //游戏难度
    String mLine;
    private String mStandard;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.game_config);
        initView();
    }

    private void initView() {
        mBtnGameLines = (Button) findViewById(R.id.btn_gamelines);
        mTvGoal = (TextView) findViewById(R.id.tv_goal);
        mBtnBack = (Button) findViewById(R.id.btn_back);
        mBtnDone = (Button) findViewById(R.id.btn_done);
        mBtnGameLines.setOnClickListener(this);
        //mBtnGoal.setOnClickListener(this);
        mBtnBack.setOnClickListener(this);
        mBtnDone.setOnClickListener(this);
        mBtnGameLines.setText(Config.mSp.getString(Config.GAME_STANDARD, "简单"));
        mTvGoal.setText(" " + Config.mSp.getInt(Config.KEY_GAME_GOAL,1024));
        mGameLinesList = new String[]{"4", "4", "5"};
        mGameStandrand=new String[]{"简单","一般","困难"};
        mGameGoalList = new String[]{"1024", "2048", "4096"};
    }

    //保存设置
    private void saveConfig(){
        SharedPreferences.Editor editor=Config.mSp.edit();
        try{
            editor.putInt(Config.KEY_GAME_LINES,Integer.parseInt(mLine));
            Log.d("syso", "mStandard=: "+mStandard);
            editor.putString(Config.GAME_STANDARD,mStandard);
            editor.putInt(Config.KEY_GAME_GOAL, Integer.parseInt(mTvGoal.getText().toString()));
        }catch (Exception e){
            Toast.makeText(this,"没有改变游戏难度",Toast.LENGTH_SHORT).show();
        }finally {
            editor.commit();
        }

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_gamelines:
                mBuilder=new AlertDialog.Builder(this);
                mBuilder.setTitle("选择难度");
                mBuilder.setItems(mGameStandrand, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        mBtnGameLines.setText(mGameStandrand[i]);
                        mStandard=mGameStandrand[i];
                        mLine=mGameLinesList[i];
                        mTvGoal.setText(mGameGoalList[i]);;
                    }
                }).create().show();
                break;
            case R.id.tv_goal:
                mBuilder = new AlertDialog.Builder(this);
                mBuilder.setTitle("设置完成游戏目标分数");
                mBuilder.setItems(mGameGoalList,
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                mTvGoal.setText(mGameGoalList[which]);
                            }
                        });
                mBuilder.create().show();
                break;
            case R.id.btn_back:
                this.finish();
                break;
            case R.id.btn_done:
                saveConfig();
                setResult(RESULT_OK);
                this.finish();
                break;
            default:
                break;
        }
        }
    }
到这里我们的2048小游戏就到此结束了,一件艺术品就这么被完美的雕琢出来了,好开心啊QAQ

最后来体验一下游戏吧:

完整代码已上传至github:https://github.com/Kingwentao/Game2048

 
 
 
 
 
 
 
 
 
 
 
 
 
 

猜你喜欢

转载自blog.csdn.net/king_guoguo/article/details/78313918
今日推荐