/* CodeQ: an online programming tutor.
Copyright (C) 2015,2016 UL FRI
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
(function() {
"use strict";
var jqScreen = $('#screen_robot'), // the screen container element
// quadrants
jqCode = jqScreen.find('.block2'),
jqConsole = jqScreen.find('.block3'),
jqInfo = jqScreen.find('.block-left'),
jqAllQuadrants = jqCode.add(jqConsole).add(jqInfo), // all the quadrants
// buttons
jqBtnPlan = jqScreen.find('.btn-plan'),
jqBtnHint = jqScreen.find('.btn-hint').ladda(),
jqBtnRun = jqScreen.find('.btn-run'),
jqBtnStop = jqScreen.find('.btn-stop'),
jqInfoButtons = jqBtnPlan.add(jqBtnHint), // all info-focusing buttons
jqAllButtons = jqInfoButtons.add(jqBtnRun).add(jqBtnStop), // all buttons
robotHandler;
var enterFun = function(problemDef, commonDef, currentSolution) {
$('#navigation-problem_list').css('display', '');
var navigationRobot = $("#navigation-robot");
navigationRobot.addClass("active");
navigationRobot.css('display', '');
jqScreen.css('display', '');//we have to show the screen now so the code editor shows its initial values correctly
robotHandler = createRobotHandler(problemDef, commonDef, currentSolution);
};
var robotHandler; //created when we enter the robot state and destroyed once we leave it
codeq.globalStateMachine.register('robot', {
'jqScreen': jqScreen,
'enter': function (ref, data) {
codeq.loadProblemData(ref,data).then(function(problem){
//codeq.log.debug("then:"+JSON.stringify(problem));
enterFun(problem.generalProblemData,data.commonDef,problem.solution);
})
.fail(function (reason) {
codeq.log.error('Failed to obtain the problem definition: ' + reason, reason);
alert('Failed to obtain the problem definition: ' + reason);
history.back();//TODO test
})
.done();
},
'exit': function () {
jqAllButtons.off(); // unregister all event handlers
jqAllQuadrants.off();
jqScreen.css('display', 'none');
robotHandler.destroy();
robotHandler = null;
$('#navigation-problem_list').css('display', 'none');
var navigationRobot = $("#navigation-robot");
navigationRobot.removeClass("active");
navigationRobot.css('display', 'none');
}
});
// a constant
var firstCharacterPos = {'line': 0, 'ch': 0};
var makeRobotTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) {
var terminal = codeq.makeConsole(jqConsole, {
'greeting': 'Robot messages\n--------------\n\n',
});
terminal.inputDisable();
return terminal;
};
codeq.on('init', function (args) {
codeq.tr.registerDictionary('robot', codeq.tr.emptyDictionary); // to make the translator happy, when this screen is not active
});
var createRobotHandler = function (problemDef, commonDef, currentSolution) {
var jqEditor = jqCode.find('.code_editor'),
jqTerminal = jqConsole.find('.console'),
jqHints = jqInfo.find('.hints'),
jqStatus = jqConsole.find('.status'),
editor = codeq.makeEditor(jqEditor[0],
{
mode: 'python',
indentUnit: 4,
value: currentSolution || ''
},
function () {
jqBtnRun.focus();
}),
activityHandler = codeq.makeActivityHandler(editor, problemDef.id),
terminal = makeRobotTerminalHandler(jqTerminal, editor, problemDef.id, activityHandler),
hinter = codeq.makeHinter(jqHints, jqEditor, editor, 'robot_hints', problemDef, commonDef, activityHandler),
commError = function (error) {
alert(error);
},
reconnectTimer = null,
url = 'ws://' + codeq.settings['robot_address'] + ':8000/',
socket = eio(url);
// set up the websocket events
socket.on('close', function (data) {
console.log('websocket closed, trying to reopen in 1 s');
jqStatus.html('Not connected.');
reconnectTimer = setTimeout(function () {
reconnectTimer = null;
socket.open();
}, 1000);
});
socket.on('message', function(data) {
//console.log('Received: ' + data);
var json_obj = JSON.parse(data),
sensors, sensor, text = '';
if (json_obj.event == 'update') {
sensors = json_obj.sensors;
for (sensor in sensors) {
if (!sensors.hasOwnProperty(sensor)) continue;
text += sensor + ': ' + sensors[sensor] + '
\n'
}
jqStatus.html(text);
}
else if (json_obj.event == 'output') {
text = json_obj.text;
terminal.append(text, 'output');
}
});
codeq.template.processDictionary(problemDef.translations.description,
[problemDef.language, problemDef.group, problemDef.problem]);
codeq.tr.registerDictionary('robot', problemDef.translations);
codeq.tr.translateDom(jqScreen);
jqBtnPlan.prop('disabled', !hinter.hasNextPlan());
editor.on('change', function (instance, changeObj) {
var doc = editor.getDoc(),
pos = codeq.codePointCount(doc.getRange(firstCharacterPos, changeObj.from)),
text;
text = changeObj.removed.join('\n');
if (text) {
activityHandler.queueTrace({'typ': 'rm', 'off': pos, 'txt': text});
}
text = changeObj.text.join('\n');
if (text) {
activityHandler.queueTrace({'typ': 'ins', 'off': pos, 'txt': text});
}
});
jqBtnPlan.on('click', function () {
if (!hinter.planNext()) {
jqBtnPlan.prop('disabled', true).blur();
}
});
jqBtnHint.on('click', function () {
$(editor.getWrapperElement()).addClass('disabled');
editor.setOption('readOnly', 'nocursor');
jqBtnHint.ladda('start');
codeq.comms.sendHint({
'program': editor.getDoc().getValue(),
'problem_id': problemDef.id
})
.then(function (data) {
if (data.code === 0) {
hinter.handle(data.hints);
}
else {
commError('error: ' + data.message);
}
})
.fail(commError)
.fin(function () {
editor.setOption('readOnly', false);
$(editor.getWrapperElement()).removeClass('disabled');
jqBtnHint.ladda('stop');
})
.done();
});
jqBtnRun.on('click', function () {
var program = editor.getDoc().getValue();
activityHandler.queueTrace({'typ': 'robot_run', 'program': program});
socket.send(JSON.stringify({action: 'run', program: program}));
terminal.append('\n', 'output');
});
jqBtnStop.on('click', function () {
activityHandler.queueTrace({'typ': 'robot_stop'});
socket.send(JSON.stringify({action: 'stop'}));
terminal.append('\n', 'output');
});
codeq.comms.loadProblem(problemDef.id).done();
activityHandler.queueTrace({
'typ': 'open',
'time': Date.now(),
'content': editor.getDoc().getValue()
});
return {
destroy: function () {
codeq.comms.endProblem().done();
if (socket) {
socket.off('close');
socket.off('message');
socket.close();
socket = null;
}
if (reconnectTimer !== null) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
$('#screen_robot .title').text('');//empty the title text
jqAllButtons.off();
editor.off('change');
activityHandler.queueTrace({'typ': 'close'});
activityHandler.flush();
hinter.destroy();
jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it
jqEditor = null;
jqHints = null;
codeq.tr.registerDictionary('robot', codeq.tr.emptyDictionary);
}
};
};
})();