summaryrefslogtreecommitdiff
path: root/js/codeq/problem.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/codeq/problem.js')
-rw-r--r--js/codeq/problem.js193
1 files changed, 170 insertions, 23 deletions
diff --git a/js/codeq/problem.js b/js/codeq/problem.js
index 3e218d8..7ae54d8 100644
--- a/js/codeq/problem.js
+++ b/js/codeq/problem.js
@@ -2,8 +2,93 @@
var jqScreen = $('#screen_problem'),
languageCache = {}, // keyed by language identifier: processed data about languages
translationCache = [], // keys are autogenerated in ta(), a value is a dictionary of translations of a translation key for every language
+ problemCache = {}, // problem data cache, 3-level, keyed by: language, problem group, and problem identifier
langs, Nlangs, // constants, set on init
+ // ================================================================================
+ // Hint processing: extract hints from the translations and return them in the
+ // processed form, hint key -> translation language -> value
+ // ================================================================================
+
+ processHints = function (rawTranslations) {
+ var defaultHint = {}, // here we put all the hints with their default translations
+ allHints = {}, // the result
+ allHintKeys = [],
+ tr, key, i, lang, hint, h, j;
+ // find the default hint translations, they will form the basis of default hints
+ tr = chooseDefaultTranslation(rawTranslations, 'hint') || {};
+ for (key in tr) { // copy the hints
+ if (!tr.hasOwnProperty(key)) continue;
+ defaultHint[key] = tr[key];
+ allHintKeys.push(key);
+ }
+ // copy any hints not in the default hints to the default hints
+ for (i = langs.length - 1; i >= 0; i--) {
+ lang = langs[i];
+ tr = rawTranslations[lang];
+ if (!tr || !tr.hint) continue; // skip unavailable translations or translations with no hints
+ hint = tr.hint;
+ for (key in hint) {
+ if (!hint.hasOwnProperty(key) || !hint[key]) continue;
+ if (!key in defaultHint) {
+ defaultHint[key] = hint[key];
+ allHintKeys.push(key);
+ }
+ }
+ }
+ // create all translations for hints
+ for (i = langs.length - 1; i >= 0; i--) {
+ lang = langs[i];
+ tr = rawTranslations[lang];
+ // set up hints
+ if (!tr || !tr.hint) {
+ // there's no hint in the current language, copy the default in its entirety
+ allHints[lang] = defaultHint;
+ }
+ else {
+ // make a copy of all hints, using the default hint value where a hint value is missing
+ hint = {};
+ allHints[lang] = hint;
+ h = tr.hint;
+ for (j = allHintKeys.length; j >= 0; j--) {
+ key = allHintKeys[j];
+ hint[key] = h[key] || defaultHint[key];
+ }
+ }
+ }
+ return allHints;
+ },
+
+ // ================================================================================
+ // Plan processing: extract plans from the translations and return them in the
+ // processed form, hint key -> translation language -> value
+ // ================================================================================
+
+ processPlans = function (rawTranslations) {
+ // find the default plan translation
+ var defaultPlan = chooseDefaultTranslation(rawTranslations, 'plan') || [],
+ allPlans = {}, // the result
+ i, lang, tr;
+ // create all translations for plan
+ for (i = langs.length - 1; i >= 0; i--) {
+ lang = langs[i];
+ tr = rawTranslations[lang];
+ // set up plan
+ if (!tr || !tr.plan) {
+ // there's no plan in the current language, copy the default plan
+ allPlans[lang] = defaultPlan;
+ }
+ else {
+ allPlans[lang] = tr.plan;
+ }
+ }
+ return allPlans;
+ },
+
+ // ================================================================================
+ // Group + problems directory processing
+ // ================================================================================
+
chooseTranslation = function (keyword, lang, currentDict, enDict, translations) {
var tr = currentDict[keyword],
otherLang;
@@ -65,50 +150,56 @@
*/
createLanguageData = function (data, languageIdentifier) { // data is the content of language.json
var li = languageIdentifier, // a shorthand
- groups = data.groups || {},
+ rawTranslations = data.translations || {},
+ groups = data.groups || [],
+ Ngroups = groups.length,
html = [],
problemReferences = [],
- groupIdentifier, group, problems, problemIdentifier, problem;
- var langDict = convertTranslations(data.translations, 'name', 'description'), // this will be the resulting dictionary: multi-level keys that lead up to the lang-dict
+ group, problems, Nproblems, problem, i, j;
+ var langDict = convertTranslations(rawTranslations, 'name', 'description'), // this will be the resulting dictionary: multi-level keys that lead up to the lang-dict
groupDict, problemDict;
// title: HTML structure for "name" and "desc"
html.push('<h1 class="language-title translatable" ', ta(langDict.name), '></h1><hr>');
html.push('<div class="language-description translatable" ', ta(langDict.description), '></div>');
// content: problem directory
html.push('<ul class="language-problems">');
- for (groupIdentifier in groups) {
- if (!groups.hasOwnProperty(groupIdentifier)) continue;
- group = groups[groupIdentifier] || {};
+ for (i = 0; i < Ngroups; i++) {
+ group = groups[i] || {};
groupDict = convertTranslations(group.translations, 'name', 'description'); // the group-level translations, added will
// group content
html.push('<li><div class="group-title translatable" ', ta(groupDict.name), '></div>');
html.push('<div class="group-description translatable" ', ta(groupDict.description), '></div>');
html.push('<ul class="group-problems">');
// problem content
- problems = group.problems || {};
- for (problemIdentifier in problems) {
- if (!problems.hasOwnProperty(problemIdentifier)) continue;
- problem = problems[problemIdentifier] || {};
+ problems = group.problems || [];
+ Nproblems = problems.length;
+ for (j = 0; j < Nproblems; j++) {
+ problem = problems[j] || {};
problemDict = convertTranslations(problem.translations, 'name');
html.push('<li><a class="problem-', '' + problemReferences.length, ' translatable" ', ta(problemDict.name), '></a></li>');
- problemReferences.push({'g': groupIdentifier, 'p': problemIdentifier});
+ problemReferences.push({'g': group.identifier || 'nogroup', 'p': problem.identifier || 'noproblem', 'id': problem.id});
}
html.push('</ul></li>');
}
html.push('</ul>');
return {
- 'language': languageIdentifier,
- 'html': html.join(''),
- 'refs': problemReferences,
- 'hints': {} // TODO: prepare common hints for the language
+ 'language': languageIdentifier, // 'prolog', 'python', ...
+ 'html': html.join(''), // the DOM structure (without textual content), as HTML text
+ 'refs': problemReferences, // array of problem info {g: group, p: problem, id: problem_id}, referenced from DOM <a> elements
+ 'hints': processHints(rawTranslations) // hint translations: keyword -> lang -> value
};
},
+ // ================================================================================
+ // DOM instantiation.
+ // The transition to problem solving takes place here, after the user clicks on a
+ // problem and all the required data is loaded.
+ // ================================================================================
/**
* Instantiates the screen from the given processed data.
*/
- createDom = function (data) {
+ createDom = function (data) { // data is the (cached) result of createLanguageData()
var language = data.language;
jqScreen.html(data.html);
codeq.tr.translateDom(jqScreen);
@@ -119,12 +210,15 @@
codeq.log.error('Clicked on a problem link having erroneous index: ' + index);
return;
}
- // transition
codeq.wait(
- codeq.comms.getProblem(language, ref.g, ref.p)
- .then(function (data) {
- if (data.code !== 0) throw new Error('Failed to obtain problem data, code: ' + data.code + ', message: ' + data.message);
- codeq.globalStateMachine.transition(language, data);
+ Q.all([
+ codeq.comms.getProblem(language, ref.g, ref.p), // TODO: use ref.id instead // the current solution
+ getProblemData(language, ref.g, ref.p) // the (cached) result of processProblemData()
+ ])
+ .spread(function (userProblemData, generalProblemData) {
+ if (userProblemData.code !== 0) throw new Error('Failed to obtain user problem data, code: ' + userProblemData.code + ', message: ' + userProblemData.message);
+ if (!generalProblemData) throw new Error('General problem data is not defined');
+ codeq.globalStateMachine.transition(language, generalProblemData, data.hints, userProblemData.solution);
})
)
.fail(function (reason) {
@@ -134,6 +228,61 @@
.done();
});
},
+
+ // ================================================================================
+ // Problem definition processing
+ // ================================================================================
+
+ chooseDefaultTranslation = function (rawTranslations, translationKey) {
+ var tr = rawTranslations.en, // try English as the default
+ lang;
+ if (tr && tr[translationKey]) return tr[translationKey];
+ for (lang in rawTranslations) { // find a translation with hints
+ if (!rawTranslations.hasOwnProperty(lang) || rawTranslations[lang]) continue;
+ tr = rawTranslations[lang];
+ if (tr[translationKey]) return tr[translationKey];
+ }
+ return null; // default must be chosen by the caller
+ },
+
+ processProblemData = function (rawData, language, group, problem) {
+ var rawTranslations = rawData.translations || {};
+ return {
+ 'language': language,
+ 'group': group,
+ 'problem': problem,
+ 'id': rawData.id,
+ 'translations': convertTranslations(rawTranslations, 'title', 'name', 'slug', 'description'), // GUI translations: keyword -> lang -> value
+ 'hint': processHints(rawTranslations), // hint translations: keyword -> lang -> value
+ 'plan': processPlans(rawTranslations) // plan translations: keyword -> lang -> value
+ };
+ },
+
+ getProblemData = function (language, group, problem) {
+ var langCache = problemCache[language],
+ groupCache, cachedProblem, promise;
+ if (langCache) {
+ groupCache = langCache[group];
+ if (!groupCache) {
+ groupCache = {};
+ langCache[group] = groupCache;
+ }
+ }
+ else {
+ langCache = {};
+ problemCache[language] = langCache;
+ groupCache = {};
+ langCache[group] = groupCache;
+ }
+ cachedProblem = groupCache[problem];
+ if (cachedProblem) return Q(cachedProblem);
+ return codeq.comms.getProblemDef(language, group, problem).then(function (rawData) {
+ var data = processProblemData(rawData);
+ groupCache[problem] = data;
+ return data;
+ });
+ },
+
currentLanguage; // the currently active language
// ================================================================================
@@ -154,7 +303,6 @@
'enter': function(language){
var data = null; // language data
- $('#navigation-login').css('display', '');
$('#navigation-language').css('display', '');
$("#navigation-problem").addClass("active").css('display', '');
@@ -180,7 +328,6 @@
},
'exit' : function(){
jqScreen.css('display', 'none');
- $('#navigation-login').css('display', 'none');
$('#navigation-language').css('display', 'none');
$('#navigation-problem').css('display', 'none').removeClass("active");
}