Alert fires out of sequence with rest of code

This simple card match game works fine for the most part, with the exception of when it fires off the alert advising whether a match has been found or not.

Following the function calls through, createCards(); sets up a click event to isTwoCards(), which displays the cards, before it makes a call to isMatch(), which in turn calls the alert. But it doesn’t seem to want to honour that sequence. What am I missing?

###HTML:

<nav class="clearfix">
  <a href="#instructions">Instructions</a>
  <a href="#game-board">Game</a>
</nav>
<h1>Memory Game</h1>
<h2 id="instructions">Instructions</h2>
<p>Concentration, also known as Match Match, Memory, Pelmanism, Shinkei-suijaku, Pexeso or simply Pairs, is a card game in which all of the cards are laid face down on a surface and two cards are flipped face up over each turn. The object of the game is
  to turn over pairs of matching cards.</p>
<h2>Why play?</h2>
<p>This will help you develop your observational skills, memory and reflexes</p>
<hr>
<div id="game-board" class="board"></div>
<button id="resetButton" class="resetButton_invisible">Play Again?</button>
<footer>Created with <span id="heart">&hearts;</span> by <span id="name">Chris Perry</span></footer>

###CSS:

body {
  text-align: center;
}

h1 {
  color: rgb(2, 132, 130);
}

h2 {
  color: #ffd700;
}

nav {
  background: #808080;
}

a {
  background: #ffbf00;
  color: #fff;
  padding: 35px;
  display: block;
  float: left;
}

img {
  height: 200px;
  width: 150px;
}

a:hover {
  background: #ee5f3c;
}

footer {
  background: #808080;
}

#heart {
  color: #f00;
}

#name {
  color: #f00;
}

.resetButton_invisible {
  display: block;
  visibility: hidden;
  margin: 0 auto 30px;
}

.resetButton_visible {
  display: block;
  margin: 0 auto 30px;
}

.board {
  height: 50%;
  width: 50%;
  display: inline-block;
}

.card {
  background-color: yellow;
  border: 1px solid black;
  border-radius: 10px;
  float: left;
  height: 200px;
  margin: 8%;
  width: 150px;
}

.clearfix:after {
  visibility: hidden;
  display: block;
  content: " ";
  clear: both;
  height: 0;
  font-size: 0;
}

##JavaScript

var cards = ['queen', 'queen', 'king', 'king'];
var cardsInPlay = [];
var resetButton = document.getElementById('resetButton');

// From the article "The only way to shuffle an array in JavaScript"
// https://www.frankmitchell.org/2015/01/fisher-yates/

function shuffle(array) {
  'use strict';
  var i = 0,
    j = 0,
    temp = null;

  for (i = array.length - 1; i > 0; i -= 1) {
    j = Math.floor(Math.random() * (i + 1));
    temp = array[i];
    array[i] = array[j];
    array[j] = temp;
  }
}

shuffle(cards);

var isMatch = function(selectedCard) {
  'use strict';
  // alert winning/losing message
  if (selectedCard[0] === selectedCard[1]) {
    alert('You found a match!');
  } else {
    alert('Sorry, try again.');
  }
};

var resetBoard = function() {
  'use strict';
  // reset array
  cardsInPlay = []; // clear cards in play array for next try
  // Remove card images
  var faceDown = document.getElementsByClassName('card');
  for (var k = 0; k < faceDown.length; k++) {
    faceDown[k].innerHTML = '';
  }
  // Re-oder the cards array
  shuffle(cards);
  // apply revised array to the cards on the board
  for (var l = 0; l < cards.length; l++) {
    var cardReset = document.getElementsByClassName('card'); // create a new div tag
    cardReset[l].setAttribute('data-card', cards[l]); // set the data-card to its card value, king or queen
  }
  // Hide reset button again
  resetButton.className = 'resetButton_invisible';
};

resetButton.addEventListener('click', resetBoard);

var isTwoCards = function() {
  'use strict';
  // add card to array of cards in play
  cardsInPlay.push(this.getAttribute('data-card'));
  console.log(this.getAttribute('data-card'));
  if (this.getAttribute('data-card') === 'king') {
    this.innerHTML = '<img src="https://pixabay.com/static/uploads/photo/2013/05/11/08/22/playing-card-110298_640.jpg">'; // king
  } else {
    this.innerHTML = '<img src="https://pixabay.com/static/uploads/photo/2013/05/11/08/23/playing-card-110300_640.jpg">'; //queen
  }

  // if you have two cards in play check for a match
  if (cardsInPlay.length === 2) {
    isMatch(cardsInPlay); // pass the cardsInPlay as an argument to isMatch function
    // make reset button visible
    resetButton.className = 'resetButton_visible';
    // resetBoard();
  }
};

// Create board & cards
var createCards = function() {
  'use strict';
  var gameBoard = document.getElementById('game-board');

  for (var i = 0; i < cards.length; i++) {
    var cardElement = document.createElement('div'); // create a new div tag
    cardElement.className = 'card'; // give the div a class name
    cardElement.setAttribute('data-card', cards[i]); // set the data-card to its card value, king or queen
    cardElement.addEventListener('click', isTwoCards); // add event listener to the card
    gameBoard.appendChild(cardElement); // add the div to the game bord
  }
};

createCards();

You’ll have to ignore the image hot-linking message for the moment, whilst I sort out some replacements.

This is purely a guess, but it is likely due to the img having to load and is thus a network request, so what is happening, is your JS is continuing to fire as the network request starts to happen and the alert then pauses everything and when the alert is cleared the network request has finished and is thus loaded into view.

To solve this, you might have to put that alert logic in a load event of the image you are loading.

I think that is something like

// this goes in your isTwoCards
var img = $('<img src="...">');
img.on('load', checkCardStatus);
this.innerHTML = img.html(); // maybe?

var checkCardStatus = function() {
  'use strict';
  // if you have two cards in play check for a match
  if (cardsInPlay.length === 2) {
    isMatch(cardsInPlay); // pass the cardsInPlay as an argument to isMatch function
    // make reset button visible
    resetButton.className = 'resetButton_visible';
    // resetBoard();
  }
}

Just my theory.

I think I follow that. I’ll do a bit of testing tomorrow when my head’s more awake. :slight_smile:

One small question, I’m used to seeing $ symbols in jQuery code, but not so much vanilla JS. Is there some specific reason for using it here?

Oh, that was jQuery, I think the native version is document.createElement or something along those lines.

You will also have to replace img.on with img.addEventListener

1 Like

Finally got some time to concentrate on this, but it’s not quite going to plan. I reverse-engineered the jQuery as best I could, and ended up with this, but it isn’t quite doing as I’d expect it to. The JS snippet below shows those parts of the code I’ve altered.

var checkCardStatus = function() {
  'use strict';
  // if you have two cards in play check for a match
  if (cardsInPlay.length === 2) {
    // pass the cardsInPlay as an argument to isMatch function
    isMatch(cardsInPlay);
    // make reset button visible
    resetButton.className = 'resetButton_visible';
  }
};
************************

var isTwoCards = function() {
  'use strict';
  // add card to array of cards in play
  cardsInPlay.push(this.getAttribute('data-card'));
  console.log(this.getAttribute('data-card'));
  if (this.getAttribute('data-card') === 'king') {
    var imgKing = document.createElement('img');
    imgKing.setAttribute('src', 'king.jpg');
    imgKing.addEventListener('load', checkCardStatus);
    this.innerHTML = imgKing; // king
    console.log(imgKing);
  }
  else {
    var imgQueen = document.createElement('img');
    imgQueen.setAttribute('src', 'queen.jpg');
    imgQueen.addEventListener('load', checkCardStatus);
    this.innerHTML = imgQueen; //queen
    console.log(imgQueen);
  }
};

Whilst it looks like it should be amenable to something more concise (DRY), it…

  • causes no errors in the console
  • does not show any images
  • instead shows [object HTMLImageElement]
  • though the console.log(imgQueen/imgKing) correctly shows the result <img src="queen.jpg">
  • it still stops at the alert before showing the content of the 2nd click

This is what you see in the console when a card is clicked

I amended the order of the append, and the event listener happening within isTwoCards(). It still gives no error, and the [object HTMLImageElement] text has gone, but there’s still no sign of the <img> tag being added to the markup, though it seems to console.log() OK.

Colour me baffled…

var isTwoCards = function() {
  'use strict';
  // add card to array of cards in play
  cardsInPlay.push(this.getAttribute('data-card'));
  console.log(this.getAttribute('data-card'));
  if (this.getAttribute('data-card') === 'king') {
    var imgKing = document.createElement('img');
    imgKing.setAttribute('src', 'king.jpg');
    this.appendChild = imgKing; // king
    imgKing.addEventListener('load', checkCardStatus);
    console.log(imgKing);
  }
  else {
    var imgQueen = document.createElement('img');
    imgQueen.setAttribute('src', 'queen.jpg');
    this.appendChild = imgQueen; //queen
    imgQueen.addEventListener('load', checkCardStatus);
    console.log(imgQueen);
  }
};

The Codepen with this in it is below

I think we can call this closed - thanks @fretburner - there was an error in my use of appendChild, and it needed a setTimeout value adding.

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.