点击查看html编辑器说明文档

纯JS写的扫雷小游戏edit icon

|
|
Fork(复制)
|
|
作者:
穿越者X57

👉 新版编辑器已上线,点击进行体验吧!

BUG反馈
嵌入
设置
下载
HTML
格式化
支持Emmet,输入 p 后按 Tab键试试吧!
<head> ...
展开
</head>
<body>
            
              <svg id="main" class="hide">
    <symbol id="bomb-svg" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 786.7 687.8" width="50px">
      <path d="M427.4,144.2l40.7-41.8a23.1,23.1,0,0,1,32.5,0l72.8,72.8a23.1,23.1,0,0,1,0,32.5l-37.4,37.4" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
      <circle cx="291.1" cy="385.2" r="282.6" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
      <path d="M540.2,141.2l15.4-20.5c12.6-16.8,30.4-17.7,43.6-2.4l0.3,0.3c11.8,13.7,31.3,22.6,48.3,11.4,6.1-4,20.7-20.5,20.7-20.5" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
      <line x1="701.7" y1="63.3" x2="742" y2="23.1" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
      <line x1="713.2" y1="107.4" x2="766.2" y2="128.2" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
      <line x1="654.5" y1="60.2" x2="630.8" y2="8.5" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
      <path d="M82,396.8c0-118.4,95.9-214.3,214.3-214.3" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
      <g id="Happy_face" data-name="Happy face">
        <path d="M170.4,432.1a34.6,34.6,0,0,1,69.2,0" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
        <path d="M342.5,432.1a34.6,34.6,0,0,1,69.2,0" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
        <path d="M367,481.7c0,33.7-33.4,64.1-74.6,64.1s-74.6-30.3-74.6-64.1" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17"/>
      </g>
    </symbol>
  </svg>
  <div id="background"></div>
  <div id="modal">
      <div id="result-box">
        <div id="result-top">
          <svg id="bomb" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 774.7 676.2">
            <path d="M427.4,144.2l40.7-41.8a23.1,23.1,0,0,1,32.5,0l72.8,72.8a23.1,23.1,0,0,1,0,32.5l-37.4,37.4" transform="translate(0 -11.6)" fill="#8b6af5" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke bomb-fill"/>
            <circle cx="291.1" cy="385.2" r="282.6" fill="#8b6af5" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke bomb-fill"/>
            <path d="M540.2,141.2l15.4-20.5c12.6-16.8,30.4-17.7,43.6-2.4l0.3,0.3c11.8,13.7,31.3,22.6,48.3,11.4,6.1-4,20.7-20.5,20.7-20.5" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
            <line x1="701.7" y1="63.3" x2="742" y2="23.1" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
            <line x1="713.2" y1="107.4" x2="766.2" y2="128.2" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
            <line x1="654.5" y1="60.2" x2="630.8" y2="8.5" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
            <path d="M82,396.8c0-118.4,95.9-214.3,214.3-214.3" transform="translate(0 -11.6)" fill="none" stroke="#fff" stroke-linecap="round" stroke-miterlimit="10" stroke-opacity="0.45" stroke-width="17"/>
            <g id="happy-face" class="happy-face">
              <path d="M170.4,432.1a34.6,34.6,0,0,1,69.2,0" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
              <path d="M342.5,432.1a34.6,34.6,0,0,1,69.2,0" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
              <path d="M367,481.7c0,33.7-33.4,64.1-74.6,64.1s-74.6-30.3-74.6-64.1" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
            </g>
            <g id="sad-face" class="sad-face">
              <g>
                <circle cx="377.1" cy="406" r="17.7" fill="#39395b" class="bomb-fill-dark"/>
                <path d="M250,517.1c0-19.2,17.6-42.5,41.1-42.5s43.8,23.3,43.8,42.5" transform="translate(0 -11.6)" fill="none" stroke="#39395b" stroke-linecap="round" stroke-miterlimit="10" stroke-width="17" class="bomb-stroke"/>
                <circle cx="205" cy="406" r="17.7" fill="#39395b" class="bomb-fill-dark"/>
              </g>
            </g>
          </svg>
          <h1 id="result-message"></h1>
          <h2 class="result-time">Your time: <span class="time-display"></span> seconds</h2>
        </div>
        <div id="new-game">
          <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 512 512" enable-background="new 0 0 512 512" fill="#fff" width="30">
            <g>
              <path d="M480.6,235.6c-11.3,0-20.4,9.1-20.4,20.4c0,112.6-91.6,204.2-204.2,204.2c-112.6,0-204.2-91.6-204.2-204.2   S143.4,51.8,256,51.8c61.5,0,118.5,27.1,157.1,73.7h-70.5c-11.3,0-20.4,9.1-20.4,20.4s9.1,20.4,20.4,20.4h114.6   c11.3,0,20.4-9.1,20.4-20.4V31.4c0-11.3-9.1-20.4-20.4-20.4s-20.4,9.1-20.4,20.4v59C390.7,40.1,325.8,11,256,11   C120.9,11,11,120.9,11,256c0,135.1,109.9,245,245,245s245-109.9,245-245C501,244.7,491.9,235.6,480.6,235.6z"/>
            </g>
          </svg>
          <h2>重新开始</h2>
        </div>
      </div>
  </div>
  <div class="container">
    <div class="header">
      <div class='dropdown'>
        <div class='title'>初级</div>
        <div class='menu'>
          <div class='option' id='option1'>初级</div>
          <div class='option' id='option2'>中级</div>
          <div class='option' id='option3'>高级</div>
        </div>
      </div>
      <div id='flag-countdown'><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 287.987 287.987" fill="#fff" width="30" style="enable-background:new 0 0 287.987 287.987;" xml:space="preserve"><g><path d="M228.702,141.029c-3.114-3.754-3.114-9.193,0-12.946l33.58-40.474c2.509-3.024,3.044-7.226,1.374-10.783   c-1.671-3.557-5.246-5.828-9.176-5.828h-57.647v60.98c0,16.618-13.52,30.138-30.138,30.138h-47.093v25.86   c0,5.599,4.539,10.138,10.138,10.138h124.74c3.93,0,7.505-2.271,9.176-5.828c1.671-3.557,1.135-7.759-1.374-10.783L228.702,141.029   z"/><path d="M176.832,131.978V25.138c0-5.599-4.539-10.138-10.138-10.138H53.37c0-8.284-6.716-15-15-15s-15,6.716-15,15   c0,7.827,0,253.91,0,257.987c0,8.284,6.716,15,15,15s15-6.716,15-15c0-6.943,0-126.106,0-130.871h113.324   C172.293,142.116,176.832,137.577,176.832,131.978z"/></g></svg><span id='flags-left'></span></div>
      <div id="timer"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 559.98 559.98"  fill="#fff" width="30px" style="enable-background:new 0 0 559.98 559.98;" xml:space="preserve"><g><path d="M279.99,0C125.601,0,0,125.601,0,279.99c0,154.39,125.601,279.99,279.99,279.99c154.39,0,279.99-125.601,279.99-279.99    C559.98,125.601,434.38,0,279.99,0z M279.99,498.78c-120.644,0-218.79-98.146-218.79-218.79    c0-120.638,98.146-218.79,218.79-218.79s218.79,98.152,218.79,218.79C498.78,400.634,400.634,498.78,279.99,498.78z"/><path d="M304.226,280.326V162.976c0-13.103-10.618-23.721-23.716-23.721c-13.102,0-23.721,10.618-23.721,23.721v124.928    c0,0.373,0.092,0.723,0.11,1.096c-0.312,6.45,1.91,12.999,6.836,17.926l88.343,88.336c9.266,9.266,24.284,9.266,33.543,0    c9.26-9.266,9.266-24.284,0-33.544L304.226,280.326z"/></g></svg><span class="counter">000</span></div>
    </div>
    <div class="grid"></div>
  </div>
        
</body>
SCSS
格式化
            
            $main: var(--main-color);
$dark: var(--dark-color);
$tile: #f3f1ff;

:root {
  --grid-width: 350;
  --tile-width: 35;
  --main-color: #8B6AF5;
}

body {
  padding: 0;
  margin: 0;
  background: #f9f8fe;
  width: 100%;
  height:100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-family: 'Roboto Mono', monospace;
}

*,
*:before,
*:after {
  box-sizing: border-box;
}

.container {
  width: calc(var(--grid-width) * 1px);
  align-content: center;
  text-align: center;
  box-shadow: 5px 5px 20px 0 rgba(100, 74, 74, 0.1);
  position: relative;
  z-index:2;
}

.header {
  display: flex;
  position: relative;
  align-items: flex-start;
  background-color: $main;
  color: white;
  justify-content: space-between;
  padding:1rem;

}

.grid {
  height: calc(var(--grid-width) * 1px);
  width: 100%;
  display: flex;
  flex-wrap: wrap;
}

.tile {
  height: calc(var(--tile-width) * 1px);
  width: calc(var(--tile-width)  * 1px);
  cursor: pointer;
  border: 2px solid;
  border-color: lighten($tile, 5%) darken($tile, 5%) darken($tile, 5%) lighten($tile, 5%);
  box-sizing: border-box;
  background-color: $tile;
  font-weight: 700;
  font-size: 25px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.tile svg{
  width: 80%;
}

.checked {
  border: 1px solid;
  background-color: darken($tile, 2%);
  border-color: darken($tile, 5%);
}

#refresh {
  cursor:pointer;
  width:30px;
  align-self: flex-end;
}

.dropdown {
  color: white;
  background-color: rgba(255, 255, 255, 0.2);
  border-radius: 3px;
  font-family: Verdana, Geneva, Tahoma, sans-serif;
  font-size: 1rem;
  text-align: center;

  .title {
    width: 100%;
    padding:0.5rem 1rem;
    cursor: pointer;
  }

  .menu {
    background:$main;
    position:absolute;
    overflow: hidden;
    cursor: pointer;
    display: none;
    width: 5rem;
    text-align: left;
    line-height:1.4rem;
    &.show {
      display:block;
    }
    .option{
      padding: .5rem;
      &:hover {
        background: rgba(255, 255, 255, 0.2);
      }
    }
  }
}

.has-bomb {
  transition: background .25s ease-in;
}

#flag-countdown, #timer {
  display: flex;
  font-size:35px;
  span {
    margin-left:0.5rem;
  }
}

#modal {
  position: fixed;
  background-color: rgba(#39395b, 0.2);
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 999;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transition: all 0.3s;
  display: flex;
  align-items: center;
  justify-content: center;
  &.show {
    visibility: visible;
    opacity: 1;
    pointer-events: auto;
  }
  .modal-close {
    text-align: right;
  }
  h2 {
    color: $main;
  }
}

#result-box {
  background-color: #f9f8fe;
  box-shadow: 5px 5px 20px 0 rgba(100, 74, 74, 0.1);
  border-radius: 4px;
  min-width: 400px;
  text-align: center;
}

#result-top {
  margin:2rem;
}

#result-message {
  color: $dark;
  font-size:40px;
}

.result-time {
  display: none;
}

.show {
  display: block;
}

#new-game {
  padding:0.5rem;
  background-color: $main;
  color: #fff;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  margin:1rem;
  height:60px;
  border-radius: 4px;
  font-family: sans-serif;
  * {
    display: inline-block;
  }
  h2 {
    line-height:30px;
    margin: 0 0 0 1rem;
    color: white;
  }
}

#background {
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
  z-index: 1;
  position: absolute;
  z-index: 1;
  width: 100%;
  height: 100%;
  svg {
    width: 50px;
    height: 40px;
    opacity: 0.3;
    position: absolute;
  }
}

svg#bomb{
  width: 200px;
  .sad-face, .happy-face {
    display: none;
    &.show {
      display: block;
    }
  }
  .bomb-fill {
    fill: $main;
  }
  .bomb-stroke {
    stroke: $dark;
  }
  .bomb-fill-dark {
    fill: $dark;
  }
}

svg.hide {
  display: none;
}

.shake {
  animation: shake 0.75s cubic-bezier(.38,.06,.22,.95) both;
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  perspective: 1000px;
}

@keyframes shake {
  10%, 90% {
    transform: translate3d(-1px, 0, 0);
  }
  
  20%, 80% {
    transform: translate3d(2px, 0, 0);
  }

  30%, 50%, 70% {
    transform: translate3d(-3px, 0, 0);
  }

  40%, 60% {
    transform: translate3d(2px, 0, 0);
  }
}
        
JS
格式化
            
            // watch Ania Kubow's tutorial here: https://www.youtube.com/watch?v=rxdGAKRndz8

document.addEventListener("DOMContentLoaded", () => {
  const grid = document.querySelector(".grid");
  const container = document.querySelector(".container");
  const flagsLeft = document.querySelector("#flags-left");
  const result = document.querySelector("#result-message");
  const modal = document.querySelector("#modal");
  const background = document.querySelector("#background");
  const bombSadFace = document.querySelector("#sad-face");
  const bombHappyFace = document.querySelector("#happy-face");
  const timer = document.querySelector(".counter");
  const finalTimeDisplay = document.querySelector(".time-display");
  const resultTime = document.querySelector(".result-time");

  const levels = [
    {
      name: "初级",
      width: 10,
      bombs: 15,
    },
    {
      name: "中级",
      width: 15,
      bombs: 30,
    },
    {
      name: "高级",
      width: 20,
      bombs: 70,
    },
  ];
  const flagIcon =
    '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 287.987 287.987" fill="#695ca8" style="enable-background:new 0 0 287.987 287.987;" xml:space="preserve"><g><path d="M228.702,141.029c-3.114-3.754-3.114-9.193,0-12.946l33.58-40.474c2.509-3.024,3.044-7.226,1.374-10.783   c-1.671-3.557-5.246-5.828-9.176-5.828h-57.647v60.98c0,16.618-13.52,30.138-30.138,30.138h-47.093v25.86   c0,5.599,4.539,10.138,10.138,10.138h124.74c3.93,0,7.505-2.271,9.176-5.828c1.671-3.557,1.135-7.759-1.374-10.783L228.702,141.029   z"/><path d="M176.832,131.978V25.138c0-5.599-4.539-10.138-10.138-10.138H53.37c0-8.284-6.716-15-15-15s-15,6.716-15,15   c0,7.827,0,253.91,0,257.987c0,8.284,6.716,15,15,15s15-6.716,15-15c0-6.943,0-126.106,0-130.871h113.324   C172.293,142.116,176.832,137.577,176.832,131.978z"/></g></svg>';
  const bombIcon =
    '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 512 512" fill="#695ca8" ><g><path d="m411.313,123.313c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32-9.375,9.375-20.688-20.688c-12.484-12.5-32.766-12.5-45.25,0l-16,16c-1.261,1.261-2.304,2.648-3.31,4.051-21.739-8.561-45.324-13.426-70.065-13.426-105.867,0-192,86.133-192,192s86.133,192 192,192 192-86.133 192-192c0-24.741-4.864-48.327-13.426-70.065 1.402-1.007 2.79-2.049 4.051-3.31l16-16c12.5-12.492 12.5-32.758 0-45.25l-20.688-20.688 9.375-9.375 32.001-31.999zm-219.313,100.687c-52.938,0-96,43.063-96,96 0,8.836-7.164,16-16,16s-16-7.164-16-16c0-70.578 57.422-128 128-128 8.836,0 16,7.164 16,16s-7.164,16-16,16z"/><path d="m459.02,148.98c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l16,16c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16.001-16z"/><path d="m340.395,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16-16c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l15.999,16z"/><path d="m400,64c8.844,0 16-7.164 16-16v-32c0-8.836-7.156-16-16-16-8.844,0-16,7.164-16,16v32c0,8.836 7.156,16 16,16z"/><path d="m496,96.586h-32c-8.844,0-16,7.164-16,16 0,8.836 7.156,16 16,16h32c8.844,0 16-7.164 16-16 0-8.836-7.156-16-16-16z"/><path d="m436.98,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688l32-32c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32c-6.251,6.25-6.251,16.375-0.001,22.625z"/></g></svg>';

  let root = document.documentElement;
  let selectedLevel = levels[0];
  let flags = 0;
  let tiles = [];
  let isGameOver = false;
  let isTimerOn = false;
  let timerCount = 0;
  let finalTime = 0;

  let numberColors = [
    "#8B6AF5",
    "#74c2f9",
    "#42dfbc",
    "#f9dd5b",
    "#FEAC5E",
    "#ff5d9e",
    "#F29FF5",
    "#c154d8",
  ];
  let bgColors = [
    "#b39ffd",
    "#93c1fd",
    "#8af1f8",
    "#f9dd5b",
    "#FEAC5E",
    "#f87dae",
    "#f6b8f8",
    "#f7efce",
  ];

  function getMainColor() {
    const randomColor =
      numberColors[Math.floor(Math.random() * numberColors.length)];
    root.style.setProperty("--main-color", randomColor);
    root.style.setProperty(
      "--dark-color",
      lightenDarkenColor(randomColor, -50)
    );
  }

  function clearBoard() {
    isGameOver = false;
    flags = 0;
    if (isTimerOn) stopTimer();
    timerCount = 0;

    // isTimerOn = false;
    timer.innerHTML = "000";
    tiles.forEach((tile) => {
      tile.remove();
    });
    tiles = [];
    container.classList.remove("shake");
    bombHappyFace.classList.remove("show");
    bombSadFace.classList.remove("show");
    resultTime.classList.remove("show");
    createBoard();
  }

  function setUp() {
    createBackground();
    createBoard();
  }

  function createBoard() {
    getMainColor();

    flagsLeft.innerHTML = selectedLevel.bombs;
    const width = selectedLevel.width;
    const tileWidth = parseInt(
      getComputedStyle(root).getPropertyValue("--tile-width")
    );
    root.style.setProperty("--grid-width", width * tileWidth);

    //add tiles
    for (let i = 0; i < width * width; i++) {
      const tile = document.createElement("div");
      tile.setAttribute("id", i);
      tile.classList.add("tile");
      grid.appendChild(tile);
      tiles.push(tile);

      //left click
      tile.addEventListener("click", () => clickTile(tile));

      //ctrl and right click
      tile.oncontextmenu = (e) => {
        e.preventDefault();
        addFlag(tile);
      };
    }

    //add bombs
    const randomTiles = tiles
      .sort(() => Math.random() - 0.5)
      .slice(0, selectedLevel.bombs)
      .map((tile) => tile.id); // suffle tile's id and select 20 firsts to get 20 random tiles
    tiles.forEach((tile) =>
      randomTiles.includes(tile.id)
        ? tile.classList.add("has-bomb")
        : tile.classList.add("is-empty")
    ); // if tile's id is in ramdomTile array, add bomb
    tiles.sort((a, b) => a.id - b.id); //sort array by id again

    //add numbers
    for (let i = 0; i < tiles.length; i++) {
      let total = 0;
      const isLeftEdge = i % width === 0;
      const isRightEdge = i % width === width - 1;

      if (!tiles[i].classList.contains("has-bomb")) {
        if (!isLeftEdge) {
          if (tiles[i - 1] && tiles[i - 1].classList.contains("has-bomb"))
            total++;
          if (
            tiles[i - 1 + width] &&
            tiles[i - 1 + width].classList.contains("has-bomb")
          )
            total++;
          if (
            tiles[i - 1 - width] &&
            tiles[i - 1 - width].classList.contains("has-bomb")
          )
            total++;
        }

        if (!isRightEdge) {
          if (tiles[i + 1] && tiles[i + 1].classList.contains("has-bomb"))
            total++;
          if (
            tiles[i + 1 + width] &&
            tiles[i + 1 + width].classList.contains("has-bomb")
          )
            total++;
          if (
            tiles[i + 1 - width] &&
            tiles[i + 1 - width].classList.contains("has-bomb")
          )
            total++;
        }

        if (tiles[i - width] && tiles[i - width].classList.contains("has-bomb"))
          total++;
        if (tiles[i + width] && tiles[i + width].classList.contains("has-bomb"))
          total++;

        tiles[i].setAttribute("data", total);
      }
    }
  }

  //click on tile
  function clickTile(tile) {
    if (!isTimerOn) startTimer();
    let currentId = tile.id;
    if (isGameOver) return;
    if (tile.classList.contains("checked") || tile.classList.contains("flag"))
      return;
    if (tile.classList.contains("has-bomb")) {
      gameOver(tile);
    } else {
      let total = tile.getAttribute("data");
      if (total != 0) {
        tile.classList.add("checked");
        tile.style.color = numberColors[total - 1];
        tile.style.textShadow =
          "1px 1px" + lightenDarkenColor(numberColors[total - 1], -20);
        tile.innerHTML = total;
        return;
      }
      checktile(currentId);
    }
    tile.classList.add("checked");
  }

  //check neighboring tiles once tile is clicked
  function checktile(currentId) {
    const width = selectedLevel.width;
    const isLeftEdge = currentId % width === 0;
    const isRightEdge = currentId % width === width - 1;
    const parsedId = parseInt(currentId);

    function loopThroughtiles(tileId) {
      const newId = tileId.id;
      const newTile = document.getElementById(newId);
      clickTile(newTile);
    }
    setTimeout(() => {
      if (!isRightEdge) {
        if (tiles[parsedId + 1 - width])
          loopThroughtiles(tiles[parsedId + 1 - width]);
        if (tiles[parsedId + 1]) loopThroughtiles(tiles[parsedId + 1]);
        if (tiles[parsedId + 1 + width])
          loopThroughtiles(tiles[parsedId + 1 + width]);
      }
      if (!isLeftEdge) {
        if (tiles[parsedId - 1]) loopThroughtiles(tiles[parsedId - 1]);
        if (tiles[parsedId - 1 - width])
          loopThroughtiles(tiles[parsedId - 1 - width]);
        if (tiles[parsedId - 1 + width])
          loopThroughtiles(tiles[parsedId - 1 + width]);
      }
      if (tiles[parsedId - width]) loopThroughtiles(tiles[parsedId - width]);
      if (tiles[parsedId + width]) loopThroughtiles(tiles[parsedId + width]);
    }, 50);
  }

  //game over
  function gameOver(currentTile) {
    isGameOver = true;
    stopTimer();
    currentTile.innerHTML = bombIcon;
    container.classList.add("shake");
    currentTile.style.backgroundColor =
      bgColors[Math.floor(Math.random() * bgColors.length)];
    currentTile.classList.remove("has-bomb");
    currentTile.classList.add("checked");
    let itemsProcessed = 0;

    //show all the bombs
    const bombTiles = tiles.filter((tile) =>
      tile.classList.contains("has-bomb")
    );
    bombTiles.forEach((tile, index) => {
      setTimeout(() => {
        tile.innerHTML = bombIcon;
        tile.style.backgroundColor =
          bgColors[Math.floor(Math.random() * bgColors.length)];
        tile.classList.remove("has-bomb");
        tile.classList.add("checked");
        itemsProcessed++;
        if (itemsProcessed === bombTiles.length) {
          setTimeout(() => {
            modal.classList.add("show");
            bombSadFace.classList.add("show");
            result.innerHTML = "游戏结束!";
          }, 1000);
        }
      }, 10 * index);
    });
  }

  //add Flag with right click
  function addFlag(tile) {
    if (isGameOver) return;
    if (!tile.classList.contains("checked") && flags < selectedLevel.bombs) {
      if (!tile.classList.contains("flag")) {
        tile.classList.add("flag");
        tile.innerHTML = flagIcon;
        flags++;
        flagsLeft.innerHTML = selectedLevel.bombs - flags;
        checkForWin();
      } else {
        tile.classList.remove("flag");
        tile.innerHTML = "";
        flags--;
        flagsLeft.innerHTML = selectedLevel.bombs - flags;
      }
    }
  }

  //check for win
  function checkForWin() {
    let matches = 0;
    tiles.forEach((tile) => {
      if (
        tile.classList.contains("flag") &&
        tile.classList.contains("has-bomb")
      ) {
        matches++;
      }
      if (matches === selectedLevel.bombs) {
        stopTimer();
        modal.classList.add("show");
        bombHappyFace.classList.add("show");
        resultTime.classList.add("show");
        result.innerHTML = "恭喜!";
        isGameOver = true;

        // reveal all remaining tiles
        if (!tile.classList.contains("checked")) {
          tile.classList.add("checked");
        }
      }
    });
  }

  function replay() {
    if (modal.classList.contains("show")) modal.classList.remove("show");
    clearBoard();
  }

  document.querySelector("#new-game").addEventListener("click", replay);

  // timer functions
  function startTimer() {
    isTimerOn = true;
    let sec = 0;
    timerCount = setInterval(() => {
      sec++;
      timer.innerHTML = ("00" + sec).slice(-3);
      if (sec > 998) clearInterval(timerCount);
      finalTime = sec;
    }, 1000);
  }

  function stopTimer() {
    clearInterval(timerCount);
    isTimerOn = false;
    finalTimeDisplay.innerHTML = finalTime;
  }

  // dropdown menu functions
  const dropdownTitle = document.querySelector(".dropdown .title");
  const dropdownOptions = document.querySelectorAll(".dropdown .option");

  function toggleMenuDisplay(e) {
    const dropdown = e.target.parentNode; //getting the parent selector
    const menu = dropdown.querySelector(".menu"); //selecting 'menu' fron the parent selector
    menu.classList.toggle("show");
  }

  function handleOptionSelected(e) {
    e.target.parentNode.classList.toggle("show"); // using parentnode to get the menu  elementfrom option elements
    dropdownTitle.textContent = e.target.textContent;
    // here: select current level form levels object
    selectedLevel = levels.find((level) => level.name === e.target.textContent);
    clearBoard();
  }

  // event listeners
  dropdownTitle.addEventListener("click", toggleMenuDisplay);
  dropdownOptions.forEach((option) =>
    option.addEventListener("click", handleOptionSelected)
  );

  // bg functions
  function addElement(x, y) {
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
    use.setAttributeNS(
      "http://www.w3.org/1999/xlink",
      "xlink:href",
      "#bomb-svg"
    );
    svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
    svg.setAttribute("style", "top: " + y + "px; left: " + x + "px");
    svg.appendChild(use);
    background.appendChild(svg);
  }

  function createBackground() {
    const spacing = 60;
    const w = window.innerWidth;
    const h = window.innerHeight;
    for (let y = 0; y <= h; y += spacing) {
      if (y % (spacing * 2) === 0) {
        for (let x = 0; x <= w; x += spacing) {
          addElement(x, y);
        }
      } else {
        for (let x = -(spacing / 2); x <= w; x += spacing) {
          addElement(x, y);
        }
      }
    }
  }

  //helper function
  function lightenDarkenColor(col, amt) {
    let usePound = false;
    if (col[0] == "#") {
      col = col.slice(1);
      usePound = true;
    }
    let num = parseInt(col, 16);
    let r = (num >> 16) + amt;
    if (r > 255) r = 255;
    else if (r < 0) r = 0;
    let b = ((num >> 8) & 0x00ff) + amt;
    if (b > 255) b = 255;
    else if (b < 0) b = 0;
    let g = (num & 0x0000ff) + amt;
    if (g > 255) g = 255;
    else if (g < 0) g = 0;
    return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16);
  }

  setUp();
});

        
预览
控制台