扫雷JS实现

33 篇文章 0 订阅

前几天分析了贪吃蛇的JS前端实现方案,今天分析了扫雷的实现。

因为扫雷单元格较多,获取元素设置样式频繁,所以使用Jquery库处理元素选择部分。

代码太多,CSDN 不让上传:>> 所以到github上面下载吧:https://github.com/Michael18811380328/game

下面是源代码


function Cell(row, column, opened, flagged, mined, neighborMineCount) {
  // 创建单元格对象(行列,是否打开,是否插旗,是否雷,相邻的雷的个数)ID就是行列号
  return {
    id: row + "" + column,
    row: row,
    column: column,
    opened: opened,
    flagged: flagged,
    mined: mined,
    neighborMineCount: neighborMineCount
  }
}

function Board(boardSize, mineCount) {
  // 创建棋盘对象(棋盘的尺寸,雷的个数)这里传的棋盘的尺寸是一个数值,会渲染一个正方形的棋盘(长宽相等)
  var board = {};
  for (var row = 0; row < boardSize; row++) {
    for (var column = 0; column < boardSize; column++) {
      // 每一个单元格初始化默认的空
      board[row + "" + column] = Cell(row, column, false, false, false, 0);
    }
  }
  // 随机布雷
  board = randomlyAssignMines(board, mineCount);
  // 计算相邻区域的雷的值
  board = calculateNeighborMineCounts(board, boardSize);
  return board;
}

var initializeCells = function(boardSize) {
  // 初始化单元格(传入棋盘尺寸)
  var row = 0;
  var column = 0;
  // 获取每一个单元格
  $(".cell").each(function() {
    // 设置属性、颜色、文字、背景颜色
    $(this).attr("id", row + "" + column).css('color', 'black').text("");
    $('#' + row + "" + column).css('background-image',
      'radial-gradient(#fff,#e6e6e6)');
    column++;
    // 换行
    if (column >= boardSize) {
      column = 0;
      row++;
    }

    $(this).off().click(function(e) {
      // 处理点击事件
      handleClick($(this).attr("id"));
      var isVictory = true;
      var cells = Object.keys(board);
      for (var i = 0; i < cells.length; i++) {
        if (!board[cells[i]].mined && !board[cells[i]].opened) {
          isVictory = false;
          break;
        }
      }
      if (isVictory) {
        gameOver = true;
        $('#messageBox').text('You Win!').css({
          'color': 'white',
          'background-color': 'green'
        });
        clearInterval(timeout);
      }
    });
    $(this).contextmenu(function(e) {
      handleRightClick($(this).attr("id"));
      return false;
    });
  })
}

var handleClick = function(id) {
  // 处理点击事件,区分是否按下ctrl
  if (gameOver) return;
  if (ctrlIsPressed) {
    handleCtrlClick(id);
  } else {
    handleNoCtrlClick(id);
  }
}

var handleNoCtrlClick = function(id) {
  // 根据ID获取cell对象和对应的DOM元素
  var cell = board[id];
  var $cell = $('#' + id);
  // 如果单元格已经打开或者标注旗,return
  if (cell.opened) return;
  if (cell.flagged) return;
  
  if (cell.mined) {
    // 如果点击的单元格是雷,游戏结束,所有雷区的颜色改成红色
    loss();
    // 设置雷区的颜色是红色
    $cell.html(MINE).css('color', 'red');
  } else {
    // 如果不是雷,设置 opened 
    cell.opened = true;
    if (cell.neighborMineCount > 0) {
      // 如果周边雷数量大于0,设置这个单元格的颜色和相邻类的数字
      var color = getNumberColor(cell.neighborMineCount);
      $cell.html(cell.neighborMineCount).css('color', color);
    } else {
      // 如果周边没有雷,设置默认的背景颜色
      $cell.html("").css('background-image', 'radial-gradient(#e6e6e6,#c9c7c7)');
      var neighbors = getNeighbors(id);
      // 获取周边的雷
      for (var i = 0; i < neighbors.length; i++) {
        var neighbor = neighbors[i];
        // 如果周边的单元格不是空(不是边界位置),周边的单元格不是插旗或者打开的,点击周边的全部单元格
        if (typeof board[neighbor] !== 'undefined' && !board[neighbor].flagged && !board[neighbor].opened) {
          handleClick(neighbor);
        }
      }
    }
  }
}

var handleCtrlClick = function(id) {
  // 按下ctrl点击单元格(打开这个单元格周边的全部单元格);
  var cell = board[id];
  var $cell = $('#' + id);

  // 如果这个单元格打开,并且单元格的周边雷的个数大于0
  if (cell.opened && cell.neighborMineCount > 0) {
    var neighbors = getNeighbors(id);
    var flagCount = 0;
    var flaggedCells = [];
    var neighbor;
    // 获取周边单元格、周边插旗的数量
    for (var i = 0; i < neighbors.length; i++) {
      neighbor = board[neighbors[i]];
      if (neighbor.flagged) {
        flaggedCells.push(neighbor);
      }
      flagCount += neighbor.flagged;
    }

    var lost = false;
    if (flagCount === cell.neighborMineCount) {
      for (i = 0; i < flaggedCells.length; i++) {
        if (flaggedCells[i].flagged && !flaggedCells[i].mined) {
          // 如果周边的单元格标注了,但是没有雷,游戏结束
          loss();
          lost = true;
          break;
        }
      }
      if (!lost) {
        // 如果没有结束,相邻的都被点开,并且设置ctrl属性是false
        for (var i = 0; i < neighbors.length; i++) {
          neighbor = board[neighbors[i]];
          if (!neighbor.flagged && !neighbor.opened) {
            ctrlIsPressed = false;
            handleClick(neighbor.id);
          }
        }
      }
    }
  }
}

var handleRightClick = function(id) {
  if (gameOver) return;
  var cell = board[id];
  var $cell = $('#' + id);
  if (cell.opened) return;

  // 如果没有标注,并且剩余的雷数量大于0
  if (!cell.flagged && minesRemaining > 0) {
    cell.flagged = true;
    $cell.html(FLAG).css('color', 'red');
    minesRemaining--;
    // 设置当前单元格是红旗,剩余数量减少1
  } else if (cell.flagged) {
    cell.flagged = false;
    $cell.html("").css('color', 'black');
    minesRemaining++;
  }
  // 更新剩余雷的数量
  $('#mines-remaining').text(minesRemaining);
}

var loss = function() {
  // 单击雷单元格,游戏结束
  gameOver = true;
  // 改变信息栏的文本和颜色
  $('#messageBox').text('Game Over!').css({ 'color': 'white', 'background-color': 'red' });
  var cells = Object.keys(board);
  for (var i = 0; i < cells.length; i++) {
    // 如果一个单元格是雷并且没有标识,设置触碰雷的标志并改变颜色
    if (board[cells[i]].mined && !board[cells[i]].flagged) {
      $('#' + board[cells[i]].id).html(MINE)
        .css('color', 'black');
    }
  }
  // 清空定时器
  clearInterval(timeout);
}

var randomlyAssignMines = function(board, mineCount) {
  // 随机布雷函数,设置空的雷区数组
  var mineCooridinates = [];
  for (var i = 0; i < mineCount; i++) {
    var randomRowCoordinate = getRandomInteger(0, boardSize);
    var randomColumnCoordinate = getRandomInteger(0, boardSize);
    // 获取随机的行列坐标,并设置cell的坐标
    var cell = randomRowCoordinate + "" + randomColumnCoordinate;
    // 如果已有雷区的坐标包括新单元格的坐标,循环获取,直到获取的数值不包括雷(如果雷的密度很大,这里会消耗性能)
    // 可以首先判断雷区的密度,如果密度很大,那么可以将有雷的数组传入,获取新的单元格避免这部分雷区
    // 这里可以优化成 do...while 循环
    while (mineCooridinates.includes(cell)) {
      randomRowCoordinate = getRandomInteger(0, boardSize);
      randomColumnCoordinate = getRandomInteger(0, boardSize);
      cell = randomRowCoordinate + "" + randomColumnCoordinate;
    }
    // 如果单元格没有雷,将这个单元格放在数组中,并设置对应单元格的属性是雷
    mineCooridinates.push(cell);
    board[cell].mined = true;
  }
  return board;
}

var calculateNeighborMineCounts = function(board, boardSize) {
  // 计算相邻的雷的数量
  var cell;
  var neighborMineCount = 0;
  for (var row = 0; row < boardSize; row++) {
    for (var column = 0; column < boardSize; column++) {
      // 遍历每一个单元格,获取这个单元格的ID和对应的单元格对象
      var id = row + "" + column;
      cell = board[id];
      if (!cell.mined) {
        // 如果单元格没有布雷,获取邻居
        var neighbors = getNeighbors(id);
        neighborMineCount = 0;
        // 遍历邻居,获取周边雷区的数量
        for (var i = 0; i < neighbors.length; i++) {
          neighborMineCount += isMined(board, neighbors[i]);
        }
        // 设置当前单元格的雷数量是计算的结果
        cell.neighborMineCount = neighborMineCount;
      }
    }
  }
  return board;
}

var getNeighbors = function(id) {
  // 获取一个单元格相邻八个单元格
  var row = parseInt(id[0]);
  var column = parseInt(id[1]);
  var neighbors = [];
  neighbors.push((row - 1) + "" + (column - 1));
  neighbors.push((row - 1) + "" + column);
  neighbors.push((row - 1) + "" + (column + 1));
  neighbors.push(row + "" + (column - 1));
  neighbors.push(row + "" + (column + 1));
  neighbors.push((row + 1) + "" + (column - 1));
  neighbors.push((row + 1) + "" + column);
  neighbors.push((row + 1) + "" + (column + 1));

  // 如果某一个位置的长度大于2,那么删除这个位置?
  for (var i = 0; i < neighbors.length; i++) {
    if (neighbors[i].length > 2) {
      neighbors.splice(i, 1);
      i--;
      // 不要在循环中改变循环参数
    }
  }

  return neighbors
}

var getNumberColor = function(number) {
  // 根据雷的数量获取不同的颜色
  var color = 'black';
  if (number === 1) {
    color = 'blue';
  } else if (number === 2) {
    color = 'green';
  } else if (number === 3) {
    color = 'red';
  } else if (number === 4) {
    color = 'orange';
  }
  return color;
  // 优化 var colorList = ['balck', 'blue', 'green', 'red', 'orange']; return colorList[number];
}

// 获取某个单元格是否是雷(可以优化)
var isMined = function(board, id) {
  var cell = board[id];
  var mined = 0;
  if (typeof cell !== 'undefined') {
    mined = cell.mined ? 1 : 0;
  }
  return mined;
}

// 获取随机的整数(工具函数)
var getRandomInteger = function(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
}

var newGame = function(boardSize, mines) {
  // 游戏初始化,设置文字和颜色
  $('#time').text("0");
  $('#messageBox').text('Start sweep mine and this is MC').css({
    'color': 'rgb(255, 255, 153)', 'background-color': 'rgb(102, 178, 255)'
  });
  minesRemaining = mines;
  $('#mines-remaining').text(minesRemaining);
  gameOver = false;
  // 初始化单元格
  initializeCells(boardSize);
  board = Board(boardSize, mines);
  timer = 0;
  // 设置计时器并显示时间
  clearInterval(timeout);
  timeout = setInterval(function() {
    // This will be executed after 1,000 milliseconds
    timer++;
    if (timer >= 999) {
      timer = 999;
    }
    $('#time').text(timer);
  }, 1000);
  return board;
}

var FLAG = "&#9873;"; // unicode 中小旗子标志
var MINE = "&#9881;"; // unicode 中尺寸标志(雷爆炸的效果)
var boardSize = 10; // 设置数量需要和HTML同时改动
var mines = 30;
var timer = 0;
var timeout;
var minesRemaining;

// 监听是都按下ctrl键
$(document).keydown(function(event) {
  if (event.ctrlKey)
    ctrlIsPressed = true;
});
$(document).keyup(function() {
  ctrlIsPressed = false;
});
var ctrlIsPressed = false;
var board = newGame(boardSize, mines);

// 点击开始新的游戏
$('#new-game-button').click(function() {
  board = newGame(boardSize, mines);
})

下面是HTML和CSS代码

body {
  background: rgb(102, 178, 255);
}

main {
  max-width: 610px;
  margin: auto;
}

h1 {
  text-align: center;
  color: rgb(255, 255, 153);
}

.user-display {
  text-align: center;
  color: rgb(255, 255, 153);
  border: 2px solid rgb(255, 255, 153);
  border-radius: 10px;
  display: inline-block;
  width: 15%;
  margin-right: 5px;
}

.instructions {
  text-align: left;
  padding-left: 5px;
  color: rgb(255, 255, 153);
  border: 2px solid rgb(255, 255, 153);
  border-radius: 10px;
}

.controls {
  text-align: center;
}

#messageBox {
  text-align: center;
  color: rgb(255, 255, 153);
  border: 2px solid rgb(255, 255, 153);
  border-radius: 10px;
}

.board {
  width: 520px;
  height: 520px;
  margin: 20px;
  border: 25px solid #333;
}

.cell {
  float: left;
  width: 50px;
  height: 50px;
  background-image: radial-gradient(#fff, #e6e6e6);
  border: 1px solid black;
  font-size: 50px;
  text-align: center;
  display: table-cell;
  vertical-align: middle;
  line-height: 50px;
}

.button {
  display: inline-block;
  padding: 15px 25px;
  font-size: 24px;
  cursor: pointer;
  text-align: center;
  text-decoration: none;
  outline: none;
  color: #666;
  background-color: rgb(255, 255, 153);
  border: none;
  border-radius: 15px;
  box-shadow: 0 9px #999;
}
.button:hover {
  background-color: rgb(224, 224, 139)
}
.button:active {
  background-color: rgb(224, 224, 139);
  box-shadow: 0 5px #666;
  transform: translateY(4px);
}
<html>

<head>
  <title>Minesweeper</title>
  <link rel="stylesheet" href="minesweeper.css">
  <link rel="shortcut icon" href="https://static.zhihu.com/static/favicon.ico" />
</head>

<body>
  <main>
    <h1>Minesweeper</h1>
    <div class="board">
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
      <div class="cell"></div>
    </div>
    <br>
    <strong>
      <div id="messageBox">Make a Move!</div>
    </strong>
    <br>
    <div class="instructions">
      <strong>Left Click: Open Cell<br>Right Click: Place Flag<br>Ctrl + Left Click: Open Adjacent, Non-Flagged Cells</strong>
    </div>
    <br>
    <div class="controls">
      <div class="user-display" id="time">0</div>
      <button class="button" id="new-game-button">New Game</button>
      <form action="https://mitchum.blog/sneaky-subscribe" style="display: inline-block;">
        <button class="button" type="submit">Do NOT Press</button>
      </form>
      <div class="user-display" id="mines-remaining">10</div>
    </div>
  </main>
  <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
  <script src="minesweeper.js"></script>
</body>

</html>

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值