首先展示软件运行演示图:

一、程序功能

1、多种不同的游戏模式(主题),本程序除了传统的数字版,还加入了死神版,还加入了秦时明月版。

2、进入不同模式,播放不同的背景音乐,可暂停/开始背景音乐。合并/移动方块会有对应的音效。

3、可重新开始游戏,退出时,可自主选择是否保存成绩。

4、实时显示本局游戏成绩及当前最高分。

5、侧滑可查看本模式的成绩排行榜。

二、基本思路

借鉴网上大神的思路,将游戏局域表格及方块都定义成控件,游戏界面的绘制即为向GameView中添加CardView。然后添加相应的游戏逻辑以及完善小功能即可。

三、程序的基本流程

四、基本功能实现

1、欢迎界面功能设计

欢迎界面有如下功能:5s启动功能选择界面;右上方显示倒计时

延时启动的实现是通过Timer和TimerTask来完成定时任务的。Timer用法如下:

new Timer().schedule(new TimerTask(){
@Override
public void run() {
// TODO Auto-generated method stub
// do some your own process when the timeout come up  }, delay, period);

Timer的作用就是延时或者循环执行。实现延时启动的具体做法为:重写TimerTask()的run()方法为:启动功能选择界面;dalay设为5s;period参数不设,因为这里仅需要单次执行即可,相应的如何设了period这个参数就表示以period为周期,循环执行run方法

右上方显示倒计时:利用android自带的CountDownTimer类。CountDownTimer是一个倒计时的类,还可以指定时间间隔定期通知,举个例子,比你倒计时是100秒的,可以指定每20秒通知一次,这样开始的时候会回调一次,20秒时会回调一次,40秒时会回调一次…,200秒时的回调和时间间隔的回调不同方法的。具体做法为:首先自定义类:MyCountdownTimer,继承于CountDownTimer,重定义onTick()方法,每隔1s修改右上角TextView(在对应的xml中添加的一个TextView控件,用于显示倒计时)的内容。最后调用start方法开始计时。代码如下:

//欢迎界面倒计时protected class MyCountdownTimer extends CountDownTimer {public MyCountdownTimer(long millisInFuture, long countDownInterval) {super(millisInFuture, countDownInterval);}@Overridepublic void onTick(long millisUntilFinished) {countdown.setText("Close(" + millisUntilFinished / 1000 + ")");//设置TextView的内容}@Overridepublic void onFinish() { countdown.setVisibility(View.INVISIBLE);//5s之后,TextView设置为invisible}}

2、功能选择界面功能设计

功能选择界面功能比较简单:点击对应的图片进入不同的游戏界面。

平滑选择图片则是通过使用HorizontalScrollView布局来实现的:具体格式如下:

<HorizontalScrollView><ImageView></ImageView><ImageView></ImageView>
//……
</HorizontalScrollView>

点击图片进入指定的界面,涉及传参。需要传的参数有:row(游戏表格界面的行数),rol(游戏表格界面的列数),model(表不同的游戏模式,1表数字版,2表死神版,3表秦时明月版),rank(表不同的游戏,1表数字版4×4,2表数字版5×5,3表数字版6×6,4表死神版,5表秦时明月版),music(表是否播放背景音乐)

根据点击图片,设置不同参数代码如下:

class ButtonListener implements View.OnClickListener {//他们都要发生跳转,然后各自传不同的参数@Overridepublic void onClick(View v) {if(v instanceof ImageView){//发生跳转Intent intent =new Intent(ChooseActivity.this,MainActivity.class);//传不同参int btnId=v.getId();switch (btnId) {case R.id.Btn4x4:model=1;column = 4;row = 4;rank = 1;break;case R.id.Btn5x5:model=1;column = 5;row = 5;rank = 2;break;case R.id.Btn6x6:model=1;column = 6;row = 6;rank = 3;break;case R.id.BleachBtn:column = 4;row = 4;model = 2;rank = 4;break;case R.id.MoonBtn:column = 4;row = 4;model = 3;rank = 5;break;default:break;}intent.putExtra("column", column);intent.putExtra("row", row);intent.putExtra("model", model);intent.putExtra("music", true);intent.putExtra("rank", rank);startActivity(intent);}}}

3、游戏主界面功能设计

游戏主界面功能如下:显示当前Score与High Score;关闭/打开背景音乐;重新开始游戏;游戏主体;保存个人成绩等。

在介绍具体功能实现之前,先介绍2个自定义控件。

3.1 自定义控件—CardView

CardView 继承 FrameLayout,相当于表格中的一个单元格。主要有三个成员变量:Num(单元格所代表的数值),pic(ImageView类型,显示不同模式下不同数字对应的图片),model(区分游戏模式,因为不同模式下的图片不同)。有四个成员函数putPic()将图片资源存放数组中,数字版数组下标与Num一一对应,死神版数组下标为Num+7,秦时明月版数组下标为Num+23;getNum()获取图片的数字;setNum()设置Card的num值及根据model设置不同的背景图,初始时num值设为0;equal()判断两个card对应的num值是否相等。详细代码见第五节核心代码。

3.2 自定义控件--GameView

GameView 继承 GridLayout,相当于表格,而表格中的每个单元格就是上面介绍的CardView。该控件主要绘制出游戏表格界面以及添加对应的游戏逻辑及游戏规则。

1、通过MainActivity的row与col算出每个CardView的宽与高。

int cardWidth = (w - 10) / colunm;

2、向GameView中添加row * col 个 CardView,num值初始化为0。

for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {c = new CardView(getContext());// 先都初始画0号图片c.setNum(0);addView(c, cardWidth, cardHeigth);// 把所有的卡片都记录下来cardsMap[x][y] = c;}}

3、开始游戏时,添加2个随机数字,随机数字的添加方法是将num值为0的CardView横纵坐标放到一个point类型的list中,然后随机产生一个数字,将对应list中的CardView的num设为2和4,2与4的出现的概率比值为9:1。

// 添加随机数的时候要先遍历private void addRandomNum() {emptyPoints.clear();for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {if (cardsMap[x][y].getNum() <= 0) {emptyPoints.add(new Point(x, y));// 把空位给emptypoints链表}}}// 随机把emptyPoints中的一个赋值,生成2的概率为9,4为1Point p = emptyPoints.remove((int) (Math.random() * emptyPoints.size()));// 2号图片和4号图片cardsMap[p.x][p.y].setNum(Math.random() > 0.1 ? 2 : 4);}

4、游戏过程中根据手的滑动方向进行相应的合并操作。当玩家滑动屏幕时,主要是通过initGameView函数的onTouchListener来监听玩家手指滑动的位置,先通过获取开始坐标和结束坐标,然后通过比较结束坐标跟开始坐标的差值来判断玩家是怎样滑动屏幕的。判断出玩家的滑动轨迹后,通过调用moveLeft、moveRight、moveUp、moveDown方法来实现卡片的移动。每进行一次移动或者合并都要调用checkComplete()判断游戏是否结束。判断游戏结束有两种情况:一是表格已满,无法继续移动;二是出现2048,游戏结束。moveLeft代码如下:

private void moveLeft() {boolean merge = false;flag = false;flag1 = false;for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {// 遍历当前位置的右边,如果有数字,如果当前位置没有数字,则合并到当前位置for (int x1 = x + 1; x1 < colunm; x1++) {// 每个右边的位置只判断执行一次if (cardsMap[x1][y].getNum() > 0) {if (cardsMap[x][y].getNum() <= 0) {cardsMap[x][y].setNum(cardsMap[x1][y].getNum());cardsMap[x1][y].setNum(0);x--;// 填补空位后,还要再次判断有没相同的可以合并的merge = true;if(!flag && !flag1) {mp = MediaPlayer.create(getContext(), R.raw.move);//mp.start();flag = true;}//MyPlayer.playTone(getContext(),//       MyPlayer.INDEX_SONG_MOVE);break;} else if (cardsMap[x][y].equals(cardsMap[x1][y])) {cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);cardsMap[x1][y].setNum(0);my2048.addScore(cardsMap[x][y].getNum());merge = true;//MyPlayer.playTone(getContext(),//        MyPlayer.INDEX_SONG_CANCEL);if(!flag1) {mp = MediaPlayer.create(getContext(), R.raw.del);//mp.start();flag1 = true;}break;}break;}}}}mp.start();if (merge) {addRandomNum();checkComplete();}}

5、GameView的高的设置

这里我困惑了很久,因为起始布局GameView的height设为0dp,自填充,导致下面有一片空白,整个布局,看起来如此的怪,在网上百度很久也没有找到一个在xml文件设置布局的方法,最后只能采用一种动态设置的方法,给GameView外面添加一个LinearLayout,然后GameView的height设为match_parent,然后动态设置外侧LinearLayout的高。

实现代码如下:

//设置表格的高度WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);int width = wm.getDefaultDisplay().getWidth();//屏幕宽度ViewGroup.LayoutParams lp;lp= linearLayout.getLayoutParams();lp.width=width;lp.height=width;linearLayout.setLayoutParams(lp);

3.4、侧滑栏功能实现

侧滑栏界面功能:滑动显示排行榜。

主要通过DrawerLayout来实现侧滑效果。布局文件格式如下:

<android.support.v4.widget.DrawerLayout><LinearLayout>
//主内容区</LinearLayout><LinearLayout>
//侧边菜单<ListView</ListView></LinearLayout>
</android.support.v4.widget.DrawerLayout>

主要重写了DrawerListener()中的onDrawerSlide(),onDrawerOpened()。OnDrawerSide()主要实现滑动过程中有比较好的视觉效果,onDrawerOpened()则需查询数据库将对应的数据适配到ListView中,因为listView一行要显示多行数据则需要使用simpleAdapter适配器。simpleAdapter是简单数组适配器,必须自定义选项item布局。一般使用思路为:设定一个布局文件添加对应控件并赋ID,创建一个用嵌套Map集合的List集合 创建一适配器,实现onItemslectedListener接口 并重写方法 添加数据源,赋值适配器,最后由控件绑定适配器,完成!

//onDrawerOpened的实现代码如下:
@Overridepublic void onDrawerOpened(View drawerView) {// 打开//Toast.makeText(getApplicationContext(), "已打开Drawer", Toast.LENGTH_SHORT).show();//显示排行榜int cnt = 1;db = myDBHelper.getReadableDatabase();cursor = db.rawQuery("select * from " + MyDBHelper.TABLE_NAME + " where Rank = " + rank + " order by Score desc, LastTime", null);if (cursor.getCount() > 0) {list.clear();Map map;while (cursor.moveToNext()) {map=new HashMap<String, String>();map.put("RankNum", String.valueOf(cnt));map.put("IdName", cursor.getString(1));map.put("Score", cursor.getString(2));map.put("LastTime", cursor.getString(3));list.add(map);++cnt;if(cnt > 10)//仅显示前10名break;}SimpleAdapter simpleAdapter =new SimpleAdapter(getApplicationContext(),//第一个参数上下文 当前的Activitylist, //第二个参数是一个集合类型的数据源R.layout.simple_list_item_layout, //第三个参数是一个用于展示效果的Layout就是我们设定的布局文件new String[]{"RankNum","IdName", "Score", "LastTime"}, //第四个参数通过源码可以看出需要的是一个K值的字符串数组new int[]{R.id.rankNum, R.id.idName, R.id.score, R.id.lastTime}//第五个参数通过源码看出是一个与K值匹配的的控件对象);listView.setAdapter(simpleAdapter);}db.close();}

3.5 数据库存储

数据存储主要用到了共享偏好和sqlite

每种模式中最高分采用的是共享偏好存储,为了保证不同模式下的最高分不会相互覆盖,引入一个rank来区分。

数据更新与插入:当当前分数大于最高分时,则会进行更新重写的操作。

// 保存分数public void saveBestScore(int s, int rank) {SharedPreferences.Editor e = getPreferences(MODE_PRIVATE).edit();e.putInt("bestScore" + rank, s);e.commit();}

排行榜需要显现排名,用户名,分数,游戏用时等多个信息,这里用的是SQLite来存储。数据库设计如下表:

字段名

数据类型

说明

Rank

int

模式

Id

text

主键,用户名

Score

int

分数

LastTime

text

游戏时长

数据插入:起始时数据库为空,游戏中定义规则如下:玩家退出游戏时若当前排行榜中人数少于10人,或者成绩进入前10,则弹出对话框提醒用户是否保存成绩。

对话框中用户的成绩和游戏时长已自动读取,无需玩家输入,但是昵称不能为空或者重复。

五、程序核心代码:

MainActivity.Java

public class MainActivity extends AppCompatActivity {private int score = 0;private TextView tvScore;private TextView tvBestScore;private ListView listView;private int column = 4;private int row = 4;private int model = 1;private int rank = 1;private MediaPlayer mp;private Chronometer lastTime;private boolean music = false;private MainActivity mainActivity;private Toolbar toolbar;private DrawerLayout drawer;private ActionBarDrawerToggle toggle;private MyDBHelper myDBHelper;private SQLiteDatabase db;private Cursor cursor;private ArrayList<Map<String,String>> list=new ArrayList<Map<String,String>>();private CreateUserDialog createUserDialog;private LinearLayout linearLayout;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题栏getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);setContentView(R.layout.activity_main);setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);ImageButton resbtn = (ImageButton) findViewById(R.id.restartBtn);final ImageButton controlbtn = (ImageButton)findViewById(R.id.controlBtn);lastTime = (Chronometer) findViewById(R.id.lastTime);tvScore = (TextView) findViewById(R.id.tvScore);tvBestScore = (TextView) findViewById(R.id.bestScore);toolbar = (Toolbar) findViewById(R.id.toolbar);drawer = (DrawerLayout) findViewById(R.id.drawer);listView = (ListView)findViewById(R.id.listView1);linearLayout = (LinearLayout)findViewById(R.id.mylinearlaout);myDBHelper = new MyDBHelper(this);db = myDBHelper.getReadableDatabase();cursor = db.rawQuery("select * from " + myDBHelper.getTableName(), null);controlbtn.setImageDrawable(getResources().getDrawable(R.drawable.pause));Intent intent = this.getIntent();row = intent.getIntExtra("row", 4);column = intent.getIntExtra("column", 4);model = intent.getIntExtra("model", 1);rank = intent.getIntExtra("rank", 1);//设置背景音乐switch (model) {case 1:// 数字版mp = MediaPlayer.create(this, R.raw.num);break;case 2:// 死神版mp = MediaPlayer.create(this, R.raw.bleach);break;case 3:// 秦时明月版mp = MediaPlayer.create(this, R.raw.moon);break;case 4:// 火影版//mp = MediaPlayer.create(this, R.raw.huoyin);break;}try {mp.prepare();// 让mp对象准备} catch (Exception e) {e.printStackTrace();}// 一进来就播放音乐music = intent.getBooleanExtra("music", false);if (music && (null != mp)) {mp.start();mp.setLooping(true);}//设置titleswitch (rank){case 1:toolbar.setTitle(R.string.rank1);break;case 2:toolbar.setTitle(R.string.rank2);break;case 3:toolbar.setTitle(R.string.rank3);break;case 4:toolbar.setTitle(R.string.rank4);case 5:toolbar.setTitle(R.string.rank5);}// 开始游戏的逻辑GameView.getGameView().initGameView();int hour = (int) ((SystemClock.elapsedRealtime() - lastTime.getBase()) / 1000 / 60);lastTime.setFormat("0"+String.valueOf(hour)+":%s");lastTime.start();//开始计时//设置表格的高度WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);int width = wm.getDefaultDisplay().getWidth();//屏幕宽度ViewGroup.LayoutParams lp;lp= linearLayout.getLayoutParams();lp.width=width;lp.height=width;linearLayout.setLayoutParams(lp);//排行榜数据库数据初始化(仅为测试数据库使用)if(cursor.getCount() == 0){db = myDBHelper.getWritableDatabase();//db.beginTransaction();//db.execSQL("insert into " + MyDBHelper.TABLE_NAME + "(Rank, Id, Score, LastTime)values(1, 'Onwaier', 1250, '03:20')");//db.execSQL("insert into " + MyDBHelper.TABLE_NAME + "(Rank, Id, Score, LastTime)values(1, 'Mary', 3550, '06:45')");//db.execSQL("insert into " + MyDBHelper.TABLE_NAME + "(Rank, Id, Score, LastTime)values(1, 'Tom', 1450, '10:20')");// db.setTransactionSuccessful();db.close();}//侧边栏toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close);toggle.syncState();//drawer.addDrawerListener(toggle);drawer.addDrawerListener(new DrawerLayout.DrawerListener() {@Overridepublic void onDrawerStateChanged(int newState) {// 状态发生改变}@Overridepublic void onDrawerSlide(View drawerView, float slideOffset) {// 滑动的过程当中不断地回调 slideOffset:0~1View content = drawer.getChildAt(0);float scale = 1 - slideOffset;//1~0float leftScale = (float) (1 - 0.7 * scale);float rightScale = (float) (0.3f + 0.7 * scale);//0.7~1drawerView.setScaleX(leftScale);//1~0.7drawerView.setScaleY(leftScale);//1~0.7content.setScaleX(rightScale);content.setScaleY(rightScale);content.setTranslationX(drawerView.getMeasuredWidth() * (1 - scale));//0~width}@Overridepublic void onDrawerOpened(View drawerView) {// 打开//Toast.makeText(getApplicationContext(), "已打开Drawer", Toast.LENGTH_SHORT).show();//显示排行榜int cnt = 1;db = myDBHelper.getReadableDatabase();cursor = db.rawQuery("select * from " + MyDBHelper.TABLE_NAME + " where Rank = " + rank + " order by Score desc, LastTime", null);if (cursor.getCount() > 0) {list.clear();Map map;while (cursor.moveToNext()) {map=new HashMap<String, String>();map.put("RankNum", String.valueOf(cnt));map.put("IdName", cursor.getString(1));map.put("Score", cursor.getString(2));map.put("LastTime", cursor.getString(3));list.add(map);++cnt;if(cnt > 10)//仅显示前10名break;}SimpleAdapter simpleAdapter =new SimpleAdapter(getApplicationContext(),//第一个参数上下文 当前的Activitylist, //第二个参数是一个集合类型的数据源R.layout.simple_list_item_layout, //第三个参数是一个用于展示效果的Layout就是我们设定的布局文件new String[]{"RankNum","IdName", "Score", "LastTime"}, //第四个参数通过源码可以看出需要的是一个K值的字符串数组new int[]{R.id.rankNum, R.id.idName, R.id.score, R.id.lastTime}//第五个参数通过源码看出是一个与K值匹配的的控件对象);listView.setAdapter(simpleAdapter);}db.close();}@Overridepublic void onDrawerClosed(View drawerView) {// 关闭//Toast.makeText(getApplicationContext(), "已关闭Drawer", Toast.LENGTH_SHORT).show();}});//重新开始游戏resbtn.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {// TODO Auto-generated method stubnew AlertDialog.Builder(getMainActivity()).setTitle("Reset Game").setMessage("Do you reset game?").setNegativeButton("No", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {}}).setPositiveButton("Yes", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {GameView.getGameView().startGame();clearScore();}}).create().show();}});//暂停/开始播放背景音乐controlbtn.setOnClickListener(new View.OnClickListener(){public void onClick(View v){Drawable myDrawable = controlbtn.getDrawable();Drawable.ConstantState constantState1 =  getResources().getDrawable(R.drawable.start).getConstantState();Drawable.ConstantState constantState2 =  getResources().getDrawable(R.drawable.pause).getConstantState();if( myDrawable.getConstantState().equals(constantState1)){controlbtn.setImageDrawable(getResources().getDrawable(R.drawable.pause));if (null != mp){mp.start();mp.setLooping(true);}}else if(myDrawable.getConstantState().equals(constantState2)){if (null != mp)mp.pause();controlbtn.setImageDrawable(getResources().getDrawable(R.drawable.start));}}});}private long exitTime = 0;//点击返回键时public boolean onKeyDown(int keyCode, KeyEvent event) {if (keyCode == KeyEvent.KEYCODE_BACK&& event.getAction() == KeyEvent.ACTION_DOWN) {if ((System.currentTimeMillis() - exitTime) > 2000) {//弹出提示,可以有多种方式Toast.makeText(getApplicationContext(), "再按一次退出游戏", Toast.LENGTH_SHORT).show();exitTime = System.currentTimeMillis();} else {if(myDBHelper.isOverRank(rank, score, lastTime.getText().toString().substring(5))){new AlertDialog.Builder(getMainActivity()).setTitle("保存成绩").setMessage("Do you want to store the score?").setNegativeButton("No", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {finish();}}).setPositiveButton("Yes", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {showEditDialog();//finish();}}).create().show();}else {lastTime.stop();//计时结束finish();}}return true;}return super.onKeyDown(keyCode, event);}@Overrideprotected   void onPause() {super.onPause();mp.pause();}protected void onResume(){super.onResume();if(mp != null){mp.start();}}public void showEditDialog() {createUserDialog = new CreateUserDialog(this);createUserDialog.InitDialog();createUserDialog.show();createUserDialog.setRank(rank);createUserDialog.setScore(score);createUserDialog.setLastTime(lastTime.getText().toString());createUserDialog.setMainActivity(this);}public MainActivity getMainActivity(){return this;}public int getColumn() {return column;}public int getRow() {return row;}public int getModel() {return model;}public MyDBHelper getMyDBHelper(){return myDBHelper;}public int getRank(){return rank;}public int getScore(){return score;}public String getLastTime(){return lastTime.getText().toString().substring(5);}public void clearScore() {//分数清0score = 0;showScore();}public void showScore() {//显示当前分数tvScore.setText(score + "");}public void addScore(int s) {//更新增加当前分数score += s;showScore();int maxScore = Math.max(score, getBestScore(rank));saveBestScore(maxScore, rank);showBestScore(maxScore);}public void initScore(){//初始化分数score = 0;showScore();db = myDBHelper.getReadableDatabase();cursor = db.rawQuery("select max(Score) from " + MyDBHelper.TABLE_NAME + " where Rank = " + rank, null);if(cursor.getCount() > 0){if(cursor.moveToFirst()){if(getBestScore(rank) > cursor.getInt(0)){saveBestScore(cursor.getInt(0), rank);}}}showBestScore(getBestScore(rank));db.close();}// 保存分数public void saveBestScore(int s, int rank) {SharedPreferences.Editor e = getPreferences(MODE_PRIVATE).edit();e.putInt("bestScore" + rank, s);e.commit();}public int getBestScore(int rank) {//获取当前最高分数return getPreferences(MODE_PRIVATE).getInt("bestScore" + rank, 0);}public void showBestScore(int s) {//显示最高分数tvBestScore.setText(s + "");}// 销毁Activity时执行的,重写此方法表示Activity的生命周期结束不要一直播放占用资源protected void onDestroy() {super.onDestroy();if (null != mp)try {mp.release();} catch (Exception e) {e.getStackTrace();}}
}

GameView.java

/*** Created by Onwaier on 2018/4/7.*/public class GameView extends GridLayout {private float startX, startY, endX, endY, offX, offY;private int row = 4, colunm = 4;// 行row对应y,列colunm对应x,默认开始都为4private CardView[][] cardsMap = new CardView[10][10];// 用一个二维数组来存private List<Point> emptyPoints = new ArrayList<Point>();// 链表方便增加删除private static GameView gview = null;private MainActivity my2048;private MediaPlayer mp;private boolean flag = false, flag1 = false;//private CardAnim ca = new CardAnim();private int cw;// 在xml中能够访问则要添加构造方法// 以防万一三个构造方法都要写:对应参分别为上下文,属性,样式public GameView(Context context) {super(context);gview = this;}public GameView(Context context, AttributeSet attrs) {super(context, attrs);gview = this;}public GameView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);gview = this;}public static GameView getGameView() {return gview;}// 由于手机可能不同,我们需要动态地获取卡片的宽高,所以要重写下面这个方法获取当前布局的宽高,// 为了让手机不会因倒过来改变宽高,要去mainifest里配置// 只会在手机里第一次运行的时候执行,之后不会改变protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);int cardWidth = (w - 10) / colunm;addCards(cardWidth, cardWidth);// 把参数传过去startGame();}public void startGame() {for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {cardsMap[x][y].setNum(0);}}my2048.initScore();my2048.clearScore();addRandomNum();addRandomNum();}private void addCards(int cardWidth, int cardHeigth) {CardView c;cw = cardWidth;for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {c = new CardView(getContext());// 先都初始画0号图片c.setNum(0);addView(c, cardWidth, cardHeigth);// 把所有的卡片都记录下来cardsMap[x][y] = c;}}}// 添加随机数的时候要先遍历private void addRandomNum() {emptyPoints.clear();for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {if (cardsMap[x][y].getNum() <= 0) {emptyPoints.add(new Point(x, y));// 把空位给emptypoints链表}}}// 随机把emptyPoints中的一个赋值,生成2的概率为9,4为1Point p = emptyPoints.remove((int) (Math.random() * emptyPoints.size()));// 2号图片和4号图片cardsMap[p.x][p.y].setNum(Math.random() > 0.1 ? 2 : 4);}// GameView实现的功能public void initGameView() {my2048 = (MainActivity) this.getContext();colunm = my2048.getColumn();row = my2048.getRow();setColumnCount(colunm);// 设置表格为4列setRowCount(row);setBackgroundColor(0xffbbada0);setOnTouchListener(new OnTouchListener() {public boolean onTouch(View v, MotionEvent event) {int action = event.getAction();// 获取触屏的动作switch (action) {// 按下获取起始点case MotionEvent.ACTION_DOWN:startX = event.getX();startY = event.getY();break;// 松开获取终止点,通过比较位移来判断滑动方向// 要处理一下滑动偏的,看offx和offy哪个绝对值大就按照哪个来case MotionEvent.ACTION_UP:endX = event.getX();endY = event.getY();offX = startX - endX;offY = startY - endY;if (Math.abs(offX) >= Math.abs(offY)) {if (offX >= 5)moveLeft();// System.out.println("左");else if (offX < -5)moveRight();// System.out.println("右");} else if (Math.abs(offX) <= Math.abs(offY)) {if (offY >= 5)moveUp();// System.out.println("上");else if (offY < -5)moveDown();// System.out.println("下");}break;}// !!!要改为true,否则ACTION_UP不会执行return true;}});}private void moveLeft() {boolean merge = false;flag = false;flag1 = false;for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {// 遍历当前位置的右边,如果有数字,如果当前位置没有数字,则合并到当前位置for (int x1 = x + 1; x1 < colunm; x1++) {// 每个右边的位置只判断执行一次if (cardsMap[x1][y].getNum() > 0) {if (cardsMap[x][y].getNum() <= 0) {cardsMap[x][y].setNum(cardsMap[x1][y].getNum());cardsMap[x1][y].setNum(0);x--;// 填补空位后,还要再次判断有没相同的可以合并的merge = true;if(!flag && !flag1) {mp = MediaPlayer.create(getContext(), R.raw.move);//mp.start();flag = true;}//MyPlayer.playTone(getContext(),//       MyPlayer.INDEX_SONG_MOVE);break;} else if (cardsMap[x][y].equals(cardsMap[x1][y])) {cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);cardsMap[x1][y].setNum(0);my2048.addScore(cardsMap[x][y].getNum());merge = true;//MyPlayer.playTone(getContext(),//        MyPlayer.INDEX_SONG_CANCEL);if(!flag1) {mp = MediaPlayer.create(getContext(), R.raw.del);//mp.start();flag1 = true;}break;}break;}}}}mp.start();if (merge) {addRandomNum();checkComplete();}}private void moveRight() {boolean merge = false;flag  = false;flag1 = false;for (int y = 0; y < row; y++) {for (int x = colunm - 1; x >= 0; x--) {// 遍历当前位置的右边,如果有数字,如果当前位置没有数字,则合并到当前位置for (int x1 = x - 1; x1 >= 0; x1--) {// 每个右边的位置只判断执行一次if (cardsMap[x1][y].getNum() > 0) {if (cardsMap[x][y].getNum() <= 0) {cardsMap[x][y].setNum(cardsMap[x1][y].getNum());cardsMap[x1][y].setNum(0);x++;// 填补空位后,还要再次判断有没相同的可以合并的merge = true;//MyPlayer.playTone(getContext(),//       MyPlayer.INDEX_SONG_MOVE);if(!flag && !flag1) {mp = MediaPlayer.create(getContext(), R.raw.move);//mp.start();flag = true;}break;} else if (cardsMap[x][y].equals(cardsMap[x1][y])) {cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);cardsMap[x1][y].setNum(0);my2048.addScore(cardsMap[x][y].getNum());merge = true;//MyPlayer.playTone(getContext(),//        MyPlayer.INDEX_SONG_CANCEL);if(!flag1) {mp = MediaPlayer.create(getContext(), R.raw.del);// mp.start();flag1 = true;}break;}break;}}}}mp.start();if (merge) {addRandomNum();checkComplete();}}private void moveUp() {boolean merge = false;flag = false;flag1 = false;for (int x = 0; x < colunm; x++) {for (int y = 0; y < row; y++) {// 遍历当前位置的右边,如果有数字,如果当前位置没有数字,则合并到当前位置for (int y1 = y + 1; y1 < row; y1++) {// 每个右边的位置只判断执行一次if (cardsMap[x][y1].getNum() > 0) {if (cardsMap[x][y].getNum() <= 0) {cardsMap[x][y].setNum(cardsMap[x][y1].getNum());cardsMap[x][y1].setNum(0);y--;// 填补空位后,还要再次判断有没相同的可以合并的merge = true;//MyPlayer.playTone(getContext(),//        MyPlayer.INDEX_SONG_MOVE);if(!flag && !flag1) {mp = MediaPlayer.create(getContext(), R.raw.move);//mp.start();flag = true;}break;} else if (cardsMap[x][y].equals(cardsMap[x][y1])) {cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);cardsMap[x][y1].setNum(0);my2048.addScore(cardsMap[x][y].getNum());merge = true;//MyPlayer.playTone(getContext(),//        MyPlayer.INDEX_SONG_CANCEL);if(!flag1) {mp = MediaPlayer.create(getContext(), R.raw.del);//mp.start();flag1 = true;}break;}break;}}}}mp.start();if (merge) {addRandomNum();checkComplete();}}private void moveDown() {boolean merge = false;flag = false;flag1 = false;for (int x = 0; x < colunm; x++) {for (int y = row - 1; y >= 0; y--) {// 遍历当前位置的右边,如果有数字,如果当前位置没有数字,则合并到当前位置for (int y1 = y - 1; y1 >= 0; y1--) {// 每个右边的位置只判断执行一次if (cardsMap[x][y1].getNum() > 0) {if (cardsMap[x][y].getNum() <= 0) {cardsMap[x][y].setNum(cardsMap[x][y1].getNum());cardsMap[x][y1].setNum(0);y++;// 填补空位后,还要再次判断有没相同的可以合并的merge = true;//MyPlayer.playTone(getContext(),//       MyPlayer.INDEX_SONG_MOVE);if(!flag && !flag1) {mp = MediaPlayer.create(getContext(), R.raw.move);// mp.start();flag = true;}break;} else if (cardsMap[x][y].equals(cardsMap[x][y1])) {cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);cardsMap[x][y1].setNum(0);my2048.addScore(cardsMap[x][y].getNum());merge = true;// MyPlayer.playTone(getContext(),//        MyPlayer.INDEX_SONG_CANCEL);if(!flag1) {mp = MediaPlayer.create(getContext(), R.raw.del);//mp.start();flag1 = true;}break;}break;}}}}mp.start();if (merge) {addRandomNum();checkComplete();}}// 判断结束private void checkComplete() {boolean complete = true;for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {if (cardsMap[x][y].getNum() == 2048)new AlertDialog.Builder(getContext()).setTitle("你好").setMessage("游戏胜利").setPositiveButton("重来",new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {startGame();}}).show();}}ALL: for (int y = 0; y < row; y++) {for (int x = 0; x < colunm; x++) {// 如果还有空位,或者四个方向上还有相同的if (cardsMap[x][y].getNum() == 0|| (x > 0 && cardsMap[x][y].equals(cardsMap[x - 1][y]))|| (x < 3 && cardsMap[x][y].equals(cardsMap[x + 1][y]))|| (y > 0 && cardsMap[x][y].equals(cardsMap[x][y - 1]))|| (y < 3 && cardsMap[x][y].equals(cardsMap[x][y + 1]))) {complete = false;break ALL;// 如果出现这种情况,跳出双重循环,只写一个break只能跳出当前循环}}}if (complete) {if(my2048.getMyDBHelper().isOverRank(my2048.getRank(), my2048.getScore(), my2048.getLastTime())){new AlertDialog.Builder(getContext()).setTitle("保存成绩").setMessage("Do you want to store the score?").setNegativeButton("No", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {}}).setPositiveButton("Yes", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {my2048.showEditDialog();}}).create().show();}new AlertDialog.Builder(getContext()).setTitle("你好").setMessage("游戏结束").setPositiveButton("重来",new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog,int which) {startGame();}}).show();}}
}

CardView.java

/*** Created by Onwaier on 2018/4/7.*/public class CardView extends FrameLayout {private int num = 0;// 只显示数字的话可以用Textview,要是用图片的话要用imgviewprivate TextView label;private ImageView pic;private int[] picArray = new int[3000];private MainActivity my2048;private int model = 1;private LayoutParams lp;public CardView(Context context) {super(context);putPic();pic = new ImageView(getContext());lp = new LayoutParams(-1, -1);// -1,-1就是填充完父类容器的意思lp.setMargins(10, 10, 0, 0);// 用来设置边框很管用addView(pic, lp);// 把imageView加到CardView上setNum(0);}//把数字逻辑实现的2048转化为图片逻辑,只需要把数字定位数组序数,数字对应图片,并保持一一对应关系public void putPic() {//        // 数字版+0picArray[0] = R.drawable.bleach0;picArray[2] = R.drawable.num2;picArray[4] = R.drawable.num4;picArray[8] = R.drawable.num8;picArray[16] = R.drawable.num16;picArray[32] = R.drawable.num32;picArray[64] = R.drawable.num64;picArray[128] = R.drawable.num128;picArray[256] = R.drawable.num256;picArray[512] = R.drawable.num512;picArray[1024] = R.drawable.num1024;picArray[2048] = R.drawable.num2048;
//        // 死神版+5picArray[5] = R.drawable.bleach0;picArray[7] = R.drawable.bleach2;picArray[9] = R.drawable.bleach4;picArray[13] = R.drawable.bleach8;picArray[21] = R.drawable.bleach16;picArray[37] = R.drawable.bleach32;picArray[69] = R.drawable.bleach64;picArray[133] = R.drawable.bleach128;picArray[261] = R.drawable.bleach256;picArray[517] = R.drawable.bleach512;picArray[1029] = R.drawable.bleach1024;picArray[2053] = R.drawable.bleach2048;//秦时明月+25picArray[25] = R.drawable.bleach0;picArray[27] = R.drawable.moon2;picArray[29] = R.drawable.moon4;picArray[33] = R.drawable.moon8;picArray[41] = R.drawable.moon16;picArray[57] = R.drawable.moon32;picArray[89] = R.drawable.moon64;picArray[153] = R.drawable.moon128;picArray[281] = R.drawable.moon256;picArray[537] = R.drawable.moon512;picArray[1049] = R.drawable.moon1024;picArray[2073] = R.drawable.moon2048;}// 数字:数字相当于图片idpublic int getNum() {return num;}public void setNum(int num) {this.num = num;my2048 = (MainActivity) this.getContext();model = my2048.getModel();switch (model) {case 1:// 普通模式pic.setBackgroundResource(picArray[num]);break;case 2:// 死神(bleach)模式pic.setBackgroundResource(picArray[num + 5]);break;case 3:// 秦时明月模式pic.setBackgroundResource(picArray[num + 25]);break;case 4:// **模式pic.setBackgroundResource(picArray[num + 17]);break;}}// 判断数字是否相同public boolean equals(CardView cv) {return getNum() == cv.getNum();}
}

六、参考功能

本程序的部分代码参考博客:https://blog.csdn.net/findingxu/article/details/52420990

在此十分感谢!!!

参考工程:点击打开链接

github地址:在此处

百度网盘:here(提取码:ab8w)

Android小游戏--2048相关推荐

  1. android小游戏模版—重力感应

    好久没更新博客了,今天来谈谈android小游戏---重力感应,一般在游戏里运用的比較多,比方这类游戏有:神庙逃亡.极品飞车,平衡球.三围重力迷宫,重力赛车等. 首先什么是重力感应.重力感应是指如今手 ...

  2. html+css+javascript实现小游戏2048(详解,附源代码)

    html+css+javascript实现小游戏2048(详解,附源代码) 1.上下左右的移动原理相同,这里只详细说明向上移动的方法 2.这里的上下左右由wasd四个键控制 3-小方块空的意思就是没数 ...

  3. 前端小游戏2048(一步步详解附带源代码,源码上传到csdn,可以免费下载)

    2048小游戏 2048是前端开发必经的一个小游戏,2048小游戏包含了HTML,CSS和JavaScript. 简介 <2048>,是一款益智小游戏,这款游戏是由年仅19岁的意大利程序员 ...

  4. android小游戏 开发-推广-盈利 介绍

    ------------------------------ 前一阵写了一个android小游戏<彩色方块对对碰(TetraVex)>自娱自乐,后来在里面加了有米广告放到各个应用市场上免费 ...

  5. 【python教程入门学习】Python实例:小游戏2048

    本节教程通过 2048 的小游戏快速.完整地呈现了使用 Python 语言编程的过程,将之前介绍的内容有机地结合在了一起 .2048是一款流行于手机.平板等终端设备上的益智小游戏,最早于 2014 年 ...

  6. Cocos Creator小游戏-2048(PC、安卓、H5)益智类 项目展示+完整项目源码

    游戏录像 Cocos Creator小游戏-2048 游戏玩法 在棋盘上,每次会增加一个小动物,你可以选择四个方向滑动,然后小动物会按方向移动,遇到相同的小动物就会合并,看谁合并的最多. 功能 1.初 ...

  7. c语言2048项目报告,c语言----项目_小游戏2048

    2048 小游戏 主要是针对逻辑思维的一个训练. 主要学习方面:1.随机数产生的概率.2.行与列在进行移动的时候几种情况.3.messagebox的使用 #include #include #incl ...

  8. CSS小游戏2048—简单小游戏编程有手就行~会玩?你会写吗?

    简单实现2048小游戏 想实现2048游戏书写代码时可以分为三个步骤 一.HTML部分 先书写HTML把游戏结构搭建出来 <!DOCTYPE html> <html lang=&qu ...

  9. C语言小游戏: 2048.c

    概要:2048.c是一个C语言编写的2048游戏,本文将详细分析它的源码和实现.C语言是一种经典实用的编程语言,本身也不复杂,但是学会C语言和能够编写实用的程序还是有一道鸿沟的.本文试图通过一个例子展 ...

  10. Android小游戏开发:简单的合金弹头游戏(一)游戏框架

    本篇博客适合给初学安卓,熟悉基本的JAVA语法,并希望用安卓写个小游戏的初学者参考,因为博主也因大学课程需要,是在2个星期内自学的JAVA和安卓开发,可能会有一些错误和需要优化的地方,望指出 我在这2 ...

最新文章

  1. 渣科如何逆袭进阿里 No.137
  2. linux文字模式怎么传文件夹,虚拟机VMWare文本模式Linux 与 Windows 文件互传
  3. mkdirp——递归创建目录及其子目录
  4. Asp.net中的HttpModule和HttpHandler的简单用法
  5. 重新整理了下mount/umount,免得老是不来新机忘记了
  6. 深度学习准备「爆破」著名的欧拉方程
  7. python-集合数据类型
  8. 压测|关于PHP7和5.6的压测对比
  9. lambda理解与应用
  10. 获取mysql 时钟,员工时钟的MySQL数据库结构
  11. 什么是程序中的数字指纹
  12. 指纹识别算法c++实现
  13. Virtex-4 cameralink开发板PCI图像采集卡定制FPGA-HDMI编码-LVDS相机
  14. 极小化极大(Minimax)算法原理
  15. 服务器系统照片备份,利用云服务器备份照片
  16. 实用的Linux命令行技巧
  17. 传递关系的复合不一定是传递的
  18. Java中基本数据类型和引用数据类型存放的位置(栈内存和堆内存)
  19. python爬虫系列--lxml(etree/parse/xpath)的使用
  20. 清明祭:缅怀手机领域的那些先烈厂商们,你可还记得?

热门文章

  1. 三菱modbusRTU通讯实例_PLC编程 | 用三菱PLC控制机械手实例
  2. Java 后端开发必读的 6 本书!
  3. Excel导入Sql Server出现Null的解决方法
  4. 传智播客java基础入门pdf
  5. 【Java8实战】list分组、过滤、统计、排序等常用操作
  6. cuda+cudnn安装(cudnn下载失败解决),环境配置以及遇到的问题记录!
  7. Oracle的分析函数over()
  8. 计算机科学类单独分区,2020年中科院JCR分区升级版和基础版区别大解析
  9. assign ur here php,ecshop源码分析01
  10. 用 Python 自动生成快闪文字视频