preface

This project is based on the Silicon Valley TypeScript tutorial with some improvements to practice TypeScript writing and learn how to code with TS thinking.

1. Project interface construction

  • HTML structure

    <body>
        <! Create the main container for the game -->
        <div id="main">
            <! -- Set the stage for the game -->
            <div id="stage">
                <! -- Set snake -->
                <div id="snake">
                    <! -- Div inside snake represents parts of snake -->
                    <div></div>
                </div>
                <! -- Set food -->
                <div id="food">
                    <! -- Set four small divs to style the food -->
                    <div></div>
                    <div></div>
                    <div></div>
                    <div></div>
                </div>
            </div>
            <! -- Set the scoreboard -->
            <div id="score-panel">
                <div> 
                    SCORE:<span id="score">0</span>
                </div>
                <div>
                    level:<span id="level">1</span>
                </div>
            </div>
        </div>
    </body
    Copy the code
  • Less structure

    // Set variables@bg-color:#b7d4a8; // Clear the default style *{padding: 0;
        margin: 0; // Change the calculation method of the box modelbox-sizing: border-box;
    }
    body{
        font:bold 20px "Courier"} // Set the main window style#main{
        width: 360px;
        height: 420px; // Set the background colorbackground-color: @bg-color; // Set the centermargin:100px auto;
        border:10pxsolid black; // Set the rounded cornersborder-radius:20px; // Enable the elastic box modedisplay: flex; // Set the main axis direction from top to bottomflex-flow: column; // Set the alignment of the main axisjustify-content: space-around; // Set the alignment of the side axesalign-items: center; // The stage of the game#stage{
            width:304px;
            height:304px;
            border: 2pxsolid black; // Enable relative positioningposition: relative; // Set the style of the snakedivThe style of the#snake{
                &>div{
                width: 10px;
                height: 10px;
                background-color: black;
                // margin-right: 2px; // Here we set a frame for the snake's body. The purpose is to keep each section of the snake separate. The background color of the frame is the same as the background color of the stage, so we see sections of the snakeborder: 1pxsolid @bg-color; // Turn on absolute positioning, because the snake moves with a referenceposition: absolute; } // Snake head is red, easy to distinguish &>div:first-child{
                    background-color: red; }}#food{
                width: 10px;
                height: 10px;
                //background-color: rgb(167.37.37);
                // border: 1px solid @bg-color;
                position: absolute;
                left: 40px;
                top:100px;
                display: flex; // Set the horizontal axis as the main axis, and warp to automatically wrap linesflex-flow: row wrap;
                justify-content: space-between;
                align-content: space-between; & >div{
                    width: 4px;
                    height: 4px;
                    background-color: rgb(167.37.37); / / makes fourdivrotating45°
                    transform: rotate(45deg); }}} // Integrator disk#score-panel{
        width: 300px;
        display: flex; // Set the alignment method for the main axisflex-flow: row;
        justify-content: space-between;
        align-items: center; }}Copy the code

2 complete the Food category

The first step is to identify which properties and methods are in the class

  1. Because it’s food, you have to get the elements that correspond to food
  2. Because the location of the food changes, you need to get the coordinates of the food, and you need to have a way to change the location of the food
class Food{
    // Define an attribute that represents the element corresponding to the food
    element:HTMLElement;
    constructor(){
        // Get the food element from the page and assign it to the element
        // Note that the statement may be blank, so just add one after the statement! Can be
        this.element = document.getElementById('food')! ; }// Define a method to get the X coordinate of food
    get X() {return this.element.offsetLeft;
    }

    // Define a method to get the Y coordinate of food
    get Y() {return this.element.offsetTop;
    }
    // Change the location of the food
    change(){
        // Generate a random location
        // The snake moves one square at a time. The size of one square is 10, so the coordinates of the food must be full ten
        // Since the width of the food is 10 and the width of the stage is 304, the minimum position of the food is 0 and the maximum is 290.

        // math.random (), generates a random number between 0 and 1, with interval [0,1]
        Math.random()*29 generates random numbers between [0,29]
        The math.round () method rounds a number to the nearest integer.
        let top = Math.round(Math.random()*29) *10
        let left = Math.round(Math.random()*29) *10
        this.element.style.left = left +'px';
        this.element.style.top = top + 'px'}}export default Food;
Copy the code

3 ScorePanel class

class ScorePanel{
    // Score and level are used to record scores and levels, respectively
    score = 0;
    level = 1;

    // The element where the fraction and rank are, initialized in the constructor
    scoreEle:HTMLElement;
    levelEle:HTMLElement;
    
    // Set a variable to limit the level
    maxLevel:number;
    // Set a variable to indicate how much time to upgrade
    upScore:number
    
    // Default values are used if no parameters are passed
    constructor(maxLevel:number=10,upScore:number = 10){
        this.scoreEle = document.getElementById('score')! ;this.levelEle = document.getElementById('level')! ;this.maxLevel = maxLevel;
        this.upScore = upScore;
    }

    // Set bonus points
    addScore(){
        // Incrementing the score, adding '' to convert it to a string
        this.scoreEle.innerHTML = ++this.score+' ';
        // Determine the score
        if(this.score%this.upScore===0) {this.levelUp()
        }
    }
    levelUp(){
        if(this.level < this.maxLevel){
            this.levelEle.innerHTML = ++this.level + ' '; }}}export default ScorePanel;
Copy the code

4. Writing the Snake class

  1. First of all, we need to identify the head and body of the snake. We do not operate the div with the ID snake, but the div under the DIV with the ID snake

  2. Set properties, including snake head, snake body and snake container

    // The element representing the snake head
        head:HTMLElement;
        // The body of the snake (including the head)
        //HTMLCollection: a collection of HTML elements.
        // The getElementsByTagName() method returns an HTMLCollection object.
        bodies:HTMLCollection;
        // Get the snake container
        element:HTMLElement;
        constructor(){
            this.element = document.getElementById('snake')! ;// Retrieve the first div in Snake and tag the element type HTMLElement
            this.head = document.querySelector('#snake > div') as HTMLElement;
            // Retrieve all divs under Snake
            this.bodies = this.element.getElementsByTagName('div')}Copy the code
  3. Get the coordinate of the snake (the coordinate of the head)

    // Get the coordinates of the snake's head
        / / the X coordinate
        get X() {return this.head.offsetLeft;
        }
        / / Y coordinates
        get Y() {return this.head.offsetTop
        }
    Copy the code
  4. Set the coordinates of the snakehead, so you have to think about that

    • Whether the new value and the old value are the same, if the same, do not modify, directly return
    • If the value is in the valid range, if not, the snake hits the wall, go to the exception handling
    • When changing the coordinates, take into account the direction the snake is moving
    • The head of the snake moves, the body of the snake has to move, and it has to check to see if it hits itself
    // Set the coordinates of the snake head
        set X(value) {// If the new value is the same as the old value, return it without modification
            if(this.X === value){
                return;
            }
    
            // the valid range of X values is between [0,290]
            if(value < 0 || value > 290) {// Enter the judgment and prove that the snake hit the wall
                throw new Error('The snake has hit the wall! ');
            }
            // When you change x, you are changing the horizontal coordinates, the snake is moving left and right, the snake is moving left, it cannot turn right, and vice versa
            if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value){
                console.log("There has been a turn in the horizontal direction.");
                // If called, let the snake continue moving in the opposite direction
                if(value > this.X){
                    // If the new value is greater than the old value X, then the snake is moving to the right
                    value = this.X - 10;
                }else{
                     / / left
                     value = this.X + 10; }}// Move the body
            this.moveBody();
    
            this.head.style.left = value + 'px';
            // Check if you hit yourself
            this.checkHeadBody();
    
        }
        set Y(value) {// If the new value is the same as the old value, the value is returned without modification
             if(this.Y === value){
                return;
            }
             // The value of Y ranges from 0 to 290
             if(value < 0 || value > 290) {// Throw an exception when the snake hits the wall
                throw new Error('The snake has hit the wall! ');
            }
            // When y is changed, it is changing the vertical coordinates, the snake is moving up and down, the snake is moving up, it cannot turn down, and vice versa
            if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value){
            if(value > this.Y){
                value = this.Y - 10;
            }else{
                value = this.Y + 10; }}// Move the body
            this.moveBody();
            this.head.style.top = value + 'px';
            // Check if you hit yourself
            this.checkHeadBody();
        }
    Copy the code
  5. Increases the body of the snake

    Add div directly to element

       // The insertAdjacentHTML() method inserts a given element node at a given location relative to the element being called
        // 'beforeBegin' : before the element itself.
        // 'afterBegin' : before inserting the first child node inside the element.
        // 'beforeEnd' : after inserting the last child node inside the element.
        // 'afterend' : after the element itself.
        addBody(){
            // Add div to element
            this.element.insertAdjacentHTML("beforeend"."<div></div>");
        }
    Copy the code
  6. The way a snake moves its body

    Set the back body to the position of the front body

    For example:

    • Section 4 = the position of section 3
    • Section 3 = the position of section 2
    • Section 2 = Position of snakehead

    So you need to go through and get all the bodies, and then set the position of the bodies

      // Added a snake body movement method
        moveBody(){
            /* * Set the rear body to the position of the front body * For example: * Section 4 = Section 3 position * Section 3 = Section 2 position * Section 2 = Section 2 position * */
           // Iterate to get all the bodies
           for(let i = this.bodies.length-1; i>0; i--){// Get the position of the front body
           let X = (this.bodies[i-1] as HTMLElement).offsetLeft;
           let Y = (this.bodies[i-1] as HTMLElement).offsetTop;
    
           // Set the value to the current body
           (this.bodies[i] as HTMLElement).style.left = X + 'px';
           (this.bodies[i] as HTMLElement).style.top = Y + 'px'; }}Copy the code
  7. Check to see if the snakehead hit the body

    Get all the bodies and check if they overlap with the coordinates of the snake’s head. If they overlap, it proves that the snake’s head hit the body, and enter the exception processing

    // How to check if the head of a snake hit the body
        checkHeadBody(){
            // Get all the bodies and check to see if they overlap with the snake's head
            for(let i =1; i<this.bodies.length; i++){let bd = this.bodies[i] as HTMLElement;
                if(this.X === bd.offsetLeft && this.Y === bd.offsetTop){
                    // Enter the judgment that the snake hit its head on its body, game over
                    throw new Error('I hit myself! ')}console.log(this.X); }}Copy the code

5. GameControl

  1. GameControl controls the core classes for the entire game, so I’ll introduce the Food, Snake, and ScorePanel classes I wrote earlier
// Introduce other classes
import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel"
Copy the code

The property is then defined and declared in the constructor

    // Define three attributes
    snake:Snake;
    food:Food;
    scorePanel:ScorePanel;
    // Create a property that records whether the game is over
    isLive:boolean= true;
    constructor(){
        this.snake = new Snake();
        this.food = new Food();
        this.scorePanel = new ScorePanel(10.10)}Copy the code
  1. Bind key events

    First create a property to store the snake’s movement direction (i.e., control direction)

     // Initialize the direction
      direction: string = 'Right';
    Copy the code

    Define two string arrays that record the directions of the legal keys. This includes both Chrome and IE

     /* chrome IE * ArrowUp Up ArrowDown Down ArrowLeft Left ArrowRight Right * */
    private XDirectionEvents:string[] = ['ArrowLeft'.'Left'.'ArrowRight'."Right"];
    private YDirectionEvents:string[] = ['ArrowUp'.'Up'.'ArrowDown'."Down"];
    Copy the code

    Then bind the keyboard key pressed event, which is bound at initialization time

    document.addEventListener('keydown'.this.keydownHandler.bind(this))
    Copy the code
      keydownHandler(event:KeyboardEvent){
            if (this.direction === event.key) return;
            // We need to check whether the event. Key is valid (whether the user pressed the correct key).
            switch(event.key){
                case 'Up':
                    case 'ArrowUp':
                    case 'Down':
                    case 'ArrowDown':
                      if (this.YDirectionEvents.includes(this.direction))  return;
                      this.direction = event.key
                      break;
                    case 'Left':
                    case 'ArrowLeft':
                    case 'Right':
                    case 'ArrowRight':
                      if (this.XDirectionEvents.includes(this.direction)) return;
                      this.direction = event.key
                      break; }}Copy the code
  2. Create a method to control the movement of the snake

    // Create a method to control the movement of the snake
        run(){
             /* * Change the position of the snake according to this. Direction * up top decrease * down top increase * left left decrease * right left increase * */
            // Get the snake's current coordinates
            let X = this.snake.X;
            let Y = this.snake.Y;
    
            // Change the X and Y values according to the key direction
            switch(this.direction){
                case "ArrowUp":
                case "Up":
                    // Move top up to reduce
                    Y -=10;
                    break;
                case "ArrowDown":
                case "Down":
                    // Move the top down to increase
                    Y += 10;
                    break;
                case "ArrowLeft":
                case "Left":
                    // Move left to reduce the left
                    X -= 10;
                    break;
                case "ArrowRight":
                case "Right":
                    // Move the left increment to the right
                    X += 10;
                    break;
    
            }
    
        this.checkEat(X,Y);
    
        // Modify the X and Y values of the snake
        try{
            this.snake.X =X;
            this.snake.Y = Y;
        }catch(e){
            // Enter catch, indicating that an exception has occurred, game over, and a pop-up message is displayed
            alert(e.message+' GAME OVER! ');
            // Set isLive to false
            this.isLive = false;
        }
         // Start a scheduled call
         // Snake movement events are based on rank
       this.isLive && setTimeout(this.run.bind(this), 300- (this.scorePanel.level-1) *30);
        }
    
    Copy the code
  3. Define a method to check if a snake has eaten food

     checkEat(X:number,Y:number){
            // If food is eaten
            if(X === this.food.X && Y===this.food.Y){
                // The food should be repositioned
                this.food.change()
                // The score is increased
                this.scorePanel.addScore();
                // The snake wants to add a section
                this.snake.addBody()
            }
    
        }
    Copy the code
  4. Initialize the game

     // The initialization of the game is called after the game starts
        init(){
            // Bind the keyboard key pressed event
            document.addEventListener('keydown'.this.keydownHandler.bind(this));
            // Call the run method to make the snake move
            this.run();
        }
    Copy the code

    It is then called in the constructor, so that the game is initialized as soon as the object is created

Effect of the project

The project address

Making the address