共计 16723 个字符,预计需要花费 42 分钟才能阅读完成。
前言
大家好,我是林三心,置信大家看了我前一篇 canvas 入门文章为了让她 10 分钟入门 canvas,我熬夜写了 3 个小我的项目和这篇文章,对 canvas 曾经有了入门级的理解。明天,我又用 canvas 写了三个乏味的小游戏,来哄你们开心,没错,我的心里只有你们,没有她。
当初是凌晨 0 点 15 分,咱们开搞🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍,一边调试一边把这篇文章写了!!!
贪吃蛇🐍
最终成果如下:
实现步骤分为以下几步:
- 1、把蛇画进去
- 2、让蛇动起来
- 3、随机投放食物
- 4、蛇吃食物
- 5、边缘检测与撞本人检测
1. 把蛇画进去
其实画蛇很简略,蛇就是由 蛇头和蛇身
组成,而其实都能够用 正方格
来示意,蛇头
就是一个方格,而 蛇身
能够是很多个方格
画方格能够用 ctx.fillRect
来画,蛇头应用 head
示意,而蛇身应用 数组 body
来示意
// html
<canvas id="canvas" width="800" height="800"></canvas>
// js
draw()
function draw() {const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
// 小方格的构造函数
function Rect(x, y, width, height, color) {
this.x = x
this.y = y
this.width = width
this.height = height
this.color = color
}
Rect.prototype.draw = function () {ctx.beginPath()
ctx.fillStyle = this.color
ctx.fillRect(this.x, this.y, this.width, this.height)
ctx.strokeRect(this.x, this.y, this.width, this.height)
}
// 蛇的构造函数
function Snake(length = 0) {
this.length = length
// 蛇头
this.head = new Rect(canvas.width / 2, canvas.height / 2, 40, 40, 'red')
// 蛇身
this.body = []
let x = this.head.x - 40
let y = this.head.y
for (let i = 0; i < this.length; i++) {const rect = new Rect(x, y, 40, 40, 'yellow')
this.body.push(rect)
x -= 40
}
}
Snake.prototype.drawSnake = function () {
// 绘制蛇头
this.head.draw()
// 绘制蛇身
for (let i = 0; i < this.body.length; i++) {this.body[i].draw()}
}
const snake = new Snake(3)
snake.drawSnake()}
2. 让蛇动起来
蛇动起来有两种状况:
- 1、蛇一开始就会默认向右挪动
- 2、通过方向键管制,往不同方向挪动
这两种状况每秒都是挪动一个方格的地位
让蛇动起来,其实原理很简略,我就以蛇向右挪动来举例子吧:
- 1、蛇头先右移一个方格间隔,蛇身不动
- 2、蛇身
首部
加一个方格 - 3、蛇身
尾部
的方格去除 -
4、利用定时器,造成蛇一直向右挪动的视觉
Snake.prototype.moveSnake = function () { // 将蛇头上一次状态,拼到蛇身首部 const rect = new Rect(this.head.x, this.head.y, this.head.width, this.head.height, 'yellow') this.body.unshift(rect) this.body.pop() // 依据方向,管制蛇头的坐标 switch (this.direction) { case 0: this.head.x -= this.head.width break case 1: this.head.y -= this.head.height break case 2: this.head.x += this.head.width break case 3: this.head.y += this.head.height break } } document.onkeydown = function (e) { // 键盘事件 e = e || window.event // 左 37 上 38 右 39 下 40 switch (e.keyCode) { case 37: console.log(37) // 三元表达式,避免右挪动时按左,上面同理(贪吃蛇可不能间接掉头) snake.direction = snake.direction === 2 ? 2 : 0 snake.moveSnake() break case 38: console.log(38) snake.direction = snake.direction === 3 ? 3 : 1 break case 39: console.log(39) snake.direction = snake.direction === 0 ? 0 : 2 break case 40: console.log(40) snake.direction = snake.direction === 1 ? 1 : 3 break } } const snake = new Snake(3) // 默认 direction 为 2,也就是右 snake.direction = 2 snake.drawSnake() function animate() { // 先清空 ctx.clearRect(0, 0, canvas.width, canvas.height) // 挪动 snake.moveSnake() // 再画 snake.drawSnake()} var timer = setInterval(() => {animate() }, 100) }
实现成果如下:
3. 随机投放食物
随机投放食物,也就是在画布中随机画一个方格,要留神以下两点:
- 1、坐标要在画布
范畴内
-
2、食物
不能投到蛇身或者蛇头上
(这样会把蛇砸晕的嘿嘿)function randomFood(snake) { let isInSnake = true let rect while (isInSnake) {const x = Math.round(Math.random() * (canvas.width - 40) / 40) * 40 const y = Math.round(Math.random() * (canvas.height - 40) / 40) * 40 console.log(x, y) // 保障是 40 的倍数啊 rect = new Rect(x, y, 40, 40, 'blue') // 判断食物是否与蛇头蛇身重叠 if ((snake.head.x === x && snake.head.y === y) || snake.body.find(item => item.x === x && item.y === y)) { isInSnake = true continue } else {isInSnake = false} } return rect } const snake = new Snake(3) // 默认 direction 为 2,也就是右 snake.direction = 2 snake.drawSnake() // 创立随机食物实例 var food = randomFood(snake) // 画出食物 food.draw() function animate() { // 先清空 ctx.clearRect(0, 0, canvas.width, canvas.height) // 挪动 snake.moveSnake() // 再画 snake.drawSnake() food.draw()}
成果如下,随机食物画进去了:
4. 蛇吃食物
其实蛇吃食物,很简略了解,也就是蛇头挪动到跟食物的 坐标重叠
时,就算是吃到食物了,留神两点:
- 1、吃到食物后,蛇身要
缩短一个空格
- 2、吃到食物后,随机食物要
变换位置
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
// 定义一个全局的是否吃到食物的一个变量
let isEatFood = false
Snake.prototype.moveSnake = function () {
// 将蛇头上一次状态,拼到蛇身首部
const rect = new Rect(this.head.x, this.head.y, this.head.width, this.head.height, 'yellow')
this.body.unshift(rect)
// 判断蛇头是否与食物重叠,重叠就是吃到了,没重叠就是没吃到
isEatFood = food && this.head.x === food.x && this.head.y === food.y
// 咱们下面在蛇身首部插入方格
if (!isEatFood) {
// 没吃到就要去尾,相当于整条蛇没变长
this.body.pop()} else {
// 吃到了就不去尾,相当于整条蛇缩短一个方格
// 并且吃到了,就要从新生成一个随机食物
food = randomFood(this)
food.draw()
isEatFood = false
}
// 依据方向,管制蛇头的坐标
switch (this.direction) {
case 0:
this.head.x -= this.head.width
break
case 1:
this.head.y -= this.head.height
break
case 2:
this.head.x += this.head.width
break
case 3:
this.head.y += this.head.height
break
}
}
5. 碰边界与碰本人
家喻户晓,蛇头碰到边界,或者碰到蛇身,都会终止游戏
Snake.prototype.drawSnake = function () {
// 如果碰到了
if (isHit(this)) {
// 革除定时器
clearInterval(timer)
const con = confirm(` 总共吃了 ${this.body.length - this.length}个食物,从新开始吗 `)
// 是否重开
if (con) {draw()
}
return
}
// 绘制蛇头
this.head.draw()
// 绘制蛇身
for (let i = 0; i < this.body.length; i++) {this.body[i].draw()}
}
function isHit(snake) {
const head = snake.head
// 是否碰到左右边界
const xLimit = head.x < 0 || head.x >= canvas.width
// 是否碰到高低边界
const yLimit = head.y < 0 || head.y >= canvas.height
// 是否撞到蛇身
const hitSelf = snake.body.find(({x, y}) => head.x === x && head.y === y)
// 三者其中一个为 true 则游戏完结
return xLimit || yLimit || hitSelf
}
自此,贪吃蛇🐍小游戏实现喽:
6. 全副代码:
draw()
function draw() {const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
// 定义一个全局的是否吃到食物的一个变量
let isEatFood = false
// 小方格的构造函数
function Rect(x, y, width, height, color) {
this.x = x
this.y = y
this.width = width
this.height = height
this.color = color
}
Rect.prototype.draw = function () {ctx.beginPath()
ctx.fillStyle = this.color
ctx.fillRect(this.x, this.y, this.width, this.height)
ctx.strokeRect(this.x, this.y, this.width, this.height)
}
// 蛇的构造函数
function Snake(length = 0) {
this.length = length
// 蛇头
this.head = new Rect(canvas.width / 2, canvas.height / 2, 40, 40, 'red')
// 蛇身
this.body = []
let x = this.head.x - 40
let y = this.head.y
for (let i = 0; i < this.length; i++) {const rect = new Rect(x, y, 40, 40, 'yellow')
this.body.push(rect)
x -= 40
}
}
Snake.prototype.drawSnake = function () {
// 如果碰到了
if (isHit(this)) {
// 革除定时器
clearInterval(timer)
const con = confirm(` 总共吃了 ${this.body.length - this.length}个食物,从新开始吗 `)
// 是否重开
if (con) {draw()
}
return
}
// 绘制蛇头
this.head.draw()
// 绘制蛇身
for (let i = 0; i < this.body.length; i++) {this.body[i].draw()}
}
Snake.prototype.moveSnake = function () {
// 将蛇头上一次状态,拼到蛇身首部
const rect = new Rect(this.head.x, this.head.y, this.head.width, this.head.height, 'yellow')
this.body.unshift(rect)
// 判断蛇头是否与食物重叠,重叠就是吃到了,没重叠就是没吃到
isEatFood = food && this.head.x === food.x && this.head.y === food.y
// 咱们下面在蛇身首部插入方格
if (!isEatFood) {
// 没吃到就要去尾,相当于整条蛇没变长
this.body.pop()} else {
// 吃到了就不去尾,相当于整条蛇缩短一个方格
// 并且吃到了,就要从新生成一个随机食物
food = randomFood(this)
food.draw()
isEatFood = false
}
// 依据方向,管制蛇头的坐标
switch (this.direction) {
case 0:
this.head.x -= this.head.width
break
case 1:
this.head.y -= this.head.height
break
case 2:
this.head.x += this.head.width
break
case 3:
this.head.y += this.head.height
break
}
}
document.onkeydown = function (e) {
// 键盘事件
e = e || window.event
// 左 37 上 38 右 39 下 40
switch (e.keyCode) {
case 37:
console.log(37)
// 三元表达式,避免右挪动时按左,上面同理(贪吃蛇可不能间接掉头)
snake.direction = snake.direction === 2 ? 2 : 0
snake.moveSnake()
break
case 38:
console.log(38)
snake.direction = snake.direction === 3 ? 3 : 1
break
case 39:
console.log(39)
snake.direction = snake.direction === 0 ? 0 : 2
break
case 40:
console.log(40)
snake.direction = snake.direction === 1 ? 1 : 3
break
}
}
function randomFood(snake) {
let isInSnake = true
let rect
while (isInSnake) {const x = Math.round(Math.random() * (canvas.width - 40) / 40) * 40
const y = Math.round(Math.random() * (canvas.height - 40) / 40) * 40
console.log(x, y)
// 保障是 40 的倍数啊
rect = new Rect(x, y, 40, 40, 'blue')
// 判断食物是否与蛇头蛇身重叠
if ((snake.head.x === x && snake.head.y === y) || snake.body.find(item => item.x === x && item.y === y)) {
isInSnake = true
continue
} else {isInSnake = false}
}
return rect
}
function isHit(snake) {
const head = snake.head
// 是否碰到左右边界
const xLimit = head.x < 0 || head.x >= canvas.width
// 是否碰到高低边界
const yLimit = head.y < 0 || head.y >= canvas.height
// 是否撞到蛇身
const hitSelf = snake.body.find(({x, y}) => head.x === x && head.y === y)
// 三者其中一个为 true 则游戏完结
return xLimit || yLimit || hitSelf
}
const snake = new Snake(3)
// 默认 direction 为 2,也就是右
snake.direction = 2
snake.drawSnake()
// 创立随机食物实例
var food = randomFood(snake)
// 画出食物
food.draw()
function animate() {
// 先清空
ctx.clearRect(0, 0, canvas.width, canvas.height)
// 挪动
snake.moveSnake()
// 再画
snake.drawSnake()
food.draw()}
var timer = setInterval(() => {animate()
}, 100)
}
星星连线
成果如下,是不是很酷炫呢,兄弟们(背景图片 能够本人去下载一下):
这个小游戏可分为以下几步:
- 1、画出单个小星星并使他
挪动
- 2、造出
一百个
小星星 - 3、星星之间凑近时,进行
连线
- 4、鼠标
挪动生成
小星星 - 5、鼠标点击产生
5 个小星星
1. 画出单个小星星,并使他挪动
其实挪动星星很简略,就是革除后从新绘制星星,并利用定时器,就会有挪动的视觉了。留神点在于:碰到边界要反弹
。
// html
<style>
#canvas {background: url(./ 光能使者.jpg) 0 0/cover no-repeat;
}
</style>
<canvas id="canvas"></canvas>
// js
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
// 获取以后视图的宽度和高度
let aw = document.documentElement.clientWidth || document.body.clientWidth
let ah = document.documentElement.clientHeight || document.body.clientHeight
// 赋值给 canvas
canvas.width = aw
canvas.height = ah
// 屏幕变动时也要监听实时宽高
window.onresize = function () {
aw = document.documentElement.clientWidth || document.body.clientWidth
ah = document.documentElement.clientHeight || document.body.clientHeight
// 赋值给 canvas
canvas.width = aw
canvas.height = ah
}
// 本游戏无论是实心,还是线条,色调都是红色
ctx.fillStyle = 'white'
ctx.strokeStyle = 'white'
function Star(x, y, r) {
// x,y 是坐标,r 是半径
this.x = x
this.y = y
this.r = r
// speed 参数,在 -3 ~ 3 之间取值
this.speedX = (Math.random() * 3) * Math.pow(-1, Math.round(Math.random()))
this.speedY = (Math.random() * 3) * Math.pow(-1, Math.round(Math.random()))
}
Star.prototype.draw = function () {ctx.beginPath()
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2)
ctx.fill()
ctx.closePath()}
Star.prototype.move = function () {
this.x -= this.speedX
this.y -= this.speedY
// 碰到边界时,反弹,只须要把 speed 取反就行
if (this.x < 0 || this.x > aw) this.speedX *= -1
if (this.y < 0 || this.y > ah) this.speedY *= -1
}
// 随机在 canvas 范畴内找一个坐标画星星
const star = new Star(Math.random() * aw, Math.random() * ah, 3)
star
// 星星的挪动
setInterval(() => {ctx.clearRect(0, 0, aw, ah)
star.move()
star.draw()}, 50)
达到以下 挪动以及反弹
的成果:
2、画 100 个小星星
创立一个 数组 stars
来存储这些星星
const stars = []
for (let i = 0; i < 100; i++) {
// 随机在 canvas 范畴内找一个坐标画星星
stars.push(new Star(Math.random() * aw, Math.random() * ah, 3))
}
// 星星的挪动
setInterval(() => {ctx.clearRect(0, 0, aw, ah)
// 遍历挪动渲染
stars.forEach(star => {star.move()
star.draw()})
}, 50)
成果如下:
3. 星星之间凑近时,进行连线
当两个星星的 x 和 y 相差都小于 50 时,就进行连线,连线只须要应用 ctx.moveTo 和 ctx.lineTo
就能够了
function drawLine(startX, startY, endX, endY) {ctx.beginPath()
ctx.moveTo(startX, startY)
ctx.lineTo(endX, endY)
ctx.stroke()
ctx.closePath()}
// 星星的挪动
setInterval(() => {ctx.clearRect(0, 0, aw, ah)
// 遍历挪动渲染
stars.forEach(star => {star.move()
star.draw()})
stars.forEach((star, index) => {
// 相似于冒泡排序那样,去比拟,确保所有星星两两之间都比拟到
for (let i = index + 1; i < stars.length; i++) {if (Math.abs(star.x - stars[i].x) < 50 && Math.abs(star.y - stars[i].y) < 50) {drawLine(star.x, star.y, stars[i].x, stars[i].y)
}
}
})
}, 50)
大家能够想一想,为什么 两个 forEach
不能何在一起去执行。这是个值得思考的问题,或者大家能够合并在一起执行,试试成果,获取就懂了。算是给大家留的一个作业哈!
成果如下:
4. 鼠标挪动时带着小星星
也就是鼠标到哪,那个小星星就到哪,并且这个小星星走到哪都会跟间隔近的小星星 连线
const mouseStar = new Star(0, 0, 3)
canvas.onmousemove = function (e) {
mouseStar.x = e.clientX
mouseStar.y = e.clientY
}
// 星星的挪动
setInterval(() => {ctx.clearRect(0, 0, aw, ah)
// 鼠标星星渲染
mouseStar.draw()
// 遍历挪动渲染
stars.forEach(star => {star.move()
star.draw()})
stars.forEach((star, index) => {
// 相似于冒泡排序那样,去比拟,确保所有星星两两之间都比拟到
for (let i = index + 1; i < stars.length; i++) {if (Math.abs(star.x - stars[i].x) < 50 && Math.abs(star.y - stars[i].y) < 50) {drawLine(star.x, star.y, stars[i].x, stars[i].y)
}
}
// 判断鼠标星星连线
if (Math.abs(mouseStar.x - star.x) < 50 && Math.abs(mouseStar.y - star.y) < 50) {drawLine(mouseStar.x, mouseStar.y, star.x, star.y)
}
})
}, 50)
成果如下:
5. 鼠标点击生成五个小星星
思路就是,鼠标点击,生成 5 个小星星,并加到 数组 stars
中
window.onclick = function (e) {for (let i = 0; i < 5; i++) {stars.push(new Star(e.clientX, e.clientY, 3))
}
}
成果如下:
最终成果:
6. 全副代码
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
// 获取以后视图的宽度和高度
let aw = document.documentElement.clientWidth || document.body.clientWidth
let ah = document.documentElement.clientHeight || document.body.clientHeight
// 赋值给 canvas
canvas.width = aw
canvas.height = ah
// 屏幕变动时也要监听实时宽高
window.onresize = function () {
aw = document.documentElement.clientWidth || document.body.clientWidth
ah = document.documentElement.clientHeight || document.body.clientHeight
// 赋值给 canvas
canvas.width = aw
canvas.height = ah
}
// 本游戏无论是实心,还是线条,色调都是红色
ctx.fillStyle = 'white'
ctx.strokeStyle = 'white'
function Star(x, y, r) {
// x,y 是坐标,r 是半径
this.x = x
this.y = y
this.r = r
// speed 参数,在 -3 ~ 3 之间取值
this.speedX = (Math.random() * 3) * Math.pow(-1, Math.round(Math.random()))
this.speedY = (Math.random() * 3) * Math.pow(-1, Math.round(Math.random()))
}
Star.prototype.draw = function () {ctx.beginPath()
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2)
ctx.fill()
ctx.closePath()}
Star.prototype.move = function () {
this.x -= this.speedX
this.y -= this.speedY
// 碰到边界时,反弹,只须要把 speed 取反就行
if (this.x < 0 || this.x > aw) this.speedX *= -1
if (this.y < 0 || this.y > ah) this.speedY *= -1
}
function drawLine(startX, startY, endX, endY) {ctx.beginPath()
ctx.moveTo(startX, startY)
ctx.lineTo(endX, endY)
ctx.stroke()
ctx.closePath()}
const stars = []
for (let i = 0; i < 100; i++) {
// 随机在 canvas 范畴内找一个坐标画星星
stars.push(new Star(Math.random() * aw, Math.random() * ah, 3))
}
const mouseStar = new Star(0, 0, 3)
canvas.onmousemove = function (e) {
mouseStar.x = e.clientX
mouseStar.y = e.clientY
}
window.onclick = function (e) {for (let i = 0; i < 5; i++) {stars.push(new Star(e.clientX, e.clientY, 3))
}
}
// 星星的挪动
setInterval(() => {ctx.clearRect(0, 0, aw, ah)
// 鼠标星星渲染
mouseStar.draw()
// 遍历挪动渲染
stars.forEach(star => {star.move()
star.draw()})
stars.forEach((star, index) => {
// 相似于冒泡排序那样,去比拟,确保所有星星两两之间都比拟到
for (let i = index + 1; i < stars.length; i++) {if (Math.abs(star.x - stars[i].x) < 50 && Math.abs(star.y - stars[i].y) < 50) {drawLine(star.x, star.y, stars[i].x, stars[i].y)
}
}
if (Math.abs(mouseStar.x - star.x) < 50 && Math.abs(mouseStar.y - star.y) < 50) {drawLine(mouseStar.x, mouseStar.y, star.x, star.y)
}
})
}, 50)
3. 五子棋
看看将实现的成果:
五子棋分为以下步骤:
- 1、画出棋盘
- 2、黑白棋切换着下,
不能笼罩已下的坑位
- 3、判断是否
五连子
,是的话就赢了 - 4、彩蛋:跟
AI 下棋
(实现单人玩游戏)
1. 画出棋盘
其实很简略,利用ctx.moveTo 和 ctx.lineTo
,横着画 15 条线,竖着画 15 条线,就 OK 了。
// html
#canvas {background: #e3cdb0;}
<canvas id="canvas" width="600" height="600"></canvas>
// js
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()}
}
这样就画出了棋盘:
2. 黑白棋切换着下
- 1、鼠标点击事件,获取坐标,将棋画进去(
ctx.arc
) - 2、确保已下的棋位不能反复下
第一步,获取鼠标坐标,然而咱们要留神一件事,棋子只能下在线的 交叉处
,所以拿到鼠标坐标后,要做一下解决,四舍五入,以 最近
的一个 线交叉点
为圆的 圆心
第二步,如何确保棋位不反复下呢?咱们能够应用一个 二维数组
来记录,初始是 0,下过黑棋就变为 1,下过白棋就变为 2,然而这里要留神一点,数组索引的 x,y 跟画布坐标的 x,y 是相同的
,所以前面代码里坐标反过来,心愿大家能思考一下为啥。
// 是否下黑棋
// 黑棋先走
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()
// 切换黑白
isBlack = !isBlack
}
成果如下:
3. 判断是否五连子
如何判断呢?有四种状况:高低五连子,左右吴连子,左上右下五连子,右上左下五连子
,只有咱们每次落子的时候全副判断一次就好了。
顺便附上所有代码
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
}
}
4. 彩蛋:与 AI 下棋
其实很简略,每次下完棋,设置一个函数:随机找地位下棋。这样就实现了和电脑下棋,单人游戏的性能了,这个性能我曾经实现,然而我就不写进去了,交给大家吧,当做是大家坚固这篇文章的作业。哈哈哈哈
结语
我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜爱前端,想学习前端,那咱们能够交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】