Android Development's fourth bullet: neural CAT (simple Demo)
Preface
As shown in, this simple example is to be completed in this article. The animation and sound will be added later. It mainly contains some simple elements and logic of the game.
After many attempts, I found it difficult to win ...... After all, it is not a simple Random.
The Code has been uploaded to Github. It is recommended that you directly Fork instead of Download. After all, the significance of open source is to share the Code with each other. This is too simple and will be updated in the future. So ......
Definition of game background elements
As the code will continue to be updated, the blog will only give a brief introduction and I have written comments.
As you can see, there are many points in the background. Here we can define a Dot class.
For the Dot class, it should obviously have coordinates X, Y, and a State. What are the specific states?
1. Click it to open the roadblock. Here it is set to Red 2. When you do not click it, the roadblock is closed. Here it is set to gray 3. The location of the neurocat should naturally be a status, after all, players in this position cannot Click again.
Then set the corresponding constructor.
Package myapplication.nomasp.com. catchcrazycat;/*** Created by nomasp on 2015/09/25. */public class Dot {int x, y; int status; // Gray: the cat can go, red: And is set as a roadblock, orange: location of the cat public static final int STATUS_ON = 1; // enable the roadblock (red) public static final int STATUS_OFF = 0; // close the roadblock (Gray) public static final int STATUS_IN = 9; // The Position of the neurocat public Dot (int x, int y) {super (); this. x = x; this. y = y; status = STATUS_OFF;} public void setXY (int x, int y) {this. x = x; this. y = y;} public void setX (int x) {this. x = x;} public int getX () {return x;} public int getY () {return y;} public void setY (int y) {this. y = y;} public int getStatus () {return status;} public void setStatus (int status) {this. status = status ;}}
Game Initialization
The background of the game is all in the Playground class.
Before initializing the game, we need to define a Dot-type two-dimensional array to save the origin of these games. Here I will call it a game element.
Then we need to instantiate the neural cat, which is also of the Dot type in essence.
Private Dot matrix [] []; // declare the array matrix to save the game element private Dot cat; // cat
Of course, rows, columns, and roadblocks can all be set to static and final. If you want to set different levels of difficulty in the game, you can modify them later.
Private static final int COL = 10; // each column has 10 elements: private static final int ROW = 10; // each ROW has 10 elements: private static final int BLOCKS = 15; // default number of roadblocks added
The following describes how to initialize the game. The final output is used for testing. For convenience, I have not deleted the learning of this beginner.
Private void initGame () {for (int I = 0; I <ROW; I ++) {for (int j = 0; j <COL; j ++) {matrix [I] [j]. setStatus (Dot. STATUS_OFF) ;}} cat = new Dot (ROW/2, COL/2); // initialize the cat location getDot (ROW/2, COL/2 ). setStatus (Dot. STATUS_IN); // initialize the status of the cat's location for (int I = 0; I <BLOCKS;) {int x = (int) (Math. random () * 1000) % COL); // x is the abscissa, And the int y = (int) (Math. random () * 1000) % ROW); // y is the ordinate, And the ROW in the array is if (getDot (x, y ). getStatus () = Dot. STATUS_OFF) {// if the current Dot status is OFF getDot (x, y ). setStatus (Dot. STATUS_ON); // open it and let I auto-increment I ++; // System. out. println (BLOCKS ++ I );}}}
The width of the element can also be declared here:
private static int WIDTH;
There are two ways to set the width:
Method 1: Obtain the screen resolution in the MainActivity class.
Public static int screenWidth = 0; // use DisplayMetrics metrics = new DisplayMetrics () as mentioned in the Playground class (). getdefadisplay display (). getMetrics (metrics); screenWidth = metrics. widthPixels;
Then, set it in the Playground class.
private static final int WIDTH = (int)(MainActivity.screenWidth*0.8)/10;
Method 2: continue with the introduction below.
Redraw, callback, and touch
First, expand SurfaceView for the Playground class and implement OnTouchListener.
The following describes how to redraw the background. Remember to set anti-aliasing for the paint, and set different colors based on the Dot in different States. Finally, instantiate a RectF in the drawOval method class.
Private void redraw () {Canvas c = getHolder (). lockCanvas (); // lock c. drawColor (Color. LTGRAY); // set canvas to cyan Paint = new paint (); // start painting to the screen. setFlags (Paint. ANTI_ALIAS_FLAG); // anti-sawtooth for (int I = 0; I <ROW; I ++) {int offset = 0; // set the offset if (I % 2! = 0) {// I is an odd number, indicating 2, 4, 6 ...... Rows (the index is 1, 3, 5 ......) Offset = WIDTH/2; // The offset is half the WIDTH of the element} for (int j = 0; j <COL; j ++) {Dot one = getDot (j, i); switch (one. getStatus () {case Dot. STATUS_OFF: paint. setColor (0 xFFEEEEEE); break; case Dot. STATUS_IN: paint. setColor (0xFFFF0000); break; case Dot. STATUS_ON: paint. setColor (0xFFFFAA00); break; default: break;} c. drawOval (new RectF (one. getX () * WIDTH + offset, one. getY () * WIDTH, (one. getX () + 1) * WIDTH + offset, (one. getY () + 1) * WIDTH), paint); // The size is determined by the screen WIDTH} getHolder (). unlockCanvasAndPost (c); // unlock}
Then you can call back it. Here is the second method used to set the WIDTH mentioned previously. it cleverly utilizes the width parameter passed in surfaceChanged.
Callback callback = new Callback () {// Callback method @ Override public void surfaceCreated (SurfaceHolder holder) {redraw (); // call redraw for repainting} @ Override public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) {WIDTH = width/(COL + 1 ); redraw (); // re-paint after modifying the width} @ Override public void surfaceDestroyed (SurfaceHolder holder ){}};
The following describes how to write touch functions. Simply reload the onTouch method.
The annotations have been written in great detail. Like above, the test code is retained for learning.
@ Override public boolean onTouch (View v, MotionEvent event) {if (event. getAction () = MotionEvent. ACTION_UP) {// Toast. makeText (getContext (), event. getX () +: + event. getY (), // Toast. LENGTH_SHORT ). show (); int x, y; // the X and Y coordinates of the touch y = (int) (event. getY ()/WIDTH); if (y % 2 = 0) {// indicates 1st, 3, 5 ...... Row Element (index 0, 2, 4 ......) X = (int) (event. getX ()/WIDTH);} else {x = (int) (event. getX ()-WIDTH/2)/WIDTH); // The X coordinate obtained by the event minus the remaining WIDTH/2 on the left, divide it by WIDTH} // The following Code protects the coordinates, if you do not have the code, when the click position is out of the coordinate range, it is beyond the array index if (x + 1> COL | y + 1> ROW) {// exceeds the boundary, the current click is invalid // The following is the test code // System. out. println (------------------------); // getNrighbour (cat, k ). setStatus (Dot. STATUS_IN); // k ++; // redraw (); initGame (); // The following is the test code // System. out. println (------------------------); // for (int I = 1; I <7; I ++) {// System. out. println (I ++ getDistance (cat, I);} else if (getDot (x, y ). getStatus () = Dot. STATUS_OFF) {// getDot (x, y) when this point is available ). setStatus (Dot. STATUS_ON); // click valid to enable move ();} redraw (); // redraw} return true ;}
By the way, the getDot method used previously has not been mentioned. This is because X and Y above are the X and Y coordinates of the touch event, but corresponding to X in the array is the column, Y is the row, while the two-dimensional array is the row before and column after, so we set a method for convenience.
Private Dot getDot (int x, int y) {// input x, y returns matrix [y] [x] return matrix [y] [x];}
Of course, you can also directly write matrix [y] [x].
The following is actually written at the beginning, but some of the things used here are supplemented one by one, so they are placed at the end.
Public Playground (Context context) {super (context); getHolder (). addCallback (callback); // set the callback matrix = new Dot [ROW] [COL] for getHolder; // initialize for (int I = 0; I <ROW; I ++) {for (int j = 0; j <COL; j ++) {matrix [I] [j] = new Dot (j, I); // x indicates the row, y indicates the column, so the COL/ROW of the array is staggered} setOnTouchListener (this); // this class has implemented the OnTouchListener interface, so you only need to pass in this to initGame ();}
Obtain distance and other functional methods
I
First, it is self-evident that the game needs to determine whether a neurocat is on the edge. It naturally requires a Dot parameter.
Private boolean isAtEdge (Dot d) {if (d. getX () * d. getY () = 0 | d. getX () + 1 = COL | d. getY () + 1 = ROW) {// return true at the game boundary;} return false ;}
II
Next, we need to take a good look at the relationship. With the following image example, we can understand it very well and calculate it clockwise.
Private Dot getNeighbor (Dot d, int dir) {// start from the left point of the horizontal link and count switch (dir) {case 1: return getDot (d. getX ()-1, d. getY (); case 2: if (d. getY () % 2 = 0) {return getDot (d. getX ()-1, d. getY ()-1);} else {return getDot (d. getX (), d. getY ()-1);} case 3: if (d. getY () % 2 = 0) {return getDot (d. getX (), d. getY ()-1);} else {return getDot (d. getX () + 1, d. getY ()-1);} case 4: return getDot (d. getX () + 1, d. getY (); case 5: if (d. getY () % 2 = 0) {return getDot (d. getX (), d. getY () + 1);} else {return getDot (d. getX () + 1, d. getY () + 1);} case 6: if (d. getY () % 2 = 0) {return getDot (d. getX ()-1, d. getY () + 1);} else {return getDot (d. getX (), d. getY () + 1);} default: return null ;}}
3.
The following method is used to calculate the distance:
If there are two gray (OFF) blocks between a certain direction,-2 is returned; if there is a block next to this direction, 0 is returned; if there is no block in this direction, returns the number of gray elements in the middle of the boundary, positive.
Private int getDistance (Dot d, int dir) {int distance = 0; if (isAtEdge (d) {// if the point is already at the edge of the screen, then you do not need to continue to judge whether to directly return distance to return 1;} Dot ori = d, next; while (true) {next = getNrighbour (ori, dir ); // assign the neighbor value in a certain direction of the current vertex d to next if (next. getStatus () = Dot. STATUS_ON) {// returns 0 or negative return distance *-1;} if (isAtEdge (next) {// reaches the scene edge, returns the positive number distance ++; // It indicates that the next vertex is also available return distance;} distance ++; // distance Auto-incrementing ori = next; // set next as the current reference point }}
Winning or losing
As this is just a simple Demo, you can use Toast to notify you about the outcome. We will continue to improve it in the future. You are also welcome to contribute your code.
Private void lose () {// game failure Toast. makeText (getContext (), Lose, Toast. LENGTH_SHORT ). show ();} private void win () {// Toast successful game. makeText (getContext (), Win, Toast. LENGTH_SHORT ). show ();}
Measure the test taker's knowledge about the neural cat movement method.
Okay, I admit that the order of the mobile part of the code being written is not at the end. This is the focus here and I will focus on the final introduction.
Moving and moving are divided into two steps by function. Of course, moving is definitely to call MoveTo, but the latter focuses on the movements of neurocats. The former focuses on the movements of neurocats.
Private void MoveTo (Dot d) {// move the neural cat to a certain vertex d. setStatus (Dot. STATUS_IN); getDot (cat. getX (), cat. getY ()). setStatus (Dot. STATUS_OFF); cat. setXY (d. getX (), d. getY ());}
Next, we will write the move method. Here, we will perform the steps for the sake of learning.
1. Determine whether a neurocat is at the boundary. If yes, the game fails. If no move is performed, return directly.
If (isAtEdge (cat) {// determines whether the cat is at the scene boundary lose (); // return for game failure ;}
Then, you can determine whether the six elements are available ).
For (int I = 1; I <7; I ++) {Dot n = getNeighbor (cat, I); if (n. getStatus () = Dot. STATUS_OFF) {// if this neighbor (point) can be avaliable. add (n); // add it to avaliable length. put (n, I); // I indicates that the boundary positive can be reached if (getDistance (n, I)> 0) {// positive number. add (n );}}}
N is instantiated above, and you can also directly write it:
if(getNeighbor(cat,i).getStatus() == Dot.STATUS_OFF)
If the status of its neighbor is disabled, it means that this is available and is added to avaliable. Before the for loop, define them:
Vector
Avaliable = new Vector <> (); // available point Vector in the six points around the current point
Positive = new Vector <> (); // HashMap of the distance from the current point to the Boundary
Length = new HashMap
(); // With positive, which is used to record the direction
If there is no available point, it means that it has successfully surrounded the nervous cat; if there is only one available point, just move it directly.
If (avaliable. size () = 0) {// no available point win (); // The nerve cat is surrounded successfully} else if (avaliable. size () = 1) {// only one available point is MoveTo (avaliable. get (0 ));}
So what should we do if there are multiple routes?
Else {// This is neither the first time you click, and there are multiple routes to go. // based on the distance to the edge (including the calculation of roadblocks) to determine the direction of the Dot best = null; // finally decide the element (point) to be moved if (positive. size ()! = 0) {// contains the System that can reach the boundary. out. println (forward); int min = 999; // used to record the minimum value of reaching the boundary. The initial value is larger for (int I = 0; I <positive. size (); I ++) {int tempDis = getDistance (positive. get (I), length. get (positive. get (I); if (tempDis <min) {min = tempDis; best = positive. get (I) ;}} else {// all directions have a roadblock System. out. println (Dodge block); int max = 0; // when there is a block in all directions, the distance is either negative or 0 for (int I = 0; I <avaliable. size (); I ++) {int tempDis = getDistance (avaliable. get (I), length. get (avaliable. get (I); if (tempDis <max) {// because tempDis is a negative number, use max = tempDis; best = avaliable. get (I );}}}
If no obstacle exists in all directions, select the shortest path. If yes, you need to avoid these obstacles. Here, we use min and max to record the optimal path, and assign it to best:
best = avaliable.get(i);
Set a random path for best if it is null.
If (best = null) {// when best is null, a random path int s = (int) (Math. random () * 1000) % avaliable. size (); MoveTo (avaliable. get (s);} else {MoveTo (best); // move to the most appropriate point}
Welcome to pull request
I will upload them to Github, and there will be comments. To help more people, we welcome Fork and Star. We also welcome pull request at: Portal.