我也来开发2048之终极奥义

本文深入解析2048游戏的实现原理,包括初始化游戏、触摸事件处理、算法实现、数字Item生成及游戏状态判断等核心功能。通过详细代码解读,帮助读者理解2048游戏背后的算法逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

               

本次教程跟之前隔了不少时间哈,有点忘记了的建议先看看前面的熟悉下,今天我准备把这个2048给结束了,拖了这么久。


按照惯例,我们已经把准备工作都做好了,今天这一部分信息量比较大,也是整个游戏的核心所在,所以我准备分功能来讲,最后大家结合源码来看就不会感觉太吃力了。


1、初始化游戏

 初始化的时候,我们要干嘛呢,首先要看配置,配置了几行,然后先画好面板,然后要给在面板上随机生成2个数字Item,这涉及到2个方法,一个是初始化面板,一个是添加随机数字

private void initGameView(int cardSize) { removeAllViews(); GameItem card; for (int i = 0; i < gameLines; i++) {     for (int j = 0; j < gameLines; j++) {  card = new GameItem(getContext(), 0);  addView(card, cardSize, cardSize);  // 初始化GameMatrix全部为0 空格List为所有  gameMatrix[i][j] = card;  blanks.add(new Point(i, j));     } } // 添加随机数字 addRandomNum(); addRandomNum();    }

private void addRandomNum() { getBlanks(); if (blanks.size() > 0) {     int randomNum = (int) (Math.random() * blanks.size());     Point randomPoint = blanks.get(randomNum);     gameMatrix[randomPoint.x][randomPoint.y].setNum(Math.random() > 0.2d ? 2 : 4);     Game.getGameActivity().getAnimationLayer().animcreate(gameMatrix[randomPoint.x][randomPoint.y]); }    }

在判断生成2、4的时候,我们使用了通常产生范围随机数的方法,同时指定2和4的比例在1:4,当然这个大家可以根据需要更改


2、具体参数等初始化

这个比较简单了,直接上代码,大家应该都看得懂

private void initGameMatrix() // 初始化矩阵 removeAllViews(); scoreHistory = 0; Config.Scroe = 0; Config.GameLines = Config.sp.getInt(Config.KEY_GameLines, 4); gameLines = Config.GameLines; gameMatrix = new GameItem[gameLines][gameLines]; gameMatrixHistory = new int[gameLines][gameLines]; calList = new ArrayList<Integer>(); blanks = new ArrayList<Point>(); highScore = Config.sp.getInt(Config.KEY_HighScore, 0); setColumnCount(gameLines); setRowCount(gameLines); setOnTouchListener(this); // 初始化View参数 DisplayMetrics metrics = new DisplayMetrics(); WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); display.getMetrics(metrics); Config.ItemSize = metrics.widthPixels / Config.GameLines; initGameView(Config.ItemSize);    }

这部分最好还是结合源码看比较好,注释清晰


3、触摸事件

触摸的时候判断4个方向,这个基本也是通用的写法了,不多说了

public boolean onTouch(View v, MotionEvent event) switch (event.getAction()) { case MotionEvent.ACTION_DOWN:     saveHistoryMatrix();     startX = (int) event.getX();     startY = (int) event.getY();     breakcase MotionEvent.ACTION_MOVE:     breakcase MotionEvent.ACTION_UP:     endX = (int) event.getX();     endY = (int) event.getY();     judgeDirection(endX - startX, endY - startY);     if (isMoved()) {  addRandomNum();  // 修改显示分数  Game.getGameActivity().setScore(Config.Scroe, 0);     }     int result = checkCompleted();     if (result == 0) {  if (Config.Scroe > highScore) {      Editor editor = Config.sp.edit();      editor.putInt(Config.KEY_HighScore, Config.Scroe);      editor.commit();  }  Toast.makeText(getContext(), "lose", Toast.LENGTH_LONG).show();  Config.Scroe = 0;     } else if (result == 2) {  Toast.makeText(getContext(), "success", Toast.LENGTH_LONG).show();  Config.Scroe = 0;     }     breakdefault:     break; } return true;    }
其中判断偏移的方法

private void judgeDirection(int offsetX, int offsetY) if (Math.abs(offsetX) > Math.abs(offsetY)) {     if (offsetX > 10) {  swipeRight();     } else {  swipeLeft();     } } else {     if (offsetY > 10) {  swipeDown();     } else {  swipeUp();     } }    }

下面我们来讲2048的终极奥义了,就是算法的实现,具体的算法在第一篇中已经讲解了大概的过程, 点我去看,下面我们选一个方向来讲如何实现

private void swipeLeft() for (int i = 0; i < gameLines; i++) {     for (int j = 0; j < gameLines; j++) {  int currentNum = gameMatrix[i][j].getNum();  if (currentNum != 0) {      if (keyItemNum == -1) {   keyItemNum = currentNum;      } else {   if (keyItemNum == currentNum) {       calList.add(keyItemNum * 2);       Config.Scroe += keyItemNum * 2;       keyItemNum = -1;   } else {       calList.add(keyItemNum);       keyItemNum = currentNum;   }      }  } else {      continue;  }     }     if (keyItemNum != -1) {  calList.add(keyItemNum);     }     // 改变Item值     for (int j = 0; j < calList.size(); j++) {  gameMatrix[i][j].setNum(calList.get(j));     }     for (int m = calList.size(); m < gameLines; m++) {  gameMatrix[i][m].setNum(0);     }     // 重置行参数     keyItemNum = -1;     calList.clear(); }    }

概括来说,就是选取基准,挨个比较,重新排列

代码很清晰,大家看看就知道了,关键是如何总结出这个算法。


4、下面就是判断游戏什么时候需要新加入一个数字Item

当当前的数字矩阵结构域上次的结构发生差别的时候,我们就要add一个新的数字Item了

private boolean isMoved() for (int i = 0; i < gameLines; i++) {     for (int j = 0; j < gameLines; j++) {  if (gameMatrixHistory[i][j] != gameMatrix[i][j].getNum()) {      return true;  }     } } return false;    }

这个地方我们使用了一个历史矩阵来存储上一次的数字矩阵


5、最后就是如何判断结束了

如果当前还有空格的Item,则必定没有结束,若相邻的数字都没有相同的数字,则必定结束,若出现配置的Goal,则赢了

private int checkCompleted() { getBlanks(); if (blanks.size() == 0) {     for (int i = 0; i < gameLines; i++) {  for (int j = 0; j < gameLines; j++) {      if (j < gameLines - 1) {   if (gameMatrix[i][j].getNum() == gameMatrix[i][j + 1].getNum()) {       return 1;   }      }      if (i < gameLines - 1) {   if (gameMatrix[i][j].getNum() == gameMatrix[i + 1][j].getNum()) {       return 1;   }      }  }     }     return 0; } for (int i = 0; i < gameLines; i++) {     for (int j = 0; j < gameLines; j++) {  if (gameMatrix[i][j].getNum() == 2048) {      return 2;  }     } } return 1;    }

下面放出一些最终的图片:



 由于刚换了工作,上下班时间倍增,所以写代码的时间也少了,其实关于这个游戏我还有很多想法没做,这个版本的2048,比网上的版本,多了撤销上次移动功能,多了可以定制游戏维数的功能,多了配置目标值的功能,我这个版本的2048,配置要求极低,相比cocos2dx版本的来说,极大的降低了电量消耗,可配置性更强。这些都是我在玩的过程中,觉得不爽的地方,然后改进的,还有一些想法,目前还没有去做,平时工作去做android的framework了,应用层也就研究的少了,希望大家能改进我的代码,做出更好的2048。

1、添加debug功能,也称作弊后门,我原来是想,当手指滑动超过多少距离后,调用一个新方法,设置添加的随机数的位置和大小,这个用我现在的代码是很好实现的,只要把addRandom方法改下,写一个debugRandom方法就OK了

2、分享功能,这个用ShareSdk就可以了,玩游戏嘛,就是要大家一起玩才好玩,无社交不游戏

3、更换2、4、8、16……数字的背景,这个网上很多了,我们也可以自定义一套背景,这个实现也是比较简单的,只要把GameItem这个类里面的Item的背景添加一个set方法就ok了

以上,终了

以下,刷代码

package com.xys.game2048.view;import java.util.ArrayList;import java.util.List;import android.content.Context;import android.content.SharedPreferences.Editor;import android.graphics.Point;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.view.Display;import android.view.MotionEvent;import android.view.View;import android.view.View.OnTouchListener;import android.view.WindowManager;import android.widget.GridLayout;import android.widget.Toast;import com.xys.game2048.activity.Game;import com.xys.game2048.bean.GameItem;import com.xys.game2048.config.Config;public class GameView extends GridLayout implements OnTouchListener {    // GameView对应矩阵    private GameItem[][] gameMatrix;    // 空格List    private List<Point> blanks;    // 矩阵行列数    private int gameLines;    // 记录坐标    private int startX, startY, endX, endY;    // 辅助数组    private List<Integer> calList;    private int keyItemNum = -1;    // 历史记录数组    private int[][] gameMatrixHistory;    // 历史记录分数    private int scoreHistory;    // 最高记录    private int highScore;    public GameView(Context context) super(context); initGameMatrix();    }    public GameView(Context context, AttributeSet attrs) super(context, attrs); initGameMatrix();    }    public void startGame() { initGameMatrix(); initGameView(Config.ItemSize);    }    private void initGameView(int cardSize) { removeAllViews(); GameItem card; for (int i = 0; i < gameLines; i++) {     for (int j = 0; j < gameLines; j++) {  card = new GameItem(getContext(), 0);  addView(card, cardSize, cardSize);  // 初始化GameMatrix全部为0 空格List为所有  gameMatrix[i][j] = card;  blanks.add(new Point(i, j));     } } // 添加随机数字 addRandomNum(); addRandomNum();    }    /**     * 撤销上次移动     */    public void revertGame() if (gameMatrixHistory.length != 0) {     Game.getGameActivity().setScore(scoreHistory, 0);     Config.Scroe = scoreHistory;     for (int i = 0; i < gameLines; i++) {  for (int j = 0; j < gameLines; j++) {      gameMatrix[i][j].setNum(gameMatrixHistory[i][j]);  }     } }    }    /**     * 添加随机数字     */    private void addRandomNum() { getBlanks(); if (blanks.size() > 0) {     int randomNum = (int) (Math.random() * blanks.size());     Point randomPoint = blanks.get(randomNum);     gameMatrix[randomPoint.x][randomPoint.y].setNum(Math.random() > 0.2d ? 2 : 4);     Game.getGameActivity().getAnimationLayer().animcreate(gameMatrix[randomPoint.x][randomPoint.y]); }    }    /**     * 获取空格Item数组     */    private void getBlanks() { blanks.clear(); for (int i = 0; i < gameLines; i++) {     for (int j = 0; j < gameLines; j++) {  if (gameMatrix[i][j].getNum() == 0) {      blanks.add(new Point(i, j));  }     } }    }    /**     * 初始化View     */    private void initGameMatrix() // 初始化矩阵 removeAllViews(); scoreHistory = 0; Config.Scroe = 0; Config.GameLines = Config.sp.getInt(Config.KEY_GameLines, 4); gameLines = Config.GameLines; gameMatrix = new GameItem[gameLines][gameLines]; gameMatrixHistory = new int[gameLines][gameLines]; calList = new ArrayList<Integer>(); blanks = new ArrayList<Point>(); highScore = Config.sp.getInt(Config.KEY_HighScore, 0); setColumnCount(gameLines); setRowCount(gameLines); setOnTouchListener(this); // 初始化View参数 DisplayMetrics metrics = new DisplayMetrics(); WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); display.getMetrics(metrics); Config.ItemSize = metrics.widthPixels / Config.GameLines; initGameView(Config.ItemSize);    }    @Override    public boolean onTouch(View v, MotionEvent event) switch (event.getAction()) { case MotionEvent.ACTION_DOWN:     saveHistoryMatrix();     startX = (int) event.getX();     startY = (int) event.getY();     breakcase MotionEvent.ACTION_MOVE:     breakcase MotionEvent.ACTION_UP:     endX = (int) event.getX();     endY = (int) event.getY();     judgeDirection(endX - startX, endY - startY);     if (isMoved()) {  addRandomNum();  // 修改显示分数  Game.getGameActivity().setScore(Config.Scroe, 0);     }     int result = checkCompleted();     if (result == 0) {  if (Config.Scroe > highScore) {      Editor editor = Config.sp.edit();      editor.putInt(Config.KEY_HighScore, Config.Scroe);      editor.commit();  }  Toast.makeText(getContext(), "lose", Toast.LENGTH_LONG).show();  Config.Scroe = 0;     } else if (result == 2) {  Toast.makeText(getContext(), "success", Toast.LENGTH_LONG).show();  Config.Scroe = 0;     }     breakdefault:     break; } return true;    }    /**     * 保存历史记录     */    private void saveHistoryMatrix() { scoreHistory = Config.Scroe; for (int i = 0; i < gameLines; i++) {     for (int j = 0; j < gameLines; j++) {  gameMatrixHistory[i][j] = gameMatrix[i][j].getNum();     } }    }    /**     * 根据偏移量判断移动方向     *      * @param offsetX     * @param offsetY     */    private void judgeDirection(int offsetX, int offsetY) if (Math.abs(offsetX) > Math.abs(offsetY)) {     if (offsetX > 10) {  swipeRight();     } else {  swipeLeft();     } } else {     if (offsetY > 10) {  swipeDown();     } else {  swipeUp();     } }    }    /**     * 判断是否结束     *      * @return 0:结束 1:正常 2:成功     */    private int checkCompleted() { getBlanks(); if (blanks.size() == 0) {     for (int i = 0; i < gameLines; i++) {  for (int j = 0; j < gameLines; j++) {      if (j < gameLines - 1) {   if (gameMatrix[i][j].getNum() == gameMatrix[i][j + 1].getNum()) {       return 1;   }      }      if (i < gameLines - 1) {   if (gameMatrix[i][j].getNum() == gameMatrix[i + 1][j].getNum()) {       return 1;   }      }  }     }     return 0; } for (int i = 0; i < gameLines; i++) {     for (int j = 0; j < gameLines; j++) {  if (gameMatrix[i][j].getNum() == 2048) {      return 2;  }     } } return 1;    }    /**     * 判断是否移动过(是否需要新增Item)     *      * @return     */    private boolean isMoved() for (int i = 0; i < gameLines; i++) {     for (int j = 0; j < gameLines; j++) {  if (gameMatrixHistory[i][j] != gameMatrix[i][j].getNum()) {      return true;  }     } } return false;    }    /**     * 滑动事件:上     */    private void swipeUp() for (int i = 0; i < gameLines; i++) {     for (int j = 0; j < gameLines; j++) {  int currentNum = gameMatrix[j][i].getNum();  if (currentNum != 0) {      if (keyItemNum == -1) {   keyItemNum = currentNum;      } else {   if (keyItemNum == currentNum) {       calList.add(keyItemNum * 2);       Config.Scroe += keyItemNum * 2;       keyItemNum = -1;   } else {       calList.add(keyItemNum);       keyItemNum = currentNum;   }      }  } else {      continue;  }     }     if (keyItemNum != -1) {  calList.add(keyItemNum);     }     // 改变Item值     for (int j = 0; j < calList.size(); j++) {  gameMatrix[j][i].setNum(calList.get(j));     }     for (int m = calList.size(); m < gameLines; m++) {  gameMatrix[m][i].setNum(0);     }     // 重置行参数     keyItemNum = -1;     calList.clear(); }    }    /**     * 滑动事件:下     */    private void swipeDown() for (int i = gameLines - 1; i >= 0; i--) {     for (int j = gameLines - 1; j >= 0; j--) {  int currentNum = gameMatrix[j][i].getNum();  if (currentNum != 0) {      if (keyItemNum == -1) {   keyItemNum = currentNum;      } else {   if (keyItemNum == currentNum) {       calList.add(keyItemNum * 2);       Config.Scroe += keyItemNum * 2;       keyItemNum = -1;   } else {       calList.add(keyItemNum);       keyItemNum = currentNum;   }      }  } else {      continue;  }     }     if (keyItemNum != -1) {  calList.add(keyItemNum);     }     // 改变Item值     for (int j = 0; j < gameLines - calList.size(); j++) {  gameMatrix[j][i].setNum(0);     }     int index = calList.size() - 1;     for (int m = gameLines - calList.size(); m < gameLines; m++) {  gameMatrix[m][i].setNum(calList.get(index));  index--;     }     // 重置行参数     keyItemNum = -1;     calList.clear();     index = 0; }    }    /**     * 滑动事件:左     */    private void swipeLeft() for (int i = 0; i < gameLines; i++) {     for (int j = 0; j < gameLines; j++) {  int currentNum = gameMatrix[i][j].getNum();  if (currentNum != 0) {      if (keyItemNum == -1) {   keyItemNum = currentNum;      } else {   if (keyItemNum == currentNum) {       calList.add(keyItemNum * 2);       Config.Scroe += keyItemNum * 2;       keyItemNum = -1;   } else {       calList.add(keyItemNum);       keyItemNum = currentNum;   }      }  } else {      continue;  }     }     if (keyItemNum != -1) {  calList.add(keyItemNum);     }     // 改变Item值     for (int j = 0; j < calList.size(); j++) {  gameMatrix[i][j].setNum(calList.get(j));     }     for (int m = calList.size(); m < gameLines; m++) {  gameMatrix[i][m].setNum(0);     }     // 重置行参数     keyItemNum = -1;     calList.clear(); }    }    /**     * 滑动事件:右     */    private void swipeRight() for (int i = gameLines - 1; i >= 0; i--) {     for (int j = gameLines - 1; j >= 0; j--) {  int currentNum = gameMatrix[i][j].getNum();  if (currentNum != 0) {      if (keyItemNum == -1) {   keyItemNum = currentNum;      } else {   if (keyItemNum == currentNum) {       calList.add(keyItemNum * 2);       Config.Scroe += keyItemNum * 2;       keyItemNum = -1;   } else {       calList.add(keyItemNum);       keyItemNum = currentNum;   }      }  } else {      continue;  }     }     if (keyItemNum != -1) {  calList.add(keyItemNum);     }     // 改变Item值     for (int j = 0; j < gameLines - calList.size(); j++) {  gameMatrix[i][j].setNum(0);     }     int index = calList.size() - 1;     for (int m = gameLines - calList.size(); m < gameLines; m++) {  gameMatrix[i][m].setNum(calList.get(index));  index--;     }     // 重置行参数     keyItemNum = -1;     calList.clear();     index = 0; }    }}



PS 需要源码的请留言


PS 需要源码的请留言
           
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值