Android 2048的设计(2)

来源:互联网 发布:模拟人生4男性捏脸数据 编辑:程序博客网 时间:2024/05/18 14:23

在去年的时候曾经写了一个Android小游戏——2048
当初设计的时候还不觉得什么,最近在整理代码时却觉得当时代码设计得很是糟糕,代码混乱,界面也不好看。于是就趁着假期重写了一遍,游戏运行界面如下

这里写图片描述

实现的功能有:

  • 有4x4,5x5,6x6三种规则
  • 记录历史最高分
  • 使用纯色块
  • 保存游戏
  • 开启音效
  • 更换背景图

开发工具用的是Android Studio

这里写图片描述

游戏的思路并不复杂,甚至可以说是挺简单的。
首先要自定义一个View,作为可滑动的方块(其实滑动效果是通过改变数字与颜色来模拟实现的),这个View要继承于FrameLayout
每一种不同数值的方块有不同的颜色,通过设置“setBackgroundColor”来实现。

public class Card extends FrameLayout {    private TextView label;    private int num = 0;    //用于判断是否纯色块    public boolean flag;    public Card(Context context) {        super(context);        label = new TextView(context);        label.setGravity(Gravity.CENTER);        label.setTextSize(24);        label.setBackgroundColor(Color.parseColor("#77E8E2D8"));        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);        lp.setMargins(5, 5, 0, 0);        addView(label, lp);    }    public void setNum(int num) {        this.num = num;        if (num == 0) {            label.setText("");            label.setBackgroundColor(Color.parseColor("#77E8E2D8"));        }else{            if(!flag){                label.setText(num + "");            }            changeCardColor();        }    }    public int getNum() {        return num;    }    public void changeCardColor() {        switch (num) {        case 2:            label.setBackgroundColor(Color.parseColor("#5DB8E8"));            break;        case 4:            label.setBackgroundColor(Color.parseColor("#A52812"));            break;        case 8:            label.setBackgroundColor(Color.parseColor("#0E7171"));            break;        case 16:            label.setBackgroundColor(Color.parseColor("#C0BB39"));            break;        case 32:            label.setBackgroundColor(Color.parseColor("#623889"));            break;        case 64:            label.setBackgroundColor(Color.parseColor("#5C7235"));            break;        case 128:            label.setBackgroundColor(Color.parseColor("#826FA3"));            break;        case 256:            label.setBackgroundColor(Color.parseColor("#355659"));            break;        case 512:            label.setBackgroundColor(Color.parseColor("#BB719B"));            break;        case 1024:            label.setBackgroundColor(Color.parseColor("#9B8B53"));            break;        case 2048:            label.setBackgroundColor(Color.parseColor("#196A5D"));            break;        default:            label.setBackgroundColor(Color.parseColor("#8A7760"));        }    }    public boolean equals(Card c) {        return this.getNum() == c.getNum();    }}

此外,可以看到不管是4x4规则的或者5x5,6x6的,整个可滑动区域都是一个正方形,方块平均分布,因此可以自定义一个View,继承于GridLayout,为之添加多个Card 。
GameView 的重点在于方块的滑动判断以及实现滑动效果。
SoundPool 的使用方法在Android5.0之后发生了改变,所以需要在代码中判断当前系统版本,从而使用不同的初始化方法。

public class GameView extends GridLayout {    // 存储所有方块    private Card[][] Cards;    // 当前游戏的行数与列数    private int Row;    // 游戏记录    private SharedPreferences gameRecord;    // 存储游戏音效开关记录    private SharedPreferences GameSettings;    private SharedPreferences.Editor grEditor;    public ScoreChangeListen scoreChangeListen=null;    private Context context;    //当前得分    private int Score;    public SoundPool soundPool;   // private HashMap<Integer, Integer> soundID;    private int soundID;;    private boolean soundSwitch;    private class Point {        public Point(int x, int y) {            this.x = x;            this.y = y;        }        int x;        int y;    }    public GameView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.context = context;        TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.ViewSet);        Row = mTypedArray.getInt(R.styleable.ViewSet_Row, 5);        mTypedArray.recycle();        super.setColumnCount(Row);        init();    }    public GameView(Context context, AttributeSet attrs) {        super(context, attrs);        this.context = context;        TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.ViewSet);        Row = mTypedArray.getInt(R.styleable.ViewSet_Row, 5);        mTypedArray.recycle();        super.setColumnCount(Row);        init();    }    public GameView(Context context) {        super(context);        this.context = context;        init();    }    // 初始化    private void init() {        gameRecord = context.getSharedPreferences("GameRecord", Context.MODE_PRIVATE);        GameSettings = context.getSharedPreferences("GameSettings", Context.MODE_PRIVATE);        boolean flag=GameSettings.getBoolean("SolidColorSwitch",false);        soundSwitch=GameSettings.getBoolean("SoundSwitch",false);        //SoundPool的构建方法在5.0系统之后发生了变化        if (Build.VERSION.SDK_INT < 21) {            soundPool = new SoundPool(1,AudioManager.STREAM_MUSIC,0);        }else{            SoundPool.Builder builder = new SoundPool.Builder();            builder.setMaxStreams(1);            AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder();            attrBuilder.setLegacyStreamType(AudioManager.STREAM_MUSIC);            builder.setAudioAttributes(attrBuilder.build());            soundPool = builder.build();        }        soundID=soundPool.load(context,R.raw.sound,1);        grEditor = gameRecord.edit();        Cards = new Card[Row][Row];        for (int y = 0; y < Row; y++) {            for (int x = 0; x < Row; x++) {                Cards[x][y] = new Card(context);                Cards[x][y].flag=flag;            }        }        // 添加两个初始方块        randomCard();        randomCard();    }    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        // 计算方块的边长        int cardWidth = (Math.min(w, h) - 5) / Row;        // 添加方块        addCard(cardWidth);    }    // 计算分数    private void countScore(int num) {        Score = Score + num;        if(scoreChangeListen!=null){            scoreChangeListen.OnNowScoreChange(Score);            if(soundSwitch){                soundPool.play(soundID, 1, 1, 0, 0, 1);            }        }    }    // 添加方块    private void addCard(int cardWidth) {        for (int y = 0; y < Row; y++) {            for (int x = 0; x < Row; x++) {                addView(Cards[x][y], cardWidth, cardWidth);            }        }    }    // 生成伪随机方块    private void randomCard() {        List<Point> points = new ArrayList<>();        for (int x = 0; x < Row; x++) {            for (int y = 0; y < Row; y++) {                // 如果还有空白方块                if (Cards[x][y].getNum() == 0) {                    points.add(new Point(x, y));                }            }        }        if (points.size() == 0) {            return;        }        int index = points.size() / 2;        Cards[points.get(index).x][points.get(index).y].setNum(2);    }    // 左移    private void moveLeftCard() {        allMoveLeft();        for (int y = 0; y < Row; y++) {            for (int x = 0; x < Row - 1; x++) {                if (Cards[x][y].getNum() != 0) {                    if (Cards[x][y].equals(Cards[x + 1][y])) {                        int num = Cards[x][y].getNum();                        Cards[x][y].setNum(2 * num);                        Cards[x + 1][y].setNum(0);                        countScore(num);                        allMoveLeft();                    }                }            }        }        randomCard();    }    // 右移    private void moveRightCard() {        allMoveRight();        for (int y = 0; y < Row; y++) {            for (int x = Row - 1; x > 0; x--) {                if (Cards[x][y].getNum() != 0) {                    if (Cards[x][y].equals(Cards[x - 1][y])) {                        int num = Cards[x][y].getNum();                        Cards[x][y].setNum(2 * num);                        Cards[x - 1][y].setNum(0);                        countScore(num);                        allMoveRight();                    }                }            }        }        randomCard();    }    // 上移    private void moveUpCard() {        allMoveUp();        for (int x = 0; x < Row; x++) {            for (int y = 0; y < Row - 1; y++) {                if (Cards[x][y].getNum() != 0) {                    if (Cards[x][y].equals(Cards[x][y + 1])) {                        int num = Cards[x][y].getNum();                        Cards[x][y].setNum(2 * num);                        Cards[x][y + 1].setNum(0);                        countScore(num);                        allMoveUp();                    }                }            }        }        randomCard();    }    // 下移    private void moveDownCard() {        allMoveDown();        for (int x = 0; x < Row; x++) {            for (int y = Row - 1; y > 0; y--) {                if (Cards[x][y].getNum() != 0) {                    if (Cards[x][y].equals(Cards[x][y - 1])) {                        int num = Cards[x][y].getNum();                        Cards[x][y].setNum(2 * num);                        Cards[x][y - 1].setNum(0);                        countScore(num);                        allMoveDown();                    }                }            }        }        randomCard();    }    // 全部左移    private void allMoveLeft() {        for (int y = 0; y < Row; y++) {            int i = 0;            for (int x = 0; x < Row; x++) {                if (Cards[x][y].getNum() != 0) {                    int num = Cards[x][y].getNum();                    Cards[x][y].setNum(0);                    Cards[i++][y].setNum(num);                }            }        }    }    // 全部右移    private void allMoveRight() {        for (int y = 0; y < Row; y++) {            int i = Row - 1;            for (int x = Row - 1; x > -1; x--) {                if (Cards[x][y].getNum() != 0) {                    int num = Cards[x][y].getNum();                    Cards[x][y].setNum(0);                    Cards[i--][y].setNum(num);                }            }        }    }    // 全部上移    private void allMoveUp() {        for (int x = 0; x < Row; x++) {            int i = 0;            for (int y = 0; y < Row; y++) {                if (Cards[x][y].getNum() != 0) {                    int num = Cards[x][y].getNum();                    Cards[x][y].setNum(0);                    Cards[x][i++].setNum(num);                }            }        }    }    // 全部下移    private void allMoveDown() {        for (int x = 0; x < Row; x++) {            int i = Row - 1;            for (int y = Row - 1; y > -1; y--) {                if (Cards[x][y].getNum() != 0) {                    int num = Cards[x][y].getNum();                    Cards[x][y].setNum(0);                    Cards[x][i--].setNum(num);                }            }        }    }    // 触屏事件监听    float X;    float Y;    float OffsetX;    float OffsetY;    int HintCount = 0;    public boolean isHalfway = true;    public boolean onTouchEvent(MotionEvent event) {        // 为了避免当游戏结束时消息多次提示        if (HintCount == 1) {            return true;        }        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                X = event.getX();                Y = event.getY();                break;            case MotionEvent.ACTION_UP:                OffsetX = event.getX() - X;                OffsetY = event.getY() - Y;                if (Math.abs(OffsetX) > (Math.abs(OffsetY))) {                    if (OffsetX < -5) {                        moveLeftCard();                    } else if (OffsetX > 5) {                        moveRightCard();                    }                } else {                    if (OffsetY < -5) {                        moveUpCard();                    } else if (OffsetY > 5) {                        moveDownCard();                    }                }                HintMessage();                break;        }        return true;    }    // 判断游戏是否结束    private boolean isOver() {        for (int y = 0; y < Row; y++) {            for (int x = 0; x < Row; x++) {                if ((Cards[x][y].getNum() == 0) || (x - 1 >= 0 && Cards[x - 1][y].equals(Cards[x][y]))                        || (x + 1 <= Row - 1 && Cards[x + 1][y].equals(Cards[x][y]))                        || (y - 1 >= 0 && Cards[x][y - 1].equals(Cards[x][y]))                        || (y + 1 <= Row - 1 && Cards[x][y + 1].equals(Cards[x][y]))) {                    return false;                }            }        }        return true;    }    // 当游戏结束时提示信息    private void HintMessage() {        if (isOver()) {            Toast.makeText(getContext(), "游戏结束啦", Toast.LENGTH_SHORT).show();            HintCount=1;        }    }    //重新开始    public void restart(){        for (int y = 0; y < Row; y++) {            for (int x = 0; x < Row; x++) {                Cards[x][y].setNum(0);            }        }        Score=0;        HintCount=0;        // 添加两个初始方块        randomCard();        randomCard();    }    //保存游戏    public void saveGame(){        grEditor.clear();        grEditor.putInt("Row", Row);        grEditor.putInt("Score",Score);        int k = 0;        for (int i = 0; i < Row; i++) {            for (int j = 0; j < Row; j++) {                k++;                String str = k + "";                grEditor.putInt(str, Cards[i][j].getNum());            }        }        if( grEditor.commit()){            Toast.makeText(context, "保存成功", Toast.LENGTH_SHORT).show();        }else{            Toast.makeText(context, "保存失败,请重试", Toast.LENGTH_SHORT).show();        }    }    // 恢复游戏    public void recoverGame() {        int k = 0;        for (int i = 0; i < Row; i++) {            for (int j = 0; j < Row; j++) {                k++;                String str = k + "";                int num = gameRecord.getInt(str, 0);                Cards[i][j].setNum(num);            }        }        Score=gameRecord.getInt("Score",0);        scoreChangeListen.OnNowScoreChange(Score);    }}

要注意的是,在GameView的构造函数中,需要读取GameView的一个自定义属性“Row”,如果没有指定则默认为5。该属性的定义在values文件夹的attrs.xml文件中。

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="ViewSet">        <attr name="Row" format="integer"/>    </declare-styleable></resources>

这样,在布局文件中使用GameView时,先加上属性声明

xmlns:my="http://schemas.android.com/apk/res-auto"

然后就可以为GameView设置显示行数了

整个游戏界面是由GameActivity呈现的,该Activity通过Bundle 携带的数据使用不同的布局文件。

public class GameActivity extends AppCompatActivity {    private GameView gameView;    private TextView text_nowScore;    private TextView text_highestScore;    private TextView text_restart;    private TextView text_saveGame;    private ScoreChangeListen scoreChangeListen;    // 游戏设置    private SharedPreferences gameSettings;    private SharedPreferences.Editor gsEditor;    // 历史最高分    private int highestScore;    // 用于实现“在点击一次返回键退出程序”的效果    private boolean isExit = false;    private boolean flag;    private int temp;    private Handler mHandler = new Handler() {        public void handleMessage(Message msg) {            super.handleMessage(msg);            isExit = false;        }    };    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        getSupportActionBar().hide();        Intent intent = getIntent();        Bundle bundle = intent.getExtras();        int row = bundle.getInt("Row", 4);        if (row == 4) {            setContentView(R.layout.activity_four);        } else if (row == 5) {            setContentView(R.layout.activity_five);        } else {            setContentView(R.layout.activity_six);        }        init();        //判断是否需要恢复游戏记录        if (bundle.getBoolean("RecoverGame", false)) {            gameView.recoverGame();        }    }    // 初始化    public void init() {        gameView = (GameView) findViewById(R.id.gameView_five);        text_nowScore = (TextView) findViewById(R.id.nowScore);        text_highestScore = (TextView) findViewById(R.id.highestScore);        text_restart = (TextView) findViewById(R.id.restart);        text_saveGame = (TextView) findViewById(R.id.save_game);        gameSettings = getSharedPreferences("GameSettings", Context.MODE_PRIVATE);        gsEditor = gameSettings.edit();        highestScore = gameSettings.getInt("HighestScore", 0);        text_nowScore.setText("当前得分\n" + 0);        text_highestScore.setText("最高得分\n" + highestScore);        flag = true;        LinearLayout rootLayout = (LinearLayout) findViewById(R.id.rootLayout);        int themeIndex = gameSettings.getInt("ThemeIndex", 1);        switch (themeIndex) {            case 1:                rootLayout.setBackgroundResource(R.drawable.back1);                break;            case 2:                rootLayout.setBackgroundResource(R.drawable.back2);                break;            case 3:                rootLayout.setBackgroundResource(R.drawable.back3);                break;            case 4:                rootLayout.setBackgroundResource(R.drawable.back4);                break;            case 5:                rootLayout.setBackgroundResource(R.drawable.back5);                break;            case 6:                rootLayout.setBackgroundResource(R.drawable.back6);                break;        }        //重新开始游戏        text_restart.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                AlertDialog.Builder builder = new AlertDialog.Builder(GameActivity.this);                builder.setMessage("确认重新开始游戏吗?");                builder.setTitle("提示");                builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {                    @Override                    public void onClick(DialogInterface dialog, int which) {                        gameView.restart();                        text_nowScore.setText("当前得分\n" + 0);                        if (temp != 0) {                            scoreChangeListen.OnHighestScoreChange(temp);                            highestScore = temp;                            flag = true;                        }                    }                });                builder.setNegativeButton("取消", null);                builder.create().show();            }        });        //保存游戏        text_saveGame.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                AlertDialog.Builder builder = new AlertDialog.Builder(GameActivity.this);                builder.setMessage("确认保存游戏吗?");                builder.setTitle("提示");                builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {                    @Override                    public void onClick(DialogInterface dialog, int which) {                        gameView.saveGame();                    }                });                builder.setNegativeButton("取消", null);                builder.create().show();            }        });        scoreChangeListen = new ScoreChangeListen() {            @Override            public void OnNowScoreChange(int Score) {                text_nowScore.setText("当前得分\n" + Score);                if (Score > highestScore) {                    if (flag && highestScore != 0) {                        Toast.makeText(GameActivity.this, "打破最高纪录啦,请继续保持", Toast.LENGTH_SHORT).show();                        flag = false;                    }                    temp = Score;                    text_highestScore.setText("最高得分\n" + temp);                }            }            @Override            public void OnHighestScoreChange(int Score) {                gsEditor.putInt("HighestScore", Score);                gsEditor.commit();            }        };        gameView.scoreChangeListen = scoreChangeListen;    }    // 重写返回键监听事件    public boolean onKeyDown(int keyCode, KeyEvent event) {        if (keyCode == KeyEvent.KEYCODE_BACK) {            exit();            return false;        }        return super.onKeyDown(keyCode, event);    }    private void exit() {        if (!isExit) {            isExit = true;            if (gameView.isHalfway) {                Toast.makeText(this, "再按一次结束游戏,建议保存游戏", Toast.LENGTH_SHORT).show();            } else {                Toast.makeText(this, "再按一次结束游戏", Toast.LENGTH_SHORT).show();            }            // 利用handler延迟发送更改状态信息            mHandler.sendEmptyMessageDelayed(0, 2000);        } else {            finish();        }    }    @Override    protected void onDestroy() {        super.onDestroy();        if (temp != 0) {            scoreChangeListen.OnHighestScoreChange(temp);        }        gameView.soundPool.release();    }}

当中,可以通过mHandler实现“再按一次退出程序的效果”,这个效果需要靠boolean类型的isExit 来控制。
即如果用户点击了一次返回键后,mHandler就会在两秒后发送一条消息改变isExit 的值,如果在这两秒内用户没有再次点击返回键,则就又需要连续点击两次返回键才能退出。

private void exit() {        if (!isExit) {            isExit = true;            if (gameView.isHalfway) {                Toast.makeText(this, "再按一次结束游戏,建议保存游戏", Toast.LENGTH_SHORT).show();            } else {                Toast.makeText(this, "再按一次结束游戏", Toast.LENGTH_SHORT).show();            }            // 利用handler延迟发送更改状态信息            mHandler.sendEmptyMessageDelayed(0, 2000);        } else {            finish();        }    }

GameActivity中使用到了一个自定义接口ScoreChangeListen

/** * Created by ZY on 2016/7/18. */public interface ScoreChangeListen {    void OnNowScoreChange(int Score);    void OnHighestScoreChange(int Score);}

因为显示当前分数以及历史最高分的是两个TextView,GameView无法直接控制,所以就使用回调函数来间接控制TextView的值。

MainActivity的布局也较为简单,一共是六个ImageView,设定点击不同的ImageView执行特定的函数

public class MainActivity extends AppCompatActivity {    // 游戏记录    private SharedPreferences gameRecord;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        getSupportActionBar().hide();        gameRecord = getSharedPreferences("GameRecord", Context.MODE_PRIVATE);    }    //软件说明    public void explain(View view){        Intent intent=new Intent(MainActivity.this,ExplainActivity.class);        startActivity(intent);    }    // 4乘4    public void fourRow(View view) {        Intent intent = new Intent(MainActivity.this, GameActivity.class);        Bundle bundle=new Bundle();        bundle.putInt("Row",4);        intent.putExtras(bundle);        startActivity(intent);    }    // 5乘5    public void fiveRow(View view) {        Intent intent = new Intent(MainActivity.this, GameActivity.class);        Bundle bundle=new Bundle();        bundle.putInt("Row",5);        intent.putExtras(bundle);        startActivity(intent);    }    // 6乘6    public void sixRow(View view) {        Intent intent = new Intent(MainActivity.this, GameActivity.class);        Bundle bundle=new Bundle();        bundle.putInt("Row",6);        intent.putExtras(bundle);        startActivity(intent);    }    //恢复游戏    public void recoverGame(View view){        if(gameRecord.contains("Row")){            int row=gameRecord.getInt("Row",4);            Bundle bundle=new Bundle();            Intent intent = new Intent(MainActivity.this, GameActivity.class);            if(row==4){                bundle.putInt("Row",4);            }else if(row==5){                bundle.putInt("Row",5);            }else{                bundle.putInt("Row",6);            }            bundle.putBoolean("RecoverGame",true);            intent.putExtras(bundle);            startActivity(intent);        }else{            Toast.makeText(MainActivity.this,"没有保存记录,来一局新游戏吧",Toast.LENGTH_SHORT).show();        }    }    //设置    public void settings(View view){        Intent intent = new Intent(MainActivity.this, SettingsActivity.class);        startActivity(intent);    }}

ExplainActivity和SettingsActivity两个Activity较为简单这里就不再赘述

代码我也已上传到了GitHub上了:https://github.com/initobject/2048

2 0
原创粉丝点击