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
|
|
|
|
|
|
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
|
|
|
|
*
|
2015-10-19 11:17:38 +02:00
|
|
|
|
* @class
|
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) {
|
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
|
|
|
|
|
2017-01-23 14:12:33 +01:00
|
|
|
|
var flipped, timer, counter, popup, $feedback, $wrapper, maxWidth, numCols;
|
2015-10-19 11:17:38 +02:00
|
|
|
|
var cards = [];
|
|
|
|
|
var removed = 0;
|
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) {
|
|
|
|
|
if (mate === correct) {
|
|
|
|
|
// Remove them from the game.
|
|
|
|
|
card.remove();
|
|
|
|
|
mate.remove();
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
removed += 2;
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-09-16 14:33:14 +02:00
|
|
|
|
var finished = (removed === cards.length);
|
|
|
|
|
var desc = card.getDescription();
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2016-05-26 14:07:14 +02:00
|
|
|
|
if (finished) {
|
|
|
|
|
self.triggerXAPIScored(1, 1, 'completed');
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-16 14:33:14 +02:00
|
|
|
|
if (desc !== undefined) {
|
|
|
|
|
// Pause timer and show desciption.
|
2016-10-02 16:09:34 +02:00
|
|
|
|
timer.pause();
|
2015-08-26 15:51:43 +02:00
|
|
|
|
popup.show(desc, card.getImage(), function () {
|
2014-09-16 14:33:14 +02:00
|
|
|
|
if (finished) {
|
|
|
|
|
// Game has finished
|
|
|
|
|
$feedback.addClass('h5p-show');
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Popup is closed, continue.
|
2016-10-02 16:09:34 +02:00
|
|
|
|
timer.play();
|
2014-09-16 14:33:14 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else if (finished) {
|
|
|
|
|
// Game has finished
|
2014-03-28 14:52:41 +01:00
|
|
|
|
timer.stop();
|
|
|
|
|
$feedback.addClass('h5p-show');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Flip them back
|
|
|
|
|
card.flipBack();
|
|
|
|
|
mate.flipBack();
|
|
|
|
|
}
|
2014-09-16 14:33:14 +02:00
|
|
|
|
};
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
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 () {
|
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
|
|
|
|
|
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 {
|
|
|
|
|
// 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();
|
|
|
|
|
});
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
cards.push(card);
|
|
|
|
|
};
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
// Initialize cards.
|
|
|
|
|
for (var i = 0; i < parameters.cards.length; i++) {
|
2017-01-20 11:58:08 +01:00
|
|
|
|
var cardParams = parameters.cards[i];
|
|
|
|
|
if (MemoryGame.Card.isValid(cardParams)) {
|
|
|
|
|
// Create first card
|
|
|
|
|
var cardTwo, cardOne = new MemoryGame.Card(cardParams.image, id, cardParams.description);
|
|
|
|
|
|
|
|
|
|
if (MemoryGame.Card.hasTwoImages(cardParams)) {
|
|
|
|
|
// Use matching image for card two
|
|
|
|
|
cardTwo = new MemoryGame.Card(cardParams.match, id, cardParams.description);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Add two cards with the same image
|
|
|
|
|
cardTwo = new MemoryGame.Card(cardParams.image, id, cardParams.description);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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('');
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
// Add cards to list
|
|
|
|
|
var $list = $('<ul/>');
|
|
|
|
|
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) {
|
|
|
|
|
$list.appendTo($container);
|
2015-10-19 11:17:38 +02:00
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
$feedback = $('<div class="h5p-feedback">' + parameters.l10n.feedback + '</div>').appendTo($container);
|
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">' +
|
|
|
|
|
'<dt>' + parameters.l10n.timeSpent + '</dt>' +
|
|
|
|
|
'<dd class="h5p-time-spent">0:00</dd>' +
|
|
|
|
|
'<dt>' + parameters.l10n.cardTurns + '</dt>' +
|
|
|
|
|
'<dd class="h5p-card-turns">0</dd>' +
|
|
|
|
|
'</dl>').appendTo($container);
|
|
|
|
|
|
2016-10-02 16:09:34 +02:00
|
|
|
|
timer = new H5P.Timer(100);
|
2016-10-11 18:47:26 +02:00
|
|
|
|
timer.notify('every_tenth_second', function () {
|
|
|
|
|
var time = timer.getTime();
|
|
|
|
|
var minutes = H5P.Timer.extractTimeElement(time, 'minutes');
|
2017-01-20 14:15:59 +01:00
|
|
|
|
var seconds = H5P.Timer.extractTimeElement(time, 'seconds') % 60;
|
2016-10-11 18:47:26 +02:00
|
|
|
|
if (seconds < 10) {
|
|
|
|
|
seconds = '0' + seconds;
|
|
|
|
|
}
|
|
|
|
|
$status.find('.h5p-time-spent').text(minutes + ':' + seconds);
|
|
|
|
|
});
|
|
|
|
|
|
2015-10-19 11:17:38 +02:00
|
|
|
|
counter = new MemoryGame.Counter($status.find('.h5p-card-turns'));
|
|
|
|
|
popup = new MemoryGame.Popup($container);
|
|
|
|
|
|
2014-09-16 14:33:14 +02:00
|
|
|
|
$container.click(function () {
|
|
|
|
|
popup.close();
|
|
|
|
|
});
|
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.
|
|
|
|
|
* Puts the cards into a grid layout to make it as square as possible –
|
|
|
|
|
* which improves the playability on multiple devices.
|
|
|
|
|
*
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
function scaleGameSize() {
|
|
|
|
|
|
|
|
|
|
// Check how much space we have available
|
|
|
|
|
var $list = $wrapper.children('ul');
|
|
|
|
|
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');
|
|
|
|
|
// due to rounding errors in browsers the margins may vary a bit…
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2014-03-28 14:52:41 +01:00
|
|
|
|
return MemoryGame;
|
2015-10-19 11:17:38 +02:00
|
|
|
|
})(H5P.EventDispatcher, H5P.jQuery);
|