preface
Snake is a very classic little game, I 00, the first play is in childhood with nokia mobile phone for play. This is a copy of the classic HHH, Snake is a very simple small game, this article uses Kotlin language development, with Java partners can compare their own code transformation, but 2021 writing Android is still necessary to learn Kotlin. I will also post some Kotlin articles later.
Make ideas
The map
You can create a map using a two-dimensional array, or a one-dimensional array, in this case a two-dimensional array, and then the operation will be done by xy.
The head and the body and the food
Snake heads can be identified by a Point class, as can food. For the snake body, you can use a List to store points, and the snake head also needs to be stored in it.
Map element identification
The map uses a two-dimensional array, so the values in it have to have a certain meaning, so you can create two classes, one for setting the identity, one for declaring the identity, and of course you can write them directly together, so I’m going to separate out the two classes.
mobile
Move is up, down, left, right, you can move by sliding, you can move by button clicking, so I’m going to move by button clicking, slide moving and I’ll fill that in later. One rule of movement is that when the snake is moving in one direction, it cannot turn directly in the opposite direction, such as moving to the right, and cannot move directly to the left when changing direction of movement.
Game over
The end condition is very simple, is to eat their own body to end, some versions and meet the boundary of the end, my version is to meet the boundary does not end, but continue to move.
Results the preview
Started making
Create a logo
Type class
object Type {
const val GRID = 0
const val FOOD = 1
const val HEAD = 2
const val BODY = 3
}
Copy the code
It’s not enough to have an identifier. You need a class that sets the identifier and returns a different color depending on the identifier
class GameStyle(var type: Int) {
fun getColor(a) = when (type) {
Type.BODY -> Color.BLUE
Type.HEAD -> Color.RED
Type.GRID -> Color.GRAY
Type.FOOD -> Color.YELLOW
else -> Color.GRAY
}
}
Copy the code
The code is very simple, I won’t go into detail
Directions also need to be identified
object Direction {
const val LEFT = 0
const val RIGHT = 1
const val UP = 2
const val DOWN = 3
}
Copy the code
Once you’ve created these three classes, you can start writing your game class
Create the GameView
class GameView : View.Runnable {
override fun onDraw(canvas: Canvas) {}
override fun run(a) {}
constructor(context: Context?) : this(context, null)
constructor(context: Context? , attrs: AttributeSet?) :this(context, attrs, 0)
constructor(context: Context? , attrs: AttributeSet? , defStyleAttr:Int) :super(context,attrs,defStyleAttr)
}
Copy the code
Create the View and implement the Runnable interface, because snake needs to be constantly moving, we can open a thread and do it in a child thread.
Now I’m going to create some variables that I need later, and this is all very simple code and I’m going to comment it so you can understand it
Some variables
private val gameSize = 14 // Map length and width
private var screenHeight = 0 // The overall height of the screen
private var screenWidth = 0 // The overall width of the screen
private val map = arrayListOf<ArrayList<GameStyle>>() // The entire map element
private val snakeLocation = arrayListOf<Point>() // Snake position
private val snakeHead = Point(gameSize / 2, gameSize / 2) // Snake head position
private var foodLocation = Point() // Food location
private var moveSpeed = 4 // Move speed
private var snakeLength = 4 The length of the snake
private var snakeDirection = Direction.UP // Move direction
private var eatCount = 0 // The amount of food eaten
private val thread = Thread(this) // Game threads
private var gameStart = false // Whether the game starts
private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG) / / brush
/** * Inside onSizeChanged we can get the width and height set externally to the GameView, so here we assign the previously created variable */
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
screenHeight = height
screenWidth = width
}
Copy the code
Initialization function
You also need an initialization function before drawing, which is called in the third constructor
/** * initializes the function */
private fun init(a) {
// Map initialization
for (y in 0 until gameSize) {
val styleList = arrayListOf<GameStyle>()
for (x in 0 until gameSize) {
styleList.add(GameStyle(Type.GRID)) // All are grids by default
}
map.add(styleList)
}
// Random food location
randomCreateFood()
// Snake head position updated to snake
snakeLocation.add(Point(snakeHead.x, snakeHead.y))
gameStart = true
thread.start() // Start the game
}
// called in the third constructor
constructor(context: Context? , attrs: AttributeSet? , defStyleAttr:Int) : super(
context,
attrs,
defStyleAttr
) {
init()}Copy the code
You’ll notice that a new function is called, which is the random food location.
Random food location
The location of the food is supposed to be random, but it can’t be random on the snake, so it needs a loop, and if it is on the snake, it needs to be random again
/** * Randomly generates food */
private fun randomCreateFood(a) {
var food = Point(Random.nextInt(gameSize), Random.nextInt(gameSize))
var index = 0
while (index < snakeLocation.size - 1) {
if (food.x == snakeLocation[index].x && food.y == snakeLocation[index].y) {
food = Point(Random.nextInt(gameSize), Random.nextInt(gameSize))
index = 0
}
index++
}
foodLocation = food
refreshFood()
}
Copy the code
The code is simple enough to follow the description above
Food to refresh
It’s not enough to generate the food, but you need to refresh it into the map and draw it, so refreshFood() is called at the end, which makes the code simpler and should make sense at first
/** * Food updates to the map */
private fun refreshFood(a) {
map[foodLocation.y][foodLocation.x].type = Type.FOOD
}
Copy the code
Rewrite the ontouch (canvas, canvas)
Now we need to draw, draw we can use different markers to set whether the brush is hollow or solid, if the mesh is hollow, the food, the body and the head are solid, the color can also be obtained by the logo, draw the shape can draw the rectangle.
The complete onDraw(canvas: canvas) code is as follows:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val blockWidth = screenWidth / gameSize // The width of each grid
val blockHeight = screenHeight / gameSize // The height of each grid
// Draw map elements
for (y in 0 until gameSize) {
for (x in 0 until gameSize) {
// The range of each rectangle
val left = x * blockWidth.toFloat()
val right = (x + 1f) * blockWidth
val top = y * blockHeight.toFloat()
val bottom = (y + 1f) * blockHeight
// Set different brush styles for different logos
when (map[y][x].type) {
Type.GRID -> mPaint.style = Paint.Style.STROKE
Type.FOOD, Type.BODY -> mPaint.style = Paint.Style.FILL
}
// Set the brush color according to the logo
mPaint.color = map[y][x].getColor()
// Whether the current position is the head
if (x == snakeHead.x && y == snakeHead.y) {
mPaint.style = Paint.Style.FILL
mPaint.color = GameStyle(Type.HEAD).getColor()
}
// Draw a rectangle
canvas.drawRect(left, top, right, bottom, mPaint)
}
}
}
Copy the code
The movement of the snake
If the snake is moving up and the head is less than zero on one move, then we need the head to be at gamesize-1 at the next move, or at the current position -1. Finally we add the moved position to the snake position array, and the final code looks like this:
/**
* 移动
*/
private fun moveSnake(a) {
when (snakeDirection) {
Direction.LEFT -> {
if (snakeHead.x - 1 < 0) {
snakeHead.x = gameSize - 1
} else {
snakeHead.x = snakeHead.x - 1
}
snakeLocation.add(Point(snakeHead.x, snakeHead.y))
}
Direction.RIGHT -> {
if (snakeHead.x + 1 >= gameSize) {
snakeHead.x = 0
} else {
snakeHead.x = snakeHead.x + 1
}
snakeLocation.add(Point(snakeHead.x, snakeHead.y))
}
Direction.UP -> {
if (snakeHead.y - 1 < 0) {
snakeHead.y = gameSize - 1
} else {
snakeHead.y = snakeHead.y - 1
}
snakeLocation.add(Point(snakeHead.x, snakeHead.y))
}
Direction.DOWN -> {
if (snakeHead.y + 1 >= gameSize) {
snakeHead.y = 0
} else {
snakeHead.y = snakeHead.y + 1
}
snakeLocation.add(Point(snakeHead.x, snakeHead.y))
}
}
}
Copy the code
The when statement determines the direction of the move, the inner if determines whether it is at the edge, and then sets the new value of the move based on this condition, adding to the snake’s position array.
Draw a snake
Moving the snake is now the position of the snake. The snake moves forward, so the original position of the snake needs to be updated to the road surface. At the same time, because the update of the above move position adds a bit, it needs to delete a bit in the snake body
private fun drawSnakeBody(a) {
var length = snakeLength
for (i in snakeLocation.indices.reversed()) {
if (length > 0) {
length--
} else {
val body = snakeLocation[i]
map[body.y][body.x].type = Type.GRID
}
}
length = snakeLength
for (i in snakeLocation.indices.reversed()) {
if (length > 0) {
length--
} else {
snakeLocation.removeAt(i)
}
}
}
Copy the code
The refresh snake-body.
/** * Body updates to map */
private fun refreshBody(a) {
// Minus 1 because you don't need to include the snake head
for (i in 0 until snakeLocation.size - 1) {
map[snakeLocation[i].y][snakeLocation[i].x].type = Type.BODY
}
}
Copy the code
Judgment to eat
Now the last step, the way to determine the eating, if the eating is food, then the length +1, the food will refresh the position, if the eating is body game is over
/** * eat judgment */
private fun judgeEat(a) {
// If you eat yourself
val head = snakeLocation[snakeLocation.size - 1]
for (i in 0 until snakeLocation.size - 2) {
val body = snakeLocation[i]
if (body.x == head.x && body.y == head.y) {
gameStart = false // Eat until the body game is over}}// Eat food
if (head.x == foodLocation.x && head.y == foodLocation.y) {
snakeLength++ / / length + 1
randomCreateFood() // Refresh the food}}Copy the code
run()
The above moves and draws need to be performed in the thread, so they need to be written to the run method
override fun run(a) {
while (gameStart) {
moveSnake() / / move the snake
drawSnakeBody() // Draw the snake body
refreshBody() // Refresh the snake
judgeEat() / / to eat
postInvalidate() // Refresh the view
Thread.sleep(1000 / moveSpeed.toLong())
}
}
Copy the code
Externally set the move function
/** * set the movement direction */
fun setMove(direction: Int) {
when {
snakeDirection == Direction.LEFT && direction == Direction.RIGHT -> return
snakeDirection == Direction.RIGHT && direction == Direction.LEFT -> return
snakeDirection == Direction.UP && direction == Direction.DOWN -> return
snakeDirection == Direction.DOWN && direction == Direction.UP -> return
}
snakeDirection = direction
}
Copy the code
conclusion
Is now the game is finished, very simple, the snake is actually made up a very simple game, the code is also very few, so I don’t have too much to explain the meaning of each function as well as some of the details, but is explained in the form of comments, you can by making ideas with understanding, finally thank you for watching!