Merge branch 'release'

pull/18/head
Frode Petterson 2017-02-15 10:36:50 +01:00
commit fa66937750
13 changed files with 336 additions and 66 deletions

17
card.js
View File

@ -4,11 +4,13 @@
* Controls all the operations for each card. * Controls all the operations for each card.
* *
* @class H5P.MemoryGame.Card * @class H5P.MemoryGame.Card
* @extends H5P.EventDispatcher
* @param {Object} image * @param {Object} image
* @param {number} id * @param {number} id
* @param {string} [description] * @param {string} [description]
*/ */
MemoryGame.Card = function (image, id, description) { MemoryGame.Card = function (image, id, description) {
/** @alias H5P.MemoryGame.Card# */
var self = this; var self = this;
// Initialize event inheritance // Initialize event inheritance
@ -53,6 +55,13 @@
$card.addClass('h5p-matched'); $card.addClass('h5p-matched');
}; };
/**
* Reset card to natural state
*/
self.reset = function () {
$card[0].classList.remove('h5p-flipped', 'h5p-matched');
};
/** /**
* Get card description. * Get card description.
* *
@ -92,6 +101,14 @@
}) })
.end(); .end();
}; };
/**
* Re-append to parent container
*/
self.reAppend = function () {
var parent = $card[0].parentElement.parentElement;
parent.appendChild($card[0].parentElement);
};
}; };
// Extends the event dispatcher // Extends the event dispatcher

View File

@ -7,16 +7,32 @@
* @param {H5P.jQuery} $container * @param {H5P.jQuery} $container
*/ */
MemoryGame.Counter = function ($container) { MemoryGame.Counter = function ($container) {
/** @alias H5P.MemoryGame.Counter# */
var self = this; var self = this;
var current = 0; var current = 0;
/**
* @private
*/
var update = function () {
$container[0].innerText = current;
};
/** /**
* Increment the counter. * Increment the counter.
*/ */
self.increment = function () { self.increment = function () {
current++; current++;
$container.text(current); update();
};
/**
* Revert counter back to its natural state
*/
self.reset = function () {
current = 0;
update();
}; };
}; };

View File

@ -29,8 +29,10 @@
"description": "These options will let you control how the game behaves.", "description": "These options will let you control how the game behaves.",
"fields": [ "fields": [
{ {
"englishLabel": "Put the cards in a grid layout", "englishLabel": "Position the cards in a square",
"label": "Put the cards in a grid layout" "label": "Position the cards in a square",
"englishDescription": "Will try to match the number of columns and rows when laying out the cards. Afterward, the cards will be scaled to fit the container.",
"description": "Will try to match the number of columns and rows when laying out the cards. Afterward, the cards will be scaled to fit the container."
}, },
{ {
"englishLabel": "Number of cards to use", "englishLabel": "Number of cards to use",
@ -55,6 +57,12 @@
}, },
{ {
"label": "نص الملاحظات" "label": "نص الملاحظات"
},
{
"englishLabel": "Try again button text",
"label": "Try again button text",
"englishDefault": "Try again?",
"default": "Try again?"
} }
] ]
} }

View File

@ -29,8 +29,10 @@
"description": "These options will let you control how the game behaves.", "description": "These options will let you control how the game behaves.",
"fields": [ "fields": [
{ {
"englishLabel": "Put the cards in a grid layout", "englishLabel": "Position the cards in a square",
"label": "Put the cards in a grid layout" "label": "Position the cards in a square",
"englishDescription": "Will try to match the number of columns and rows when laying out the cards. Afterward, the cards will be scaled to fit the container.",
"description": "Will try to match the number of columns and rows when laying out the cards. Afterward, the cards will be scaled to fit the container."
}, },
{ {
"englishLabel": "Number of cards to use", "englishLabel": "Number of cards to use",
@ -58,6 +60,12 @@
{ {
"label": "Text als Rückmeldung", "label": "Text als Rückmeldung",
"default": "Gute Arbeit!" "default": "Gute Arbeit!"
},
{
"englishLabel": "Try again button text",
"label": "Try again button text",
"englishDefault": "Try again?",
"default": "Try again?"
} }
] ]
} }

View File

@ -29,8 +29,10 @@
"description": "These options will let you control how the game behaves.", "description": "These options will let you control how the game behaves.",
"fields": [ "fields": [
{ {
"englishLabel": "Put the cards in a grid layout", "englishLabel": "Position the cards in a square",
"label": "Put the cards in a grid layout" "label": "Position the cards in a square",
"englishDescription": "Will try to match the number of columns and rows when laying out the cards. Afterward, the cards will be scaled to fit the container.",
"description": "Will try to match the number of columns and rows when laying out the cards. Afterward, the cards will be scaled to fit the container."
}, },
{ {
"englishLabel": "Number of cards to use", "englishLabel": "Number of cards to use",
@ -58,6 +60,12 @@
{ {
"label": "Texte de l'appréciation finale", "label": "Texte de l'appréciation finale",
"default": "Bien joué !" "default": "Bien joué !"
},
{
"englishLabel": "Try again button text",
"label": "Try again button text",
"englishDefault": "Try again?",
"default": "Try again?"
} }
] ]
} }

View File

@ -29,8 +29,10 @@
"description": "These options will let you control how the game behaves.", "description": "These options will let you control how the game behaves.",
"fields": [ "fields": [
{ {
"englishLabel": "Put the cards in a grid layout", "englishLabel": "Position the cards in a square",
"label": "Put the cards in a grid layout" "label": "Position the cards in a square",
"englishDescription": "Will try to match the number of columns and rows when laying out the cards. Afterward, the cards will be scaled to fit the container.",
"description": "Will try to match the number of columns and rows when laying out the cards. Afterward, the cards will be scaled to fit the container."
}, },
{ {
"englishLabel": "Number of cards to use", "englishLabel": "Number of cards to use",
@ -55,6 +57,12 @@
}, },
{ {
"label": "Testo Feedback" "label": "Testo Feedback"
},
{
"englishLabel": "Try again button text",
"label": "Try again button text",
"englishDefault": "Try again?",
"default": "Try again?"
} }
] ]
} }

View File

@ -35,8 +35,10 @@
"description": "Disse instillingene lar deg bestemme hvordan spillet skal oppføre seg.", "description": "Disse instillingene lar deg bestemme hvordan spillet skal oppføre seg.",
"fields": [ "fields": [
{ {
"englishLabel": "Put the cards in a grid layout", "englishLabel": "Position the cards in a square",
"label": "Putt kortene i et rutenett" "label": "Plasser kortene i en firkant",
"englishDescription": "Will try to match the number of columns and rows when laying out the cards. Afterward, the cards will be scaled to fit the container.",
"description": "Vil forsøk å samsvare antall kolonner og rader når kortene legges ut. Etterpå vil kortene bli skalert til å passe beholderen."
}, },
{ {
"englishLabel": "Number of cards to use", "englishLabel": "Number of cards to use",
@ -71,6 +73,12 @@
"label": "Tilbakemeldingstekst", "label": "Tilbakemeldingstekst",
"englishDefault": "Good work!", "englishDefault": "Good work!",
"default": "Godt jobbet!" "default": "Godt jobbet!"
},
{
"englishLabel": "Try again button text",
"label": "Prøv på nytt-tekst",
"englishDefault": "Try again?",
"default": "Prøv på nytt?"
} }
] ]
} }

View File

@ -3,9 +3,9 @@
"description": "See how many cards you can remember!", "description": "See how many cards you can remember!",
"majorVersion": 1, "majorVersion": 1,
"minorVersion": 2, "minorVersion": 2,
"patchVersion": 1, "patchVersion": 2,
"runnable": 1, "runnable": 1,
"author": "Amendor AS", "author": "Joubel AS",
"license": "MIT", "license": "MIT",
"machineName": "H5P.MemoryGame", "machineName": "H5P.MemoryGame",
"preloadedCss": [ "preloadedCss": [
@ -25,6 +25,9 @@
}, },
{ {
"path": "popup.js" "path": "popup.js"
},
{
"path": "timer.js"
} }
], ],
"preloadedDependencies": [ "preloadedDependencies": [

View File

@ -172,6 +172,7 @@
background: #fff; background: #fff;
padding: 1em; padding: 1em;
width: 20em; width: 20em;
max-width: 90%;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
@ -182,7 +183,6 @@
} }
.h5p-memory-game .h5p-memory-image { .h5p-memory-game .h5p-memory-image {
float: left; float: left;
margin: 0 1em 1em 0;
border: 2px solid #d0d0d0; border: 2px solid #d0d0d0;
box-sizing: border-box; box-sizing: border-box;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
@ -190,7 +190,25 @@
background: #f0f0f0; background: #f0f0f0;
width: 6.25em; width: 6.25em;
height: 6.25em; height: 6.25em;
text-align: center;
} }
.h5p-memory-game .h5p-row-break { .h5p-memory-game .h5p-row-break {
clear: left; clear: left;
} }
.h5p-memory-game .h5p-memory-desc {
margin-left: 7em;
}
.h5p-memory-reset {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
cursor: pointer;
font-style: italic;
text-shadow: 0 0 0.5em white;
padding: 0.125em 0.25em;
line-height: 1;
}
.h5p-memory-reset:focus {
outline: dashed pink;
}

View File

@ -9,11 +9,13 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
/** /**
* Memory Game Constructor * Memory Game Constructor
* *
* @class * @class H5P.MemoryGame
* @extends H5P.EventDispatcher
* @param {Object} parameters * @param {Object} parameters
* @param {Number} id * @param {Number} id
*/ */
function MemoryGame(parameters, id) { function MemoryGame(parameters, id) {
/** @alias H5P.MemoryGame# */
var self = this; var self = this;
// Initialize event inheritance // Initialize event inheritance
@ -21,6 +23,8 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
var flipped, timer, counter, popup, $feedback, $wrapper, maxWidth, numCols; var flipped, timer, counter, popup, $feedback, $wrapper, maxWidth, numCols;
var cards = []; var cards = [];
var flipBacks = []; // Que of cards to be flipped back
var numFlipped = 0;
var removed = 0; var removed = 0;
/** /**
@ -32,17 +36,31 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
* @param {H5P.MemoryGame.Card} correct * @param {H5P.MemoryGame.Card} correct
*/ */
var check = function (card, mate, correct) { var check = function (card, mate, correct) {
if (mate === correct) { 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;
}
// Remove them from the game. // Remove them from the game.
card.remove(); card.remove();
mate.remove(); mate.remove();
// Update counters
numFlipped -= 2;
removed += 2; removed += 2;
var finished = (removed === cards.length); var isFinished = (removed === cards.length);
var desc = card.getDescription(); var desc = card.getDescription();
if (finished) { if (isFinished) {
self.triggerXAPIScored(1, 1, 'completed'); self.triggerXAPIScored(1, 1, 'completed');
} }
@ -50,10 +68,9 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
// Pause timer and show desciption. // Pause timer and show desciption.
timer.pause(); timer.pause();
popup.show(desc, card.getImage(), function () { popup.show(desc, card.getImage(), function () {
if (finished) { if (isFinished) {
// Game has finished // Game done
$feedback.addClass('h5p-show'); finished();
if (parameters.behaviour && parameters.behaviour.allowRetry) { /* TODO */ }
} }
else { else {
// Popup is closed, continue. // Popup is closed, continue.
@ -61,18 +78,92 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
} }
}); });
} }
else if (finished) { else if (isFinished) {
// Game has finished // Game done
finished();
}
};
/**
* Game has finished!
* @private
*/
var finished = function () {
timer.stop(); timer.stop();
$feedback.addClass('h5p-show'); $feedback.addClass('h5p-show');
if (parameters.behaviour && parameters.behaviour.allowRetry) { /* TODO */ } if (parameters.behaviour && parameters.behaviour.allowRetry) {
// Create retry button
var retryButton = createButton('reset', parameters.l10n.tryAgain || 'Try again?', function () {
// Trigger handler (action)
resetGame();
// Remove button from DOM
$wrapper[0].removeChild(this);
});
// Same size as cards
retryButton.style.fontSize = $wrapper.children('ul')[0].style.fontSize;
$wrapper[0].appendChild(retryButton); // Add to DOM
} }
};
/**
* Shuffle the cards and restart the game!
* @private
*/
var resetGame = function () {
// Reset cards
removed = 0;
for (var i = 0; i < cards.length; i++) {
cards[i].reset();
} }
else {
// Flip them back // Remove feedback
card.flipBack(); $feedback[0].classList.remove('h5p-show');
mate.flipBack();
// 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();
} }
// Scale new layout
$wrapper.children('ul').children('.h5p-row-break').removeClass('h5p-row-break');
maxWidth = -1;
self.trigger('resize');
}, 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;
buttonElement.addEventListener('click', function (event) {
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;
}; };
/** /**
@ -88,6 +179,9 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
// Keep track of time spent // Keep track of time spent
timer.play(); timer.play();
// Keep track of the number of flipped cards
numFlipped++;
if (flipped !== undefined) { if (flipped !== undefined) {
var matie = flipped; var matie = flipped;
// Reset the flipped card. // Reset the flipped card.
@ -98,6 +192,11 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
}, 800); }, 800);
} }
else { else {
if (flipBacks.length > 1) {
// Turn back any flipped cards
processFlipBacks();
}
// Keep track of the flipped card. // Keep track of the flipped card.
flipped = card; flipped = card;
} }
@ -109,6 +208,16 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
cards.push(card); cards.push(card);
}; };
/**
* Will flip back two and two cards
*/
var processFlipBacks = function () {
flipBacks[0].flipBack();
flipBacks[1].flipBack();
flipBacks.splice(0, 2);
numFlipped -= 2;
};
/** /**
* @private * @private
*/ */
@ -191,17 +300,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
'<dd class="h5p-card-turns">0</dd>' + '<dd class="h5p-card-turns">0</dd>' +
'</dl>').appendTo($container); '</dl>').appendTo($container);
timer = new H5P.Timer(100); timer = new MemoryGame.Timer($status.find('.h5p-time-spent')[0]);
timer.notify('every_tenth_second', function () {
var time = timer.getTime();
var minutes = H5P.Timer.extractTimeElement(time, 'minutes');
var seconds = H5P.Timer.extractTimeElement(time, 'seconds') % 60;
if (seconds < 10) {
seconds = '0' + seconds;
}
$status.find('.h5p-time-spent').text(minutes + ':' + seconds);
});
counter = new MemoryGame.Counter($status.find('.h5p-card-turns')); counter = new MemoryGame.Counter($status.find('.h5p-card-turns'));
popup = new MemoryGame.Popup($container); popup = new MemoryGame.Popup($container);
@ -268,6 +367,7 @@ H5P.MemoryGame = (function (EventDispatcher, $) {
// We use font size to evenly scale all parts of the cards. // We use font size to evenly scale all parts of the cards.
$list.css('font-size', fontSize + 'px'); $list.css('font-size', fontSize + 'px');
popup.setSize(fontSize);
// due to rounding errors in browsers the margins may vary a bit… // due to rounding errors in browsers the margins may vary a bit…
}; };

View File

@ -7,6 +7,7 @@
* @param {H5P.jQuery} $container * @param {H5P.jQuery} $container
*/ */
MemoryGame.Popup = function ($container) { MemoryGame.Popup = function ($container) {
/** @alias H5P.MemoryGame.Popup# */
var self = this; var self = this;
var closed; var closed;
@ -39,6 +40,23 @@
closed = undefined; closed = undefined;
} }
}; };
/**
* Sets popup size relative to the card size
* @param {number} fontSize
*/
self.setSize = function (fontSize) {
// Set image size
$image[0].style.fontSize = fontSize + 'px';
// Determine card size
var cardSize = fontSize * 6.25; // From CSS
// Set popup size
$popup[0].style.minWidth = (cardSize * 2) + 'px';
$popup[0].style.minHeight = cardSize + 'px';
$desc[0].style.marginLeft = (cardSize + 20) + 'px';
};
}; };
})(H5P.MemoryGame, H5P.jQuery); })(H5P.MemoryGame, H5P.jQuery);

View File

@ -52,7 +52,8 @@
{ {
"name": "useGrid", "name": "useGrid",
"type": "boolean", "type": "boolean",
"label": "Put the cards in a grid layout", "label": "Position the cards in a square",
"description": "Will try to match the number of columns and rows when laying out the cards. Afterward, the cards will be scaled to fit the container.",
"importance": "low", "importance": "low",
"default": true "default": true
}, },
@ -101,6 +102,13 @@
"name": "feedback", "name": "feedback",
"type": "text", "type": "text",
"default": "Good work!" "default": "Good work!"
},
{
"label": "Try again button text",
"importance": "low",
"name": "tryAgain",
"type": "text",
"default": "Try again?"
} }
] ]
} }

50
timer.js Normal file
View File

@ -0,0 +1,50 @@
(function (MemoryGame, Timer) {
/**
* Adapter between memory game and H5P.Timer
*
* @class H5P.MemoryGame.Timer
* @extends H5P.Timer
* @param {Element} element
*/
MemoryGame.Timer = function (element) {
/** @alias H5P.MemoryGame.Timer# */
var self = this;
// Initialize event inheritance
Timer.call(self, 100);
/** @private {string} */
var naturalState = element.innerText;
/**
* Set up callback for time updates.
* Formats time stamp for humans.
*
* @private
*/
var update = function () {
var time = self.getTime();
var minutes = Timer.extractTimeElement(time, 'minutes');
var seconds = Timer.extractTimeElement(time, 'seconds') % 60;
if (seconds < 10) {
seconds = '0' + seconds;
}
element.innerText = minutes + ':' + seconds;
};
// Setup default behavior
self.notify('every_tenth_second', update);
self.on('reset', function () {
element.innerText = naturalState;
self.notify('every_tenth_second', update);
});
};
// Inheritance
MemoryGame.Timer.prototype = Object.create(Timer.prototype);
MemoryGame.Timer.prototype.constructor = MemoryGame.Timer;
})(H5P.MemoryGame, H5P.Timer);