Android allows you to play games 2048. In fact, 2048 is only a common control, android2048

Source: Internet
Author: User
Tags drawtext gety

Android allows you to play games 2048. In fact, 2048 is only a common control, android2048
Reprinted please indicate the source: bytes

The blogger wanted to step into the open game industry, but the water was too deep to swim. Therefore, he could only continue to develop applications, but could the native Android develop games, 2048. pixel birds, don't step on anything. Today we will bring you a 2048 development article. Don't worry about context-free, or 1, 2, 3, and 4, you can have fun with a pack ~ Although I have never played 2048 !!! You can also use it as a custom control ~~~

In particular, the blocks in game 2048 are of the following colors: blocks;

Next we will post a project:


OK, I am going to lead you to conquer this game ~~~

2. Implementation Analysis

Paste a static graph and start designing our game:


As you can see, the game is actually a container. There are many blocks in it, and the form of the blocks in the container will change. So:

1. Container we prepare a custom ViewGroup called Game2048Layout. The block custom View in the container is called Game2048Item.

Next, let's start with a simple process:

2. Game2048Item

Game2048Item is a View and what attributes are required?

First, you must have a number to display the number. Then, you can draw the background color based on the number. What else do you need? Well, the side length of the square needs to be taken into consideration. Should this side length be controlled by the Item itself? Obviously not. Game2048Layout is an n * n panel and n is uncertain. Therefore, the length of the side of the Item must be calculated by Game2048Layout. In this way, these two attributes are required.

3. Game2048Layout

Game2048Layout is a container. we can observe that the View in it is an n * n arrangement, and we are going to let it inherit RelativeLayout. In this way, we can locate it by setting attributes such as RIGHT_OF of the Item;

In onMeasure, we get the width and height of Layout, then generate a certain number of items based on n * n, set the width and height for them, and place them in Layout, in this way, the layout of the entire game is ready. In terms of the details of the painting, there is a horizontal and vertical spacing between items. Therefore, you need to set this value, which is called mMargin. Then the length of the side of the Item = (Layout side length-(n-1) * mMagin)/n;

The rest is the onTouchEvent to determine the user gesture, and then perform various logical operations ~

3. Code journey

First, let's take a look at our Game2048Item

1. Game2048Item

Package com. zhy. game2048.view; import android. content. context; import android. graphics. canvas; import android. graphics. color; import android. graphics. paint; import android. graphics. paint. style; import android. graphics. rect; import android. util. attributeSet; import android. util. log; import android. view. view;/*** every Item in *** @ author zhy ***/public class Game2048Item extends View {/*** number on this View */private Int mNumber; private String mNumberVal; private Paint mPaint;/*** area for drawing text */private Rect mBound; public Game2048Item (Context context Context, AttributeSet attrs, int defStyle) {super (context, attrs, defStyle); mPaint = new Paint ();} public Game2048Item (Context context) {this (context, null);} public Game2048Item (Context context, attributeSet attrs) {this (context, attrs, 0);} public void setNumber (int number) {mNumb Er = number; mNumberVal = mNumber + ""; mPaint. setTextSize (30366f); mBound = new Rect (); mPaint. getTextBounds (mNumberVal, 0, mNumberVal. length (), mBound); invalidate () ;}public int getNumber () {return mNumber ;}@ Overrideprotected void onDraw (Canvas canvas) {super. onDraw (canvas); String mBgColor = ""; switch (mNumber) {case 0: mBgColor = "# CCC0B3"; break; case 2: mBgColor = "# EEE4DA"; break; case 4: mBgColor = "# EDE0C 8 "; break; case 8: mBgColor =" # F2B179 "; // # F2B179break; case 16: mBgColor =" # F49563 "; break; case 32: mBgColor = "# F5794D"; break; case 64: mBgColor = "# F55D37"; break; case 128: mBgColor = "# EEE863"; break; case 256: mBgColor = "# EDB04D"; break; case 512: mBgColor = "# ECB04D"; break; case 1024: mBgColor = "# EB9437"; break; case 2048: mBgColor = "# EA7821"; break; default: mBgColor = "# EA7821"; break;} mPaint. setColor (Color. pa RseColor (mBgColor); mPaint. setStyle (Style. FILL); canvas. drawRect (0, 0, getWidth (), getHeight (), mPaint); if (mNumber! = 0) drawText (canvas);}/*** draw text ** @ param canvas */private void drawText (Canvas canvas) {mPaint. setColor (Color. BLACK); float x = (getWidth ()-mBound. width ()/2; float y = getHeight ()/2 + mBound. height ()/2; canvas. drawText (mNumberVal, x, y, mPaint );}}

It's easy. Basically, an onDraw uses number to draw the background and number. number is set by calling setNumer. Its width and height are fixed values, so we don't need to perform measurement on our own ~~

2. Game2048Layout

1. member variables

This is our most important class. First, let's take a look at the member variables of this class. First, let's look at the role of each member variable:

/*** Set the number of items n * n. The default value is 4 */private int mColumn = 4;/*** stores all items */private Game2048Item [] mGame2048Items; /*** margin between Item horizontal and vertical */private int mMargin = 10;/*** panel padding */private int mPadding; /*** user sliding gesture detection */private GestureDetector mGestureDetector; // determines whether to generate a new value private boolean isMergeHappen = true; private boolean isMoveHappen = true; /*** record score */private int mScore;

These are the main member variables. It is easier to understand the comments directly ~~

After learning about the member variables, we need to get some values and initialize some variables in the constructor.

2. Constructor

Public Game2048Layout (Context context, AttributeSet attrs, int defStyle) {super (context, attrs, defStyle); mMargin = (int) TypedValue. applyDimension (TypedValue. COMPLEX_UNIT_DIP, mMargin, getResources (). getDisplayMetrics (); // set the padding of Layout. The four sides are the same. Set it to the minimum value of the four padding mPadding = min (getPaddingLeft (), getPaddingTop (), getPaddingRight (), getPaddingBottom (); mGestureDetector = new GestureDetector (context, new MyGestureDetector ());}

In the constructor, we can get the margin (margin) between items and the padding (padding,) of our container. This value should be consistent with the four sides, so we take the minimum value of the four sides; these two attributes can be extracted as custom attributes, and then our mGestureDetector is initialized.

With margin and padding, we can calculate the edge length of our item. This calculation process must be in onMeasure, because we need to get the width and height of the container in onMeasure.


3. onMeasure

Private boolean once;/*** measure the width and height of Layout and set the width and height of Item, ignore wrap_content to draw a square with the minimum value in width and height */@ Overrideprotected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {super. onMeasure (widthMeasureSpec, heightMeasureSpec); // obtain the side length of a square. int length = Math. min (getMeasuredHeight (), getMeasuredWidth (); // get the Item width int childWidth = (length-mPadding * 2-mMargin * (mColumn-1)/mColumn; if (! Once) {if (mGame2048Items = null) {mGame2048Items = new Game2048Item [mColumn * mColumn] ;}// place Itemfor (int I = 0; I <mGame2048Items. length; I ++) {Game2048Item item = new Game2048Item (getContext (); mGame2048Items [I] = item; item. setId (I + 1); RelativeLayout. layoutParams lp = new LayoutParams (childWidth, childWidth); // sets the horizontal margin, not the last column if (I + 1) % mColumn! = 0) {lp. rightMargin = mMargin;} // if it is not the first column if (I % mColumn! = 0) {lp. addRule (RelativeLayout. RIGHT_OF, // mGame2048Items [I-1]. getId ();} // if it is not the first row, // set the vertical margin, if (I + 1)> mColumn) {lp. topMargin = mMargin; lp. addRule (RelativeLayout. BELOW, // mGame2048Items [I-mColumn]. getId ();} addView (item, lp);} generateNum ();} once = true; setMeasuredDimension (length, length );}

First, set the side length of the container to the minimum value of the width and height, and then (length-mPadding * 2-mMargin * (mColumn-1)/mColumn; to calculate the side length of the Item;

After obtaining the information, initialize our Item Array Based on our mColumn, traverse and generate the Item, set the LayoutParams and Rule (RIGHT_OF, BELOW) of the Item, and add it to our container;

Finally, we use setMeasuredDimension (length, length); to change the space occupied by our layout;

At this point, the entire panel has been drawn;

The next step is to operate the game logic based on the user's gestures. The gestures must be onTouchEvent:

4. onTouchEvent

@Overridepublic boolean onTouchEvent(MotionEvent event){mGestureDetector.onTouchEvent(event);return true;}

We handed the touch event to mGestureDetector. Let's take a look at our mGestureDetector. The constructor includes the following sentence:

MGestureDetector = new GestureDetector (context, new MyGestureDetector ());

So, let's take a look at MyGestureDetector:

class MyGestureDetector extends GestureDetector.SimpleOnGestureListener{final int FLING_MIN_DISTANCE = 50;@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY){float x = e2.getX() - e1.getX();float y = e2.getY() - e1.getY();if (x > FLING_MIN_DISTANCE&& Math.abs(velocityX) > Math.abs(velocityY)){action(ACTION.RIGHT);} else if (x < -FLING_MIN_DISTANCE&& Math.abs(velocityX) > Math.abs(velocityY)){action(ACTION.LEFT);} else if (y > FLING_MIN_DISTANCE&& Math.abs(velocityX) < Math.abs(velocityY)){action(ACTION.DOWM);} else if (y < -FLING_MIN_DISTANCE&& Math.abs(velocityX) < Math.abs(velocityY)){action(ACTION.UP);}return true;}}

It is very easy, that is, to interpret the user's sliding up, down, left, right; then call the action method; ACTION is an enumeration:

/*** Motion direction enumeration ** @ author zhy **/private enum ACTION {LEFT, RIGHT, UP, DOWM}

In this case, the core code is included in the action method:

5. redraw items based on user gestures

Before reading the code, consider how the Panel should change when the user slides from the right to the left; take one of the rows, the possibility is:

0 0 0 2-> 2 0 0 0

2 0 4 0-> 2 4 0 0

2 2 4 0-> 4 4 4 0 0

Probably this is the possible 3;

Our algorithms do this:

Take 2 2 4 0 for example:

1. First, extract numbers from each row and store them temporarily, that is, [2, 2, 4].

2. traverse and merge the same of the first encounter, that is, [4, 4, 0].

3. Place the data directly to the original row. If the value is less than 0, it is [4, 4, 0, 0].

There are several operations in the middle:

1. generate a new number. Each time a user slides, the game may generate a number. Our Generation Policy: If a move or merge occurs, generate a number;

The moving judgment compares the original data, that is, [2, 2, 4, 0] with the temporary storage in our first step. The comparison is one-to-one (traversing the temporary table) and the difference is found, it is deemed to have moved;

The flag is set to true during merging;

2. Add points. If merged, add points. The score is the combined number, for example, 4-> 8, that is, add 8 points; you only need to add the values during the merge operation;

After the introduction, let's look at our code:

/*** Move and merge values according to user movement. */private void action (ACTION action) {// row | column for (int I = 0; I <mColumn; I ++) {List <Game2048Item> row = new ArrayList <Game2048Item> (); // row | column for (int j = 0; j <mColumn; j ++) {// obtain the subscript int index = getIndexByAction (action, I, j); Game2048Item item = mGame2048Items [index]; // if (item. getNumber ()! = 0) {row. add (item) ;}}for (int j = 0; j <mColumn & j <row. size (); j ++) {int index = getIndexByAction (action, I, j); Game2048Item item = mGame2048Items [index]; if (item. getNumber ()! = Row. get (j ). getNumber () {isMoveHappen = true ;}// merge the same mergeItem (row); // set the merged value for (int j = 0; j <mColumn; j ++) {int index = getIndexByAction (action, I, j); if (row. size ()> j) {mGame2048Items [index]. setNumber (row. get (j ). getNumber ();} else {mGame2048Items [index]. setNumber (0) ;}}} generateNum ();}

Generally, it is a two-tier loop, with the number of code loops in the outer loop and three for loops in the inner layer;

The first for loop corresponds to the above: first, extract the numbers in each row and store them temporarily, that is, [2, 2, 4];

The second for loop is used to determine whether to move;

// Merge the same
MergeItem (row); is to merge operations, corresponding to the above: Then traverse the same of the first encounter in the merge, that is, [4, 4, 0]; and add points and set the merged flag spaces in the method;

The third for loop: Set the merged value, corresponding to the above: then place it directly to the original row. If it is not enough, fill 0, that is, [4, 4, 0, 0];

Finally, a number is generated. The method internally determines whether the game is over and whether a number needs to be generated;

Let's take a look at mergeItem code:

/*** Merge the same Item ** @ param row */private void mergeItem (List <Game2048Item> row) {if (row. size () <2) return; for (int j = 0; j <row. size ()-1; j ++) {Game2048Item item1 = row. get (j); Game2048Item item2 = row. get (j + 1); if (item1.getNumber () = item2.getNumber () {isMergeHappen = true; int val = item1.getNumber () + item2.getNumber (); item1.setNumber (val ); // Add mScore + = val; if (mGame2048Listener! = Null) {mGame2048Listener. onScoreChange (mScore);} // move forward for (int k = j + 1; k <row. size ()-1; k ++) {row. get (k ). setNumber (row. get (k + 1 ). getNumber ();} row. get (row. size ()-1 ). setNumber (0); return ;}}}

It is also relatively simple. You can find the same number in a loop and find the combined number, plus points;

For extra points, we set a callback to callback the score:

If (mGame2048Listener! = Null)
{
MGame2048Listener. onScoreChange (mScore );
}

Finally, let's look at the code for generating numbers:

/*** Generate a number */public void generateNum () {if (checkOver () {Log. e ("TAG", "game over"); if (mGame2048Listener! = Null) {mGame2048Listener. onGameOver () ;}return ;}if (! IsFull () {if (isMoveHappen | isMergeHappen) {Random random = new Random (); int next = random. nextInt (16); Game2048Item item = mGame2048Items [next]; while (item. getNumber ()! = 0) {next = random. nextInt (16); item = mGame2048Items [next];} item. setNumber (Math. random ()> 0.75? 4: 2); isMergeHappen = isMoveHappen = false ;}}}

First, determine whether it is over. If it is over, it is still called back to let the players know that it is over;

Then, determine whether the panel is a blank grid. If not, do you need to generate a new number? If yes, a new 2 or 4 is randomly generated;

So how can we determine whether it is over?

First, there must be no space, and then there will be no identical numbers in the four directions to end:

/*** Check that all current locations have numbers, and adjacent ones do not have the same number ** @ return */private boolean checkOver () {// check whether all locations have numbers if (! IsFull () {return false ;}for (int I = 0; I <mColumn; I ++) {for (int j = 0; j <mColumn; j ++) {int index = I * mColumn + j; // The current ItemGame2048Item item = mGame2048Items [index]; // if (index + 1) % mColumn on the right! = 0) {Log. e ("TAG", "RIGHT"); // ItemGame2048Item itemRight = mGame2048Items [index + 1]; if (item. getNumber () = itemRight. getNumber () return false;} // if (index + mColumn) <mColumn * mColumn) {Log. e ("TAG", "DOWN"); Game2048Item itemBottom = mGame2048Items [index + mColumn]; if (item. getNumber () = itemBottom. getNumber () return false;} // left if (index % mColumn! = 0) {Log. e ("TAG", "LEFT"); Game2048Item itemLeft = mGame2048Items [index-1]; if (itemLeft. getNumber () = item. getNumber () return false;} // The above if (index + 1> mColumn) {Log. e ("TAG", "UP"); Game2048Item itemTop = mGame2048Items [index-mColumn]; if (item. getNumber () = itemTop. getNumber () return false ;}} return true ;}

/*** Whether to fill the number *** @ return */private boolean isFull () {// check whether there is a number for (int I = 0; I <mGame2048Items. length; I ++) {if (mGame2048Items [I]. getNumber () = 0) {return false;} return true ;}

At this point, our code has been introduced ~~~ After completing our Game2048Layout, how can we use it?

The process of writing a game is very difficult, but you can use it to see what is so easy. Just use it as a normal View:

4. Practice 1. layout file:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="fill_parent"    android:layout_height="fill_parent" >    <com.zhy.game2048.view.Game2048Layout        android:id="@+id/id_game2048"        android:layout_width="fill_parent"        android:layout_height="fill_parent"        android:layout_centerInParent="true"        android:background="#ffffff"        android:padding="10dp" >    </com.zhy.game2048.view.Game2048Layout>    <LinearLayout        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_above="@id/id_game2048"        android:layout_centerHorizontal="true"        android:layout_marginBottom="20dp"        android:background="#EEE4DA"        android:orientation="horizontal" >        <TextView            android:id="@+id/id_score"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:padding="4dp"            android:text="Score: 0"            android:textColor="#EA7821"            android:textSize="30sp"            android:textStyle="bold" />    </LinearLayout></RelativeLayout>

2. MainActivity

package com.zhy.game2048;import android.app.Activity;import android.app.AlertDialog;import android.content.DialogInterface;import android.content.DialogInterface.OnClickListener;import android.os.Bundle;import android.widget.TextView;import com.zhy.game2048.view.Game2048Layout;import com.zhy.game2048.view.Game2048Layout.OnGame2048Listener;public class MainActivity extends Activity implements OnGame2048Listener{private Game2048Layout mGame2048Layout;private TextView mScore;@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mScore = (TextView) findViewById(R.id.id_score);mGame2048Layout = (Game2048Layout) findViewById(R.id.id_game2048);mGame2048Layout.setOnGame2048Listener(this);}@Overridepublic void onScoreChange(int score){mScore.setText("SCORE: " + score);}@Overridepublic void onGameOver(){new AlertDialog.Builder(this).setTitle("GAME OVER").setMessage("YOU HAVE GOT " + mScore.getText()).setPositiveButton("RESTART", new OnClickListener(){@Overridepublic void onClick(DialogInterface dialog, int which){mGame2048Layout.restart();}}).setNegativeButton("EXIT", new OnClickListener(){@Overridepublic void onClick(DialogInterface dialog, int which){finish();}}).show();}}

The code is mainly used to set up an interface. When the bonus points have been added to the game, the code will be handed over to the Activity for processing ~~~ If you like it, you can play 4 games in one interface ~~~

Of course, the number of game items can also be dynamically set ~~~ End with a 5*5 game ~~



Well, 2048 is over now. Start designing with just a pen and write it based on the custom View experience. I believe you can learn a lot ~~~


In addition, our View is extracted. In fact, it is very easy to replace it with an image ~~

Today, I saw the top 10 classic battles of war3 and presented the war3 version. The Code will not be pasted, and several lines of code will be changed. I will post it to commemorate our previous war3 ~~~ :



Well, it's actually 5*5 ~ You can change mColumn to 4 ~~~



2048 download source code


War3 version 2048 click to download





Bytes ---------------------------------------------------------------------------------------------------------

I have created a QQ Group for your convenience. Group Number:55032675



Bytes ----------------------------------------------------------------------------------------------------------

Some of the bloggers have already been online. If you do not like boring text, stamp it (for initial record, we look forward to your support ):

1. High imitation 5.2.1 main interface and message reminder

2. High imitation QQ5.0 slide



















2048 game URL

Instructor elecirulli: github: io/2048/

VB does not use any controls to make a 2048 game. If the game gives priority to Keyboard Events, how can I add a beautiful background image to the game?

Z-order of picturebox

Related Article

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.