Abstract: Do you think you can insert the snake game into the edit?

This article is shared from Huawei cloud community “the whole snake to play in the editor”, the original author: DevUI.

Can we insert this snake game into the edit?

Just follow these four steps:

  • Step 1: Customize toolbar buttons
  • Step 2: Customize Blot content
  • Step 3: Register a custom Blot with Quill
  • Step 4: Call Quill’s API to insert custom content

Step 1: Customize toolbar buttons

This one is very simple:

const TOOLBAR_CONFIG = [ [{ header: ['1', '2', '3', false] }], ['bold', 'italic', 'underline', 'link'], [{ list: 'ordered'}, {list: 'bullet'}], [' clean '], [' card ', 'divider', 'emoji', 'file', 'tag'], [' dragon 'and' snake '], / / the new];Copy the code

Custom toolbar button ICONS:

const snakeIcon = `<svg>... </svg>`; const icons = Quill.import('ui/icons'); icons.snake = snakeIcon;Copy the code

Added toolbar button events:

const quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: { container: TOOLBAR_CONFIG, handlers: { ... // Add an empty event snake(value): void {console.log('snake~~'); },},}},});Copy the code

Step 2: Customize the SnakeBlot content

No longer wordy, refer to the previous article directly written:

How to Insert dragons into an Editor?

Quill Rich Text Editor in Action

snake.ts

import Quill from 'quill'; import GreedySnake from '.. /.. /shared/greedy-snake'; const BlockEmbed = Quill.import('blots/block/embed'); class SnakeBlot extends BlockEmbed { static blotName = 'snake'; static tagName = 'canvas'; static create(value): any { const node = super.create(value); const { id, width, height } = value; node.setAttribute('id', id || SnakeBlot.blotName); if (width ! == undefined) { node.setAttribute('width', width); } if (height ! == undefined) { node.setAttribute('height', height); } / / draw the snake game code reference half-and-half classmate's article: https://juejin.cn/post/6959789039566192654 new GreedySnake (node). The start (); return node; } } export default SnakeBlot;Copy the code

Draw the snake

As half of the students spend an hour to write out the code is very elegant, could not help but post its code (invasion deletion), article source code from half of the students article:

Canvas 300 lines of code to implement a snake

greedy-snake.ts

// 大小为64 * 40
export default class GreedySnake {
  canvas;
  ctx;
  maxX;
  maxY;
  itemWidth;
  direction;
  speed;
  isStop;
  isOver;
  isStart;
  score;
  timer;
  j;
  canChange;
  grid;
  snake;
  food;


  // mask;
  // scoreDom;


  constructor(container) {
    this.canvas = typeof container === 'string' ? document.querySelector(container) : container;
    this.canvas.setAttribute('width', 640);
    this.canvas.setAttribute('height', 400);
    this.canvas.setAttribute('style', 'border: solid 2px #ddd');
    this.ctx = this.canvas.getContext('2d');
    this.maxX = 64;          // 最大行
    this.maxY = 40;          // 最大列
    this.itemWidth = 10;     // 每个点的大小
    this.direction = 'right'; // up down right left 方向
    this.speed = 150;        // ms 速度
    this.isStop = false;     // 是否暂停
    this.isOver = false;     // 是否结束
    this.isStart = false;    // 是否开始
    this.score = 0;          // 分数
    this.timer = null;       // 移动定时器
    this.j = 1;
    this.canChange = true;


    this.grid = new Array();


    // this.scoreDom = document.querySelector('#score');
    // this.mask = document.querySelector('#mask');


    for (let i = 0; i < this.maxX; i++) {
      for (let j = 0; j < this.maxY; j++) {
        this.grid.push([i, j]);
      }
    }


    this.drawGridLine();
    this.getDirection();


    document.addEventListener('keydown', (event) => {
      if (event.keyCode === 13) {
        if (!this.isStart) return;
        this.start();
      }
    });
  }


  // 开始
  start(): void {
    if (this.timer) {
      clearTimeout(this.timer);
    }
    if (!this.isStart) {
      this.isStart = true;
    }
    this.score = 0;
    this.speed = 150;
    this.isStop = false;
    this.isOver = false;
    this.direction = 'right';
    this.createSnake();
    this.createFood();
    this.draw();
    this.move();
    // this.mask.style.display = 'none';
  }


  // 创建蛇主体
  createSnake(): void {
    this.snake = [
      [4, 25],
      [3, 25],
      [2, 25],
      [1, 25],
      [0, 25]
    ];
  }


  // 移动
  move(): void {
    if (this.isStop) {
      return;
    }


    let [x, y] = this.snake[0];
    switch (this.direction) {
      case 'left':
        x--;
        break;
      case 'right':
        x++;
        break;
      case 'up':
        y--;
        break;
      case 'down':
        y++;
        break;
    }


    // 如果下一步不是食物的位置
    if (x !== this.food[0] || y !== this.food[1]) {
      this.snake.pop();
    } else {
      this.createFood();
    }


    if (this.over([x, y])) {
      this.isOver = true;
      // this.mask.style.display = 'block';
      // this.mask.innerHTML = '结束';
      return;
    }
    if (this.completed()) {
      // this.mask.style.display = 'block';
      // this.mask.innerHTML = '恭喜您,游戏通关';
      return;
    }


    this.snake.unshift([x, y]);


    this.draw();
    this.canChange = true;
    this.timer = setTimeout(() => this.move(), this.speed);
  }


  // 暂停游戏
  stop(): void {
    if (this.isOver) {
      return;
    }
    this.isStop = true;
    // this.mask.style.display = 'block';
    // this.mask.innerHTML = '暂停';
  }


  // 继续游戏
  continue(): void {
    if (this.isOver) {
      return;
    }
    this.isStop = false;
    this.move();
    // this.mask.style.display = 'none';
  }


  getDirection(): void {
    // 上38 下40 左37 右39 不能往相反的方向走
    document.onkeydown = (e) => {
      // 在贪吃蛇移动的间隔内不能连续改变两次方向
      if (!this.canChange) {
        return;
      }
      switch (e.keyCode) {
        case 37:
          if (this.direction !== 'right') {
            this.direction = 'left';
            this.canChange = false;
          }
          break;
        case 38:
          if (this.direction !== 'down') {
            this.direction = 'up';
            this.canChange = false;
          }
          break;
        case 39:
          if (this.direction !== 'left') {
            this.direction = 'right';
            this.canChange = false;
          }
          break;
        case 40:
          if (this.direction !== 'up') {
            this.direction = 'down';
            this.canChange = false;
          }
          break;
        case 32:
          // 空格暂停与继续
          if (!this.isStop) {
            this.stop();
          } else {
            this.continue();
          }
          break;
      }
    };
  }
  createPos(): any {
    // tslint:disable-next-line: no-bitwise
    const [x, y] = this.grid[(Math.random() * this.grid.length) | 0];


    for (const item of this.snake) {
      if (item[0] === x && item[1] === y) {
        return this.createPos();
      }
    }
    // for (let i = 0; i < this.snake.length; i++) {
    //   if (this.snake[i][0] === x && this.snake[i][1] === y) {
    //     return this.createPos();
    //   }
    // }


    return [x, y];
  }
  // 生成食物
  createFood(): void {
    this.food = this.createPos();


    // 更新分数
    // this.scoreDom.innerHTML = 'Score: ' + this.score++;


    if (this.speed > 50) {
      this.speed--;
    }
  }


  // 结束
  over([x, y]): boolean {
    if (x < 0 || x >= this.maxX || y < 0 || y >= this.maxY) {
      return true;
    }


    if (this.snake.some(v => v[0] === x && v[1] === y)) {
      return true;
    }
  }


  // 完成
  completed(): boolean {
    if (this.snake.length === this.maxX * this.maxY) {
      return true;
    }
  }


  // 网格线
  drawGridLine(): void {
    for (let i = 1; i < this.maxY; i++) {
      this.ctx.moveTo(0, i * this.itemWidth);
      this.ctx.lineTo(this.canvas.width, i * this.itemWidth);
    }


    for (let i = 1; i < this.maxX; i++) {
      this.ctx.moveTo(i * this.itemWidth, 0);
      this.ctx.lineTo(i * this.itemWidth, this.canvas.height);
    }
    this.ctx.lineWidth = 1;
    this.ctx.strokeStyle = '#ddd';
    this.ctx.stroke();
  }


  // 绘制
  draw(): void {
    // 清空画布
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);


    this.drawGridLine();


    this.ctx.fillStyle = '#000';
    this.ctx.fillRect(
      this.food[0] * this.itemWidth + this.j,
      this.food[1] * this.itemWidth + this.j,
      this.itemWidth - this.j * 2,
      this.itemWidth - + this.j * 2
    );
    // tslint:disable-next-line: no-bitwise
    this.j ^= 1;


    this.ctx.fillStyle = 'green';
    this.ctx.fillRect(
      this.snake[0][0] * this.itemWidth + 0.5,
      this.snake[0][1] * this.itemWidth + 0.5,
      this.itemWidth - 1,
      this.itemWidth - 1
    );
    this.ctx.fillStyle = 'red';
    for (let i = 1; i < this.snake.length; i++) {
      this.ctx.fillRect(
        this.snake[i][0] * this.itemWidth + 0.5,
        this.snake[i][1] * this.itemWidth + 0.5,
        this.itemWidth - 1,
        this.itemWidth - 1
      );
    }
  }
}
Copy the code

Step 3: Register a custom Blot with Quill

With SnakeBlot, you also need to register it with Quill to use it:

import SnakeBlot from './formats/snake';
Quill.register('formats/snake', SnakeBlot);
Copy the code

Step 4: Call Quill’s API to insert custom content

Call API can play snake game, happy to fly!

const quill = new Quill('#editor', { theme: 'snow', modules: { toolbar: { container: TOOLBAR_CONFIG, handlers: { ... snake(value): void { console.log('snake~~'); const index = this.quill.getSelection().index; This.quill. InsertEmbed (index, 'snake', {id: 'canvas-snake',}); },},}},});Copy the code

Effect:

Click to follow, the first time to learn about Huawei cloud fresh technology ~