前端给客户实现了一个【五子棋】的小游戏,有点6!

科技   2024-12-21 11:09   广东  

前言

大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心~

又很多朋友最近说想要入门 Canvas,问我能不能出一篇讲解 Canvas 的文章,但是我感觉直接讲会比较无聊,还不如通过一些案例来让大家去手敲呢

所以我准备使用 Canvas 实现一个五子棋的小游戏,希望大家能喜欢~

效果 & 实现思路

最终的效果如下:

五子棋的游戏流程如下:

  • 1、首先绘制棋盘
  • 2、黑白双方轮流落子,且不能将棋子下在已有棋子的位置上
  • 3、检查是否有五子连成一线的情况,若有,则该方获胜
  • 4、附加玩法:与AI对弈(实现单人游戏体验)

绘制棋盘

实际上非常容易,只需使用 ctx.moveTo 和 ctx.lineTo 方法,横向绘制 15 条线条,再纵向绘制 15 条线条,便大功告成

这样就画出了棋盘:

黑白子轮流下棋

  • 一、绘制棋子的操作

监听鼠标的点击事件,从而获取点击位置的坐标,然后利用 ctx.arc 方法在该坐标位置画出棋子。

  • 二、防止重复下棋的策略

首先,我们需要获取鼠标的精确坐标。但这里有个关键点:棋子必须落在网格线的交叉点上。因此,在获取到鼠标坐标后,我们要进行适当的处理——四舍五入,以确保棋子位于最近的交叉点中心。

接下来,为确保同一位置不会重复下棋,我们可以采用一个二维数组来跟踪棋盘上的状态。最初,数组的所有值都设为 0,表示该位置尚未下棋。当此处下了黑棋时,对应的数组值变为1;若下了白棋,则值变为2。但请注意,这里的数组索引(x,y)与画布上的实际坐标(x,y)是相反的。因此,在后续的代码实现中,我们需要对坐标进行相应的转换。希望大家能深入思考其中的原因~

效果如下:

判断五子连线

怎样去判断呢?存在四种情形:一是横向上有五个棋子相连;二是纵向上有五个棋子相连;三是从左上角到右下角斜着有五个棋子相连;四是从右上角到左下角斜着有五个棋子相连。每次落子的时候把这四种情况都判断一遍就可以了

完整代码

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(2020 + i * 40)
        ctx.lineTo(58020 + i * 40)
        ctx.stroke()
        ctx.closePath()
    }

    // 垂直,总共15条线
    for (let i = 0; i < 15; i++) {
        ctx.beginPath()
        ctx.moveTo(20 + i * 4020)
        ctx.lineTo(20 + i * 40580)
        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, 2002 * Math.PI)
        // 判断走黑还是白
        ctx.fillStyle = isBlack ? 'black' : 'white'
        ctx.fill()
        ctx.closePath()

        // canvas画图是异步的,保证画出来再去检测输赢
        setTimeout(() => {
            if (isWin(cheeksX, cheeksY)) {
                const con = confirm(`${isBlack ? '黑棋' : '白棋'}赢了!是否重新开局?`)
                // 重新开局
                ctx.clearRect(00600600)
                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
    }

}

彩蛋:与AI下棋

这其实很容易做到。每次下完棋后,编写一个函数,让它随机选择一个位置下棋。通过这种方式,我们就能够实现与电脑对弈的单人游戏功能。这个功能我已经开发完成了,不过我就不在这里展示了,留给大家作为练习吧,就当是巩固这篇文章内容的一个小作业。嘿嘿嘿

结语

我是林三心,一个待过小型toG型外包公司、大型外包公司、小公司、潜力型创业公司、大公司的作死型前端选手


前端之神
一位前端小菜鸡,写过400多篇原创文章,全网有6w+个前端朋友,梦想是成为”前端之神“~
 最新文章