13
头图

foreword

Hello everyone, I'm Lin Sanxin, I believe you have read my previous article on canvas introduction In order to let her get started with canvas in 10 minutes, I stayed up late and wrote 3 small projects and this article , I already have an entry level to canvas understanding. Today, I wrote three interesting little games with canvas to make you happy. Yes, I only have you in my heart, not her.

image.png

截屏2021-07-25 上午12.15.24.png

It's 0:15 in the morning, let's start 🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍, and write this article while debugging! ! !

greedy snake 🐍

The final effect is as follows:
贪吃蛇.gif
The implementation steps are divided into the following steps:

  • 1. Draw the snake
  • 2. Make the snake move
  • 3. Random food delivery
  • 4. Snakes eat food
  • 5. Edge detection and collision detection

1. Draw the snake

In fact, it is very simple to draw a snake. A snake is composed of a snake head and a snake body. In fact, it can be represented by a square. A snake head is a square, and a snake body can be many squares.

The drawing square can ctx.fillRect , the snake head is head , and the snake body is represented array body
截屏2021-07-24 下午10.43.46.png

// html
<canvas id="canvas" width="800" height="800"></canvas>

// js


draw()

function draw() {
    const canvas = document.getElementById('canvas')

    const ctx = canvas.getContext('2d')

    // 小方格的构造函数
    function Rect(x, y, width, height, color) {
        this.x = x
        this.y = y
        this.width = width
        this.height = height
        this.color = color
    }

    Rect.prototype.draw = function () {
        ctx.beginPath()
        ctx.fillStyle = this.color
        ctx.fillRect(this.x, this.y, this.width, this.height)
        ctx.strokeRect(this.x, this.y, this.width, this.height)
    }

    // 蛇的构造函数
    function Snake(length = 0) {

        this.length = length
        // 蛇头
        this.head = new Rect(canvas.width / 2, canvas.height / 2, 40, 40, 'red')

        // 蛇身
        this.body = []

        let x = this.head.x - 40
        let y = this.head.y

        for (let i = 0; i < this.length; i++) {
            const rect = new Rect(x, y, 40, 40, 'yellow')
            this.body.push(rect)
            x -= 40
        }
    }

    Snake.prototype.drawSnake = function () {
        // 绘制蛇头
        this.head.draw()
        // 绘制蛇身
        for (let i = 0; i < this.body.length; i++) {
            this.body[i].draw()
        }
    }

    const snake = new Snake(3)
    snake.drawSnake()
}

2. Make the snake move

There are two situations in which the snake moves:

  • 1. The snake will move to the right by default at the beginning
  • 2. Controlled by the arrow keys to move in different directions
    Both cases move one square per second

Let the snake move, in fact, the principle is very simple, let me take the snake moving to the right as an example:
截屏2021-07-24 下午10.57.06.png

  • 1. The snake head first moves a square distance to the right, and the snake body does not move
  • 2. Add a square to the head of the snake body
  • the squares at the tail of the snake body 161ef3d89e1565
  • 4. Use the timer to create the vision that the snake keeps moving to the right

      Snake.prototype.moveSnake = function () {
          // 将蛇头上一次状态,拼到蛇身首部
          const rect = new Rect(this.head.x, this.head.y, this.head.width, this.head.height, 'yellow')
          this.body.unshift(rect)
    
          this.body.pop()
    
          // 根据方向,控制蛇头的坐标
          switch (this.direction) {
              case 0:
                  this.head.x -= this.head.width
                  break
              case 1:
                  this.head.y -= this.head.height
                  break
              case 2:
                  this.head.x += this.head.width
                  break
              case 3:
                  this.head.y += this.head.height
                  break
          }
      }
    
      document.onkeydown = function (e) {
          // 键盘事件
          e = e || window.event
          // 左37  上38  右39  下40
          switch (e.keyCode) {
              case 37:
                  console.log(37)
                  // 三元表达式,防止右移动时按左,下面同理(贪吃蛇可不能直接掉头)
                  snake.direction = snake.direction === 2 ? 2 : 0
                  snake.moveSnake()
                  break
              case 38:
                  console.log(38)
                  snake.direction = snake.direction === 3 ? 3 : 1
                  break
              case 39:
                  console.log(39)
                  snake.direction = snake.direction === 0 ? 0 : 2
                  break
              case 40:
                  console.log(40)
                  snake.direction = snake.direction === 1 ? 1 : 3
                  break
    
          }
      }
    
      const snake = new Snake(3)
      // 默认direction为2,也就是右
      snake.direction = 2
      snake.drawSnake()
    
      function animate() {
          // 先清空
          ctx.clearRect(0, 0, canvas.width, canvas.height)
          // 移动
          snake.moveSnake()
          // 再画
          snake.drawSnake()
      }
    
      var timer = setInterval(() => {
          animate()
      }, 100)
    }

    The effect is as follows:

蛇动起来.gif

3. Random food delivery

Randomly placing food, that is, drawing a square randomly on the canvas, pay attention to the following two points:

  • 1. The coordinates should be within the range of
  • 2. Food snake's body or head (this will smash the snake into a daze)

      function randomFood(snake) {
          let isInSnake = true
          let rect
          while (isInSnake) {
              const x = Math.round(Math.random() * (canvas.width - 40) / 40) * 40
              const y = Math.round(Math.random() * (canvas.height - 40) / 40) * 40
              console.log(x, y)
              // 保证是40的倍数啊
              rect = new Rect(x, y, 40, 40, 'blue')
              // 判断食物是否与蛇头蛇身重叠
              if ((snake.head.x === x && snake.head.y === y) || snake.body.find(item => item.x === x && item.y === y)) {
                  isInSnake = true
                  continue
              } else {
                  isInSnake = false
              }
          }
          return rect
      }
    
      const snake = new Snake(3)
      // 默认direction为2,也就是右
      snake.direction = 2
      snake.drawSnake()
      // 创建随机食物实例
      var food = randomFood(snake)
      // 画出食物
      food.draw()
    
      function animate() {
          // 先清空
          ctx.clearRect(0, 0, canvas.width, canvas.height)
          // 移动
          snake.moveSnake()
          // 再画
          snake.drawSnake()
          food.draw()
      }

    The effect is as follows, the random food is drawn:
    截屏2021-07-24 下午11.17.03.png

4. Snakes eat food

In fact, the snake eats food, it is very simple to understand, that is, when the snake head moves to coordinates of the food, even if it eats food, pay attention to two points:

  • 1. After eating the food, the snake body extend a space
  • 2. After eating the food, the random food change position
const canvas = document.getElementById('canvas')

const ctx = canvas.getContext('2d')

// 定义一个全局的是否吃到食物的一个变量
let isEatFood = false
    

    Snake.prototype.moveSnake = function () {
        // 将蛇头上一次状态,拼到蛇身首部
        const rect = new Rect(this.head.x, this.head.y, this.head.width, this.head.height, 'yellow')
        this.body.unshift(rect)

        // 判断蛇头是否与食物重叠,重叠就是吃到了,没重叠就是没吃到
        isEatFood = food && this.head.x === food.x && this.head.y === food.y

        // 咱们上面在蛇身首部插入方格
        if (!isEatFood) {
            // 没吃到就要去尾,相当于整条蛇没变长
            this.body.pop()
        } else {
            // 吃到了就不去尾,相当于整条蛇延长一个方格

            // 并且吃到了,就要重新生成一个随机食物
            food = randomFood(this)
            food.draw()
            isEatFood = false
        }

        // 根据方向,控制蛇头的坐标
        switch (this.direction) {
            case 0:
                this.head.x -= this.head.width
                break
            case 1:
                this.head.y -= this.head.height
                break
            case 2:
                this.head.x += this.head.width
                break
            case 3:
                this.head.y += this.head.height
                break
        }
    }

5. Touch boundaries and touch yourself

As we all know, if the snake head touches the boundary, or touches the snake body, the game will be terminated.

    Snake.prototype.drawSnake = function () {
        // 如果碰到了
        if (isHit(this)) {
            // 清除定时器
            clearInterval(timer)
            const con = confirm(`总共吃了${this.body.length - this.length}个食物,重新开始吗`)
            // 是否重开
            if (con) {
                draw()
            }
            return
        }
        // 绘制蛇头
        this.head.draw()
        // 绘制蛇身
        for (let i = 0; i < this.body.length; i++) {
            this.body[i].draw()
        }
    }
    
    
    function isHit(snake) {
        const head = snake.head
        // 是否碰到左右边界
        const xLimit = head.x < 0 || head.x >= canvas.width
        // 是否碰到上下边界
        const yLimit = head.y < 0 || head.y >= canvas.height
        // 是否撞到蛇身
        const hitSelf = snake.body.find(({ x, y }) => head.x === x && head.y === y)
        // 三者其中一个为true则游戏结束
        return xLimit || yLimit || hitSelf
    }

Since then, the Snake 🐍 mini-game has been completed:
贪吃蛇.gif

6. All code:


draw()

function draw() {
    const canvas = document.getElementById('canvas')

    const ctx = canvas.getContext('2d')

    // 定义一个全局的是否吃到食物的一个变量
    let isEatFood = false

    // 小方格的构造函数
    function Rect(x, y, width, height, color) {
        this.x = x
        this.y = y
        this.width = width
        this.height = height
        this.color = color
    }

    Rect.prototype.draw = function () {
        ctx.beginPath()
        ctx.fillStyle = this.color
        ctx.fillRect(this.x, this.y, this.width, this.height)
        ctx.strokeRect(this.x, this.y, this.width, this.height)
    }

    // 蛇的构造函数
    function Snake(length = 0) {

        this.length = length
        // 蛇头
        this.head = new Rect(canvas.width / 2, canvas.height / 2, 40, 40, 'red')

        // 蛇身
        this.body = []

        let x = this.head.x - 40
        let y = this.head.y

        for (let i = 0; i < this.length; i++) {
            const rect = new Rect(x, y, 40, 40, 'yellow')
            this.body.push(rect)
            x -= 40
        }
    }

    Snake.prototype.drawSnake = function () {
        // 如果碰到了
        if (isHit(this)) {
            // 清除定时器
            clearInterval(timer)
            const con = confirm(`总共吃了${this.body.length - this.length}个食物,重新开始吗`)
            // 是否重开
            if (con) {
                draw()
            }
            return
        }
        // 绘制蛇头
        this.head.draw()
        // 绘制蛇身
        for (let i = 0; i < this.body.length; i++) {
            this.body[i].draw()
        }
    }

    Snake.prototype.moveSnake = function () {
        // 将蛇头上一次状态,拼到蛇身首部
        const rect = new Rect(this.head.x, this.head.y, this.head.width, this.head.height, 'yellow')
        this.body.unshift(rect)

        // 判断蛇头是否与食物重叠,重叠就是吃到了,没重叠就是没吃到
        isEatFood = food && this.head.x === food.x && this.head.y === food.y

        // 咱们上面在蛇身首部插入方格
        if (!isEatFood) {
            // 没吃到就要去尾,相当于整条蛇没变长
            this.body.pop()
        } else {
            // 吃到了就不去尾,相当于整条蛇延长一个方格

            // 并且吃到了,就要重新生成一个随机食物
            food = randomFood(this)
            food.draw()
            isEatFood = false
        }

        // 根据方向,控制蛇头的坐标
        switch (this.direction) {
            case 0:
                this.head.x -= this.head.width
                break
            case 1:
                this.head.y -= this.head.height
                break
            case 2:
                this.head.x += this.head.width
                break
            case 3:
                this.head.y += this.head.height
                break
        }
    }

    document.onkeydown = function (e) {
        // 键盘事件
        e = e || window.event
        // 左37  上38  右39  下40
        switch (e.keyCode) {
            case 37:
                console.log(37)
                // 三元表达式,防止右移动时按左,下面同理(贪吃蛇可不能直接掉头)
                snake.direction = snake.direction === 2 ? 2 : 0
                snake.moveSnake()
                break
            case 38:
                console.log(38)
                snake.direction = snake.direction === 3 ? 3 : 1
                break
            case 39:
                console.log(39)
                snake.direction = snake.direction === 0 ? 0 : 2
                break
            case 40:
                console.log(40)
                snake.direction = snake.direction === 1 ? 1 : 3
                break

        }
    }

    function randomFood(snake) {
        let isInSnake = true
        let rect
        while (isInSnake) {
            const x = Math.round(Math.random() * (canvas.width - 40) / 40) * 40
            const y = Math.round(Math.random() * (canvas.height - 40) / 40) * 40
            console.log(x, y)
            // 保证是40的倍数啊
            rect = new Rect(x, y, 40, 40, 'blue')
            // 判断食物是否与蛇头蛇身重叠
            if ((snake.head.x === x && snake.head.y === y) || snake.body.find(item => item.x === x && item.y === y)) {
                isInSnake = true
                continue
            } else {
                isInSnake = false
            }
        }
        return rect
    }

    function isHit(snake) {
        const head = snake.head
        // 是否碰到左右边界
        const xLimit = head.x < 0 || head.x >= canvas.width
        // 是否碰到上下边界
        const yLimit = head.y < 0 || head.y >= canvas.height
        // 是否撞到蛇身
        const hitSelf = snake.body.find(({ x, y }) => head.x === x && head.y === y)
        // 三者其中一个为true则游戏结束
        return xLimit || yLimit || hitSelf
    }

    const snake = new Snake(3)
    // 默认direction为2,也就是右
    snake.direction = 2
    snake.drawSnake()
    // 创建随机食物实例
    var food = randomFood(snake)
    // 画出食物
    food.draw()

    function animate() {
        // 先清空
        ctx.clearRect(0, 0, canvas.width, canvas.height)
        // 移动
        snake.moveSnake()
        // 再画
        snake.drawSnake()
        food.draw()
    }

    var timer = setInterval(() => {
        animate()
    }, 100)
}

star connection

The effect is as follows, isn't it cool, brothers ( background picture can be downloaded by yourself):

星星连线.gif

This little game can be divided into the following steps:

  • 1. Draw a single little star and move it
  • 2, create one hundred little stars
  • 3. When the stars are close, connect
  • 4. Move the to generate small stars
  • 5. Click the mouse to generate 5 small stars

1. Draw a single little star and make it move

In fact, moving the stars is very simple, that is, redrawing the stars after clearing, and using the timer, there will be a moving vision. Note that: hits the border.

// html
<style>
    #canvas {
            background: url(./光能使者.jpg) 0 0/cover no-repeat;
        }
</style>
<canvas id="canvas"></canvas>

// js

const canvas = document.getElementById('canvas')

const ctx = canvas.getContext('2d')

// 获取当前视图的宽度和高度
let aw = document.documentElement.clientWidth || document.body.clientWidth
let ah = document.documentElement.clientHeight || document.body.clientHeight
// 赋值给canvas
canvas.width = aw
canvas.height = ah

// 屏幕变动时也要监听实时宽高
window.onresize = function () {
    aw = document.documentElement.clientWidth || document.body.clientWidth
    ah = document.documentElement.clientHeight || document.body.clientHeight
    // 赋值给canvas
    canvas.width = aw
    canvas.height = ah
}

// 本游戏无论是实心,还是线条,色调都是白色
ctx.fillStyle = 'white'
ctx.strokeStyle = 'white'

function Star(x, y, r) {
    // x,y是坐标,r是半径
    this.x = x
    this.y = y
    this.r = r
    // speed参数,在  -3 ~ 3 之间取值
    this.speedX = (Math.random() * 3) * Math.pow(-1, Math.round(Math.random()))
    this.speedY = (Math.random() * 3) * Math.pow(-1, Math.round(Math.random()))
}

Star.prototype.draw = function () {
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2)
    ctx.fill()
    ctx.closePath()
}

Star.prototype.move = function () {
    this.x -= this.speedX
    this.y -= this.speedY
    // 碰到边界时,反弹,只需要把speed取反就行
    if (this.x < 0 || this.x > aw) this.speedX *= -1
    if (this.y < 0 || this.y > ah) this.speedY *= -1
}

// 随机在canvas范围内找一个坐标画星星
const star = new Star(Math.random() * aw, Math.random() * ah, 3)
star

// 星星的移动
setInterval(() => {
    ctx.clearRect(0, 0, aw, ah)
    star.move()
    star.draw()
}, 50)

To achieve the following movement and rebound:

星星反弹.gif

2. Draw 100 little stars

Create a array stars to store these stars

const stars = []
for (let i = 0; i < 100; i++) {
    // 随机在canvas范围内找一个坐标画星星
    stars.push(new Star(Math.random() * aw, Math.random() * ah, 3))
}

// 星星的移动
setInterval(() => {
    ctx.clearRect(0, 0, aw, ah)
    // 遍历移动渲染
    stars.forEach(star => {
        star.move()
        star.draw()
    })
}, 50)

The effect is as follows:

100个星星.gif

3. When the stars are close to each other, make a connection

When the difference between the x and y of the two stars is less than 50, the connection is made. The connection only needs to use ctx.moveTo and ctx.lineTo.

function drawLine(startX, startY, endX, endY) {
    ctx.beginPath()
    ctx.moveTo(startX, startY)
    ctx.lineTo(endX, endY)
    ctx.stroke()
    ctx.closePath()
}

// 星星的移动
setInterval(() => {
    ctx.clearRect(0, 0, aw, ah)
    // 遍历移动渲染
    stars.forEach(star => {
        star.move()
        star.draw()
    })
    stars.forEach((star, index) => {
        // 类似于冒泡排序那样,去比较,确保所有星星两两之间都比较到
        for (let i = index + 1; i < stars.length; i++) {
            if (Math.abs(star.x - stars[i].x) < 50 && Math.abs(star.y - stars[i].y) < 50) {
                drawLine(star.x, star.y, stars[i].x, stars[i].y)
            }
        }
    })
}, 50)

You can think about why the two forEach cannot be executed together. This is a question worth thinking about, or you can combine them to execute, try the effect, and you will understand when you get it. It's a homework for you all!

The effect is as follows:

连线星星.gif

4. The mouse moves with a small star

, the little star will go there, and wherever the little star goes, it will connect with the close little star 161ef3d89e1aee

const mouseStar = new Star(0, 0, 3)

canvas.onmousemove = function (e) {
    mouseStar.x = e.clientX
    mouseStar.y = e.clientY
}

// 星星的移动
setInterval(() => {
    ctx.clearRect(0, 0, aw, ah)
    // 鼠标星星渲染
    mouseStar.draw()
    // 遍历移动渲染
    stars.forEach(star => {
        star.move()
        star.draw()
    })
    stars.forEach((star, index) => {
        // 类似于冒泡排序那样,去比较,确保所有星星两两之间都比较到
        for (let i = index + 1; i < stars.length; i++) {
            if (Math.abs(star.x - stars[i].x) < 50 && Math.abs(star.y - stars[i].y) < 50) {
                drawLine(star.x, star.y, stars[i].x, stars[i].y)
            }
        }
        // 判断鼠标星星连线
        if (Math.abs(mouseStar.x - star.x) < 50 && Math.abs(mouseStar.y - star.y) < 50) {
            drawLine(mouseStar.x, mouseStar.y, star.x, star.y)
        }
    })
}, 50)

The effect is as follows:

鼠标星星.gif

5. Click the mouse to generate five small stars

The idea is to click the mouse to generate 5 small stars and add them to the array stars

window.onclick = function (e) {
    for (let i = 0; i < 5; i++) {
        stars.push(new Star(e.clientX, e.clientY, 3))
    }
}

The effect is as follows:

点击生成星星.gif

final effect:
星星连线.gif

6. All code

const canvas = document.getElementById('canvas')

const ctx = canvas.getContext('2d')

// 获取当前视图的宽度和高度
let aw = document.documentElement.clientWidth || document.body.clientWidth
let ah = document.documentElement.clientHeight || document.body.clientHeight
// 赋值给canvas
canvas.width = aw
canvas.height = ah

// 屏幕变动时也要监听实时宽高
window.onresize = function () {
    aw = document.documentElement.clientWidth || document.body.clientWidth
    ah = document.documentElement.clientHeight || document.body.clientHeight
    // 赋值给canvas
    canvas.width = aw
    canvas.height = ah
}

// 本游戏无论是实心,还是线条,色调都是白色
ctx.fillStyle = 'white'
ctx.strokeStyle = 'white'

function Star(x, y, r) {
    // x,y是坐标,r是半径
    this.x = x
    this.y = y
    this.r = r
    // speed参数,在  -3 ~ 3 之间取值
    this.speedX = (Math.random() * 3) * Math.pow(-1, Math.round(Math.random()))
    this.speedY = (Math.random() * 3) * Math.pow(-1, Math.round(Math.random()))
}

Star.prototype.draw = function () {
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2)
    ctx.fill()
    ctx.closePath()
}

Star.prototype.move = function () {
    this.x -= this.speedX
    this.y -= this.speedY
    // 碰到边界时,反弹,只需要把speed取反就行
    if (this.x < 0 || this.x > aw) this.speedX *= -1
    if (this.y < 0 || this.y > ah) this.speedY *= -1
}

function drawLine(startX, startY, endX, endY) {
    ctx.beginPath()
    ctx.moveTo(startX, startY)
    ctx.lineTo(endX, endY)
    ctx.stroke()
    ctx.closePath()
}

const stars = []
for (let i = 0; i < 100; i++) {
    // 随机在canvas范围内找一个坐标画星星
    stars.push(new Star(Math.random() * aw, Math.random() * ah, 3))
}

const mouseStar = new Star(0, 0, 3)

canvas.onmousemove = function (e) {
    mouseStar.x = e.clientX
    mouseStar.y = e.clientY
}
window.onclick = function (e) {
    for (let i = 0; i < 5; i++) {
        stars.push(new Star(e.clientX, e.clientY, 3))
    }
}

// 星星的移动
setInterval(() => {
    ctx.clearRect(0, 0, aw, ah)
    // 鼠标星星渲染
    mouseStar.draw()
    // 遍历移动渲染
    stars.forEach(star => {
        star.move()
        star.draw()
    })
    stars.forEach((star, index) => {
        // 类似于冒泡排序那样,去比较,确保所有星星两两之间都比较到
        for (let i = index + 1; i < stars.length; i++) {
            if (Math.abs(star.x - stars[i].x) < 50 && Math.abs(star.y - stars[i].y) < 50) {
                drawLine(star.x, star.y, stars[i].x, stars[i].y)
            }
        }

        if (Math.abs(mouseStar.x - star.x) < 50 && Math.abs(mouseStar.y - star.y) < 50) {
            drawLine(mouseStar.x, mouseStar.y, star.x, star.y)
        }
    })
}, 50)

3. Gobang

Take a look at what will be achieved:

截屏2021-07-25 下午12.21.39.png
Gobang is divided into the following steps:

  • 1. Draw a chessboard
  • 2. Othello is switching, cannot cover the pit that has been played
  • 3. Determine whether the five consecutive sons, if yes, win
  • egg: play chess with 161ef3d89e1ca4 AI (achieve single player game)

1. Draw the chessboard

It's actually very simple. Use ctx.moveTo and ctx.lineTo to draw 15 lines horizontally and 15 lines vertically, and it's OK.

// html
#canvas {
            background: #e3cdb0;
        }
<canvas id="canvas" width="600" height="600"></canvas>


// js
play()

function play() {
    const canvas = document.getElementById('canvas')

    const ctx = canvas.getContext('2d')

    // 绘制棋盘

    // 水平,总共15条线
    for (let i = 0; i < 15; i++) {
        ctx.beginPath()
        ctx.moveTo(20, 20 + i * 40)
        ctx.lineTo(580, 20 + i * 40)
        ctx.stroke()
        ctx.closePath()
    }

    // 垂直,总共15条线
    for (let i = 0; i < 15; i++) {
        ctx.beginPath()
        ctx.moveTo(20 + i * 40, 20)
        ctx.lineTo(20 + i * 40, 580)
        ctx.stroke()
        ctx.closePath()
    }
}

This draws the chessboard:

截屏2021-07-25 下午12.25.09.png

2. Othello switches

  • 1. The mouse click event, get the coordinates, and draw the chess ( ctx.arc )
  • 2. Make sure that the chess positions that have been played cannot be played repeatedly

The first step in obtaining mouse coordinates, but we have to pay attention to one thing at a pawn only online intersection, so get the mouse coordinates, to do what treatment, rounded off to recent line crossing point circle center

The second step, how to ensure that the chess position is not repeated? We can use a 161ef3d89e1d90 two-dimensional array a white chess, it becomes 2, but here is a point to note, the x and y of the 161ef3d89e1d92 array index are the coordinates of the canvas. x, y are opposite, so the coordinates in the following code are reversed, I hope everyone can think about why.

截屏2021-07-25 下午12.33.29.png

// 是否下黑棋
    // 黑棋先走
    let isBlack = true


    // 棋盘二维数组
    let cheeks = []

    for (let i = 0; i < 15; i++) {
        cheeks[i] = new Array(15).fill(0)
    }

    canvas.onclick = function (e) {
        const clientX = e.clientX
        const clientY = e.clientY
        // 对40进行取整,确保棋子落在交叉处
        const x = Math.round((clientX - 20) / 40) * 40 + 20
        const y = Math.round((clientY - 20) / 40) * 40 + 20
        // cheeks二维数组的索引
        // 这么写有点冗余,这么写你们好理解一点
        const cheeksX = (x - 20) / 40
        const cheeksY = (y - 20) / 40
        // 对应元素不为0说明此地方已有棋,返回
        if (cheeks[cheeksY][cheeksX]) return
        // 黑棋为1,白棋为2
        cheeks[cheeksY][cheeksX] = isBlack ? 1 : 2
        ctx.beginPath()
        // 画圆
        ctx.arc(x, y, 20, 0, 2 * Math.PI)
        // 判断走黑还是白
        ctx.fillStyle = isBlack ? 'black' : 'white'
        ctx.fill()
        ctx.closePath()
        // 切换黑白
        isBlack = !isBlack
    }

下棋.gif
The effect is as follows:

3. Determine whether Wulianzi is

How to judge? There are four kinds of situations: , the upper and lower five joints, the left and right Wu joints, the upper left and the lower right five joints, the upper right and the lower left five joints, as long as we judge all of them once every time we make a move.

截屏2021-07-25 下午12.55.53.png

Attach all the code by the way

play()

function play() {
    const canvas = document.getElementById('canvas')

    const ctx = canvas.getContext('2d')

    // 绘制棋盘

    // 水平,总共15条线
    for (let i = 0; i < 15; i++) {
        ctx.beginPath()
        ctx.moveTo(20, 20 + i * 40)
        ctx.lineTo(580, 20 + i * 40)
        ctx.stroke()
        ctx.closePath()
    }

    // 垂直,总共15条线
    for (let i = 0; i < 15; i++) {
        ctx.beginPath()
        ctx.moveTo(20 + i * 40, 20)
        ctx.lineTo(20 + i * 40, 580)
        ctx.stroke()
        ctx.closePath()
    }

    // 是否下黑棋
    // 黑棋先走
    let isBlack = true


    // 棋盘二维数组
    let cheeks = []

    for (let i = 0; i < 15; i++) {
        cheeks[i] = new Array(15).fill(0)
    }

    canvas.onclick = function (e) {
        const clientX = e.clientX
        const clientY = e.clientY
        // 对40进行取整,确保棋子落在交叉处
        const x = Math.round((clientX - 20) / 40) * 40 + 20
        const y = Math.round((clientY - 20) / 40) * 40 + 20
        // cheeks二维数组的索引
        // 这么写有点冗余,这么写你们好理解一点
        const cheeksX = (x - 20) / 40
        const cheeksY = (y - 20) / 40
        // 对应元素不为0说明此地方已有棋,返回
        if (cheeks[cheeksY][cheeksX]) return
        // 黑棋为1,白棋为2
        cheeks[cheeksY][cheeksX] = isBlack ? 1 : 2
        ctx.beginPath()
        // 画圆
        ctx.arc(x, y, 20, 0, 2 * Math.PI)
        // 判断走黑还是白
        ctx.fillStyle = isBlack ? 'black' : 'white'
        ctx.fill()
        ctx.closePath()

        // canvas画图是异步的,保证画出来再去检测输赢
        setTimeout(() => {
            if (isWin(cheeksX, cheeksY)) {
                const con = confirm(`${isBlack ? '黑棋' : '白棋'}赢了!是否重新开局?`)
                // 重新开局
                ctx.clearRect(0, 0, 600, 600)
                con && play()
            }
            // 切换黑白
            isBlack = !isBlack
        }, 0)
    }
    // 判断是否五连子
    function isWin(x, y) {
        const flag = isBlack ? 1 : 2
        // 上和下
        if (up_down(x, y, flag)) {
            return true
        }

        // 左和右
        if (left_right(x, y, flag)) {
            return true
        }
        // 左上和右下
        if (lu_rd(x, y, flag)) {
            return true
        }

        // 右上和左下
        if (ru_ld(x, y, flag)) {
            return true
        }

        return false
    }

    function up_down(x, y, flag) {
        let num = 1
        // 向上找
        for (let i = 1; i < 5; i++) {
            let tempY = y - i
            console.log(x, tempY)
            if (tempY < 0 || cheeks[tempY][x] !== flag) break
            if (cheeks[tempY][x] === flag) num += 1
        }
        // 向下找
        for (let i = 1; i < 5; i++) {
            let tempY = y + i
            console.log(x, tempY)
            if (tempY > 14 || cheeks[tempY][x] !== flag) break
            if (cheeks[tempY][x] === flag) num += 1
        }
        return num >= 5
    }

    function left_right(x, y, flag) {
        let num = 1
        // 向左找
        for (let i = 1; i < 5; i++) {
            let tempX = x - i
            if (tempX < 0 || cheeks[y][tempX] !== flag) break
            if (cheeks[y][tempX] === flag) num += 1
        }
        // 向右找
        for (let i = 1; i < 5; i++) {
            let tempX = x + i
            if (tempX > 14 || cheeks[y][tempX] !== flag) break
            if (cheeks[y][tempX] === flag) num += 1
        }
        return num >= 5

    }

    function lu_rd(x, y, flag) {
        let num = 1
        // 向左上找
        for (let i = 1; i < 5; i++) {
            let tempX = x - i
            let tempY = y - i
            if (tempX < 0 || tempY < 0 || cheeks[tempY][tempX] !== flag) break
            if (cheeks[tempY][tempX] === flag) num += 1
        }
        // 向右下找
        for (let i = 1; i < 5; i++) {
            let tempX = x + i
            let tempY = y + i
            if (tempX > 14 || tempY > 14 || cheeks[tempY][tempX] !== flag) break
            if (cheeks[tempY][tempX] === flag) num += 1
        }

        return num >= 5
    }

    function ru_ld(x, y, flag) {
        let num = 1
        // 向右上找
        for (let i = 1; i < 5; i++) {
            let tempX = x - i
            let tempY = y + i
            if (tempX < 0 || tempY > 14 || cheeks[tempY][tempX] !== flag) break
            if (cheeks[tempY][tempX] === flag) num += 1
        }
        // 向左下找
        for (let i = 1; i < 5; i++) {
            let tempX = x + i
            let tempY = y - i
            if (tempX > 14 || tempY < 0 || cheeks[tempY][tempX] !== flag) break
            if (cheeks[tempY][tempX] === flag) num += 1
        }

        return num >= 5
    }

}

4. Easter eggs: play chess with AI

In fact, it is very simple. Every time you play chess, set a function: find a random position to play chess. In this way, the function of playing chess with the computer and playing single-player games is realized. I have already realized this function, but I will not write it out. I will leave it to you as your homework to consolidate this article. Ha ha ha ha

Epilogue

I'm Lin Sanxin, an enthusiastic front-end rookie programmer. If you are motivated, like the front-end, and want to learn the front-end, then we can make friends and fish together haha, touch the fish group, add me, please note [Si No]

image.png


Sunshine_Lin
2.1k 声望7.1k 粉丝