安卓版本2.3.3
关于移动的详细算法见MFC版本有介绍
效果图:
代码下载: http://pan.baidu.com/s/1qW2ZBMc
APK下载: http://pan.baidu.com/s/1c0rmOcK
代码结构图:
详细代码:
一、主Activity:
Game2048.java
package com.yhy.game2048; import android.os.Bundle; import android.preference.PreferenceManager; import android.app.Activity; import android.content.SharedPreferences; import android.view.Menu; import android.view.View; import android.view.Window; public class Game2048 extends Activity { MyView view; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); view = new MyView(this); /* 注: 自定义标题栏第1步*/ requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); // 读取保存数据 loadData(); // 设置自定义的视图 setContentView(view); /* 自定义标题栏第2步,R.layout.title是自己新建的标题栏的布局文件*/ getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.title); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.game2048, menu); return true; } //onSaveInstanceState用来处理窗口数据保存 @Override protected void onSaveInstanceState(Bundle savedInstanceState) { // TODO Auto-generated method stub //savedInstanceState.putBoolean("hasState", true); saveData(); super.onSaveInstanceState(savedInstanceState); } private void saveData(){ // 数据保存方法之一---SharedPreferences, 类似以xml文件的形式保存 SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences.Editor editor = settings.edit(); //保存是否有保存数据 editor.putBoolean("hasState", true); //保存游戏布局数据 for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) { editor.putInt(i+" "+j, view.game.getValue(i, j)); } //保存游戏得分 editor.putInt("score", view.game.score); //保存最高分 editor.putInt("highestscore", view.game.highestScore); //提交 editor.commit(); } //恢复数据函数 private void loadData(){ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); //第一次安装时,执行面板上数字的初始化,用return返回,不执行后面的代码 if (settings.getBoolean("hasState", false) == false){ view.game.initGrid(); return; } //装载4*4布局中的每个值 for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) { //读取保存的值,并设置到面板上 int value = settings.getInt(i+" "+j, -1); if (value > 0) { view.game.setValue(i, j, value); } } //装载得分 int score = settings.getInt("score", -1); if (score > 0){ view.game.score = score; } //装载最高分 int highestscore = settings.getInt("highestscore", -1); if (highestscore > 0){ view.game.highestScore = highestscore; } } //重新开始游戏按钮响应 public void onReset(View v){ //调取开始新游戏函数 view.game.beginNewGame(); //刷新视图 view.invalidate(); } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); saveData(); } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); loadData(); } }
二、 自定义视图类
先新建MyView类,继承自View类,关于显示的部分都在这里设置,包括设置背景图片,添加触摸屏操作监听器,绘制16个小矩形,绘制每个小矩形的数字并居中,根据每个小矩形的数字填充每个小矩形的颜色,绘制游戏当前得分以及最高分等。
关于字体的参数(用于居中显示):
MyView.java
package com.yhy.game2048; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.FontMetrics; import android.view.View; import android.widget.TextView; //自定义视图类,继承自View类 public class MyView extends View { //状态保存标志 boolean hasSaveState = false; private int height; private int width; private Paint paint; private Paint textPaint; //x方向空白宽度 private int xBlank; //外面框架边长 private int sideLength; //计算外面框架的坐标 private float leftupX; private float leftupY; //内部小矩形的边长 private int litterSideLength; public Game game; // 在自定义view 类里访问主activity 用(Game2048)this.getContext() TextView tv = new TextView((Game2048)this.getContext()); public MyView(Context context) { super(context); // TODO Auto-generated constructor stub paint = new Paint(); textPaint = new Paint(); //设置背景图片 this.setBackgroundDrawable(getContext().getResources().getDrawable(R.drawable.bg2)); //添加监听器用来监听和响应触摸屏操作 this.setOnTouchListener(new InputListener(this)); game = new Game(); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub paint.setColor(Color.BLACK); paint.setStyle(Paint.Style.STROKE); height = getHeight(); width = getWidth(); //x方向空白宽度 xBlank = 100; //边长 sideLength = (width - xBlank); //计算外面框架的坐标 leftupX = xBlank / 2; leftupY = (height - sideLength)/2; litterSideLength = sideLength / 4; textPaint.setTextSize((float)(litterSideLength*0.4)); //画内小矩形 int mycolor; // 根据面板上的值设置小矩形的颜色 for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) { switch(game.getValue(i, j)) { case 0: mycolor =getResources().getColor(R.drawable.color0);break; case 2: mycolor =getResources().getColor(R.drawable.color2);break; case 4: mycolor =getResources().getColor(R.drawable.color4);break; case 8: mycolor =getResources().getColor(R.drawable.color8);break; case 16: mycolor =getResources().getColor(R.drawable.color16);break; case 32: mycolor =getResources().getColor(R.drawable.color32);break; case 64: mycolor =getResources().getColor(R.drawable.color64);break; case 128: mycolor =getResources().getColor(R.drawable.color128);break; case 256: mycolor =getResources().getColor(R.drawable.color256);break; case 512: mycolor =getResources().getColor(R.drawable.color512);break; case 1024: mycolor =getResources().getColor(R.drawable.color1024);break; case 2048: mycolor =getResources().getColor(R.drawable.color2048);break; default:mycolor =getResources().getColor(R.drawable.color2048);break; } paint.setColor(mycolor); //设置画笔为填充 paint.setStyle(Paint.Style.FILL); //画小矩形 canvas.drawRect(leftupX+litterSideLength*j+10, leftupY+litterSideLength*i+10, leftupX+litterSideLength*(j+1), leftupY+litterSideLength*(i+1), paint); } //写数字 textPaint.setColor(Color.BLACK); paint.setStyle(Paint.Style.STROKE); textPaint.setTextAlign(Paint.Align.CENTER); //设置数字的坐标,使居中对齐 //数字的居中对齐跟fm的几个详细参数相关,详细见图片 FontMetrics fm = textPaint.getFontMetrics(); float x = litterSideLength / 2; float y = litterSideLength / 2 - (fm.ascent+fm.descent)/2; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) { if (game.getValue(i, j) != 0) { canvas.drawText(game.getValue(i, j)+"", leftupX+j*litterSideLength+x+5, leftupY+i*litterSideLength+y+5, textPaint); } } textPaint.setTextSize((float)(litterSideLength*0.3)); //写分数 canvas.drawText("分数:", leftupX+litterSideLength/2, (height - sideLength)/4+30, textPaint); canvas.drawText(""+game.score, leftupX+litterSideLength*3/2, (height - sideLength)/4+30, textPaint); canvas.drawText("最高分: ", leftupX+litterSideLength*5/2, (height - sideLength)/4+30, textPaint); canvas.drawText(""+game.highestScore, leftupX+litterSideLength*7/2, (height - sideLength)/4+30, textPaint); super.onDraw(canvas); } }
三、 面板上数字的逻辑部分
跟数字逻辑相关的都在这里处理,包括改变和获取每个小矩形的值,计算得分,开始新游戏,4个方向的移动逻辑等。
Game.java
package com.yhy.game2048; public class Game { private int value[][] = new int[4][4]; int x1,y1; int x2,y2; //标记是否有动作,用来判断是否生成新数字 boolean bHaveDoneSth; //当前得分 public int score = 0; public int highestScore = 0; public Game() { super(); // TODO Auto-generated constructor stub } //第一次游戏时生成初始布局 public void initGrid(){ for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) { if (value[i][j] != 0) return; } //计算初始值是2或者是4 int firstValue; int temp0 = (int)(Math.random()*7); if (temp0 == 0) firstValue = 4; else firstValue = 2; //计算第一个初始位置 int tempX = (int)(Math.random()*4); int tempY = (int)(Math.random()*4); value[tempX][tempY] = firstValue; int tempX2 = (int)(Math.random()*4); int tempY2 = (int)(Math.random()*4); //计算第二个初始位置,如果跟第一个重复则寻找下一个 while (tempX == tempX2 && tempY == tempY2) { tempX2 = (int)(Math.random()*4); tempY2 = (int)(Math.random()*4); } value[tempX2][tempY2] = 2; } //开始新游戏 public void beginNewGame(){ for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) { value[i][j] = 0; } initGrid(); score = 0; } //判断游戏结束 public boolean gameOver(){ //如果有值为0 的矩形,则游戏肯定可以继续,所以直接返回false for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) { if ( value[i][j] == 0 ) return false; } // 对每一行相邻的两个数,如果有相同的,那么游戏可以继续,返回false for (int i = 0; i < 4; i++) for (int j = 0; j < 3; j++) { if ( value[i][j] == value[i][j+1] ) return false; } // 对每一列相邻的两个数,如果有相同的,那么游戏可以继续,返回false for (int j = 0; j < 4; j++) for (int i = 0; i < 3; i++) { if ( value[i][j] == value[i+1][j] ) return false; } return true; } //获取x行y列的矩形中数字值 public int getValue(int x, int y) { return value[x][y]; } //设置x行y列的矩形中数字值 public void setValue(int x, int y, int value1) { value[x][y] = value1; } //移动后如果发生了动作,那么就需要生成一个新数字 public void GenerateNewNum() { int firstValue; int temp0 = (int)(Math.random()*7); if (temp0 == 0) firstValue = 4; else firstValue = 2; //计算位置 int tempA = (int)(Math.random()*16); int tempX = tempA/4; int tempY = tempA%4; //如果位置有值那么重新生成 while (value[tempX][tempY] != 0) { tempA = (int)(Math.random()*16); tempX = tempA/4; tempY = tempA%4; } value[tempX][tempY] = firstValue; } //左移函数 public void MoveLeft() { bHaveDoneSth = false; for (int i = 0; i < 4; i++) { //去掉空格 for (int j = 0; j < 4; j++) { if (value[i][j] != 0) { for (int k = 0; k < j; k++) { if (value[i][k] == 0) { bHaveDoneSth = true; value[i][k] = value[i][j]; value[i][j] = 0; break; } } } } //相加 for (int j = 0; j < 3; j++) { if (value[i][j] != 0) { if (value[i][j+1] == value[i][j]) { bHaveDoneSth = true; //计分,要放在前面,防止value[i][j]改变 score += 2 * value[i][j]; if (score > highestScore) { highestScore = score; } value[i][j] += value[i][j+1]; value[i][j+1] = 0; } } } //去掉空格 for (int j = 0; j < 4; j++) { if (value[i][j] != 0) { for (int k = 0; k < j; k++) { if (value[i][k] == 0) { bHaveDoneSth = true; value[i][k] = value[i][j]; value[i][j] = 0; break; } } } } } if (bHaveDoneSth) { GenerateNewNum(); } } //上移函数 public void MoveUp() { bHaveDoneSth = false; for (int j = 0; j < 4; j++) { //去掉空格 for (int i = 0; i < 4 ; i++) { // if ( value[i][j] != 0 ) { for (int k = 0; k < i; k++) { if (value[k][j] == 0) { bHaveDoneSth = true; value[k][j] = value[i][j]; value[i][j] = 0; break; } } } } //相加 for (int i = 0; i < 3 ; i++) { if ( value[i][j] != 0 ) { if ( value[i+1][j] == value[i][j] ) { bHaveDoneSth = true; //计分 score += 2 * value[i][j]; if (score > highestScore) { highestScore = score; } value[i][j] += value[i+1][j]; value[i+1][j] = 0; } } } //去掉空格 for (int i = 0; i < 4 ; i++) { // if ( value[i][j] != 0 ) { for (int k = 0; k < i; k++) { if (value[k][j] == 0) { bHaveDoneSth = true; value[k][j] = value[i][j]; value[i][j] = 0; break; } } } } } if (bHaveDoneSth) { GenerateNewNum(); } } //右移函数 public void MoveRight() { bHaveDoneSth = false; for (int i = 0; i < 4; i++) { //去掉空格 for (int j = 4 - 1; j >= 0 ; j--) { // if ( value[i][j] != 0 ) { for (int k = 3; k >= j; k--) { if (value[i][k] == 0) { bHaveDoneSth = true; value[i][k] = value[i][j]; value[i][j] = 0; break; } } } } //相加 for (int j = 3; j > 0 ; j--) { if ( value[i][j] != 0 ) { if ( value[i][j-1] == value[i][j] ) { bHaveDoneSth = true; //计分 score += 2 * value[i][j]; if (score > highestScore) { highestScore = score; } value[i][j] += value[i][j-1]; value[i][j-1] = 0; } } } //去掉空格 for (int j = 3; j >= 0 ; j--) { // if ( value[i][j] != 0 ) { for (int k = 3; k >= j; k--) { if (value[i][k] == 0) { bHaveDoneSth = true; value[i][k] = value[i][j]; value[i][j] = 0; break; } } } } } if (bHaveDoneSth) { GenerateNewNum(); } } //下移函数 public void MoveDowm() { bHaveDoneSth = false; for (int j = 3; j >= 0; j--) { //去掉空格 for (int i = 3; i >= 0; i--) { // if ( value[i][j] != 0 ) { for (int k = 3; k >= i; k--) { if (value[k][j] == 0) { bHaveDoneSth = true; value[k][j] = value[i][j]; value[i][j] = 0; break; } } } } //相加 for (int i = 3; i > 0 ; i--) { if ( value[i][j] != 0 ) { if ( value[i-1][j] == value[i][j] ) { bHaveDoneSth = true; //计分 score += 2 * value[i][j]; if (score > highestScore) { highestScore = score; } value[i][j] += value[i-1][j]; value[i-1][j] = 0; } } } //去掉空格 for (int i = 3; i >= 0; i--) { // if ( value[i][j] != 0 ) { for (int k = 3; k >= i; k--) { if (value[k][j] == 0) { bHaveDoneSth = true; value[k][j] = value[i][j]; value[i][j] = 0; break; } } } } } //如果有数字移动那么就产生一个新数字 if (bHaveDoneSth) { GenerateNewNum(); } } }
四、触摸屏操作监听类
用来响应触摸屏操作
InputListener.java
package com.yhy.game2048; import android.app.AlertDialog; import android.content.DialogInterface; import android.view.MotionEvent; import android.view.View; public class InputListener implements View.OnTouchListener{ public MyView mview; //记录触屏操作的起始位置和结束位置 int beginX = 0; int beginY = 0; int endX = 0; int endY = 0; //触屏反应最少距离,少于这个距离的不响应 final int minLength = 50; public InputListener(MyView mview) { super(); // TODO Auto-generated constructor stub this.mview = mview; } //重写onTouch函数用来处理屏幕操作 @Override public boolean onTouch(View view, MotionEvent event) { // TODO Auto-generated method stub switch (event.getAction()) { //按下时获取坐标 case MotionEvent.ACTION_DOWN: beginX = (int)event.getRawX(); beginY = (int)event.getRawY(); break; case MotionEvent.ACTION_MOVE: break; //抬起时计算移动的方向,然后调用方向移动函数 case MotionEvent.ACTION_UP: endX = (int)event.getRawX(); endY = (int)event.getRawY(); int disX = endX - beginX;//x方向移动距离 int disY = endY - beginY;//y方向移动距离 //移动距离太短的忽略掉 if (Math.abs(disX) <= minLength && Math.abs(disY) <= minLength) break; if (Math.abs(disX) > Math.abs(disY))//x方向移动距离大于y { if (beginX > endX) mview.game.MoveLeft(); else mview.game.MoveRight(); } else { if (beginY > endY) mview.game.MoveUp(); else mview.game.MoveDowm(); } //按键后重绘view mview.invalidate(); //判断游戏结束 if (mview.game.gameOver()){ //弹出游戏结束提示框 new AlertDialog.Builder(mview.getContext()) .setTitle("提示") .setMessage("游戏结束!") .setCancelable(false) .setPositiveButton("重新开始", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { // TODO Auto-generated method stub mview.game.beginNewGame(); mview.invalidate(); } }).setNegativeButton("退出游戏", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { // TODO Auto-generated method stub //在view中如何关闭Activity ((Game2048)mview.getContext()).finish(); } }).show(); } break; } return true; } }
五、 其他
1. 自定义布局xml文件
title.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/app_name" android:textStyle="bold" android:layout_centerInParent="true" android:textColor="@drawable/white"/> <ImageButton android:id="@+id/setvalue" android:layout_width="30dp" android:layout_height="30dp" android:layout_marginLeft="30dp" android:layout_marginTop="10dp" android:scaleType="fitStart" android:background="#00000000" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="20dp" android:contentDescription="" android:onClick="onReset" android:src="@drawable/reset"/> </RelativeLayout>
2. 颜色文件 color.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <drawable name="dkgray">#80808FF0</drawable> <drawable name="yello">#F8F8FF00</drawable> <drawable name="white">#FFFFFF</drawable> <drawable name="darkgray">#938192</drawable> <drawable name="lightgreen">#7cd12e</drawable> <drawable name="black">#ff000000</drawable> <drawable name="blue">#ff0000ff</drawable> <drawable name="cyan">#ff00ffff</drawable> <drawable name="gray">#ff888888</drawable> <drawable name="green">#ff00ff00</drawable> <drawable name="ltgray">#ffcccccc</drawable> <drawable name="magenta">#ffff00ff</drawable> <drawable name="red">#ffff0000</drawable> <drawable name="transparent">#00000000</drawable> <drawable name="yellow">#ffffff00</drawable> <drawable name="diyzongse">#663300</drawable> <drawable name="color0">#C8BEB4</drawable> <drawable name="color2">#F0E6DC</drawable> <drawable name="color4">#F0DCC8</drawable> <drawable name="color8">#F0B478</drawable> <drawable name="color16">#F08C5A</drawable> <drawable name="color32">#F0785A</drawable> <drawable name="color64">#F05A3C</drawable> <drawable name="color128">#F05A3C</drawable> <drawable name="color256">#F0C846</drawable> <drawable name="color512">#F0C846</drawable> <drawable name="color1024">#008200</drawable> <drawable name="color2048">#008200</drawable> </resources>
3. 国际化文件
strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">2048</string> <string name="action_settings">Settings</string> <string name="hello_world">Hello world!</string> <string name="game_over">游戏结束!</string> <string name="score">您的得分是:</string> <string name="highestscore">最高分是:</string> <string name="newgame">重新开始</string> <string name="exitgame">退出游戏</string> </resources>
4. 标题栏主题
title_bar.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="TitleBackgroundColor"> <item name="android:background">@drawable/diyzongse</item> </style> <style name="titlestyle" parent="android:Theme"> <item name="android:windowTitleSize">40dp</item> <item name="android:windowTitleBackgroundStyle">@drawable/white</item> </style> </resources>
5. 应用配置
android:theme="@style/titlestyle" 这一句用来设置自定义标题
android:screenOrientation="portrait"这一句用来设置应用程序不能旋转
AndroidManifest.xml
<?xml version="1.1" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.yhy.game2048" android:versionCode="1" android:versionName="1.2" > <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="10" /> <application android:allowBackup="true" android:icon="@drawable/logo2048" android:label="@string/app_name" android:theme="@style/titlestyle" > <activity android:name="com.yhy.game2048.Game2048" android:screenOrientation="portrait" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
(待)