TS snake project details

Project introduction

The project is completed by using the object-oriented idea in ts language.

preface

After learning the TS project, it is a good choice to practice with this project. Here is a process of js - > ts. js is a process oriented language and TS is an object-oriented language. This is the biggest difference between the two languages.

1, Several objects in greedy snake

Before formally doing this project, we should make it clear that the greedy Snake game can be divided into several modules. Here, we can imagine that the greedy snake can be divided into three objects: food, scoreboard and snake. Next, explain how each object should be completed one by one in this order.

1. Food

For an object, it just contains two points: methods and attributes. Analyze the attributes of food: coordinate position (X,Y). The method to be included in the food object is to change the coordinate position, because after the snake eats the food, the food will change accordingly change(): that is, a random number generates a position.
Code for this class:

class Food{
    elements: HT0MLElement;
    constructor(){
        //If you write directly, you may report an error because you can't get this div,
        //So there will be an error, but we can certainly get it, so we use an exclamation mark to indicate that we can get it.
        //This is a kind of assertion
        this.elements=document.getElementById('food')!;
    }
    //Get food X axis
    get X(){
        return this.elements.offsetLeft;
    }
    get Y(){
        return this.elements.offsetTop;
    }
    change(){
        //The minimum food position is 0 and the maximum is 290. Because the snake moves 10px each time, the food should be divided by 10
        //Generate random position round up and round floor down
        let top=Math.round(Math.random()*29)*10
        let left=Math.round(Math.random()*29)*10
        this.elements.style.top=top+'px'
        this.elements.style.left=left+'px'
    }
 }
 export default Food;

Several ts knowledge points are involved here:
1. Type assertion: when our compiler cannot determine that a variable is a type, but we are sure that it is a type, we can assert this variable. The syntax is as follows:

XX as String
<String>XX 

2. When creating each object, it will correspond to an HTMLElement element. See the first line of the code for details, which actually explains object - > instance
3. get X() {} in ts is the attribute of an object. You can set this attribute by directly return ing the attribute value you want to express in this function, which is similar to a getter in vue. It is convenient for us to perform some operations on the attribute before obtaining the attribute.

2. Scoreboard ScorePannel class

For this class, it should contain two attributes: score and grade.

The idea of realizing this class here is: when a snake eats a food, its score is + 1, and when added to a certain value, its level is + 1. Therefore, this class should contain two methods, addScore() and addLevel(). The specific implementation code is as follows:

class ScorePannel{
    // Record scores and grades
    score=0;
    level=1;
    //dom element corresponding to score level
    scoreEle:HTMLElement;
    levelEle:HTMLElement;
    // Set the variable limit level to increase scalability. If the value is transferred, it will not be transferred to 10
    maxLevel:number;
    // Set the variable to indicate how many decimals are raised one level
    upScore:number;
    constructor(maxLevel:number=10,upScore:number=10){
        this.scoreEle=document.getElementById('score')!;
        this.levelEle=document.getElementById('level')!;
        this.maxLevel=maxLevel;
        this.upScore=upScore
    }
    // Bonus method: the score increases automatically
    addScore(){
        this.score++
        this.scoreEle.innerHTML=this.score+'';
        // Judge the score to determine whether to upgrade
        if(this.score%this.upScore===0){
            this.addLevel()
        }
    }
    // Level increase: assign a value after self increase to set a level judgment
    addLevel(){
        if(this.level<this.maxLevel)
        {
            this.levelEle.innerHTML=++this.level+'';
        }
        
    }
}
// Test code
// const scorePannel=new ScorePannel(100,2)
// for(let i=0;i<200;i++)
// {
//     scorePannel.addScore()
// }
export default ScorePannel

Here, in order to increase the extensibility of the upgrade rule, the relevant variables are defined in the form of constructor parameters, which can also be absorbed in future development. In order to improve the extensibility, the variables to be extended can be used as parameters.

3. Snake

Since the snake has only one head at first, the html structure of the snake here is as follows:

When writing snake, you can split the snake into two parts: head + body, that is, it contains two attributes: head and bodies. However, it should be noted that head also belongs to the body. After eating food, you need to add div to make the snake longer. Therefore, you also need to obtain the div with id snake as an attribute to facilitate the subsequent addition of body; Let's analyze the methods that the snake should have. First, the method to control the movement of the snake. moveBody is essential. Then, there should be an addBody because the length of the snake should be increased. In addition, we also need to consider the case that the snake bumps into itself when its body becomes very long, so we need to add an additional method of checkHeadBody.
addBody: a relatively simple method is to directly add a new dom element to the div with id snake based on the html structure.
moveBody: call this method when set ting the Head coordinates of the snake. In this method, traverse the body dom nodes except the snake Head, so that the positions of these nodes are equal to the position of the previous node, so as to realize the movement of the body. For example, the coordinates of body[3] are equal to the coordinates of body[2] when moving.
The checkHeadBody method is to traverse whether the body nodes other than the head are equal to the head nodes. When they are equal, they hit themselves.
In addition to these methods, we also need to explain what needs to be done inside the set X and set Y functions. Since only one value of X or y of the snake will change at a certain time, you can judge whether it has changed before setting the coordinates of the snake Head. If there is no change, you can directly return, After judgment, it is also necessary to check whether the current value exceeds the boundary range of the snake panel. If it exceeds the boundary range, it means that it has hit the wall. Another point is that whether it moves horizontally or vertically, the snake cannot turn around vertically. Here, it is also necessary to judge whether the coordinate position of the snake's Head in the next second overlaps with the position of the first body, Then call the moveBody and checkHeadBody methods inside the set.
The specific implementation details are as follows:
because

class Snake{
    // The snake head element is the first child element in the div
    head:HTMLElement;
    // Including snake head, the whole snake HTMLCollection will be updated in real time 
    bodys:HTMLCollection;
    element:HTMLElement;
    
    constructor(){
        this.element=document.getElementById('snake')!;
        // The assertion data type gets the first div child element of the snake
        this.head=document.querySelector('#snake > div') as HTMLElement;
        //head is part of the body
        this.bodys=document.getElementById('snake')!.getElementsByTagName('div');
    }
    // Obtain snakehead coordinates:
    get X(){
        return this.head.offsetLeft;
    }
    get Y(){
        return this.head.offsetTop;
    }
    // Set snakehead coordinates
    set X(value:number){
        
        if(this.X===value)
        {
            return
        }
        
    
    if(value<0||value>290)
    {
        throw new Error('The snake hit the wall x')
    }

        // If you turn around and the snake continues
        if(this.bodys[1]&&(this.bodys[1] as HTMLElement).offsetLeft===value){
            if(value>this.X){
                value=this.X-10
            }else{
                value=this.X+10
            }
        }
        this.moveBody()
        this.head.style.left=value+'px'
        this.checkHeadBody()
    }
    set Y(value:number){
        if(this.Y===value)
        {
            return
        }
        // The snake throws an exception when it hits the wall
        if(value<0||value>290)
        {
            throw new Error('The snake hit the wall y')
        }

        // When modifying X, the horizontal coordinates are modified. When the snake moves left and right, it cannot turn right directly
        if(this.bodys[1]&&(this.bodys[1] as HTMLElement).offsetTop===value){
            if(value>this.Y){
                value=this.Y-10
            }else{
                value=this.Y+10
            }
        }
        this.moveBody()
        this.head.style.top=value+'px'
        this.checkHeadBody()
    }
    // Method for increasing body length of snake after eating food 
    addBody(){
     
        // Add div to element
        let newBody=document.createElement('div')
        this.element.appendChild(newBody)
     
    }
    // This method is called when the head is moved
    moveBody(){
        // Set the fourth knot to the third knot position - > three equals two - > two equals one
        // Traverse all bodies 
       console.log(this.bodys.length)
        for(let i=this.bodys.length-1;i>0;i--)
        {
            // Gets the front body position type assertion
            let X=(this.bodys[i-1] as HTMLElement).offsetLeft;
            let Y=(this.bodys[i-1] as HTMLElement).offsetTop;
            // The body has changed
            (this.bodys[i] as HTMLElement).style.left=X+'PX';
            (this.bodys[i] as HTMLElement).style.top=Y+'PX';
        }
    }
      checkHeadBody(){
        //   Get whether all physical examinations overlap with snake head coordinates
        for(let i=1;i<this.bodys.length;i++){
            let bd=this.bodys[i] as HTMLElement
            if(this.X===bd.offsetLeft&&this.Y===bd.offsetTop){
                 throw new Error('Hit yourself')
            }
        }
      }
}
export default Snake;

PS: all the error reports here are realized by throwing exceptions, because next, we need to implement a GameControl class to control the movement of snakes. In this class, we can catch exceptions.

4. Game control GameControl class

This class mainly controls the above three classes to realize the operation of the game. Therefore, in addition to calling the above three classes, it also needs to add attributes such as direction (judge the moving direction of the snake) and ISLIVE (judge whether the snake is alive, that is, whether the game is to be terminated). For methods, the methods that this class should implement include init() to start the game, keydownHandler() to check the key, run() to move the snake, and checkEat() to check the food.
init(): listen for mouse buttons and call run().
keydownHandler() listens for pressing the key and sets the direction value of the attribute to facilitate the implementation of the run method.
run() determines the coordinate changes of X and Y according to the keys. If Y-10 is the upper, then Y+10, left X-10, right X+10, then checkEat is used to determine whether to eat the food. Finally, try modifies the X Y coordinates of the Snake. The reason for using the X here is to capture the anomalies in the class, and then adjust the number of points to the exception when the exception occurs. Finally, the most important thing is to call the run method regularly. The shorter the time, the faster the speed. The call time can be evaluated according to the level, so as to realize the change of speed.
checkEat() directly determines the location of the snake head and the location of the food, then calls the change() of the Food class and addScore() of the scorePannel class, and finally calls the addBody() method of the snake class.
The detailed code is as follows:

import Snake from "./Snake";
import Food from "./Food";
import ScorePannel from "./ScorePannel";
//A unified class that controls all classes
class GameControl {
    // snake
    snake: Snake;
    // food
    food: Food;
    //Scoreboard
    scorePannel: ScorePannel;
    //    Create an attribute to store the moving direction of the snake (key direction)
    direction: string = ''
    // Create an attribute to record whether the snake is alive
    isLive=true;
    constructor() {
        // Declare three new classes    
        this.snake = new Snake();
        this.food = new Food();
        this.scorePannel = new ScorePannel(10,1);
        this.init()
    }
    //    Start the game
    init() {
        //    Bind the event of keyboard key pressing. Note that this point needs to be bound to the gamecontrol object with bind, otherwise it points to document
        document.addEventListener('keydown', this.keydownHandler.bind(this))
        // Call the run method to move the snake
        this.run()

    }
    //   The response function of pressing the keyboard returns a string
    // Chrome:  ArrowUp    IE:   up   
    //          ArrowDown        down
    //          ArrowRight       right
    //          ArrowLeft        left
    keydownHandler(event: KeyboardEvent) {
        console.log(event.key)
        //    Check whether the user has pressed the direction key
        //    Modify direction
        this.direction = event.key
    }
    // Snake Movement
    run() {
        //   top up decrease
        //   top down increase
        //   Right left increase
        //   left decrease
        let X = this.snake.X
        let Y = this.snake.Y
        // Modify the X and Y values according to the key direction
        switch (this.direction) {
            case "ArrowUp":
            case "Up":
                Y-=10;
                break;
            case "ArrowDown":
            case "Down":
                Y+=10;
                break;
            case "ArrowLeft":
            case "Left":
                X-=10;
                break;
            case "ArrowRight":
            case "Right":
                X+=10;
                break;
        }
    // Detect whether you eat food
    this.checkEat(X,Y)
    
    
    //    Modify the X and y of the snake 
    try{
        this.snake.X=X;
        this.snake.Y=Y;
    }catch(e){
      alert((e as Error).message)
      this.isLive=false
    }

    // The time when the timed call run is started is the speed
    this.isLive&&setTimeout(this.run.bind(this),300-(this.scorePannel.level-1)*30)
    }
    // Define a method to detect whether snakes eat food
    checkEat(X:number,Y:number){
        if (X===this.food.X&&Y===this.food.Y){
        this.food.change()
        this.scorePannel.addScore()
        this.snake.addBody()}

    }
}
let test=new Food()
console.log(test.X,test.Y)
export default GameControl


2, Run

Export the gameControl class to the webpack packaged entry file and create a new object to run the game. The running interface is as follows:

3, Summary

The biggest difference between ts and js is that TS is an object-oriented development process, while js is a process oriented development process. The development of TS should be more abstract, but its logic can be seen from the above development.

Keywords: Front-end TypeScript ts

Added by polywog on Wed, 19 Jan 2022 17:53:41 +0200