五子棋中的算法实现:如何判断胜负?(附完整代码)

目录

五子棋中的算法实现:如何判断胜负?

一、前言

二、胜负判断的核心思路

三、常见实现方法

1、暴力全盘扫描

2、基于落子点的方向枚举

3、带缓存的方向优化(进阶)

四、代码实现与优化

1、代码简介

2、代码分享

五、结语


        作者:watermelo37

        CSDN全栈领域优质创作者、万粉博主、华为云云享专家、阿里云专家博主、腾讯云“创作之星”特邀作者、支付宝合作作者,全平台博客昵称watermelo37。

        一个假装是giser的coder,做不只专注于业务逻辑的前端工程师,Java、Docker、Python、LLM均有涉猎。

---------------------------------------------------------------------

温柔地对待温柔的人,包容的三观就是最大的温柔。

---------------------------------------------------------------------

五子棋中的算法实现:如何判断胜负?

一、前言

        五子棋是一种极其经典的棋类游戏,规则简单却蕴含丰富的博弈思想。其核心目标是在棋盘上连成五个同色棋子(横、竖、斜任意方向),因此“如何高效判断胜负”就成了五子棋程序实现的关键。最近找到一个实现五子棋的html文件,于是想琢磨琢磨其中的“奥秘”。

        在一个典型的 15×15 棋盘上,可能存在成千上万种棋形,如果算法不合理,会导致运行效率低下,甚至影响用户体验。下面我们就从基础到优化,逐步分析实现思路。

        优化后的代码放在文章最后,有需要摸鱼的小伙伴可以自取(全部复制,粘贴到记事本里面,然后将后缀名修改为.html,然后双击打开就可以玩咯

二、胜负判断的核心思路

        五子棋的胜负条件可以简化为一句话:某一方刚下的棋子,在横向、纵向、两条对角线方向上,是否形成连续的 5 个棋子?

        也就是说,程序只需要关注最近落下的棋子,而不是全盘扫描,这就是算法优化的关键。

三、常见实现方法

1、暴力全盘扫描

        每下一步就扫描整个棋盘,逐个格子检查是否有连续 5 个相同棋子。时间复杂度:O(N²),N 为棋盘大小。

2、基于落子点的方向枚举

        只从“当前落子点”出发,检查四个方向:横向(左右延伸)、纵向(上下延伸)、左上-右下对角线、右上-左下对角线。每个方向都往两边扩展,统计连续棋子的数量,若 ≥ 5,则判定胜利。

        时间复杂度O(1),效率极高。

3、带缓存的方向优化(进阶)

        如果希望在AI五子棋(即高难度人机对战)中提升速度,可以给每个格子维护一个“方向统计表”,避免重复计算。这种方法更适合复杂对局,尤其是实现AI搜索时。

四、代码实现与优化

1、代码简介

        这段五子棋代码结构比较清楚,主要分为棋盘绘制、游戏逻辑和用户交互几个部分。棋盘用 drawBoard() 画出来,包括横竖格子和星位,棋子用径向渐变填色,看起来更像真实棋子。胜利的五子会用红色边框高亮。

        胜负判断用 checkWin(),枚举横、竖、主对角和副对角四个方向,向两端延伸统计连续棋子,如果连续五个或以上就判定胜利,并返回胜利五子的坐标数组。

        用户交互方面,通过canvas的点击事件处理落子,同时用board数组保证落子合法性。当前轮到谁和胜利信息会显示在页面上的#status。

2、代码分享

        下面是一份基于 方向枚举法 的五子棋实现代码,页面截图如下,快拿去和好盆友微机课摸鱼去吧~

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>五子棋</title>
  <style>
    body { text-align: center; font-family: "微软雅黑", sans-serif; background: #f5deb3; }
    canvas { border: 2px solid #333; display: block; margin: 20px auto; background: linear-gradient(#f5deb3, #deb887); }
    button { display: block; margin: 10px auto; padding: 8px 16px; font-size: 16px; }
    #status { font-size: 18px; margin: 10px; }
  </style>
</head>
<body>
  <h1>五子棋</h1>
  <div id="status">当前由黑方落子</div>
  <canvas id="board" width="600" height="600"></canvas>
  <button onclick="restart()">重新开始</button>
  
  <script>
    const SIZE = 15;
    const GRID = 40;
    const board = Array.from({ length: SIZE }, () => Array(SIZE).fill(0)); 
    const canvas = document.getElementById("board");
    const ctx = canvas.getContext("2d");
    const status = document.getElementById("status");
    let current = 1; // 1 黑棋,2 白棋
    let gameOver = false;
    let winCells = []; // 保存胜利五子坐标

    // 画棋盘
    function drawBoard() {
      ctx.clearRect(0, 0, 600, 600);

      // 绘制格子
      for (let i = 0; i < SIZE; i++) {
        ctx.beginPath();
        ctx.moveTo(GRID / 2, GRID / 2 + i * GRID);
        ctx.lineTo(GRID / 2 + (SIZE - 1) * GRID, GRID / 2 + i * GRID);
        ctx.strokeStyle = "#333";
        ctx.stroke();

        ctx.beginPath();
        ctx.moveTo(GRID / 2 + i * GRID, GRID / 2);
        ctx.lineTo(GRID / 2 + i * GRID, GRID / 2 + (SIZE - 1) * GRID);
        ctx.strokeStyle = "#333";
        ctx.stroke();
      }

      // 绘制星位
      const stars = [3, 7, 11];
      for (let i of stars) {
        for (let j of stars) {
          ctx.beginPath();
          ctx.arc(GRID / 2 + i * GRID, GRID / 2 + j * GRID, 5, 0, 2 * Math.PI);
          ctx.fillStyle = "#333";
          ctx.fill();
        }
      }

      // 重绘棋子
      for (let y = 0; y < SIZE; y++) {
        for (let x = 0; x < SIZE; x++) {
          if (board[y][x] !== 0) {
            const highlight = winCells.some(cell => cell[0] === x && cell[1] === y);
            drawStone(x, y, board[y][x], highlight);
          }
        }
      }
    }

    // 画棋子
    function drawStone(x, y, player, highlight=false) {
      ctx.beginPath();
      ctx.arc(GRID/2 + x*GRID, GRID/2 + y*GRID, 15, 0, 2 * Math.PI);
      const grad = ctx.createRadialGradient(GRID/2 + x*GRID - 3, GRID/2 + y*GRID - 3, 2,
                                            GRID/2 + x*GRID, GRID/2 + y*GRID, 15);
      if (player === 1) {
        grad.addColorStop(0, "#555");
        grad.addColorStop(1, "#000");
      } else {
        grad.addColorStop(0, "#fff");
        grad.addColorStop(1, "#aaa");
      }
      ctx.fillStyle = grad;
      ctx.fill();
      ctx.strokeStyle = highlight ? "red" : "#333";
      ctx.lineWidth = highlight ? 3 : 1;
      ctx.stroke();
      ctx.lineWidth = 1;
    }

    // 检查胜负并返回胜利五子
    function checkWin(x, y, player) {
      const directions = [
        [1, 0], [0, 1], [1, 1], [1, -1]
      ];
      for (const [dx, dy] of directions) {
        let count = 1;
        let cells = [[x, y]];

        for (let i = 1; i < 5; i++) {
          const nx = x + dx * i, ny = y + dy * i;
          if (nx < 0 || ny < 0 || nx >= SIZE || ny >= SIZE) break;
          if (board[ny][nx] === player) { count++; cells.push([nx, ny]); } 
          else break;
        }
        for (let i = 1; i < 5; i++) {
          const nx = x - dx * i, ny = y - dy * i;
          if (nx < 0 || ny < 0 || nx >= SIZE || ny >= SIZE) break;
          if (board[ny][nx] === player) { count++; cells.push([nx, ny]); } 
          else break;
        }
        if (count >= 5) return cells;
      }
      return null;
    }

    // 点击下棋
    canvas.addEventListener("click", e => {
      if (gameOver) return;
      const x = Math.floor(e.offsetX / GRID);
      const y = Math.floor(e.offsetY / GRID);
      if (board[y][x] !== 0) return;

      board[y][x] = current;

      const win = checkWin(x, y, current);
      if (win) {
        winCells = win;
        gameOver = true;
        status.textContent = (current === 1 ? "黑棋" : "白棋") + "获胜!";
      } else {
        current = 3 - current;
        status.textContent = "当前由" + (current === 1 ? "黑方" : "白方") + "落子";
      }

      drawBoard();
    });

    // 重新开始
    function restart() {
      for (let i = 0; i < SIZE; i++) board[i].fill(0);
      gameOver = false;
      winCells = [];
      current = 1;
      status.textContent = "当前由黑方落子";
      drawBoard();
    }

    drawBoard();
  </script>
</body>
</html>

五、结语

        实现一个五子棋小游戏方向枚举法是最常见、最实用的写法,几乎所有五子棋实现都用它,如果做AI五子棋,还可以用缓存优化、启发式搜索来进一步提升性能。

        只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

        其他热门文章,请关注:

        极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图

        你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解

        Web Worker:让前端飞起来的隐形引擎

        通过array.filter()实现数组的数据筛选、数据清洗和链式调用

        DeepSeek:全栈开发者视角下的AI革命者

        测评:这B班上的值不值?在不同城市过上同等生活水平到底需要多少钱?

        通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能

        TreeSize:免费的磁盘清理与管理神器,解决C盘爆满的燃眉之急

        通过MongoDB Atlas 实现语义搜索与 RAG——迈向AI的搜索机制

        深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解

        前端实战:基于Vue3与免费满血版DeepSeek实现无限滚动+懒加载+瀑布流模块及优化策略

      【前端实战】如何让用户回到上次阅读的位置?

        el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能

        JavaScript双问号操作符(??)详解,解决使用 || 时因类型转换带来的问题

        高效工作流:用Mermaid绘制你的专属流程图;如何在Vue3中导入mermaid绘制流程图

        内存泄漏——海量数据背后隐藏的项目生产环境崩溃风险!如何避免内存泄漏

        MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver

        JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、DOM操作等

评论 70
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值