原生JS实现五子棋:从棋盘绘制到胜负判定与悔棋功能

作者:拾光时间:2026-10-25 13:03:59浏览:654

本文详细讲解如何使用原生JavaScript从零开发一个五子棋小游戏。内容涵盖15x15棋盘与棋子的DOM绘制、利用二维数组管理棋子状态、黑白棋轮流落子的交互逻辑,以及通过遍历横、竖、正斜、反斜四个方向判定胜负的核心算法。此外,还实现了基于数组数据删除与重绘的悔棋功能,适合JavaScript初学者理解数组操作与事件处理。

第一步是完成棋盘和棋子的视觉绘制。

 let arr = [
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
      [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},],
    ]
    //封装渲染函数
    const render = () => {
      document.querySelector('table').innerHTML = ''
      arr.forEach((item, index) => {
        let tr = document.createElement('tr')
        item.forEach((item2, index2) => {
          let td = document.createElement('td')//遍历数组,绘制棋盘
          //给td标签设置自定义属性,用来作为坐标使用
          td.dataset.y = index
          td.dataset.x = index2
          tr.appendChild(td)
          //给数组里面的对象做条件判断,这样就能渲染出颜色
          if (item2.num === 1) {
            td.classList.add('bgc1')
          }
          else if (item2.num === 2) {
            td.classList.add('bgc2')
          }
        })
        document.querySelector('table').appendChild(tr)
      })
    }
    render()

首先构建一个15行15列的二维数组,通过双层遍历该数组生成对应的HTML表格结构,从而形成棋盘。使用数组来驱动棋盘绘制的优势在于便于后续的数据查找与筛选。表格中的每个单元格(td)初始对应一个空对象,在用户下棋时,通过为该对象添加num属性来区分棋子颜色:num为1表示黑棋,num为2表示白棋。配合适当的CSS样式调整,即可呈现出完整的棋盘与棋子效果。此外,每个td标签都设置了自定义属性x和y作为坐标标识,这使得DOM元素与数组中的数值能够建立清晰的对应关系。以下是相关的CSS代码。

<style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      list-style: none;
    }
    table {
      position: relative;
      width: 730px;
      height: 730px;
      margin: 0 auto;
      border: 5px solid black;
      background: url(./src=http___pic45.nipic.com_20140804_2372131_155038114014_2.jpg&refer=http___pic45.nipic.webp) no-repeat;
      background-size: 100%;
      background-position: center;
      padding: 24px 12px;
    }
    td {
      width: 35px;
      height: 35px;
      border-radius: 50%;
      margin-right: 13px;
      margin-bottom: 11px;
      cursor: pointer;
    }
    .bgc1 {
      background-color: black;
    }
    .bgc2 {
      background-color: white;
    }
    button {
      position: absolute;
      width: 200px;
      height: 100px;
      bottom: 100px;
      right: 200px;
      text-align: center;
      line-height: 100px;
      font-size: 25px;
    }
  </style>
<table></table>
  <button>悔棋</button>

第一步是完成棋盘和棋子的视觉绘制。

第二步是实现轮流下棋的点击交互逻辑。

下棋的核心逻辑较为直观:当用户点击棋盘上的某个单元格时,程序获取该单元格对应的数据对象,并为其添加num属性(黑棋为1,白棋为2),随后触发界面渲染。为了控制黑白棋的轮流落子顺序,引入一个全局变量flag进行状态切换。同时,声明两个全局数组分别用于存储所有黑棋和白棋的坐标信息,这些数组将在后续的胜负判定中被遍历调用。

//判断下棋顺序的全局变量
    let flag = true
    //所有黑棋数组
    let blackArr = []
    //所有白棋数组
    let whiteArr = []
    //轮流下棋逻辑
    document.querySelector('table').addEventListener('click', function (e) {
      if (e.target.dataset.x) {
        let td = e.target
        //判断黑白棋子的顺序
        if (flag) {
          //判断点击的地方是否已经有棋子了,避免棋子覆盖
          if (!arr[td.dataset.y][td.dataset.x].num) {
            flag = !flag
            arr[td.dataset.y][td.dataset.x].num = 1
            //每走一步,就将其添加至对应的数组当中
            blackArr.push([td.dataset.y, td.dataset.x])            
          }
        } else {
          if (!arr[td.dataset.y][td.dataset.x].num) {
            flag = !flag
            arr[td.dataset.y][td.dataset.x].num = 2
            whiteArr.push([td.dataset.y, td.dataset.x])
          }
        }
        //调用判断胜负的函数
        XWin(td)
        YWin(td)
        X_YWin(td)
        Y_XWin(td)
      }
      render()
    })

第一步是完成棋盘和棋子的视觉绘制。

第三步是编写获胜条件的判断逻辑。

接下来需要实现胜负判定功能。判定逻辑主要分为四个方向:横轴、纵轴、正斜轴和反斜轴。这四个方向的判定逻辑与方法大体一致,仅在具体的数据筛选条件上存在细微差异。

3.1 横轴获胜判定

以横轴为例说明判定过程。首先需判断当前落子的是黑棋还是白棋,然后遍历对应的棋子数组。以黑棋为例,遍历后将y坐标相同的黑棋筛选出来,存入一个临时数组中,这代表了同一行内的所有黑棋。接着,对这些黑棋的x坐标进行比较,若存在5个连续的x值,则判定横轴方向获胜。具体的比较方法是:先使用sort()方法对x坐标进行排序,然后从小到大依次检查相邻值的差值。若第二个值等于第一个值加1,则将其加入一个新的连续计数数组;一旦发现数值不连续,则清空该计数数组。最终通过判断该计数数组的长度是否达到5来确定胜负。

//横轴获胜逻辑
  function XWin(td) {
      //当前X轴的所有棋子集合
      let xAllArr = []
      //判断横轴胜负逻辑的X轴棋子
      let xWinArr = []
      //判断下的是黑棋还是白棋
      if (!flag) {
        blackArr.map(item => {
          if (item[0] == td.dataset.y) {
            //将当前排的所有棋子加入对应数组
            xAllArr.push(item[1])
          }
        })
      } else {
        whiteArr.map(item => {
          if (item[0] == td.dataset.y) {
            xAllArr.push(item[1])
          }
        })
      }
      //把横排总数组排序,方便比较
      xAllArr.sort((a, b) => a - b)
      for (let i = 1; i < xAllArr.length; i++) {
        // console.log(xAllArr[i]);
        if (xAllArr[i] == (+xAllArr[i - 1] + 1)) {
          //如果相邻的两个棋子数量相差1,就将其添加至胜负逻辑数组
          xWinArr.push(xAllArr[i])
        } else {
          //否则得清空
          xWinArr = []
        }
      }
      //获胜条件
      if (xWinArr.length == 4) {
        //这里要用定时器将弹框变成异步任务,否则第五颗棋子渲染不出来就提示获胜了
        if (!flag) {
          setTimeout(function () {
            alert('黑棋获胜!')
            location.reload()
          }, 100)
        } else {
          setTimeout(function () {
            alert('白棋获胜!')
            location.reload()
          }, 100)
        }
      }
    }

第一步是完成棋盘和棋子的视觉绘制。

3.2 纵轴获胜判定

纵轴的判定代码与横轴基本相同。

主要区别在于筛选条件,将if (item[0] == td.dataset.y) 替换为if (item[1] == td.dataset.x),即改为筛选出x坐标相同的棋子,从而获取同一列的所有棋子。后续的判定逻辑与代码结构与横轴完全一致。

//竖轴获胜逻辑
    function YWin(td) {
      //当前Y轴的所有棋子集合
      let yAllArr = []
      //判断竖轴胜负逻辑的X轴棋子
      let yWinArr = []
      if (!flag) {
        blackArr.map(item => {
          if (item[1] == td.dataset.x) {
            yAllArr.push(item[0])
          }
        })
      } else {
        whiteArr.map(item => {
          if (item[1] == td.dataset.x) {
            yAllArr.push(item[0])
          }
        })
      }
      //竖排总数组排序
      yAllArr.sort((a, b) => a - b)
      for (let i = 1; i < yAllArr.length; i++) {
        // console.log(xAllArr[i]);
        if (yAllArr[i] == (+yAllArr[i - 1] + 1)) {
          yWinArr.push(yAllArr[i])
        } else {
          yWinArr = []
        }
      }
      if (yWinArr.length == 4) {
        if (!flag) {
          setTimeout(function () {
            alert('黑棋获胜!')
            location.reload()
          }, 100)
        } else {
          setTimeout(function () {
            alert('白棋获胜!')
            location.reload()
          }, 100)
        }
      }
    }

第一步是完成棋盘和棋子的视觉绘制。

3.3 正斜轴获胜判定

斜轴判定的难点在于如何筛选出特定斜线上的所有棋子。

一旦成功筛选出斜线上的棋子,后续的判定逻辑便与前述方向相同。由于所有斜线均为45度角,斜线上任意两个棋子的x坐标之差与y坐标之差必然相等。基于这一几何特性,判定条件为if ((item[0] - td.dataset.y) == (item[1] - td.dataset.x))。找到斜线上的棋子后,后续步骤只需复制粘贴之前的逻辑即可。

//正斜轴获胜逻辑
    function X_YWin(td) {
      //当前X轴的所有棋子集合
      let x_yAllArr = []
      //判断横轴胜负逻辑的X轴棋子
      let x_yWinArr = []
      if (!flag) {
        blackArr.map(item => {
          //判断斜轴棋子,斜轴棋子的x和y之差都是相同的
          if ((item[0] - td.dataset.y) == (item[1] - td.dataset.x)) {
            x_yAllArr.push(item[1])
          }
        })
      } else {
        whiteArr.map(item => {
          if ((item[0] - td.dataset.y) == (item[1] - td.dataset.x)) {
            x_yAllArr.push(item[1])
          }
        })
      }
      x_yAllArr.sort((a, b) => a - b)
      for (let i = 1; i < x_yAllArr.length; i++) {
        if (x_yAllArr[i] == (+x_yAllArr[i - 1] + 1)) {
          //如果相邻的两个棋子数量相差1,就将其添加至胜负逻辑数组
          x_yWinArr.push(x_yAllArr[i])
        } else {
          //否则得清空
          x_yWinArr = []
        }
      }
      //获胜条件
      if (x_yWinArr.length == 4) {
        if (!flag) {
          setTimeout(function () {
            alert('黑棋获胜!')
            location.reload()
          }, 100)
        } else {
          setTimeout(function () {
            alert('白棋获胜!')
            location.reload()
          }, 100)
        }
      }
    }

第一步是完成棋盘和棋子的视觉绘制。

3.4 反斜轴获胜判定

反斜轴的判定同理,只需将条件修改为if (0 - (item[0] - td.dataset.y) == (item[1] - td.dataset.x)),其余部分同样采用复制粘贴的方式实现。

 //反斜轴获胜逻辑
    function Y_XWin(td) {
      //当前X轴的所有棋子集合
      let y_xAllArr = []
      //判断横轴胜负逻辑的X轴棋子
      let y_xWinArr = []
      if (!flag) {
        blackArr.map(item => {
          //判断斜轴棋子
          if (0 - (item[0] - td.dataset.y) == (item[1] - td.dataset.x)) {
            y_xAllArr.push(item[1])
          }
        })
      } else {
        whiteArr.map(item => {
          if (0 - (item[0] - td.dataset.y) == (item[1] - td.dataset.x)) {
            y_xAllArr.push(item[1])
          }
        })
      }
      y_xAllArr.sort((a, b) => a - b)
      for (let i = 1; i < y_xAllArr.length; i++) {
        if (y_xAllArr[i] == (+y_xAllArr[i - 1] + 1)) {
          //如果相邻的两个棋子数量相差1,就将其添加至胜负逻辑数组
          y_xWinArr.push(y_xAllArr[i])
        } else {
          //否则得清空
          y_xWinArr = []
        }
      }
      //获胜条件
      if (y_xWinArr.length == 4) {
        if (!flag) {
          setTimeout(function () {
            alert('黑棋获胜!')
            location.reload()
          }, 100)
        } else {
          setTimeout(function () {
            alert('白棋获胜!')
            location.reload()
          }, 100)
        }
      }
    }

第一步是完成棋盘和棋子的视觉绘制。

将上述各个方向的判定函数集成到落棋事件处理程序中,整个游戏的核心功能即告完成。

第四步是实现悔棋功能。

最后补充悔棋功能。当用户点击悔棋按钮时,程序从对应的棋子数组中删除最新添加的数据,并重新渲染棋盘,从而撤销上一步操作。

 //悔棋
    document.querySelector('button').addEventListener('click', function () {
      //判断前面一步是黑棋还是白棋
      if (!flag) {
        //黑棋
        //获取对应棋子总数组的最后一个数据的值
        const y = blackArr[blackArr.length - 1][0]
        const x = blackArr[blackArr.length - 1][1]
        //将对应的对象里的num值删除,这样渲染出来对应棋子就消失了
        delete arr[y][x].num
        //删除总数组里的最后一个数据,否则胜负逻辑会有问题
        blackArr.splice(blackArr.length - 1, 1)
        //重置下棋顺序
        flag = !flag
      } else {
        //白棋
        const y = whiteArr[whiteArr.length - 1][0]
        const x = whiteArr[whiteArr.length - 1][1]
        delete arr[y][x].num
        whiteArr.splice(whiteArr.length - 1, 1)
        flag = !flag
      }
      render()
    })

总结

整个代码实现主要依赖于JavaScript的基本语法,重点在于灵活运用几个数组方法。希望这篇教程能为JavaScript初学者提供有益的参考。

第一步是完成棋盘和棋子的视觉绘制。

到此这篇关于使用原生JS快速写出一个五子棋小游戏的文章就介绍到这了,更多相关原生JS写五子棋小游戏内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

作者最新文章