快捷目录
上一节中,我们对于横向、纵向胜利做了算法计算逻辑,计算出了横排、纵向中的可胜利方法共有多少次。那么在这一节中,我们将要对斜向胜利做算法的逻辑处理。
获胜规则逻辑处理
斜向获胜跟横向以及纵向最不一样的就是斜向获胜可以分为:左斜以及右斜。那我们就按顺序来,先处理左斜。
左斜获胜规则
那么我们还是一样,先说一下我们的解题思路:
我们可以通过三重循环遍历所有可能的左斜五子连棋情况,然后将这些情况记录到wins
数组中。其中,wins数组中的每个元素代表一种五子连棋情况,如果当前玩家在这种情况下已经有了五个棋子,那么就代表着当前玩家获胜了。
然后第一重循环用于遍历所有可能的纵向坐标,第二重循环用于遍历所有可能的横向坐标,第三重循环用于遍历当前五子连棋情况下的所有棋子。在第三重循环中,通过i-k
和j+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+k
和j+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 的算法去计算出来的,不信的话你百度去👻。
五子相连算法逻辑处理
那么截至目前,我们最开始定下的三个字段,wins
和count
已经使用上了,但是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
去计算所有有可能获胜的方法。
然后,我们用myWin
跟count
做了一个交互,用于计算我这个赢法上一共有多少个棋子。
最后,我们每下一个棋子,就在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>