前端人工智能:【算法实战】胜利方式算法逻辑处理(下)

64 阅读9分钟

快捷目录

前端人工智能:来谈谈前端人工智能

前端人工智能:前端AI研发之算法

前端人工智能:canvas 绘制棋盘线条

前端人工智能:canvas 绘制黑白棋子

前端人工智能:canvas 绘制棋盘及落子逻辑

前端人工智能:【算法实战】胜利方式算法逻辑处理(上)


上一节中,我们对于横向、纵向胜利做了算法计算逻辑,计算出了横排、纵向中的可胜利方法共有多少次。那么在这一节中,我们将要对斜向胜利做算法的逻辑处理。

获胜规则逻辑处理

斜向获胜跟横向以及纵向最不一样的就是斜向获胜可以分为:左斜以及右斜。那我们就按顺序来,先处理左斜。

左斜获胜规则

那么我们还是一样,先说一下我们的解题思路:

我们可以通过三重循环遍历所有可能的左斜五子连棋情况,然后将这些情况记录到wins数组中。其中,wins数组中的每个元素代表一种五子连棋情况,如果当前玩家在这种情况下已经有了五个棋子,那么就代表着当前玩家获胜了。

然后第一重循环用于遍历所有可能的纵向坐标,第二重循环用于遍历所有可能的横向坐标,第三重循环用于遍历当前五子连棋情况下的所有棋子。在第三重循环中,通过i-kj+k来计算出当前棋子的坐标,并将这个坐标对应的wins数组元素标记为true。最后,count变量用于记录总共有多少种左斜五子连棋情况。

左斜的其实是相对比较难理解的,我们结合图来看一下:

如上图,纵向的取值一共有 0 ~14(一共有15格)。但是左斜的所有胜法中,纵向的前四个是不可能有任何胜法的,也就代表着我们i的取值必须大于等于 4,然后小于 15,这个应该都能理解吧:

 for (var i = 4; i < 15 i++) {}

那接下来我们再来看看横向的:

如上图,横向的取值一共是 0 ~10。左侧的前四个是不可能有任何胜法的,也就代表着我们i的取值必须大于等于 0,然后小于 11 。然后再做个获胜棋子数的循环:

 // i - 纵向   j - 横向
  for (var i = 14; i >= 4; i--) {
    for (var j = 0; j < 11; j++) {
      for (var k = 0; k < 5; k++) {
        wins[i - k][j + k][count] = true
      }
      count++
    }
  }

以上代码中,[i - k]我们可以这么理解,当你纵向继续去落棋时,纵向肯定是会越来越小的;反之,所占用的行数就会越来越多,所以横向就会越来越多,也就是为什么要j + k

结合图片以及代码,大家再认真理解一下。一定要把这一块的逻辑给捋清楚,那么接下来我们接着做右斜的。

右斜获胜规则

右斜的解题思路其实是跟左斜很相似的:

通过三重循环遍历所有可能的右斜五子连棋情况,然后将这些情况记录到wins数组中。其中,wins数组中的每个元素代表一种五子连棋情况,如果当前玩家在这种情况下已经有了五个棋子,那么就代表着当前玩家获胜了。

第一重循环用于遍历所有可能的纵向坐标,第二重循环用于遍历所有可能的横向坐标,第三重循环用于遍历当前五子连棋情况下的所有棋子。在第三重循环中,通过i+kj+k来计算出当前棋子的坐标,并将这个坐标对应的wins数组元素标记为true。最后,count变量用于记录总共有多少种右斜五子连棋情况。

如下图,右斜中我们一共有11种获胜方法::

那么我们的获胜方法就可以这么去写:

// i - 纵向   j - 横向
for (var i = 0; i < 11; i++) {
    for (var j = 0; j < 11; j++) {
      for (var k = 0; k < 5; k++) {
        wins[i + k][j + k][count] = true
      }
      count++
    }
}

这里的[j + k]是这么理解的:比如我们先在第一行第一列下第一颗棋子,接着如果我们还想斜向取胜的话,是不是得在第二行往右走一格。而j代表横向(取值范围0~10),k代表获胜棋子数(取值范围0~4),所以是[j + k]。而[i + k]也是同样的道理。

这个相比于左斜,相对于会更容易理解一点。那么截至目前为止,我们横向、纵向、左斜、右斜的获胜数量基本都计算完成了,也就是说所有能赢的算法我们都完成了。

那到底有多少种呢?我们解除一下对横向、纵向、左斜这几个计算获胜数量方法的注释,然后再打印一下我们定义用来统计一共有多少种赢法的变量count

  // 计算横向有多少种赢法
  // i - 纵向   j - 横向
  for (var i = 0; i < 15; i++) {
    for (var j = 0; j < 11; j++) {
      for (var k = 0; k < 5; k++) {
        wins[i][j + k][count] = true
      }
      count++
    }
  }
  // 计算纵向有多少种赢法
  // i - 纵向   j - 横向
  for (var i = 0; i < 11; i++) {
    for (var j = 0; j < 15; j++) {
      for (var k = 0; k < 5; k++) {
        wins[i + k][j][count] = true
      }
      count++
    }
  }
  //计算左斜有多少种赢法
  // i - 纵向   j - 横向
  for (var i = 14; i >= 4; i--) {
    for (var j = 0; j < 11; j++) {
      for (var k = 0; k < 5; k++) {
        wins[i - k][j + k][count] = true
      }
      count++
    }
  }
  //计算右斜有多少种赢法
  // i - 纵向   j - 横向
  for (var i = 0; i < 11; i++) {
    for (var j = 0; j < 11; j++) {
      for (var k = 0; k < 5; k++) {
        wins[i + k][j + k][count] = true
      }
      count++
    }
  }
  console.log(count);

我们可以看到在五子棋的 15 * 15 棋盘中,一共是有572种获胜方式的。不可能再多了,因为我们是用 JavaScript 的算法去计算出来的,不信的话你百度去👻。

五子相连算法逻辑处理

那么截至目前,我们最开始定下的三个字段,winscount已经使用上了,但是myWin却还没有用上。我们一共有 572 种算法,也就是说myWin这个数组的长度至少也会到 572。

// 赢法数组
var wins = []
// 一共有多少种赢法
var count = 0
// 用于统计某种赢法上已有多少个棋子
var myWin = []

那接下来我们来写一个for循环,去初始化myWin这个数组,给它添加 572 项,并且每一项都等于 0:

  for (var i = 0; i < count; i++) {
    myWin[i] = 0
  }

那现在所有的算法都算完后,我们就只需要在点击事件那里调用就好了:

for (var k = 0; k < count; k++) {
  if (wins[i][j][k]) {
    myWin[k]++
  }
}

首先wins是我们定义的所有赢法的数组,而[i][j]其实就是你在棋盘上的棋子。当在wins里找到[i][j]这个坐标的棋子时,那就代表至少有一种赢法。我们举个例子:

比如我们放置了一颗棋子在(0,0)的位置,那么我们起码是不是能找得到三种赢法是能匹配得上的。当我们最后点击(0,4)的位置的时候,那就能匹配得上其中的一种赢法。

那么每一次我们的赢法myWin[k]就都会加 1 ,而如果一旦赢法myWin[k]的棋子数达到了五次,那么就说明游戏结束,已经五子相连了:

for (var k = 0; k < count; k++) {
  if (wins[i][j][k]) {
    myWin[k]++
  }
  if (myWin[k] == 5) {
    console.log('游戏结束!恭喜你获胜了')
  }
}

那我们保存代码运行看看是不是这个效果:

我们可以看到当我们五子斜向相连的时候,控制台就开始弹出获胜的信息了。由于这部分的逻辑还是比较复杂的,那我们再来回顾一遍:

首先,我们定义了一个数组wins去计算所有有可能获胜的方法。

然后,我们用myWincount做了一个交互,用于计算我这个赢法上一共有多少个棋子。

最后,我们每下一个棋子,就在wins里查找有没有这个棋子的胜法,有的话就往myWin里面加 1。

而最终一旦myWin里的某种获胜方法棋子数达到五颗,那么就说明游戏获胜了。

那我们现在呢就基本完成五子棋的一个算法了,那么接下来我们就得进入到人工智能的部分。也就是说人工的部分算完了,现在我们得开始计算电脑了。

但是在这之前,还是建议大家多阅读几遍,自己亲手写一遍这个逻辑。因为这个看似简单,其实还是很烧脑的。


本节代码

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title></title>
</head>

<body>
  <canvas id="chess" width=800 height=800></canvas>
</body>
<script type="text/javascript">
  var chess = document.getElementById("chess");
  // 棋盘数组
  var chessBoard = []
  // 赢法数组
  var wins = []
  // 一共有多少种赢法
  var count = 0
  // 赢法统计数组
  var myWin = []
  var context = chess.getContext("2d");
  context.strokeStyle = "#666";
  context.beginPath()
  // 绘制棋盘
  for (var i = 0; i < 15; i++) {
    // 横线起点
    context.moveTo(15, 15 + i * 30)
    // 横线终点
    context.lineTo(435, 15 + i * 30)
    // 竖线起点
    context.moveTo(15 + i * 30, 15)
    // 竖线终点
    context.lineTo(15 + i * 30, 435)
    // 结束绘画
    context.stroke()
  }

  // 数组显示棋盘
  for (var i = 0; i < 15; i++) {
    chessBoard[i] = []
    for (var j = 0; j < 15; j++) {
      chessBoard[i][j] = 0
    }
  }

  // 初始化赢法数组
  for (var i = 0; i < 15; i++) {
    wins[i] = []
    for (var j = 0; j < 15; j++) {
      wins[i][j] = []
    }
  }

  // 计算横向有多少种赢法
  // i - 纵向   j - 横向
  for (var i = 0; i < 15; i++) {
    for (var j = 0; j < 11; j++) {
      for (var k = 0; k < 5; k++) {
        wins[i][j + k][count] = true
      }
      count++
    }
  }
  // 计算纵向有多少种赢法
  // i - 纵向   j - 横向
  for (var i = 0; i < 11; i++) {
    for (var j = 0; j < 15; j++) {
      for (var k = 0; k < 5; k++) {
        wins[i + k][j][count] = true
      }
      count++
    }
  }
  //计算左斜有多少种赢法
  // i - 纵向   j - 横向
  for (var i = 14; i >= 4; i--) {
    for (var j = 0; j < 11; j++) {
      for (var k = 0; k < 5; k++) {
        wins[i - k][j + k][count] = true
      }
      count++
    }
  }
  //计算右斜有多少种赢法
  // i - 纵向   j - 横向
  for (var i = 0; i < 11; i++) {
    for (var j = 0; j < 11; j++) {
      for (var k = 0; k < 5; k++) {
        wins[i + k][j + k][count] = true
      }
      count++
    }
  }



  for (var i = 0; i < count; i++) {
    myWin[i] = 0
  }


  // 落子
  chess.onclick = function (e) {
    // 横坐标
    var i = Math.floor(e.offsetX / 30)
    // 纵坐标
    var j = Math.floor(e.offsetY / 30)

    if (chessBoard[j][i] == 1) {
      return
    }

    var context = chess.getContext("2d");
    context.beginPath();
    context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);
    var grd = context.createRadialGradient(15 + i * 30, 15 + j * 30, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0)
    grd.addColorStop(0, 'black')
    grd.addColorStop(1, 'white')
    context.fillStyle = grd
    context.fill()
    context.stroke()

    chessBoard[j][i] = 1

    for (var k = 0; k < count; k++) {
      if (wins[i][j][k]) {
        myWin[k]++
      }
      if (myWin[k] == 5) {
        console.log('游戏结束!恭喜你获胜了')
      }
    }
  }
</script>

</html>