rendering

Nonsense not to say, first dry effect, source code at the end of the article


Game concept

Tetris is made up of seven squares. At the beginning, during a fall, the player can rotate the squares by 90 degrees, move the squares left and right by the grid, or accelerate the fall of the squares. When a block falls to the bottom of the area or lands on another block and cannot move further down, it is fixed in place and a new random block appears on top of the area and begins to fall. The score increases exponentially as more rows are eliminated simultaneously in a row in a region. The game ends when a fixed stack of blocks reaches the top of the area without eliminating layers.


Operation design

At first I thought it would be buttons at the bottom of the screen, but then I thought, that’s crazy

Now is a touch screen mobile phone, do the button is not easy to press, easy to mistakenly touch, set the following gesture:

  1. Slide up: Rotate the falling block 90° clockwise
  2. Slide left: A falling square moves one square to the left
  3. Swipe right and the falling square moves one square unit to the right
  4. Keep sliding left: a falling block keeps moving left
  5. Keep sliding right: a falling block keeps moving right
  6. Slide down: Blocks fall straight to the bottom

Speaking of bullshit, let’s do something even more bullshit. You can set it to swipe down and swipe up as opposed to swipe left and swipe right as opposed to swipe left and swipe right as opposed to swipe right.


Algorithm rules

When the line satisfies to have no blank space, eliminate current line, have a few line can eliminate a few lines, eliminate more, score also is more, ZA is uncertain 1 cent 2 minutes, so playing have no what meaning

Just eliminate 1 line 100 points at a time eliminate 2 lines 200 points at a time eliminate 3 lines 400 points at a time eliminate 4 lines 800 points

Blocks will fall faster and faster. At the beginning, the falling speed is 750ms/ grid, gradually increasing, the fastest is 150ms/ grid. According to the number of blocks falling, every 5 blocks will increase by 1, increasing by 20ms/ grid (here is my own idea).


Overview of the overall algorithm

The UI is implemented using a custom View, named GameView, and the running logic behind it is controlled by a two-dimensional array, with a 10X20 layout; Refresh the entire GameView every time a block falls and is eliminated; 0 represents operable space placeholder; 1 means fixed block type 1 square placeholder, 11 means falling type 1 square 2 means fixed block type 2 square placeholder, 12 means falling type 2 square placeholder, 3 means fixed block type 3 square placeholder, 13 means falling type 3 square placeholder and so on a total of 7


The code field

“Only say don’t practice the handle, light practice don’t say silly handle”, this is the programmer’s biggest taboo, yesterday daughter-in-law to force (do delicious rice), today quickly lu code!

Operation design

Creates a two-dimensional array that can be called statically, with a default value of 0. Encapsulate multiple public call methods! Read the following notes in detail

package com.blog.tetris.operation

import java.lang.Thread
import kotlin.math.pow

/** * Operation class, including the map, the game's main operation logic, operation entrance */
object Operation {

    / / map
    //0 stands for operable space placeholder (default)
    //1 indicates a fixed block placeholder
    //2 indicates that a block is falling
    val gameMap = Array(20) { Array(10) { 0}}// Drop speed of blocks
    // The initial default value is 750 milliseconds (0.75s)/grid
    var speed = 750L

    var isInGame = false

    // Current rotation degree, default is 1
    private var rotate = 1

    // Game score
    var score = 0L

    // The square to be generated
    var forecastType = 0

    // The number of blocks that have been generated, according to this variable to increase the falling speed of blocks
    var boxNum = 0

    // Game threads
    private lateinit var thread: Thread

    /**
     * 开始游戏
     */
    fun startGame(a) {

        if (isInGame) return

        isInGame = true

        thread = Thread {

            while (isInGame) {

                Thread.sleep(speed)

                val downBox = downBox()
                // Can not continue to move down
                if(! downBox) {// Fix the module
                    fixedMoveBox()

                    // Eliminate blocks
                    eliminate()

                    // Generate a new modulecreateTopBox() } changeListener? .onChange() } } thread.start() }// Eliminate the number of rows
    var fullLine = 0

    /** * eliminate squares * recursively scan squares from bottom to top, eliminating */ line by line
    private fun eliminate(a): Int {

        for (i in gameMap.size - 1 downTo 0) {

            // Whether the current line is satisfied
            var isFull = true

            for (j in gameMap[i].indices) if (gameMap[i][j] == 0) isFull = false

            if (isFull) {

                fullLine++

                for (j in gameMap[i].indices) {
                    gameMap[i][j] = 0
                }

                for (k in i downTo 1) for (n in gameMap[k].indices)
                    gameMap[k][n] = gameMap[k - 1][n]

                eliminate()
            }
        }

        if (fullLine > 0) {

            // Calculate the score based on the elimination rows
            score += (2.toDouble().pow((fullLine - 1).toDouble())).toLong() * 100changeListener? .onChange() Thread.sleep(200)
            fullLine = 0
        }

        return fullLine
    }

    /** * Deform - The falling module rotates 90 degrees clockwise */
    fun deformation(a) {

        // Scan for falling blocks
        val boxArr = mutableListOf<Coordinate>()

        /** * reset to null */
        fun reset(a) {
            for (coordinate in boxArr) gameMap[coordinate.x][coordinate.y] = 0
        }

        // Drop the upper-left corner of the module
        var boxMinX = 20
        var boxMinY = 10

        for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 12.17.) {
            boxArr.add(Coordinate(i, j, gameMap[i][j]))
            if (i < boxMinX) boxMinX = i
            if (j < boxMinY) boxMinY = j
        }

        if (0 == boxArr.size) return

        val boxType = boxArr[0].value

        if (0! = boxArr.size) {// Check whether the rotation criteria are met
            // There is enough space to deform (do not touch left, right and bottom boundaries, do not touch other fixed squares)

            // Retrieve the module type
            when (boxType) {

                12- > {when (rotate) {
                        1- > {// Check whether the rotation condition is met
                            if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY] !in 1.7.
                                && gameMap[boxMinX][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 2][boxMinY + 1]!in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 12
                                gameMap[boxMinX][boxMinY + 1] = 12
                                gameMap[boxMinX + 1][boxMinY + 1] = 12
                                gameMap[boxMinX + 2][boxMinY + 1] = 12
                                rotate = 2}}2- > {if (boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY] !in 1.7.
                                && gameMap[boxMinX][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX][boxMinY + 2]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY] !in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 12
                                gameMap[boxMinX][boxMinY + 1] = 12
                                gameMap[boxMinX][boxMinY + 2] = 12
                                gameMap[boxMinX + 1][boxMinY] = 12
                                rotate = 3}}3- > {if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 1][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 2][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 2][boxMinY + 1]!in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 12
                                gameMap[boxMinX + 1][boxMinY] = 12
                                gameMap[boxMinX + 2][boxMinY] = 12
                                gameMap[boxMinX + 2][boxMinY + 1] = 12
                                rotate = 4}}4- > {if (boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY + 2]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 2]!in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY + 2] = 12
                                gameMap[boxMinX + 1][boxMinY] = 12
                                gameMap[boxMinX + 1][boxMinY + 1] = 12
                                gameMap[boxMinX + 1][boxMinY + 2] = 12
                                rotate = 1}}}}13- > {when (rotate) {
                        1- > {if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 2][boxMinY] !in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY + 1] = 13
                                gameMap[boxMinX + 1][boxMinY] = 13
                                gameMap[boxMinX + 1][boxMinY + 1] = 13
                                gameMap[boxMinX + 2][boxMinY] = 13
                                rotate = 2}}2- > {if (
                                boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY] !in 1.7.
                                && gameMap[boxMinX][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 2]!in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 13
                                gameMap[boxMinX][boxMinY + 1] = 13
                                gameMap[boxMinX + 1][boxMinY + 1] = 13
                                gameMap[boxMinX + 1][boxMinY + 2] = 13
                                rotate = 1}}}}14- > {when (rotate) {
                        1- > {if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 1][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 2][boxMinY + 1]!in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 14
                                gameMap[boxMinX + 1][boxMinY] = 14
                                gameMap[boxMinX + 1][boxMinY + 1] = 14
                                gameMap[boxMinX + 2][boxMinY + 1] = 14
                                rotate = 2}}2- > {if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX][boxMinY + 2]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 1]!in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY + 1] = 14
                                gameMap[boxMinX][boxMinY + 2] = 14
                                gameMap[boxMinX + 1][boxMinY] = 14
                                gameMap[boxMinX + 1][boxMinY + 1] = 14
                                rotate = 1}}}}15- > {when (rotate) {
                        1- > {if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 2][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 2][boxMinY + 1]!in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY + 1] = 15
                                gameMap[boxMinX + 1][boxMinY + 1] = 15
                                gameMap[boxMinX + 2][boxMinY] = 15
                                gameMap[boxMinX + 2][boxMinY + 1] = 15
                                rotate = 2}}2- > {if (boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY] !in 1.7.
                                && gameMap[boxMinX][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX][boxMinY + 2]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 2]!in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 15
                                gameMap[boxMinX][boxMinY + 1] = 15
                                gameMap[boxMinX][boxMinY + 2] = 15
                                gameMap[boxMinX + 1][boxMinY + 2] = 15
                                rotate = 3}}3- > {if (boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY] !in 1.7.
                                && gameMap[boxMinX][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 2][boxMinY] !in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 15
                                gameMap[boxMinX][boxMinY + 1] = 15
                                gameMap[boxMinX + 1][boxMinY] = 15
                                gameMap[boxMinX + 2][boxMinY] = 15
                                rotate = 4}}4- > {if (boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 1][boxMinY] !in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 1]!in 1.7.
                                && gameMap[boxMinX + 1][boxMinY + 2]!in 1.7.
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 15
                                gameMap[boxMinX + 1][boxMinY] = 15
                                gameMap[boxMinX + 1][boxMinY + 1] = 15
                                gameMap[boxMinX + 1][boxMinY + 2] = 15
                                rotate = 1}}}}16- > {when (rotate) {
                        1- > {val midpoint = boxArr[2]

                            if (midpoint.x - 1> =0
                                && midpoint.x + 1 < 20
                                && midpoint.y - 1> =0
                            ) {
                                reset()
                                gameMap[midpoint.x - 1][midpoint.y] = 16
                                gameMap[midpoint.x][midpoint.y] = 16
                                gameMap[midpoint.x][midpoint.y - 1] = 16
                                gameMap[midpoint.x + 1][midpoint.y] = 16
                                rotate = 2}}2- > {val midpoint = boxArr[2]

                            if (midpoint.x - 1> =0
                                && midpoint.x + 1 < 20
                                && midpoint.y - 1> =0
                                && midpoint.y + 1 < 10
                            ) {
                                reset()
                                gameMap[midpoint.x][midpoint.y - 1] = 16
                                gameMap[midpoint.x][midpoint.y] = 16
                                gameMap[midpoint.x][midpoint.y + 1] = 16
                                gameMap[midpoint.x + 1][midpoint.y] = 16
                                rotate = 3}}3- > {val midpoint = boxArr[1]
                            reset()

                            gameMap[midpoint.x - 1][midpoint.y] = 16
                            gameMap[midpoint.x][midpoint.y] = 16
                            gameMap[midpoint.x][midpoint.y + 1] = 16
                            gameMap[midpoint.x + 1][midpoint.y] = 16

                            rotate = 4
                        }
                        4- > {val midpoint = boxArr[1]

                            if (midpoint.y - 1> =0) {
                                reset()
                                gameMap[midpoint.x - 1][midpoint.y] = 16
                                gameMap[midpoint.x][midpoint.y - 1] = 16
                                gameMap[midpoint.x][midpoint.y] = 16
                                gameMap[midpoint.x][midpoint.y + 1] = 16
                                rotate = 1}}}}17- > {val midpoint = boxArr[1]

                    when (rotate) {

                        1- > {if (midpoint.x - 1> =0 && midpoint.x + 2 < 20) {
                                reset()
                                gameMap[midpoint.x - 1][midpoint.y] = 17
                                gameMap[midpoint.x][midpoint.y] = 17
                                gameMap[midpoint.x + 1][midpoint.y] = 17
                                gameMap[midpoint.x + 2][midpoint.y] = 17
                                rotate = 2}}2- > {if (midpoint.y - 1> =0 && midpoint.y + 2 < 10) {
                                reset()
                                gameMap[midpoint.x][midpoint.y - 1] = 17
                                gameMap[midpoint.x][midpoint.y] = 17
                                gameMap[midpoint.x][midpoint.y + 1] = 17
                                gameMap[midpoint.x][midpoint.y + 2] = 17
                                rotate = 1} } } } } changeListener? .onChange() } }/** * moves the falling block to the left */
    fun toLeft(a) {
        val leftArr = mutableListOf<Coordinate>()

        for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 11.17.)
            leftArr.add(Coordinate(i, j, gameMap[i][j]))

        if (0 == leftArr.size) return

        // Whether to allow left movement
        var toLeftStatus = true

        // Move left condition: matches move square and any left square is empty
        for (coordinate in leftArr) {
            if (coordinate.y == 0 || (coordinate.y > 0 && gameMap[coordinate.x][coordinate.y - 1] in 1.7.)) {
                toLeftStatus = false
                break}}// Move left
        if (toLeftStatus) {
            for (i in 0 until leftArr.size) {
                gameMap[leftArr[i].x][leftArr[i].y - 1] = leftArr[0].value
                gameMap[leftArr[i].x][leftArr[i].y] = 0} changeListener? .onChange() } }/** * moves the falling block to the right */
    fun toRight(a) {

        val rightArr = mutableListOf<Coordinate>()

        for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 11.17.)
            rightArr.add(Coordinate(i, j, gameMap[i][j]))

        if (0 == rightArr.size) return

        // Whether moving to the right is allowed
        var toRightStatus = true

        // Move to the right: matches the move square and any square to the right is empty
        for (coordinate in rightArr) if (coordinate.y == 9 || (coordinate.y < 10 && gameMap[coordinate.x][coordinate.y + 1] in 1.7.)) {
            toRightStatus = false
            break
        }

        // Move to the right
        if (toRightStatus) {
            for (i in rightArr.size - 1 downTo 0) {
                gameMap[rightArr[i].x][rightArr[i].y + 1] = rightArr[0].value
                gameMap[rightArr[i].x][rightArr[i].y] = 0} changeListener? .onChange() } }/** * drop directly to the bottom */
    fun downBottom(a) {
        val downArr = mutableListOf<Coordinate>()
        for (i in gameMap.size - 1 downTo 0) for (j in gameMap[i].size - 1 downTo 0) {
            val coordinate = gameMap[i][j]
            if (coordinate in 11.17.) downArr.add(Coordinate(i, j, coordinate))
        }

        if (0! = downArr.size) {var isFixed = false

            val valueTemp = downArr[0].value

            for (coordinate in downArr) if (coordinate.x + 1> =20 || gameMap[coordinate.x + 1][coordinate.y] in 1.7.) return

            for (coordinate in downArr) {

                gameMap[coordinate.x][coordinate.y] = 0
                gameMap[coordinate.x + 1][coordinate.y] = valueTemp

                // Determine if you are at the end
                if (coordinate.x + 1> =19 || gameMap[coordinate.x + 2][coordinate.y] in 1.7.)
                    isFixed = true
            }

            if(! isFixed) downBottom()elsechangeListener? .onChange() }elsechangeListener? .onChange() }/** * Create a new module */ at the top
    private fun createTopBox(a) {

        // Determine if there is a prediction, if there is no prediction, generate a box
        if (0 == forecastType) forecastType = (1.7.).random()

        // Reset rotation degree
        rotate = 1

        // Increase the falling speed of blocks for every 5 blocks generated
        if (++boxNum % 5= =0 && speed > 150) speed -= 20

        // Whether the game is over
        var gameOverStatus = false

        // Generate a drop block
        when (forecastType) {
            1 -> if (gameMap[0] [4]!in 1.7.
                && gameMap[0] [5]!in 1.7.
                && gameMap[1] [4]!in 1.7.
                && gameMap[1] [5]!in 1.7.
            ) {
                gameMap[0] [4] = 11
                gameMap[0] [5] = 11
                gameMap[1] [4] = 11
                gameMap[1] [5] = 11
            } else gameOverStatus = true

            2 -> if (gameMap[0] [5]!in 1.7.
                && gameMap[1] [3]!in 1.7.
                && gameMap[1] [4]!in 1.7.
                && gameMap[1] [5]!in 1.7.
            ) {
                gameMap[0] [5] = 12
                gameMap[1] [3] = 12
                gameMap[1] [4] = 12
                gameMap[1] [5] = 12
            } else gameOverStatus = true

            3 -> if (gameMap[0] [3]!in 1.7.
                && gameMap[0] [4]!in 1.7.
                && gameMap[1] [4]!in 1.7.
                && gameMap[1] [5]!in 1.7.
            ) {
                gameMap[0] [3] = 13
                gameMap[0] [4] = 13
                gameMap[1] [4] = 13
                gameMap[1] [5] = 13
            } else gameOverStatus = true

            4 -> if (gameMap[0] [4]!in 1.7.
                && gameMap[0] [5]!in 1.7.
                && gameMap[1] [3]!in 1.7.
                && gameMap[1] [4]!in 1.7.
            ) {
                gameMap[0] [4] = 14
                gameMap[0] [5] = 14
                gameMap[1] [3] = 14
                gameMap[1] [4] = 14
            } else gameOverStatus = true

            5 -> if (gameMap[0] [3]!in 1.7.
                && gameMap[1] [3]!in 1.7.
                && gameMap[1] [4]!in 1.7.
                && gameMap[1] [5]!in 1.7.
            ) {
                gameMap[0] [3] = 15
                gameMap[1] [3] = 15
                gameMap[1] [4] = 15
                gameMap[1] [5] = 15
            } else gameOverStatus = true

            6 -> if (gameMap[0] [4]!in 1.7.
                && gameMap[1] [3]!in 1.7.
                && gameMap[1] [4]!in 1.7.
                && gameMap[1] [5]!in 1.7.
            ) {
                gameMap[0] [4] = 16
                gameMap[1] [3] = 16
                gameMap[1] [4] = 16
                gameMap[1] [5] = 16
            } else gameOverStatus = true

            7 -> if (gameMap[0] [3]!in 1.7.
                && gameMap[0] [4]!in 1.7.
                && gameMap[0] [5]!in 1.7.
                && gameMap[0] [6]!in 1.7.
            ) {
                gameMap[0] [3] = 17
                gameMap[0] [4] = 17
                gameMap[0] [5] = 17
                gameMap[0] [6] = 17
            } else gameOverStatus = true
        }

        // Generate prediction squares
        forecastType = (1.7.).random()

        if (gameOverStatus) {

            // Callback game overchangeListener? .gameOver(score)// Reset the game data
            speed = 750L
            isInGame = false
            rotate = 1
            score = 0L
            forecastType = 0
            boxNum = 0
            for (i in gameMap.indices) for (j in gameMap[i].indices) gameMap[i][j] = 0}}/** * Fixed moving blocks blocks */
    private fun fixedMoveBox(a) {
        for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 11.17.)
            gameMap[i][j] = gameMap[i][j] - 10
    }

    /** * blocks fall */
    private fun downBox(a): Boolean {

        // Scan for blocks that need to fall
        val downArr = mutableListOf<Coordinate>()
        for (i in gameMap.size - 1 downTo 0) for (j in gameMap[i].size - 1 downTo 0) if (gameMap[i][j] in 11.17.)
            downArr.add(Coordinate(i, j, gameMap[i][j]))

        // Determine if it cannot continue to move down
        if (downArr.size == 0) return false

        // Determine if you can continue to move down
        // Determine whether any of the moving blocks have reached line 20 or touched a fixed block
        for (i in 0 until downArr.size) if (downArr[i].x + 1 > 19 || gameMap[downArr[i].x + 1][downArr[i].y] in 1.7.) return false

        val coordinateTempValue = downArr[0]
        for (j in 0 until downArr.size) {
            val coordinateTemp = downArr[j]
            gameMap[coordinateTemp.x][coordinateTemp.y] = 0
            gameMap[coordinateTemp.x + 1][coordinateTemp.y] = coordinateTempValue.value
        }

        return true
    }

    // The game changes in real time callback
    var changeListener: ChangeListener? = null

    /** * Game real-time change callback */
    interface ChangeListener {
        fun onChange(a)
        fun gameOver(score: Long)
    }

    /** * coordinate entity class */
    data class Coordinate(
        var x: Int.var y: Int.var value: Int)}Copy the code

Visual View

Data logic is finished, to let the user see see ah, through custom View to achieve, see the following notes in detail

package com.blog.tetris.view

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.View
import com.blog.tetris.operation.Operation

class GameView(context: Context? , attrs: AttributeSet?) : View(context, attrs) {// Block size
    val boxSize = 70F

    // Box border size
    private val edge = boxSize / 4F

    // Map drawing
    private var mapPaint: Paint = Paint()

    // Block drawing
    private var boxPaint: Paint = Paint()

    // Predict block drawing
    private var forecastPaint: Paint = Paint()

    // Draw fractions
    private var scorePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)

    private var leftMargin = 0
    private var topMargin = 5 * boxSize

    // Block color
    private var color10 = Color.parseColor("#0302FF")
    private var color11 = Color.parseColor("#3437F9")
    private var color12 = Color.parseColor("#6568FC")
    private var color13 = Color.parseColor("#00089C")
    private var color14 = Color.parseColor("#00027B")

    private var color20 = Color.parseColor("#FEFF04")
    private var color21 = Color.parseColor("#FDFE3E")
    private var color22 = Color.parseColor("#FCFD65")
    private var color23 = Color.parseColor("#ACA706")
    private var color24 = Color.parseColor("# 717804")

    private var color30 = Color.parseColor("#FB0101")
    private var color31 = Color.parseColor("#F33A36")
    private var color32 = Color.parseColor("#F66A64")
    private var color33 = Color.parseColor("#A80400")
    private var color34 = Color.parseColor("#7E0003")

    private var color40 = Color.parseColor("#06FD06")
    private var color41 = Color.parseColor("#37F936")
    private var color42 = Color.parseColor("#68FD67")
    private var color43 = Color.parseColor("#0AA109")
    private var color44 = Color.parseColor("# 037407")

    private var color50 = Color.parseColor("#FF7F04")
    private var color51 = Color.parseColor("#F89A38")
    private var color52 = Color.parseColor("#F8B56A")
    private var color53 = Color.parseColor("#A75304")
    private var color54 = Color.parseColor("# 723709")

    private var color60 = Color.parseColor("#F505F8")
    private var color61 = Color.parseColor("#FB36FB")
    private var color62 = Color.parseColor("#FF66F9")
    private var color63 = Color.parseColor("#A801A7")
    private var color64 = Color.parseColor("# 780378")

    private var color70 = Color.parseColor("#01FFFD")
    private var color71 = Color.parseColor("#37FDFE")
    private var color72 = Color.parseColor("#69FAF9")
    private var color73 = Color.parseColor("#08A5AA")
    private var color74 = Color.parseColor("#01746F")

    init {
        scorePaint.color = Color.parseColor("#FFFFFF")
        mapPaint.color = Color.parseColor("# 000000")
        scorePaint.textSize = boxSize / 1.8 F
        boxPaint.isAntiAlias = true
        forecastPaint.isAntiAlias = true
    }

    override fun onDraw(canvas: Canvas?).{ canvas? .let {// Calculate the distance between the map boundary and the left side of the screen (the game map is placed in the middle of the View)
            leftMargin = width / 2 - boxSize.toInt() * 5

            // Draw a map
            drawMap(it)

            // Draw a square
            drawBoxByArr(it)

            // Draw the score
            drawScore(it)

            // Draw the forecast
            drawForecast(it)
        }
    }

    /** * draw the fraction */
    private fun drawScore(canvas: Canvas) {

        // Draw the title
        canvas.drawText(
            "Score",
            leftMargin.toFloat() + boxSize / 2,
            scorePaint.textSize + boxSize / 2,
            scorePaint
        )

        // Draw the score
        canvas.drawText(
            Operation.score.toString(),
            leftMargin.toFloat() + boxSize / 2,
            scorePaint.textSize + boxSize / 2 + boxSize,
            scorePaint
        )
    }

    /** * draw the forecast */
    private fun drawForecast(canvas: Canvas) {

        // Draw the title
        canvas.drawText(
            "Next",
            width / 2F,
            scorePaint.textSize + boxSize / 2,
            scorePaint
        )

        // Draw a square
        val forecastMap = Array(2) { Array(4) { 0}}when (Operation.forecastType) {
            1 -> {
                forecastMap[0] [0] = 1
                forecastMap[0] [1] = 1
                forecastMap[1] [0] = 1
                forecastMap[1] [1] = 1
            }
            2 -> {
                forecastMap[0] [2] = 2
                forecastMap[1] [0] = 2
                forecastMap[1] [1] = 2
                forecastMap[1] [2] = 2
            }
            3 -> {
                forecastMap[0] [0] = 3
                forecastMap[0] [1] = 3
                forecastMap[1] [1] = 3
                forecastMap[1] [2] = 3
            }
            4 -> {
                forecastMap[0] [1] = 4
                forecastMap[0] [2] = 4
                forecastMap[1] [0] = 4
                forecastMap[1] [1] = 4
            }
            5 -> {
                forecastMap[0] [0] = 5
                forecastMap[1] [0] = 5
                forecastMap[1] [1] = 5
                forecastMap[1] [2] = 5
            }
            6 -> {
                forecastMap[0] [1] = 6
                forecastMap[1] [0] = 6
                forecastMap[1] [1] = 6
                forecastMap[1] [2] = 6
            }
            7 -> {
                forecastMap[0] [0] = 7
                forecastMap[0] [1] = 7
                forecastMap[0] [2] = 7
                forecastMap[0] [3] = 7}}// Draw the prediction box
        val boxColor = getBoxColor(Operation.forecastType)

        for (i in forecastMap.indices) {
            for (j in forecastMap[i].indices) {
                if (forecastMap[i][j] > 0) {

                    val left = width / 2F + boxSize * j
                    val top = i * boxSize + boxSize * 1.5 F
                    val right = width / 2F + boxSize * j + boxSize
                    val bottom = (i + 1) * boxSize + boxSize * 1.5 F

                    // Draw a square
                    forecastPaint.color = boxColor[0]
                    canvas.drawRect(left, top, right, bottom, forecastPaint)

                    // Draw a trapezoid around a single module
                    val path1 = Path()
                    path1.moveTo(left, bottom)
                    path1.lineTo(left, bottom - boxSize)
                    path1.lineTo(left + edge, bottom - boxSize + edge)
                    path1.lineTo(left + edge, bottom - edge)
                    path1.close()

                    val path2 = Path()
                    path2.moveTo(left + edge, top + edge)
                    path2.lineTo(left, top)
                    path2.lineTo(right, top)
                    path2.lineTo(right - edge, top + edge)
                    path2.close()

                    val path3 = Path()
                    path3.moveTo(right - edge, bottom - edge)
                    path3.lineTo(right - edge, top + edge)
                    path3.lineTo(right, top)
                    path3.lineTo(right, bottom)
                    path3.close()

                    val path4 = Path()
                    path4.moveTo(left, bottom)
                    path4.lineTo(left + edge, bottom - edge)
                    path4.lineTo(right - edge, bottom - edge)
                    path4.lineTo(right, bottom)
                    path4.close()

                    boxPaint.color = boxColor[1]
                    canvas.drawPath(path1, boxPaint)
                    boxPaint.color = boxColor[2]
                    canvas.drawPath(path2, boxPaint)
                    boxPaint.color = boxColor[3]
                    canvas.drawPath(path3, boxPaint)
                    boxPaint.color = boxColor[4]
                    canvas.drawPath(path4, boxPaint)
                }
            }
        }

    }


    /** * Map */
    private fun drawMap(canvas: Canvas) {

        // Draw a map
        for (i in 0 until 20) for (j in 0 until 10) canvas.drawRect(
            j * boxSize + leftMargin,
            i * boxSize + topMargin,
            (j + 1) * boxSize + leftMargin,
            (i + 1) * boxSize + topMargin,
            mapPaint
        )

        // Draw the top state diagram
        for (i in 0 until 4) for (j in 0 until 10) canvas.drawRect(
            j * boxSize + leftMargin,
            i * boxSize,
            (j + 1) * boxSize + leftMargin,
            (i + 1) * boxSize,
            mapPaint
        )

    }

    /** * Draw graphs from arrays */
    private fun drawBoxByArr(canvas: Canvas) {

        val mapArr = Operation.gameMap
        for (i in mapArr.indices) {

            for (j in mapArr[i].indices) {
                if (mapArr[i][j] > 0) {

                    boxPaint.color = getBoxColor(mapArr[i][j])[0]

                    val left = j * boxSize + leftMargin
                    val top = i * boxSize + topMargin
                    val right = (j + 1) * boxSize + leftMargin
                    val bottom = (i + 1) * boxSize + topMargin
                    canvas.drawRect(left, top, right, bottom, boxPaint)

                    // Draw a trapezoid around a single module
                    val path1 = Path()
                    path1.moveTo(left, top)
                    path1.lineTo(left + edge, i * boxSize + edge + topMargin)
                    path1.lineTo(left + edge, bottom - edge)
                    path1.lineTo(left, bottom)
                    path1.close()

                    val path2 = Path()
                    path2.moveTo(left, top)
                    path2.lineTo(left + boxSize, top)
                    path2.lineTo(left + boxSize - edge, top + edge)
                    path2.lineTo(left + edge, top + edge)
                    path2.close()

                    val path3 = Path()
                    path3.moveTo(left + boxSize - edge, top + edge)
                    path3.lineTo(left + boxSize, top)
                    path3.lineTo(left + boxSize, bottom)
                    path3.lineTo(left + boxSize - edge, bottom - edge)
                    path3.close()

                    val path4 = Path()
                    path4.moveTo(left, bottom)
                    path4.lineTo(left + edge, bottom - edge)
                    path4.lineTo(left + boxSize - edge, bottom - edge)
                    path4.lineTo(left + boxSize, bottom)
                    path4.close()

                    boxPaint.color = getBoxColor(mapArr[i][j])[1]
                    canvas.drawPath(path1, boxPaint)
                    boxPaint.color = getBoxColor(mapArr[i][j])[2]
                    canvas.drawPath(path2, boxPaint)
                    boxPaint.color = getBoxColor(mapArr[i][j])[3]
                    canvas.drawPath(path3, boxPaint)
                    boxPaint.color = getBoxColor(mapArr[i][j])[4]
                    canvas.drawPath(path4, boxPaint)
                }
            }
        }
    }

    private fun getBoxColor(type: Int): Array<Int> {
        return when (type) {
            1.11 -> arrayOf(color10, color11, color12, color13, color14)
            2.12 -> arrayOf(color20, color21, color22, color23, color24)
            3.13 -> arrayOf(color30, color31, color32, color33, color34)
            4.14 -> arrayOf(color40, color41, color42, color43, color44)
            5.15 -> arrayOf(color50, color51, color52, color53, color54)
            6.16 -> arrayOf(color60, color61, color62, color63, color64)
            7.17 -> arrayOf(color70, color71, color72, color73, color74)
            else -> arrayOf(color10, color11, color12, color13, color14)
        }
    }

    /** * refresh */
    fun refresh(a) {
        postInvalidate()
    }


}
Copy the code

MainActivity

That’s it. That’s all the code is

package com.blog.tetris

import android.annotation.SuppressLint
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.blog.tetris.operation.Operation
import com.blog.tetris.view.GameView
import com.blog.tetris.view.StatusBarUtils


class MainActivity : AppCompatActivity() {

    private lateinit var gameView: GameView
    private lateinit var playGame: TextView

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initView()
        initData()
        initListener()

    }

    private fun initView(a) {

        // Set the status bar font to black
        StatusBarUtils.darkMode(this)

        gameView = findViewById(R.id.gameView)
        playGame = findViewById(R.id.playGame)
    }

    private fun initData(a){}@SuppressLint("ClickableViewAccessibility")
    private fun initListener(a) {

        var downX = 0F
        var downY = 0F
        var direction = 0

        gameView.setOnTouchListener { _, p1 ->
            when(p1? .action) { MotionEvent.ACTION_DOWN -> { downX = p1.x downY = p1.y } MotionEvent.ACTION_MOVE -> {// Lock the direction
                    if (0 == direction) {
                        if (p1.y - downY > 200) direction = 4
                        if ((p1.x - downX < -80 || p1.x - downX > 80)) direction = 1
                        if (p1.y - downY < -200) direction = 2
                    } else {
                        when (direction) {

                            // Slide horizontally
                            1- > {if (p1.x - downX > gameView.boxSize) {
                                    downX = p1.x
                                    Operation.toRight()
                                } else if (p1.x - downX < -gameView.boxSize) {
                                    downX = p1.x
                                    Operation.toLeft()
                                }
                            }

                            // Slide up
                            2 -> {
                                direction = -1
                                Operation.deformation()
                            }

                            // Slide down
                            4 -> {
                                direction = -1
                                Operation.downBottom()
                            }
                        }
                    }


                }
                MotionEvent.ACTION_UP -> {
                    downX = 0F
                    downY = 0F
                    direction = 0}}true
        }


        // Game change callback
        Operation.changeListener = object : Operation.ChangeListener {
            override fun onChange(a) {
                gameView.refresh()
            }

            override fun gameOver(score: Long) {
                runOnUiThread {
                    val dialog = AlertDialog.Builder(this@MainActivity)
                    dialog.setTitle("Tip")
                    dialog.setMessage("Game over! Score:$score")
                    dialog.setPositiveButton(
                        "Start over"
                    ) { _, _ -> Operation.startGame() }
                    dialog.setNegativeButton(
                        "Cancel"
                    ) { _, _ -> }
                    dialog.setCancelable(false)
                    dialog.show()
                }
            }
        }

        playGame.setOnClickListener {
            Operation.startGame()
        }
    }


}
Copy the code

Download the source code

Github:github.com/ThirdGoddes…