1
Summary: You guys, can you insert this snake game into the editor?

This article is shared from the HUAWEI cloud community " Let's play ", the original author: DevUI.

insert this snake game into the editor?

Just follow the four steps below:

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

Step 1: Customize toolbar buttons

This 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', 'snake'], // 新增的
];

Customize toolbar button icons:

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

Add toolbar button event:

const quill = new Quill('#editor', {
  theme: 'snow',
  modules: {
    toolbar: {
      container: TOOLBAR_CONFIG,
      handlers: {
        ...
        // 增加一个空的事件
        snake(value): void {
          console.log('snake~~');
        },
      },
    }
  },
});

Step 2: Customize Blot content SnakeBlot

No longer verbose, just refer to the previous article and write it directly:

"How to insert the dragon into the editor? 》

"Practice of Quill Rich Text Editor"

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);
    }

    // 绘制贪吃蛇游戏的代码参考对半同学的文章:https://juejin.cn/post/6959789039566192654
    new GreedySnake(node).start();
    
    return node;
  }
}

export default SnakeBlot;

Draw greedy snake

Since the code written by half of the students in an hour is really elegant, I couldn't help posting the code (invasion). The source code of the article comes from the article of half of the students:

Canvas 300 lines of code to achieve a greedy 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
      );
    }
  }
}

Step 3: Register custom Blot on Quill

With SnakeBlot, you also need to register it in Quill before you can use it:

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

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

After calling the API, you can play the snake game, so 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',
          });
        },
      },
    }
  },
});

Effect picture:
image.png

Always keep your childish innocence, keep your curiosity about the world, you are the best gift God brings to this world.

Click to follow and learn about Huawei Cloud's fresh technology for the first time~


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量