Actualización
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 400 225" style="enable-background:new 0 0 400 225;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;}
|
||||
.st1{fill:#F0F3FC;}
|
||||
.st2{fill:#4574FF;}
|
||||
.st3{fill:#3D6084;}
|
||||
.st4{fill:#2C4277;}
|
||||
.st5{fill:#DAEFE0;}
|
||||
.st6{fill:#84BE93;}
|
||||
.st7{fill:#435E49;}
|
||||
</style>
|
||||
<title>column</title>
|
||||
<g>
|
||||
<g id="Layer_2">
|
||||
<g id="column">
|
||||
<rect y="0" class="st0" width="400" height="225"/>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M219.5,187.4L219.5,187.4c-6.5,6.5-17.3,6.5-23.9,0l-85-85c-6.5-6.5-6.5-17.3,0-23.9l0,0
|
||||
c6.5-6.5,17.3-6.5,23.9,0l85,85C226.1,170.2,226.1,180.9,219.5,187.4z"/>
|
||||
<path class="st2" d="M206.6,194.1c-1.3-0.1-2.6-0.2-3.9-0.6l0.9-3.4c1.1,0.2,2.1,0.5,3.2,0.5L206.6,194.1z M210.5,193.9
|
||||
l-0.6-3.5c1.1-0.1,2.1-0.5,3.1-0.8l1.3,3.3C213,193.4,211.8,193.7,210.5,193.9z M199.1,192.1c-1.2-0.6-2.2-1.3-3.3-2.1l2.2-2.7
|
||||
c0.8,0.7,1.8,1.2,2.7,1.6L199.1,192.1z M217.7,191.2l-1.9-2.9c0.8-0.6,1.6-1.3,2.5-2l0.1-0.1l2.5,2.5l-0.2,0.2
|
||||
C219.8,189.7,218.9,190.5,217.7,191.2z M193.1,187.4l-2.5-2.5l2.5-2.5l2.5,2.5L193.1,187.4z M223.3,185.4l-2.9-1.9
|
||||
c0.6-0.9,1.1-1.9,1.4-2.8l3.3,1.2C224.6,183.2,224.1,184.4,223.3,185.4z M188.2,182.5l-2.5-2.5l2.5-2.5l2.5,2.5L188.2,182.5z
|
||||
M225.9,178.2l-3.5-0.5c0.1-0.7,0.1-1.4,0.1-2.1c0-0.4,0-0.7,0-1.1l3.5-0.2c0,0.5,0,0.8,0,1.3
|
||||
C226.2,176.5,226.1,177.3,225.9,178.2z M183.1,177.5l-2.5-2.5l2.5-2.5l2.5,2.5L183.1,177.5z M178.2,172.5l-2.5-2.5l2.5-2.5
|
||||
l2.5,2.5L178.2,172.5z M222.1,171.4c-0.2-1.1-0.7-2-1.2-2.9l3.2-1.6c0.6,1.2,1.1,2.4,1.4,3.6L222.1,171.4z M173.2,167.5
|
||||
l-2.5-2.5l2.5-2.5l2.5,2.5L173.2,167.5z M219.2,165.9c-0.2-0.4-0.6-0.7-0.9-1.1l-1.4-1.4l2.5-2.5l1.4,1.4
|
||||
c0.4,0.5,0.8,0.8,1.2,1.3L219.2,165.9z M168.2,162.5l-2.5-2.5l2.5-2.5l2.5,2.5L168.2,162.5z M214.4,160.9l-2.5-2.5l2.5-2.5
|
||||
l2.5,2.5L214.4,160.9z M163.2,157.5l-2.5-2.5l2.5-2.5l2.5,2.5L163.2,157.5z M209.3,155.9l-2.5-2.5l2.5-2.5l2.5,2.5L209.3,155.9
|
||||
z M158.2,152.5l-2.5-2.5l2.5-2.5l2.5,2.5L158.2,152.5z M204.4,150.9l-2.5-2.5l2.5-2.5l2.5,2.5L204.4,150.9z M153.2,147.5
|
||||
l-2.5-2.5l2.5-2.5l2.5,2.5L153.2,147.5z M199.3,146l-2.5-2.5l2.5-2.5l2.5,2.5L199.3,146z M148.1,142.6l-2.5-2.5l2.5-2.5
|
||||
l2.5,2.5L148.1,142.6z M194.4,140.9l-2.5-2.5l2.5-2.5l2.5,2.5L194.4,140.9z M143.2,137.5l-2.5-2.5l2.5-2.5l2.5,2.5L143.2,137.5
|
||||
z M189.3,136l-2.5-2.5l2.5-2.5l2.5,2.5L189.3,136z M138.3,132.6l-2.5-2.5l2.5-2.5l2.5,2.5L138.3,132.6z M184.4,130.9l-2.5-2.5
|
||||
l2.5-2.5l2.5,2.5L184.4,130.9z M133.2,127.5l-2.5-2.5l2.5-2.5l2.5,2.5L133.2,127.5z M179.5,126l-2.5-2.5l2.5-2.5l2.5,2.5
|
||||
L179.5,126z M128.3,122.6l-2.5-2.5l2.5-2.5l2.5,2.5L128.3,122.6z M174.4,120.9l-2.5-2.5l2.5-2.5l2.5,2.5L174.4,120.9z
|
||||
M123.2,117.5l-2.5-2.5l2.5-2.5l2.5,2.5L123.2,117.5z M169.4,116l-2.5-2.5l2.5-2.5l2.5,2.5L169.4,116z M118.3,112.6l-2.5-2.5
|
||||
l2.5-2.5l2.5,2.5L118.3,112.6z M164.4,111l-2.5-2.5l2.5-2.5l2.5,2.5L164.4,111z M113.2,107.6l-2.5-2.5l2.5-2.5l2.5,2.5
|
||||
L113.2,107.6z M159.4,106l-2.5-2.5l2.5-2.5l2.5,2.5L159.4,106z M108.3,102.5c-0.8-1.1-1.5-2.1-2.1-3.3l3.2-1.6
|
||||
c0.5,0.9,1.1,1.8,1.8,2.6L108.3,102.5z M154.5,101l-2.5-2.5l2.5-2.5l2.5,2.5L154.5,101z M149.4,96l-2.5-2.5l2.5-2.5l2.5,2.5
|
||||
L149.4,96z M104.6,95.5c-0.4-1.3-0.6-2.6-0.6-3.9l3.5-0.2c0.1,1.1,0.2,2.1,0.5,3.2L104.6,95.5z M144.5,91l-2.5-2.5l2.5-2.5
|
||||
l2.5,2.5L144.5,91z M107.7,88.3l-3.5-0.5c0.2-1.3,0.5-2.6,0.9-3.8l3.3,1.2C108.1,86.2,107.9,87.3,107.7,88.3z M139.4,86
|
||||
l-2.5-2.5l2.5-2.5l2.5,2.5L139.4,86z M109.9,82.4l-2.9-1.9c0.7-1.1,1.5-2.1,2.4-3.1l2.6,2.5C111.1,80.7,110.5,81.5,109.9,82.4z
|
||||
M134.5,81l-1.2-1.2c-0.4-0.4-0.8-0.7-1.3-1.1l2.2-2.8c0.6,0.5,1.1,0.9,1.5,1.4l1.2,1.2L134.5,81z M114.3,78l-1.9-2.9
|
||||
c1.1-0.7,2.2-1.3,3.4-1.8l1.3,3.3C116.1,76.9,115.2,77.4,114.3,78z M129.4,77c-0.9-0.5-1.9-0.8-2.9-1.1l0.8-3.4
|
||||
c1.3,0.4,2.5,0.8,3.6,1.3L129.4,77z M120.1,75.7l-0.6-3.5c0.9-0.1,2-0.2,2.9-0.2c0.2,0,0.6,0,0.9,0l-0.1,3.5
|
||||
c-0.2,0-0.5,0-0.7,0C121.8,75.5,121,75.6,120.1,75.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st3" d="M123.9,58.4L123.9,58.4c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C131.3,55,128,58.4,123.9,58.4z"/>
|
||||
<path class="st3" d="M164.7,58.4L164.7,58.4c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C172.3,55,168.9,58.4,164.7,58.4z"/>
|
||||
<path class="st4" d="M121.1,98.9L121.1,98.9c-4.3,0-7.8-3.5-7.8-7.8l0,0c0-4.3,3.5-7.8,7.8-7.8l0,0c4.3,0,7.8,3.5,7.8,7.8l0,0
|
||||
C128.8,95.4,125.4,98.9,121.1,98.9z"/>
|
||||
<path class="st3" d="M164.7,99.4L164.7,99.4c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C172.3,96,168.9,99.4,164.7,99.4z"/>
|
||||
<path class="st3" d="M205.7,58.4L205.7,58.4c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C213.1,55,209.8,58.4,205.7,58.4z"/>
|
||||
<path class="st3" d="M205.7,99.4L205.7,99.4c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C213.1,96,209.8,99.4,205.7,99.4z"/>
|
||||
<path class="st3" d="M123.9,140.2L123.9,140.2c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C131.3,136.8,128,140.2,123.9,140.2z"/>
|
||||
<path class="st4" d="M164.7,140.2L164.7,140.2c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C172.3,136.8,168.9,140.2,164.7,140.2z"/>
|
||||
<path class="st3" d="M123.9,181.2L123.9,181.2c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C131.3,177.8,128,181.2,123.9,181.2z"/>
|
||||
<path class="st3" d="M164.7,181.2L164.7,181.2c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C172.3,177.8,168.9,181.2,164.7,181.2z"/>
|
||||
<path class="st3" d="M205.7,140.2L205.7,140.2c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C213.1,136.8,209.8,140.2,205.7,140.2z"/>
|
||||
<path class="st4" d="M205.7,181.2L205.7,181.2c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C213.1,177.8,209.8,181.2,205.7,181.2z"/>
|
||||
<path class="st3" d="M246.5,181.2L246.5,181.2c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C254.1,177.8,250.6,181.2,246.5,181.2z"/>
|
||||
<path class="st3" d="M287.5,58.4L287.5,58.4c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C294.9,55,291.6,58.4,287.5,58.4z"/>
|
||||
<path class="st3" d="M287.5,99.4L287.5,99.4c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C294.9,96,291.6,99.4,287.5,99.4z"/>
|
||||
<path class="st3" d="M287.5,140.2L287.5,140.2c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C294.9,136.8,291.6,140.2,287.5,140.2z"/>
|
||||
<path class="st3" d="M287.5,181.2L287.5,181.2c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C294.9,177.8,291.6,181.2,287.5,181.2z"/>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st5" d="M246.5,154.7L246.5,154.7c-9.4,0-17.1-7.6-17.1-17.1V48.4c0-9.4,7.6-17.1,17.1-17.1l0,0
|
||||
c9.4,0,17.1,7.6,17.1,17.1v89.2C263.6,147.1,255.9,154.7,246.5,154.7z"/>
|
||||
<path class="st6" d="M246.5,156.5c-10.4,0-18.8-8.5-18.8-18.8V48.4c0-10.4,8.5-18.8,18.8-18.8s18.8,8.5,18.8,18.8v89.2
|
||||
C265.4,148,256.9,156.5,246.5,156.5z M246.5,33.1c-8.5,0-15.3,6.8-15.3,15.3v89.2c0,8.5,6.8,15.3,15.3,15.3
|
||||
c8.5,0,15.3-6.8,15.3-15.3V48.4C261.8,40,255,33.1,246.5,33.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st7" d="M246.5,58.4L246.5,58.4c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C254.1,55,250.6,58.4,246.5,58.4z"/>
|
||||
<path class="st7" d="M246.5,99.4L246.5,99.4c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C254.1,96,250.6,99.4,246.5,99.4z"/>
|
||||
<path class="st7" d="M246.5,140.2L246.5,140.2c-4.1,0-7.5-3.4-7.5-7.5l0,0c0-4.1,3.4-7.5,7.5-7.5l0,0c4.1,0,7.5,3.4,7.5,7.5l0,0
|
||||
C254.1,136.8,250.6,140.2,246.5,140.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.0 KiB |
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"title": "Find The Words",
|
||||
"description": "Game to find a list of words in a given word grid",
|
||||
"machineName": "H5P.FindTheWords",
|
||||
"majorVersion": 1,
|
||||
"minorVersion": 4,
|
||||
"patchVersion": 4,
|
||||
"embedTypes": [
|
||||
"iframe"
|
||||
],
|
||||
"runnable": 1,
|
||||
"author": "JithinSpace",
|
||||
"license": "MIT",
|
||||
"preloadedCss": [
|
||||
{
|
||||
"path": "styles/h5p-find-the-words.css"
|
||||
}
|
||||
],
|
||||
"preloadedJs": [
|
||||
{
|
||||
"path": "scripts/h5p-find-the-words.js"
|
||||
},
|
||||
{
|
||||
"path": "scripts/h5p-find-the-words-word-grid.js"
|
||||
},
|
||||
{
|
||||
"path": "scripts/h5p-find-the-words-vocabulary.js"
|
||||
},
|
||||
{
|
||||
"path": "scripts/h5p-find-the-words-timer.js"
|
||||
},
|
||||
{
|
||||
"path": "scripts/h5p-find-the-words-counter.js"
|
||||
}
|
||||
],
|
||||
"preloadedDependencies": [
|
||||
{
|
||||
"machineName": "H5P.Timer",
|
||||
"majorVersion": 0,
|
||||
"minorVersion": 4
|
||||
},
|
||||
{
|
||||
"machineName": "FontAwesome",
|
||||
"majorVersion": 4,
|
||||
"minorVersion": 5
|
||||
},
|
||||
{
|
||||
"machineName": "H5P.JoubelUI",
|
||||
"majorVersion": 1,
|
||||
"minorVersion": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
(function (FindTheWords) {
|
||||
/**
|
||||
* Keeps track of the number of times the game is submitted.
|
||||
* @class H5P.FindTheWords.Counter
|
||||
* @param {H5P.jQuery} $container
|
||||
*/
|
||||
|
||||
FindTheWords.Counter = function ($container) {
|
||||
/** @alias H5P.FindTheWords.Counter# */
|
||||
var self = this;
|
||||
var current = 0;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
var update = function () {
|
||||
$container[0].innerText = current;
|
||||
};
|
||||
|
||||
/**
|
||||
* Increment the counter.
|
||||
*/
|
||||
self.increment = function () {
|
||||
current++;
|
||||
update();
|
||||
};
|
||||
/**
|
||||
* Revert counter back to its natural state
|
||||
*/
|
||||
self.reset = function () {
|
||||
current = 0;
|
||||
update();
|
||||
};
|
||||
};
|
||||
|
||||
})(H5P.FindTheWords);
|
||||
@@ -0,0 +1,47 @@
|
||||
(function (FindTheWords, Timer) {
|
||||
|
||||
/**
|
||||
* FindTheWords.Timer - Adapter between Find the words and H5P.Timer.
|
||||
* @class H5P.FindTheWords.Timer
|
||||
* @extends H5P.Timer
|
||||
* @param {H5P.jQuery} $element
|
||||
*/
|
||||
FindTheWords.Timer = function ($element) {
|
||||
/** @alias H5P.FindTheWords.Timer# */
|
||||
const that = this;
|
||||
// Initialize event inheritance
|
||||
Timer.call(that, 100);
|
||||
|
||||
/** @private {string} */
|
||||
const naturalState = '0:00';
|
||||
|
||||
/**
|
||||
* update - Set up callback for time updates.
|
||||
* Formats time stamp for humans.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
const update = function () {
|
||||
const time = that.getTime();
|
||||
|
||||
const minutes = Timer.extractTimeElement(time, 'minutes');
|
||||
let seconds = Timer.extractTimeElement(time, 'seconds') % 60;
|
||||
if (seconds < 10) {
|
||||
seconds = '0' + seconds;
|
||||
}
|
||||
$element.text(minutes + ':' + seconds);
|
||||
};
|
||||
|
||||
// Setup default behavior
|
||||
that.notify('every_tenth_second', update);
|
||||
that.on('reset', function () {
|
||||
$element.text(naturalState);
|
||||
that.notify('every_tenth_second', update);
|
||||
});
|
||||
};
|
||||
|
||||
// Inheritance
|
||||
FindTheWords.Timer.prototype = Object.create(Timer.prototype);
|
||||
FindTheWords.Timer.prototype.constructor = FindTheWords.Timer;
|
||||
|
||||
}) (H5P.FindTheWords, H5P.Timer);
|
||||
@@ -0,0 +1,137 @@
|
||||
(function (FindTheWords, EventDispatcher, $) {
|
||||
|
||||
/**
|
||||
* Vocabulary - Handles the vocabulary part.
|
||||
* @class H5P.FindTheWords.Vocabulary
|
||||
* @param {Object} params
|
||||
* @param {boolean} showVocabulary
|
||||
*/
|
||||
FindTheWords.Vocabulary = function (params, showVocabulary, header) {
|
||||
/** @alias H5P.FindTheWords.Vocabulary# */
|
||||
this.words = params;
|
||||
this.header = header;
|
||||
this.showVocabulary = showVocabulary;
|
||||
this.wordsFound = [];
|
||||
this.wordsNotFound = [];
|
||||
this.wordsSolved = [];
|
||||
};
|
||||
|
||||
FindTheWords.Vocabulary.prototype = Object.create(EventDispatcher.prototype);
|
||||
FindTheWords.Vocabulary.prototype.constructor = FindTheWords.Vocabulary;
|
||||
|
||||
/**
|
||||
* appendTo - appending vocabulary to the play area.
|
||||
* @param {H5P.jQuery} $container
|
||||
* @param {string} isModeBlock Either in inline/block mode.
|
||||
*/
|
||||
FindTheWords.Vocabulary.prototype.appendTo = function ($container, isModeBlock) {
|
||||
let output = '<div class="vocHeading"><em class="fa fa-book fa-fw" ></em>' +
|
||||
this.header + '</div><ul role="list" tabindex="0">';
|
||||
this.words.forEach(function (element) {
|
||||
const identifierName = element.replace(/ /g, '');
|
||||
output += '<li role="presentation" ><div role="listitem" aria-label="' + identifierName + ' not found" id="' + identifierName + '"class="word">\
|
||||
<em class="fa fa-check" ></em>' + element + '</div></li>';
|
||||
});
|
||||
output += '</ul>';
|
||||
|
||||
$container.html(output);
|
||||
$container.addClass('vocabulary-container');
|
||||
this.$container = $container;
|
||||
this.setMode(isModeBlock);
|
||||
};
|
||||
|
||||
/**
|
||||
* setMode - set the vocabularies.
|
||||
* @param {string} mode
|
||||
*/
|
||||
FindTheWords.Vocabulary.prototype.setMode = function (isModeBlock) {
|
||||
this.$container
|
||||
.toggleClass('vocabulary-block-container', isModeBlock)
|
||||
.toggleClass('vocabulary-inline-container', !isModeBlock);
|
||||
};
|
||||
|
||||
/**
|
||||
* checkWord - if the marked word belongs to the vocabulary as not found.
|
||||
* @param {string} word
|
||||
*/
|
||||
FindTheWords.Vocabulary.prototype.checkWord = function (word) {
|
||||
const reverse = word.split('').reverse().join('');
|
||||
const originalWord = (this.words.indexOf(word) !== -1) ? word : ( this.words.indexOf(reverse) !== -1) ? reverse : null;
|
||||
|
||||
if (!originalWord || this.wordsFound.indexOf(originalWord) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.wordsFound.push(originalWord);
|
||||
if (this.showVocabulary) {
|
||||
const idName = originalWord.replace(/ /g, '');
|
||||
this.$container.find('#' + idName).addClass('word-found').attr('aria-label', idName + ' found');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* reset - reset the vocabulary upon game resetting.
|
||||
*/
|
||||
FindTheWords.Vocabulary.prototype.reset = function () {
|
||||
this.wordsFound = [];
|
||||
this.wordsNotFound = this.words;
|
||||
if (this.showVocabulary) {
|
||||
this.$container.find('.word').each(function () {
|
||||
$(this).removeClass('word-found').removeClass('word-solved').attr('aria-label', $(this).attr('id') + ' not found');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* solveWords - changes on vocabulary upon showing the solution.
|
||||
*/
|
||||
FindTheWords.Vocabulary.prototype.solveWords = function () {
|
||||
const that = this;
|
||||
that.wordsSolved = that.wordsNotFound;
|
||||
if (that.showVocabulary) {
|
||||
that.wordsNotFound.forEach(function (word) {
|
||||
const idName = word.replace(/ /g, '');
|
||||
that.$container.find('#' + idName).addClass('word-solved').attr('aria-label', idName + ' solved');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getNotFound - return the list of words that are not found yet.
|
||||
* @return {Object[]}
|
||||
*/
|
||||
FindTheWords.Vocabulary.prototype.getNotFound = function () {
|
||||
const that = this;
|
||||
this.wordsNotFound = this.words.filter(function (word) {
|
||||
return (that.wordsFound.indexOf(word) === -1);
|
||||
});
|
||||
return this.wordsNotFound;
|
||||
};
|
||||
|
||||
/**
|
||||
* getFound - returns the words found so far.
|
||||
* @return {Object[]}
|
||||
*/
|
||||
FindTheWords.Vocabulary.prototype.getFound = function () {
|
||||
const that = this;
|
||||
return this.words.filter(function (word) {
|
||||
return (that.wordsFound.indexOf(word) !== -1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* getSolved - get the words solved by the game by show solution feature.
|
||||
* @return {Object[]}
|
||||
*/
|
||||
FindTheWords.Vocabulary.prototype.getSolved = function () {
|
||||
const that = this;
|
||||
return this.words.filter(function (word) {
|
||||
return (that.wordsSolved.indexOf(word) !== -1);
|
||||
});
|
||||
};
|
||||
|
||||
return FindTheWords.Vocabulary;
|
||||
|
||||
}) (H5P.FindTheWords, H5P.EventDispatcher, H5P.jQuery);
|
||||
@@ -0,0 +1,788 @@
|
||||
(function (FindTheWords, EventDispatcher, $) {
|
||||
|
||||
/**
|
||||
* WordGrid - Handles the word grid part of the game.
|
||||
* @class H5P.FindTheWords.WordGrid
|
||||
* @extends H5P.EventDispatcher
|
||||
* @param {Object} params Description.
|
||||
*/
|
||||
FindTheWords.WordGrid = function (params) {
|
||||
/** @alias H5P.FindTheWords.WordGrid# */
|
||||
// extending the default parameter set for the grid
|
||||
this.options = params;
|
||||
|
||||
EventDispatcher.call(this);
|
||||
|
||||
this.createWordGrid();
|
||||
};
|
||||
|
||||
FindTheWords.WordGrid.prototype = Object.create(EventDispatcher.prototype);
|
||||
FindTheWords.WordGrid.prototype.constructor = FindTheWords.WordGrid;
|
||||
|
||||
// get i th element position based on the current position for different orientations
|
||||
const orientations = {
|
||||
horizontal: function (x, y, i) {
|
||||
return {
|
||||
x: x + i,
|
||||
y: y
|
||||
};
|
||||
},
|
||||
horizontalBack: function (x, y, i) {
|
||||
return {
|
||||
x: x - i,
|
||||
y: y
|
||||
};
|
||||
},
|
||||
vertical: function (x, y, i) {
|
||||
return {
|
||||
x: x,
|
||||
y: y + i
|
||||
};
|
||||
},
|
||||
verticalUp: function (x, y, i) {
|
||||
return {
|
||||
x: x,
|
||||
y: y - i
|
||||
};
|
||||
},
|
||||
diagonal: function (x, y, i) {
|
||||
return {
|
||||
x: x + i,
|
||||
y: y + i
|
||||
};
|
||||
},
|
||||
diagonalBack: function (x, y, i) {
|
||||
return {
|
||||
x: x - i,
|
||||
y: y + i
|
||||
};
|
||||
},
|
||||
diagonalUp: function (x, y, i) {
|
||||
return {
|
||||
x: x + i,
|
||||
y: y - i
|
||||
};
|
||||
},
|
||||
diagonalUpBack: function (x, y, i) {
|
||||
return {
|
||||
x: x - i,
|
||||
y: y - i
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Determines if an orientation is possible given the starting square (x,y),
|
||||
* the height (h) and width (w) of the puzzle, and the length of the word (l).
|
||||
* Returns true if the word will fit starting at the square provided using
|
||||
* the specified orientation.
|
||||
*/
|
||||
const checkOrientations = {
|
||||
horizontal: function (x, y, h, w, l) {
|
||||
return w >= x + l;
|
||||
},
|
||||
horizontalBack: function (x, y, h, w, l) {
|
||||
return x + 1 >= l;
|
||||
},
|
||||
vertical: function (x, y, h, w, l) {
|
||||
return h >= y + l;
|
||||
},
|
||||
verticalUp: function (x, y, h, w, l) {
|
||||
return y + 1 >= l;
|
||||
},
|
||||
diagonal: function (x, y, h, w, l) {
|
||||
return (w >= x + l) && (h >= y + l);
|
||||
},
|
||||
diagonalBack: function (x, y, h, w, l) {
|
||||
return (x + 1 >= l) && (h >= y + l);
|
||||
},
|
||||
diagonalUp: function (x, y, h, w, l) {
|
||||
return (w >= x + l) && (y + 1 >= l);
|
||||
},
|
||||
diagonalUpBack: function (x, y, h, w, l) {
|
||||
return (x + 1 >= l) && (y + 1 >= l);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Determines the next possible valid square given the square (x,y) was ]
|
||||
* invalid and a word lenght of (l). This greatly reduces the number of
|
||||
* squares that must be checked. Returning {x: x+1, y: y} will always work
|
||||
* but will not be optimal.
|
||||
*/
|
||||
const skipOrientations = {
|
||||
horizontal: function (x, y) {
|
||||
return {
|
||||
x: 0,
|
||||
y: y + 1
|
||||
};
|
||||
},
|
||||
horizontalBack: function (x, y, l) {
|
||||
return {
|
||||
x: l - 1,
|
||||
y: y
|
||||
};
|
||||
},
|
||||
vertical: function (x, y) {
|
||||
return {
|
||||
x: 0,
|
||||
y: y + 100
|
||||
};
|
||||
},
|
||||
verticalUp: function (x, y, l) {
|
||||
return {
|
||||
x: 0,
|
||||
y: l - 1
|
||||
};
|
||||
},
|
||||
diagonal: function (x, y) {
|
||||
return {
|
||||
x: 0,
|
||||
y: y + 1
|
||||
};
|
||||
},
|
||||
diagonalBack: function (x, y, l) {
|
||||
return {
|
||||
x: l - 1,
|
||||
y: x >= l - 1 ? y + 1 : y
|
||||
};
|
||||
},
|
||||
diagonalUp: function (x, y, l) {
|
||||
return {
|
||||
x: 0,
|
||||
y: y < l - 1 ? l - 1 : y + 1
|
||||
};
|
||||
},
|
||||
diagonalUpBack: function (x, y, l) {
|
||||
return {
|
||||
x: l - 1,
|
||||
y: x >= l - 1 ? y + 1 : y
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* calcOverlap - returns the overlap if the word can be fitted with the grid parameters provided.
|
||||
* @param {string} word Word to be fitted.
|
||||
* @param {Object[]} wordGrid Grid to which word needs to be fitted.
|
||||
* @param {number} x Starting x cordinate.
|
||||
* @param {nuber} y Starting y cordinate.
|
||||
* @param {function} fnGetSquare Function to get the next grid pos as per the specified direction.
|
||||
* @return {number} Overlap value if it can be fitted , -1 otherwise.
|
||||
*/
|
||||
const calcOverlap = function (word, wordGrid, x, y, fnGetSquare) {
|
||||
let overlap = 0;
|
||||
|
||||
// traverse the squares to determine if the word fits
|
||||
for (let index = 0 ; index < word.length; index++) {
|
||||
const next = fnGetSquare(x, y, index);
|
||||
const square = wordGrid[next.y][next.x];
|
||||
if (square === word[index]) {
|
||||
overlap++;
|
||||
}
|
||||
else if (square !== '') {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return overlap;
|
||||
};
|
||||
|
||||
/**
|
||||
* findBestLocations - Find the best possible location for a word in the grid.
|
||||
* @param {Object[]} wordGrid
|
||||
* @param {Object} options
|
||||
* @param {string} word
|
||||
*/
|
||||
const findBestLocations = function (wordGrid, options, word) {
|
||||
const locations = [];
|
||||
const height = options.height;
|
||||
const width = options.width;
|
||||
const wordLength = word.length;
|
||||
let maxOverlap = 0;
|
||||
|
||||
options.orientations.forEach(function (orientation) {
|
||||
|
||||
const check = checkOrientations[orientation];
|
||||
const next = orientations[orientation];
|
||||
const skipTo = skipOrientations[orientation];
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
while (y < height) {
|
||||
if (check(x, y, height, width, wordLength)) {
|
||||
const overlap = calcOverlap(word, wordGrid, x, y, next);
|
||||
if (overlap >= maxOverlap || (!options.preferOverlap && overlap > -1 )) {
|
||||
maxOverlap = overlap;
|
||||
locations.push({
|
||||
x: x,
|
||||
y: y,
|
||||
orientation: orientation,
|
||||
overlap: overlap
|
||||
});
|
||||
}
|
||||
x++;
|
||||
if ( x >= width) {
|
||||
x = 0;
|
||||
y++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const nextPossible = skipTo(x, y, wordLength);
|
||||
x = nextPossible.x;
|
||||
y = nextPossible.y;
|
||||
}
|
||||
}
|
||||
});
|
||||
return locations;
|
||||
};
|
||||
|
||||
/**
|
||||
* placeWordInGrid - find the best location and place the word.
|
||||
* @param {Object[]} wordGrid
|
||||
* @param {Object} options
|
||||
* @param {string} word
|
||||
*/
|
||||
const placeWordInGrid = function (wordGrid, options, word) {
|
||||
const locations = findBestLocations(wordGrid, options, word);
|
||||
if (locations.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const selectedLoc = locations[Math.floor(Math.random() * locations.length)];
|
||||
for (let index = 0; index < word.length; index++) {
|
||||
const next = orientations[selectedLoc.orientation](selectedLoc.x, selectedLoc.y, index);
|
||||
wordGrid[next.y][next.x] = word[index];
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* fillGrid - Create an empty grid and fill it with words.
|
||||
* @param {Object[]} words Description.
|
||||
* @param {Object} options Description.
|
||||
* @return {Object[]|null} Grid array if all words can be fitted, else null.
|
||||
*/
|
||||
const fillGrid = function (words, options) {
|
||||
const wordGrid = [];
|
||||
for (let i = 0; i < options.height; i++) {
|
||||
wordGrid[i] = [];
|
||||
for (let j = 0; j < options.width; j++) {
|
||||
wordGrid[i][j] = '';
|
||||
}
|
||||
}
|
||||
|
||||
for (const i in words) {
|
||||
if (!placeWordInGrid(wordGrid, options, words[i])) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return wordGrid;
|
||||
};
|
||||
|
||||
/**
|
||||
* fillBlanks - fill the unoccupied spaces with blanks.
|
||||
* @param {Object[]} wordGrid
|
||||
* @param {string} fillPool
|
||||
* @return {Object[]} Resulting word grid.
|
||||
*/
|
||||
const fillBlanks = function (wordGrid, fillPool) {
|
||||
for (let i = 0; i < wordGrid.length; i++) {
|
||||
for (let j = 0;j < wordGrid[0].length; j++) {
|
||||
if (!wordGrid[i][j]) {
|
||||
const randomLetter = Math.floor(Math.random() * fillPool.length);
|
||||
wordGrid[i][j] = fillPool[randomLetter];
|
||||
}
|
||||
}
|
||||
}
|
||||
return wordGrid;
|
||||
};
|
||||
|
||||
/**
|
||||
* calculateCordinates - function to calculate the cordinates & grid postions at which the event occured.
|
||||
* @param {number} x X-cordinate of the event.
|
||||
* @param {number} y Y-cordinate of the event.
|
||||
* @param {number} elementSize Current element size.
|
||||
* @return {Object[]} [normalized x, normalized y, row ,col].
|
||||
*/
|
||||
const calculateCordinates = function (x, y, elementSize) {
|
||||
const row1 = Math.floor(x / elementSize);
|
||||
const col1 = Math.floor(y / elementSize);
|
||||
const x_click = row1 * elementSize + (elementSize / 2);
|
||||
const y_click = col1 * elementSize + (elementSize / 2);
|
||||
return [x_click, y_click, row1, col1];
|
||||
};
|
||||
|
||||
/*
|
||||
* function to process the line drawn to find if it is a valid marking
|
||||
* in terms of possible grid directions
|
||||
* returns directional value if it is a valid marking
|
||||
* else return false
|
||||
*/
|
||||
|
||||
/**
|
||||
* getValidDirection - process the line drawn to find if it is a valid marking.
|
||||
* @param {number} x1 Starting x cordinate.
|
||||
* @param {number} y1 Starting y cordinate.
|
||||
* @param {number} x2 Ending x cordinate.
|
||||
* @param {number} y2 Ending y cordinate.
|
||||
* @return {Object[]|boolean} Direction array if a valid marking, false otherwise.
|
||||
*/
|
||||
const getValidDirection = function (x1, y1, x2, y2) {
|
||||
const dirx = (x2 > x1) ? 1 : ((x2 < x1) ? -1 : 0);
|
||||
const diry = (y2 > y1) ? 1 : ((y2 < y1) ? -1 : 0);
|
||||
let y = y1;
|
||||
let x = x1;
|
||||
|
||||
if (dirx !== 0) {
|
||||
while (x !== x2) {
|
||||
x = x + dirx;
|
||||
y = y + diry;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (y !== y2) {
|
||||
y = y + diry;
|
||||
}
|
||||
}
|
||||
|
||||
if (y2 === y) {
|
||||
return [dirx, diry];
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// All event handlers are registered here
|
||||
|
||||
/**
|
||||
* mouseDownEventHandler.
|
||||
* @param {Object} e Event Object.
|
||||
* @param {HTMLelement} canvas Html5 canvas element.
|
||||
* @param {number} elementSize Element size.
|
||||
* @return {Object[]}
|
||||
*/
|
||||
const mouseDownEventHandler = function (e, canvas, elementSize) {
|
||||
const x = e.pageX - $(canvas).offset().left;
|
||||
const y = e.pageY - $(canvas).offset().top;
|
||||
return calculateCordinates(x, y, elementSize);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* event handler for handling mousemove events
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* mouseMoveEventHandler.
|
||||
* @param {Object} e Event Object.
|
||||
* @param {HTMLelement} canvas Html5 Canvas Element.
|
||||
* @param {Object[]} srcPos Position from which the movement started.
|
||||
* @param {number} eSize Current element size.
|
||||
*/
|
||||
const mouseMoveEventHandler = function (e, canvas, srcPos, eSize) {
|
||||
const offsetTop = ($(canvas).offset().top > eSize * 0.75) ? Math.floor(eSize * 0.75) : $(canvas).offset().top;
|
||||
const desX = e.pageX - $(canvas).offset().left;
|
||||
const desY = e.pageY - Math.abs(offsetTop);
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
// Draw the current marking
|
||||
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
|
||||
context.fillStyle = 'rgba(107,177,125,0.3)';
|
||||
context.beginPath();
|
||||
context.lineCap = 'round';
|
||||
context.moveTo(srcPos[0] - (eSize / 8), srcPos[1] + (offsetTop / 8));
|
||||
context.strokeStyle = 'rgba(107,177,125,0.4)';
|
||||
context.lineWidth = Math.floor(eSize / 2);
|
||||
context.lineTo(desX - (eSize / 8), desY + (offsetTop / 8));
|
||||
context.stroke();
|
||||
context.closePath();
|
||||
};
|
||||
|
||||
/*
|
||||
* event handler for handling mouseup events
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* mouseUpEventHandler.
|
||||
* @param {Object} e Event Object.
|
||||
* @param {HTMLelement} canvas Html5 Canvas Element.
|
||||
* @param {number} elementSize Current element size.
|
||||
* @param {Object[]} clickStart Starting Event location.
|
||||
* @return {Object} return staring,ending and direction of the current marking.
|
||||
*/
|
||||
const mouseUpEventHandler = function (e, canvas, elementSize, clickStart) {
|
||||
let wordObject = {};
|
||||
const offsetTop = ($(canvas).offset().top > elementSize * 0.75) ? Math.floor(elementSize * 0.75) * (-1) : $(canvas).offset().top;
|
||||
const x = e.pageX - $(canvas).offset().left;
|
||||
const y = e.pageY - Math.abs(offsetTop);
|
||||
const clickEnd = calculateCordinates(x, y, elementSize);
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
if ((Math.abs(clickEnd[0] - x) < 20) && (Math.abs(clickEnd[1] - y) < 15)) {
|
||||
// Drag ended within permissible range
|
||||
wordObject = {
|
||||
'start': clickStart,
|
||||
'end': clickEnd,
|
||||
'dir': getValidDirection(clickStart[2], clickStart[3], clickEnd[2], clickEnd[3])
|
||||
};
|
||||
}
|
||||
|
||||
// Clear if there any markings started
|
||||
context.closePath();
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
return wordObject;
|
||||
};
|
||||
|
||||
/**
|
||||
* touchHandler - Mapping touchevents to corresponding mouse events.
|
||||
* @param {Object} event Description.
|
||||
*/
|
||||
const touchHandler = function (event) {
|
||||
const touches = event.changedTouches;
|
||||
const first = touches[0];
|
||||
const simulatedEvent = document.createEvent('MouseEvent');
|
||||
|
||||
let type = '';
|
||||
switch (event.type) {
|
||||
case 'touchstart':
|
||||
type = 'mousedown';
|
||||
break;
|
||||
case 'touchmove':
|
||||
type = 'mousemove';
|
||||
break;
|
||||
case 'touchend':
|
||||
type = 'mouseup';
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// Created and fire a simulated mouse event
|
||||
simulatedEvent.initMouseEvent(type, true, true, window, 1,
|
||||
first.screenX, first.screenY,
|
||||
first.clientX, first.clientY, false,
|
||||
false, false, false, 0 /*left*/, null);
|
||||
first.target.dispatchEvent(simulatedEvent);
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
FindTheWords.WordGrid.prototype.createWordGrid = function () {
|
||||
let wordGrid = null ;
|
||||
let attempts = 0;
|
||||
|
||||
// sorting the words by length speedup the word fitting algorithm
|
||||
const wordList = this.options.vocabulary.slice(0).sort(function (a, b) {
|
||||
return (a.length < b.length);
|
||||
});
|
||||
|
||||
while (!wordGrid) {
|
||||
while (!wordGrid && attempts++ < this.options.maxAttempts) {
|
||||
wordGrid = fillGrid(wordList, this.options);
|
||||
}
|
||||
|
||||
// if grid cannot be formed in the current dimensions
|
||||
if (!wordGrid) {
|
||||
this.options.height++;
|
||||
this.options.width++;
|
||||
attempts = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// fill in empty spaces with random letters
|
||||
if (this.options.fillBlanks) {
|
||||
wordGrid = fillBlanks(wordGrid, this.options.fillPool);
|
||||
}
|
||||
|
||||
// set the output puzzle
|
||||
this.wordGrid = wordGrid;
|
||||
};
|
||||
|
||||
/**
|
||||
* markWord - mark the word on the output canvas (permanent).
|
||||
* @param {Object} wordParams
|
||||
*/
|
||||
FindTheWords.WordGrid.prototype.markWord = function (wordParams) {
|
||||
const dirKey = wordParams['directionKey'];
|
||||
const clickStart = wordParams['start'];
|
||||
const clickEnd = wordParams['end'];
|
||||
const context = this.$outputCanvas[0].getContext('2d');
|
||||
const offsetTop = (this.$container.offset().top > this.elementSize * 0.75) ? Math.floor(this.elementSize * 0.75) * (-1) : this.$container.offset().top;
|
||||
const topRadius = Math.floor(this.elementSize / 8);
|
||||
const bottomRadius = Math.abs(Math.floor(offsetTop / 8));
|
||||
const lineWidth = Math.floor(this.elementSize / 4);
|
||||
|
||||
let startingAngle;
|
||||
|
||||
// set the drawing property values
|
||||
context.lineWidth = 2;
|
||||
context.strokeStyle = 'rgba(107,177,125,0.9)';
|
||||
context.fillStyle = 'rgba(107,177,125,0.3)';
|
||||
|
||||
if (!this.options.gridActive) {
|
||||
context.strokeStyle = 'rgba(51, 102, 255,0.9)';
|
||||
context.fillStyle = 'rgba(51, 102, 255,0.1)';
|
||||
context.setLineDash([8, 4]);
|
||||
}
|
||||
|
||||
// find the arc starting angle depending on the direction
|
||||
switch (dirKey) {
|
||||
case 'horizontal': {
|
||||
startingAngle = (Math.PI / 2);
|
||||
break;
|
||||
}
|
||||
case 'horizontalBack': {
|
||||
startingAngle = -(Math.PI / 2);
|
||||
break;
|
||||
}
|
||||
case 'diagonal': {
|
||||
startingAngle = 3 * (Math.PI / 4);
|
||||
break;
|
||||
}
|
||||
case 'diagonalBack': {
|
||||
startingAngle = 5 * (Math.PI / 4);
|
||||
break;
|
||||
}
|
||||
case 'diagonalUp': {
|
||||
startingAngle = (Math.PI / 4);
|
||||
break;
|
||||
}
|
||||
case 'diagonalUpBack': {
|
||||
startingAngle = -(Math.PI / 4);
|
||||
break;
|
||||
}
|
||||
case 'vertical': {
|
||||
startingAngle = (Math.PI);
|
||||
break;
|
||||
}
|
||||
case 'verticalUp': {
|
||||
startingAngle = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// start drawing
|
||||
context.beginPath();
|
||||
context.arc(clickStart[0] - topRadius, clickStart[1] + bottomRadius, lineWidth, startingAngle, startingAngle + (Math.PI));
|
||||
context.arc(clickEnd[0] - topRadius, clickEnd[1] + bottomRadius, lineWidth, startingAngle + (Math.PI), startingAngle + (2 * Math.PI));
|
||||
context.closePath();
|
||||
context.stroke();
|
||||
context.fill();
|
||||
};
|
||||
|
||||
/**
|
||||
* mark - mark the words if they are not found.
|
||||
* @param {Object[]} wordList
|
||||
*/
|
||||
FindTheWords.WordGrid.prototype.mark = function (wordList) {
|
||||
const words = wordList;
|
||||
const that = this;
|
||||
const options = {
|
||||
height: this.wordGrid.length,
|
||||
width: this.wordGrid[0].length,
|
||||
orientations: this.options.orientations,
|
||||
preferOverlap: this.options.preferOverlap
|
||||
};
|
||||
const found = [];
|
||||
const notFound = [];
|
||||
|
||||
words.forEach(function (word) {
|
||||
const locations = findBestLocations(that.wordGrid, options, word);
|
||||
if (locations.length > 0 && locations[0].overlap === word.length) {
|
||||
locations[0].word = word;
|
||||
found.push(locations[0]);
|
||||
}
|
||||
else {
|
||||
notFound.push(word);
|
||||
}
|
||||
});
|
||||
|
||||
this.markSolution(found);
|
||||
};
|
||||
|
||||
/**
|
||||
* markSolution.
|
||||
* @param {Object[]} solutions
|
||||
*/
|
||||
FindTheWords.WordGrid.prototype.markSolution = function (solutions) {
|
||||
const that = this;
|
||||
|
||||
solutions.forEach(function (solution) {
|
||||
const next = orientations[solution.orientation];
|
||||
const word = solution.word;
|
||||
const startX = solution.x;
|
||||
const startY = solution.y;
|
||||
const endPos = next(startX, startY, word.length - 1);
|
||||
const clickStartX = startX * that.elementSize + (that.elementSize / 2);
|
||||
const clickStartY = startY * that.elementSize + (that.elementSize / 2);
|
||||
const clickEndX = endPos.x * that.elementSize + (that.elementSize / 2);
|
||||
const clickEndY = endPos.y * that.elementSize + (that.elementSize / 2);
|
||||
const wordParams = {
|
||||
'start': [clickStartX, clickStartY, startX, startY],
|
||||
'end': [clickEndX, clickEndY, endPos.x, endPos.y],
|
||||
'directionKey': solution.orientation
|
||||
};
|
||||
that.markWord(wordParams);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* disableGrid.
|
||||
*/
|
||||
FindTheWords.WordGrid.prototype.disableGrid = function () {
|
||||
this.options.gridActive = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* enableGrid.
|
||||
*/
|
||||
FindTheWords.WordGrid.prototype.enableGrid = function () {
|
||||
this.options.gridActive = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* appendTo - Placing the container for drawing the grid.
|
||||
* @param {H5P.jQuery} $container
|
||||
* @param {number} elementSize
|
||||
*/
|
||||
FindTheWords.WordGrid.prototype.appendTo = function ($container, elementSize) {
|
||||
this.$container = $container;
|
||||
this.canvasWidth = elementSize * this.wordGrid[0].length;
|
||||
this.canvasHeight = elementSize * this.wordGrid.length;
|
||||
this.elementSize = elementSize;
|
||||
$container.css('height', this.canvasHeight);
|
||||
$container.css('width', this.canvasWidth);
|
||||
};
|
||||
|
||||
/**
|
||||
* drawGrid - draw the letter on the canvas element provided.
|
||||
* @param {number} margin Description.
|
||||
*/
|
||||
FindTheWords.WordGrid.prototype.drawGrid = function (margin) {
|
||||
const that = this;
|
||||
|
||||
const marginResp = (Math.floor(that.elementSize / 8) < margin) ? (Math.floor(that.elementSize / 8)) : margin;
|
||||
const offsetTop = (that.$container.offset().top > that.elementSize * 0.75) ? Math.floor(that.elementSize * 0.75) : that.$container.offset().top;
|
||||
|
||||
this.$gridCanvas = $('<canvas id="grid-canvas" class="canvas-element" height="' + that.canvasHeight + 'px" width="' + that.canvasWidth + 'px" />').appendTo(that.$container);
|
||||
this.$outputCanvas = $('<canvas class="canvas-element" height="' + that.canvasHeight + 'px" width="' + that.canvasWidth + 'px"/>').appendTo(that.$container);
|
||||
this.$drawingCanvas = $('<canvas id="drawing-canvas" class="canvas-element" height="' + that.canvasHeight + 'px" width="' + that.canvasWidth + 'px"/>').appendTo(that.$container);
|
||||
|
||||
const ctx1 = this.$gridCanvas[0].getContext('2d');
|
||||
const offset = that.$container.offset();
|
||||
|
||||
ctx1.clearRect(offset.left, offset.top, that.canvasWidth, that.canvasHeight);
|
||||
ctx1.font = (that.elementSize / 3 ) + 'px sans-serif';
|
||||
|
||||
that.wordGrid.forEach(function (row, index1) {
|
||||
row.forEach(function (element, index2) {
|
||||
ctx1.fillText(element.toUpperCase(), index2 * that.elementSize + 2 * marginResp, index1 * that.elementSize + (offsetTop) );
|
||||
});
|
||||
});
|
||||
|
||||
let clickStart = [];
|
||||
let isDragged = false;
|
||||
let clickMode = false;
|
||||
|
||||
this.$container[0].addEventListener('keydown', function () {
|
||||
//TODO: need to implement for a11y
|
||||
}, false);
|
||||
|
||||
this.$drawingCanvas[0].addEventListener('touchstart', function (event) {
|
||||
touchHandler(event);
|
||||
}, false);
|
||||
|
||||
this.$drawingCanvas[0].addEventListener('touchmove', function (event) {
|
||||
touchHandler(event);
|
||||
}, false);
|
||||
|
||||
this.$drawingCanvas[0].addEventListener('touchend', function (event) {
|
||||
touchHandler(event);
|
||||
}, false);
|
||||
|
||||
this.$drawingCanvas.on('mousedown', function (event) {
|
||||
if (that.options.gridActive) {
|
||||
if (!clickMode) {
|
||||
that.enableDrawing = true;
|
||||
clickStart = mouseDownEventHandler(event, this, that.elementSize);
|
||||
that.trigger('drawStart');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.$drawingCanvas.on('mouseup', function (event) {
|
||||
if (that.enableDrawing) {
|
||||
if (isDragged || clickMode) {
|
||||
if (clickMode) {
|
||||
clickMode = false;
|
||||
}
|
||||
let markedWord = '';
|
||||
const wordObject = mouseUpEventHandler(event, this, that.elementSize, clickStart);
|
||||
const dict = {
|
||||
'horizontal' : [1, 0],
|
||||
'horizontalBack' : [-1, 0],
|
||||
'diagonal' : [1, 1],
|
||||
'diagonalBack' : [-1, 1],
|
||||
'diagonalUp' : [1, -1],
|
||||
'diagonalUpBack' : [-1, -1],
|
||||
'vertical' : [0, 1],
|
||||
'verticalUp' : [0, -1]
|
||||
};
|
||||
|
||||
if (!$.isEmptyObject(wordObject) && wordObject['dir'] !== false) {
|
||||
const dir = wordObject['dir'];
|
||||
let y1 = wordObject['start'][3];
|
||||
let x1 = wordObject['start'][2];
|
||||
let x2 = wordObject['end'][2];
|
||||
const y2 = wordObject['end'][3];
|
||||
|
||||
do {
|
||||
markedWord += that.wordGrid[y1][x1];
|
||||
x1 = x1 + dir[0];
|
||||
y1 = y1 + dir[1];
|
||||
} while (!((y1 === y2) && (x1 === x2)));
|
||||
|
||||
markedWord += that.wordGrid[y2][x2];
|
||||
for (const key in dict) {
|
||||
if (dict[key][0] === dir[0] && dict[key][1] === dir[1]) {
|
||||
wordObject['directionKey'] = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
that.enableDrawing = false;
|
||||
isDragged = false;
|
||||
that.trigger('drawEnd', {'markedWord': markedWord, 'wordObject': wordObject});
|
||||
}
|
||||
else if (!clickMode) {
|
||||
clickMode = true;
|
||||
const offsetTop = (that.$container.offset().top > that.elementSize * 0.75) ? Math.floor(that.elementSize * 0.75) : that.$container.offset().top;
|
||||
const context = that.$drawingCanvas[0].getContext('2d');
|
||||
//drawing the dot on initial click
|
||||
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
|
||||
context.lineWidth = Math.floor(that.elementSize / 2);
|
||||
context.strokeStyle = 'rgba(107,177,125,0.9)';
|
||||
context.fillStyle = 'rgba(107,177,125,0.3)';
|
||||
context.beginPath();
|
||||
context.arc(clickStart[0] - (that.elementSize / 8), clickStart[1] + Math.floor(offsetTop / 8), that.elementSize / 4, 0, 2 * Math.PI);
|
||||
context.fill();
|
||||
context.closePath();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.$drawingCanvas.on('mousemove', function (event) {
|
||||
if (that.enableDrawing ) {
|
||||
isDragged = true;
|
||||
mouseMoveEventHandler(event, this, clickStart, that.elementSize);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return FindTheWords.WordGrid;
|
||||
|
||||
}) (H5P.FindTheWords, H5P.EventDispatcher, H5P.jQuery);
|
||||
@@ -0,0 +1,468 @@
|
||||
H5P.FindTheWords = (function ($, UI) {
|
||||
const ELEMENT_MIN_SIZE = 32; // PX
|
||||
const ELEMENT_MAX_SIZE = 64; // PX
|
||||
const MARGIN = 8; //PX
|
||||
const VOCABULARY_INLINE_WIDTH = 200;// PX
|
||||
|
||||
/**
|
||||
* FindTheWords.
|
||||
* @class H5P.FindTheWords
|
||||
* @extends H5P.EventDispatcher
|
||||
* @param {Object} options
|
||||
* @param {number} id
|
||||
* @param {Object} extras
|
||||
*/
|
||||
function FindTheWords(options, id, extras) {
|
||||
|
||||
/** @alias H5P.FindTheWords# */
|
||||
this.id = id;
|
||||
this.extras = extras;
|
||||
this.numFound = 0;
|
||||
this.isAttempted = false;
|
||||
this.isGameStarted = false;
|
||||
|
||||
// Only take the unique words
|
||||
const vocabulary = options.wordList
|
||||
.split(',')
|
||||
.map(function (word) {
|
||||
return word.trim();
|
||||
})
|
||||
.filter(function (word, pos, self) {
|
||||
return self.indexOf(word) === pos;
|
||||
});
|
||||
|
||||
this.options = $.extend(true, {
|
||||
vocabulary: vocabulary,
|
||||
height: 5,
|
||||
width: 5,
|
||||
fillBlanks: true,
|
||||
maxAttempts: 5,
|
||||
l10n: {
|
||||
wordListHeader: 'Find the words'
|
||||
}
|
||||
}, options);
|
||||
|
||||
H5P.EventDispatcher.call(this);
|
||||
|
||||
this.gridParams = {
|
||||
height: this.options.height,
|
||||
width: this.options.width,
|
||||
orientations: filterOrientations(options.behaviour.orientations),
|
||||
fillBlanks: this.options.fillBlanks,
|
||||
maxAttempts: this.options.maxAttempts,
|
||||
preferOverlap: options.behaviour.preferOverlap,
|
||||
vocabulary: this.options.vocabulary,
|
||||
gridActive: true,
|
||||
fillPool: this.options.behaviour.fillPool
|
||||
};
|
||||
|
||||
this.grid = new FindTheWords.WordGrid(this.gridParams);
|
||||
this.vocabulary = new FindTheWords.Vocabulary(
|
||||
this.options.vocabulary,
|
||||
this.options.behaviour.showVocabulary,
|
||||
this.options.l10n.wordListHeader
|
||||
);
|
||||
this.registerDOMElements();
|
||||
|
||||
// responsive functionality
|
||||
this.on('resize', function () {
|
||||
const currentSize = this.elementSize;
|
||||
const currentVocMod = this.isVocModeBlock;
|
||||
this.calculateElementSize();
|
||||
this.setVocabularyMode();
|
||||
|
||||
if (this.elementSize !== currentSize) {
|
||||
this.$puzzleContainer.empty();
|
||||
this.grid.appendTo(this.$puzzleContainer, this.elementSize );
|
||||
this.grid.drawGrid(MARGIN);
|
||||
|
||||
// If there are already marked elements on the grid mark them
|
||||
if (!this.grid.options.gridActive) {
|
||||
this.grid.enableGrid();
|
||||
this.grid.mark(this.vocabulary.getFound());
|
||||
this.grid.disableGrid();
|
||||
this.grid.mark(this.vocabulary.getSolved());
|
||||
}
|
||||
else {
|
||||
this.grid.mark(this.vocabulary.getFound());
|
||||
}
|
||||
|
||||
this.registerGridEvents();
|
||||
}
|
||||
|
||||
// vocabulary adjustments on resize
|
||||
if (this.options.behaviour.showVocabulary) {
|
||||
if (currentVocMod !== this.isVocModeBlock ) {
|
||||
this.vocabulary.setMode(this.isVocModeBlock);
|
||||
if (this.isVocModeBlock) {
|
||||
this.$puzzleContainer.removeClass('puzzle-inline').addClass('puzzle-block');
|
||||
}
|
||||
else {
|
||||
//initial update has to be done manually
|
||||
this.$playArea.css({'width': parseInt(this.$gameContainer.width()) + VOCABULARY_INLINE_WIDTH});
|
||||
this.$puzzleContainer.removeClass('puzzle-block').addClass('puzzle-inline');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make the playarea just to fit its content
|
||||
if (! this.isVocModeBlock) {
|
||||
this.$playArea.css({'width': parseInt(this.$gameContainer.width()) + 2});
|
||||
}
|
||||
else {
|
||||
this.$playArea.css({'width': parseInt(this.$puzzleContainer.width()) + 2});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FindTheWords.prototype = Object.create(H5P.EventDispatcher.prototype);
|
||||
FindTheWords.prototype.constructor = FindTheWords;
|
||||
|
||||
// private and all prototype function goes there
|
||||
|
||||
/**
|
||||
* filterOrientations - Mapping of directions from semantics to those used by algorithm.
|
||||
* @param {Object} directions
|
||||
* @return {Object[]}
|
||||
*/
|
||||
const filterOrientations = function (directions) {
|
||||
return Object.keys(directions).filter(function (key) {
|
||||
return directions[key];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* registerDOMElements.
|
||||
*/
|
||||
FindTheWords.prototype.registerDOMElements = function () {
|
||||
const that = this;
|
||||
|
||||
this.$playArea = $('<div />', {
|
||||
class: 'h5p-play-area'
|
||||
});
|
||||
|
||||
this.$taskDescription = $('<div />', {
|
||||
class: 'h5p-task-description',
|
||||
html: this.options.taskDescription,
|
||||
tabIndex: 0,
|
||||
});
|
||||
|
||||
// timer part
|
||||
this.$timer = $('<div/>', {
|
||||
class: 'time-status',
|
||||
tabindex: 0,
|
||||
html: '<span role="term" ><em class="fa fa-clock-o" ></em>' +
|
||||
this.options.l10n.timeSpent + '</span >:' +
|
||||
'<span role="definition" class="h5p-time-spent" >0:00</span>'
|
||||
});
|
||||
this.timer = new FindTheWords.Timer(this.$timer.find('.h5p-time-spent'));
|
||||
|
||||
// counter part
|
||||
const counterText = that.options.l10n.found
|
||||
.replace('@found', '<span class="h5p-counter">0</span>')
|
||||
.replace('@totalWords', '<span><strong>' + this.vocabulary.words.length + '</strong></span>');
|
||||
|
||||
this.$counter = $('<div/>', {
|
||||
class: 'counter-status',
|
||||
tabindex: 0,
|
||||
html: '<div role="term"><span role="definition">' + counterText + '</span></div>'
|
||||
});
|
||||
this.counter = new FindTheWords.Counter(this.$counter.find('.h5p-counter'));
|
||||
|
||||
// feedback plus progressBar
|
||||
this.$feedback = $('<div/>', {
|
||||
class: 'feedback-element',
|
||||
tabindex: '0'
|
||||
});
|
||||
this.$progressBar = UI.createScoreBar(this.vocabulary.words.length, 'scoreBarLabel');
|
||||
|
||||
// buttons section
|
||||
that.$submitButton = that.createButton('submit', 'check', that.options.l10n.check, that.gameSubmitted);
|
||||
if (this.options.behaviour.enableShowSolution) {
|
||||
this.$showSolutionButton = this.createButton('solution', 'eye', this.options.l10n.showSolution, that.showSolutions);
|
||||
}
|
||||
if (this.options.behaviour.enableRetry) {
|
||||
this.$retryButton = this.createButton('retry', 'undo', this.options.l10n.tryAgain, that.resetTask);
|
||||
}
|
||||
|
||||
// container elements
|
||||
this.$gameContainer = $('<div class="game-container"/>');
|
||||
this.$puzzleContainer = $('<div class="puzzle-container puzzle-inline" tabIndex="0" role="grid" />');
|
||||
this.$vocabularyContainer = $('<div class="vocabulary-container" tabIndex="0" />');
|
||||
this.$footerContainer = $('<div class="footer-container" />');
|
||||
this.$statusContainer = $('<div />', {
|
||||
class: 'game-status',
|
||||
'aria-label': 'game-status',
|
||||
role: 'group',
|
||||
tabindex: '0'
|
||||
});
|
||||
this.$feedbackContainer = $('<div class="feedback-container"/>');
|
||||
this.$buttonContainer = $('<div class="button-container" />');
|
||||
};
|
||||
|
||||
/**
|
||||
* createButton - creating all buttons used in this game.
|
||||
* @param {string} name Buttonname.
|
||||
* @param {string} icon Fa icon name.
|
||||
* @param {string} param Button text parameter.
|
||||
* @param {function} callback Callback function.
|
||||
* @return {H5P.JoubelUI.Button} Joubel ui button object.
|
||||
*/
|
||||
FindTheWords.prototype.createButton = function (name, icon, param, callback) {
|
||||
const cfunction = callback.bind(this);
|
||||
return UI.createButton({
|
||||
title: name,
|
||||
click: cfunction,
|
||||
html: '<span><i class="fa fa-' + icon + '" aria-hidden="true"></i></span>' + param
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* calculateElementSize - calculate the grid element size according to the container width.
|
||||
*/
|
||||
FindTheWords.prototype.calculateElementSize = function () {
|
||||
const containerWidth = this.$container.width();
|
||||
const gridCol = this.grid.wordGrid[0].length;
|
||||
const gridMaxWidth = gridCol * ELEMENT_MAX_SIZE + 2 * MARGIN;
|
||||
const gridElementStdSize = (containerWidth - 2 * MARGIN) / gridCol;
|
||||
|
||||
if (gridMaxWidth < containerWidth) {
|
||||
this.elementSize = ELEMENT_MAX_SIZE;
|
||||
}
|
||||
else if (gridElementStdSize > ELEMENT_MIN_SIZE) {
|
||||
this.elementSize = gridElementStdSize;
|
||||
}
|
||||
else {
|
||||
this.elementSize = ELEMENT_MAX_SIZE;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* setVocabularyMode - set vocabulary mode (either inline or block).
|
||||
*/
|
||||
FindTheWords.prototype.setVocabularyMode = function () {
|
||||
const gridCol = this.grid.wordGrid[0].length;
|
||||
this.isVocModeBlock = (this.$container.width() - (gridCol * this.elementSize + 2 * MARGIN) > VOCABULARY_INLINE_WIDTH) ? false : true;
|
||||
};
|
||||
|
||||
/**
|
||||
* gameSubmitted - callback function for check button.
|
||||
*/
|
||||
FindTheWords.prototype.gameSubmitted = function () {
|
||||
const totalScore = this.vocabulary.words.length;
|
||||
const scoreText = this.options.l10n.score
|
||||
.replace('@score', this.numFound)
|
||||
.replace('@total', totalScore);
|
||||
|
||||
this.timer.stop();
|
||||
this.$progressBar.setScore(this.numFound);
|
||||
this.$feedback.html(scoreText);
|
||||
this.$submitButton = this.$submitButton.detach();
|
||||
this.grid.disableGrid();
|
||||
|
||||
if (totalScore !== this.numFound) {
|
||||
if (this.options.behaviour.enableShowSolution) {
|
||||
this.$showSolutionButton.appendTo(this.$buttonContainer);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.behaviour.enableRetry) {
|
||||
this.$retryButton.appendTo(this.$buttonContainer);
|
||||
}
|
||||
|
||||
this.$feedbackContainer.addClass('feedback-show'); //show feedbackMessage
|
||||
this.$feedback.focus();
|
||||
|
||||
const xAPIEvent = this.createXAPIEventTemplate('answered');
|
||||
this.addQuestionToXAPI(xAPIEvent);
|
||||
this.addResponseToXAPI(xAPIEvent);
|
||||
this.trigger(xAPIEvent);
|
||||
|
||||
this.trigger('resize');
|
||||
};
|
||||
|
||||
/**
|
||||
* showSolutions - call back function for show solution button.
|
||||
*/
|
||||
FindTheWords.prototype.showSolutions = function () {
|
||||
this.grid.disableGrid();
|
||||
this.grid.mark(this.vocabulary.getNotFound());
|
||||
this.vocabulary.solveWords();
|
||||
this.$showSolutionButton.detach();
|
||||
this.$vocabularyContainer.focus();
|
||||
this.trigger('resize');
|
||||
};
|
||||
|
||||
/**
|
||||
* resetTask - resetting the game.
|
||||
*/
|
||||
FindTheWords.prototype.resetTask = function () {
|
||||
this.numFound = 0;
|
||||
this.timer.reset();
|
||||
this.counter.reset();
|
||||
this.$progressBar.reset();
|
||||
this.$puzzleContainer.empty();
|
||||
this.vocabulary.reset();
|
||||
|
||||
if (this.$showSolutionButton) {
|
||||
this.$showSolutionButton.detach();
|
||||
}
|
||||
|
||||
this.$retryButton.detach();
|
||||
this.$feedbackContainer.removeClass('feedback-show');
|
||||
|
||||
this.grid = new FindTheWords.WordGrid(this.gridParams);
|
||||
this.grid.appendTo(this.$puzzleContainer, this.elementSize);
|
||||
this.grid.drawGrid(MARGIN);
|
||||
this.grid.enableGrid();
|
||||
this.registerGridEvents();
|
||||
|
||||
this.$submitButton.appendTo(this.$buttonContainer);
|
||||
this.$puzzleContainer.focus();
|
||||
|
||||
this.trigger('resize');
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether user is able to play the game.
|
||||
* @return {boolean}
|
||||
*/
|
||||
FindTheWords.prototype.getAnswerGiven = function () {
|
||||
return this.isAttempted;
|
||||
};
|
||||
|
||||
/**
|
||||
* getScore - Return the score obtained.
|
||||
* @return {number}
|
||||
*/
|
||||
FindTheWords.prototype.getScore = function () {
|
||||
return this.numFound;
|
||||
};
|
||||
|
||||
/**
|
||||
* Turn the maximum possible score that can be obtained.
|
||||
* @return {number}
|
||||
*/
|
||||
FindTheWords.prototype.getMaxScore = function () {
|
||||
return this.vocabulary.words.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* getXAPIData - Get xAPI data.
|
||||
* @see contract at {@link https://h5p.org/documentation/developers/contracts#guides-header-6}
|
||||
* @return {Object} xApi data statement
|
||||
*/
|
||||
FindTheWords.prototype.getXAPIData = function () {
|
||||
const xAPIEvent = this.createXAPIEventTemplate('answered');
|
||||
this.addQuestionToXAPI(xAPIEvent);
|
||||
this.addResponseToXAPI(xAPIEvent);
|
||||
return {
|
||||
statement: xAPIEvent.data.statement
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* addQuestionToXAPI - Add the question to the definition part of an xAPIEvent.
|
||||
* @param {H5P.XAPIEvent} xAPIEvent
|
||||
*/
|
||||
FindTheWords.prototype.addQuestionToXAPI = function (xAPIEvent) {
|
||||
const definition = xAPIEvent.getVerifiedStatementValue(
|
||||
['object', 'definition']
|
||||
);
|
||||
definition.description = {
|
||||
'en-US': this.options.taskDescription
|
||||
};
|
||||
definition.type =
|
||||
'http://adlnet.gov/expapi/activities/cmi.interaction';
|
||||
definition.interactionType = 'choice';
|
||||
definition.correctResponsesPattern = [];
|
||||
definition.correctResponsesPattern[0] = this.vocabulary.words.join([',']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the response part to an xAPI event.
|
||||
* @param {H5P.XAPIEvent} xAPIEvent
|
||||
*/
|
||||
FindTheWords.prototype.addResponseToXAPI = function (xAPIEvent) {
|
||||
const maxScore = this.getMaxScore();
|
||||
const score = this.getScore();
|
||||
const success = (score === maxScore);
|
||||
const response = this.vocabulary.getFound().join('[,]');
|
||||
|
||||
xAPIEvent.setScoredResult(score, maxScore, this, true, success);
|
||||
xAPIEvent.data.statement.result.response = response;
|
||||
};
|
||||
|
||||
/**
|
||||
* registerGridEvents.
|
||||
*/
|
||||
FindTheWords.prototype.registerGridEvents = function () {
|
||||
const that = this;
|
||||
|
||||
this.grid.on('drawStart', function () {
|
||||
if (!that.isGameStarted) {
|
||||
that.timer.play();
|
||||
that.triggerXAPI('interacted');
|
||||
that.isGameStarted = true;
|
||||
}
|
||||
});
|
||||
|
||||
this.grid.on('drawEnd', function (event) {
|
||||
that.isAttempted = true;
|
||||
if (that.vocabulary.checkWord(event.data['markedWord'])) {
|
||||
that.numFound++;
|
||||
that.counter.increment();
|
||||
that.grid.markWord(event.data['wordObject']);
|
||||
if (that.numFound === that.vocabulary.words.length) {
|
||||
that.gameSubmitted();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* attach - main attach function.
|
||||
* @param {H5P.jQuery} $container Description.
|
||||
*/
|
||||
FindTheWords.prototype.attach = function ($container) {
|
||||
this.$container = $container.addClass('h5p-find-the-words');
|
||||
this.triggerXAPI('attempted');
|
||||
|
||||
if (this.grid) {
|
||||
this.calculateElementSize();
|
||||
this.grid.appendTo(this.$puzzleContainer, this.elementSize );
|
||||
this.$puzzleContainer.appendTo(this.$gameContainer);
|
||||
if (this.options.behaviour.showVocabulary) {
|
||||
this.setVocabularyMode();
|
||||
this.vocabulary.appendTo(this.$vocabularyContainer, this.isVocModeBlock);
|
||||
this.$vocabularyContainer.appendTo(this.$gameContainer);
|
||||
}
|
||||
}
|
||||
|
||||
this.$timer.appendTo(this.$statusContainer);
|
||||
this.$counter.appendTo(this.$statusContainer);
|
||||
|
||||
this.$feedback.appendTo(this.$feedbackContainer);
|
||||
this.$progressBar.appendTo(this.$feedbackContainer);
|
||||
|
||||
this.$submitButton.appendTo(this.$buttonContainer);
|
||||
|
||||
//append status and feedback and button containers to footer
|
||||
this.$statusContainer.appendTo(this.$footerContainer);
|
||||
this.$feedbackContainer.appendTo(this.$footerContainer);
|
||||
this.$buttonContainer.appendTo(this.$footerContainer);
|
||||
|
||||
//append description , cards and footer to main container.
|
||||
this.$taskDescription.appendTo(this.$playArea);
|
||||
this.$gameContainer.appendTo(this.$playArea);
|
||||
this.$footerContainer.appendTo(this.$playArea);
|
||||
this.$playArea.appendTo(this.$container);
|
||||
|
||||
this.grid.drawGrid(MARGIN);
|
||||
this.registerGridEvents();
|
||||
this.trigger('resize');
|
||||
};
|
||||
|
||||
return FindTheWords;
|
||||
|
||||
}) (H5P.jQuery, H5P.JoubelUI);
|
||||
@@ -0,0 +1,187 @@
|
||||
[
|
||||
{
|
||||
"label": "Task description",
|
||||
"name": "taskDescription",
|
||||
"type": "text",
|
||||
"description": "Description of the Game",
|
||||
"default": "Find the words from the grid",
|
||||
"importance": "high"
|
||||
},
|
||||
{
|
||||
"name": "wordList",
|
||||
"type": "text",
|
||||
"label": "Word list",
|
||||
"description": "Comma Separated list of words. Special Characters, White Spaces and Numbers Not allowed",
|
||||
"default": "one,two,three",
|
||||
"regexp": {
|
||||
"pattern": "^(?!(?:.*[\"!#$%&./:;<=>?@\\[\\]^_`\\{|}~'()\\-*+\\d]|^[,])).*$"
|
||||
},
|
||||
"importance": "high"
|
||||
},
|
||||
{
|
||||
"name": "behaviour",
|
||||
"type": "group",
|
||||
"label": "Behavioural settings",
|
||||
"importance": "low",
|
||||
"description": "These options will let you control how the game behaves.",
|
||||
"optional": true,
|
||||
"fields": [
|
||||
{
|
||||
"name": "orientations",
|
||||
"type": "group",
|
||||
"label": "Orientations",
|
||||
"description": "An array containing the names of the word directions that should be used when creating the puzzle",
|
||||
"fields": [
|
||||
{
|
||||
"name": "horizontal",
|
||||
"type": "boolean",
|
||||
"label": "Horizontal- left to right",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "horizontalBack",
|
||||
"type": "boolean",
|
||||
"label": "Horizontal- right to left",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "vertical",
|
||||
"type": "boolean",
|
||||
"label": "Vertical downwards",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "verticalUp",
|
||||
"type": "boolean",
|
||||
"label": "Vertical upwards",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "diagonal",
|
||||
"type": "boolean",
|
||||
"label": "Diagonal downwards- left to right",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "diagonalBack",
|
||||
"type": "boolean",
|
||||
"label": "Diagonal downwards- right to left",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "diagonalUp",
|
||||
"type": "boolean",
|
||||
"label": "Diagonal upwards- left to right",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "diagonalUpBack",
|
||||
"type": "boolean",
|
||||
"label": "Diagonal upwards- right to left",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fillPool",
|
||||
"type": "text",
|
||||
"label": "Vertical downwards",
|
||||
"description": "pool of letters from which the blanks to be filled",
|
||||
"default": "abcdefghijklmnopqrstuvwxyz",
|
||||
"regexp": {
|
||||
"pattern": "^[^\t\n .<>?;:\"'`!@#$%^&*()\\[\\]{}_+=|\\-]*$"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "preferOverlap",
|
||||
"type": "boolean",
|
||||
"label": "Prefer overlap",
|
||||
"description": "Determines how wordfind decides where to place a word within the puzzle. When true, it randomly selects amongst the positions the highest number of letters that overlap creating a more compact puzzle. When false, it randomly selects amongst all valid positions creating a less compact puzzle.",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "showVocabulary",
|
||||
"type": "boolean",
|
||||
"label": "Show vocabulary",
|
||||
"description": "Determines whether to show vocabularies to the player",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "enableShowSolution",
|
||||
"type": "boolean",
|
||||
"label": "Enable show solution",
|
||||
"description": "Add a show solution button for the game",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"name": "enableRetry",
|
||||
"type": "boolean",
|
||||
"label": "Enable retry",
|
||||
"description": "Add a retry button for the game",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Localization",
|
||||
"importance": "low",
|
||||
"name": "l10n",
|
||||
"type": "group",
|
||||
"common": true,
|
||||
"fields": [
|
||||
{
|
||||
"label": "Text for \"Check\" button",
|
||||
"importance": "low",
|
||||
"name": "check",
|
||||
"type": "text",
|
||||
"default": "Check"
|
||||
},
|
||||
{
|
||||
"label": "Text for \"Retry\" button",
|
||||
"importance": "low",
|
||||
"name": "tryAgain",
|
||||
"type": "text",
|
||||
"default": "Retry"
|
||||
},
|
||||
{
|
||||
"label": "Text for \"Show Solution\" button",
|
||||
"importance": "low",
|
||||
"name": "showSolution",
|
||||
"type": "text",
|
||||
"default": "Show Solution"
|
||||
},
|
||||
{
|
||||
"label": "Counter text",
|
||||
"importance": "low",
|
||||
"name": "found",
|
||||
"type": "text",
|
||||
"default": "@found of @totalWords found",
|
||||
"description": "Feedback text, variables available: @found and @totalWords. Example: '@found of @totalWords found'"
|
||||
},
|
||||
{
|
||||
"label": "Time spent text",
|
||||
"importance": "low",
|
||||
"name": "timeSpent",
|
||||
"type": "text",
|
||||
"default": "Time Spent",
|
||||
"description": "label for showing the time spent while playing the game"
|
||||
},
|
||||
{
|
||||
"label": "Feedback text",
|
||||
"importance": "low",
|
||||
"name": "score",
|
||||
"type": "text",
|
||||
"default": "You got @score of @total points",
|
||||
"description": "Feedback text, variables available: @score and @total. Example: 'You got @score of @total possible points'"
|
||||
},
|
||||
{
|
||||
"label": "Word list header",
|
||||
"importance": "low",
|
||||
"name": "wordListHeader",
|
||||
"type": "text",
|
||||
"default": "Find the words",
|
||||
"maxLength": 20
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,176 @@
|
||||
.h5p-find-the-words {
|
||||
max-width: 68.25em;
|
||||
margin:0 auto;
|
||||
width: 100%;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .h5p-play-area {
|
||||
margin: 0 auto;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.h5p-task-description {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
color: #000000;
|
||||
font-size: 1.125em;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .game-container {
|
||||
position: relative;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .canvas-element {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .puzzle-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .puzzle-inline {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .puzzle-block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-container {
|
||||
background-color: #eeeeee;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-container .vocHeading {
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
padding: 10px 20px 10px 20px;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-container ul {
|
||||
padding-left:8px;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-container li {
|
||||
text-align: left;
|
||||
margin: 4px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-container li em {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-block-container {
|
||||
height: auto !important;
|
||||
width: 100% !important;
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-block-container li {
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-inline-container {
|
||||
width: auto !important;
|
||||
overflow-y: auto;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-container .word-found {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-inline-container .word-found em {
|
||||
color: green;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-container .word-solved {
|
||||
color: #0067de;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-inline-container .word-solved em {
|
||||
color: #0067de;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-container .word {
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .vocabulary-container li i {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .footer-container {
|
||||
border-top: 1px solid #eeeeee;
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .feedback-container {
|
||||
clear: both;
|
||||
display: none;
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .feedback-show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .feedback-show .feedback-element {
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .button-container {
|
||||
clear: both;
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.h5p-find-the-words button .fa {
|
||||
/* Add some marging between FA icon and button text */
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .time-status {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .counter-status {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .feedback-text {
|
||||
display: block;
|
||||
margin-bottom: 0.8em;
|
||||
font-weight: bold;
|
||||
color: rgb(26, 115, 217);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .h5p-found,
|
||||
.h5p-find-the-words .h5p-counter,
|
||||
.h5p-find-the-words .h5p-time-spent {
|
||||
font-weight: bold;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.h5p-find-the-words .time-status span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.h5p-find-the-words em::before {
|
||||
margin-right: 8px;
|
||||
}
|
||||
Reference in New Issue
Block a user