记忆游戏错误。当我重新启动游戏时,它无法正常运行

Memory Game Bug. When I restart my game it doesn't function properly

我创建了一个记忆游戏,您可以在其中尝试匹配成对的卡片。它在第一场比赛中运行正常,但之后如果我点击一张牌,它会自动匹配。我在这个错误上花了很多时间,并且一直在努力解决它。任何建议将不胜感激。谢谢!

Memory Game CodePen

let symbol = [
  "diamond",
  "paper-plane-o",
  "anchor",
  "bolt",
  "cube",
  "leaf",
  "bomb",
  "bicycle",
];
let symbols = [...symbol, ...symbol];
const deck = document.querySelector(".deck");
const cards = deck.getElementsByTagName("li");
const stars = document.querySelector(".stars");
const restart = document.querySelector(".restart");
const timer = document.querySelector(".timer");
let starRating = 3;
let seconds = 0;
let opened = [];
let moves = 0;
let matches = 0;
const totalMatches = symbols.length / 2;
let counter;

function startGame() {
  // Remove cards from previous game if any
  while (deck.firstChild) {
    deck.removeChild(deck.firstChild);
  }

  // Shuffle symbols
  let shuffleDeck = shuffle(symbols);

  // Reset Stats Function call
  resetStats();

  // Loop through symbols and add cards to deck w/ icons
  for (let i = 0; i < shuffleDeck.length; i++) {
    let card = document.createElement("li");
    card.className = "card";
    let icon = document.createElement("i");
    icon.className = "fa fa-" + shuffleDeck[i];
    card.appendChild(icon);
    deck.appendChild(card);
  }

  cardListener();

  resetTimer(counter);
  seconds = 0;
  startTimer();
}

function shuffle(array) {
  var currentIndex = array.length,
    temporaryValue,
    randomIndex;

  while (currentIndex !== 0) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

// Function for star ratings
function starRater(moves) {
  starRating = 3;
  if (moves >= 12 && moves < 18) {
    document.querySelector("#star_1").className = "fa fa-star-o";
    starRating = 2;
  } else if (moves >= 18 && moves < 25) {
    document.querySelector("#star_2").className = "fa fa-star-o";
    starRating = 1;
  } else if (moves >= 25) {
    document.querySelector("#star_3").className = "fa fa-star-o";
    starRating = 0;
  }
}

// Function to reset stats
function resetStats() {
  // Start all moves and card-matches at a default value of 0
  matches = 0;
  moves = 0;
  document.querySelector(".moves").innerText = moves;

  // Replace any stars lost in previous games and give starRating default value of 3
  starRating = 3;
  document.querySelector("#star_1").className = "fa fa-star";
  document.querySelector("#star_2").className = "fa fa-star";
  document.querySelector("#star_3").className = "fa fa-star";
}

// Create function to add event listener to deck and delegate events to all cards
const cardListener = function() {
  deck.addEventListener("click", function(e) {
    let card = e.target;

    // Makes sure that target can only be a card
    if (card.tagName != "LI") return;

    // If card has class name show or match return true
    if (
      card.className === "card open show" ||
      card.className === "card match"
    ) {
      return true;
    }

    // If card is true then push into opened array
    if (card) {
      card.className = "card open show animated flipInY";
      opened.push(card);
    }
    // Conditional that if opened has two items in array then run conditional to see if they match
    if (opened.length > 1) {
      // If the items match then iterate through the cards in deck and change class names to card match
      if (card.firstChild.className === opened[0].firstChild.className) {
        setTimeout(function() {
          for (let x = 0; x < cards.length; x++) {
            if (cards[x].className === "card open show animated flipInY") {
              cards[x].className = "card match animated pulse";
            }
          }
        }, 1000);
        matches++;
        // If the items do not match iterate through the cards and change class names back to just card add a delay so the user can see the second card they click on for a moment
      } else {
        setTimeout(function() {
          for (let x = 0; x < cards.length; x++) {
            if (cards[x].className === "card open show animated flipInY") {
              cards[x].className = "card animated flipInX";
              setTimeout(function(){
                cards[x].className = "card";
              }, 200);
            };
          }
        }, 1000);
      };
      moves++;
      document.querySelector(".moves").innerText = moves;
      starRater(moves);
      opened = [];
    }

    // Conditional to check to see if it's the end of the game
    if (totalMatches === matches) {
      endGame(moves, starRating);
    }
  });
};

// Reset Game Logic
restart.addEventListener("click", function() {
  swal({
    title: "Do you want to restart the game?",
    text: "The clock is ticking!",
    buttons: ["Nope!", "Restart!"],
    dangerMode: true
  }).then(function(isConfirm) {
    if (isConfirm) {
      startGame();
    }
  });
});

// Function to start timer
function startTimer() {
  counter = setInterval(function() {
    timer.innerText = seconds;
    seconds += 1;
  }, 1000);
}

// Function to reset timer
function resetTimer(counter) {
  if (counter) {
    clearInterval(counter);
  }
}

// // Function to reset cards
// function resetCards(){
//
// }

// End game function
function endGame(moves, starRating) {
  swal({
    icon: "success",
    title: "You did it!!",
    text:
      "It took " +
      moves +
      " moves and you got a " +
      starRating +
      " Star Rating in " +
      seconds +
      " seconds.\n Party on Garth. Do you want to play again?",
    buttons: ["Nope!", "Play Again!"]
  }).then(function(isConfirm) {
    if (isConfirm) {
      startGame();
    }
  });
  resetTimer(counter);
}

// Initialize game
startGame();
html {
  box-sizing: border-box;
}

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

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  background: #ffffff url("../img/geometry2.png"); /* Background pattern from Subtle Patterns */
  font-family: "Coda", cursive;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

h1 {
  font-family: "Open Sans", sans-serif;
  font-weight: 300;
}

/*
 * Styles for the deck of cards
 */

.deck {
  width: 660px;
  min-height: 680px;
  background: linear-gradient(160deg, #02ccba 0%, #aa7ecd 100%);
  padding: 32px;
  border-radius: 10px;
  box-shadow: 12px 15px 20px 0 rgba(46, 61, 73, 0.5);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  margin: 0 0 3em;
}

.deck .card {
  height: 125px;
  width: 125px;
  background: #2e3d49;
  font-size: 0;
  color: #ffffff;
  border-radius: 8px;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  box-shadow: 5px 2px 20px 0 rgba(46, 61, 73, 0.5);
}

.deck .card.open {
  transform: rotateY(0);
  background: #02b3e4;
  cursor: default;
}

.deck .card.show {
  font-size: 33px;
}

.deck .card.match {
  cursor: default;
  background: #02ccba;
  font-size: 33px;
}

/*
 * Styles for the Score Panel
 */

.score-panel {
  text-align: left;
  width: 345px;
  margin-bottom: 10px;
}

.score-panel .stars {
  margin: 0;
  padding: 0;
  display: inline-block;
  margin: 0 5px 0 0;
}

.score-panel .stars li {
  list-style: none;
  display: inline-block;
}

.score-panel .restart {
  float: right;
  cursor: pointer;
}
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Matching Game</title>
  <meta name="description" content="">
  <link rel="stylesheet prefetch" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css">
  <link rel="stylesheet prefetch" href="https://fonts.googleapis.com/css?family=Coda">
  <link rel="stylesheet" href="css/app.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.5.2/animate.min.css">
  </head>

<body>

  <div class="container">
    <header>
      <h1>Matching Game</h1>
    </header>

    <section class="score-panel">
      <ul class="stars">
        <li><i id="star_1" class="fa fa-star star-items"></i></li>
        <li><i id="star_2" class="fa fa-star star-items"></i></li>
        <li><i id="star_3" class="fa fa-star star-items"></i></li>
      </ul>

      <span class="moves">0</span> Moves - Time: <span class="timer">0</span>s

      <div class="restart">
        <i class="fa fa-repeat"></i>
      </div>
    </section>

    <ul class="deck"></ul>
  </div>

  <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
  <script src="js/app.js"></script>
</body>

</html>

我测试后发现我忘记从甲板上删除事件侦听器。因此,当我调用添加监听器的函数时,它添加了一个新的事件监听器,导致匹配发生,因为被点击的单张卡片被推入数组两次。