最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

js+canvas實現(xiàn)簡單掃雷小游戲

 更新時間:2021年01月22日 11:17:15   作者:Ze明  
這篇文章主要為大家詳細介紹了js+canvas實現(xiàn)簡單掃雷小游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下

掃雷小游戲作為windows自帶的一個小游戲,受到很多人的喜愛,今天我們就來嘗試使用h5的canvas結合js來實現(xiàn)這個小游戲。

要寫游戲,首先要明確游戲的規(guī)則,掃雷游戲是一個用鼠標操作的游戲,通過點擊方塊,根據方塊的數字推算雷的位置,標記出所有的雷,打開所有的方塊,即游戲成功,若點錯雷的位置或標記雷錯誤,則游戲失敗。

具體的游戲操作如下

1.可以通過鼠標左鍵打開隱藏的方塊,打開后若不是雷,則會向四個方向擴展

2.可以通過鼠標右鍵點擊未打開的方塊來標記雷,第二次點擊取消標記

3.可以通過鼠標右鍵點擊已打開且有數字的方塊來檢查當前方塊四周的標記是否正確

接下來開始編寫代碼

首先寫好HTML的結構,這里我簡單地使用一個canvas標簽,其他內容的擴展在之后實現(xiàn)(游戲的規(guī)則,游戲的難度設置)

<!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; //雷數

為了后面的操作,我要用幾個數組來儲存一些位置,一個方塊是否為雷的數組,該數組用于描繪出整個畫面每個方塊對應的內容;一個數組用于描述方塊狀態(tài),即是否打開或者被標記;一個數組用來記載生成的雷的位置;一個數組用來記載標記的位置。

var wholeArr = drawInitialize(row, col, N, R, L, P);
var gameArr = wholeArr[0] //位置數組
var bombArr = wholeArr[1] //雷的位置數組
var statusArr = zoneInitialize(row, col); //狀態(tài)數組 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);
 }
 }
}

接下來寫出點擊事件的處理,這里對于點擊后的向四個方向擴展,我采用了以下圖片所示的擴展

如上圖片,在點擊時在點擊位置往四周擴散,之后上下的按上下方向繼續(xù)擴散,左右的除本方向外還有往上下方向擴散,在遇到數字時停下。

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;//重置狀態(tài)
 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);

一個簡單的掃雷游戲就這樣實現(xiàn)了(說實話我覺得是簡陋不是簡單。。。。) 

當然這個只是游戲的初步實現(xiàn),其實這個游戲還可以增加難度設置,用圖片來表示雷,在點到雷的時候增加聲音等等,當然這些也并不難,如果大家有興趣的話可以嘗試優(yōu)化這個游戲。

希望這篇博客能對大家有所幫助,也希望大神能指出我的不足。

附上一張丑爆的游戲界面

更多關于Js游戲的精彩文章,請查看專題:《JavaScript經典游戲 玩不?!?/a>

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • js實現(xiàn)模態(tài)窗口增加與刪除

    js實現(xiàn)模態(tài)窗口增加與刪除

    這篇文章主要為大家詳細介紹了js實現(xiàn)模態(tài)窗口增加與刪除,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • JS動態(tài)插入并立即執(zhí)行回調函數的方法

    JS動態(tài)插入并立即執(zhí)行回調函數的方法

    這篇文章主要介紹了JS動態(tài)插入并立即執(zhí)行回調函數的方法,實例分析了動態(tài)插入js文件及執(zhí)行回調函數的相關技巧,需要的朋友可以參考下
    2016-04-04
  • js parentElement和offsetParent之間的區(qū)別

    js parentElement和offsetParent之間的區(qū)別

    這里主要說的是 offsetParent 屬性,這個屬性在 MSDN 的文檔中也沒有解釋清楚,這就讓人更難理解這個屬性。 這幾天在網上找了些資料看看,再加上自己的一些測試,對此屬性有了那么一點的了解,在這里總結一下。
    2010-03-03
  • event.keyCode鍵碼值表 附只能輸入特定的字符串代碼

    event.keyCode鍵碼值表 附只能輸入特定的字符串代碼

    非常不錯的應用,讓文本框里只能輸入money大家看下具體的實現(xiàn)代碼,真是只有想到,原理很簡單。
    2009-05-05
  • JS獲取月的最后一天與JS得到一個月份最大天數的實例代碼

    JS獲取月的最后一天與JS得到一個月份最大天數的實例代碼

    本篇文章主要是對JS獲取月的最后一天與JS得到一個月份最大天數的實例代碼進行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2013-12-12
  • javascript 嵌套的函數(作用域鏈)

    javascript 嵌套的函數(作用域鏈)

    當你進行函數的嵌套時,要注意實際上作用域鏈是發(fā)生變化的,這點可能看起來不太直觀。你可把下面的代碼置入firebug監(jiān)視值的變化。
    2010-03-03
  • JavaScript 32位整型無符號操作示例

    JavaScript 32位整型無符號操作示例

    所有整數字變量默認都是有符號整數,JavaScript 進行位操作時,是采用32位有符號整型,這意味著其轉換的結果也是32位有符號整型
    2013-12-12
  • 一道超經典js面試題Foo.getName()的故事

    一道超經典js面試題Foo.getName()的故事

    Foo.getName算是一道比較老的面試題了,大致百度了一下在17年就有相關文章在介紹它,下面這篇文章主要給大家介紹了關于一道超經典js面試題Foo.getName()的相關資料,需要的朋友可以參考下
    2022-03-03
  • 淺談Javascript數組(推薦)

    淺談Javascript數組(推薦)

    數組,即Array類型,是開發(fā)中最常用的類型之一。這篇文章主要介紹了淺談Javascript數組的相關資料,需要的朋友可以參考下
    2016-05-05
  • JS數組reduce你不得不知道的25個高級用法

    JS數組reduce你不得不知道的25個高級用法

    reduce作為ES5新增的常規(guī)數組方法之一,對比forEach 、filter和map,在實際使用上好像有些被忽略,下面這篇文章主要給大家介紹了關于JS數組reduce你不得不知道的25個高級用法,需要的朋友可以參考下
    2021-06-06

最新評論

三门县| 炎陵县| 孝感市| 博罗县| 左云县| 湖南省| 襄樊市| 宁远县| 湘西| 天峻县| 电白县| 东乡| 卢龙县| 涿州市| 孟连| 开鲁县| 独山县| 清丰县| 古丈县| 万载县| 台南县| 仁布县| 德江县| 新乡县| 吉安市| 浮梁县| 建始县| 聂拉木县| 大姚县| 同德县| 治多县| 灵宝市| 灵丘县| 南汇区| 陆川县| 津市市| 汾西县| 阳新县| 科技| 车险| 甘孜|