100 lines of code teach you to implement the snake game

100 lines of code teach you to implement the snake game

Recently, some small games such as Snake, Tetris, Tic-tac-toe, etc. have been built into the project. Here, the implementation steps will be shared one by one for everyone to learn. If there are any shortcomings or mistakes, please correct them in the comments

Because it is just an example, the interface may not look good. You can replace the squares with better-looking resources. The final effect is as follows:

write picture description here

In fact, the implementation of these small games is not complicated. As long as you understand the idea and build it step by step, you will find that the principle is actually very simple. It’s just that we think it is complicated. The complete demo will be posted later.
The source code of the custom view can be directly viewed here . Snake game view

The implementation steps and instructions are as follows :

1. Customize the game interface
Needless to say, the game must be a custom view to realize the drawing of the interface.
Here we directly inherit the View and rewrite the draw method to draw our game interface, because the small game is relatively lightweight, here I Not using SurfaceView to draw the interface

2. Definition of game attribute value
Snake should have played before, here we simply define the direction, the number of rows and columns, the color of the snake and the color of the food, the direction of the snake's movement, the code and description are as follows:
"`java
/* *
* Snake head direction constant
*/
public final static int S_LEFT = 0;
public final static int S_TOP = 1;
public final static int S_RIGHT = 2;
public final static int S_BOTTOM = 3;
public final static int S_BASE_SPEED = 300;//basic Speed, refresh milliseconds
int mColNum = 18;//Number of columns
int mRowNum = 18;//Number of columns
float mColWidth;//Column width

int mBgColor = Color.parseColor("#a7a6ab");//背景颜色
int mGridLineColor = Color.parseColor("#838692");//网格线颜色
int mSnackBodyColor = Color.WHITE;//蛇身颜色
int mSnackHeadColor = Color.WHITE;//蛇头颜色
int mFoodColor = Color.parseColor("#fcae14");//食物颜色
int mSnackDefLen = 3;//蛇身默认长度
int mDirec = S_LEFT;//蛇头方向
int mSpeed = S_BASE_SPEED;//移动速度
boolean mGamePause = false;
LinkedList<SnackBody> mSnackBodys = new LinkedList<>();//蛇身
SnackBody mFood = new SnackBody(0, 0);//食物
```

3. Explanation of constants and ideas. Here the snake body is implemented using a doubly linked list, which simplifies the addition and removal operations. Both food and body are made of blocks, so a single SnackBody class is directly used here to represent food
4. Interface drawing. Interface The drawing can be simplified to the drawing of the background, grid food and snake, the code is as follows:

   protected void onDraw(Canvas canvas) {
        canvas.drawColor(mBgColor);
        drawFood(canvas);
        drawSnack(canvas);
        drawGridBg(canvas);
    }

5. Interface drawing - draw grid. That is to draw horizontal and vertical lines, here are two loops for easy understanding. (Can be combined into one)

  /**
     * 绘制网格背景
     * @param canvas
     */
    private void drawGridBg(Canvas canvas) {
        int colNum = this.mColNum;
        int rowNum = (int) (getHeight() / mColWidth);//计算行数
        //绘制网格
        mPaint.setColor(mGridLineColor);
        //绘制列
        for (int i = 0; i <= colNum; i++) {
            float startX = i * mColWidth;
            canvas.drawLine(startX, 0, startX, getHeight(), mPaint);
        }
        //绘制行
        for (int i = 0; i <= rowNum; i++) {
            float startY = i * mColWidth;
            canvas.drawLine(0, startY, getWidth(), startY, mPaint);
        }
    }

6. Interface Drawing - Drawing Food

    /**
     * 绘制食物
     * @param canvas
     */
    private void drawFood(Canvas canvas) {
        SnackBody mFood = this.mFood;
        mPaint.setColor(mFoodColor);
        float left = mFood.x * mColWidth;
        float top = mFood.y * mColWidth;
        canvas.drawRect(left, top, left + mColWidth, top + mColWidth, mPaint);
    }

7. Interface drawing - drawing snake

    /**
     * 绘制蛇
     * @param canvas
     */
    private void drawSnack(Canvas canvas) {
        LinkedList<SnackBody> mSnackBodys = this.mSnackBodys;
        for (int i = mSnackBodys.size() - 1; i >= 0; i--) {
            SnackBody mSnackBody = mSnackBodys.get(i);
            mPaint.setColor(i == 0 ? mSnackHeadColor : mSnackBodyColor);
            float left = mSnackBody.x * mColWidth;
            float top = mSnackBody.y * mColWidth;
            canvas.drawRect(left, top, left + mColWidth, top + mColWidth, mPaint);
        }
    }

8. Relevant logic processing. The logic of greedy snake is relatively simple. It can be divided into three blocks. Food random logic snake movement logic game state check

9. Food Random Logic

    //随机食物
    private void randomFood() {
        LinkedList<SnackBody> mSnackBodys = this.mSnackBodys;
        if (mSnackBodys.size() >= mColNum * mRowNum) {
            return;//满了,这么吊的么
        }
        SnackBody first = mSnackBodys.getFirst();
        SnackBody food = new SnackBody(0, 0);
        do {
        //随机一个点,不在蛇头上就行. 这里允许食物和身体重叠
            food.setPositon(random(0, mColNum), random(0, mRowNum));
        } while (first.equals(food));
        this.mFood = food;
    }

10. Snake Movement Logic

/**
     * 蛇的移动
     */
    private void snackMove() {
        if (mGamePause) return;
        LinkedList<SnackBody> mSnackBodys = this.mSnackBodys;
        SnackBody nextHead = new SnackBody(0, 0);
        SnackBody snackHead = mSnackBodys.getFirst();
        switch (mDirec) {
            case S_LEFT:
                nextHead.setPositon(snackHead.x - 1, snackHead.y);
                break;
            case S_TOP:
                nextHead.setPositon(snackHead.x, snackHead.y - 1);
                break;
            case S_RIGHT:
                nextHead.setPositon(snackHead.x + 1, snackHead.y);
                break;
            case S_BOTTOM:
                nextHead.setPositon(snackHead.x, snackHead.y + 1);
                break;
        }
        mSnackBodys.addFirst(nextHead);
        mSnackBodys.removeLast();
        //检查是否吃到食物了,
        SnackBody mFood = this.mFood;
        if (nextHead.equals(mFood)) {
            mSnackBodys.addFirst(mFood);
            mSpeed = getSpeed();//计算新速度
            if (onEatFoodListener != null) {
                onEatFoodListener.eatFood(mSpeed);
            }
            //追加到头部,重新随机食物
            randomFood();
        }
        checkGameEnd();
    }

11. Game state check logic

  private void checkGameEnd() {
        LinkedList<SnackBody> mSnackBodys = this.mSnackBodys;
        SnackBody headBody = mSnackBodys.getFirst();
        //查询头部与身体的交集
        int indexOf = mSnackBodys.lastIndexOf(headBody);
        int cellNum = mColNum * mRowNum;
        int snackLen = mSnackBodys.size();
        //边界判断,咬到自己的判断,和占满了
        if (headBody.x < 0 ||
                headBody.y < 0 ||
                headBody.x >= mColNum ||
                headBody.y >= mRowNum ||
                //0 是蛇头 1是刚吃到的食物 不可能咬到脖子(第二格)
                (indexOf != 0 &&indexOf != 1 && indexOf != -1)||
                    snackLen>=cellNum
                ) {
            mGamePause = true;
            if (onGameOverListener != null) {
                onGameOverListener.gameOver(snackLen, cellNum);
            }
            Logger.D("游戏结束");
        }
    }

12. Game interface refresh and redraw logic

   private void beginGameRun() {
        removeCallbacks(runnable);
        final Runnable localRun = new Runnable() {
            @Override
            public void run() {
                int speed = mIsQuickMove ? mQuickSpeed : mSpeed;
                snackMove();//自动移动
                invalidate();
                postDelayed(runnable, speed);
            }
        };
        postDelayed(this.runnable = localRun, mSpeed);
    }

13. Direction control logic

public void turnTo(int direc) {
        switch (direc) {
            case S_LEFT:
                if (mDirec != S_RIGHT) {
                    mDirec = direc;
                }
                break;
            case S_TOP:
                if (mDirec != S_BOTTOM) {
                    mDirec = direc;
                }
                break;
            case S_RIGHT:
                if (mDirec != S_LEFT) {
                    mDirec = direc;
                }
                break;
            case S_BOTTOM:
                if (mDirec != S_TOP) {
                    mDirec = direc;
                }
                break;
        }
        beginGameRun();//重置重绘时间
        snackMove();//移动蛇
        invalidate();//刷新界面
    }

14. Snake body block entity class

class SnackBody {
        public int x, y;//坐标

        public void setPositon(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public SnackBody(int x, int y) {
            this.x = x;
            this.y = y;
        }
//以下为自动生成代码,由坐标来判断方块是否一样
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            SnackBody snackBody = (SnackBody) o;
            if (x != snackBody.x) return false;
            return y == snackBody.y;
        }

        @Override
        public int hashCode() {
            int result = x;
            result = 31 * result + y;
            return result;
        }
    }

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325205624&siteId=291194637