openct-tasks/algorea/algorea_2016/2016-FR-21-snowflake/task.js

679 lines
24 KiB
JavaScript

function initTask() {
var instructions;
var instructionsLevel = {
easy: [taskStrings.cmdForward, taskStrings.cmdBackwards, taskStrings.cmdRight, taskStrings.cmdLeft],
medium: [taskStrings.cmdForward, taskStrings.cmdBackwards, taskStrings.cmdRight, taskStrings.cmdLeft, taskStrings.cmdForward, taskStrings.cmdBackwards, taskStrings.cmdRight, taskStrings.cmdF1],
hard: [taskStrings.cmdForward, taskStrings.cmdBackwards, taskStrings.cmdRight, taskStrings.cmdLeft, taskStrings.cmdF1, taskStrings.cmdF2]
};
var instructionId;
var instructionIdLevel = {
easy: { avance: 0, recule: 1, droite90: 2, gauche90: 3, f1: 4 },
medium: { avance: 0, recule: 1, droite90: 2, gauche90: 3, f1: 4 },
hard: { avance: 0, recule: 1, droite60: 2, gauche60: 3, f1: 4, f2: 5 }
};
var nbFonctions;
var nbFonctionsLevel = {
easy: 0,
medium: 1,
hard: 2
};
var examples = [{
easy: {main: [1, 0, 0, 1, 2] },
medium: {main: [4, 1, 0], f1: [0, 0, 2, 0, 1] },
hard: {main: [2, 0, 0, 1, 3, 0], f1: [], f2: [] }
},
{
easy: {main: [1, 0, 2]},
medium: {main: [4, 2, 4, 2, 4], f1: [0, 1]},
hard: {main: [2, 4, 4], f1: [5, 2, 2], f2: [0, 0, 1, 1] }
}];
// {u0, u1, u2, u3}
// avec x = u0 + u2*1/2 + u1*sqrt(3)/2
// avec y = u3 + u1*1/2 + u2*sqrt(3)/2
var allPaths = {
easy: [
[[0,0,0,0], [1,0,0,0]],
[[0,0,0,0], [-1,0,0,0]],
[[0,0,0,0], [0,0,0,1]],
[[0,0,0,0], [0,0,0,-1]]
],
medium: [
[[0,0,0,0], [1,0,0,0]],
[[3,0,0,0], [2,0,0,0]],
[[2,0,0,0], [1,0,0,0]],
[[2,0,0,0], [2,0,0,1]],
[[2,0,0,0], [2,0,0,-1]],
[[0,0,0,0], [-1,0,0,0]],
[[-3,0,0,0], [-2,0,0,0]],
[[-2,0,0,0], [-1,0,0,0]],
[[-2,0,0,0], [-2,0,0,1]],
[[-2,0,0,0], [-2,0,0,-1]],
[[0,0,0,0], [0,0,0,1]],
[[0,0,0,3], [0,0,0,2]],
[[0,0,0,2], [0,0,0,1]],
[[0,0,0,2], [1,0,0,2]],
[[0,0,0,2], [-1,0,0,2]],
[[0,0,0,0], [0,0,0,-1]],
[[0,0,0,-3], [0,0,0,-2]],
[[0,0,0,-2], [0,0,0,-1]],
[[0,0,0,-2], [1,0,0,-2]],
[[0,0,0,-2], [-1,0,0,-2]]
],
hard: [ // Generated by running the solution and displaying the coordinates
[[0,0,0,0],[0,0,1,0]],
[[0,0,1,0],[1,0,1,0]],
[[1,0,1,0],[2,0,1,0]],
[[1,0,1,0],[2,0,0,0]],
[[0,0,1,0],[0,0,2,0]],
[[0,0,2,0],[0,0,3,0]],
[[0,0,2,0],[1,0,2,0]],
[[0,0,1,0],[-1,0,2,0]],
[[-1,0,2,0],[-2,0,3,0]],
[[-1,0,2,0],[-1,0,3,0]],
[[0,0,0,0],[-1,0,0,0]],
[[-1,0,0,0],[-2,0,1,0]],
[[-2,0,1,0],[-3,0,2,0]],
[[-2,0,1,0],[-2,0,2,0]],
[[-1,0,0,0],[-2,0,0,0]],
[[-2,0,0,0],[-3,0,0,0]],
[[-2,0,0,0],[-3,0,1,0]],
[[-1,0,0,0],[-1,0,-1,0]],
[[-1,0,-1,0],[-1,0,-2,0]],
[[-1,0,-1,0],[-2,0,-1,0]],
[[0,0,0,0],[1,0,-1,0]],
[[1,0,-1,0],[1,0,-2,0]],
[[1,0,-2,0],[1,0,-3,0]],
[[1,0,-2,0],[0,0,-2,0]],
[[1,0,-1,0],[2,0,-2,0]],
[[2,0,-2,0],[3,0,-3,0]],
[[2,0,-2,0],[2,0,-3,0]],
[[1,0,-1,0],[2,0,-1,0]],
[[2,0,-1,0],[3,0,-1,0]],
[[2,0,-1,0],[3,0,-2,0]]
]};
var path;
var dragAndDrop;
var instructionDefs;
var widthProgram = 750;
var heightProgram = {easy: 110, medium: 160, hard: 210};
var widthDrawing = {easy: 500, medium: 500, hard: 500 };
var heightDrawing = { easy: 150, medium: 300, hard: 300 };
var stepDist = 45;
var widthLabel = 40;
var heightLabel = 40;
var maxSequenceLength = 15;
var simulationSpeeds = { easy: 200, medium: 200, hard: 200 };
var simulationSpeed;
var paper;
var paperDrawing;
var drawing;
var robotSize = 20;
var level = null;
var simuStates = {
initial: 0,
animating: 1,
stopped: 2
};
var simuState = simuStates.initial;
var answer = null;
var getCoordinateX = function(t) {
if (t == null) t = [0, 0, 0, 0];
return parseInt((t[0] + t[2]/2 + Math.sqrt(3)*t[1]/2) * stepDist + widthDrawing[level]/2, 10);
};
var getCoordinateY = function(t) {
if (t == null) t = [0, 0, 0, 0];
return parseInt((t[3] + t[1]/2 + Math.sqrt(3)*t[2]/2) * stepDist + heightDrawing[level]/2, 10);
};
var compare = function(a, b) {
for (var i=0; i<2; i++)
for (var j=0; j<4; j++)
if (a[i][j] != b[i][j])
return a[i][j] - b[i][j];
return 0;
};
var dichotomie = function(e, t, inf, sup) {
var med = Math.floor((inf + sup) / 2);
if (inf+1 == sup) {
if (compare(e, t[med]) == 0)
return inf;
else
return -1;
}
if (compare(e, t[med]) < 0)
return dichotomie(e, t, inf, med);
else
return dichotomie(e, t, med, sup);
};
task.load = function(views, callback) {
answer = task.getDefaultAnswerObject();
displayHelper.hideValidateButton = true;
displayHelper.timeoutMinutes = 10;
displayHelper.setupLevels();
callback();
};
var getInstructionObject = function(iInstr) {
var label = paper.rect(-widthLabel/2, -heightLabel/2, widthLabel, heightLabel, heightLabel/5)
.attr({'fill': '#E0E0F8'});
var text = paper.text(0, 0, instructions[iInstr]).attr({"font-size": 16, "font-weight": "bold"});
//var text = paper.image(instructions[iInstr], -widthLabel/2, -heightLabel/2, widthLabel, heightLabel);
return [label, text];
};
var buildProgram = function() {
if (paper != null) {
paper.remove();
}
paper = Raphael("program", widthProgram, heightProgram[level]);
dragAndDrop = DragAndDropSystem({
paper : paper,
drop: function(srcCont, srcPos, dstCont, dstPos, dropType) {
if (simuState != simuStates.initial) {
stopExecution();
}
},
actionIfDropped: function(srcCont, srcPos, dstCont, dstPos, dropType) {
var dropOk = dstCont == 'main';
for (var iFonct=1; iFonct<=nbFonctions; iFonct++)
dropOk = dropOk || dstCont == ('f' + iFonct);
if (dropOk) {
var oldSequence = dragAndDrop.getObjects(dstCont);
var maxiPos = 0;
for (var i = 0; i < oldSequence.length; i++) {
if (oldSequence[i] != null)
maxiPos = i+1;
}
if (srcCont == dstCont)
maxiPos--;
if (dstPos <= maxiPos)
return true;
if (maxiPos < maxSequenceLength)
return DragAndDropSystem.action(dstCont, maxiPos, 'insert');
} else {
return dstCont == null;
}
}
});
// Function main
dragAndDrop.addContainer({
ident : 'main',
cx : 140 + maxSequenceLength * widthLabel / 2,
cy : 20 + 3 * heightLabel / 2,
widthPlace : widthLabel,
heightPlace : heightLabel,
nbPlaces : maxSequenceLength,
dropMode : 'insertBefore',
dragDisplayMode : 'preview',
direction : 'horizontal',
align : 'left',
placeBackgroundArray : [paper.rect(-widthLabel/2,-heightLabel/2,widthLabel,heightLabel).attr('fill', '#F2F2FF')]
});
paper.text(65, 20 + 3 * heightLabel / 2, taskStrings.programLabel).attr({"font-size": "20px", "font-weight": "bold"});
for (var iFonct=1; iFonct<=nbFonctions; iFonct++) {
dragAndDrop.addContainer({
ident : 'f' + iFonct,
cx : 140 + maxSequenceLength * widthLabel / 2,
cy : 20 + 10 * iFonct + (3 + 2*iFonct) * heightLabel / 2,
widthPlace : widthLabel,
heightPlace : heightLabel,
nbPlaces : maxSequenceLength,
dropMode : 'insertBefore',
dragDisplayMode : 'preview',
direction : 'horizontal',
align : 'left',
placeBackgroundArray : [paper.rect(-widthLabel/2,-heightLabel/2,widthLabel,heightLabel).attr('fill', '#F2F2FF')]
});
paper.text(67, 20 + 10 * iFonct + (3 + 2*iFonct) * heightLabel / 2, taskStrings.functionLabel("F" + iFonct)).attr({"font-size": "20px", "font-weight": "bold"});
}
// Sources (Drag&Drop)
instructionDefs = [];
for (var iInstr = 0; iInstr < instructions.length; iInstr++) {
instructionDefs[iInstr] = dragAndDrop.addContainer({
ident : iInstr,
cx : 210 + widthLabel / 2 + iInstr * widthLabel,
cy : 10 + widthLabel / 2,
widthPlace : widthLabel,
heightPlace : heightLabel,
type : 'source',
dropMode : 'insertBefore',
sourceElemArray : getInstructionObject(iInstr)
});
}
paper.text(110, 10 + widthLabel / 2, taskStrings.instructionsLabel).attr({"font-size": "20px", "font-weight": "bold"});
return paper;
};
var buildDrawing = function(name) {
if (paperDrawing != null) {
paperDrawing.remove();
}
paperDrawing = Raphael(name, widthDrawing[level], heightDrawing[level]);
return { paper: paperDrawing };
};
var clearDrawing = function() {
stopExecution();
simuState = simuStates.initial;
$("#resetExecution").attr("disabled", true);
$(".examples td").css("background-color", "");
$("#completedMessage").html("");
var pathString = "";
for (var i = 0; i < path.length; i++) {
var segment = path[i];
pathString += "M" + getCoordinateX(segment[0]) + "," + getCoordinateY(segment[0]);
pathString += "L" + getCoordinateX(segment[1]) + "," + getCoordinateY(segment[1]);
}
drawing.paper.clear();
drawing.paper.path(pathString).attr("stroke", "gray");
for (var i = 0; i < path.length; i++) {
var segment = path[i];
for (var side = 0; side < 2; side++) {
drawing.paper.circle(getCoordinateX(segment[side]), getCoordinateY(segment[side]), 2).attr({"stroke": "black", "fill": "black"});
}
}
drawing.robot = drawing.paper.image("robot.png", getCoordinateX(null) - robotSize/2,
getCoordinateY(null) - robotSize/2,
robotSize, robotSize);
};
task.unload = function(callback) {
DelayedExec.stopAll();
callback();
};
var refreshAnswer = function() {
var sequence = answer[level];
for (var l in sequence) {
while ((sequence[l].length > 0) && (sequence[l][sequence[l].length - 1] == null)) {
sequence[l].pop();
}
dragAndDrop.removeAllObjects(l);
dragAndDrop.insertObjects(l, 0, $.map(sequence[l], function(iInstr) {
return { ident: iInstr, elements: getInstructionObject(iInstr) };
}));
}
};
task.getDefaultStateObject = function() {
return { level: "easy" };
};
task.reloadStateObject = function(stateObj, display) {
state = stateObj;
level = state.level;
instructions = instructionsLevel[level];
instructionId = instructionIdLevel[level];
nbFonctions = nbFonctionsLevel[level];
path = allPaths[level];
path.sort(compare);
if (display) {
DelayedExec.stopAll();
if (paper != null) {
paper.remove();
paperDrawing.remove();
}
simulationSpeed = simulationSpeeds[level];
drawing = buildDrawing("drawing");
displayExamples();
buildProgram();
}
};
task.getStateObject = function() {
state.level = level;
return state;
};
task.reloadAnswerObject = function(answerObj) {
clearDrawing();
answer = answerObj;
refreshAnswer();
};
task.getAnswerObject = function() {
answer[level] = { 'main': dragAndDrop.getObjects('main') };
for (var iFonct=1; iFonct<=nbFonctions; iFonct++)
answer[level]['f' + iFonct] = dragAndDrop.getObjects('f' + iFonct);
return answer;
};
task.getDefaultAnswerObject = function() { // Todo, automatiser ici (nb variable de fonctions) ?
return {
'easy': { 'main': [] },
'medium': { 'main': [], 'f1': [] },
'hard': { 'main': [], 'f1': [], 'f2': [], 'f3': [] }
};
};
var displayExamples = function() {
for (var iExample = 0; iExample < examples.length; iExample++) {
var buttonText = "";
var example = examples[iExample][level];
var description = {
main: taskStrings.programLabel,
f1: taskStrings.functionLabel("F1"),
f2: taskStrings.functionLabel("F2"),
f3: taskStrings.functionLabel("F3") };
for (var idFonct in example) {
if (example[idFonct].length > 0) {
buttonText += "<table class='exampleProgramRow'><tr><td width='112px'><b>" + description[idFonct] + "</b>&nbsp;</td><td>";
buttonText += "<table class='exampleProgram' cellspacing=0><tr>";
for (var i = 0; i < example[idFonct].length; i++) {
buttonText += "<td>" + instructions[example[idFonct][i]] + "</td>";
}
buttonText += "</tr></table>";
buttonText += "</td></tr></table>";
}
}
$("#example" + iExample).html(buttonText);
}
};
var stopExecution = function() {
DelayedExec.stopAll();
simuState = simuStates.stopped;
$("#tryExecution").attr('value', taskStrings.tryLabel);
};
var simulateStep = function(simulation, display) {
if (simulation.stack.length == 0) {
simulation.completed = true;
return;
} else {
var act = simulation.stack.pop();
if (act.id < simulation.seq[act.name].length) {
simulation.stack.push({name: act.name, id: act.id+1});
var rotation = [[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
[-1, 0, 1, 0],
[0, -1, 0, 1],
[-1, 0, 0, 0],
[0, -1, 0, 0],
[0, 0, -1, 0],
[0, 0, 0, -1],
[1, 0, -1, 0],
[0, 1, 0, -1]];
var decalage = 0;
switch (simulation.seq[act.name][act.id]) {
case instructionId.f1:
simulation.stack.push({name: "f1", id: 0});
break;
case instructionId.f2:
simulation.stack.push({name: "f2", id: 0});
break;
case instructionId.f3:
simulation.stack.push({name: "f3", id: 0});
break;
case instructionId.droite90:
if (display) {
drawing.robot.transform("...r90");
}
decalage = 3;
break;
case instructionId.gauche90:
if (display) {
drawing.robot.transform("...r-90");
}
decalage = 12 - 3;
break;
case instructionId.droite60:
if (display) {
drawing.robot.transform("...r60");
}
decalage = 2;
break;
case instructionId.gauche60:
if (display) {
drawing.robot.transform("...r-60");
}
decalage = 12 - 2;
break;
case instructionId.avance:
case instructionId.recule:
var reculer = simulation.seq[act.name][act.id] == instructionId.recule;
var oldPosition = simulation.position.slice(0);
for (var i=0; i<4; i++) {
if (reculer)
simulation.position[i] -= simulation.direction[i];
else
simulation.position[i] += simulation.direction[i];
}
var actPosition = simulation.position.slice(0);
var indice = Math.max(dichotomie([oldPosition, actPosition], path, 0, path.length),
dichotomie([actPosition, oldPosition], path, 0, path.length));
if (display) {
var pathString = "M" + getCoordinateX(oldPosition) + "," + getCoordinateY(oldPosition)
+ "L" + getCoordinateX(actPosition) + "," + getCoordinateY(actPosition);
drawing.paper.path(pathString).attr("stroke", "blue").attr("stroke-opacity", 1).attr("stroke-width", 4);
if (reculer)
drawing.robot.transform("...t-" + stepDist + "," + 0);
else
drawing.robot.transform("...t" + stepDist + "," + 0);
drawing.robot.toFront();
}
if (indice == -1) {
throw taskStrings.outOfZone;
} else {
if (!simulation.segmentSeen[indice])
//console.log(JSON.stringify([oldPosition, actPosition]) + ",");
simulation.segmentSeenNb++;
simulation.segmentSeen[indice] = true;
if (simulation.segmentSeenNb == path.length) {
simulation.success = true;
}
}
break;
}
for (var i=0; i<12; i++) {
if (simulation.direction[0] == rotation[i][0]
&& simulation.direction[1] == rotation[i][1]
&& simulation.direction[2] == rotation[i][2]
&& simulation.direction[3] == rotation[i][3]) {
simulation.direction = rotation[(i + decalage) % 12];
break;
}
}
}
}
};
var getSimulation = function(curAnswer) {
var curSimulation = {
stack: [],
direction: [1, 0, 0, 0],
position: [0, 0, 0, 0],
seq: {},
pathStatus: [],
message: "",
success: false,
fail: false,
segmentSeen: new Array(path.length),
segmentSeenNb: 0
};
for (var iSeg = 0; iSeg < path.length; iSeg++) {
curSimulation.segmentSeen[iSeg] = false;
}
curSimulation.seq.main = $.grep(curAnswer.main, function(i) { return i !== null; });
for (var iFonct=1; iFonct<=nbFonctions; iFonct++)
curSimulation.seq['f' + iFonct] = $.grep(curAnswer['f' + iFonct], function(i) { return i !== null; });
curSimulation.stack = [{name: "main", id: 0}];
curSimulation.pathStatus = new Array(path.length);
for (var i=0; i<path.length; i++) {
curSimulation.pathStatus[i] = false;
}
var recursivite = Array(nbFonctions);
for (iFonct=0; iFonct<=nbFonctions; iFonct++) {
recursivite[iFonct] = new Array(nbFonctions);
for (jFonct=0; jFonct<=nbFonctions; jFonct++) {
recursivite[iFonct][jFonct] = $.inArray(instructionId["f" + (iFonct + 1)], curSimulation.seq["f" + (jFonct + 1)]) != -1;
}
}
for (pFonct=0; pFonct<nbFonctions; pFonct++) {
for (dFonct=0; dFonct<nbFonctions; dFonct++) {
for (fFonct=0; fFonct<nbFonctions; fFonct++) {
if (recursivite[dFonct][pFonct] && recursivite[pFonct][fFonct]) {
recursivite[dFonct][fFonct] = true;
}
}
}
}
for (iFonct=0; iFonct<nbFonctions; iFonct++) {
if (recursivite[iFonct][iFonct]) {
throw taskStrings.infiniteLoop("F" + (iFonct+1));
}
}
return curSimulation;
};
var executeSlow = function(curAnswer, isExample) {
simuState = simuStates.animating;
$("#tryExecution").attr('value', taskStrings.stopLabel);
$("#resetExecution").attr("disabled", false);
try {
var simulation = getSimulation(curAnswer);
} catch(exception) {
stopExecution();
if (!isExample) {
displayHelper.validate("stay");
}
return;
}
DelayedExec.setInterval("executeStep", function() {
try {
simulateStep(simulation, true);
} catch (exception) {
stopExecution();
if (!isExample) {
displayHelper.validate("stay");
}
return;
}
if (simulation.completed) {
stopExecution();
$("#completedMessage").html(taskStrings.executionComplete);
if (!isExample) {
if (simulation.success) {
platform.validate("done");
} else {
displayHelper.validate("stay");
}
}
}
}, simulationSpeed);
};
task.tryExecution = function() {
var curAnswer = task.getAnswerObject()[level];
if (simuState == simuStates.initial) {
executeSlow(curAnswer, false);
} else if (simuState == simuStates.animating) {
stopExecution();
} else if (simuState == simuStates.stopped) {
task.resetExecution();
DelayedExec.setTimeout("execute", task.tryExecution , 500);
}
};
task.resetExecution = function() {
var curAnswer = task.getAnswerObject()[level];
if (simuState == simuStates.initial) {
} else if (simuState == simuStates.animating) {
stopExecution();
clearDrawing();
} else if (simuState == simuStates.stopped) {
clearDrawing();
}
};
task.loadExample = function(iExample) {
if (simuState == simuStates.initial) {
$("#example" + iExample).parent().css("background-color", "#AAFFAA");
executeSlow(examples[iExample][level], true);
} else {
task.resetExecution();
DelayedExec.setTimeout("executeExample", function() { task.loadExample(iExample); } , 500);
}
};
grader.gradeTask = function(strAnswer, token, callback) {
task.getLevelGrade(strAnswer, token, callback, null);
};
task.getLevelGrade = function(strAnswer, token, callback, gradedLevel) {
platform.getTaskParams(null, null, function(taskParams) {
if (strAnswer === '') {
callback(taskParams.minScore, '');
return;
}
var answer = $.parseJSON(strAnswer);
var scores = {};
var simulation;
var messages = {};
var maxScores = displayHelper.getLevelsMaxScores();
// clone the state to restore after grading.
var oldState = $.extend({}, task.getStateObject());
for (var curLevel in allPaths) {
state.level = curLevel;
task.reloadStateObject(state, false);
scores[curLevel] = 0;
try {
simulation = getSimulation(answer[level]);
while (!simulation.completed) {
simulateStep(simulation, false);
}
} catch(exception) {
messages[curLevel] = exception;
continue;
}
if (simulation.success) {
scores[curLevel] = maxScores[curLevel];
messages[curLevel] = taskStrings.success;
} else {
messages[curLevel] = taskStrings.incomplete;
}
}
task.reloadStateObject(oldState, false);
if (gradedLevel == null) {
displayHelper.sendBestScore(callback, scores, messages);
} else {
callback(scores[gradedLevel], messages[gradedLevel]);
}
});
};
}
initTask();