openct-tasks/bebras/2016/2016-FR-07-shapes/task.js

674 lines
20 KiB
JavaScript

function initTask(subTask) {
var state = {};
var rtl = false;
var level;
var answer = null;
var bank = ["diamond", "circle", "triangle", "hexagon", "star"];
var paperWidth = 760;
var paperHeight = null; // calculated
var data = {
easy: {
start: [null, null, null],
rules: [
{
oldPattern: [null],
newPattern: [null, null]
}
],
//target: ["hexagon", "star", "circle", "hexagon"]
target: ["diamond", "triangle", "diamond", "triangle", "diamond", "triangle"]
},
medium: {
start: [null],
rules: [
{
oldPattern: [null],
newPattern: [null, null, null]
},
{
oldPattern: [null],
newPattern: [null, null]
}
],
target: ["circle", "star", "circle", "star", "circle"]
},
hard: {
start: [null, null, null, null],
rules: [
{
oldPattern: [null],
newPattern: [null, null]
},
{
oldPattern: [null],
newPattern: [null, null]
}
],
target: ["triangle", "hexagon", "star", "circle", "circle", "triangle", "hexagon", "star"]
}
};
var paper;
var dragAndDrop;
var visualResults;
var levelTarget;
var ruleMaxX;
var resultsX;
var shapeParams = {
cellDiameter: 45,
shapeSpacing: 35,
shapeDiameter: 30,
bankCenterX: 210,
bankCenterY: 25,
startCenterY: 110,
slotAttr: {
"stroke-width": 2,
fill: "#DDDDDD"
},
shapeAttr: {
circle: {
fill: "red"
},
triangle: {
fill: "green"
},
diamond: {
fill: "yellow"
},
hexagon: {
fill: "cyan"
},
star: {
fill: "purple"
},
ellipsis: {
"stroke-width": 1,
fill: "black",
r: 2
}
}
};
var textParams = {
startY: shapeParams.startCenterY,
ruleX: 0,
ruleYSpacing: 70,
textShapePadX: 9,
textSuffixPadX: 10,
targetY: null, // calculated
targetYPad: 10,
attr: {
"font-size": 16,
"text-anchor": "start"
},
targetAttr: {
"text-anchor": "end",
"font-weight": "bold"
}
};
var separatorParams = {
attr: {
"stroke-width": 2
}
};
var targetRectAttr = {
"stroke-width": 2
};
subTask.loadLevel = function(curLevel) {
level = curLevel;
initPermutation();
if (typeof(enableRtl) != "undefined") {
rtl = true;
}
};
subTask.getStateObject = function() {
return state;
};
subTask.reloadAnswerObject = function(answerObj) {
answer = answerObj;
if(!validateFormat()) {
answer = subTask.getDefaultAnswerObject();
}
};
subTask.resetDisplay = function() {
initPaper();
fillFromAnswer();
refreshResults();
};
subTask.getAnswerObject = function() {
return answer;
};
subTask.getDefaultAnswerObject = function() {
return {
start: $.extend(true, [], data[level].start),
rules: $.extend(true, [], data[level].rules)
};
};
subTask.unloadLevel = function(callback) {
if(dragAndDrop) {
dragAndDrop.disable();
}
callback();
};
var initPermutation = function() {
var randomGenerator = new RandomGenerator(subTask.taskParams.randomSeed);
var permutation = $.extend([], bank);
// No need to safeShuffle - the original order is arbitrary and doesn't hint at anything.
randomGenerator.shuffle(permutation);
var permutationObject = {};
for(var index = 0; index < bank.length; index++) {
permutationObject[bank[index]] = permutation[index];
}
levelTarget = $.map(data[level].target, function(shape) {
return permutationObject[shape];
});
};
var initPaper = function() {
textParams.targetY = textParams.startY + textParams.ruleYSpacing * (data[level].rules.length + 1);
paperHeight = textParams.targetY + shapeParams.shapeDiameter;
paper = subTask.raphaelFactory.create("anim", "anim", paperWidth, paperHeight);
// This fixes a bug that causes Raphael's element.getBBox() method to return wrong values for texts in IE6.
// Taken from here: https://github.com/DmitryBaranovskiy/raphael/issues/410#issuecomment-17374032
if (Raphael.vml) {
var OriginalFunc = Raphael._engine.create;
Raphael._engine.create = function() {
res = OriginalFunc.apply(Raphael, arguments);
res.span.style.cssText += ";white-space:nowrap;";
return res;
};
}
dragAndDrop = DragAndDropSystem({
paper: paper,
actionIfDropped: actionIfDropped,
drop: onDrop
});
initStart();
for(var iRule = 0; iRule < data[level].rules.length; iRule++) {
initRule(iRule);
}
initResults();
initTarget();
initBank();
initSeparators();
};
var initSeparators = function() {
paper.path(["M", 0, shapeParams.startCenterY - 40, "H", paperWidth]).attr(separatorParams.attr);
paper.path(["M", 0, textParams.targetY - 30, "H", paperWidth]).attr(separatorParams.attr);
};
var initStart = function() {
var textX = textParams.ruleX;
if (rtl) {
textX = paper.width - textX;
}
var start = paper.text(textX, textParams.startY, taskStrings.start).attr(textParams.attr);
var leftX = textParams.ruleX + start.getBBox().width + textParams.textShapePadX;
var array = data[level].start;
var arrayWidth = array.length * shapeParams.cellDiameter;
var centerX = leftX + arrayWidth / 2;
if (rtl) {
centerX = paper.width-centerX;
}
createShapeArray(array, centerX, shapeParams.startCenterY, "start");
ruleMaxX = leftX + arrayWidth + textParams.textSuffixPadX;
};
var initBank = function() {
var firstCenterX = shapeParams.bankCenterX - (bank.length * (shapeParams.cellDiameter)) / 2 + (shapeParams.cellDiameter) / 2;
for(var iShape = 0; iShape < bank.length; iShape++) {
dragAndDrop.addContainer({
ident: "bank_" + bank[iShape],
type: "source",
cx: firstCenterX + shapeParams.cellDiameter * iShape * 1.5,
cy: shapeParams.bankCenterY,
dropMode: "replace",
dragDisplayMode: "preview",
nbPlaces: 1,
widthPlace: shapeParams.cellDiameter,
heightPlace: shapeParams.cellDiameter,
placeBackgroundArray: [],
sourceElemArray: [drawShape(bank[iShape])]
});
}
};
var initRule = function(iRule) {
// Prefix.
var leftX = textParams.ruleX;
var centerY = textParams.startY + textParams.ruleYSpacing * (iRule + 1);
var textX = leftX;
if (rtl) {
textX = paper.width - textX;
}
var prefix = paper.text(textX, centerY, taskStrings.rulePrefix(iRule)).attr(textParams.attr);
leftX += prefix.getBBox().width + textParams.textShapePadX;
// Old pattern.
var array = data[level].rules[iRule].oldPattern;
var arrayWidth = array.length * shapeParams.cellDiameter;
var centerX = leftX + arrayWidth / 2;
if (rtl) {
centerX = paper.width - centerX;
}
createShapeArray(array, centerX, centerY, "rule_" + iRule + "_old");
leftX += arrayWidth + textParams.textShapePadX;
// Infix.
textX = leftX;
if (rtl) {
textX = paper.width - leftX;
}
var infix = paper.text(textX, centerY, taskStrings.ruleInfix).attr(textParams.attr);
leftX += infix.getBBox().width + textParams.textShapePadX;
// New pattern.
array = data[level].rules[iRule].newPattern;
arrayWidth = array.length * shapeParams.cellDiameter;
centerX = leftX + arrayWidth / 2;
var shapesCenterX = centerX;
if (rtl) {
shapesCenterX = paper.width - centerX;
}
createShapeArray(array, shapesCenterX, centerY, "rule_" + iRule + "_new");
ruleMaxX = Math.max(ruleMaxX, centerX + arrayWidth / 2 + textParams.textSuffixPadX);
};
var initResults = function() {
var textObject;
var textX = ruleMaxX;
if (rtl) {
textX = paper.width - ruleMaxX;
}
for(var row = 0; row < data[level].rules.length + 1; row++) {
textObject = paper.text(textX, textParams.startY + row * textParams.ruleYSpacing, taskStrings.ruleSuffix).attr(textParams.attr);
}
resultsX = ruleMaxX + textObject.getBBox().width + textParams.textShapePadX;
};
var initTarget = function() {
var textX = resultsX - textParams.textShapePadX;
if (rtl) {
textX = paper.width - textX;
}
paper.text(textX, textParams.targetY, taskStrings.target).attr(textParams.attr).attr(textParams.targetAttr);
var targetCenterX = resultsX + (levelTarget.length * shapeParams.shapeSpacing / 2);
if (rtl) {
targetCenterX = paper.width - targetCenterX;
}
createShapeArray(levelTarget, targetCenterX, textParams.targetY, "target");
};
var createShapeArray = function(array, centerX, centerY, iden) {
var leftX;
if(array[0] === null) {
leftX = centerX - (array.length * shapeParams.cellDiameter) / 2;
var elements = Beav.Array.init(array.length, drawSlot);
var positions = Beav.Array.init(array.length, function(index) {
return [
leftX + shapeParams.cellDiameter / 2 + (shapeParams.cellDiameter * index),
centerY
];
});
dragAndDrop.addContainer({
cx: centerX,
cy: centerY,
ident: iden,
// type: "list",
dropMode : 'replace',
dragDisplayMode: "preview",
nbPlaces: elements.length,
widthPlace: shapeParams.cellDiameter,
heightPlace: shapeParams.cellDiameter,
placeBackgroundArray: elements
// places: positions
});
}
else {
leftX = centerX - (array.length * shapeParams.shapeSpacing) / 2;
for(var iShape = 0; iShape < array.length; iShape++) {
var shapeSet = drawShape(array[iShape]);
shapeSet.transform(["T", leftX + shapeParams.shapeSpacing / 2 + iShape * shapeParams.shapeSpacing, centerY]);
}
}
};
var drawSlot = function() {
return paper.rect(- shapeParams.cellDiameter / 2, - shapeParams.cellDiameter / 2, shapeParams.cellDiameter, shapeParams.cellDiameter).attr(shapeParams.slotAttr);
};
var drawShape = function(shape) {
var set = paper.set();
set.push(drawSlot().attr({
fill: "green",
opacity: 0
}));
var radius = shapeParams.shapeDiameter / 2;
var element;
if(shape == "circle") {
element = paper.circle(0, 0, radius);
}
else if(shape == "triangle") {
element = paper.path(["M", -radius, radius,
"L", radius, radius,
"L", 0, -radius,
"Z"]);
}
else if(shape == "diamond") {
element = paper.path(["M", 0, -radius,
"L", radius, 0,
"L", 0, radius,
"L", -radius, 0,
"Z"]);
}
else if(shape == "hexagon") {
element = paper.path(["M", 0, -radius,
"L", -radius, -radius / 2,
"L", -radius, radius / 2,
"L", 0, radius,
"L", radius, radius / 2,
"L", radius, -radius / 2,
"Z"]);
}
else if(shape == "star") {
element = paper.path(["M", 0, -radius,
"L", 0.27 * radius, -0.3 * radius,
"L", radius, -0.3 * radius,
"L", 0.4 * radius, 0.2 * radius,
"L", 0.6 * radius, 0.8 * radius,
"L", 0, 0.4 * radius,
"L", - 0.6 * radius, 0.8 * radius,
"L", - 0.4 * radius, 0.2 * radius,
"L", - radius, -0.3 * radius,
"L", - 0.27 * radius, -0.3 * radius,
"Z"]);
}
else if(shape == "ellipsis") {
element = paper.set();
element.push(paper.circle(0, 0));
element.push(paper.circle(-8, 0));
element.push(paper.circle(8, 0));
}
if(shapeParams.shapeAttr[shape]) {
element.attr(shapeParams.shapeAttr[shape]);
}
set.push(element);
return set;
};
var actionIfDropped = function(srcCont, srcPos, dstCont, dstPos, dropType) {
if(dstCont == null) {
return true;
}
if(dstCont.substring(0, 4) == "bank") {
return false;
}
return true;
};
var convertToAnswerFormat = function(array) {
return Beav.Array.init(array.length, function(index) {
if(array[index] === null) {
return null;
}
return array[index].replace("bank_", "");
});
};
var convertFromAnswerFormat = function(array) {
return Beav.Array.init(array.length, function(index) {
if(array[index] === null) {
return null;
}
return "bank_" + array[index];
});
};
var onDrop = function() {
if(data[level].start[0] === null) {
answer.start = convertToAnswerFormat(dragAndDrop.getObjects("start"));
}
for(var iRule = 0; iRule < answer.rules.length; iRule++) {
if(data[level].rules[iRule].oldPattern[0] === null) {
answer.rules[iRule].oldPattern = convertToAnswerFormat(dragAndDrop.getObjects("rule_" + iRule + "_old"));
}
if(data[level].rules[iRule].newPattern[0] === null) {
answer.rules[iRule].newPattern = convertToAnswerFormat(dragAndDrop.getObjects("rule_" + iRule + "_new"));
}
}
refreshResults();
};
var refreshResults = function() {
removeVisualResults();
visualResults = [];
var allResults = getGradualResults();
for(var iResult = 0; iResult < allResults.length; iResult++) {
var centerY;
if(iResult === 0) {
centerY = shapeParams.startCenterY;
}
else {
centerY = textParams.startY + textParams.ruleYSpacing * (iResult);
}
var pattern = allResults[iResult];
for(var iShape = 0, offScreen = false; iShape < pattern.length && !offScreen; iShape++) {
var centerX;
if (!rtl) {
centerX = resultsX + (iShape + 0.5) * shapeParams.shapeSpacing;
} else {
centerX = (paper.width-resultsX) - (pattern.length-iShape - 0.5) * shapeParams.shapeSpacing;
}
var shapeSet;
// If the right edge of this shape is off screen, or if there is another shape
// and its right edge is going to be off screen, draw ellipsis instead.
if((centerX + shapeParams.shapeSpacing / 2 >= paperWidth) || (iShape < pattern.length - 1 && centerX + shapeParams.shapeSpacing * 1.5 >= paperWidth)) {
shapeSet = drawShape("ellipsis");
offScreen = true;
}
else {
shapeSet = drawShape(pattern[iShape]);
}
shapeSet.transform(["T", centerX, centerY]);
visualResults.push(shapeSet);
}
}
};
var removeVisualResults = function() {
if(!visualResults || visualResults.length === 0) {
return;
}
while(visualResults.length > 0) {
visualResults.pop().remove();
}
};
var fillContainer = function(iden, array) {
var dragFormatArray = convertFromAnswerFormat(array);
for(var iPos = 0; iPos < array.length; iPos++) {
if(array[iPos] !== null) {
dragAndDrop.insertObject(iden, iPos, {
ident: dragFormatArray[iPos],
elements: drawShape(array[iPos])
});
}
}
};
var fillFromAnswer = function() {
if(data[level].start[0] === null) {
fillContainer("start", answer.start);
}
for(var iRule = 0; iRule < data[level].rules.length; iRule++) {
var rule = data[level].rules[iRule];
if(rule.oldPattern[0] === null) {
fillContainer("rule_" + iRule + "_old", answer.rules[iRule].oldPattern);
}
if(rule.newPattern[0] === null) {
fillContainer("rule_" + iRule + "_new", answer.rules[iRule].newPattern);
}
}
};
var validateFormat = function() {
if(!answer || !answer.start || !answer.rules || !answer.rules.length) {
return false;
}
if(answer.start.length !== data[level].start.length || answer.rules.length !== data[level].rules.length) {
return false;
}
for(var iRule in data[level].rules) {
if(!(answer.rules[iRule].oldPattern) || !(answer.rules[iRule].newPattern)) {
return false;
}
if(answer.rules[iRule].oldPattern.length !== data[level].rules[iRule].oldPattern.length) {
return false;
}
if(answer.rules[iRule].newPattern.length !== data[level].rules[iRule].newPattern.length) {
return false;
}
}
return true;
};
var getGradualResults = function() {
var allResults = [];
var isOccurrence = function(array, index, subarray) {
if(index + subarray.length > array.length) {
return false;
}
for(var checkIndex = index; checkIndex < index + subarray.length; checkIndex++) {
if(array[checkIndex] !== subarray[checkIndex - index]) {
return false;
}
}
return true;
};
var applyRule = function(array, rule) {
var oldPattern = rule.oldPattern;
var newPattern = rule.newPattern;
if(array.length === 0 || hasNull(oldPattern) || hasNull(newPattern)) {
return [];
}
var newArray = [];
var index = 0;
while(index < array.length) {
if(isOccurrence(array, index, oldPattern)) {
newArray = newArray.concat(newPattern);
index += oldPattern.length;
}
else {
newArray.push(array[index]);
index++;
}
}
return newArray;
};
if(hasNull(answer.start)) {
return Beav.Array.init(answer.rules.length + 1, function() {
return [];
});
}
var pattern = $.extend(true, [], answer.start);
allResults.push(pattern);
for(var iRule = 0; iRule < answer.rules.length; iRule++) {
var rule = answer.rules[iRule];
pattern = applyRule(pattern, rule);
allResults.push(pattern);
}
return allResults;
};
var hasNull = function(array) {
for(var index = 0; index < array.length; index++) {
if(array[index] === null) {
return true;
}
}
return false;
};
var checkAnswer = function() {
var allResults = getGradualResults();
var pattern = allResults[allResults.length - 1];
if(pattern.length > levelTarget.length) {
return false;
}
while(pattern.length < levelTarget.length) {
pattern.push(null);
}
for(var iTarget = 0; iTarget < levelTarget.length; iTarget++) {
if(pattern[iTarget] !== levelTarget[iTarget]) {
return false;
}
}
return true;
};
var getResultAndMessage = function() {
if(!validateFormat()) {
return {
successRate: 0,
message: taskStrings.missing
};
}
if(!checkAnswer()) {
return {
successRate: 0,
message: taskStrings.wrong
};
}
return {
successRate: 1,
message: taskStrings.success
};
};
subTask.getGrade = function(callback) {
callback(getResultAndMessage());
};
}
initWrapper(initTask, ["easy", "medium", "hard"]);