/* 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 jqConsole = jqScreen.find('.block-console'), jqBtnPlan = jqScreen.find('.btn-plan'), jqBtnHint = jqScreen.find('.btn-hint').ladda(), jqBtnRun = jqScreen.find('.btn-run'), jqBtnStop = jqScreen.find('.btn-stop'), jqAllButtons = jqBtnPlan.add(jqBtnHint).add(jqBtnRun).add(jqBtnStop), // all buttons robotHandler; // created when we enter the robot state and destroyed once we leave it 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); }; codeq.globalStateMachine.register('robot', { 'jqScreen': jqScreen, 'enter': function (ref, data) { codeq.loadProblemData(ref, data).then(function (problem) { enterFun(problem.generalProblemData, data.commonDef, problem.solution); }) .fail(function (error) { var message = 'Could not obtain problem definition: ' + error.message; codeq.log.error(message, error); alert(message); history.back(); }) .done(); }, 'exit': function () { jqScreen.css('display', 'none'); if (robotHandler) 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 = jqScreen.find('.code-editor'), jqHints = jqScreen.find('.hints'), jqStatus = jqConsole.find('.status'), jqTerminal = jqConsole.find('.console'), editor = codeq.makeEditor(jqEditor[0], { mode: 'python', indentUnit: 4, value: currentSolution || problemDef.initial || '' }, 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), 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 () { hinter.clear(); if (!hinter.planNext()) { jqBtnPlan.prop('disabled', true).blur(); } }); jqBtnHint.on('click', function () { hinter.clear(); $(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 { alert('error: ' + data.message); } }) .fail(alert) .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); } }; }; })();