foreword
Hello everyone, I'm Lin Sanxin, I believe you have read my previous article on canvas introduction In order to let her get started with canvas in 10 minutes, I stayed up late and wrote 3 small projects and this article , I already have an entry level to canvas understanding. Today, I wrote three interesting little games with canvas to make you happy. Yes, I only have you in my heart, not her.
It's 0:15 in the morning, let's start 🐍🐍🐍🐍🐍🐍🐍🐍🐍🐍, and write this article while debugging! ! !
greedy snake 🐍
The final effect is as follows:
The implementation steps are divided into the following steps:
- 1. Draw the snake
- 2. Make the snake move
- 3. Random food delivery
- 4. Snakes eat food
- 5. Edge detection and collision detection
1. Draw the snake
In fact, it is very simple to draw a snake. A snake is composed of a snake head and a snake body. In fact, it can be represented by a
square. A
snake head is a square, and a
snake body can be many squares.
The drawing square can ctx.fillRect
, the snake head is head
, and the snake body is represented array 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. Make the snake move
There are two situations in which the snake moves:
- 1. The snake will move to the right by default at the beginning
- 2. Controlled by the arrow keys to move in different directions
Both cases moveone square per second
Let the snake move, in fact, the principle is very simple, let me take the snake moving to the right as an example:
- 1. The snake head first moves a square distance to the right, and the snake body does not move
- 2. Add a square to the head of the snake body
the squares at the tail of the snake body 161ef3d89e1565
4. Use the timer to create the vision that the snake keeps moving to the right
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) }
The effect is as follows:
3. Random food delivery
Randomly placing food, that is, drawing a square randomly on the canvas, pay attention to the following two points:
- 1. The coordinates should be within the range of
2. Food
snake's body or head (this will smash the snake into a daze)
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() }
The effect is as follows, the random food is drawn:
4. Snakes eat food
In fact, the snake eats food, it is very simple to understand, that is, when the snake head moves to coordinates of the food, even if it eats food, pay attention to two points:
- 1. After eating the food, the snake body
extend a space
- 2. After eating the food, the random food
change position
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. Touch boundaries and touch yourself
As we all know, if the snake head touches the boundary, or touches the snake body, the game will be terminated.
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
}
Since then, the Snake 🐍 mini-game has been completed:
6. All code:
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)
}
star connection
The effect is as follows, isn't it cool, brothers ( background picture can be downloaded by yourself):
This little game can be divided into the following steps:
- 1. Draw a single little star and move it
- 2, create
one hundred little stars
- 3. When the stars are close, connect
- 4. Move the
to generate small stars
- 5. Click the mouse to generate
5 small stars
1. Draw a single little star and make it move
In fact, moving the stars is very simple, that is, redrawing the stars after clearing, and using the timer, there will be a moving vision. Note that: hits the border.
// 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)
To achieve the following movement and rebound:
2. Draw 100 little stars
Create a array stars to store these 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)
The effect is as follows:
3. When the stars are close to each other, make a connection
When the difference between the x and y of the two stars is less than 50, the connection is made. The connection only needs to use ctx.moveTo and 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)
You can think about why the two forEach cannot be executed together. This is a question worth thinking about, or you can combine them to execute, try the effect, and you will understand when you get it. It's a homework for you all!
The effect is as follows:
4. The mouse moves with a small star
, the little star will go there, and wherever the little star goes, it will connect with the close little star 161ef3d89e1aee
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)
The effect is as follows:
5. Click the mouse to generate five small stars
The idea is to click the mouse to generate 5 small stars and add them to the array stars
window.onclick = function (e) {
for (let i = 0; i < 5; i++) {
stars.push(new Star(e.clientX, e.clientY, 3))
}
}
The effect is as follows:
final effect:
6. All code
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. Gobang
Take a look at what will be achieved:
Gobang is divided into the following steps:
- 1. Draw a chessboard
- 2. Othello is switching,
cannot cover the pit that has been played
- 3. Determine whether the
five consecutive sons, if yes, win
egg: play chess with 161ef3d89e1ca4 AI (achieve single player game)
1. Draw the chessboard
It's actually very simple. Use ctx.moveTo and ctx.lineTo to draw 15 lines horizontally and 15 lines vertically, and it's 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()
}
}
This draws the chessboard:
2. Othello switches
- 1. The mouse click event, get the coordinates, and draw the chess (
ctx.arc
) - 2. Make sure that the chess positions that have been played cannot be played repeatedly
The first step in obtaining mouse coordinates, but we have to pay attention to one thing at a pawn only online intersection, so get the mouse coordinates, to do what treatment, rounded off to
recent
line crossing point
circle center
The second step, how to ensure that the chess position is not repeated? We can use a 161ef3d89e1d90 two-dimensional array
a white chess, it becomes 2, but here is a point to note, the x and y of the 161ef3d89e1d92 array index are the coordinates of the canvas. x, y are opposite, so the coordinates in the following code are reversed, I hope everyone can think about why.
// 是否下黑棋
// 黑棋先走
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
}
The effect is as follows:
3. Determine whether Wulianzi is
How to judge? There are four kinds of situations: , the upper and lower five joints, the left and right Wu joints, the upper left and the lower right five joints, the upper right and the lower left five joints, as long as we judge all of them once every time we make a move.
Attach all the code by the way
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. Easter eggs: play chess with AI
In fact, it is very simple. Every time you play chess, set a function: find a random position to play chess. In this way, the function of playing chess with the computer and playing single-player games is realized. I have already realized this function, but I will not write it out. I will leave it to you as your homework to consolidate this article. Ha ha ha ha
Epilogue
I'm Lin Sanxin, an enthusiastic front-end rookie programmer. If you are motivated, like the front-end, and want to learn the front-end, then we can make friends and fish together haha, touch the fish group, add me, please note [Si No]
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。