In order to make you advanced Canvas, I spent 7 hours writing 3 interesting games!!!

Posted by spicey on Wed, 19 Jan 2022 07:09:16 +0100

preface

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

It's 0:15 in the morning. Let's do it 🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍, Write this article while debugging!!!

Greedy snake 🐍

The final effect is as follows:

The implementation steps are divided into the following steps:

  • 1. Draw the snake
  • 2. Let the snake move
  • 3. Random feeding of food
  • 4. Snakes eat food
  • 5. Edge detection and collision detection

1. Draw the snake

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

You can draw squares with CTX FillRect is used to draw the snake head, and the snake body is represented by the array body

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

// js


draw()

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

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

    // Constructor for small squares
    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)
    }

    // Snake constructor
    function Snake(length = 0) {

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

        // Snake body
        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 () {
        // Draw snake head
        this.head.draw()
        // Draw snake body
        for (let i = 0; i < this.body.length; i++) {
            this.body[i].draw()
        }
    }

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

2. Let the snake move

There are two situations when a snake moves:

  • 1. The snake moves to the right by default at the beginning
  • 2. Through the direction key control, move in different directions
    In both cases, the position of a square is moved every second

The principle of making the snake move is actually very simple. Let me take the snake moving to the right as an example:

  • 1. First move the snake's head a square distance to the right, and the snake doesn't move
  • 2. Add a square to the head of the snake
  • 3. Square removal of snake tail
  • 4. Use the timer to cause the snake to move to the right continuously

      Snake.prototype.moveSnake = function () {
          // Put the snake head in the last state and spell it to the head of the snake
          const rect = new Rect(this.head.x, this.head.y, this.head.width, this.head.height, 'yellow')
          this.body.unshift(rect)
    
          this.body.pop()
    
          // Control the coordinates of the snake head according to the direction
          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) {
          // Keyboard events
          e = e || window.event
          // Left 37 upper 38 right 39 lower 40
          switch (e.keyCode) {
              case 37:
                  console.log(37)
                  // Ternary expression to prevent the left from moving to the right. The same is true below (greedy snakes can't turn around directly)
                  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)
      // The default direction is 2, that is, right
      snake.direction = 2
      snake.drawSnake()
    
      function animate() {
          // Empty first
          ctx.clearRect(0, 0, canvas.width, canvas.height)
          // move
          snake.moveSnake()
          // Repaint
          snake.drawSnake()
      }
    
      var timer = setInterval(() => {
          animate()
      }, 100)
    }

    The results are as follows:

3. Put food at random

Put food at random, that is, draw a square on the canvas at random. Pay attention to the following two points:

  • 1. The coordinates should be within the canvas
  • 2. Food can't be thrown on the snake's body or head (it will knock the snake unconscious, hehe)

      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)
              // It's guaranteed to be a multiple of 40
              rect = new Rect(x, y, 40, 40, 'blue')
              // Judge whether the food overlaps with the head and body of the snake
              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)
      // The default direction is 2, that is, right
      snake.direction = 2
      snake.drawSnake()
      // Create random food instances
      var food = randomFood(snake)
      // Draw the food
      food.draw()
    
      function animate() {
          // Empty first
          ctx.clearRect(0, 0, canvas.width, canvas.height)
          // move
          snake.moveSnake()
          // Repaint
          snake.drawSnake()
          food.draw()
      }

    The effect is as follows: the random food is drawn:

4. Snakes eat food

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

  • 1. After eating food, the snake's body should be extended by one space
  • 2. After eating the food, the random food should change its position
const canvas = document.getElementById('canvas')

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

// A variable that defines whether a global eats food
let isEatFood = false
    

    Snake.prototype.moveSnake = function () {
        // Put the snake head in the last state and spell it to the head of the snake
        const rect = new Rect(this.head.x, this.head.y, this.head.width, this.head.height, 'yellow')
        this.body.unshift(rect)

        // Judge whether the snake head overlaps with the food. If it overlaps, it means you have eaten it. If it doesn't overlap, it means you haven't eaten it
        isEatFood = food && this.head.x === food.x && this.head.y === food.y

        // Let's put a square above the head of the snake
        if (!isEatFood) {
            // If you don't eat it, you have to go to the tail, which is equivalent to that the whole snake hasn't grown
            this.body.pop()
        } else {
            // If you eat it, you don't go to the tail, which is equivalent to extending the whole snake by a square

            // And when you eat it, you have to regenerate a random food
            food = randomFood(this)
            food.draw()
            isEatFood = false
        }

        // Control the coordinates of the snake head according to the direction
        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 the border and touch yourself

As we all know, when the snake head touches the border or the snake body, it will terminate the game

    Snake.prototype.drawSnake = function () {
        // If you do
        if (isHit(this)) {
            // Clear timer
            clearInterval(timer)
            const con = confirm(`A total of ${this.body.length - this.length}A food, start over`)
            // Reopen
            if (con) {
                draw()
            }
            return
        }
        // Draw snake head
        this.head.draw()
        // Draw snake body
        for (let i = 0; i < this.body.length; i++) {
            this.body[i].draw()
        }
    }
    
    
    function isHit(snake) {
        const head = snake.head
        // Does it touch the left and right boundaries
        const xLimit = head.x < 0 || head.x >= canvas.width
        // Does it touch the upper and lower boundaries
        const yLimit = head.y < 0 || head.y >= canvas.height
        // Did you hit the snake
        const hitSelf = snake.body.find(({ x, y }) => head.x === x && head.y === y)
        // If one of the three is true, the game ends
        return xLimit || yLimit || hitSelf
    }

Since then, greedy snake 🐍 The game is complete:

6. All codes:

draw()

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

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

    // A variable that defines whether a global eats food
    let isEatFood = false

    // Constructor for small squares
    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)
    }

    // Snake constructor
    function Snake(length = 0) {

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

        // Snake body
        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 you do
        if (isHit(this)) {
            // Clear timer
            clearInterval(timer)
            const con = confirm(`A total of ${this.body.length - this.length}A food, start over`)
            // Reopen
            if (con) {
                draw()
            }
            return
        }
        // Draw snake head
        this.head.draw()
        // Draw snake body
        for (let i = 0; i < this.body.length; i++) {
            this.body[i].draw()
        }
    }

    Snake.prototype.moveSnake = function () {
        // Put the snake head in the last state and spell it to the head of the snake
        const rect = new Rect(this.head.x, this.head.y, this.head.width, this.head.height, 'yellow')
        this.body.unshift(rect)

        // Judge whether the snake head overlaps with the food. If it overlaps, it means you have eaten it. If it doesn't overlap, it means you haven't eaten it
        isEatFood = food && this.head.x === food.x && this.head.y === food.y

        // Let's put a square above the head of the snake
        if (!isEatFood) {
            // If you don't eat it, you have to go to the tail, which is equivalent to that the whole snake hasn't grown
            this.body.pop()
        } else {
            // If you eat it, you don't go to the tail, which is equivalent to extending the whole snake by a square

            // And when you eat it, you have to regenerate a random food
            food = randomFood(this)
            food.draw()
            isEatFood = false
        }

        // Control the coordinates of the snake head according to the direction
        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) {
        // Keyboard events
        e = e || window.event
        // Left 37 upper 38 right 39 lower 40
        switch (e.keyCode) {
            case 37:
                console.log(37)
                // Ternary expression to prevent the left from moving to the right. The same is true below (greedy snakes can't turn around directly)
                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)
            // It's guaranteed to be a multiple of 40
            rect = new Rect(x, y, 40, 40, 'blue')
            // Judge whether the food overlaps with the head and body of the snake
            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
        // Does it touch the left and right boundaries
        const xLimit = head.x < 0 || head.x >= canvas.width
        // Does it touch the upper and lower boundaries
        const yLimit = head.y < 0 || head.y >= canvas.height
        // Did you hit the snake
        const hitSelf = snake.body.find(({ x, y }) => head.x === x && head.y === y)
        // If one of the three is true, the game ends
        return xLimit || yLimit || hitSelf
    }

    const snake = new Snake(3)
    // The default direction is 2, that is, right
    snake.direction = 2
    snake.drawSnake()
    // Create random food instances
    var food = randomFood(snake)
    // Draw the food
    food.draw()

    function animate() {
        // Empty first
        ctx.clearRect(0, 0, canvas.width, canvas.height)
        // move
        snake.moveSnake()
        // Repaint
        snake.drawSnake()
        food.draw()
    }

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

Star connection

The effect is as follows. Isn't it cool, brothers( Background picture You can download it yourself):

This little game can be divided into the following steps:

  • 1. Draw a single little star and make it move
  • 2. Make a hundred little stars
  • 3. Connect when the stars are close
  • 4. Move the mouse 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, after clearing, redrawing the stars and using the timer, you will have the vision of moving. The point to note is: bounce when you hit the boundary.

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

// js

const canvas = document.getElementById('canvas')

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

// Gets the width and height of the current view
let aw = document.documentElement.clientWidth || document.body.clientWidth
let ah = document.documentElement.clientHeight || document.body.clientHeight
// Assign to canvas
canvas.width = aw
canvas.height = ah

// Monitor the real-time width and height when the screen changes
window.onresize = function () {
    aw = document.documentElement.clientWidth || document.body.clientWidth
    ah = document.documentElement.clientHeight || document.body.clientHeight
    // Assign to canvas
    canvas.width = aw
    canvas.height = ah
}

// Whether solid or line, the color tone of this game is white
ctx.fillStyle = 'white'
ctx.strokeStyle = 'white'

function Star(x, y, r) {
    // x. y is the coordinate and r is the radius
    this.x = x
    this.y = y
    this.r = r
    // speed parameter, value between - 3 and 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
    // When you hit the boundary, you just need to reverse the speed
    if (this.x < 0 || this.x > aw) this.speedX *= -1
    if (this.y < 0 || this.y > ah) this.speedY *= -1
}

// Randomly find a coordinate within the canvas and draw a star
const star = new Star(Math.random() * aw, Math.random() * ah, 3)
star

// The movement of stars
setInterval(() => {
    ctx.clearRect(0, 0, aw, ah)
    star.move()
    star.draw()
}, 50)

Achieve the following movement and rebound effects:

2. Draw 100 little stars

Create an array stars to store the stars

const stars = []
for (let i = 0; i < 100; i++) {
    // Randomly find a coordinate within the canvas and draw a star
    stars.push(new Star(Math.random() * aw, Math.random() * ah, 3))
}

// The movement of stars
setInterval(() => {
    ctx.clearRect(0, 0, aw, ah)
    // Traverse mobile rendering
    stars.forEach(star => {
        star.move()
        star.draw()
    })
}, 50)

The effects are as follows:

3. Connect when the stars are close

When the difference between x and y of the two stars is less than 50, the connection is carried out. The connection only needs to use CTX Moveto and CTX Just lineto

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

// The movement of stars
setInterval(() => {
    ctx.clearRect(0, 0, aw, ah)
    // Traverse mobile rendering
    stars.forEach(star => {
        star.move()
        star.draw()
    })
    stars.forEach((star, index) => {
        // Similar to bubble sorting, compare and make sure all stars are compared between two
        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 two forEach cannot be executed together. This is a question worth thinking about, or you can combine it together, try the effect and get it. It's an assignment for everyone!

The effects are as follows:

4. The mouse moves with a small star

That is, the little star will go wherever the mouse goes, and the little star will connect with the little star close to it wherever it goes

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

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

// The movement of stars
setInterval(() => {
    ctx.clearRect(0, 0, aw, ah)
    // Mouse star rendering
    mouseStar.draw()
    // Traverse mobile rendering
    stars.forEach(star => {
        star.move()
        star.draw()
    })
    stars.forEach((star, index) => {
        // Similar to bubble sorting, compare and make sure all stars are compared between two
        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)
            }
        }
        // Judge mouse star connection
        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 effects are as follows:

5. Click the mouse to generate five small stars

The idea is to click the mouse to generate five 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 effects are as follows:

Final effect:

6. All codes

const canvas = document.getElementById('canvas')

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

// Gets the width and height of the current view
let aw = document.documentElement.clientWidth || document.body.clientWidth
let ah = document.documentElement.clientHeight || document.body.clientHeight
// Assign to canvas
canvas.width = aw
canvas.height = ah

// Monitor the real-time width and height when the screen changes
window.onresize = function () {
    aw = document.documentElement.clientWidth || document.body.clientWidth
    ah = document.documentElement.clientHeight || document.body.clientHeight
    // Assign to canvas
    canvas.width = aw
    canvas.height = ah
}

// Whether solid or line, the color tone of this game is white
ctx.fillStyle = 'white'
ctx.strokeStyle = 'white'

function Star(x, y, r) {
    // x. y is the coordinate and r is the radius
    this.x = x
    this.y = y
    this.r = r
    // speed parameter, value between - 3 and 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
    // When you hit the boundary, you just need to reverse the 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++) {
    // Randomly find a coordinate within the canvas and draw a star
    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))
    }
}

// The movement of stars
setInterval(() => {
    ctx.clearRect(0, 0, aw, ah)
    // Mouse star rendering
    mouseStar.draw()
    // Traverse mobile rendering
    stars.forEach(star => {
        star.move()
        star.draw()
    })
    stars.forEach((star, index) => {
        // Similar to bubble sorting, compare and make sure all stars are compared between two
        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

See what will happen:


Gobang is divided into the following steps:

  • 1. Draw a chessboard
  • 2. When black and white chess is switched, it cannot cover the pit
  • 3. Judge whether five children are connected. If so, you win
  • 4. Egg: play chess with AI (realize single player game)

1. Draw a chessboard

It's actually very simple. Use CTX Moveto and CTX Lineto, 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')

    // Draw chessboard

    // Horizontal, 15 lines in total
    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()
    }

    // Vertical, 15 lines in total
    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:

2. Switch between black and white chess

  • 1. Click the event with the mouse to obtain the coordinates and draw the chess (ctx.arc)
  • 2. Ensure that the played chess position cannot be played repeatedly

The first step is to obtain the mouse coordinates, but we should pay attention to one thing. The chess pieces can only be placed at the intersection of the line. Therefore, after obtaining the mouse coordinates, we should do some processing and round them to the nearest line intersection as the center of the circle

The second step, how to ensure that the chess position is not repeated? We can use a two-dimensional array to record. The initial value is 0. After playing black chess, it becomes 1 and after playing white chess, it becomes 2. However, it should be noted that the x and y of the array index are opposite to the x and y of the canvas coordinates, so the coordinates in the following code are reversed. I hope you can think about why.

// Whether to play black chess
    // Black chess go first
    let isBlack = true


    // Checkerboard 2D array
    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
        // Round 40 to make sure the pieces fall at the intersection
        const x = Math.round((clientX - 20) / 40) * 40 + 20
        const y = Math.round((clientY - 20) / 40) * 40 + 20
        // Index of cheeks two-dimensional array
        // It's a little redundant. You can understand it better
        const cheeksX = (x - 20) / 40
        const cheeksY = (y - 20) / 40
        // The corresponding element is not 0, indicating that there is chess in this place. Return
        if (cheeks[cheeksY][cheeksX]) return
        // Black chess is 1 and white chess is 2
        cheeks[cheeksY][cheeksX] = isBlack ? 1 : 2
        ctx.beginPath()
        // Draw a circle
        ctx.arc(x, y, 20, 0, 2 * Math.PI)
        // Judge whether to go black or white
        ctx.fillStyle = isBlack ? 'black' : 'white'
        ctx.fill()
        ctx.closePath()
        // Switch black and white
        isBlack = !isBlack
    }


The effects are as follows:

3. Judge whether it is wulianzi

How to judge? There are four situations: upper and lower five Lianzi, left and right Wu Lianzi, upper left and lower right five Lianzi, upper right and lower left five Lianzi. As long as we judge them all every time we fall.

By the way, all codes are attached

play()

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

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

    // Draw chessboard

    // Horizontal, 15 lines in total
    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()
    }

    // Vertical, 15 lines in total
    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()
    }

    // Do you play black chess
    // Black chess go first
    let isBlack = true


    // Checkerboard 2D array
    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
        // Round 40 to make sure the pieces fall at the intersection
        const x = Math.round((clientX - 20) / 40) * 40 + 20
        const y = Math.round((clientY - 20) / 40) * 40 + 20
        // Index of cheeks two-dimensional array
        // It's a little redundant, so you can understand it better
        const cheeksX = (x - 20) / 40
        const cheeksY = (y - 20) / 40
        // The corresponding element is not 0, indicating that there is chess in this place. Return
        if (cheeks[cheeksY][cheeksX]) return
        // Black chess is 1 and white chess is 2
        cheeks[cheeksY][cheeksX] = isBlack ? 1 : 2
        ctx.beginPath()
        // Draw a circle
        ctx.arc(x, y, 20, 0, 2 * Math.PI)
        // Judge whether to go black or white
        ctx.fillStyle = isBlack ? 'black' : 'white'
        ctx.fill()
        ctx.closePath()

        // canvas drawing is asynchronous, so you can draw it and then test whether you win or lose
        setTimeout(() => {
            if (isWin(cheeksX, cheeksY)) {
                const con = confirm(`${isBlack ? 'black ' : 'White chess'}Win! Start again?`)
                // Restart
                ctx.clearRect(0, 0, 600, 600)
                con && play()
            }
            // Switch black and white
            isBlack = !isBlack
        }, 0)
    }
    // Judge whether five connected children
    function isWin(x, y) {
        const flag = isBlack ? 1 : 2
        // Up and down
        if (up_down(x, y, flag)) {
            return true
        }

        // left and right
        if (left_right(x, y, flag)) {
            return true
        }
        // Upper left and lower right
        if (lu_rd(x, y, flag)) {
            return true
        }

        // Upper right and lower left
        if (ru_ld(x, y, flag)) {
            return true
        }

        return false
    }

    function up_down(x, y, flag) {
        let num = 1
        // Look up
        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
        }
        // Look down
        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
        // Look left
        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
        }
        // Look right
        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
        // Look up to the left
        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
        }
        // Look down to the right
        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
        // Look up to the right
        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
        }
        // Look down to the left
        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. Colored eggs: playing chess with AI

In fact, it is very simple. After playing chess, set a function: randomly find a position to play chess. In this way, the functions of playing chess and single player games with computers are realized. I have realized this function, but I won't write it out. Let's give 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 self-motivated, like the front-end and want to learn from the front-end, we can make friends and fish together. Ha ha, fish school, add me, please note [Si no]

Topics: Javascript Front-end canvas