Welcome to pay attention to the public number: Ananzhuo to learn more knowledge

inspiration

I was inspired by fundroid’s article about Composing Tetris for Compose, and decided to bring Nokia’s Snake to Android as well

The interface and ideas for this article refer to the fundroid article

Teleport: fundroid tetris blog.csdn.net/vitaviva/ar…

The final result

Send a result in advance, let the reader have a psychological expectation, logic is really not complicated, be sure to finish reading patiently

Train of thought

Our interface is divided into two parts, the top half is the dynamic display area of the game, and the bottom half is the operation area

Display area

The display area can also be subdivided into two parts,

  • Border + black border
  • Screen dynamic display area

So we split the two parts into @composable BaseScreen and @Composable GameScreen

BaseScreen is used to draw borders and black lines, this part only needs to be drawn once, so we need to pull it out

GameScreen is used to draw dynamic areas, mainly the base light-colored brick matrix, and the snake dark brick, free snake dark brick

Operating area

The operation area also has two parts,

  • The start reset button and the Resume pause button are used to control the state of the game
  • Arrow button Used to control the direction of the snake head

The operation area group name is @composable OperateBody.

The action status button is implemented by wrapping two @Composable GameButtons around a Row

The d-pad button is implemented with a Box wrapped around the four @composable DirectionButtom

logic

How to communicate data

Use the stateflow. collectAsState method to get the Compose state and refresh the interface whenever the state changes

How to refresh the game state

An internal loop is defined using The LauncherEffect to maintain the clock, allowing us to update the game state at custom intervals.

State snake-body.

We use an array to maintain all the snake bricks, and then the logic of snake moving, snake eating free snake, snake hitting the wall

The snake mobile

If the snake simply moves, we can do this by creating a new element, the first element of the new array is the block under the snake to move to, then adding addAll to the old snake array, and finally removing the last snake in the new snake array. Example Refresh the StateFlow state

Snakes eat free snakes

If the next tile the snake moves to happens to be the free tile, we insert the free tile directly into the first element of the array and do not remove the last tile of the snake array

The snake hit the wall

The entire screen is rasterized as a Width_Matrix * Height_Matrix array. If the snake’s head is outside the raster array, the snake hits the wall and dies. Game over.

The code analysis

StateFlow declaration and use

Declare StateFlow, which does not need to introduce any dependencies

private val _flow = MutableStateFlow(SnakeState(action = Action.GameTick))
val stateFlow = _flow.asStateFlow()
Copy the code

Use StateFlow to get the state

 val model = viewModel<GameViewModel>()
 val state = model.stateFlow.collectAsState().value
Copy the code

Here SnakeState is the state entity we defined as follows:

data class SnakeState(
    var action: Action,
    val direction: Direction = Direction.Bottom,
    val snakeBody: MutableList<SnakePart> = mutableListOf(SnakePart(10.10)),
    val freeSnakePart: SnakePart = SnakePart(15.15),
    var Width_Matrix: Int = 0.var Height_Matrix: Int = 0.var isRunning: Boolean = true
)
Copy the code

Game state clock code

Use the LauncherEffect to maintain an infinite loop and distribute a clock at 300ms

val model = viewModel<GameViewModel>()
    LaunchedEffect(key1 = Unit) {
        while (isActive) {
            delay(300)
            model.dispatch()
        }
    }
Copy the code

Snake movement, eat free snake body, hit the wall code

fun dispatch(a) {
        val state = stateFlow.value
        if(state.action! =Action.GameTick){return
        }
        if(! state.isRunning) {// The pause state does not update data
            return
        }
        val firstPart = state.snakeBody.first()
        val newList = mutableListOf<SnakePart>()
        if (state.direction == Direction.Left || state.direction == Direction.Right) {
            val x = firstPart.x + state.direction.increase
            if (x == state.freeSnakePart.x && firstPart.y == state.freeSnakePart.y) {// Eat free snake body
                newList.add(state.freeSnakePart)
                newList.addAll(state.snakeBody)
                emit(state.copy(snakeBody = newList, freeSnakePart = newFreeSnakePart()))
                return
            }
            if (x > state.Width_Matrix || x < 0) {/ / the wall
                emit(
                    state.copy(
                        action = Action.GameOver,
                    )
                )
                return
            } else {
                newList.add(SnakePart(x, firstPart.y))
                newList.addAll(state.snakeBody)
                newList.removeLast()// Delete the last section of the snake}}else {
            val y = firstPart.y + state.direction.increase
            if (y == state.freeSnakePart.y && firstPart.x == state.freeSnakePart.x) {// Eat free snake body
                newList.add(state.freeSnakePart)
                newList.addAll(state.snakeBody)
                emit(state.copy(snakeBody = newList, freeSnakePart = newFreeSnakePart()))
                return
            }
            if (y > state.Height_Matrix || y < 0) {/ / the wall
                emit(
                    state.copy(
                        action = Action.GameOver,
                    )
                )
                return
            } else {
                newList.add(SnakePart(firstPart.x, y))
                newList.addAll(state.snakeBody)
                newList.removeLast()// Delete the last section of the snake
            }
        }
        emit(state.copy(snakeBody = newList))
    }

    fun emit(state: SnakeState) {
        _flow.value = state
    }
Copy the code

Unrealized part

  • Snake strikes its own snake logic
  • An animation of the game ending after the snake hits the wall

If you are interested in this section, you can fork the code and implement it yourself.

Code: github.com/ananananzhu…