JavaScript

超轻量级php框架startmvc

js实现扫雷小程序的示例代码

更新时间:2020-06-08 05:36 作者:startmvc
初学javascript,写了一个扫雷程序练练手!扫雷规则及功能扫雷想必大家都不陌生,就是windo

初学javascript,写了一个扫雷程序练练手!

扫雷规则及功能

扫雷想必大家都不陌生,就是windows上点击排雷的小游戏,它的主要规则有

1.左键点击显示当前格子是否为雷,如果为雷的话,GameOver啦,如果不是雷的话,这个格子会显示周围八个格子内的雷数量。 2.鼠标右键标记,标记可能的雷,标记了之后取消需要再次右键点击该格子,左键无效果。 3.鼠标中键(滚轮)按下后,快捷扫雷(如果周围雷数和未被标记且未被翻开的格子相等,会将这些格子一并翻开)

主要功能基本完全复刻了windows7扫雷的功能

扫雷github地址:扫雷github地址

扫雷算法

1.首先我定义了一个构造函数,里面是一系列的属性:


 var mineCraft = function(num1,num2,mine_num,obj,type){
 this.num1 = num1; //整个扫雷的行数
 this.num2 = num2; //整个扫雷的列数 
 this.mine_num = mine_num; //雷的个数
 this.tiles = []; //数组里面存放的是每个小格子
 this.obj = obj; //扫雷放置的对象
 this.flag = true; 
 this.arr = [];
 this.arr_2 = [];
 this.time_dsq = null; 
 this.time_dc ='';
 this.time_arr = [[],[],[]]; //时间统计信息
 this.details = [[],[],[]]; // 游戏统计详情
 this.type = type; //游戏类型:初级/中级/高级/自定义
 this.buildTiles(); //创建游戏函数
 };

2.在页面上创建扫雷的界面 函数buildTiles


 buildTiles:function(){
 this.obj.style.width = 51*this.num1+'px'; //在传进来的对象上画整体格子,每个小格子51px大小,总大小就为个数*单个大小
 this.obj.style.height = 51*this.num2+'px';
 var indexOfdiv = 0; 
 for(var i = 0;i<this.num2;i++){
 for(var j = 0;j<this.num1;j++){
 var tile = document.createElement('div');
 tile.className = 'tile'; //定义小格子class
 tile.index = indexOfdiv; //为每个小格子添加索引
 this.tiles[indexOfdiv] = tile; //将小格子存入数组中
 indexOfdiv++;
 this.obj.appendChild(tile); //将小格子插入到整个扫雷界面中 
 }
 }
 this.obj.oncontextmenu = function(){ //取消浏览器的默认右键菜单事件
 return false;
 }
 this.event(); //点击事件
 },

3.绑事件函数:


 event : function(){
 var _this = this;
 this.obj.onmouseover = function(e){ //鼠标悬停事件---
 if(e.target.className == 'tile'){
 e.target.className = 'tile current';
 }
 }
 this.obj.onmouseout = function(e){ //鼠标移出事件--
 if(e.target.className == 'tile current'){
 e.target.className = 'tile';
 }
 }
 this.obj.onmousedown = function(e){ //鼠标按下事件
 var index = e.target.index;
 if(e.button == 1){ //e.button属性 左键0/中键1/右键2
 event.preventDefault(); //取消默认
 }
 _this.changeStyle(e.button,e.target,index);
 }
 this.obj.onmouseup = function(e){ //鼠标弹起事件
 if(e.button == 1){
 _this.changeStyle(3,e.target);
 }
 }
 },

4.点击调用的函数:


 changeStyle:function(num1,obj,num_index){ 
 if(num1 == 0){ //是左键的话
 if(this.flag){ //this.flag 是之前定义的用于判断是否为第一次点击
 this.store(num_index); //store函数,存放被点击的格子周围的8个格子
 this.setMineCraft(this.mine_num,this.arr,num_index); //如果是第一次点击 即调用布雷函数 更改flag状态
 this.flag = false;
 this.detail_statistics(0,false); //开始信息统计函数
 } 
 if(obj.className != 'tile'&&obj.className !='tile current'){//如果不是第一次点击,被点击的格子不是未点击状态,无效
 return false;
 }
 if(obj.getAttribute('val') == 0){ //如果不是雷。改为翻开状态
 obj.className = "showed"; 
 obj.innerHTML = obj.getAttribute('value') == 0?'':obj.getAttribute('value'); //显示周围雷数
 this.showAll(obj.index); //递归函数判断周围格子的情况,就是扫雷游戏上一点开会出现一片的那种
 }
 if(this.over(obj)){ //判断游戏是否结束
 this.last(); 
 }
 }
 if(num1 == 2){ //右键标记事件
 if(obj.className == 'biaoji'){
 obj.className = 'tile';
 }else if(obj.className !='biaoji'&&obj.className != 'showed'){
 obj.className = 'biaoji';
 }
 }
 if(num1 == 1){ // 中键事件
 if(obj.className =="showed"){
 this.show_zj1(obj.index);
 }
 }
 if(num1 == 3){ //鼠标弹起事件
 
 if (obj.className == "showed") {
 var flag1 = this.show_zj2(obj.index,0);
 }else{
 this.show_zj2(obj.index,1)
 return false;
 }

 if(flag1&&this.over()){ //弹起判断是否结束
 this.last();
 }
 }
 },

5.布雷:我之前的布雷是在页面加载在buildTiles()的时候布雷的,但是这样会导致有可能你电机的第一个格子就是雷(游戏性不强),后来修改到第一次点击完成之后布雷(确保第一下点的不是雷),避开直接炸死的现象.所以把调用放在后面的event后触发的changeStyle函数中


 setMineCraft:function(num,arr_first,num_first){ //雷的个数、最开始被点击的格子周围的八个、被点击的那个格子
 var arr_index = []; 
 for(var i = 0;i<arr_first.length;i++){
 arr_index.push(arr_first[i].index);
 }
 var length = this.tiles.length;
 for (var i = 0; i < length; i++) {
 this.tiles[i].setAttribute("val", 0);
 }
 for (var i = 0; i < num; i++) { 
 var index_Mine = Math.floor(Math.random() * this.tiles.length);
 if(index_Mine == num_first||arr_index.lastIndexOf(index_Mine)>-1){//如果是属于第一次点击的周围的直接跳过在该位置布雷
 num++;
 continue;
 }
 
 if (this.tiles[index_Mine].getAttribute("val") == 0) {
 this.tiles[index_Mine].setAttribute("val", 1);
 }else {
 num++;
 }
 }
 this.showValue();
 this.event()
 },

6.存储周围格子的函数:


 store : function(num) { //传入格子的index.
 var tiles_2d = [];
 var indexs = 0;
 for(var i = 0;i<this.num2;i++){
 tiles_2d.push([]);
 for(var j = 0;j<this.num1;j++){
 tiles_2d[i].push(this.tiles[indexs]);
 indexs++;
 } 
 }
 var j = num % this.num1;
 var i = (num - j) / this.num1;
 this.arr = [];
 //左上
 if (i - 1 >= 0 && j - 1 >= 0) {
 this.arr.push(tiles_2d[i - 1][j - 1]);
 }
 //正上
 if (i - 1 >= 0) {
 this.arr.push(tiles_2d[i - 1][j]);
 }
 //右上
 if (i - 1 >= 0 && j + 1 <= this.num1-1) {
 this.arr.push(tiles_2d[i - 1][j + 1]);
 }
 //左边
 if (j - 1 >= 0) {
 this.arr.push(tiles_2d[i][j - 1]);
 }
 //右边
 if (j + 1 <= this.num1-1) {
 this.arr.push(tiles_2d[i][j + 1]);
 }
 //左下
 if (i + 1 <= this.num2-1 && j - 1 >= 0) {
 this.arr.push(tiles_2d[i + 1][j - 1]);
 }
 //正下
 if (i + 1 <= this.num2-1) {
 this.arr.push(tiles_2d[i + 1][j]);
 }
 //右下
 if (i + 1 <= this.num2-1 && j + 1 <= this.num1-1) {
 this.arr.push(tiles_2d[i + 1][j + 1]);
 }
 },

7.showAll函数:作用是如果该格子周围没有雷,自动翻开周围8个格子,然后再判断周围八个格子的周围8隔格子是否有雷,利用了递归的方法  


 showAll:function(num){
 if (this.tiles[num].className == "showed" && this.tiles[num].getAttribute("value") == 0){
 this.store(this.tiles[num].index);
 var arr2 = new Array();
 arr2 = this.arr;
 for (var i = 0; i < arr2.length; i++) {
 if (arr2[i].className != "showed"&&arr2[i].className !='biaoji') {
 if (arr2[i].getAttribute("value") == 0) {
 arr2[i].className = "showed";
 this.showAll(arr2[i].index);
 } else {
 arr2[i].className = "showed";
 arr2[i].innerHTML = arr2[i].getAttribute("value");
 }
 }
 }
 }
 },

8.show_zj函数:主要是中键按钮的作用中键点击后的函数,这里的show_zj1是鼠标键按下后的显示效果, show_zj2函数就是


 show_zj1:function(num){
 this.store(this.tiles[num].index);
 for (var i = 0; i < this.arr.length; i++) {
 if (this.arr[i].className == "tile") {
 this.arr_2.push(this.arr[i]);
 this.arr[i].className = "showed";
 // this.arr[i].className = "test";
 }
 }
 },
 show_zj2:function(num,zt){
 
 var count = 0;
 this.store(this.tiles[num].index); 
 
 for(var i = 0,len = this.arr_2.length;i<len;i++){
 this.arr_2[i].className = 'tile'; //按下效果恢复原状
 }

 this.arr_2.length = 0;
 for(var i = 0;i<this.arr.length;i++){
 this.arr[i].className == 'biaoji'&&count++;
 }
 if(zt == 1){
 return false;
 }
 var numofmines = this.tiles[num].getAttribute("value");
 if(numofmines == count){ //如果周围雷数和周围被标记数相等就翻开周围的格子
 var arr = new Array(this.arr.length);
 for(var i = 0;i<this.arr.length;i++){
 arr[i] = this.arr[i];
 }
 for (var i = 0,length = arr.length; i < length; i++) { 
 if (arr[i].className == "tile" && arr[i].getAttribute("val") != 1) {//如果周围格子无雷则继续。
 arr[i].className = "showed";
 arr[i].innerHTML = arr[i].getAttribute("value") == 0?"":arr[i].getAttribute("value");
 this.showAll(arr[i].index);
 } else if (arr[i].className == "tile" && arr[i].getAttribute("val") == 1) { //如果周围格子有雷,游戏结束
 this.over(arr[i]);
 this.last();
 return false;
 }
 }
 }
 return true;
 },

9.结束判断:


 over:function(obj){
 var flag = false;
 var showed = document.getElementsByClassName('showed'); 
 var num = this.tiles.length - this.mine_num; 
 if(showed.length == num){ //如果被排出来的格子数等于总格子数-雷数,这游戏成功结束 
 this.detail_statistics(1,true); //游戏统计 ,true代表胜,false,代表失败
 alert('恭喜你获得成功');
 flag = true;
 }else if(obj&&obj.getAttribute('val') == 1){ //如果被点击的是雷,则炸死
 this.detail_statistics(1,false);
 alert('被炸死!');
 flag = true;

 }
 return flag;
 },

10.结束后的显示函数:


 last:function(){ 
 var len = this.tiles.length;
 for(var i = 0;i<len;i++){
 this.tiles[i].className = this.tiles[i].getAttribute('val') == 1?'boom':'showed';
 if(this.tiles[i].className != 'boom'){ //
 this.tiles[i].innerHTML = this.tiles[i].getAttribute('value') == 0?'':this.tiles[i].getAttribute('value');
 }
 }
 this.obj.onclick = null;
 this.obj.oncontextmenu = null;
 },

11 统计信息:还是比较全的和windows7扫雷版的判断项目是一样的,使用的是每次结束游戏后将数据存入localStorage中,


 //已玩游戏,已胜游戏,胜率,最多连胜,最多连败,当前连局;
 detail_statistics:function(num,zt){
 var time_pay = 1;
 var _this = this;
 if(num == 0){
 this.time_dsq = setInterval(function(){
 $('#time_need').text(time_pay);
 _this.time_dc =time_pay;
 time_pay++;
 },1000);
 
 }
 else if(num == 1){
 clearInterval(this.time_dsq);
 if(this.type == 4){return false;}
 if(localStorage.details == undefined){ 
 localStorage.details = JSON.stringify([[0,0,0,0,0,0],[0,0,0,0,0,0],[0,0,0,0,0,0]]); //这里存放的就是上面注释中的六项数据
 }
 if(JSON.parse(localStorage.details) instanceof Array){
 this.details = JSON.parse(localStorage.details); 
 }
 this.details[this.type][0] += 1;
 if(zt == false){
 if(this.details[this.type][5]>=0){
 this.details[this.type][5] = -1;
 }else{
 this.details[this.type][5] -= 1;
 } 
 if(this.details[this.type][5]<this.details[this.type][4]){
 this.details[this.type][4] = this.details[this.type][5];
 }
 this.details[this.type][2] = this.toPercent(this.details[this.type][2]/this.details[this.type][0]); 
 localStorage.details = JSON.stringify(this.details);
 return false;
 }

 if(this.details[this.type][5]>=0){
 this.details[this.type][5] += 1;
 }else{
 this.details[this.type][5] = 1;
 }
 if(this.details[this.type][5]>this.details[this.type][3]){
 this.details[this.type][3] = this.details[this.type][5];
 }
 this.details[this.type][3] += 1;
 this.details[this.type][2] = this.toPercent(this.details[this.type][4]/this.details[this.type][0]);
 localStorage.details = JSON.stringify(this.details);
 
 var time1 = new Date(); 
 var time_str = time1.getFullYear()+'/'+time1.getMonth()+'/'+time1.getDate()+' '+time1.getHours()+':'+time1.getMinutes();
 if(localStorage.time == undefined){
 localStorage.time = JSON.stringify([[],[],[]]);
 }
 if(JSON.parse(localStorage.time) instanceof Array){
 this.time_arr = JSON.parse(localStorage.time);
 }

 this.time_arr[this.type].push([this.time_dc,time_str]);
 this.time_arr[this.type].sort(function(a,b){
 return a[0]-b[0];
 });
 if(this.time_arr[this.type].length>5){
 this.time_arr[this.type].pop();
 }
 localStorage.time = JSON.stringify(this.time_arr);
 
 }
 },

扫雷的主要部分就是这些了,还有一些小功能包括没写来,要看完整的可以看gitHub

之前看书学习总觉得学了就忘,感觉懂了公式却不知道怎么用,写完扫雷小程序觉得收获了很多

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。