• 注册
  • 前端博客 前端博客 关注:0 内容:404

    我的编辑器能玩贪吃蛇,一起玩不?

  • 查看作者
  • 打赏作者
  • 当前位置: 职业司 > 前端开发 > 前端博客 > 正文
    • 前端博客
    • ​​​​​摘要:你们说,能不能把这个贪吃蛇游戏插入到编辑中呢?

      本文分享自华为云社区《整个贪吃蛇到编辑器里玩吧》,原文作者:DevUI。

      能不能把这个贪吃蛇游戏插入到编辑中呢?

      按照以下四个步骤来就行:

      • 第一步:自定义工具栏按钮
      • 第二步:自定义 Blot 内容
      • 第三步:在 Quill 注册自定义 Blot
      • 第四步:调用 Quill 的 API 插入自定义内容

      第一步:自定义工具栏按钮

      这个非常简单:

      const TOOLBAR_CONFIG = [
      [{ header: ['1', '2', '3', false] }],
      ['bold', 'italic', 'underline', 'link'],
      [{ list: 'ordered' }, { list: 'bullet' }],
      ['clean'],
      ['card', 'divider', 'emoji', 'file', 'tag'],
      ['dragon', 'snake'], // 新增的
      ];
      复制代码

      自定义工具栏按钮图标:

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

      增加工具栏按钮事件:

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

      第二步:自定义 Blot 内容 SnakeBlot

      不再啰嗦,参考之前的文章直接写就好:

      《如何将龙插入到编辑器中?》

      《Quill 富文本编辑器的实践》

      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);
      }
      // 绘制贪吃蛇游戏的代码参考对半同学的文章: 链接
      new GreedySnake(node).start();
      return node;
      }
      }
      export default SnakeBlot;
      复制代码

      绘制贪吃蛇

      由于对半同学花一个小时写出来的代码实在非常优雅,忍不住将其代码贴出来了(侵删),文章源码来源于对半同学的文章:

      canvas 300 行代码实现一个贪吃蛇

      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
      );
      }
      }
      }
      复制代码

      第三步:在 Quill 注册自定义 Blot

      有了 SnakeBlot,还需要将其注册到 Quill 中才能使用:

      import SnakeBlot from './formats/snake';
      Quill.register('formats/snake', SnakeBlot);
      复制代码

      第四步:调用 Quill 的 API 插入自定义内容

      调用完 API 就可以玩贪吃蛇游戏啦,开心到飞起!

      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',
      });
      },
      },
      }
      },
      });
      复制代码

      效果图:

      我的编辑器能玩贪吃蛇,一起玩不?

      点击关注,第一时间了解华为云新鲜技术~

      请登录之后再进行评论

      登录

      手机阅读天地(APP)

      • 微信公众号
      • 微信小程序
      • 安卓APP
      手机浏览,惊喜多多
      匿名树洞,说我想说!
      问答悬赏,VIP可见!
      密码可见,回复可见!
      即时聊天、群聊互动!
      宠物孵化,赠送礼物!
      动态像框,专属头衔!
      挑战/抽奖,金币送不停!
      赶紧体会下,不会让你失望!
    • 实时动态
    • 签到
    • 做任务
    • 发表内容
    • 偏好设置
    • 到底部
    • 帖子间隔 侧栏位置:
    • 还没有账号?点这里立即注册