动态操作宽高需要注意写法,否则画布内容会被清空
一、获取画笔
<template>
<canvas :width="canvasWidth" :height="canvasHeight" @click.stop="leftClick" @contextmenu.prevent.stop="rightClick" id="canvas"></canvas>
</template>
mounted() {
// 找到canvas节点// 获取画笔
this.context = document.getElementById('canvas').getContext('2d')
}
二、模拟棋盘格
1、画矩形
drawBlock(i, j) {
this.context.fillStyle = '#c0c0c0'
this.context.fillRect(i * this.blockWidth, j * this.blockWidth, this.blockWidth, this.blockWidth)
// 画四条线让小方块有立体感
// 左
this.drawLine(
'#ececec',
i * this.blockWidth + 1,
j * this.blockWidth + 1,
i * this.blockWidth + 1,
j * this.blockWidth + 1 + this.blockWidth - 2
)
// 上
this.drawLine(
'#ececec',
i * this.blockWidth + 1,
j * this.blockWidth + 1,
i * this.blockWidth + 1 + this.blockWidth - 2,
j * this.blockWidth + 1
)
// 右
this.drawLine(
'rgb(128,128,128)',
i * this.blockWidth + 1 + this.blockWidth - 2,
j * this.blockWidth + 1,
i * this.blockWidth + 1 + this.blockWidth - 2,
j * this.blockWidth + 1 + this.blockWidth - 2
)
// 下
this.drawLine(
'rgb(128,128,128)',
i * this.blockWidth + 2,
j * this.blockWidth + 1 + this.blockWidth - 2,
i * this.blockWidth + 2 + this.blockWidth,
j * this.blockWidth + 1 + this.blockWidth - 2
)
}
2、画线
drawLine(color, beginX, beginY, endX, endY) {
this.context.beginPath()
this.context.strokeStyle = color
this.context.moveTo(beginX, beginY)
this.context.lineTo(endX, endY)
this.context.lineWidth = 2;
this.context.stroke()
}
3.循环拼出棋盘格
this.scan = []
for (let i = 0; i < this.canvasWidth / this.blockWidth; i++) {
this.scan[i] = []
for (let j = 0; j < this.canvasHeight / this.blockWidth; j++) {
this.scan[i][j] = {
isFlag: false,//被右键标记的方块
isMine: false,//是否是雷
isTurn: false,//被翻开的方块
}
this.drawBlock(i, j)
}
}
三、右键模拟插旗
rightClick(e) {
// 获取点击坐标
let x = parseInt(e.offsetX / this.blockWidth);
let y = parseInt(e.offsetY / this.blockWidth);
this.context.beginPath()
this.context.fillStyle = 'red'
this.context.moveTo(x * this.blockWidth + this.blockWidth / 2, y * this.blockWidth + 3)
this.context.lineTo(x * this.blockWidth + this.blockWidth / 2, y * this.blockWidth + this.blockWidth / 2 + 3)
this.context.lineTo(x * this.blockWidth + this.blockWidth / 4 - 1, y * this.blockWidth + this.blockWidth / 4)
this.context.fill()
this.context.beginPath()
this.context.fillStyle = 'black'
this.context.moveTo(x * this.blockWidth + this.blockWidth / 2, y * this.blockWidth + this.blockWidth / 2 + 3)
this.context.lineTo(x * this.blockWidth + this.blockWidth / 4 - 3, y * this.blockWidth + this.blockWidth / 4 * 3 + 3)
this.context.lineTo(x * this.blockWidth + this.blockWidth / 4 * 3, y * this.blockWidth + this.blockWidth / 4 * 3 + 3)
this.context.fill()
}
四、画圆模拟雷
drawArc(x, y) {
this.context.beginPath()
this.context.fillStyle = 'black'
this.context.arc(x * this.blockWidth + this.blockWidth / 2, y * this.blockWidth + this.blockWidth / 2, this.blockWidth / 4, 0, 2 * Math.PI)
this.context.fill()
this.context.stroke()
}
五、左键模拟翻牌
第一次点击后再随机生成雷,画出翻牌样式,统计翻牌位置周围八个格子是否有雷,有则画出提示文本并结束,没有则记录一下翻牌位置以及其周围八个格子的坐标,再统计这八个格子周围的八个格子是否有雷,没有则这八个格子继续翻牌,继续记录一下被翻牌的位置以及其周围八个格子的坐标,如果坐标不在记录中则递归继续找雷翻牌
leftClick(e) {
// 获取点击坐标
let x = parseInt(e.offsetX / this.blockWidth);
let y = parseInt(e.offsetY / this.blockWidth);
// 当第一次点击后才生成雷
if (!this.gameStart) {
this.gameStart = true
const column = this.canvasHeight / this.blockWidth
const row = this.canvasWidth / this.blockWidth
let list = []
while (true) {
if (list.length === this.mineTotal) {
break;
}
const randomX = Math.floor(Math.random() * (row - 1))
const randomY = Math.floor(Math.random() * (column - 1))
if (!list.includes(randomX + ',' + randomY) && randomX !== x && randomY !== y) {
//排除点击的位置,防止刚点击就失败
list.push(randomX + ',' + randomY)
this.scan[randomX][randomY].isMine = true
}
}
}
// 如果没雷自动翻牌
if (!this.scan[x][y].isTurn && this.handelBlock(x, y)) {
this.history = []
this.findBlock(x, y)
}
//如果是雷则显示
if (this.scan[x][y].isMine && this.gameStart) {
this.drawArc(x, y)
}
}
1.处理翻牌效果以及画文本提示数字
handelBlock(row, column) {
let block = true;
if (!this.scan[row][column].isMine) {
this.scan[row][column].isTurn = true
// 改背景
this.context.beginPath()
this.context.fillStyle = '#fff'
this.context.fillRect(row * this.blockWidth, column * this.blockWidth, this.blockWidth, this.blockWidth);
// 画线框
this.context.beginPath()
this.context.fillStyle = '#c0c0c0'
this.context.strokeRect(row * this.blockWidth, column * this.blockWidth, this.blockWidth, this.blockWidth);
let index = this.getNumber(row, column);
if (index > 0) {
block = false
// 画文本雷的个数
this.context.beginPath()
this.context.fillStyle = this.getNumberColor(index)
this.context.font = '27px'
this.context.fillText(index, row * this.blockWidth + this.blockWidth / 2 - 5, column * this.blockWidth + this.blockWidth / 2 + 6)
}
}
return block
}
2.计算翻牌位置周围八个格子雷的个数
getNumber(row, column) {
let index = 0;
// 左
if (row - 1 >= 0 && this.scan[row - 1][column].isMine) {
index++;
}
// 右
if (row + 1 < this.scan.length && this.scan[row + 1][column].isMine) {
index++;
}
// 上
if (column - 1 >= 0 && this.scan[row][column - 1].isMine) {
index++;
}
// 下
if (column + 1 < this.scan[row].length && this.scan[row][column + 1].isMine) {
index++;
}
// 左上
if (row - 1 >= 0 && column - 1 >= 0 && this.scan[row - 1][column - 1].isMine) {
index++;
}
// 右上
if (row + 1 < this.scan.length && column - 1 >= 0 && this.scan[row + 1][column - 1].isMine) {
index++;
}
// 左下
if (row - 1 >= 0 && column + 1 < this.scan[row].length && this.scan[row - 1][column + 1].isMine) {
index++;
}
// 右下
if (row + 1 < this.scan.length && column + 1 < this.scan[row].length && this.scan[row + 1][column + 1].isMine) {
index++;
}
return index
}
不同个数提示文本的颜色可以不同
getNumberColor(index) {
let color = ''
switch (index) {
case 7:
color = 'red';
break;
case 6:
color = 'orange';
break;
case 5:
color = 'yellow'
break;
case 4:
color = 'green'
break;
case 3:
color = 'cyan'
break;
case 2:
color = 'blue'
break;
case 1:
color = 'purple'
break;
}
return color
}
3.记录坐标如果无雷则继续翻牌,如果坐标不在记录中则递归继续翻牌
isExists(x, y) {
let exist = false
exist = this.history.includes(x + ',' + y)
return exist
},
findBlockTemplate(x, y) {
this.history.push(x + ',' + y)
if (!this.scan[x][y].isMine) {
if (this.handelBlock(x, y)) {
this.findBlock(x, y)
}
}
},
findBlock(x, y) {
this.history.push(x + ',' + y)
// 左
if (x - 1 >= 0 && !this.isExists(x - 1, y)) {
this.findBlockTemplate(x - 1, y)
}
// 右
if (x + 1 < this.scan.length && !this.isExists(x + 1, y)) {
this.findBlockTemplate(x + 1, y)
}
// 上
if (y - 1 >= 0 && !this.isExists(x, y - 1)) {
this.findBlockTemplate(x, y - 1)
}
// 下
if (y + 1 < this.scan[x].length && !this.isExists(x, y + 1)) {
this.findBlockTemplate(x, y + 1)
}
// 左上
if (x - 1 >= 0 && y - 1 >= 0 && !this.isExists(x - 1, y - 1)) {
this.findBlockTemplate(x - 1, y - 1)
}
// 右上
if (x + 1 < this.scan.length && y - 1 >= 0 && !this.isExists(x + 1, y - 1)) {
this.findBlockTemplate(x + 1, y - 1)
}
// 左下
if (x - 1 >= 0 && y + 1 < this.scan[x].length && !this.isExists(x - 1, y + 1)) {
this.findBlockTemplate(x - 1, y + 1)
}
// 右下
if (x + 1 < this.scan.length && y + 1 < this.scan[x].length && !this.isExists(x + 1, y + 1)) {
this.findBlockTemplate(x + 1, y + 1)
}
}
六、判断胜利
当所有旗正确标记出所有雷以及翻完所有牌,游戏结束
gameOver() {
this.realMineTotal = 0 //插旗中雷的个数
let flagTotal = 0 //插旗的个数
let turnTotal = 0 //翻牌的个数
for (let i = 0; i < this.canvasWidth / this.blockWidth; i++) {
for (let j = 0; j < this.canvasHeight / this.blockWidth; j++) {
if (this.scan[i][j].isFlag) {
flagTotal++;
}
if (this.scan[i][j].isMine && this.scan[i][j].isFlag) {
this.realMineTotal++;
}
if (this.scan[i][j].isTurn) {
turnTotal++;
}
}
}
if (flagTotal === this.mineTotal && this.realMineTotal === this.mineTotal && turnTotal === (this.canvasWidth / this.blockWidth) * (this.canvasHeight / this.blockWidth) - this.realMineTotal) {
this.gameEnd = true
this.$confirm('胜利!是否重新开始?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
}).then(() => {
this.change()
})
.catch(() => {
})
}
}