前言
大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心~
又很多朋友最近说想要入门 Canvas
,问我能不能出一篇讲解 Canvas
的文章,但是我感觉直接讲会比较无聊,还不如通过一些案例来让大家去手敲呢
所以我准备使用 Canvas
实现一个五子棋的小游戏,希望大家能喜欢~
效果 & 实现思路
最终的效果如下:
五子棋的游戏流程如下:
1、首先绘制棋盘 2、黑白双方轮流落子,且不能将棋子下在已有棋子的位置上 3、检查是否有五子连成一线的情况,若有,则该方获胜 4、附加玩法:与AI对弈(实现单人游戏体验)
绘制棋盘
实际上非常容易,只需使用 ctx.moveTo
和 ctx.lineTo
方法,横向绘制 15
条线条,再纵向绘制 15
条线条,便大功告成
这样就画出了棋盘:
黑白子轮流下棋
一、绘制棋子的操作
监听鼠标的点击事件,从而获取点击位置的坐标,然后利用 ctx.arc
方法在该坐标位置画出棋子。
二、防止重复下棋的策略
首先,我们需要获取鼠标的精确坐标。但这里有个关键点:棋子必须落在网格线的交叉点上。因此,在获取到鼠标坐标后,我们要进行适当的处理——四舍五入
,以确保棋子位于最近的交叉点中心。
接下来,为确保同一位置不会重复下棋,我们可以采用一个二维数组来跟踪棋盘上的状态。最初,数组的所有值都设为 0
,表示该位置尚未下棋。当此处下了黑棋时,对应的数组值变为1;若下了白棋,则值变为2。但请注意,这里的数组索引(x,y)
与画布上的实际坐标(x,y)
是相反的。因此,在后续的代码实现中,我们需要对坐标进行相应的转换。希望大家能深入思考其中的原因~
效果如下:
判断五子连线
怎样去判断呢?存在四种情形:一是横向上有五个棋子相连
;二是纵向上有五个棋子相连
;三是从左上角到右下角斜着有五个棋子相连
;四是从右上角到左下角斜着有五个棋子相连
。每次落子的时候把这四种情况都判断一遍就可以了
完整代码
play()
function play() {
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
// 绘制棋盘
// 水平,总共15条线
for (let i = 0; i < 15; i++) {
ctx.beginPath()
ctx.moveTo(20, 20 + i * 40)
ctx.lineTo(580, 20 + i * 40)
ctx.stroke()
ctx.closePath()
}
// 垂直,总共15条线
for (let i = 0; i < 15; i++) {
ctx.beginPath()
ctx.moveTo(20 + i * 40, 20)
ctx.lineTo(20 + i * 40, 580)
ctx.stroke()
ctx.closePath()
}
// 是否下黑棋
// 黑棋先走
let isBlack = true
// 棋盘二维数组
let cheeks = []
for (let i = 0; i < 15; i++) {
cheeks[i] = new Array(15).fill(0)
}
canvas.onclick = function (e) {
const clientX = e.clientX
const clientY = e.clientY
// 对40进行取整,确保棋子落在交叉处
const x = Math.round((clientX - 20) / 40) * 40 + 20
const y = Math.round((clientY - 20) / 40) * 40 + 20
// cheeks二维数组的索引
// 这么写有点冗余,这么写你们好理解一点
const cheeksX = (x - 20) / 40
const cheeksY = (y - 20) / 40
// 对应元素不为0说明此地方已有棋,返回
if (cheeks[cheeksY][cheeksX]) return
// 黑棋为1,白棋为2
cheeks[cheeksY][cheeksX] = isBlack ? 1 : 2
ctx.beginPath()
// 画圆
ctx.arc(x, y, 20, 0, 2 * Math.PI)
// 判断走黑还是白
ctx.fillStyle = isBlack ? 'black' : 'white'
ctx.fill()
ctx.closePath()
// canvas画图是异步的,保证画出来再去检测输赢
setTimeout(() => {
if (isWin(cheeksX, cheeksY)) {
const con = confirm(`${isBlack ? '黑棋' : '白棋'}赢了!是否重新开局?`)
// 重新开局
ctx.clearRect(0, 0, 600, 600)
con && play()
}
// 切换黑白
isBlack = !isBlack
}, 0)
}
// 判断是否五连子
function isWin(x, y) {
const flag = isBlack ? 1 : 2
// 上和下
if (up_down(x, y, flag)) {
return true
}
// 左和右
if (left_right(x, y, flag)) {
return true
}
// 左上和右下
if (lu_rd(x, y, flag)) {
return true
}
// 右上和左下
if (ru_ld(x, y, flag)) {
return true
}
return false
}
function up_down(x, y, flag) {
let num = 1
// 向上找
for (let i = 1; i < 5; i++) {
let tempY = y - i
console.log(x, tempY)
if (tempY < 0 || cheeks[tempY][x] !== flag) break
if (cheeks[tempY][x] === flag) num += 1
}
// 向下找
for (let i = 1; i < 5; i++) {
let tempY = y + i
console.log(x, tempY)
if (tempY > 14 || cheeks[tempY][x] !== flag) break
if (cheeks[tempY][x] === flag) num += 1
}
return num >= 5
}
function left_right(x, y, flag) {
let num = 1
// 向左找
for (let i = 1; i < 5; i++) {
let tempX = x - i
if (tempX < 0 || cheeks[y][tempX] !== flag) break
if (cheeks[y][tempX] === flag) num += 1
}
// 向右找
for (let i = 1; i < 5; i++) {
let tempX = x + i
if (tempX > 14 || cheeks[y][tempX] !== flag) break
if (cheeks[y][tempX] === flag) num += 1
}
return num >= 5
}
function lu_rd(x, y, flag) {
let num = 1
// 向左上找
for (let i = 1; i < 5; i++) {
let tempX = x - i
let tempY = y - i
if (tempX < 0 || tempY < 0 || cheeks[tempY][tempX] !== flag) break
if (cheeks[tempY][tempX] === flag) num += 1
}
// 向右下找
for (let i = 1; i < 5; i++) {
let tempX = x + i
let tempY = y + i
if (tempX > 14 || tempY > 14 || cheeks[tempY][tempX] !== flag) break
if (cheeks[tempY][tempX] === flag) num += 1
}
return num >= 5
}
function ru_ld(x, y, flag) {
let num = 1
// 向右上找
for (let i = 1; i < 5; i++) {
let tempX = x - i
let tempY = y + i
if (tempX < 0 || tempY > 14 || cheeks[tempY][tempX] !== flag) break
if (cheeks[tempY][tempX] === flag) num += 1
}
// 向左下找
for (let i = 1; i < 5; i++) {
let tempX = x + i
let tempY = y - i
if (tempX > 14 || tempY < 0 || cheeks[tempY][tempX] !== flag) break
if (cheeks[tempY][tempX] === flag) num += 1
}
return num >= 5
}
}
彩蛋:与AI下棋
这其实很容易做到。每次下完棋后,编写一个函数,让它随机选择一个位置下棋。通过这种方式,我们就能够实现与电脑对弈的单人游戏功能。这个功能我已经开发完成了,不过我就不在这里展示了,留给大家作为练习吧,就当是巩固这篇文章内容的一个小作业。嘿嘿嘿
结语
我是林三心,一个待过小型toG型外包公司、大型外包公司、小公司、潜力型创业公司、大公司的作死型前端选手