Imitation of Russian design to make greedy snake
-
Basic design ideas
Draw a grid, make a map,
Four buttons to control the direction, up, down, left and right,
A snake is composed of a snake body and a snake head - all of which are squares of the same size
Box attribute - x-y color - can be added when drawing
The change in the movement xy of the snake up, down, left and right,
The production of food, snakes eat food;
-
Problems to be solved
-
Boundary treatment
The snake's head touches the border and dies or passes through the wall,
-
Meet yourself
A snake's head touches its body
-
Random production of food
-
The length of food itself increases
-
Up, down, left and right movement processing
-
-
Creation of block entity class
Write the basic attributes and methods of the box. At present, only the basic attributes are written, and the method will be written later.
public class BlockUnit { public static final int UNIT_SIZE=50; public static final int BEGIN=10; // public int color; public int x,y; public BlockUnit() { } public BlockUnit( int x, int y) { this.x = x; this.y = y; // this.color = color; }
-
Creation of snakes
Set the basic properties and color of the snake, and the method to obtain the snake
public class SnakeBlock { private static final int SnakeSize=7; //private int HandColor,SnakeColo ,x,y; private int x,y; public SnakeBlock() { } public SnakeBlock(int x, int y) { this.x = x; this.y = y; } public List<BlockUnit> getUnit(int x,int y){ this.x=x; this.y=y; return returnSnake(); } private List<BlockUnit> returnSnake() { List<BlockUnit> units=new ArrayList<BlockUnit>(); units.clear(); units.add(new BlockUnit(x-0*BlockUnit.UNIT_SIZE,y+0*BlockUnit.UNIT_SIZE)); units.add(new BlockUnit(x-1*BlockUnit.UNIT_SIZE,y+0*BlockUnit.UNIT_SIZE)); units.add(new BlockUnit(x-2*BlockUnit.UNIT_SIZE,y+0*BlockUnit.UNIT_SIZE)); units.add(new BlockUnit(x-3*BlockUnit.UNIT_SIZE,y+0*BlockUnit.UNIT_SIZE)); return units; } }
-
The design of the main interface of the game is very similar to the Tetris game interface designed before. First, create a brush object and initialize it. Initialize the brush in the construction method,
First initialize the game map, that is, the background brush, and set the brush color, mode or type. When the brush style is stroke, set the thickness of the brush
Initialize the snake brush and instantiate the snake class
public SnakeView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); if (paintWall==null){ //Background brush initialization paintWall=new Paint(); paintWall.setColor(Color.LTGRAY); paintWall.setStyle(Paint.Style.STROKE); //hollow paintWall.setStrokeWidth(BOUND_WIDTH_OF_WALL+1);} if (paintBlock == null) {// Initialize background wall brush paintBlock = new Paint(); paintBlock.setColor(Color.parseColor("#FF6600")); } snakeBlock= new SnakeBlock();}
-
Draw game map in onDraw
@SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); max_x=getWidth(); //Get the maximum value of the game interface max_y=getHeight(); RectF rel; //Rectangular class num_y=0; //Draw game grid for (int y=begin;y<max_y-BlockUnit.UNIT_SIZE;y+=BlockUnit.UNIT_SIZE){ num_x=0; for (int x=begin;x<max_x-BlockUnit.UNIT_SIZE;x+=BlockUnit.UNIT_SIZE){ rel=new RectF(x,y,x+BlockUnit.UNIT_SIZE,y+BlockUnit.UNIT_SIZE); //Four coordinates of the rectangle canvas.drawRoundRect(rel,8,8,paintWall);//Draw a rounded rectangle with an arc of 8 num_x++;//Record the number of rows and columns of the map } num_y++;//Record the number of rows and columns of the map }
-
Draw the snake's body and set the color of the snake's head to be different from that of the snake's body, which is convenient to distinguish.
//Draw the body of a snake int len = blockUnits.size(); // Draw square for (int i = 1; i < len; i++) { int x = blockUnits.get(i).x; int y = blockUnits.get(i).y; // int c=blockUnits.get(i).color; // Sets the color of the current square paintBlock.setColor(Color.BLUE); rel = new RectF(x + BOUND_WIDTH_OF_WALL, y + BOUND_WIDTH_OF_WALL, x + BlockUnit.UNIT_SIZE - BOUND_WIDTH_OF_WALL, y + BlockUnit.UNIT_SIZE - BOUND_WIDTH_OF_WALL); canvas.drawRoundRect(rel, 8, 8, paintBlock); paintBlock.setColor(Color.RED); rel = new RectF(blockUnits.get(0).x + BOUND_WIDTH_OF_WALL, blockUnits.get(0).y + BOUND_WIDTH_OF_WALL, blockUnits.get(0).x + BlockUnit.UNIT_SIZE - BOUND_WIDTH_OF_WALL, blockUnits.get(0).y + BlockUnit.UNIT_SIZE - BOUND_WIDTH_OF_WALL); canvas.drawRoundRect(rel, 8, 8, paintBlock); } }
-
Main page layout - the game custom view and a button can be moved by clicking the button snake first, and the default is to move to the right
<com.example.mysnakegame.SnakeView android:id="@+id/sv" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginRight="120dp" android:layout_marginBottom="200dp"/> <Button android:id="@+id/gameStart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:text="start" />
In mainactivity Binding controls and event listening in Java
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button gameStart; private SnakeView snakeView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView();} private void initView() { gameStart=(Button) findViewById(R.id.gameStart); snakeView=(SnakeView) findViewById(R.id.sv); snakeView.setFather(this); gameStart.setOnClickListener(this);} @Override public void onClick(View view) { switch (view.getId()){ case R.id.gameStart: snakeView.gameStart(); break;}}}
The event calls the gameStart() method of the custom control. The gameStart() method is set similar to that in the Russian demo to start the thread and enter the loop to make the snake move
public void gameStart() { GameStart=true; Running=true; if (mainThread==null || !mainThread.isAlive()){ getSnake(); //Produce a snake mainThread=new Thread(new MainThread()); //Create thread object mainThread.start(); //Start thread // invalidate(); } }
According to the online query, most snakes are divided into snake head, snake body, snake tail, and each part of the snake body is the inflection point. This is a little cumbersome. I set it to let the snake head move, and the remaining blocks inherit the coordinate value of the previous block to form the purpose of moving
Write the right shift method in the block basic class,
public static boolean toRight(List<BlockUnit> blockUnits) { for (int i = blockUnits.size() - 1; i > 0; i--) { blockUnits.get(i).x = blockUnits.get(i - 1).x; blockUnits.get(i).y = blockUnits.get(i - 1).y; } blockUnits.get(0).x += BlockUnit.UNIT_SIZE; return true; }
Call the shift right method in the thread and constantly redraw to make the snake move
public class MainThread implements Runnable{ @Override public void run() { while(GameStart){ while(Running){ BlockUnit.toRight(blockUnits); father.runOnUiThread(new Runnable() { @Override public void run() { invalidate();}}); try { Thread.sleep(300); }catch (InterruptedException e){ e.printStackTrace();}} } }}
-
The snake moves up, down, left and right in a certain way, which is similar to the right shift above. Three new buttons are created in the layout, which are up, down and left, respectively binding events for them,
case R.id.toDown: snakeView.toDown(); break; case R.id.toUp: snakeView.toUp(); break; case R.id.toLeft: snakeView.toLeft(); break; case R.id.toRight: snakeView.toRight(); break;
-
Move method in custom view
public void toDown(){ mDirection = DOWN;} public void toUp(){ mDirection = UP;} public void toLeft(){ mDirection = LEFT;} public void toRight(){ mDirection = RIGHT;}
Loop in thread
switch (mDirection) { case LEFT: BlockUnit.toLeft(blockUnits); break; case RIGHT: BlockUnit.toRight(blockUnits); break; case DOWN: BlockUnit.toDown(blockUnits); break; case UP: BlockUnit.toUp(blockUnits); break; }
Basic methods in block class
public static boolean toLeft(List<BlockUnit> blockUnits) { for (int i = blockUnits.size() - 1; i > 0; i--) { blockUnits.get(i).x = blockUnits.get(i - 1).x; blockUnits.get(i).y = blockUnits.get(i - 1).y;} blockUnits.get(0).x -= BlockUnit.UNIT_SIZE; return true;} public static boolean toRight(List<BlockUnit> blockUnits) { for (int i = blockUnits.size() - 1; i > 0; i--) { blockUnits.get(i).x = blockUnits.get(i - 1).x; blockUnits.get(i).y = blockUnits.get(i - 1).y;} blockUnits.get(0).x += BlockUnit.UNIT_SIZE; return true;} public static boolean toUp(List<BlockUnit> blockUnits) { for (int i = blockUnits.size() - 1; i > 0; i--) { blockUnits.get(i).x = blockUnits.get(i - 1).x; blockUnits.get(i).y = blockUnits.get(i - 1).y;} blockUnits.get(0).y -= BlockUnit.UNIT_SIZE; return true;} public static boolean toDown(List<BlockUnit> blockUnits) { for (int i = blockUnits.size() - 1; i > 0; i--) { blockUnits.get(i).x = blockUnits.get(i - 1).x; blockUnits.get(i).y = blockUnits.get(i - 1).y;} blockUnits.get(0).y += BlockUnit.UNIT_SIZE; return true;}
-
Limit the boundary, set the judgment statement to touch the boundary, and the game ends
Write a judgment method in the basic class of the box. In the loop, judge before moving, return true to execute the move statement, and return false to call the end method to end the game.
public static boolean canMove(List<BlockUnit> blockUnits ,int max_x,int max_y ) { int x=blockUnits.get(0).x; int y=blockUnits.get(0).y; if (x+BlockUnit.UNIT_SIZE>=max_x ||x-BlockUnit.UNIT_SIZE<=0 || y+BlockUnit.UNIT_SIZE>=max_y || y-BlockUnit.UNIT_SIZE<=0){ return false; } for (int i=1;i<blockUnits.size();i++){ if (x==blockUnits.get(i).x&&y==blockUnits.get(i).y){ return false; } } return true; }
-
For the modification of small defects, the direction of snake head will be modified by the button before the game starts. It is set that the button will not work when the game does not start
Add judgment before the statement of changing the direction of the snake head. If the game is not started and running, the direction of the snake head cannot be changed.
-
Food production, food is randomly generated on the map
Create food classes, create methods to get food,
public class Food { private int begin=10; private int x,y; public Food() {} public Food(int x, int y) { this.x = x; this.y = y;} public List<BlockUnit> getFood(List<BlockUnit> blockUnits,int num_x,int num_y){ List<BlockUnit> food=new ArrayList<BlockUnit>(); int x=(int)(Math.random()*num_x)*BlockUnit.UNIT_SIZE+begin; int y=(int)(Math.random()*num_y)*BlockUnit.UNIT_SIZE+begin; Log.i("food.x",String.valueOf(x)); Log.i("food.y",String.valueOf(y)); food.clear(); food.add(new BlockUnit(x,y)); return food;}}
Call in the game start method, automatically create a food at the beginning of the game.
-
After creating food, you need to judge whether the food produced does not coincide with the snake;
Add judgment to the method of obtaining food. If it coincides, call the method again until it does not coincide
for (int i=0; i<blockUnits.size();i++){ if (x==blockUnits.get(i).x&&y==blockUnits.get(i).y) getFood(blockUnits,num_x,num_y); }
-
Snake eating food event
Judgment: when the snake head coincides with the food, it means that the snake has eaten the food. The creation of the judgment method requires the snake head data and food data, so two parameters are required temporarily, and they can be added later if necessary.
public static boolean EatFood(List<BlockUnit> blockUnits,List<BlockUnit> foodUnit) { int bx=blockUnits.get(0).x; int by=blockUnits.get(0).y; int fx=foodUnit.get(0).x; int fy=foodUnit.get(0).y; if (bx==fx &&by==fy){ return true; } return false; }
After the snake eats the food, the original food disappears, obtains a new food, and the snake's body needs to be increased by one.
How to add snakes:
public List<BlockUnit> addUnit(List<BlockUnit> blockUnits){ int x=blockUnits.get(blockUnits.size()-1).x; int y=blockUnits.get(blockUnits.size()-1).y; blockUnits.add(new BlockUnit(x,y)); return blockUnits; }
Then it is realized in the thread. When eating food, the food disappears and a new food is created. The length of the snake's body is added by one. Here we add the tail.
if (BlockUnit.EatFood(blockUnits,foodUnits)){ Log.i("eat","Eat food"); foodUnits.clear(); father.runOnUiThread(new Runnable() { @Override public void run() { getFood(); blockUnits=snakeBlock.addUnit(blockUnits); invalidate(); } }); }
-
Pop up processing at the end of the game
Judge whether to continue the game by pop-up after the game
father.runOnUiThread(new Runnable() { @Override public void run() { gamePaush(); // Log.i("1234","1111"); new AlertDialog.Builder(father).setIcon(R.drawable.snake_icon_128) .setTitle("You're dead!! game over") .setMessage("Do you want to start over??") .setNegativeButton("cancel", new DialogInterface.OnClickListener(){ @Override public void onClick(DialogInterface dialogInterface, int i){ gameOver();} }) .setPositiveButton("determine", new DialogInterface.OnClickListener(){ @Override public void onClick(DialogInterface dialogInterface, int i){ gameStart();} }).create().show();}});
There is an error in the operation. Click OK to confirm that the game does not restart, but the pop-up window appears again. After searching, it is found that the snake list has not been cleared, so there is an error.
In addition, there are still problems after clearing the snake list, and there is no restart. Emptying the food list and running again still failed. It is speculated that it is a thread problem, which will be solved later
-
End of the game, pause, continue button implementation
-
game over
Add a button to the layout. No code will be posted here
Control binding and event binding,
For the writing of the game end method, first stop the game, release the thread and refresh the page
public void gameOver(){ // Stop the game and release the main thread of the game Running= false; GameStart = false; mainThread.interrupt(); blockUnits.clear(); // bottomBlockUnits.clear(); // score = 0; invalidate();}
-
Game pause
When the game is suspended, set the running flag bit to false and a window pops up. Click Cancel to end the game. Click OK to call the continue method and set the running flag bit to true
public void gamePaush(){ Running=false; new AlertDialog.Builder(father) .setIcon(R.drawable.snake_icon_128) .setTitle("Game pause!!!!") .setMessage("The game is suspended. Do you want to continue") .setNegativeButton("cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { gameOver();}}) .setPositiveButton("determine", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { gameContinue();} }).create().show();}
-
-
Score records, eating a food record is very important
And update the game home page to increase the score after judging that the food is eaten
if (BlockUnit.EatFood(blockUnits,foodUnits)){ score+=10; Log.i("eat","Eat food"); foodUnits.clear(); father.runOnUiThread(new Runnable() { @Override public void run() { father.tvScore.setText(""+score); getFood(); blockUnits=snakeBlock.addUnit(blockUnits); invalidate();}});}
Clear the score after the game decision
-
For the storage and reading of the highest score, use the lightweight storage class SharePreferences provided by Android to save data in XML format. First, create the auxiliary class SharedHelper of SharedPreferences to define the saving and reading methods
//Define a method to save data public void save(String key, String value) { SharedPreferences sp = mContext.getSharedPreferences("score", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putString(key,value); editor.commit(); } //Define a method to read SP files public String read(String key ) { Map<String, String> data = new HashMap<String, String>(); SharedPreferences sp = mContext.getSharedPreferences("score", Context.MODE_PRIVATE); return sp.getString(key,""); }
-
In mainactivity The onStart method is created in Java, and the highest score is read at the beginning of the Activity.
@Override protected void onStart() { super.onStart(); sh=new SharedHelper(MainActivity.this); String max_score = sh.read("max_score"); MaxScore.setText(max_score); try { mScore = Integer.parseInt(max_score); } catch (NumberFormatException e) { e.printStackTrace(); } }
-
At the end of the game, the score obtained by the game will be compared and replaced with the highest score
Log.i("mScore", String.valueOf(father.mScore)); if (father.mScore<score){ father.setMAX_score(score); Toast.makeText(father,"The highest score has been replaced",Toast.LENGTH_LONG).show(); }
-
For level adjustment, the level of each game will be set to zero. When the snake's body is longer and longer, the level will increase. The increase rule is to update when the score reaches a multiple of 100
father.runOnUiThread(new Runnable() { @Override public void run() { int dengji=score/100; father.Dengji.setText(" "+dengji); } });
-
Speed update
Set the speed gear, change the sleep time of the thread, and change the moving speed of the snake,
speed=score/50; father.Speed.setText(""+speed);
switch (speed){ case 0: Thread.sleep(300); break; case 1: Thread.sleep(290); break; case 2: Thread.sleep(270); break; case 3: Thread.sleep(240); break; case 4: Thread.sleep(210); break; case 5: Thread.sleep(180); break; default: Thread.sleep(120); break;}
-
So far, the greedy snake design is over. Although some small bug s have not been solved, the basic functions and game logic have been completed.