快捷目录
上一节中,我们完成了整个棋盘的布局以及处理了落子的逻辑。但是其实还没完善,还是有一些很致命的问题的。比如同样的位置,我再点一遍,其实 canvas 还是会在那个位置再绘画一个棋子。那这也违反了五子棋中一个最简单的规则 - 不能在一个地方落俩子。
那接下来我们先来完善一下这个落子逻辑,把这个问题给解决掉:
二维数组逻辑处理
在开始前我想问下大家知道什么叫"二维数组"吗?那么二维数组通俗点来讲呢,就是数组套数组。那么我们为什么要使用这个呢?因为我们需要这些数据类型去帮我们进行计算,我们需要一个数组,然后把棋盘上所有的点都给放进去,用数组显示出一个棋盘。
因为我们用的是 canvas,不是 DOM,如果是 DOM 的话我们还可以在元素上做个简单判断。这也是我们使用canvas 的一个弊端,就跟每一种框架和技术类型都有其独特的优缺点。就像人类一样,每个人都有自己的长处和不足,我们需要根据自己的需求和目标来选择最适合自己的工具。有时候,我们需要在不同的框架和技术之间进行权衡和取舍,以便找到最佳的解决方案。
那么接下来,我们就开始正式去做第一步 - 用数组去表示棋盘:
我们用数组去定义一个棋盘,用于存储游戏中的棋子。在 JavaScript 中,可以使用一个二维数组来表示棋盘,其中每个元素表示一个棋子的状态,0 可以用来表示该位置没有棋子,1 可以用来表示该位置有黑子,2 可以表示该位置有白子。
我们先定义一个数组:
然后我们定义一个for循环,让chessBoard这个数组里面的每一项至少还有个数组。然后我们还有纵向的,给它赋值为 0,就代表没有子::
接下来我们打印一下这个数组,可以看到这个数组目前长得跟棋盘是一模一样的,都是 15 * 15 的盘面,其中数组的每一项可以看作是一个横排:
那么按逻辑来说,我们点击第一行第二列的这个格子,对应的数组是不是也得有变化呢?
点击的这个点的坐标是已知的对吧,这个我们在上一节中就做过打印。那我们在落子的逻辑里面增加chessBoard[j][i] = 1的赋值,这样点击后,数值就会变成 1:
那我们就可以去加一层判断逻辑,当用户再次点击这个区域时,直接return立即停止执行:
那这个重复放子的事情解决之后呢,我们接下来就要去做更深一步的逻辑运算了。比如除此之外,五子棋是不是还有更重要的规则 - 五子相连才能获胜。
那我们就按部就班,先不谈人工智能,我们得至少先确认我们的获胜逻辑。
获胜规则逻辑处理
我们重复落子的问题解决后,接下来我们就得需要一个更大的新数组去进行计算。首先我们得需要知道这个五子棋到底有多少种赢法,这个听起来是不是挺难的。
简单来说就是五子棋可以横着、竖着、斜着,那我们就去定义一个三维数组,然后来看看有多少种赢法。
我们先定义一个wins数组,用于查看五子棋里一共有多少种赢法;然后定义一个count,用于记录一下一共有多少种用法;这两者其实并不冲突的。你看到后面就知道了,接下来我们先把有多少种赢法给算出来。
横向获胜规则
我们先来说一下我们的思路吧。我们可以通过遍历棋盘上的所有可能的五子连棋的情况,来判断当前棋局是否有一方获胜。其中,wins数组用于记录所有可能的五子连棋情况,count用于记录总共有多少种五子连棋情况,myWin数组用于记录当前玩家在每种五子连棋情况下已经有多少个棋子。
然后我们可以通过三重循环来遍历所有可能的五子连棋情况,然后将这些情况记录到wins数组中。wins数组中的每个元素代表一种五子连棋情况,如果当前玩家在这种情况下已经有了五个棋子,那么就代表着当前玩家获胜了。
第一步我们先定义三个字段:
然后我们初始化一下赢法数组,还是做个循环,跟上面做数组显示棋盘一样。但是代码我们先不做合并优化,为了大家能更直观地看到实现过程:
接下来我们来算一下到底有多少种赢法,首先我们先来看横排的,横排的是不是有以下几种赢法,分别从第 0 列开始到第 10 列:
因为到第 11 列开始,就没有足够的空间去支撑五子连棋获胜了。所以横排的循环就得从 0 开始,然后小于 11;但是纵向的就不受影响了,还是 15 ;然后我们还得定义k,当k小于 5 的时候(0~4),我们才能判定它获胜,因为获胜规则就是五颗棋子:
根据上面的代码我们再详细解释一遍,因为这里的逻辑确实有点难。首先i纵向是 0(取值范围只能在:0~14),就代表着是第一列;然后j横向也是 0(取值范围只能在:0~10),就代表着是第一行的第一列。
这个时候k循环的值在 0、1、2、3、4 这个区间。
那么[j + k]就是第一行第一列(0+0=0)、第一行第二列(0+1=1)、第一行第三列(0+2=2)、第一行第四列(0+3=3)、第一行第五列(0+4=4)。
这个时候五棋其实就互连获胜了,我们就可以把count赋值为true,代表着这是其中一种赢法。但是别忘了,count得累加,不然就会出错。
然后我们来打印一下这个数组,看看现在计算完横排的赢法后变成什么样了:
我们可以看到数组的第 0 项是个true,它对应的是横排的第一个。也意味着第一行第一个在横排中,只有一次获胜的机会。
然后数组的第 1 项是[true,true],就意味着它有两次获胜的机会,分别是:
当它放在第一个或第二个时,都能取得胜利。那我们横向就没问题了,这部分的逻辑确实很复杂。
纵向获胜规则
在做完横向的胜利逻辑处理后,接下来我们就来做纵向的获胜规则。
如上图,这就是我们纵向的赢法。也就是说跟横向刚好是反过来的,要小于 11;但是横向的就不受影响了,还是 15 。我们先把上一节的横向代码注释掉,然后把与横向的反向逻辑写进去:
通过打印这时候我们可以看到第一行到纵向全都是只有一种赢法:
那就证明我们做对了,跟横向的刚好完全是反过来,如果我们理解了横向的逻辑,那纵向的其实就简单多了。而这一节也建议大家多阅读几遍,把这里的算法逻辑全都跟着代码理清楚,对你以后的代码逻辑和算法思维都会有巨大的提升。
而本节中,我们对二维数组有了初步的认识,懂得这么去利用它修复我们的重复落子问题。然后我们又对横向、纵向的获胜方法做了算法的逻辑处理。那么在下一节中,我们将继续对获胜的规则去进行完善,而下一节所涉及的算法思维会比本节难很多,大家也要做好准备,往前冲!
本节代码
js
<!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] = []
}
}
// 计算横向有多少种赢法
// 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++
// }
// }
// 计算纵向有多少种赢法
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++
}
}
console.log(wins);
// 落子
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
}
</script>
</html>