Android实现2048游戏
1.框架
利用安卓开发2048小游戏
2.实现效果图
3.代码
MainActivity
public class MainActivity extends Activity {
public static MainActivity mainActivity = null;
private int score = 0;
public TextView tvScore;
public TextView tvHScore;
public Button restartBtn ;
int count =0 ;
private Map<String,Integer> userinfo;
public static MainActivity getMainActivity() {
return mainActivity;
}
public MainActivity() {
mainActivity = this;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvScore = (TextView) findViewById(R.id.tvScore);
tvHScore = (TextView) findViewById(R.id.tvHScore);
tvHScore.setText(getHighestSore()+"分");
count = userinfo.get("nowcount");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.action_back:
finish();
break;
case R.id.action_myrecord:
startActivity(new Intent(MainActivity.this,Game_Record.class));
break;
default:
break;
}
return true;
}
public void clearScore(){
RefreshHightScore();
score = 0;
showScore();
}
public void showScore(){
tvScore.setText(score+" 分");
tvHScore.setText(getHighestSore() + "分");
}
public void addScore(int s){
score+=s;
showScore();
}
public int getHighestSore(){
int Mnum=0;
//获取记录的最高分
userinfo = SPSave.getUserInfo(MainActivity.this);
if(userinfo.get("HightestScore")!=null){
Mnum = userinfo.get("HightestScore");
}
return Mnum;
}
public void RefreshHightScore(){
int Max = getHighestSore();
if(score>Max){
Max = score;
}
SPSave.savedUserInfo(MainActivity.this,score,Max,count);//更新最高分
}
public int getScore(){
return score;
}
}
GameView
public class GameView extends GridLayout { // 2048游戏主类 让这个类绑定xml
private Card[][] cardsMap = new Card[4][4]; // 用一个二维数组来记录下方阵
private List<Point> emptyPoints = new ArrayList<Point>(); // 把所有的位置(空点的位置)全放在一个数组里面,方便随机地去取
// 为了能让这个GameView从xml文件中能够访问到,要添加构造方法(能传入相关属性的构造方法),为了保险起见,最好把它的三个构造方法都添加上
public GameView(Context context, AttributeSet attrs, int defStyle) { // 构造函数
super(context, attrs, defStyle);
initGameView(); // 初始化
}
public GameView(Context context) { // 构造函数
super(context);
initGameView();
}
public GameView(Context context, AttributeSet attrs) { // 构造函数
super(context, attrs);
initGameView();
}
public void addReButton(){
Button re = new Button(getContext());
re.setWidth(GetCardWidth());
re.setHeight(20);
re.setText("Restart!");
re.setBackgroundColor(Color.parseColor("#A3D6CC"));
re.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startGame();
}
});
addView(re);
}
private void initGameView() { // 初始化
setColumnCount(4); // 指明GridLayout布局是4列的
setRowCount(5);
setBackgroundColor(Color.parseColor("#A3D6CC")); // 配置GameView的背景或颜色
addCards(GetCardWidth(),GetCardWidth());
addReButton();
setOnTouchListener(new View.OnTouchListener() { // 设置交互方式
// 监听上下左右滑动的这几个动作,再由这几个动作去执行特定的代码,去实现游戏的逻辑
private float startX, startY, offsetX, offsetY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_UP:
offsetX = event.getX() - startX;
offsetY = event.getY() - startY;
if (Math.abs(offsetX) > Math.abs(offsetY)) { // 加此判断是为了解决当用户向斜方向滑动时程序应如何判断的问题
if (offsetX < -5) {
swipeLeft();
// System.out.println("left");
} else if (offsetX > 5) {
swipeRight();
// System.out.println("right");
}
} else { // 判断向上向下
if (offsetY < -5) {
swipeUp();
// System.out.println("up");
} else if (offsetY > 5) {
swipeDown();
// System.out.println("down");
}
}
break;
}
return true; // 此处必须返回true,如返回false,则只会监听到MotionEvent.ACTION_DOWN这个事件,返回此事件没有成功,所以后面的事件也不会发生
}
});
}
private int GetCardWidth()
{
//屏幕信息的对象
DisplayMetrics displayMetrics;
displayMetrics = getResources().getDisplayMetrics();
//获取屏幕信息
int cardWidth;
cardWidth = displayMetrics.widthPixels;
//一行有四个卡片,每个卡片占屏幕的四分之一
return ( cardWidth - 10 ) / 4;
}
// 只有第一次创建的时候才会执行一次 只可能会执行一次
// 手机横放的时候不会执行,因为布局宽高不会发生改变,在AndroidManifest文件中配置了横放手机布局不变的参数
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { // 动态计算卡片宽高
super.onSizeChanged(w, h, oldw, oldh);
int cardWidth = (Math.min(w, h) - 10) / 4;
int cardHeight = cardWidth;
startGame();
// addCards(cardWidth, cardHeight);
// 因为第一次创建游戏时,onSizeChanged()会被调用,且仅被调用一次,所以在这里开启游戏很合适
}
private void addCards(int cardWidth, int cardHeight) {
Card card;
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
card = new Card(getContext());
card.setNum(0); // 刚开始全部添加0 此时并不会显示数字
addView(card, cardWidth, cardHeight); // card是一个继承自FrameLayout的View
// 在initGameView()中指明这个GridLayout是四列的方阵
cardsMap[x][y] = card;
}
}
}
private void startGame() { // 开启游戏 若重新开始游戏的话就要先清理,清理完成后添加随机数
MainActivity.getMainActivity().clearScore(); // 刚开始分数清零
for (int y = 0; y < 4; y++) { // 对所有值进行清理
for (int x = 0; x < 4; x++) {
cardsMap[x][y].setNum(0);
}
}
addRandomNum(); // 需要添加两次 两个随机数
addRandomNum();
}
private void addRandomNum() { // 添加随机数 首先需要遍历所有卡片
emptyPoints.clear(); // 添加随机数之前先清空emptyPoints,然后把每一个空点都添加进来
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
if (cardsMap[x][y].getNum() <= 0) {
emptyPoints.add(new Point(x, y)); // 把空点的位置添加进去
// 因为只有空点才能够去添加数字
// 已经有数字的话肯定不会去添加了
}
}
}
Point p = emptyPoints
.remove((int) (Math.random() * emptyPoints.size())); // 随机地移除一个点
// 注意这里仅仅是移除emptyPoints中记录的点了,并没有移除card
cardsMap[p.x][p.y].setNum(Math.random() > 0.1 ? 2 : 4); // 给这个空点添加一个数,2或4,概率为9:1
}
// 实现游戏逻辑 只要有位置的改变就添加新的 最重要的部分:游戏逻辑
private void swipeLeft() {
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 (cardsMap[x1][y].getNum() > 0) { // 如果往右去遍历得到的card的值(获取到的值)不是空的,则做如下逻辑判断
if (cardsMap[x][y].getNum() <= 0) { // 如果当前位置上的值是空的,则将获取到的值移动到当前位置上
cardsMap[x][y].setNum(cardsMap[x1][y].getNum());
cardsMap[x1][y].setNum(0);
x--; // 这里非常重要!!!! 可以测试理解
merge = true;
} else if (cardsMap[x][y].equals(cardsMap[x1][y])) { // 如果当前位置上的值不是空的,而且获取到的值与当前位置上的值相等,则做相加处理,并将结果放在当前位置上
cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);
cardsMap[x1][y].setNum(0);
// 合并时加分 有合并就有添加(分数)
MainActivity.getMainActivity().addScore(
cardsMap[x][y].getNum());
merge = true;
}
break; // 这个break的位置非常重要!!!!! 只能写在这里!! eg:方格最下面一行是2 32
// 64 2,然后往左滑动的情况!
}
}
}
}
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 (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;
} else if (cardsMap[x][y].equals(cardsMap[x1][y])) {
cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);
cardsMap[x1][y].setNum(0);
// 合并时加分
MainActivity.getMainActivity().addScore(
cardsMap[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 (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;
} else if (cardsMap[x][y].equals(cardsMap[x][y1])) {
cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);
cardsMap[x][y1].setNum(0);
// 合并时加分
MainActivity.getMainActivity().addScore(
cardsMap[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 (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;
} else if (cardsMap[x][y].equals(cardsMap[x][y1])) {
cardsMap[x][y].setNum(cardsMap[x][y].getNum() * 2);
cardsMap[x][y1].setNum(0);
// 合并时加分
MainActivity.getMainActivity().addScore(
cardsMap[x][y].getNum());
merge = true;
}
break;
}
}
}
}
if (merge) {
addRandomNum();
checkComplete();
}
}
// 判断游戏是否结束的逻辑
private void checkComplete() {
boolean complete = true;
All: for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
// 游戏没有结束的判定情况 5种情况
// 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; // 写一个标签,跳出所有循环
// }
if(cardsMap[x][y].getNum()==0){
complete = false;
break All;
}
}
}
if (complete) {
AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
dialog.setTitle("你好")
.setMessage("游戏结束")
.setPositiveButton("重来",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
startGame();
}
});
//
// dialog.setNegativeButton("退出游戏", new DialogInterface.OnClickListener() {
//
// @Override
// public void onClick(DialogInterface dialog, int which) {
// MainActivity.getMainActivity().RefreshHightScore();
// MainActivity.getMainActivity().finish();
// }
// });
dialog.show();
}
}
}
Card
public class Card extends FrameLayout {
public Card(Context context) {
super(context);
label = new TextView(getContext());
label.setTextSize(32);
label.setBackgroundColor(0x33ffffff);
label.setGravity(Gravity.CENTER);
LayoutParams lp = new LayoutParams(-1, -1);
lp.setMargins(10, 10, 0, 0);
addView(label, lp);
setNum(0);
}
private int num = 0;
private String[] colorlist = {"#E6C0C0","#A4A8D4","#FFF3B8","#E5C1CD","#6C9BD2","#FAC559","#E6EB94","#A3D6CC","#ffcc33","#66ff66","#B4EEB4"};
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
if (num<=0) {
label.setText("");
label.setBackgroundColor(0x33ffffff);
}else{
label.setText(num+"");
label.setBackgroundColor(Color.parseColor(colorlist[num%11]));
}
}
public boolean equals(Card o) {
return getNum()==o.getNum();
}
private TextView label;
}
GameRecord
public class Game_Record extends Activity {
private Map<String,String> userMap = new HashMap<String,String>();
private ListView mListView ;
@Override
protected void onCreate(Bundle savedInstanceState) {
userMap = SPSave.getUserRecord(getBaseContext()); //获取context??
super.onCreate(savedInstanceState);
// Toast.makeText(Game_Record.this, "Gamerecord", Toast.LENGTH_SHORT).show();
setContentView(R.layout.activity_gamerecord);
mListView = (ListView) findViewById(R.id.recordlist);
//创建一个内部类实例
MylistAdapter mAdapter = new MylistAdapter();
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> AdapterView, View view, int position, long id) {
Toast.makeText(Game_Record.this, "Toast点击显示第"+position+"个item", Toast.LENGTH_SHORT).show();
}
});
}
//创建自定义适配器
class MylistAdapter extends BaseAdapter{
@Override
public int getCount() {
return userMap.size()/2;
} //因为同时保存时间和分数,所以需要/2
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//获取view对象
View view = View.inflate(Game_Record.this,R.layout.resordlist_item,null);
TextView mTextView = (TextView) view.findViewById(R.id.score_ed);
TextView mTimeView = (TextView) view.findViewById(R.id.score_time);
if(userMap.get("score"+position)!=null){
mTextView.setText(userMap.get("score"+position)+"分");
mTimeView.setText(userMap.get("time"+position));
}
ImageView imageView = (ImageView) view.findViewById(R.id.item_image);
// imageView.setBackgroundResource();
return view;
}
}
}
SPSave
//问题:关于记录的关键id,无法确定保存,不知道该如何确定当前应该保存的id,本实验中利用count记录,但不知道如何获取缺少的id,即用户上次退出游戏所应该保存的记录应该是第几条
//每次用户开始游戏,都将从第一条记录开始修改,直到保存到15条为止,循环更新
public class SPSave {
public static boolean savedUserInfo(Context context, int score,int hightscore,int count){
SharedPreferences sp = context.getSharedPreferences("scoredata",Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
count=count%15; //最多保存15条记录
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM月dd日 HH:mm:ss");// HH:mm:ss//获取当前时间
Date date = new Date(System.currentTimeMillis());
edit.putString("time"+count, simpleDateFormat.format(date));
edit.putInt("HightestScore",hightscore);
edit.putString("score"+count, String.valueOf(score));
count++;
edit.putInt("nowcount",count); //注意要在存放之前就把count修改不然count永远是0
edit.commit();
return true;
}
public static Map<String,Integer> getUserInfo(Context context){
SharedPreferences sp = context.getSharedPreferences("scoredata",Context.MODE_PRIVATE);
int hightestScore = sp.getInt("HightestScore",0);
int count = sp.getInt("nowcount",0);
Map<String,Integer> userMap = new HashMap<String,Integer>();
userMap.put("HightestScore",hightestScore);
userMap.put("nowcount",count);
return userMap;
}
public static Map<String,String> getUserRecord(Context context){
SharedPreferences sp = context.getSharedPreferences("scoredata",Context.MODE_PRIVATE);
//获取保存数据,遍历获取。
Map<String,String> userMap = new HashMap<String,String>();
for(int i=0;i<10;i++){
String score= sp.getString("score"+i,null);
String time = sp.getString("time"+i,null);
if(score!=null){
userMap.put("score"+i,score);
}
if(time!=null){
userMap.put("time"+i,time);
}
}
return userMap;
}
}
4.总结
1.关于listview无法显示的问题 :定义的适配器类,加载时的getCount() 和getItem() 方法出了问题,只是快捷键实现了需要重写的方法,没有具体实现,直接return 0 了,就没有办法正常显示
2.对Map<> 数据类型 的使用不熟悉,导致花费了很多时间来做无用功
3.对于通过 SharedPreferences sp=context.getSharedPreferences(“scoredata”,Context.MODE_PRIVATE);存取数据不熟悉,事先没有对需要存取的数据进行准确的分析,也是绕了很多弯。
4.对于Gridlayout和RelativeLayout布局不熟悉
代码链接
代码链接
链接:https://pan.baidu.com/s/1F-VLfkApCxjIaSIrRh4-Vw
提取码:y8du