Using ES6 to masturbate a snake

Keywords: Javascript

Preface

Using es6 to write small games such as Tetris has become an old thing.
But it is necessary for us as beginners to practice.
The main reason is that I wrote one by chance, and there are no other deeper factors.
This project is written with es6 and object-oriented.

CELL model

  • Save the coordinates and class of each cell;
  • Drawing div
  • Eliminate div

In this way, the advantage of object-oriented is that it will be very convenient to write in the future, so there is no need to consider too many other methods.

box model

constructor(map, fixed, arr, color) {
  this.blockCells = [];
  this.color = color;
  this.arr = arr; //The shape of the square
  this.fixed = fixed; //Whether the position is random
  this.x = 1; //Record movement position
  this.y = 1; //Record movement position
  this.map = map;
  this.pos = null; //Location of all block cell s []
  this.rotateFlag = true; //Can I rotate
  this.moveFlag = true; //Whether it can be moved
}

Initialization

  • Randomly generate block shape
  • Randomly generate block coordinates
  • Randomly generate block color
  • Render out blocks

Square shape

Use this.arr to save the shape of the current block;
Use two-dimensional array to save various shapes

[
  [1, 0],
  [1, 0],
  [1, 1]
]

This one represents a 2 * 3 L-shaped square, 1 represents the position to be rendered, 0 represents the position not to be rendered;

Draw blocks

  • First, we analyze the corresponding 1 and 0 in the shape 2D array.
  • Draw from 1 and 0

Get the coordinates of 1

getXY()
Double loop the shape to get the position corner of 1 [0,0], [1,0], [2,0], [2,1], and then render according to the position corner.

Variation of Spin

In fact, you can use arrays to write out all shapes and corresponding rotation shapes, and then call them in the array. But when I was young, I wrote a rotation formula.
If you rotate clockwise, suppose

[
  [1, 0],
  [1, 0],
  [1, 1]
]

Transform to

[
  [1, 1, 1],
  [1, 0, 0]
]

It can be found that all coordinates of 1 and 0 are only x and y interchanged (the same 1 or 0 does not have corresponding calling relationship);
So it's actually a question of the transposition of the approximation matrix, but there are still differences.

let rotateArr = [];
for (let i = 0, len = arr.length; i < len; i++) {
  let inArr = [];
  for (let j = arr.length - 1; j >= 0; j--) {
    inArr.push(arr[j][i]);
  }
  rotateArr.push(inArr);
}
return rotateArr;

Redraw after rotation

In fact, there is no need to redraw the dom after rotation, so as to save dom operation.
Add a decision in getXY(). After obtaining the coordinates of 1, whether to initialize the drawing block or to rotate the moving block after completion;

At the end of each step, you need to record the position in the block, otherwise the rotation will return to the starting point after the end of the change

Judgement boundary

This is to detect according to the coordinate position occupied by the next step, which is needed for moving and rotating.

  • move

    • Get the position of the next step in the direction of movement
    • Judgement boundary
  • rotate

    • Obtain the position of the new shape formed after rotation
    • Judgement boundary

Judgement logic

  • Block accumulation

    • Use an array to store all stacked grid positions to see if they coincide with the next step
  • No block accumulation

    • Determine whether the next position exceeds the boundary of map.

move

Because the cell class has been written before, the move here is actually very simple. If the boundary judgment is passed, just move each square in the square to the next position obtained.

  • Coordinate movement of block object
  • dom operation corresponds to block object movement

Pile up

This is to record the stacked blocks when they reach the boundary downward, and change the class.

// Record deadblock location information to map
this.blockCells.forEach(el => {
  for (let i = 0, len = map.deadBlocks.length; i < len; i++) {
    if (el.x === map.deadBlocks[i][0] && el.y === map.deadBlocks[i][1]) break;
    map.deadBlocks.push(el);
    break;
  }
  if (!map.deadBlocks.length) map.deadBlocks.push(el);
})

// Change class
this.blockCells.forEach(el => {
  el.cell.style.background = '#7f8c8d';
})

//Elimination
this.eliminate();

//Control game restart
game.stop();
game.start();

Elimination

Traverse the stacked blocks of all records. When the number of blocks in the same row is the same as the map size, delete the corresponding blocks in the array and dom.

for (let r = MAP_COL - 1; r > 0; r--) {
  let idxs = []
  let num = 0; //Number of cell s in this line
  deadBlocks.forEach((el, i) => {
    if (el.y === 0) {
      game.over();
      return;
    }
    if (el.y === r) {
      num++;
      idxs.push(i);
    };
  })

  // Elimination
  if (num === MAP_LEN) {
    idxs.forEach(i => {
      deadBlocks[i].remove(this.map);
      delete deadBlocks[i];
    })

    //Move down
    deadBlocks.forEach((el, i) => {
      if (el.y < r) el.move(el.x, el.y + 1);
    })
    r++; //Adjust the current number of rows

    //Score
    game.addScore();
  }
}

Game model

Add a timer to the game to realize the automatic drop of the box; remove the timer when the game is stopped;

  • start-up
  • Score
  • suspend

    handlePause() {
      this.pause = !this.pause;
      if (this.pause) {
        this.stop();
        return;
      }
      this.timer = setInterval(() => {
        block.check('down')
      }, this.speed);
    }
  • Game over

Summary

  • There is no variable to adjust the difficulty of the game.
  • No direct drop function

Portal

Online consumption
Source code

Welcome to star if you like

Posted by Graphi on Mon, 21 Oct 2019 06:19:04 -0700