2015-10-19 11:17:38 +02:00
|
|
|
|
H5P.MemoryGame = (function (EventDispatcher, $) {
|
2014-03-28 14:52:41 +01:00
|
|
|
|
|
2017-01-23 14:12:33 +01:00
|
|
|
|
// We don't want to go smaller than 100px per card(including the required margin)
|
|
|
|
|
var CARD_MIN_SIZE = 100; // PX
|
|
|
|
|
var CARD_STD_SIZE = 116; // PX
|
|
|
|
|
var STD_FONT_SIZE = 16; // PX
|
|
|
|
|
var LIST_PADDING = 1; // EMs
|
2017-09-22 16:54:08 +02:00
|
|
|
|
var numInstances = 0;
|
2017-01-23 14:12:33 +01:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
/**
|
2015-10-19 11:17:38 +02:00
|
|
|
|
* Memory Game Constructor
|
2014-03-28 14:52:41 +01:00
|
|
|
|
*
|
2017-02-13 14:42:34 +01:00
|
|
|
|
* @class H5P.MemoryGame
|
|
|
|
|
* @extends H5P.EventDispatcher
|
2014-03-28 14:52:41 +01:00
|
|
|
|
* @param {Object} parameters
|
|
|
|
|
* @param {Number} id
|
|
|
|
|
*/
|
2015-10-19 11:17:38 +02:00
|
|
|
|
function MemoryGame(parameters, id) {
|
2017-02-13 14:42:34 +01:00
|
|
|
|
/** @alias H5P.MemoryGame# */
|
2014-03-28 14:52:41 +01:00
|
|
|
|
var self = this;
|
|
|
|
|
|
2015-10-19 11:17:38 +02:00
|
|
|
|
// Initialize event inheritance
|
|
|
|
|
EventDispatcher.call(self);
|
2014-03-28 14:52:41 +01:00
|
|
|
|
|
2019-01-02 11:40:27 +01:00
|
|
|
|
var flipped, timer, counter, popup, $bottom, $taskComplete, $feedback, $wrapper, maxWidth, numCols, audioCard;
|
2015-10-19 11:17:38 +02:00
|
|
|
|
var cards = [];
|
2017-02-13 15:41:06 +01:00
|
|
|
|
var flipBacks = []; // Que of cards to be flipped back
|
|
|
|
|
var numFlipped = 0;
|
2015-10-19 11:17:38 +02:00
|
|
|
|
var removed = 0;
|
2017-09-22 16:54:08 +02:00
|
|
|
|
numInstances++;
|
2014-03-28 14:52:41 +01:00
|
|
|
|
|
2017-09-25 14:38:14 +02:00
|
|
|
|
// Add defaults
|
|
|
|
|
parameters = $.extend(true, {
|
|
|
|
|
l10n: {
|
|
|
|
|
cardTurns: 'Card turns',
|
|
|
|
|
timeSpent: 'Time spent',
|
|
|
|
|
feedback: 'Good work!',
|
|
|
|
|
tryAgain: 'Reset',
|
|
|
|
|
closeLabel: 'Close',
|
|
|
|
|
label: 'Memory Game. Find the matching cards.',
|
|
|
|
|
done: 'All of the cards have been found.',
|
|
|
|
|
cardPrefix: 'Card %num: ',
|
|
|
|
|
cardUnturned: 'Unturned.',
|
|
|
|
|
cardMatched: 'Match found.'
|
|
|
|
|
}
|
|
|
|
|
}, parameters);
|
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
/**
|
2015-10-19 11:17:38 +02:00
|
|
|
|
* Check if these two cards belongs together.
|
2014-03-28 14:52:41 +01:00
|
|
|
|
*
|
2015-10-19 11:17:38 +02:00
|
|
|
|
* @private
|
|
|
|
|
* @param {H5P.MemoryGame.Card} card
|
|
|
|
|
* @param {H5P.MemoryGame.Card} mate
|
|
|
|
|
* @param {H5P.MemoryGame.Card} correct
|
2014-03-28 14:52:41 +01:00
|
|
|
|
*/
|
|
|
|
|
var check = function (card, mate, correct) {
|
2017-02-13 15:41:06 +01:00
|
|
|
|
if (mate !== correct) {
|
|
|
|
|
// Incorrect, must be scheduled for flipping back
|
|
|
|
|
flipBacks.push(card);
|
|
|
|
|
flipBacks.push(mate);
|
|
|
|
|
|
|
|
|
|
// Wait for next click to flip them back…
|
|
|
|
|
if (numFlipped > 2) {
|
|
|
|
|
// or do it straight away
|
|
|
|
|
processFlipBacks();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2017-02-13 15:41:06 +01:00
|
|
|
|
// Update counters
|
|
|
|
|
numFlipped -= 2;
|
|
|
|
|
removed += 2;
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2017-02-13 15:41:06 +01:00
|
|
|
|
var isFinished = (removed === cards.length);
|
2016-05-26 14:07:14 +02:00
|
|
|
|
|
2017-09-22 16:54:08 +02:00
|
|
|
|
// Remove them from the game.
|
|
|
|
|
card.remove(!isFinished);
|
|
|
|
|
mate.remove();
|
|
|
|
|
|
|
|
|
|
var desc = card.getDescription();
|
2017-02-13 15:41:06 +01:00
|
|
|
|
if (desc !== undefined) {
|
|
|
|
|
// Pause timer and show desciption.
|
|
|
|
|
timer.pause();
|
2017-05-08 15:14:18 +02:00
|
|
|
|
var imgs = [card.getImage()];
|
|
|
|
|
if (card.hasTwoImages) {
|
|
|
|
|
imgs.push(mate.getImage());
|
|
|
|
|
}
|
2017-09-25 14:38:14 +02:00
|
|
|
|
popup.show(desc, imgs, cardStyles ? cardStyles.back : undefined, function (refocus) {
|
2017-02-13 15:41:06 +01:00
|
|
|
|
if (isFinished) {
|
|
|
|
|
// Game done
|
2017-09-22 16:54:08 +02:00
|
|
|
|
card.makeUntabbable();
|
2017-02-13 15:41:06 +01:00
|
|
|
|
finished();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Popup is closed, continue.
|
|
|
|
|
timer.play();
|
2017-09-25 14:38:14 +02:00
|
|
|
|
|
|
|
|
|
if (refocus) {
|
|
|
|
|
card.setFocus();
|
|
|
|
|
}
|
2017-02-13 15:41:06 +01:00
|
|
|
|
}
|
|
|
|
|
});
|
2014-03-28 14:52:41 +01:00
|
|
|
|
}
|
2017-02-13 15:41:06 +01:00
|
|
|
|
else if (isFinished) {
|
|
|
|
|
// Game done
|
2017-09-22 16:54:08 +02:00
|
|
|
|
card.makeUntabbable();
|
2017-02-13 15:41:06 +01:00
|
|
|
|
finished();
|
2014-03-28 14:52:41 +01:00
|
|
|
|
}
|
2014-09-16 14:33:14 +02:00
|
|
|
|
};
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2017-02-13 14:42:34 +01:00
|
|
|
|
/**
|
|
|
|
|
* Game has finished!
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
var finished = function () {
|
|
|
|
|
timer.stop();
|
2017-09-25 14:38:14 +02:00
|
|
|
|
$taskComplete.show();
|
2017-09-22 16:54:08 +02:00
|
|
|
|
$feedback.addClass('h5p-show'); // Announce
|
|
|
|
|
$bottom.focus();
|
2017-06-20 13:17:49 +02:00
|
|
|
|
|
|
|
|
|
// Create and trigger xAPI event 'completed'
|
|
|
|
|
var completedEvent = self.createXAPIEventTemplate('completed');
|
|
|
|
|
completedEvent.setScoredResult(1, 1, self, true, true);
|
|
|
|
|
completedEvent.data.statement.result.duration = 'PT' + (Math.round(timer.getTime() / 10) / 100) + 'S';
|
|
|
|
|
self.trigger(completedEvent);
|
|
|
|
|
|
2017-02-13 14:42:34 +01:00
|
|
|
|
if (parameters.behaviour && parameters.behaviour.allowRetry) {
|
|
|
|
|
// Create retry button
|
2017-06-20 14:25:31 +02:00
|
|
|
|
var retryButton = createButton('reset', parameters.l10n.tryAgain || 'Reset', function () {
|
2017-02-13 14:42:34 +01:00
|
|
|
|
// Trigger handler (action)
|
|
|
|
|
|
2017-06-20 14:25:31 +02:00
|
|
|
|
retryButton.classList.add('h5p-memory-transout');
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
// Remove button on nextTick to get transition effect
|
|
|
|
|
$wrapper[0].removeChild(retryButton);
|
|
|
|
|
}, 300);
|
2017-02-13 14:42:34 +01:00
|
|
|
|
|
2017-06-20 14:25:31 +02:00
|
|
|
|
resetGame();
|
2017-02-13 14:42:34 +01:00
|
|
|
|
});
|
2017-06-20 14:25:31 +02:00
|
|
|
|
retryButton.classList.add('h5p-memory-transin');
|
|
|
|
|
setTimeout(function () {
|
2017-09-22 16:54:08 +02:00
|
|
|
|
// Remove class on nextTick to get transition effectupd
|
2017-06-20 14:25:31 +02:00
|
|
|
|
retryButton.classList.remove('h5p-memory-transin');
|
|
|
|
|
}, 0);
|
2017-02-13 14:42:34 +01:00
|
|
|
|
|
|
|
|
|
// Same size as cards
|
2017-06-20 14:29:23 +02:00
|
|
|
|
retryButton.style.fontSize = (parseFloat($wrapper.children('ul')[0].style.fontSize) * 0.75) + 'px';
|
2017-02-13 14:42:34 +01:00
|
|
|
|
|
|
|
|
|
$wrapper[0].appendChild(retryButton); // Add to DOM
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shuffle the cards and restart the game!
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
2017-10-17 16:16:46 +02:00
|
|
|
|
var resetGame = function () {
|
2017-02-13 14:42:34 +01:00
|
|
|
|
|
|
|
|
|
// Reset cards
|
|
|
|
|
removed = 0;
|
|
|
|
|
|
|
|
|
|
// Remove feedback
|
|
|
|
|
$feedback[0].classList.remove('h5p-show');
|
2017-09-25 14:38:14 +02:00
|
|
|
|
$taskComplete.hide();
|
2017-02-13 14:42:34 +01:00
|
|
|
|
|
|
|
|
|
// Reset timer and counter
|
|
|
|
|
timer.reset();
|
|
|
|
|
counter.reset();
|
|
|
|
|
|
|
|
|
|
// Randomize cards
|
|
|
|
|
H5P.shuffleArray(cards);
|
|
|
|
|
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
// Re-append to DOM after flipping back
|
|
|
|
|
for (var i = 0; i < cards.length; i++) {
|
|
|
|
|
cards[i].reAppend();
|
|
|
|
|
}
|
2017-09-22 16:54:08 +02:00
|
|
|
|
for (var j = 0; j < cards.length; j++) {
|
|
|
|
|
cards[j].reset();
|
|
|
|
|
}
|
2017-02-13 14:42:34 +01:00
|
|
|
|
|
|
|
|
|
// Scale new layout
|
|
|
|
|
$wrapper.children('ul').children('.h5p-row-break').removeClass('h5p-row-break');
|
|
|
|
|
maxWidth = -1;
|
|
|
|
|
self.trigger('resize');
|
2017-09-22 16:54:08 +02:00
|
|
|
|
cards[0].setFocus();
|
2017-02-13 14:42:34 +01:00
|
|
|
|
}, 600);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Game has finished!
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
var createButton = function (name, label, action) {
|
|
|
|
|
var buttonElement = document.createElement('div');
|
|
|
|
|
buttonElement.classList.add('h5p-memory-' + name);
|
|
|
|
|
buttonElement.innerHTML = label;
|
|
|
|
|
buttonElement.setAttribute('role', 'button');
|
|
|
|
|
buttonElement.tabIndex = 0;
|
2017-10-17 16:16:46 +02:00
|
|
|
|
buttonElement.addEventListener('click', function () {
|
2017-02-13 14:42:34 +01:00
|
|
|
|
action.apply(buttonElement);
|
|
|
|
|
}, false);
|
|
|
|
|
buttonElement.addEventListener('keypress', function (event) {
|
|
|
|
|
if (event.which === 13 || event.which === 32) { // Enter or Space key
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
action.apply(buttonElement);
|
|
|
|
|
}
|
|
|
|
|
}, false);
|
|
|
|
|
return buttonElement;
|
|
|
|
|
};
|
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
/**
|
2015-10-19 11:17:38 +02:00
|
|
|
|
* Adds card to card list and set up a flip listener.
|
2014-03-28 14:52:41 +01:00
|
|
|
|
*
|
2015-10-19 11:17:38 +02:00
|
|
|
|
* @private
|
|
|
|
|
* @param {H5P.MemoryGame.Card} card
|
|
|
|
|
* @param {H5P.MemoryGame.Card} mate
|
2014-03-28 14:52:41 +01:00
|
|
|
|
*/
|
|
|
|
|
var addCard = function (card, mate) {
|
2015-10-19 11:17:38 +02:00
|
|
|
|
card.on('flip', function () {
|
2019-01-02 11:40:27 +01:00
|
|
|
|
if (audioCard) {
|
|
|
|
|
audioCard.stopAudio();
|
|
|
|
|
}
|
2017-09-25 14:38:14 +02:00
|
|
|
|
|
|
|
|
|
// Always return focus to the card last flipped
|
|
|
|
|
for (var i = 0; i < cards.length; i++) {
|
|
|
|
|
cards[i].makeUntabbable();
|
|
|
|
|
}
|
|
|
|
|
card.makeTabbable();
|
|
|
|
|
|
|
|
|
|
popup.close();
|
2015-11-28 22:06:19 +01:00
|
|
|
|
self.triggerXAPI('interacted');
|
2014-03-28 14:52:41 +01:00
|
|
|
|
// Keep track of time spent
|
2016-10-02 16:09:34 +02:00
|
|
|
|
timer.play();
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2017-02-13 15:41:06 +01:00
|
|
|
|
// Keep track of the number of flipped cards
|
|
|
|
|
numFlipped++;
|
|
|
|
|
|
2017-09-22 16:54:08 +02:00
|
|
|
|
// Announce the card unless it's the last one and it's correct
|
|
|
|
|
var isMatched = (flipped === mate);
|
|
|
|
|
var isLast = ((removed + 2) === cards.length);
|
|
|
|
|
card.updateLabel(isMatched, !(isMatched && isLast));
|
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
if (flipped !== undefined) {
|
|
|
|
|
var matie = flipped;
|
|
|
|
|
// Reset the flipped card.
|
|
|
|
|
flipped = undefined;
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
setTimeout(function () {
|
|
|
|
|
check(card, matie, mate);
|
|
|
|
|
}, 800);
|
|
|
|
|
}
|
|
|
|
|
else {
|
2017-02-13 15:41:06 +01:00
|
|
|
|
if (flipBacks.length > 1) {
|
|
|
|
|
// Turn back any flipped cards
|
|
|
|
|
processFlipBacks();
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
// Keep track of the flipped card.
|
|
|
|
|
flipped = card;
|
|
|
|
|
}
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
// Count number of cards turned
|
|
|
|
|
counter.increment();
|
|
|
|
|
});
|
2019-01-02 11:40:27 +01:00
|
|
|
|
card.on('audioplay', function () {
|
2019-01-02 11:47:35 +01:00
|
|
|
|
if (audioCard) {
|
|
|
|
|
audioCard.stopAudio();
|
|
|
|
|
}
|
2019-01-02 11:40:27 +01:00
|
|
|
|
audioCard = card;
|
|
|
|
|
});
|
|
|
|
|
card.on('audiostop', function () {
|
|
|
|
|
audioCard = undefined;
|
|
|
|
|
});
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2017-06-20 16:47:07 +02:00
|
|
|
|
/**
|
2017-09-25 14:38:14 +02:00
|
|
|
|
* Create event handler for moving focus to the next or the previous
|
|
|
|
|
* card on the table.
|
|
|
|
|
*
|
2017-06-20 16:47:07 +02:00
|
|
|
|
* @private
|
2017-09-25 14:38:14 +02:00
|
|
|
|
* @param {number} direction +1/-1
|
|
|
|
|
* @return {function}
|
2017-06-20 16:47:07 +02:00
|
|
|
|
*/
|
|
|
|
|
var createCardChangeFocusHandler = function (direction) {
|
|
|
|
|
return function () {
|
|
|
|
|
// Locate next card
|
|
|
|
|
for (var i = 0; i < cards.length; i++) {
|
|
|
|
|
if (cards[i] === card) {
|
|
|
|
|
// Found current card
|
|
|
|
|
|
|
|
|
|
var nextCard, fails = 0;
|
|
|
|
|
do {
|
|
|
|
|
fails++;
|
|
|
|
|
nextCard = cards[i + (direction * fails)];
|
|
|
|
|
if (!nextCard) {
|
|
|
|
|
return; // No more cards
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-22 16:54:08 +02:00
|
|
|
|
while (nextCard.isRemoved());
|
2017-06-20 16:47:07 +02:00
|
|
|
|
|
|
|
|
|
card.makeUntabbable();
|
|
|
|
|
nextCard.setFocus();
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2017-09-25 14:38:14 +02:00
|
|
|
|
// Register handlers for moving focus to next and previous card
|
2017-06-20 16:47:07 +02:00
|
|
|
|
card.on('next', createCardChangeFocusHandler(1));
|
|
|
|
|
card.on('prev', createCardChangeFocusHandler(-1));
|
2017-09-25 14:38:14 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create event handler for moving focus to the first or the last card
|
|
|
|
|
* on the table.
|
|
|
|
|
*
|
|
|
|
|
* @private
|
|
|
|
|
* @param {number} direction +1/-1
|
|
|
|
|
* @return {function}
|
|
|
|
|
*/
|
|
|
|
|
var createEndCardFocusHandler = function (direction) {
|
|
|
|
|
return function () {
|
|
|
|
|
var focusSet = false;
|
|
|
|
|
for (var i = 0; i < cards.length; i++) {
|
|
|
|
|
var j = (direction === -1 ? cards.length - (i + 1) : i);
|
|
|
|
|
if (!focusSet && !cards[j].isRemoved()) {
|
|
|
|
|
cards[j].setFocus();
|
|
|
|
|
focusSet = true;
|
|
|
|
|
}
|
|
|
|
|
else if (cards[j] === card) {
|
|
|
|
|
card.makeUntabbable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Register handlers for moving focus to first and last card
|
|
|
|
|
card.on('first', createEndCardFocusHandler(1));
|
|
|
|
|
card.on('last', createEndCardFocusHandler(-1));
|
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
cards.push(card);
|
|
|
|
|
};
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2017-02-13 15:41:06 +01:00
|
|
|
|
/**
|
|
|
|
|
* Will flip back two and two cards
|
|
|
|
|
*/
|
|
|
|
|
var processFlipBacks = function () {
|
|
|
|
|
flipBacks[0].flipBack();
|
|
|
|
|
flipBacks[1].flipBack();
|
|
|
|
|
flipBacks.splice(0, 2);
|
|
|
|
|
numFlipped -= 2;
|
|
|
|
|
};
|
|
|
|
|
|
2017-01-23 15:46:06 +01:00
|
|
|
|
/**
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
var getCardsToUse = function () {
|
|
|
|
|
var numCardsToUse = (parameters.behaviour && parameters.behaviour.numCardsToUse ? parseInt(parameters.behaviour.numCardsToUse) : 0);
|
|
|
|
|
if (numCardsToUse <= 2 || numCardsToUse >= parameters.cards.length) {
|
|
|
|
|
// Use all cards
|
|
|
|
|
return parameters.cards;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pick random cards from pool
|
|
|
|
|
var cardsToUse = [];
|
|
|
|
|
var pickedCardsMap = {};
|
|
|
|
|
|
|
|
|
|
var numPicket = 0;
|
|
|
|
|
while (numPicket < numCardsToUse) {
|
|
|
|
|
var pickIndex = Math.floor(Math.random() * parameters.cards.length);
|
|
|
|
|
if (pickedCardsMap[pickIndex]) {
|
|
|
|
|
continue; // Already picked, try again!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cardsToUse.push(parameters.cards[pickIndex]);
|
|
|
|
|
pickedCardsMap[pickIndex] = true;
|
|
|
|
|
numPicket++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cardsToUse;
|
|
|
|
|
};
|
|
|
|
|
|
2017-05-04 14:20:20 +02:00
|
|
|
|
var cardStyles, invertShades;
|
|
|
|
|
if (parameters.lookNFeel) {
|
|
|
|
|
// If the contrast between the chosen color and white is too low we invert the shades to create good contrast
|
|
|
|
|
invertShades = (parameters.lookNFeel.themeColor &&
|
|
|
|
|
getContrast(parameters.lookNFeel.themeColor) < 1.7 ? -1 : 1);
|
|
|
|
|
var backImage = (parameters.lookNFeel.cardBack ? H5P.getPath(parameters.lookNFeel.cardBack.path, id) : null);
|
|
|
|
|
cardStyles = MemoryGame.Card.determineStyles(parameters.lookNFeel.themeColor, invertShades, backImage);
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
// Initialize cards.
|
2017-01-23 15:46:06 +01:00
|
|
|
|
var cardsToUse = getCardsToUse();
|
|
|
|
|
for (var i = 0; i < cardsToUse.length; i++) {
|
|
|
|
|
var cardParams = cardsToUse[i];
|
2017-01-20 11:58:08 +01:00
|
|
|
|
if (MemoryGame.Card.isValid(cardParams)) {
|
|
|
|
|
// Create first card
|
2018-08-23 01:44:41 +02:00
|
|
|
|
var cardTwo, cardOne = new MemoryGame.Card(cardParams.image, id, cardParams.imageAlt, parameters.l10n, cardParams.description, cardStyles, cardParams.audio);
|
2017-01-20 11:58:08 +01:00
|
|
|
|
|
|
|
|
|
if (MemoryGame.Card.hasTwoImages(cardParams)) {
|
|
|
|
|
// Use matching image for card two
|
2018-08-23 01:44:41 +02:00
|
|
|
|
cardTwo = new MemoryGame.Card(cardParams.match, id, cardParams.matchAlt, parameters.l10n, cardParams.description, cardStyles, cardParams.matchAudio);
|
2017-05-08 15:14:18 +02:00
|
|
|
|
cardOne.hasTwoImages = cardTwo.hasTwoImages = true;
|
2017-01-20 11:58:08 +01:00
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Add two cards with the same image
|
2018-08-23 01:44:41 +02:00
|
|
|
|
cardTwo = new MemoryGame.Card(cardParams.image, id, cardParams.imageAlt, parameters.l10n, cardParams.description, cardStyles, cardParams.audio);
|
2017-01-20 11:58:08 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add cards to card list for shuffeling
|
2016-05-19 09:40:22 +02:00
|
|
|
|
addCard(cardOne, cardTwo);
|
|
|
|
|
addCard(cardTwo, cardOne);
|
|
|
|
|
}
|
2014-03-28 14:52:41 +01:00
|
|
|
|
}
|
|
|
|
|
H5P.shuffleArray(cards);
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
/**
|
2015-10-19 11:17:38 +02:00
|
|
|
|
* Attach this game's html to the given container.
|
2014-03-28 14:52:41 +01:00
|
|
|
|
*
|
2015-10-19 11:17:38 +02:00
|
|
|
|
* @param {H5P.jQuery} $container
|
2014-03-28 14:52:41 +01:00
|
|
|
|
*/
|
2015-10-19 11:17:38 +02:00
|
|
|
|
self.attach = function ($container) {
|
2015-11-28 22:06:19 +01:00
|
|
|
|
this.triggerXAPI('attempted');
|
2017-01-20 11:58:08 +01:00
|
|
|
|
// TODO: Only create on first attach!
|
2017-01-23 14:12:33 +01:00
|
|
|
|
$wrapper = $container.addClass('h5p-memory-game').html('');
|
2017-05-04 14:20:20 +02:00
|
|
|
|
if (invertShades === -1) {
|
|
|
|
|
$container.addClass('h5p-invert-shades');
|
|
|
|
|
}
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
// Add cards to list
|
2017-09-22 16:54:08 +02:00
|
|
|
|
var $list = $('<ul/>', {
|
|
|
|
|
role: 'application',
|
|
|
|
|
'aria-labelledby': 'h5p-intro-' + numInstances
|
|
|
|
|
});
|
2014-03-28 14:52:41 +01:00
|
|
|
|
for (var i = 0; i < cards.length; i++) {
|
|
|
|
|
cards[i].appendTo($list);
|
|
|
|
|
}
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
if ($list.children().length) {
|
2020-09-06 16:44:56 +02:00
|
|
|
|
cards[0].makeTabbable();
|
|
|
|
|
|
2017-09-22 16:54:08 +02:00
|
|
|
|
$('<div/>', {
|
|
|
|
|
id: 'h5p-intro-' + numInstances,
|
|
|
|
|
'class': 'h5p-memory-hidden-read',
|
2017-09-25 14:38:14 +02:00
|
|
|
|
html: parameters.l10n.label,
|
2017-09-22 16:54:08 +02:00
|
|
|
|
appendTo: $container
|
|
|
|
|
});
|
2014-03-28 14:52:41 +01:00
|
|
|
|
$list.appendTo($container);
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2017-09-22 16:54:08 +02:00
|
|
|
|
$bottom = $('<div/>', {
|
2017-11-02 10:19:03 +01:00
|
|
|
|
'class': 'h5p-programatically-focusable',
|
2017-09-22 16:54:08 +02:00
|
|
|
|
tabindex: '-1',
|
2017-10-17 16:16:46 +02:00
|
|
|
|
appendTo: $container
|
2017-09-22 16:54:08 +02:00
|
|
|
|
});
|
2017-09-25 14:38:14 +02:00
|
|
|
|
$taskComplete = $('<div/>', {
|
|
|
|
|
'class': 'h5p-memory-complete h5p-memory-hidden-read',
|
|
|
|
|
html: parameters.l10n.done,
|
2017-09-22 16:54:08 +02:00
|
|
|
|
appendTo: $bottom
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$feedback = $('<div class="h5p-feedback">' + parameters.l10n.feedback + '</div>').appendTo($bottom);
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
// Add status bar
|
2015-10-19 11:17:38 +02:00
|
|
|
|
var $status = $('<dl class="h5p-status">' +
|
2017-09-25 14:38:14 +02:00
|
|
|
|
'<dt>' + parameters.l10n.timeSpent + ':</dt>' +
|
|
|
|
|
'<dd class="h5p-time-spent"><time role="timer" datetime="PT0M0S">0:00</time><span class="h5p-memory-hidden-read">.</span></dd>' +
|
|
|
|
|
'<dt>' + parameters.l10n.cardTurns + ':</dt>' +
|
|
|
|
|
'<dd class="h5p-card-turns">0<span class="h5p-memory-hidden-read">.</span></dd>' +
|
2017-09-22 16:54:08 +02:00
|
|
|
|
'</dl>').appendTo($bottom);
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2017-09-25 14:38:14 +02:00
|
|
|
|
timer = new MemoryGame.Timer($status.find('time')[0]);
|
2015-10-19 11:17:38 +02:00
|
|
|
|
counter = new MemoryGame.Counter($status.find('.h5p-card-turns'));
|
2017-06-20 13:28:15 +02:00
|
|
|
|
popup = new MemoryGame.Popup($container, parameters.l10n);
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-09-16 14:33:14 +02:00
|
|
|
|
$container.click(function () {
|
|
|
|
|
popup.close();
|
|
|
|
|
});
|
2014-03-28 14:52:41 +01:00
|
|
|
|
}
|
2020-09-06 16:44:56 +02:00
|
|
|
|
else {
|
|
|
|
|
const $foo = $('<div/>')
|
|
|
|
|
.text('No card was added to the memory game!')
|
|
|
|
|
.appendTo($list);
|
|
|
|
|
|
|
|
|
|
$list.appendTo($container);
|
|
|
|
|
}
|
2014-03-28 14:52:41 +01:00
|
|
|
|
};
|
2017-01-23 14:12:33 +01:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Will try to scale the game so that it fits within its container.
|
2017-10-17 16:16:46 +02:00
|
|
|
|
* Puts the cards into a grid layout to make it as square as possible –
|
2017-01-23 14:12:33 +01:00
|
|
|
|
* which improves the playability on multiple devices.
|
|
|
|
|
*
|
2017-10-17 16:16:46 +02:00
|
|
|
|
* @private
|
2017-01-23 14:12:33 +01:00
|
|
|
|
*/
|
2017-01-23 15:46:06 +01:00
|
|
|
|
var scaleGameSize = function () {
|
2017-01-23 14:12:33 +01:00
|
|
|
|
|
|
|
|
|
// Check how much space we have available
|
|
|
|
|
var $list = $wrapper.children('ul');
|
2017-01-23 15:46:06 +01:00
|
|
|
|
|
2017-01-23 14:12:33 +01:00
|
|
|
|
var newMaxWidth = parseFloat(window.getComputedStyle($list[0]).width);
|
|
|
|
|
if (maxWidth === newMaxWidth) {
|
|
|
|
|
return; // Same size, no need to recalculate
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
maxWidth = newMaxWidth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the card holders
|
|
|
|
|
var $elements = $list.children();
|
|
|
|
|
if ($elements.length < 4) {
|
|
|
|
|
return; // No need to proceed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Determine the optimal number of columns
|
|
|
|
|
var newNumCols = Math.ceil(Math.sqrt($elements.length));
|
|
|
|
|
|
|
|
|
|
// Do not exceed the max number of columns
|
|
|
|
|
var maxCols = Math.floor(maxWidth / CARD_MIN_SIZE);
|
|
|
|
|
if (newNumCols > maxCols) {
|
|
|
|
|
newNumCols = maxCols;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (numCols !== newNumCols) {
|
|
|
|
|
// We need to change layout
|
|
|
|
|
numCols = newNumCols;
|
|
|
|
|
|
|
|
|
|
// Calculate new column size in percentage and round it down (we don't
|
|
|
|
|
// want things sticking out…)
|
|
|
|
|
var colSize = Math.floor((100 / numCols) * 10000) / 10000;
|
|
|
|
|
$elements.css('width', colSize + '%').each(function (i, e) {
|
|
|
|
|
if (i === numCols) {
|
|
|
|
|
$(e).addClass('h5p-row-break');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate how much one percentage of the standard/default size is
|
|
|
|
|
var onePercentage = ((CARD_STD_SIZE * numCols) + STD_FONT_SIZE) / 100;
|
|
|
|
|
var paddingSize = (STD_FONT_SIZE * LIST_PADDING) / onePercentage;
|
|
|
|
|
var cardSize = (100 - paddingSize) / numCols;
|
|
|
|
|
var fontSize = (((maxWidth * (cardSize / 100)) * STD_FONT_SIZE) / CARD_STD_SIZE);
|
|
|
|
|
|
|
|
|
|
// We use font size to evenly scale all parts of the cards.
|
|
|
|
|
$list.css('font-size', fontSize + 'px');
|
2017-02-13 12:35:10 +01:00
|
|
|
|
popup.setSize(fontSize);
|
2017-01-23 14:12:33 +01:00
|
|
|
|
// due to rounding errors in browsers the margins may vary a bit…
|
2017-01-23 15:46:06 +01:00
|
|
|
|
};
|
2017-01-23 14:12:33 +01:00
|
|
|
|
|
2017-01-23 15:46:06 +01:00
|
|
|
|
if (parameters.behaviour && parameters.behaviour.useGrid && cardsToUse.length) {
|
|
|
|
|
self.on('resize', scaleGameSize);
|
|
|
|
|
}
|
2015-10-19 11:17:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extends the event dispatcher
|
|
|
|
|
MemoryGame.prototype = Object.create(EventDispatcher.prototype);
|
|
|
|
|
MemoryGame.prototype.constructor = MemoryGame;
|
|
|
|
|
|
2017-05-04 14:20:20 +02:00
|
|
|
|
/**
|
|
|
|
|
* Determine color contrast level compared to white(#fff)
|
|
|
|
|
*
|
2017-10-17 16:16:46 +02:00
|
|
|
|
* @private
|
2017-05-04 14:20:20 +02:00
|
|
|
|
* @param {string} color hex code
|
|
|
|
|
* @return {number} From 1 to Infinity.
|
|
|
|
|
*/
|
|
|
|
|
var getContrast = function (color) {
|
|
|
|
|
return 255 / ((parseInt(color.substr(1, 2), 16) * 299 +
|
|
|
|
|
parseInt(color.substr(3, 2), 16) * 587 +
|
|
|
|
|
parseInt(color.substr(5, 2), 16) * 144) / 1000);
|
|
|
|
|
};
|
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
return MemoryGame;
|
2015-10-19 11:17:38 +02:00
|
|
|
|
})(H5P.EventDispatcher, H5P.jQuery);
|