Android 学习笔记之十一 2048的实现分析
2016-05-23 12:49
411 查看
上大学时,就听挺哥说过2048是比较简单的程序,网上有一天学会的教程。
前几天,找了一下教程,实践成功了。
这个网上主要的实现是GridLayout + Card 每一个Card代表网格里面的一个数字,通过操作这些Card显示的数字,显示操作结果
主要的流程如下
完成初始化布局---->
发动点击事件 --->
判断滑动方向 ---》
处理滑动事件 --->
更新分数 ---->
判断是否结束
前面的都是一下简单的布局相关的布局,
在处理滑动事件的时候,涉及到算法的分析。
这里简单的讲一下, 滑动事件,第一,分为上下左右,他们都是类似的 ; 第二,以向左滑动为例,滑动事件可以分为四行,每一行的分析方式相同,
第三,每一行由四个元素组成, 需要逐个元素的计算结果。单个元素分析,以向左滑动为例,单个元素的结果它右边首个大于0的元素有关,如果这个元素与待确认的
元素相等,则相加之后,将该元素值0。 如果待确定的元素,值为0,则直接交换值即可,这里还得回溯一步,加快游戏的节奏,是游戏更紧凑。
MainActivity.java
MianLayout.xml
card .java
Manifst.xml
源码
前几天,找了一下教程,实践成功了。
问题分析
做问题之前,分析必不可少,知道目的,才能有的放矢。1.UI
从界面看,2048 有一个4*4的网格型布局,里面填充有数字,然后有记录分数的文字,这个网上主要的实现是GridLayout + Card 每一个Card代表网格里面的一个数字,通过操作这些Card显示的数字,显示操作结果
2.流程
主要的流程如下
完成初始化布局---->
发动点击事件 --->
判断滑动方向 ---》
处理滑动事件 --->
更新分数 ---->
判断是否结束
3.算法
前面的都是一下简单的布局相关的布局,
在处理滑动事件的时候,涉及到算法的分析。
这里简单的讲一下, 滑动事件,第一,分为上下左右,他们都是类似的 ; 第二,以向左滑动为例,滑动事件可以分为四行,每一行的分析方式相同,
第三,每一行由四个元素组成, 需要逐个元素的计算结果。单个元素分析,以向左滑动为例,单个元素的结果它右边首个大于0的元素有关,如果这个元素与待确认的
元素相等,则相加之后,将该元素值0。 如果待确定的元素,值为0,则直接交换值即可,这里还得回溯一步,加快游戏的节奏,是游戏更紧凑。
具体实现
如下:MainActivity.java
package com.longquan.a2048; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity { public MainActivity(){ mainActivity=this; } @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvscore= (TextView)findViewById(R.id.tvScore); } public void clearScore(){ score=0; showScore(); } public void showScore(){ tvscore.setText(score+""); } public void addScore(int s){ score+=s; showScore(); } private TextView tvscore; private int score=0; public static MainActivity mainActivity=null; public static MainActivity getMainActivity(){ return mainActivity; } }
MyGridView.java
package com.longquan.a2048; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.Color; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.text.TextPaint; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.GridLayout; import android.widget.GridView; import java.util.ArrayList; import java.util.List; /** * * sca */ public class MyGridView extends GridLayout { private float mStartX; private float mStartY; private float mFlipX; private float mFlipY; private Card[][] cardMap = new Card[4][4]; private List<Point> emptyPoints = new ArrayList<Point>(); public MyGridView(Context context) { super(context); init(); } public MyGridView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public MyGridView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { setColumnCount(4); //设置背景颜色 setBackgroundColor(0xffeee4da); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN : mStartX = event.getX(); mStartY = event.getY(); break; case MotionEvent.ACTION_MOVE: ; break; case MotionEvent.ACTION_UP: mFlipX = event.getX() - mStartX; mFlipY = event.getY() - mStartY; if(Math.abs(mFlipX) >= Math.abs(mFlipY)){ if(mFlipX > 15){ swipeRight(); }else if (mFlipX < -15 ){ swipeLeft(); } }else{ if(mFlipY > 15){ swipeDown(); }else if(mFlipY < -15){ swipeUp(); } } break; } return true; } }); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); int CardWidth = (Math.min(w,h))/4; addCards(CardWidth,CardWidth); startGame(); } //在4x4的方格上添加满卡片 public void addCards(int cardwidth,int cardheight){ Card c; for(int y =0;y <4;y++){ for(int x =0;x <4;x++){ c=new Card(getContext()); c.setNum(0); // addView(c,cardwidth,cardheight); addView(c,cardwidth,cardheight); cardMap[x][y]=c; } } } //游戏开始时每个卡片默认值设为0,并随机添加两张带数字的卡片 private void startGame(){ MainActivity.getMainActivity().clearScore(); for(int y =0;y <4;y++){ for(int x =0;x <4;x++){ cardMap[x][y].setNum(0); } } addRandomNum(); addRandomNum(); } private void addRandomNum(){ //使用emptypoints将数字为0的card提取出来,并随即选择一个空card赋值 emptyPoints.clear(); for(int y =0;y <4;y++){ for(int x =0;x <4;x++){ if(cardMap[x][y].getNum()<=0){ emptyPoints.add(new Point(x,y)); } } } Point p = emptyPoints.remove((int)(Math.random()*emptyPoints.size())); //2和4出现的概率控制在1:9 cardMap[p.x][p.y].setNum(Math.random()>0.1?2:4); } //左滑方法 private void swipeLeft(){ //merge作为判断能否滑动的flag boolean merge =false; for(int y =0;y <4;y++){ //以向左滑动为例,滑动事件可以分为四行,每一行的分析方式相同, for(int x =0;x <4;x++){ //每一行由四个元素组成, 需要逐个元素的计算结果。单个元素分析 for(int x1 =x+1;x1 <4;x1++){ if(cardMap[x1][y].getNum()>0){ if(cardMap[x][y].getNum()<=0){ cardMap[x][y].setNum(cardMap[x1][y].getNum()); cardMap[x1][y].setNum(0); merge=true; x--; // 加这一句是为了在两个连续的**22,*22* 的时候,会自动发生合并 }else if(cardMap[x][y].equal(cardMap[x1][y])){ cardMap[x][y].setNum(cardMap[x][y].getNum()*2); cardMap[x1][y].setNum(0); MainActivity.getMainActivity().addScore(cardMap[x][y].getNum()); merge=true; } break; } } } } if(merge){ addRandomNum(); checkComplete(); } } //下滑 private void swipeDown(){ boolean merge =false; for(int x =0;x <4;x++){ for(int y =3;y >=0;y--){ for(int y1 =y-1;y1 >=0;y1--){ if(cardMap[x][y1].getNum()>0){ if(cardMap[x][y].getNum()<=0){ cardMap[x][y].setNum(cardMap[x][y1].getNum()); cardMap[x][y1].setNum(0); y++; merge= true; }else if (cardMap[x][y].equal(cardMap[x][y1])){ cardMap[x][y].setNum(cardMap[x][y].getNum()*2); cardMap[x][y1].setNum(0); MainActivity.getMainActivity().addScore(cardMap[x][y].getNum()); merge= true; } break; } } } } if(merge){ addRandomNum(); checkComplete(); } } //上滑 private void swipeUp(){ boolean merge =false; for(int x =0;x <4;x++){ for(int y =0;y <4;y++){ for(int y1 =y+1;y1 <4;y1++){ if(cardMap[x][y1].getNum()>0){ if(cardMap[x][y].getNum()<=0){ cardMap[x][y].setNum(cardMap[x][y1].getNum()); cardMap[x][y1].setNum(0); y--; merge= true; }else if (cardMap[x][y].equal(cardMap[x][y1])){ cardMap[x][y].setNum(cardMap[x][y].getNum()*2); cardMap[x][y1].setNum(0); MainActivity.getMainActivity().addScore(cardMap[x][y].getNum()); merge= true; } break; } } } } if(merge){ addRandomNum(); checkComplete(); } } //右滑 private void swipeRight(){ boolean merge =false; for(int y =0;y <4;y++){ for(int x =3;x >=0;x--){ for(int x1 =x-1;x1 >=0;x1--){ if(cardMap[x1][y].getNum()>0){ if(cardMap[x][y].getNum()<=0){ cardMap[x][y].setNum(cardMap[x1][y].getNum()); cardMap[x1][y].setNum(0); x++; merge=true; }else if(cardMap[x][y].equal(cardMap[x1][y])){ cardMap[x][y].setNum(cardMap[x][y].getNum()*2); cardMap[x1][y].setNum(0); MainActivity.getMainActivity().addScore(cardMap[x][y].getNum()); merge=true; } break; } } } } if(merge){ addRandomNum(); checkComplete(); } } //如果有空卡片或者相邻的值相同卡片则游戏还能进行 public void checkComplete(){ boolean complete=true; ALL: for(int y =0;y <4;y++){ for(int x =0;x <4;x++){ if(cardMap[x][y].getNum()==0|| x>0&&cardMap[x][y].equal(cardMap[x-1][y])|| x<3&&cardMap[x][y].equal(cardMap[x+1][y])|| y>0&&cardMap[x][y].equal(cardMap[x][y-1])|| y<3&&cardMap[x][y].equal(cardMap[x][y+1])){ complete=false; break ALL; } } } //游戏结束弹出alert提示窗口 if(complete){ new AlertDialog.Builder(getContext()).setTitle("大林哥温馨提示").setMessage("游戏结束").setPositiveButton("重来",new DialogInterface.OnClickListener(){ @Override public void onClick(DialogInterface arg0,int arg1){ startGame(); } }).show(); } } }
MianLayout.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.longquan.a2048.MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:layout_margin="@dimen/activity_left_magin" android:id="@+id/score"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:id="@+id/tvScore"/> </LinearLayout> //使用自定义的GridLayout <com.longquan.a2048.MyGridView android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1" android:id="@+id/GameView"> </com.longquan.a2048.MyGridView> </LinearLayout>
card .java
package com.longquan.a2048; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.widget.FrameLayout; import android.widget.TextView; /** * Created by user on 16-5-20. */ public class Card extends FrameLayout { //卡片显示的数字 private int n; // private TextView labal; public Card(Context context) { super(context); init(); } public Card(Context context, AttributeSet attrs) { super(context, attrs); init(); } public Card(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public Card(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init (){ LayoutParams lp = null; lp = new LayoutParams(-1,-1); View background = new View(getContext()); //间隔 lp.setMargins(10,10,0,0); background.setBackgroundColor(0x33ffffff); addView(background,lp); labal = new TextView(getContext()); labal.setTextSize(28); labal.setGravity(Gravity.CENTER); lp.setMargins(10,10,0,0); addView(labal,lp); } public int getNum(){ return n; } //设置数字及对应的背景颜色 public void setNum(int n){ this.n=n; if(n<=0){ labal.setText(""); }else{ labal.setText(n+""); } switch (n) { case 0: labal.setBackgroundColor(0x00000000); break; case 2: labal.setBackgroundColor(0xffeee4da); break; case 4: labal.setBackgroundColor(0xffede0c8); break; case 8: labal.setBackgroundColor(0xfff2b179); break; case 16: labal.setBackgroundColor(0xfff59563); break; case 32: labal.setBackgroundColor(0xfff67c5f); break; case 64: labal.setBackgroundColor(0xfff65e3b); break; case 128: labal.setBackgroundColor(0xffedcf72); break; case 256: labal.setBackgroundColor(0xffedcc61); break; case 512: labal.setBackgroundColor(0xffedc850); break; case 1024: labal.setBackgroundColor(0xffedc53f); break; case 2048: labal.setBackgroundColor(0xffedc22e); break; default: labal.setBackgroundColor(0xff3c3a32); break; } } //判断卡片是否相等 public boolean equal(Card o){ return getNum()==o.getNum(); } }
Manifst.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.longquan.a2048"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
源码
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories