前言
大家好,我是林三心,相信大家看了我前一篇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下棋
其實很簡單,每次下完棋,設定一個函式:隨機找位置下棋。這樣就實現了和電腦下棋,單人遊戲的功能了,這個功能我已經實現,但是我就不寫出來了,交給大家吧,當做是大家鞏固這篇文章的作業。哈哈哈哈
結語
我是林三心,一個熱心的前端菜鳥程式設計師。如果你上進,喜歡前端,想學習前端,那我們們可以交朋友,一起摸魚哈哈,摸魚群,加我請備註【思否】