It's a man's next layer 100 [Fifth Layer] -- 2048 games, 100 layer 2048

Source: Internet
Author: User
Tags drawtext

It's a man's next layer 100 [Fifth Layer] -- 2048 games, 100 layer 2048
At the previous layer of the "Sunshine Xiaoqiang" series of blog posts "it's the next layer of men", we have completed our own Snake Game-CrazySnake, maybe many of our friends are not addicted, so today we are playing a 2048 game that has been very popular recently, so that we can enjoy it again. "Sunshine Xiaoqiang" is not currently engaged in Android game development, so the implementation of these games does not require professional game development knowledge, if you have an Android foundation, you can join in to complete the game. Some friends may say, "Will these games be A little simple? They don't help yourself all day, one is a really experienced Daniel, and the other is a friend who likes to look up at the stars. I think Classic is something that has a great long-term guiding significance for us, although these games are small (100 lines can be written to greedy snakes), the programming skills and algorithms contained in these games are very useful for beginners. Confucius said "Learn from the newest", and the learning process is divided into "Learning" and "Xi". Studying the code of predecessors is a kind of learning. After thinking and improving, it is learning. If one day you can talk about these Games quickly and make changes and improvements, you will find that you have improved a lot. In the first three articles about CrazySnake, "Sunshine Xiaoqiang" completed the classic Snake game in his own way and style, this article also requires us to think step by step in our own way and implement our different 2048. i. Introduction to games 2048 is a single online and mobile game developed by 19-year-old Italian Gabriel Cirulli in March 2014. The game task is to move a small square on a grid to combine until a square with a number of 2048 is formed. Why does this game appear? The author developed this game to test whether he was able to create a game from scratch, but the game's surging popularity (less than 4 million visitors within a week) was totally beyond his expectation. Now 2048 is called the "most addictive thing" on the Internet. Because the game is open-source software, there are many release versions and variants on the market.

The game uses the direction keys to move the squares up, down, and left. If two squares with the same number collide in movement, they are merged into a square and the number is changed to the sum of the two. A new square with a value of 2 or 4 will appear each time you move it. When a block with a value of 2048 appears, the game wins. Therefore, the game is called 2048. [Game introduction source Wikipedia] 2. Game layout I modeled the layout of a 2048 game on the market. The layout is as follows:
The detailed layout structure is as follows. The packages outside are all LinearLayout. This is only a layout method. You can also use RelativeLayout layout (fewer objects are created in this way)
The layout file activity_main.xml is as follows:

<LinearLayout xmlns: android = "http://schemas.android.com/apk/res/android" android: layout_width = "fill_parent" android: layout_height = "fill_parent" android: orientation = "vertical" android: background = "# ffffff" android: padding = "20dip"> <LinearLayout android: layout_width = "match_parent" android: layout_height = "100dip" android: orientation = "horizontal"> <TextView android: layout_width = "0dip" android: layout_height = "match_parent" android: layout_weight = "1" android: background = "@ drawable/text_yellow_bg" android: textColor = "# ffffff" android: gravity = "center" android: text = "2048" android: textStyle = "bold" android: textSize = "30dip"/> <LinearLayout android: layout_width = "0dip" android: layout_height = "match_parent" android: layout_weight = "1" android: orientation = "vertical" android: layout_marginLeft = "15dip"> <LinearLayout android: layout_width = "match_parent" android: layout_height = "0dip" android: layout_weight = "3" android: orientation = "vertical" android: background = "@ drawable/text_grey_bg" android: gravity = "center_vertical"> <TextView android: layout_width = "match_parent" android: layout_height = "wrap_content" android: gravity = "center" android: textColor = "# E3D4C1" android: textSize = "16sp" android: text = "score"/> <TextView android: layout_width = "match_parent" android: layout_height = "wrap_content" android: gravity = "center" android: textColor = "# ffffff" android: textSize = "20dip" android: text = "400"/> </LinearLayout> <TextView android: layout_marginTop = "15dip" android: layout_width = "match_parent" android: layout_height = "0dip" android: layout_weight = "2" android: background = "@ drawable/text_red_bg" android: textColor = "# ffffff" android: textSize = "20dip" android: gravity = "center" android: text = "Settings"/> </LinearLayout> <LinearLayout android: layout_width = "0dip" android: layout_height = "match_parent" android: layout_weight = "1" android: orientation = "vertical" android: layout_marginLeft = "15dip"> <LinearLayout android: layout_width = "match_parent" android: layout_height = "0dip" android: layout_weight = "3" android: orientation = "vertical" android: background = "@ drawable/text_grey_bg" android: gravity = "center_vertical"> <TextView android: layout_width = "match_parent" android: layout_height = "wrap_content" android: gravity = "center" android: textColor = "# E3D4C1" android: textSize = "16sp" android: text = "highest score"/> <TextView android: layout_width = "match_parent" android: layout_height = "wrap_content" android: gravity = "center" android: textColor = "# ffffff" android: textSize = "20dip" android: text = "400"/> </LinearLayout> <TextView android: layout_marginTop = "15dip" android: layout_width = "match_parent" android: layout_height = "0dip" android: layout_weight = "2" android: background = "@ drawable/text_red_bg" android: textColor = "# ffffff" android: textSize = "20dip" android: gravity = "center" android: text = "share"/> </LinearLayout> <com. example. export android: layout_marginTop = "20dip" android: layout_width = "match_parent" android: layout_height = "wrap_content" android: background = "@ drawable/my2048view_bg"/> </LinearLayout>
Iii. Custom view My2048View
@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);this.mViewWidth = w;this.mViewHeight = h;cellSpace = ((float)mViewWidth - (TOTAL_COL + 1) * SPACE) / TOTAL_COL;textPaint.setTextSize(cellSpace / 3);}
Obtain the width and height of the View in the onSizeChanged method of the View, and calculate the length (width and height) of each small grid based on the width, where TOTAL_COL = 4 indicates four columns, SPACE indicates the interval width. TextPaint. setTextSize (cellSpace/3) in the last row is used to set the font size of the text paint brush (it doesn't matter if you don't understand it now, you will understand it later ). The related definitions are as follows:
Private static final int TOTAL_ROW = 4; // The row private static final int TOTAL_COL = 4; // The column private static final int SPACE = 15; // gap between rows and columns private int mViewWidth; // View width private int mViewHeight; // View height private float cellSpace; // size of each grid
Next we will start to draw small squares in the onDraw method.
@ Overrideprotected void onDraw (Canvas canvas) {super. onDraw (canvas); String showNum; for (int I = 0; I <TOTAL_ROW; I ++) {for (int j = 0; j <TOTAL_COL; j ++) {pointX = SPACE * (j + 1) + j * cellSpace; pointY = SPACE * (I + 1) + I * cellSpace; // draw the background rectf. set (pointX, pointY, pointX + cellSpace, pointY + cellSpace); paint. setColor (Color. rgb (204,192,178); canvas. drawRect (rectf, paint );}}}
PintX and pointY are the starting positions of the drawn blocks. For example, the red points are the starting positions of Blocks 1, 2, and 3, respectively.
After drawing the square, the next goal is to draw the square of the numbers above and the color corresponding to each number. Let's first observe the rules of the numbers in this game, 2, 4, 8 ...... 2048 can represent 2 ^ 1, 2 ^ 2, 2 ^ 3 ....... 2 ^ 11. at the same time, we can regard the square without a number as the square with a number of 1, that is, 2 ^ 0. therefore, our data range is from 2 ^ 0 to 2 ^ 11. we define the color in 12.
private int[] colors = {Color.rgb(204, 192, 178), //1Color.rgb(253, 235, 213),  //2Color.rgb(252, 224, 174),  //4Color.rgb(255, 95, 95),   //8Color.rgb(255, 68, 68), //16Color.rgb(248, 58, 58), //32Color.rgb(240, 49, 49), //64Color.rgb(233, 39, 39),  //128Color.rgb(226, 29, 29),  //256Color.rgb(219, 19, 19),  //562Color.rgb(211, 10, 10),  //1024Color.rgb(204, 0, 0)   //2048};
It's really not easy to find so many colors, I simply took the last nine colors in the design specifications of Android here (here you can turn into your favorite color) http://www.apkbus.com/design/style/color.html

By analyzing the data range, we can use the 12 consecutive numbers from 0 to 11 to represent 1, 2, 4, 8, and 16 ....... 2048 these numbers are finally calculated using the pow function of Math. Let's first simulate some data to draw out the results.
/*** Simulated Test Data */private void initData () {for (int I = 0; I <TOTAL_ROW; I ++) {for (int j = 0; j <TOTAL_COL; j ++) {int a = (I + 1) * (j + 1); if (a <12) {datas [I] [j] = a;} else {datas [I] [j] = 0 ;}}}}
The above datas is the simulated integer data array. We will rewrite the onDraw method to plot the data.
Private float pointX; private float pointY; @ Overrideprotected void onDraw (Canvas canvas) {super. onDraw (canvas); String showNum; for (int I = 0; I <TOTAL_ROW; I ++) {for (int j = 0; j <TOTAL_COL; j ++) {pointX = SPACE * (j + 1) + j * cellSpace; pointY = SPACE * (I + 1) + I * cellSpace; // draw the background rectf. set (pointX, pointY, pointX + cellSpace, pointY + cellSpace); paint. setColor (colors [datas [I] [j]); canvas. drawRect (rectf, p Aint); if (datas [I] [j]! = 0) {// draw a number if (datas [I] [j] = 1 | datas [I] [j] = 2) {textPaint. setColor (Color. rgb (0, 0, 0);} else {textPaint. setColor (Color. rgb (255,255,255);} showNum = (int) Math. pow (2, datas [I] [j]) + ""; canvas. drawText (showNum, pointX + (cellSpace-textPaint. measureText (showNum)/2, pointY + (cellSpace + textPaint. measureText (showNum, 0, 1)/2, textPaint );}}}}
The font color of random numbers 2 and 4 is distinguished from other font colors. The running effect is as follows, next we will randomly generate a number 2 or 4 to draw it into the grid. It is relatively easy to randomly generate a number 2 or 4. nextInt (2) + 1) * 2 is actually the same as the above analysis. Here we only need to randomly generate 1 and 2, so it is random. nextInt (2) + 1. first let's look at the randomly generated code:
/*** Randomly generate 1 or 2 */private void randomOneOrTwo () {int row = random. nextInt (TOTAL_ROW); int col = random. nextInt (TOTAL_COL); // determines whether data already exists at this location if (datas [row] [col]! = 0) {randomOneOrTwo ();} else {datas [row] [col] = random. nextInt (2) + 1 ;}}
The above Code uses recursion. Why should we use recursion? This is because when we place this random number, we need to randomly generate a coordinate value in the x and y directions. If there is already data in the (x, y) coordinates, we need to obtain it again, until there is no data at (x, y.
Next, let's make this small square move up and down to the edge with our gesture, and rewrite the onTouchEvent method as follows.
private enum Directory{LEFT,RIGHT,BOTTOM,TOP}
private float mDownX;private float mDownY;@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:mDownX = event.getX();mDownY = event.getY();return true;case MotionEvent.ACTION_MOVE:float disX = event.getX() - mDownX;float disY = event.getY() - mDownY;if(Math.abs(disX) > touchSlop || Math.abs(disY) > touchSlop){System.out.println("isMove");isMoved = true;if(Math.abs(disX) > Math.abs(disY)){if(disX > 0){currentDirectory = Directory.RIGHT;}else{currentDirectory = Directory.LEFT;}}else{if(disY > 0){currentDirectory = Directory.BOTTOM;}else{currentDirectory = Directory.TOP;}}}return true;case MotionEvent.ACTION_UP:if(isMoved == true){changeState();randomOneOrTwo();invalidate();isMoved = false;}}return super.onTouchEvent(event);}
private void changeState(){switch (currentDirectory) {case TOP:toTop();break;case BOTTOM:toBottom();break;case LEFT:toLeft();break;case RIGHT:toRight();break;}}
The moving distance is determined when the above hand moves. If the moving distance is greater than tochSlop (16 DIP), the moving direction is determined based on the positive and negative values of the distance. Some may wonder why I need to define four directions and then change the status in ACTION_UP, instead of directly executing the code in the corresponding direction after determining the direction? A friend who has rewritten the onTouchEvent method may know that if we return true, the event is intercepted, so that if our fingers keep moving, they will continuously enter ACTION_MOVE, in fact, we only need to move the finger up and perform other operations. Finally, the core part is reached. The toTop, toBottom, tooLeft, and toRight methods are the biggest mysteries of the game. Let's take a look at this mystery to solve it. First, let's look at the toLeft () method.
Private void toLeft () {int temp; // move to the left for (int I = 0; I <TOTAL_ROW; I ++) {for (int j = 0; j <TOTAL_COL; j ++) {for (int k = 0; k <TOTAL_COL-j-1; k ++) {if (datas [I] [k] = 0) {temp = datas [I] [k]; datas [I] [k] = datas [I] [k + 1]; datas [I] [k + 1] = temp ;}}}} // merge the numbers for (int I = 0; I <TOTAL_ROW; I ++) {for (int j = 0; j <TOTAL_COL; j ++) {for (int k = 0; k <TOTAL_COL-j-1; k ++) {if (datas [I] [k]! = 0 & datas [I] [k] = datas [I] [k + 1]) {datas [I] [k] = datas [I] [k] + 1; datas [I] [k + 1] = 0 ;}}}}}
I have made two major steps in this method. One is to move the whole to the left and move the square with a value of 0 to the right. The second operation is to merge adjacent identical numbers. The above uses the bubble method. Each cycle determines whether the current position is zero. If it is zero, it is exchanged between left and right until the zero is moved to the rightmost, finally, merge the moving array and merge adjacent elements. Some may have questions. Why do we need to repeatedly write two three-tier loops? Why not place the move and merge operations in the same cycle structure? In fact, I also tried to optimize the code in this way to reduce the complexity of the code, but there is a successive problem between the two, that is, to change the above two three-tier loops (merge and move) to another location, then we can't achieve our goal. This merge is carried out under the condition of mobile sorting, so we will use two three-tier loops. In this way, we have basically achieved our own 2048 game. You can set rules and elimination in the game and mobile methods on your own. Maybe one day we can create 2048 or 4092 of our own style, the game also provides scoring, highest record, setting, and other functions. Interested friends can implement and improve the function on their own and put it on the Android Market. Don't worry ~~ Let's try it out first to ensure that there is no problem and then put it on the Android Market. Otherwise, we will be afraid of being scolded by others. 4. The following problems have been found during the trial and modification: 1. When the grid is full, it will crash and automatically exit. The cause of this problem is very simple. We have not completed the judgment of the game.
/*** Randomly generate 1 or 2 */private void randomOneOrTwo () {if (count> = TOTAL_COL * TOTAL_ROW) {currentState = State. FAILL; return;} int row = random. nextInt (TOTAL_ROW); int col = random. nextInt (TOTAL_COL); // determines whether data already exists at this location if (datas [row] [col]! = 0) {randomOneOrTwo ();} else {datas [row] [col] = random. nextInt (2) + 1; count ++ ;}}
Modify the randomOneOrTwo function as above and define a variable count that records the usage of the current grid. If a grid is not added, it will increase by 1. Reduce the number by 1 when the grid is eliminated.
// Merge the numbers for (int I = 0; I <TOTAL_ROW; I ++) {for (int j = 0; j <TOTAL_COL; j ++) {for (int k = 0; k <TOTAL_COL-j-1; k ++) {if (datas [I] [k]! = 0 & datas [I] [k] = datas [I] [k + 1]) {datas [I] [k] = datas [I] [k] + 1; datas [I] [k + 1] = 0; count --;}}}}
And draw the "game end" text to remind you when the status changes.
If (currentState = State. FAILL) {textPaint. setColor (Color. rgb (255,255,255); canvas. drawText ("game end", (mViewWidth-textPaint. measureText ("game end")/2, mViewHeight/2, textPaint );}
It seems that this is not enough, so how can we start again? Make the following judgment in the onTouchEvent method and modify the display at the end (add a restart button at the bottom)
case MotionEvent.ACTION_DOWN:mDownX = event.getX();mDownY = event.getY();if(currentState == State.FAILL){if(mDownY < mViewHeight && mDownY > mViewHeight - cellSpace){currentState = State.RUNNING;initData();invalidate();}}return true;
If (currentState = State. FAILL) {rectf. set (0, mViewHeight-cellSpace, mViewWidth, mViewHeight); paint. setColor (colors [5]); canvas. drawRect (rectf, paint); textPaint. setColor (Color. rgb (255,255,255); canvas. drawText ("game end", (mViewWidth-textPaint. measureText ("game end")/2, mViewHeight/2, textPaint); canvas. drawText ("Start again", (mViewWidth-textPaint. measureText ("game end")/2, mViewHeight-textPaint. measureText ("game end", 0, 1), textPaint );}

2. The score is not recorded. It seems very boring. Now we have added the following code in the merged box to increase the score.
// Merge the numbers for (int I = 0; I <TOTAL_ROW; I ++) {for (int j = 0; j <TOTAL_COL; j ++) {for (int k = 0; k <TOTAL_COL-j-1; k ++) {if (datas [I] [k]! = 0 & datas [I] [k] = datas [I] [k + 1]) {datas [I] [k] = datas [I] [k] + 1; datas [I] [k + 1] = 0; score = score + (int) Math. pow (2, datas [I] [k]); count --;}}}}
/*** Randomly generate 1 or 2 */private void randomOneOrTwo () {if (count> = TOTAL_COL * TOTAL_ROW) {int maxScore = sharedPreference. getInt ("maxScore", 0); if (score> maxScore) {Editor edit = sharedPreference. edit (); edit. putInt ("maxScore", score); edit. commit ();} gameChangeListener. onChangedGameOver (score, maxScore); currentState = State. FAILL; return;} int row = random. nextInt (TOTAL_ROW); int col = random. nextInt (TOTAL_CO L); // determine whether data already exists at this location if (datas [row] [col]! = 0) {randomOneOrTwo ();} else {datas [row] [col] = random. nextInt (2) + 1; count ++ ;}}
The above records are kept in SharedPreference, and the current score is determined at the end of each time. If the game exits suddenly, we should also consider recording the current highest record value. The Code is as follows:
@Overrideprotected void onVisibilityChanged(View changedView, int visibility) {super.onVisibilityChanged(changedView, visibility);if(visibility != View.VISIBLE){int maxScore = sharedPreference.getInt("maxScore", 0);if(score > maxScore){Editor edit = sharedPreference.edit();edit.putInt("maxScore", score);edit.commit();}}}
Now, how can we display these results to the TextView of MainActivity? In My2048View, we define an interface as follows:
public interface GameChangeListener{public void onChangedGameOver(int score, int maxScore);public void onChangedScore(int score);}
And provides the interface registration method:
public void setOnGameChangeListener(GameChangeListener gameChangeListener){this.gameChangeListener = gameChangeListener;gameChangeListener.onChangedGameOver(score, sharedPreference.getInt("maxScore", 0));gameChangeListener.onChangedScore(score);}
The callback is displayed when the game is completed or the extra points are eliminated. The code in MainActivity is as follows:
my2048View = (My2048View) findViewById(R.id.my2048view);my2048View.setOnGameChangeListener(new GameChangeListener() {@Overridepublic void onChangedScore(int score) {scoreText.setText(score + "");}@Overridepublic void onChangedGameOver(int score, int maxScore) {scoreText.setText(score + "");maxScoreText.setText(maxScore + "");}});

In addition to the two shortcomings above, there are still no Animations (which will suddenly disappear) during elimination, which has little impact on the future (it is not too early ). 5. Source CODE download and description of the project code I hosted on the CSDN CODE above: git@code.csdn.net: lxq_xsyu/my2048.git (Please use Git tool download) Description: this is the code that sunshine Xiaoqiang stayed up late. If you have any questions or questions about the above ideas or implementation process, please reply to the blog and we will improve it together. In addition, sunshine Xiaoqiang this blog participated in the CSDN blog contest finals, if you feel helpful to you, please vote for your valuable vote, voting address: http://vote.blog.csdn.net/article/details? Articleid = 37863693


How to get the fifth layer of the game's 100

Playing... How do you play this ~~~~
 
How can I escape the fifth layer of a mobile game on the fifth layer?

Thank you very much! Long live!
 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.