首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

为了让你们进阶Canvas,我花7小时写了3个有趣的小游戏!!!

  • 21-07-26 07:24
  • 1724
  • 16100
juejin.cn

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」

前言

大家好,我是林三心,相信大家看了我前一篇canvas入门文章为了让她10分钟入门canvas,我熬夜写了3个小项目和这篇文章,对canvas已经有了入门级的了解。今天,我又用canvas写了三个有趣的小游戏,来哄你们开心,没错,我的心里只有你们,没有她。

image.png

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

现在是凌晨0点15分,咱们开搞??????????,一边调试一边把这篇文章写了!!!

贪吃蛇?

最终效果如下: 贪吃蛇.gif 实现步骤分为以下几步:

  • 1、把蛇画出来
  • 2、让蛇动起来
  • 3、随机投放食物
  • 4、蛇吃食物
  • 5、边缘检测与撞自己检测

1. 把蛇画出来

其实画蛇很简单,蛇就是由蛇头和蛇身组成,而其实都可以用正方格来表示,蛇头就是一个方格,而蛇身可以是很多个方格

画方格可以用ctx.fillRect来画,蛇头使用head表示,而蛇身使用数组body来表示 截屏2021-07-24 下午10.43.46.png

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

// 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. 让蛇动起来

蛇动起来有两种情况:

  • 1、蛇一开始就会默认向右移动
  • 2、通过方向键控制,往不同方向移动 这两种情况每秒都是移动一个方格的位置

让蛇动起来,其实原理很简单,我就以蛇向右移动来举例子吧: 截屏2021-07-24 下午10.57.06.png

  • 1、蛇头先右移一个方格距离,蛇身不动
  • 2、蛇身首部加一个方格
  • 3、蛇身尾部的方格去除
  • 4、利用定时器,造成蛇不断向右移动的视觉
    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)
}
复制代码

实现效果如下:

蛇动起来.gif

3. 随机投放食物

随机投放食物,也就是在画布中随机画一个方格,要注意以下两点:

  • 1、坐标要在画布范围内
  • 2、食物不能投到蛇身或者蛇头上(这样会把蛇砸晕的嘿嘿)
    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()
    }
复制代码

效果如下,随机食物画出来了: 截屏2021-07-24 下午11.17.03.png

4. 蛇吃食物

其实蛇吃食物,很简单理解,也就是蛇头移动到跟食物的坐标重叠时,就算是吃到食物了,注意两点:

  • 1、吃到食物后,蛇身要延长一个空格
  • 2、吃到食物后,随机食物要变换位置
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. 碰边界与碰自己

众所周知,蛇头碰到边界,或者碰到蛇身,都会终止游戏

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

自此,贪吃蛇?小游戏完成喽: 贪吃蛇.gif

6. 全部代码:


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

星星连线

效果如下,是不是很酷炫呢,兄弟们(背景图片 可以自己去下载一下):

星星连线.gif

这个小游戏可分为以下几步:

  • 1、画出单个小星星并使他移动
  • 2、造出一百个小星星
  • 3、星星之间靠近时,进行连线
  • 4、鼠标移动生成小星星
  • 5、鼠标点击产生5个小星星

1. 画出单个小星星,并使他移动

其实移动星星很简单,就是清除后重新绘制星星,并利用定时器,就会有移动的视觉了。注意点在于:碰到边界要反弹。

// html

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

达到以下移动以及反弹的效果:

星星反弹.gif

2、画100个小星星

创建一个数组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)
复制代码

效果如下:

100个星星.gif

3. 星星之间靠近时,进行连线

当两个星星的x和y相差都小于50时,就进行连线,连线只需要使用ctx.moveTo和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)
复制代码

大家可以想一想,为什么两个forEach不能何在一起去执行。这是个值得思考的问题,或者大家可以合并在一起执行,试试效果,获取就懂了。算是给大家留的一个作业哈!

效果如下:

连线星星.gif

4.鼠标移动时带着小星星

也就是鼠标到哪,那个小星星就到哪,并且这个小星星走到哪都会跟距离近的小星星连线

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

效果如下:

鼠标星星.gif

5. 鼠标点击生成五个小星星

思路就是,鼠标点击,生成5个小星星,并加到数组stars中

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

效果如下:

点击生成星星.gif

最终效果: 星星连线.gif

6. 全部代码

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. 五子棋

看看将实现的效果:

截屏2021-07-25 下午12.21.39.png 五子棋分为以下步骤:

  • 1、画出棋盘
  • 2、黑白棋切换着下,不能覆盖已下的坑位
  • 3、判断是否五连子,是的话就赢了
  • 4、彩蛋:跟AI下棋(实现单人玩游戏)

1. 画出棋盘

其实很简单,利用ctx.moveTo和ctx.lineTo,横着画15条线,竖着画15条线,就OK了。

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


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

这样就画出了棋盘:

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

2. 黑白棋切换着下

  • 1、鼠标点击事件,获取坐标,将棋画出来(ctx.arc)
  • 2、确保已下的棋位不能重复下

第一步,获取鼠标坐标,但是我们要注意一件事,棋子只能下在线的交叉处,所以拿到鼠标坐标后,要做一下处理,四舍五入,以最近的一个线交叉点为圆的圆心

第二步,如何确保棋位不重复下呢?咱们可以使用一个二维数组来记录,初始是0,下过黑棋就变为1,下过白棋就变为2,但是这里要注意一点,数组索引的x,y跟画布坐标的x,y是相反的,所以后面代码里坐标反过来,希望大家能思考一下为啥。

截屏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 效果如下:

3. 判断是否五连子

如何判断呢?有四种情况:上下五连子,左右吴连子,左上右下五连子,右上左下五连子,只要咱们每次落子的时候全部判断一次就好了。

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

顺便附上所有代码

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. 彩蛋:与AI下棋

其实很简单,每次下完棋,设置一个函数:随机找位置下棋。这样就实现了和电脑下棋,单人游戏的功能了,这个功能我已经实现,但是我就不写出来了,交给大家吧,当做是大家巩固这篇文章的作业。哈哈哈哈

结语

睡了睡了,这篇文章连续写了我7个小时,其实这三个小游戏还有很多可以优化的地方,大家可以提出来,互相学习。喜欢的兄弟姐妹点点赞哈,谢谢大家!!

如果你觉得此文对你有一丁点帮助,点个赞,鼓励一下林三心哈哈。或者加入我的群哈哈,咱们一起摸鱼一起学习 : meron857287645

注:本文转载自juejin.cn的Sunshine_Lin的文章"https://juejin.cn/post/6989003710030413838"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top