/**
* The robot state of the state machine. When it is entered it'll prepare the code editor and load a sub-state machine which represents the 3 different parts of the screen.
*/
(function() {
var subScreens, //this will be the actual (sub)state machine
stateNameTag = 'stateName', //a tag for data which is added to some html elements
jqScreen = $('#screen_robot'), // the screen container element
//quadrants
jqDescription = jqScreen.find('.block1'),
jqCode = jqScreen.find('.block2'),
jqConsole = jqScreen.find('.block3'),
jqInfo = jqScreen.find('.block4'),
jqAllQuadrants = jqDescription.add(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
// misc
currentSubState = null,
transitionEventName = 'mousedown',//event name of the event which will trigger the transition between these substates - the most common transition at least (there are some corner cases on the hint and test buttons -> see the code below)
substates = {
'description': {
'enter': function () {
currentSubState = 'block1';
jqScreen.addClass(currentSubState);
},
'exit': function () {
jqScreen.removeClass(currentSubState);
currentSubState = null;
}
},
'code': {
'enter': function () {
currentSubState = 'block2';
jqScreen.addClass(currentSubState);
},
'exit': function () {
jqScreen.removeClass(currentSubState);
currentSubState = null;
}
},
'info': {
'enter': function () {
currentSubState = 'block4';
jqScreen.addClass(currentSubState);
},
'exit': function () {
jqScreen.removeClass(currentSubState);
currentSubState = null;
}
},
'console': {
'enter': function () {
currentSubState = 'block3';
jqScreen.addClass(currentSubState);
},
'exit': function () {
jqScreen.removeClass(currentSubState);
currentSubState = null;
}
}
};
var robotHandler; //created when we enter the robot state and destroyed once we leave it
codeq.globalStateMachine.register('robot', {
'enter': function (problemDef, commonDef, currentSolution) {
$('#navigation-problem_list').css('display', '');
$("#navigation-robot").addClass("active");
$('#navigation-robot').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);
subScreens = codeq.makeStateMachine(substates);
subScreens.transition(jqDescription.data(stateNameTag));
/* Q.delay(100).then(function(){
jqAllQuadrants.addClass('transition');//for smooth animations - need to be delayed, because otherwise we get some weird "animations" while the page is loading
}).done();*/
jqInfoButtons.on(transitionEventName, function (event) {
subScreens.transition('info'); // set focus on the hints quadrant
event.stopPropagation(); // don't allow the event to go on and trigger further transition
});
jqBtnRun.on(transitionEventName, function (event) {
subScreens.transition('console'); // set focus on the hints quadrant
event.stopPropagation(); // don't allow the event to go on and trigger further transition
});
jqAllQuadrants.on(transitionEventName, function () {
subScreens.transition($(this).data(stateNameTag));
});
},
'exit': function () {
jqAllButtons.off(); // unregister all event handlers
jqAllQuadrants.off();
jqScreen.css('display', 'none');
// jqAllQuadrants.removeClass('transition');
robotHandler.destroy();
robotHandler = null;
subScreens.destroy();
subScreens = null;
jqScreen.addClass('block1');
$('#navigation-problem_list').css('display', 'none');
$("#navigation-robot").removeClass("active");
$('#navigation-robot').css('display', 'none');
}
});
jqDescription.data(stateNameTag, 'description');
jqCode.data(stateNameTag, 'code');
jqConsole.data(stateNameTag, 'console');
jqInfo.data(stateNameTag, 'info');
// a constant
var firstCharacterPos = {'line': 0, 'ch': 0};
var makeRobotTerminalHandler = function (jqConsole, editor, problem_id, activityHandler) {
var terminal = codeq.makeConsole(jqConsole, {
'greeting': 'CodeQ Robot terminal proxy',
'autoHistory': true
});
terminal.onInput = function (text) {
terminal.append('Not implemented.\n', 'output');
};
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 jqDescriptionContent = jqDescription.find('.description'),
jqEditor = jqCode.find('.code_editor'),
jqHints = jqInfo.find('.hints'),
editor = codeq.makeEditor(jqEditor[0], {
mode: 'python',
indentUnit: 4,
value: currentSolution || ''
}),
activityHandler = codeq.makeActivityHandler(editor, problemDef.id),
hinter = codeq.makeHinter(jqHints, jqEditor, editor, 'robot_hints', problemDef, commonDef),
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');
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'
}
$('div.console').html('
'+text+'
'); } }); 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 () { activityHandler.queueTrace({'typ': 'plan'}); if (!hinter.planNext()) { jqBtnPlan.prop('disabled', true).blur(); } }); jqBtnHint.on('click', function () { jqBtnHint.ladda('start'); codeq.comms.sendHint({ 'language': 'robot', 'program': editor.getDoc().getValue(), 'problem_id': problemDef.id }) .then(function (data) { if (data.code === 0) { activityHandler.queueTrace({'typ': 'hint', 'feedback': data.hints}); hinter.handle(data.hints); } else { commError('error: ' + data.message); } }) .fail(commError) .fin(function () { 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})); }); jqBtnStop.on('click', function () { activityHandler.queueTrace({'typ': 'robot_stop'}); socket.send(JSON.stringify({action: 'stop'})); }); 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(); jqDescriptionContent.empty(); jqEditor.empty(); // TODO: perhaps you do not want to "free" the editor, just empty it jqDescriptionContent = null; jqEditor = null; jqHints = null; codeq.tr.registerDictionary('robot', codeq.tr.emptyDictionary); } }; }; })();