forked from Open-CT/openct-tasks
356 lines
16 KiB
JavaScript
356 lines
16 KiB
JavaScript
function initTask(subTask) {
|
|
var state = {};
|
|
var level;
|
|
var answer = null;
|
|
var data = {
|
|
easy: {
|
|
width: 380,
|
|
height: 380,
|
|
initialStr: '{"vertexVisualInfo":{"v_0":{"x":320,"y":192},"v_1":{"x":128,"y":256},"v_2":{"x":96,"y":64},"v_3":{"x":160,"y":160},"v_4":{"x":284,"y":278.87499237060547}},"edgeVisualInfo":{"e_0":{},"e_1":{},"e_2":{},"e_5":{},"e_3":{}},"minGraph":{"vertexInfo":{"v_0":{},"v_1":{},"v_2":{},"v_3":{},"v_4":{}},"edgeInfo":{"e_0":{},"e_1":{},"e_2":{},"e_5":{},"e_3":{}},"edgeVertices":{"e_0":["v_0","v_1"],"e_1":["v_1","v_2"],"e_2":["v_0","v_2"],"e_5":["v_2","v_3"],"e_3":["v_3","v_4"]},"directed":false}}',
|
|
targetStr: '{"vertexVisualInfo":{"v_0":{"x":192,"y":256},"v_1":{"x":64,"y":224},"v_2":{"x":160,"y":64},"v_3":{"x":288,"y":96},"v_5":{"x":295,"y":209.87499237060547}},"edgeVisualInfo":{"e_0":{},"e_1":{},"e_2":{},"e_5":{},"e_4":{}},"minGraph":{"vertexInfo":{"v_0":{},"v_1":{},"v_2":{},"v_3":{},"v_5":{}},"edgeInfo":{"e_0":{},"e_1":{},"e_2":{},"e_5":{},"e_4":{}},"edgeVertices":{"e_0":["v_0","v_1"],"e_1":["v_1","v_2"],"e_2":["v_0","v_2"],"e_5":["v_2","v_3"],"e_4":["v_3","v_5"]},"directed":false}}'
|
|
},
|
|
medium: {
|
|
width: 380,
|
|
height: 380,
|
|
initialStr: '{"vertexVisualInfo":{"v_0":{"x":51,"y":90},"v_1":{"x":209,"y":141},"v_2":{"x":169,"y":236},"v_4":{"x":313,"y":101},"v_5":{"x":320,"y":208},"v_3":{"x":56,"y":226.77778244018555},"v_6":{"x":133,"y":334.77777433395386},"v_7":{"x":137,"y":39.77777433395386},"v_8":{"x":261,"y":293.77777433395386}},"edgeVisualInfo":{"e_0":{},"e_1":{},"e_3":{},"e_4":{},"e_2":{},"e_5":{},"e_6":{},"e_7":{},"e_8":{},"e_9":{},"e_10":{},"e_11":{}},"minGraph":{"vertexInfo":{"v_0":{},"v_1":{},"v_2":{},"v_4":{},"v_5":{},"v_3":{},"v_6":{},"v_7":{},"v_8":{}},"edgeInfo":{"e_0":{},"e_1":{},"e_3":{},"e_4":{},"e_2":{},"e_5":{},"e_6":{},"e_7":{},"e_8":{},"e_9":{},"e_10":{},"e_11":{}},"edgeVertices":{"e_0":["v_0","v_1"],"e_1":["v_1","v_2"],"e_3":["v_1","v_4"],"e_4":["v_4","v_5"],"e_2":["v_0","v_2"],"e_5":["v_0","v_3"],"e_6":["v_1","v_7"],"e_7":["v_1","v_8"],"e_8":["v_8","v_5"],"e_9":["v_7","v_4"],"e_10":["v_2","v_6"],"e_11":["v_2","v_3"]},"directed":false}}',
|
|
targetStr: '{"vertexVisualInfo":{"v_0":{"x":256,"y":240},"v_1":{"x":160,"y":192},"v_2":{"x":208,"y":96},"v_4":{"x":64,"y":192},"v_5":{"x":64,"y":288},"v_3":{"x":304,"y":144},"v_6":{"x":288,"y":80},"v_7":{"x":112,"y":128},"v_8":{"x":160,"y":288}},"edgeVisualInfo":{"e_0":{},"e_1":{},"e_3":{},"e_4":{},"e_2":{},"e_5":{},"e_6":{},"e_7":{},"e_8":{},"e_9":{},"e_10":{},"e_11":{}},"minGraph":{"vertexInfo":{"v_0":{},"v_1":{},"v_2":{},"v_4":{},"v_5":{},"v_3":{},"v_6":{},"v_7":{},"v_8":{}},"edgeInfo":{"e_0":{},"e_1":{},"e_3":{},"e_4":{},"e_2":{},"e_5":{},"e_6":{},"e_7":{},"e_8":{},"e_9":{},"e_10":{},"e_11":{}},"edgeVertices":{"e_0":["v_0","v_1"],"e_1":["v_1","v_2"],"e_3":["v_1","v_4"],"e_4":["v_4","v_5"],"e_2":["v_0","v_2"],"e_5":["v_0","v_3"],"e_6":["v_1","v_7"],"e_7":["v_1","v_8"],"e_8":["v_8","v_5"],"e_9":["v_7","v_4"],"e_10":["v_2","v_6"],"e_11":["v_2","v_3"]},"directed":false}}'
|
|
},
|
|
hard: {
|
|
width: 380,
|
|
height: 480,
|
|
initialStr: '{"vertexVisualInfo":{"v_0":{"x":112,"y":64},"v_1":{"x":208,"y":64},"v_4":{"x":32,"y":32},"v_5":{"x":288,"y":32},"v_6":{"x":208,"y":192},"v_7":{"x":208,"y":128},"v_9":{"x":112,"y":128},"v_10":{"x":112,"y":192},"v_11":{"x":32,"y":288},"v_13":{"x":288,"y":288},"v_14":{"x":208,"y":256},"v_15":{"x":112,"y":256}},"edgeVisualInfo":{"e_4":{},"e_5":{},"e_7":{},"e_10":{},"e_13":{},"e_14":{},"e_15":{},"e_16":{},"e_17":{},"e_21":{},"e_22":{},"e_24":{},"e_27":{},"e_1":{},"e_2":{},"e_18":{},"e_19":{},"e_23":{},"e_25":{},"e_3":{}},"minGraph":{"vertexInfo":{"v_0":{},"v_1":{},"v_4":{},"v_5":{},"v_6":{},"v_7":{},"v_9":{},"v_10":{},"v_11":{},"v_13":{},"v_14":{},"v_15":{}},"edgeInfo":{"e_4":{},"e_5":{},"e_7":{},"e_10":{},"e_13":{},"e_14":{},"e_15":{},"e_16":{},"e_17":{},"e_21":{},"e_22":{},"e_24":{},"e_27":{},"e_1":{},"e_2":{},"e_18":{},"e_19":{},"e_23":{},"e_25":{},"e_3":{}},"edgeVertices":{"e_4":["v_7","v_6"],"e_5":["v_7","v_5"],"e_7":["v_0","v_1"],"e_10":["v_0","v_4"],"e_13":["v_4","v_11"],"e_14":["v_11","v_10"],"e_15":["v_9","v_10"],"e_16":["v_5","v_13"],"e_17":["v_13","v_14"],"e_21":["v_6","v_13"],"e_22":["v_5","v_4"],"e_24":["v_10","v_15"],"e_27":["v_11","v_13"],"e_1":["v_15","v_14"],"e_2":["v_7","v_1"],"e_18":["v_0","v_9"],"e_19":["v_6","v_14"],"e_23":["v_1","v_5"],"e_25":["v_15","v_11"],"e_3":["v_9","v_1"]},"directed":false}}',
|
|
targetStr: '{"vertexVisualInfo":{"v_0":{"x":38,"y":188},"v_1":{"x":182,"y":204},"v_4":{"x":150,"y":284},"v_5":{"x":214,"y":284},"v_6":{"x":310,"y":316},"v_7":{"x":326.3298969072165,"y":185.36082474226805},"v_9":{"x":54,"y":316},"v_10":{"x":38,"y":444},"v_11":{"x":150,"y":348},"v_13":{"x":214,"y":348},"v_14":{"x":326,"y":444},"v_15":{"x":182,"y":428}},"edgeVisualInfo":{"e_4":{},"e_5":{},"e_7":{},"e_10":{},"e_13":{},"e_14":{},"e_15":{},"e_16":{},"e_17":{},"e_21":{},"e_22":{},"e_24":{},"e_27":{},"e_1":{},"e_2":{},"e_18":{},"e_19":{},"e_23":{},"e_25":{},"e_26":{}},"minGraph":{"vertexInfo":{"v_0":{},"v_1":{},"v_4":{},"v_5":{},"v_6":{},"v_7":{},"v_9":{},"v_10":{},"v_11":{},"v_13":{},"v_14":{},"v_15":{}},"edgeInfo":{"e_4":{},"e_5":{},"e_7":{},"e_10":{},"e_13":{},"e_14":{},"e_15":{},"e_16":{},"e_17":{},"e_21":{},"e_22":{},"e_24":{},"e_27":{},"e_1":{},"e_2":{},"e_18":{},"e_19":{},"e_23":{},"e_25":{},"e_26":{}},"edgeVertices":{"e_4":["v_7","v_6"],"e_5":["v_7","v_5"],"e_7":["v_0","v_1"],"e_10":["v_0","v_4"],"e_13":["v_4","v_11"],"e_14":["v_11","v_10"],"e_15":["v_9","v_10"],"e_16":["v_5","v_13"],"e_17":["v_13","v_14"],"e_21":["v_6","v_13"],"e_22":["v_5","v_4"],"e_24":["v_10","v_15"],"e_27":["v_11","v_13"],"e_1":["v_15","v_14"],"e_2":["v_7","v_1"],"e_18":["v_0","v_9"],"e_19":["v_6","v_14"],"e_23":["v_1","v_5"],"e_25":["v_15","v_11"],"e_26":["v_9","v_1"]},"directed":false}}'
|
|
}
|
|
};
|
|
|
|
var paperUser;
|
|
var paperTarget;
|
|
|
|
var graphUser;
|
|
var graphTarget;
|
|
|
|
var visualGraphUser;
|
|
var visualGraphTarget;
|
|
var visualGraphShadow;
|
|
|
|
var graphDrawerUser;
|
|
var graphDrawerTarget;
|
|
var graphDrawerShadow;
|
|
|
|
var targetVertices;
|
|
var userVertices;
|
|
var targetPositions;
|
|
var userDefaultPositions;
|
|
var dragLimits;
|
|
var dragLimitsPad = 2;
|
|
|
|
var vertexDragger;
|
|
var graphMouse;
|
|
var snapThreshold = 20;
|
|
var overlapThreshold = 10;
|
|
var kickSize = 20;
|
|
var highlightedEdge;
|
|
|
|
var graphParams = {
|
|
borderAttr: {
|
|
"stroke-width": 1
|
|
},
|
|
user: {
|
|
circleAttr: {
|
|
r: 20,
|
|
stroke: "black",
|
|
"stroke-width": 0,
|
|
fill: "#0000cc"
|
|
},
|
|
lineAttr: {
|
|
"stroke-width": 3,
|
|
stroke: "black"
|
|
},
|
|
highlightedLineAttr: {
|
|
"stroke-width": 3,
|
|
stroke: "red"
|
|
}
|
|
},
|
|
target: {
|
|
circleAttr: {
|
|
r: 20,
|
|
stroke: "black",
|
|
"stroke-width": 0,
|
|
fill: "#cc0000"
|
|
},
|
|
lineAttr: {
|
|
"stroke-width": 3
|
|
}
|
|
},
|
|
shadow: {
|
|
circleAttr: {
|
|
r: 20,
|
|
stroke: "#df8282",
|
|
"stroke-width": 1,
|
|
"stroke-dasharray":"--",
|
|
fill: "#eee5e5"
|
|
},
|
|
lineAttr: {
|
|
"stroke-width": 3,
|
|
stroke: "#b2b2b2",
|
|
opacity: 0
|
|
}
|
|
}
|
|
};
|
|
|
|
subTask.loadLevel = function(curLevel) {
|
|
level = curLevel;
|
|
visualGraphTarget = VisualGraph.fromJSON(data[level].targetStr, "visualTarget");
|
|
graphTarget = visualGraphTarget.getGraph();
|
|
targetVertices = graphTarget.getAllVertices();
|
|
targetPositions = getAllPositions(graphTarget, visualGraphTarget);
|
|
visualGraphUser = VisualGraph.fromJSON(data[level].initialStr, "visualUser");
|
|
graphUser = visualGraphUser.getGraph();
|
|
userVertices = graphUser.getAllVertices();
|
|
userDefaultPositions = getAllPositions(graphUser, visualGraphUser);
|
|
|
|
displayHelper.hideValidateButton = true;
|
|
};
|
|
|
|
subTask.getStateObject = function() {
|
|
return state;
|
|
};
|
|
|
|
subTask.reloadAnswerObject = function(answerObj) {
|
|
answer = answerObj;
|
|
if(!answer) {
|
|
return;
|
|
}
|
|
for(var iVertex in userVertices) {
|
|
var position = answer[iVertex];
|
|
var info = visualGraphUser.getVertexVisualInfo(userVertices[iVertex]);
|
|
info.x = position.x;
|
|
info.y = position.y;
|
|
}
|
|
};
|
|
|
|
subTask.resetDisplay = function() {
|
|
initPaper();
|
|
initHandlers();
|
|
};
|
|
|
|
subTask.getAnswerObject = function() {
|
|
return answer;
|
|
};
|
|
|
|
subTask.getDefaultAnswerObject = function() {
|
|
return userDefaultPositions;
|
|
};
|
|
|
|
subTask.unloadLevel = function(callback) {
|
|
if (vertexDragger) {
|
|
vertexDragger.setEnabled(false);
|
|
visualGraphUser.remove();
|
|
}
|
|
$("#execute").unbind("click");
|
|
callback();
|
|
};
|
|
|
|
function initPaper() {
|
|
paperUser = subTask.raphaelFactory.create("animUser", "animUser", data[level].width, data[level].height);
|
|
paperUser.rect(0, 0, data[level].width, data[level].height).attr(graphParams.borderAttr);
|
|
graphDrawerUser = new SimpleGraphDrawer(graphParams.user.circleAttr, graphParams.user.lineAttr);
|
|
visualGraphUser.setDrawer(graphDrawerUser);
|
|
visualGraphUser.setPaper(paperUser);
|
|
visualGraphUser.redraw();
|
|
|
|
graphDrawerShadow = new SimpleGraphDrawer(graphParams.shadow.circleAttr, graphParams.shadow.lineAttr);
|
|
visualGraphShadow = VisualGraph.fromJSON(data[level].targetStr, "shadow", paperUser, graphTarget, graphDrawerShadow, true);
|
|
|
|
graphUser.forEachEdge(function(id) {
|
|
visualGraphUser.elementToFront(id);
|
|
});
|
|
graphUser.forEachVertex(function(id) {
|
|
visualGraphUser.elementToFront(id);
|
|
});
|
|
|
|
paperTarget = subTask.raphaelFactory.create("animTarget", "animTarget", data[level].width, data[level].height);
|
|
paperTarget.rect(0, 0, data[level].width, data[level].height).attr(graphParams.borderAttr);
|
|
graphDrawerTarget = new SimpleGraphDrawer(graphParams.target.circleAttr, graphParams.target.lineAttr);
|
|
visualGraphTarget.setDrawer(graphDrawerTarget);
|
|
visualGraphTarget.setPaper(paperTarget);
|
|
visualGraphTarget.redraw();
|
|
|
|
dragLimits = {
|
|
minX: graphParams.user.circleAttr.r + dragLimitsPad,
|
|
maxX: data[level].width - graphParams.user.circleAttr.r - dragLimitsPad,
|
|
minY: graphParams.user.circleAttr.r + dragLimitsPad,
|
|
maxY: data[level].height - graphParams.user.circleAttr.r - dragLimitsPad
|
|
};
|
|
|
|
graphMouse = new GraphMouse("mouse", graphUser, visualGraphUser);
|
|
vertexDragger = new VertexDragger({
|
|
id: "dragger",
|
|
visualGraph: visualGraphUser,
|
|
graphMouse: graphMouse,
|
|
snapThreshold: snapThreshold,
|
|
snapPositions: targetPositions,
|
|
dragLimits: dragLimits,
|
|
callback: onVisualChange,
|
|
enabled: true
|
|
});
|
|
}
|
|
|
|
function initHandlers() {
|
|
$("#execute").unbind("click");
|
|
$("#execute").click(clickExecute);
|
|
$("#animUser").unbind("mousedown");
|
|
$("#animUser").mousedown(unhighlightEdge);
|
|
}
|
|
|
|
function clickExecute() {
|
|
var result = checkIsomorphism();
|
|
if(result.success) {
|
|
platform.validate("done");
|
|
}
|
|
else {
|
|
if(result.param === "edge") {
|
|
highlightEdge(result.id);
|
|
}
|
|
platform.validate("stay");
|
|
}
|
|
}
|
|
|
|
function unhighlightEdge() {
|
|
if(highlightedEdge) {
|
|
visualGraphUser.getRaphaelsFromID(highlightedEdge)[0].attr(graphParams.user.lineAttr);
|
|
highlightedEdge = null;
|
|
}
|
|
}
|
|
|
|
function highlightEdge(id) {
|
|
unhighlightEdge();
|
|
highlightedEdge = id;
|
|
visualGraphUser.getRaphaelsFromID(highlightedEdge)[0].attr(graphParams.user.highlightedLineAttr);
|
|
}
|
|
|
|
function onVisualChange(id) {
|
|
unhighlightEdge();
|
|
var position = graphDrawerUser.getVertexPosition(id);
|
|
for(var iVertex in userVertices) {
|
|
var otherID = userVertices[iVertex];
|
|
if(id === otherID) {
|
|
continue;
|
|
}
|
|
var otherPosition = graphDrawerUser.getVertexPosition(otherID);
|
|
if(checkOverlap(position, otherPosition)) {
|
|
kickVertex(id, position);
|
|
}
|
|
}
|
|
answer = getAllPositions(graphUser, visualGraphUser);
|
|
}
|
|
|
|
function getAllPositions(graph, visualGraph) {
|
|
var vertices = graph.getAllVertices();
|
|
return $.map(vertices, function(id) {
|
|
return $.extend(true, {}, visualGraph.getVertexVisualInfo(id));
|
|
});
|
|
}
|
|
|
|
function getRandomInt(from, to) {
|
|
return Math.round(from + (to - from) * Math.random());
|
|
}
|
|
|
|
function kickVertex(id, position) {
|
|
var x = position.x + getRandomInt(-kickSize, kickSize);
|
|
var y = position.y + getRandomInt(-kickSize, kickSize);
|
|
x = Math.max(dragLimits.minX, Math.min(dragLimits.maxX, x));
|
|
y = Math.max(dragLimits.minY, Math.min(dragLimits.maxY, y));
|
|
graphDrawerUser.moveVertex(id, x, y);
|
|
}
|
|
|
|
function checkOverlap(position1, position2) {
|
|
return (position1.x - position2.x) * (position1.x - position2.x) + (position1.y - position2.y) * (position1.y - position2.y) <= overlapThreshold * overlapThreshold;
|
|
}
|
|
|
|
function checkIsomorphism() {
|
|
// Store all target and all user vertex positions in objects, for fast checking.
|
|
var targetPositionsObject = positionArrayToObject(targetPositions);
|
|
var userPositionsObject = positionArrayToObject(answer);
|
|
|
|
// Check whether any vertices are not in place.
|
|
for(var targetPosition in targetPositionsObject) {
|
|
if(userPositionsObject[targetPosition] === undefined) {
|
|
return {
|
|
success: false,
|
|
param: "vertex"
|
|
};
|
|
}
|
|
}
|
|
|
|
// Map each user vertex to the corresponding target vertex.
|
|
var userIsomorphism = {};
|
|
for(var userPosition in userPositionsObject) {
|
|
var userIndex = userPositionsObject[userPosition];
|
|
var targetIndex = targetPositionsObject[userPosition];
|
|
var userID = userVertices[userIndex];
|
|
var targetID = targetVertices[targetIndex];
|
|
userIsomorphism[userID] = targetID;
|
|
}
|
|
|
|
// Check that every edge in the user's graph appears in the target.
|
|
var userEdges = graphUser.getAllEdges();
|
|
for(var iEdge in userEdges) {
|
|
var userEdgeID = userEdges[iEdge];
|
|
var userEdgeVertices = graphUser.getEdgeVertices(userEdgeID);
|
|
var userVertex1 = userEdgeVertices[0];
|
|
var userVertex2 = userEdgeVertices[1];
|
|
var targetVertex1 = userIsomorphism[userVertex1];
|
|
var targetVertex2 = userIsomorphism[userVertex2];
|
|
if(!graphTarget.hasNeighbor(targetVertex1, targetVertex2)) {
|
|
return {
|
|
success: false,
|
|
param: "edge",
|
|
id: userEdgeID
|
|
};
|
|
}
|
|
}
|
|
return {
|
|
success: true
|
|
};
|
|
}
|
|
|
|
function positionArrayToObject(positions) {
|
|
// Take an array of positions and return an object that maps positions to their indices.
|
|
var result = {};
|
|
$.each(positions, function(iPosition, position) {
|
|
result[positionToString(position)] = iPosition;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function positionToString(position) {
|
|
return position.x + "_" + position.y;
|
|
}
|
|
|
|
function getResultAndMessage() {
|
|
var result = checkIsomorphism();
|
|
if(result.success) {
|
|
return {
|
|
successRate: 1,
|
|
message: taskStrings.success
|
|
};
|
|
}
|
|
if(result.param === "vertex") {
|
|
return {
|
|
successRate: 0,
|
|
message: taskStrings.vertexError
|
|
};
|
|
}
|
|
return {
|
|
successRate: 0,
|
|
message: taskStrings.edgeError
|
|
};
|
|
}
|
|
|
|
subTask.getGrade = function(callback) {
|
|
callback(getResultAndMessage());
|
|
};
|
|
}
|
|
initWrapper(initTask, ["easy", "medium", "hard"]);
|