怎么在JavaScript中使用canvas实现一个扫雷小游戏?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
具体的游戏操作如下
1.可以通过鼠标左键打开隐藏的方块,打开后若不是雷,则会向四个方向扩展
2.可以通过鼠标右键点击未打开的方块来标记雷,第二次点击取消标记
3.可以通过鼠标右键点击已打开且有数字的方块来检查当前方块四周的标记是否正确
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
#canvas {
display: block;
margin: 0 auto;
}
</style>
</head>
<body>
<div id="play">
<canvas id="canvas"></canvas>
</div>
<script src="js/game.js"></script>
</body>
</html>
接下来我们来初始化一些内容。包括canvas画布的宽高,游戏共有几行几列,几个雷,每个格子的大小。
//获取canvas画布
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
canvas.width = 480;
canvas.height = 480;
//定义各属性
let R = 3; //格子圆角半径
let L = 15; //每个格子实际长
let P = 16; //每个格子占长
let row = 30; //行数
let col = 30; //列数
let N = 50; //雷数
为了后面的操作,我要用几个数组来储存一些位置,一个方块是否为雷的数组,该数组用于描绘出整个画面每个方块对应的内容;一个数组用于描述方块状态,即是否打开或者被标记;一个数组用来记载生成的雷的位置;一个数组用来记载标记的位置。
var wholeArr = drawInitialize(row, col, N, R, L, P);
var gameArr = wholeArr[0] //位置数组
var bombArr = wholeArr[1] //雷的位置数组
var statusArr = zoneInitialize(row, col); //状态数组 0为未打开且未标记 1为打开 2为标记
var signArr = []; //标记数组
//画出初始界面
function drawInitialize(row, col, n, R, L, P) {
let arr = initialize(row, col, n);
for (let r = 0; r < row; r++) {
for (let c = 0; c < col; c++) {
drawRct(r * P, c * P, L, R, 'rgb(102,102,102)', context);//该方法用于绘制整个画面,下面会写出声明
}
}
return arr;
}
//初始化
function initialize(row, col, n) {
let gameArr = zoneInitialize(row, col); //生成没有标记雷的矩阵
let bomb = bombProduce(n, gameArr, row, col);
gameArr = signArrNum(bomb[0], bomb[1], n, row, col);
return [gameArr, bomb[1]];
}
//界面矩阵初始化
function zoneInitialize(row, col) { //生成row行col列的矩阵
let cArr = new Array(col);
let rArr = new Array(row);
cArr = cArr.fill(0); //将行的每个位置用0填充
for (let i = 0; i < row; i++)
rArr[i] = [...cArr];
return rArr;
}
//随机生成雷
function bombProduce(n, arr, row, col) { //随机生成n个雷
let count = 0;
let bombArr = [];
while (true) {
if (count === n)
break;
let r = Math.floor(Math.random() * row);
let c = Math.floor(Math.random() * col);
if (arr[c][r] === 0) {
arr[c][r] = -1;
bombArr[count] = strProduce(c, r);
count++;
}
}
return [arr, bombArr];
}
//标记数字
function signArrNum(gArr, bArr, n, row, col) {
for (let i = 0; i < n; i++) { //为每个雷的四周的非雷的数字标记加一
let r = parseInt(analyseStr(bArr[i]).row);
let c = parseInt(analyseStr(bArr[i]).col);
if (r > 0 && gArr[c][r - 1] != -1)//判断该位置是否为雷,是则不进行操作
gArr[c][r - 1]++;
if (r < row - 1 && gArr[c][r + 1] !== -1)
gArr[c][r + 1]++;
if (c > 0 && gArr[c - 1][r] !== -1)
gArr[c - 1][r]++;
if (c < col - 1 && gArr[c + 1][r] !== -1)
gArr[c + 1][r]++;
if (r > 0 && c > 0 && gArr[c - 1][r - 1] != -1)
gArr[c - 1][r - 1]++;
if (r < row - 1 && c < col - 1 && gArr[c + 1][r + 1] != -1)
gArr[c + 1][r + 1]++;
if (r > 0 && c < col - 1 && gArr[c + 1][r - 1] != -1)
gArr[c + 1][r - 1]++;
if (r < row - 1 && c > 0 && gArr[c - 1][r + 1] != -1)
gArr[c - 1][r + 1]++;
}
return gArr;
}
//生成字符串
function strProduce(r, c) {
return `row:${c}|col:${r}`;
}
//解析雷数组字符串
function analyseStr(str) {
str = str.split('|');
str[0] = str[0].split(':');
str[1] = str[1].split(':');
return { row: str[0][1], col: str[1][1] };
}
接下来将绘制的方法写出来,这里我使用红色的方块来代表雷
//画出单个方块
function drawRct(x, y, l, r, color, container = context) {//x,y为绘制的位置,l为方块的边长,r为方块圆角半径,color为方块的填充颜色
container.beginPath();
container.moveTo(x + r, y);
container.lineTo(x + l - r, y);
container.arcTo(x + l, y, x + l, y + r, r);
container.lineTo(x + l, y + l - r);
container.arcTo(x + l, y + l, x + l - r, y + l, r);
container.lineTo(x + r, y + l);
container.arcTo(x, y + l, x, y + l - r, r);
container.lineTo(x, y + r);
container.arcTo(x, y, x + r, y, r);
container.fillStyle = color;
container.closePath();
container.fill();
container.stroke();
}
//画出方块上对应的数字
function drawNum(x, y, l, r, alPha, color = 'rgb(0,0,0)', container = context) {//参数含义与上面的方法一样,alPha为要写的数字
if (alPha === 0)
alPha = "";
container.beginPath();
container.fillStyle = color;
container.textAlign = 'center';
container.textBaseline = 'middle';
container.font = '8Px Adobe Ming Std';
container.fillText(alPha, x + l / 2, y + l / 2);
container.closePath();
}
//画出游戏结束界面
function drawEnd(row, col, R, L, P) {
for (let r = 0; r < row; r++) {
for (let c = 0; c < col; c++) {//将整个界面绘制出来
let num = gameArr[r][c];
let color;
if (num === -1)
color = 'rgb(255,0,0)';
else
color = 'rgb(255,255,255)';
drawRct(r * P, c * P, L, R, color, context);
drawNum(r * P, c * P, L, R, num);
}
}
}
接下来写出点击事件的处理,这里对于点击后的向四个方向扩展,我采用了以下图片所示的扩展
如上图片,在点击时在点击位置往四周扩散,之后上下的按上下方向继续扩散,左右的除本方向外还有往上下方向扩散,在遇到数字时停下。
canvas.onclick = function(e) {
e = e || window.e;
let x = e.clientX - canvas.offsetLeft;
let y = e.clientY - canvas.offsetTop; //获取鼠标在canvas画布上的坐标
let posX = Math.floor(x / P);
let posY = Math.floor(y / P);//将坐标转化为数组下标
if (gameArr[posX][posY] === -1 && statusArr[posX][posY] !== 2) { //点到雷
alert('error');
drawEnd(row, col, R, L, P);
} else if (statusArr[posX][posY] === 0) {
this.style.cursor = "auto";
statusArr[posX][posY] = 1;//重置状态
drawRct(posX * P, posY * P, L, R, 'rgb(255,255,255)', context);
drawNum(posX * P, posY * P, L, R, gameArr[posX][posY]);
outNum(gameArr, posY, posX, row, col, 'middle');
}
gameComplete();//游戏成功,在下面代码定义
}
//右键标记雷,取消标记,检查四周
canvas.oncontextmenu = function(e) {
e = e || window.e;
let x = e.clientX - canvas.offsetLeft;
let y = e.clientY - canvas.offsetTop; //获取鼠标在canvas画布上的坐标
let posX = Math.floor(x / P);
let posY = Math.floor(y / P);
let str = strProduce(posX, posY);
if (gameArr[posX][posY] > 0 && statusArr[posX][posY] === 1) //检查四周雷数
checkBomb(posX, posY);
if (statusArr[posX][posY] === 0) { //标记雷
drawRct(posX * P, posY * P, L, L / 2, 'rgb(255,0,0)');
statusArr[posX][posY] = 2;
signArr[signArr.length] = str;
} else if (statusArr[posX][posY] === 2) { //取消标记
drawRct(posX * P, posY * P, L, R, 'rgb(102,102,102)');
statusArr[posX][posY] = 0;
signArr = signArr.filter(item => {//使用过滤器方法将当前位置的坐标标记清除
if (item === str)
return false;
return true;
})
}
gameComplete();
return false; //阻止事件冒泡
}
//自动跳出数字
function outNum(arr, x, y, row, col, status) {//arr为传入的数组,x,y为处理的位置,row,col为游戏的行列,status用于储存扩展的方向
if (status === 'middle') {
outNumHandle(arr, x - 1, y, row, col, 'left');
outNumHandle(arr, x + 1, y, row, col, 'right');
outNumHandle(arr, x, y - 1, row, col, 'top');
outNumHandle(arr, x, y + 1, row, col, 'down');
} else if (status === 'left') {
outNumHandle(arr, x - 1, y, row, col, 'left');
outNumHandle(arr, x, y - 1, row, col, 'top');
outNumHandle(arr, x, y + 1, row, col, 'down');
} else if (status === 'right') {
outNumHandle(arr, x + 1, y, row, col, 'right');
outNumHandle(arr, x, y - 1, row, col, 'top');
outNumHandle(arr, x, y + 1, row, col, 'down');
} else if (status === 'top') {
outNumHandle(arr, x, y - 1, row, col, 'top');
} else {
outNumHandle(arr, x, y + 1, row, col, 'down');
}
}
//跳出数字具体操作
function outNumHandle(arr, x, y, row, col, status) {
if (x < 0 || x > row - 1 || y < 0 || y > col - 1) //超出边界的情况
return;
if (arr[y][x] !== 0) {
if (arr[y][x] !== -1) {
drawRct(y * P, x * P, L, R, 'rgb(255,255,255)', context);
drawNum(y * P, x * P, L, R, arr[y][x]);
statusArr[y][x] = 1;
}
return;
}
drawRct(y * P, x * P, L, R, 'rgb(255,255,255)', context);
drawNum(y * P, x * P, L, R, arr[y][x]);
statusArr[y][x] = 1;
outNum(arr, x, y, row, col, status);
}
//检查数字四周的雷的标记并操作
function checkBomb(r, c) {
//1.检查四周是否有被标记确定的位置
//2.记下标记的位置数count
//3.若count为0,则return;若count大于0,检查是否标记正确
//4.如果标记错误,提示游戏失败,若标记正确但数量不够,则return跳出,若标记正确且数量正确,将其余位置显示出来
let bombNum = gameArr[r][c];
let count = 0;
if (r > 0 && statusArr[r - 1][c] === 2) {
if (!(bombArr.includes(strProduce(r - 1, c)))) {
alert('error');
drawEnd(row, col, R, L, P);
return;
}
count++;
}
if (r < row - 1 && statusArr[r + 1][c] === 2) {
if (!(bombArr.includes(strProduce(r + 1, c)))) {
alert('error');
drawEnd(row, col, R, L, P);
return;
}
count++;
}
if (c > 0 && statusArr[r][c - 1] === 2) {
if (!(bombArr.includes(strProduce(r, c - 1)))) {
alert('error');
drawEnd(row, col, R, L, P);
return;
}
count++;
}
if (c < col - 1 && statusArr[r][c + 1] === 2) {
if (!(bombArr.includes(strProduce(r, c + 1)))) {
alert('error');
drawEnd(row, col, R, L, P);
return;
}
count++;
}
if (r > 0 && c > 0 && statusArr[r - 1][c - 1] === 2) {
if (!(bombArr.includes(strProduce(r - 1, c - 1)))) {
alert('error');
drawEnd(row, col, R, L, P);
return;
}
count++;
}
if (r < row - 1 && c < col - 1 && statusArr[r + 1][c + 1] === 2) {
if (!(bombArr.includes(strProduce(r + 1, c + 1)))) {
alert('error');
drawEnd(row, col, R, L, P);
return;
}
count++;
}
if (r > 0 && c < col - 1 && statusArr[r - 1][c + 1] === 2) {
if (!(bombArr.includes(strProduce(r - 1, c + 1)))) {
alert('error');
drawEnd(row, col, R, L, P);
return;
}
count++;
}
if (r < row - 1 && c > 0 && statusArr[r + 1][c - 1] === 2) {
if (!(bombArr.includes(strProduce(r + 1, c - 1)))) {
alert('error');
drawEnd(row, col, R, L, P);
return;
}
count++;
}
if (count !== bombNum)
return;
else {
outNotBomb(c, r);
}
}
//跳出四周非雷的方块
function outNotBomb(c, r) {
if (r > 0 && statusArr[r - 1][c] === 0) {
drawRct((r - 1) * P, c * P, L, R, 'rgb(255,255,255)', context);
drawNum((r - 1) * P, c * P, L, R, gameArr[r - 1][c]);
statusArr[r - 1][c] = 1;
}
if (r < row - 1 && statusArr[r + 1][c] === 0) {
drawRct((r + 1) * P, c * P, L, R, 'rgb(255,255,255)', context);
drawNum((r + 1) * P, c * P, L, R, gameArr[r + 1][c]);
statusArr[r + 1][c] = 1;
}
if (c > 0 && statusArr[r][c - 1] === 0) {
drawRct(r * P, (c - 1) * P, L, R, 'rgb(255,255,255)', context);
drawNum(r * P, (c - 1) * P, L, R, gameArr[r][c - 1]);
statusArr[r][c - 1] = 1;
}
if (c < col - 1 && statusArr[r][c + 1] === 0) {
drawRct(r * P, (c + 1) * P, L, R, 'rgb(255,255,255)', context);
drawNum(r * P, (c + 1) * P, L, R, gameArr[r][c + 1]);
statusArr[r][c + 1] = 1;
}
if (r > 0 && c > 0 && statusArr[r - 1][c - 1] === 0) {
drawRct((r - 1) * P, (c - 1) * P, L, R, 'rgb(255,255,255)', context);
drawNum((r - 1) * P, (c - 1) * P, L, R, gameArr[r - 1][c - 1]);
statusArr[r - 1][c - 1] = 1;
}
if (r < row - 1 && c < col - 1 && statusArr[r + 1][c + 1] === 0) {
drawRct((r + 1) * P, (c + 1) * P, L, R, 'rgb(255,255,255)', context);
drawNum((r + 1) * P, (c + 1) * P, L, R, gameArr[r + 1][c + 1]);
statusArr[r + 1][c + 1] = 1;
}
if (r > 0 && c < col - 1 && statusArr[r - 1][c + 1] === 0) {
drawRct((r - 1) * P, (c + 1) * P, L, R, 'rgb(255,255,255)', context);
drawNum((r - 1) * P, (c + 1) * P, L, R, gameArr[r - 1][c + 1]);
statusArr[r - 1][c + 1] = 1;
}
if (r < row - 1 && c > 0 && statusArr[r + 1][c - 1] === 0) {
drawRct((r + 1) * P, (c - 1) * P, L, R, 'rgb(255,255,255)', context);
drawNum((r + 1) * P, (c - 1) * P, L, R, gameArr[r + 1][c - 1]);
statusArr[r + 1][c - 1] = 1;
}
}
接着写出找到所有雷的情况,即游戏成功通关
//成功找出所有的雷
function gameComplete() {
var count = new Set(signArr).size;
if (count != bombArr.length) //雷的数量不对
{
return false;
}
for (let i of signArr) { //雷的位置不对
if (!(bombArr.includes(i))) {
return false;
}
}
for (let i of statusArr) {
if (i.includes(0)) {
return false;
}
}
alert('恭喜你成功了');
canvas.onclick = null;
canvas.onmouseover = null;
canvas.oncontextmenu = null;
}
最后调用方法画出游戏界面,这个调用要放在数组声明之前,因为数组那里也有绘制的方法,这个方法会覆盖绘制方块的画面。
drawRct(0, 0, 800, 0, 'rgb(0,0,0)', context);
关于怎么在JavaScript中使用canvas实现一个扫雷小游戏问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。