From aea91d61a6b68a73a507387d2ad02f4d56d0543f Mon Sep 17 00:00:00 2001 From: vgx32 Date: Tue, 21 Jul 2015 17:44:15 -0700 Subject: [PATCH 01/11] MULTIPLE_CHOICES_MULTIPLE_ANSWERS implementation for EV translations complete --- client/template/challenge/challenge.js | 12 +- client/template/challenge/question_area.js | 2 + .../q_multiple_choices_multiple_answers.html | 12 ++ .../q_multiple_choices_multiple_answers.js | 124 ++++++++++++++++++ .../challenge/types/q_word_pairing.js | 1 - 5 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 client/template/challenge/types/q_multiple_choices_multiple_answers.html create mode 100644 client/template/challenge/types/q_multiple_choices_multiple_answers.js diff --git a/client/template/challenge/challenge.js b/client/template/challenge/challenge.js index a902ca8..f67c569 100644 --- a/client/template/challenge/challenge.js +++ b/client/template/challenge/challenge.js @@ -50,6 +50,8 @@ Template.challenge.helpers({ return "Match with correct answer"; case QTYPE.MULTIPLE_CHOICES_TRANSLATION_PIC: return "Match with correct picture"; + case QTYPE.MULTIPLE_CHOICES_MULTIPLE_ANSWERS: + return "Pick all answers that mean the same thing"; case QTYPE.TRUE_FALSE: return "True or False"; case QTYPE.WORD_PAIRING: @@ -180,7 +182,10 @@ function setupQuestion(lesson, phraseIndex) { case QTYPE.MULTIPLE_CHOICES_TRANSLATION_PIC: setupMultipleChoicesTranslation(lesson); break; - + case QTYPE.MULTIPLE_CHOICES_MULTIPLE_ANSWERS: + setupMultipleChoicesMultipleAnswers(lesson); + break; + case QTYPE.WORD_PAIRING: setupWordPairing(lesson); break; @@ -241,7 +246,7 @@ function challengeComplete(lesson) { var nextLevel = user.profile.level + 1; if (user.profile.xp >= LEVEL_XP_REQUIREMENTS[nextLevel]) { Session.set("reachedNewLevel", true); - Meteor.call('unlockLevel', userId, nextLevel); + Meteor.call("unlockLevel", userId, nextLevel); } } @@ -269,6 +274,9 @@ answer = function(lesson) { case QTYPE.MULTIPLE_CHOICES_TRANSLATION_PIC: answerScore = aMultipleChoicesTranslation(phrase); break; + case QTYPE.MULTIPLE_CHOICES_MULTIPLE_ANSWERS: + answerScore = aMultipleChoicesMultipleAnswers(phrase); + break; case QTYPE.WORD_PAIRING: answerScore = aWordPairing(); case QTYPE.REARRANGE: diff --git a/client/template/challenge/question_area.js b/client/template/challenge/question_area.js index f90f00c..d24bc5f 100644 --- a/client/template/challenge/question_area.js +++ b/client/template/challenge/question_area.js @@ -11,6 +11,8 @@ Template.questionArea.helpers({ case QTYPE.MULTIPLE_CHOICES_TRANSLATION: case QTYPE.MULTIPLE_CHOICES_TRANSLATION_PIC: return "qMultipleChoicesTranslation"; + case QTYPE.MULTIPLE_CHOICES_MULTIPLE_ANSWERS: + return "qMultipleChoicesMultipleAnswers"; case QTYPE.WORD_PAIRING: return "qWordPairing"; case QTYPE.REARRANGE: diff --git a/client/template/challenge/types/q_multiple_choices_multiple_answers.html b/client/template/challenge/types/q_multiple_choices_multiple_answers.html new file mode 100644 index 0000000..c1d10d2 --- /dev/null +++ b/client/template/challenge/types/q_multiple_choices_multiple_answers.html @@ -0,0 +1,12 @@ + diff --git a/client/template/challenge/types/q_multiple_choices_multiple_answers.js b/client/template/challenge/types/q_multiple_choices_multiple_answers.js new file mode 100644 index 0000000..6885a0d --- /dev/null +++ b/client/template/challenge/types/q_multiple_choices_multiple_answers.js @@ -0,0 +1,124 @@ +const CHOICE_NUM = 4; + +Template.qMultipleChoicesMultipleAnswers.helpers({ + + phrase: function() { + var phrase = this.phrases[Session.get("phraseIndex")]; + return _.first(phrase.english); + }, + + choices: function() { + return Session.get("choices"); + }, + gotFeedback: function() { + return Session.equals("qState", QSTATE.CONTINUE); + }, +}); + +Template.qMultipleChoicesMultipleAnswers.events({ + + "click .choice": function(ev) { + + // Select a choice + var choices = Session.get("choices"); + var expectedAnswers = Session.get("expectedAnswers"); + var clickWord = ev.target.id; + + var clickChoice = _.findWhere(choices, {displayedWord: clickWord}); + + clickChoice.checked = !clickChoice.checked; + if(_.indexOf(expectedAnswers,clickChoice.displayedWord) !== -1) { + clickChoice.isCorrect = true; + } + + // Update session + Session.set("choices", choices); + + // Allow for answering + enableSubmitButton(); + } + +}); + +// ---------------------- +// Public functions +// ---------------------- + +setupMultipleChoicesMultipleAnswers = function(lesson) { + pickChoices(lesson); +} + +aMultipleChoicesMultipleAnswers = function(phrase) { + var checkedAnswers = _.where(Session.get("choices"), { + checked: true + }); + checkedAnswers = _.map(checkedAnswers, function(answer) { + return answer.displayedWord; + }); + + var expectedAnswers = Session.get("expectedAnswers"); + + + var unionCheckExpect = _.union(checkedAnswers, expectedAnswers); + + var answerCorrect = false; + var feedback = "no feedback"; + if(expectedAnswers.length === checkedAnswers.length && + unionCheckExpect.length === checkedAnswers.length) { + // user checked only the correct answers + answerCorrect = true + } else { + feedback = s.toSentence(expectedAnswers); + } + + Session.set("feedback", feedback); + + return answerCorrect? CHALLENGE_PROGRESS_CORRECT : + CHALLENGE_PROGRESS_WRONG; +} + +// ---------------------- +// Private functions +// ---------------------- + +function pickChoices(lesson) { + var phraseIndex = Session.get("phraseIndex"); + var phrase = lesson.phrases[phraseIndex]; + var vnPhrases = phrase.vietnamese; + var choices = lesson.phrases; + var expectedAnswers = phrase.vietnamese; + + + vnPhrases = _.map(vnPhrases, function(phrase) { + return { + vietnamese: phrase + }; + }); + // Reject other choices that are not MCT(P) or contain vn phrases of current question + choices = _.filter(choices, function(choice) { + var choiceNotMCT = choice.qType === QTYPE.MULTIPLE_CHOICES_TRANSLATION; + var choiceNotMCTP = choice.qType === QTYPE.MULTIPLE_CHOICES_TRANSLATION_PIC; + var choiceVnNotInSolutions = !_.contains(expectedAnswers, choice.vietnamese); + return (choiceNotMCT || choiceNotMCTP) && choiceVnNotInSolutions; + }); + + // Minus one choice since we will re-add the correct choice later + var choices = _.sample(choices, CHOICE_NUM - expectedAnswers.length); + + choices = choices.concat(vnPhrases); + // Randomize! + choices = _.shuffle(choices); + + // Add hotkey and other properties + _.map(choices, function(choice) { + return _.extend(choice, { + checked: false, + isCorrect: false, + displayedWord: choice.vietnamese, + }); + }); + + Session.set("choices", choices); + Session.set("expectedAnswers", expectedAnswers); + +} diff --git a/client/template/challenge/types/q_word_pairing.js b/client/template/challenge/types/q_word_pairing.js index 7a936c8..372700c 100644 --- a/client/template/challenge/types/q_word_pairing.js +++ b/client/template/challenge/types/q_word_pairing.js @@ -47,7 +47,6 @@ function compareChoices(prevChoiceWord, curChoiceWord) { if (prevChoice.matchingWord === curChoice.displayedWord) { curChoice.checked = true; Session.set("numMatches", Session.get("numMatches") + 1); - // TODO: disable both buttons if(Session.get("numMatches") === CHOICE_NUM) { enableSubmitButton(); } From 261db9a701879bcd0fd75dfb372b557ca5d90fa2 Mon Sep 17 00:00:00 2001 From: vgx32 Date: Tue, 28 Jul 2015 20:57:51 -0700 Subject: [PATCH 02/11] added fill-in the blank template --- client/template/challenge/challenge.js | 9 +- client/template/challenge/question_area.js | 2 + .../challenge/types/q_fill_in_blank.html | 12 ++ .../challenge/types/q_fill_in_blank.js | 122 ++++++++++++++++++ 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 client/template/challenge/types/q_fill_in_blank.html create mode 100644 client/template/challenge/types/q_fill_in_blank.js diff --git a/client/template/challenge/challenge.js b/client/template/challenge/challenge.js index f67c569..0e8c8b8 100644 --- a/client/template/challenge/challenge.js +++ b/client/template/challenge/challenge.js @@ -58,6 +58,8 @@ Template.challenge.helpers({ return "Match the pairs"; case QTYPE.REARRANGE: return "Arrange this phrase in Vietnamese"; + case QTYPE.FILL_IN_BLANK: + return "Which word goes into the blank?" } }, completed: function() { @@ -193,7 +195,9 @@ function setupQuestion(lesson, phraseIndex) { case QTYPE.REARRANGE: setupRearrange(lesson); break; - + case QTYPE.FILL_IN_BLANK: + setupFillInBlank(lesson); + break; default: return; } @@ -282,6 +286,9 @@ answer = function(lesson) { case QTYPE.REARRANGE: answerScore = aRearrange(); break; + case QTYPE.FILL_IN_BLANK: + answerScore = aFillInBlank(); + break; } computeProgress(answerScore); diff --git a/client/template/challenge/question_area.js b/client/template/challenge/question_area.js index d24bc5f..0d57a55 100644 --- a/client/template/challenge/question_area.js +++ b/client/template/challenge/question_area.js @@ -17,6 +17,8 @@ Template.questionArea.helpers({ return "qWordPairing"; case QTYPE.REARRANGE: return "qRearrange"; + case QTYPE.FILL_IN_BLANK: + return "qFillInBlank"; default: return; } diff --git a/client/template/challenge/types/q_fill_in_blank.html b/client/template/challenge/types/q_fill_in_blank.html new file mode 100644 index 0000000..3ff1d8a --- /dev/null +++ b/client/template/challenge/types/q_fill_in_blank.html @@ -0,0 +1,12 @@ + diff --git a/client/template/challenge/types/q_fill_in_blank.js b/client/template/challenge/types/q_fill_in_blank.js new file mode 100644 index 0000000..2c9b03f --- /dev/null +++ b/client/template/challenge/types/q_fill_in_blank.js @@ -0,0 +1,122 @@ +const CHOICE_NUM = 4; + +Template.qFillInBlank.helpers({ + + phrase: function() { + var phrase = this.phrases[Session.get("phraseIndex")]; + var prompt = phrase.vnPhraseLower + " _____ " + phrase.vnPhraseUpper; + return prompt; + }, + + choices: function() { + return Session.get("choices"); + }, + gotFeedback: function() { + return Session.equals("qState", QSTATE.CONTINUE); + }, +}); + +Template.qFillInBlank.events({ + + "click .choice": function(ev) { + + // Select a choice + var choices = Session.get("choices"); + var expectedAnswers = Session.get("expectedAnswers"); + var clickWord = ev.target.id; + + var clickChoice = _.findWhere(choices, {displayedWord: clickWord}); + var checkedAnswers = _.where(Session.get("choices"), { + checked: true + }); + + + if(checkedAnswers.length > 0 && true) { + if (clickChoice.displayedWord === checkedAnswers[0].displayedWord) { + clickChoice.isCorrect = false; + clickChoice.checked = false; + } + + + } else { + + clickChoice.checked = !clickChoice.checked; + if(_.indexOf(expectedAnswers, clickChoice.displayedWord) !== -1) { + clickChoice.isCorrect = true; + } + } + // Update session + Session.set("choices", choices); + + // Allow for answering + enableSubmitButton(); + } + +}); + +// ---------------------- +// Public functions +// ---------------------- + +setupFillInBlank = function(lesson) { + pickChoices(lesson); +} + +aFillInBlank = function(phrase) { + var checkedAnswers = _.where(Session.get("choices"), { + checked: true + }); + checkedAnswers = _.map(checkedAnswers, function(answer) { + return answer.displayedWord; + }); + + var expectedAnswers = Session.get("expectedAnswers"); + + + var unionCheckExpect = _.union(checkedAnswers, expectedAnswers); + + var answerCorrect = false; + var feedback = "no feedback"; + if(expectedAnswers.length === checkedAnswers.length && + unionCheckExpect.length === checkedAnswers.length) { + // user checked only the correct answers + answerCorrect = true + } else { + feedback = s.toSentence(expectedAnswers); + } + + Session.set("feedback", feedback); + + return answerCorrect? CHALLENGE_PROGRESS_CORRECT : + CHALLENGE_PROGRESS_WRONG; +} + +// ---------------------- +// Private functions +// ---------------------- + +function pickChoices(lesson) { + var phraseIndex = Session.get("phraseIndex"); + var phrase = lesson.phrases[phraseIndex]; + var choices = phrase.otherChoices; + choices.push(phrase.answer); + + + choices = _.map(choices, function(choice) { + return { + checked: false, + isCorrect: false, + displayedWord: choice, + }; + }); + + + + choices = _.shuffle(choices); + + var expectedAnswers = new Array(); + expectedAnswers.push(phrase.answer); + Session.set("choices", choices); + Session.set("expectedAnswers", expectedAnswers); + +} From 98d84d5f870b80bd59a3167c6591345a117f7d72 Mon Sep 17 00:00:00 2001 From: vgx32 Date: Tue, 28 Jul 2015 20:58:34 -0700 Subject: [PATCH 03/11] added server-side FIB data type --- server/lessons/BASIC_1.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/server/lessons/BASIC_1.js b/server/lessons/BASIC_1.js index e45cd80..5ec96a7 100644 --- a/server/lessons/BASIC_1.js +++ b/server/lessons/BASIC_1.js @@ -5,9 +5,20 @@ BASIC_1 = { content: ["basic1"], phrases: [ { + qType: QTYPE.FILL_IN_BLANK, + vnPhraseLower: "Tôi", + vnPhraseUpper: "phụ nữ.", + answer: "là", + otherChoices: ["la", "lá", "lã"] + + }, { + qType: QTYPE.MULTIPLE_CHOICES_MULTIPLE_ANSWERS, + vietnamese: ["Đàn ông", "Đàn ông2!!!"], + english: ["Man"] + }, { qType: QTYPE.MULTIPLE_CHOICES_TRANSLATION_PIC, - image: "/img/lessons/man.jpg", vietnamese: "Đàn ông", + image: "/img/lessons/man.jpg", english: ["Man"] }, { qType: QTYPE.MULTIPLE_CHOICES_TRANSLATION_PIC, From 85b7157a1bd6ffb429c81daf6b66130ba6b80a46 Mon Sep 17 00:00:00 2001 From: vgx32 Date: Sun, 2 Aug 2015 22:24:19 -0700 Subject: [PATCH 04/11] cleaned MC logic in FIB; only allows one choice -- clicking on other choices will decheck previously checked --- .../challenge/types/q_fill_in_blank.js | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/client/template/challenge/types/q_fill_in_blank.js b/client/template/challenge/types/q_fill_in_blank.js index 2c9b03f..ec02ad5 100644 --- a/client/template/challenge/types/q_fill_in_blank.js +++ b/client/template/challenge/types/q_fill_in_blank.js @@ -26,25 +26,22 @@ Template.qFillInBlank.events({ var clickWord = ev.target.id; var clickChoice = _.findWhere(choices, {displayedWord: clickWord}); - var checkedAnswers = _.where(Session.get("choices"), { + var checkedChoices = _.where(choices, { checked: true }); + var ONLY_ALLOW_ONE_ANSWER = true; // placeholder for generic multiple choice template work - if(checkedAnswers.length > 0 && true) { - if (clickChoice.displayedWord === checkedAnswers[0].displayedWord) { - clickChoice.isCorrect = false; - clickChoice.checked = false; + if(checkedChoices.length && ONLY_ALLOW_ONE_ANSWER) { + // if clicked on a checked choice, then uncheck it (outside of this block) + // otherwise uncheck the previously checked choice + if(clickChoice.displayedWord !== checkedChoices[0].displayedWord) { + checkedChoices[0].checked = false; } - - } else { - - clickChoice.checked = !clickChoice.checked; - if(_.indexOf(expectedAnswers, clickChoice.displayedWord) !== -1) { - clickChoice.isCorrect = true; - } } + clickChoice.checked = !clickChoice.checked; + // Update session Session.set("choices", choices); @@ -63,22 +60,22 @@ setupFillInBlank = function(lesson) { } aFillInBlank = function(phrase) { - var checkedAnswers = _.where(Session.get("choices"), { + var checkedChoices = _.where(Session.get("choices"), { checked: true }); - checkedAnswers = _.map(checkedAnswers, function(answer) { + checkedChoices = _.map(checkedChoices, function(answer) { return answer.displayedWord; }); var expectedAnswers = Session.get("expectedAnswers"); - var unionCheckExpect = _.union(checkedAnswers, expectedAnswers); + var unionCheckExpect = _.union(checkedChoices, expectedAnswers); var answerCorrect = false; var feedback = "no feedback"; - if(expectedAnswers.length === checkedAnswers.length && - unionCheckExpect.length === checkedAnswers.length) { + if(expectedAnswers.length === checkedChoices.length && + unionCheckExpect.length === checkedChoices.length) { // user checked only the correct answers answerCorrect = true } else { @@ -98,10 +95,13 @@ aFillInBlank = function(phrase) { function pickChoices(lesson) { var phraseIndex = Session.get("phraseIndex"); var phrase = lesson.phrases[phraseIndex]; - var choices = phrase.otherChoices; - choices.push(phrase.answer); - + var expectedAnswers = new Array(); + expectedAnswers.push(phrase.answer); + + var choices = phrase.wrongChoices; + + choices = _.map(choices, function(choice) { return { checked: false, @@ -110,12 +110,15 @@ function pickChoices(lesson) { }; }); - + // push answer object onto the choices + choices.push( { + checked: false, + isCorrect: true, + displayedWord: phrase.answer, + }); choices = _.shuffle(choices); - var expectedAnswers = new Array(); - expectedAnswers.push(phrase.answer); Session.set("choices", choices); Session.set("expectedAnswers", expectedAnswers); From 063bf43de0c5b65b2c9fc625db1f58277f732fce Mon Sep 17 00:00:00 2001 From: vgx32 Date: Sun, 2 Aug 2015 22:28:35 -0700 Subject: [PATCH 05/11] renamed otherChoices to wrongChoices for clarity --- server/lessons/BASIC_1.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/lessons/BASIC_1.js b/server/lessons/BASIC_1.js index 5ec96a7..13b29bc 100644 --- a/server/lessons/BASIC_1.js +++ b/server/lessons/BASIC_1.js @@ -9,7 +9,7 @@ BASIC_1 = { vnPhraseLower: "Tôi", vnPhraseUpper: "phụ nữ.", answer: "là", - otherChoices: ["la", "lá", "lã"] + wrongChoices: ["la", "lá", "lã"] }, { qType: QTYPE.MULTIPLE_CHOICES_MULTIPLE_ANSWERS, From b1562badfdf8deaaec4c52548c4c8c83a9880dd7 Mon Sep 17 00:00:00 2001 From: vgx32 Date: Sun, 2 Aug 2015 23:04:23 -0700 Subject: [PATCH 06/11] RWW initial checking + setup code (WIP) --- client/template/challenge/challenge.js | 9 ++ client/template/challenge/question_area.js | 2 + .../challenge/types/q_replace_wrong_word.html | 12 ++ .../challenge/types/q_replace_wrong_word.js | 131 ++++++++++++++++++ server/lessons/BASIC_1.js | 2 + 5 files changed, 156 insertions(+) create mode 100644 client/template/challenge/types/q_replace_wrong_word.html create mode 100644 client/template/challenge/types/q_replace_wrong_word.js diff --git a/client/template/challenge/challenge.js b/client/template/challenge/challenge.js index bfaed49..3ab8374 100644 --- a/client/template/challenge/challenge.js +++ b/client/template/challenge/challenge.js @@ -53,6 +53,9 @@ Template.challenge.helpers({ return "Arrange this phrase in Vietnamese"; case QTYPE.FILL_IN_BLANK: return "Which word goes into the blank?" + case QTYPE.REPLACE_WRONG_WORD: + return "One of the words in the phrase is wrong. Choose which and its replacement." + } }, completed: function() { @@ -191,6 +194,9 @@ function setupQuestion(lesson, phraseIndex) { case QTYPE.FILL_IN_BLANK: setupFillInBlank(lesson); break; + case QTYPE.REPLACE_WRONG_WORD: + setupReplaceWrongWord(lesson); + break; default: return; } @@ -282,6 +288,9 @@ answer = function(lesson) { case QTYPE.FILL_IN_BLANK: answerScore = aFillInBlank(); break; + case QTYPE.REPLACE_WRONG_WORD: + answerScore = aReplaceWrongWord(); + break; } computeProgress(answerScore); diff --git a/client/template/challenge/question_area.js b/client/template/challenge/question_area.js index 0d57a55..7d28435 100644 --- a/client/template/challenge/question_area.js +++ b/client/template/challenge/question_area.js @@ -19,6 +19,8 @@ Template.questionArea.helpers({ return "qRearrange"; case QTYPE.FILL_IN_BLANK: return "qFillInBlank"; + case QTYPE.REPLACE_WRONG_WORD: + return "qReplaceWrongWord"; default: return; } diff --git a/client/template/challenge/types/q_replace_wrong_word.html b/client/template/challenge/types/q_replace_wrong_word.html new file mode 100644 index 0000000..245e525 --- /dev/null +++ b/client/template/challenge/types/q_replace_wrong_word.html @@ -0,0 +1,12 @@ + diff --git a/client/template/challenge/types/q_replace_wrong_word.js b/client/template/challenge/types/q_replace_wrong_word.js new file mode 100644 index 0000000..242e6ca --- /dev/null +++ b/client/template/challenge/types/q_replace_wrong_word.js @@ -0,0 +1,131 @@ +const CHOICE_NUM = 4; + +Template.qReplaceWrongWord.helpers({ + + phrase: function() { + var phrase = this.phrases[Session.get("phraseIndex")]; + var prompt = phrase.vnPhraseLower + " !!!!! " + phrase.vnPhraseUpper; + return prompt; + }, + + choices: function() { + return Session.get("choices"); + }, + gotFeedback: function() { + return Session.equals("qState", QSTATE.CONTINUE); + }, +}); + +Template.qReplaceWrongWord.events({ + + "click .choice": function(ev) { + + // Select a choice + var choices = Session.get("choices"); + var expectedAnswers = Session.get("expectedAnswers"); + var clickWord = ev.target.id; + + var clickChoice = _.findWhere(choices, {displayedWord: clickWord}); + var checkedChoices = _.where(choices, { + checked: true + }); + + var ONLY_ALLOW_ONE_ANSWER = true; // placeholder for generic multiple choice template work + + if(checkedChoices.length && ONLY_ALLOW_ONE_ANSWER) { + // if clicked on a checked choice, then uncheck it (outside of this block) + // otherwise uncheck the previously checked choice + if(clickChoice.displayedWord !== checkedChoices[0].displayedWord) { + checkedChoices[0].checked = false; + } + + } + clickChoice.checked = !clickChoice.checked; + + // Update session + Session.set("choices", choices); + + // Allow for answering + enableSubmitButton(); + } + +}); + +// ---------------------- +// Public functions +// ---------------------- + +setupReplaceWrongWord = function(lesson) { + pickChoices(lesson); +} + +aReplaceWrongWord = function(phrase) { + var checkedChoices = _.where(Session.get("choices"), { + checked: true + }); + checkedChoices = _.map(checkedChoices, function(answer) { + return answer.displayedWord; + }); + + var expectedAnswers = Session.get("expectedAnswers"); + + + var unionCheckExpect = _.union(checkedChoices, expectedAnswers); + + var answerCorrect = false; + var feedback = "no feedback"; + if(expectedAnswers.length === checkedChoices.length && + unionCheckExpect.length === checkedChoices.length) { + // user checked only the correct answers + answerCorrect = true + } else { + feedback = s.toSentence(expectedAnswers); + } + + Session.set("feedback", feedback); + + return answerCorrect? CHALLENGE_PROGRESS_CORRECT : + CHALLENGE_PROGRESS_WRONG; +} + +// ---------------------- +// Private functions +// ---------------------- + +function pickChoices(lesson) { + var phraseIndex = Session.get("phraseIndex"); + var phrase = lesson.phrases[phraseIndex]; + + var expectedAnswers = new Array(); + // expectedAnswers.push(phrase.answer); + + // base this RWW question on random existing FIB question + var possibleQuestions = _.where(lesson.phrases, {qType:QTYPE.FILL_IN_BLANK}); + + var question = _.sample(possibleQuestions, 1)[0]; + console.log(question); + + + var choices = new Array(); + + // choices = _.map(choices, function(choice) { + // return { + // checked: false, + // isCorrect: false, + // displayedWord: choice, + // }; + // }); + + // // push answer object onto the choices + // choices.push( { + // checked: false, + // isCorrect: true, + // displayedWord: phrase.answer, + // }); + + // choices = _.shuffle(choices); + + Session.set("choices", choices); + Session.set("expectedAnswers", expectedAnswers); + +} diff --git a/server/lessons/BASIC_1.js b/server/lessons/BASIC_1.js index 13b29bc..dca415a 100644 --- a/server/lessons/BASIC_1.js +++ b/server/lessons/BASIC_1.js @@ -5,6 +5,8 @@ BASIC_1 = { content: ["basic1"], phrases: [ { + qType: QTYPE.REPLACE_WRONG_WORD, + }, { qType: QTYPE.FILL_IN_BLANK, vnPhraseLower: "Tôi", vnPhraseUpper: "phụ nữ.", From 40da362819b706ffd30f6a6366b392ad5102a8c4 Mon Sep 17 00:00:00 2001 From: sa Date: Sun, 9 Aug 2015 22:57:28 -0700 Subject: [PATCH 07/11] RWW work -- added logic to create and (poorly styled) display a phrase with an incorrect wor --- .../challenge/types/q_replace_wrong_word.html | 8 ++- .../challenge/types/q_replace_wrong_word.js | 51 ++++++++++--------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/client/template/challenge/types/q_replace_wrong_word.html b/client/template/challenge/types/q_replace_wrong_word.html index 245e525..b8315d7 100644 --- a/client/template/challenge/types/q_replace_wrong_word.html +++ b/client/template/challenge/types/q_replace_wrong_word.html @@ -1,5 +1,9 @@ diff --git a/client/template/challenge/types/q_replace_wrong_word.js b/client/template/challenge/types/q_replace_wrong_word.js index 242e6ca..78c6ad7 100644 --- a/client/template/challenge/types/q_replace_wrong_word.js +++ b/client/template/challenge/types/q_replace_wrong_word.js @@ -2,10 +2,8 @@ const CHOICE_NUM = 4; Template.qReplaceWrongWord.helpers({ - phrase: function() { - var phrase = this.phrases[Session.get("phraseIndex")]; - var prompt = phrase.vnPhraseLower + " !!!!! " + phrase.vnPhraseUpper; - return prompt; + phraseWords: function() { + return Session.get("phraseWords"); }, choices: function() { @@ -101,31 +99,36 @@ function pickChoices(lesson) { // base this RWW question on random existing FIB question var possibleQuestions = _.where(lesson.phrases, {qType:QTYPE.FILL_IN_BLANK}); + var question = _.sample(possibleQuestions, 1).pop(); + // console.log(question); + + function toWordObj (choice) { + return { + word: choice, + isWrong: false + }; + }; + // select an incorrect word to put into the phrase + var wrongWord = { + word : _.sample(question.wrongChoices, 1).pop(), + isWrong : true + }; + + // add attributes to phrasewords + console.log(question.vnPhraseLower); + var phraseWords = s.words(question.vnPhraseLower); + console.log(phraseWords); - var question = _.sample(possibleQuestions, 1)[0]; - console.log(question); - + phraseWords = _.map(phraseWords, toWordObj); + + phraseWords.push(wrongWord); + phraseWords =phraseWords.concat(_.map(s.words(question.vnPhraseUpper), toWordObj)); + var choices = new Array(); - - // choices = _.map(choices, function(choice) { - // return { - // checked: false, - // isCorrect: false, - // displayedWord: choice, - // }; - // }); - - // // push answer object onto the choices - // choices.push( { - // checked: false, - // isCorrect: true, - // displayedWord: phrase.answer, - // }); - - // choices = _.shuffle(choices); Session.set("choices", choices); Session.set("expectedAnswers", expectedAnswers); + Session.set("phraseWords", phraseWords); } From e622bcda15cad83ce8814a9716b5346e2447ffd2 Mon Sep 17 00:00:00 2001 From: sa Date: Tue, 11 Aug 2015 20:53:50 -0700 Subject: [PATCH 08/11] RWW logic is complete; still todo: make it look pretty --- client/template/challenge/challenge.js | 2 +- .../challenge/types/q_replace_wrong_word.html | 35 ++-- .../challenge/types/q_replace_wrong_word.js | 152 ++++++++++++++---- server/lessons/BASIC_1.js | 4 +- 4 files changed, 148 insertions(+), 45 deletions(-) diff --git a/client/template/challenge/challenge.js b/client/template/challenge/challenge.js index 802b054..536a2bc 100644 --- a/client/template/challenge/challenge.js +++ b/client/template/challenge/challenge.js @@ -54,7 +54,7 @@ Template.challenge.helpers({ case QTYPE.FILL_IN_BLANK: return "Which word goes into the blank?" case QTYPE.REPLACE_WRONG_WORD: - return "One of the words in the phrase is wrong. Choose which and its replacement." + return "One of the words in the Vietnamese phrase is wrong. Choose which and its replacement." } }, diff --git a/client/template/challenge/types/q_replace_wrong_word.html b/client/template/challenge/types/q_replace_wrong_word.html index b8315d7..6861eda 100644 --- a/client/template/challenge/types/q_replace_wrong_word.html +++ b/client/template/challenge/types/q_replace_wrong_word.html @@ -1,16 +1,25 @@ diff --git a/client/template/challenge/types/q_replace_wrong_word.js b/client/template/challenge/types/q_replace_wrong_word.js index 78c6ad7..4c55c02 100644 --- a/client/template/challenge/types/q_replace_wrong_word.js +++ b/client/template/challenge/types/q_replace_wrong_word.js @@ -12,10 +12,22 @@ Template.qReplaceWrongWord.helpers({ gotFeedback: function() { return Session.equals("qState", QSTATE.CONTINUE); }, + englishTranslation: function() { + return Session.get("englishTranslation"); + }, + replaceEnabled: function() { // toggles clickability of words in phrase + return getReplaceEnabled(); + }, + showMultipleChoice: function() { + return !getReplaceEnabled(); + }, + }); + Template.qReplaceWrongWord.events({ +// @TODO -- generic MC template base "click .choice": function(ev) { // Select a choice @@ -45,6 +57,26 @@ Template.qReplaceWrongWord.events({ // Allow for answering enableSubmitButton(); + }, + + + "click .phraseWord": function (ev) { + var phraseWords = Session.get("phraseWords"); + + var clickWord = ev.target.textContent; + var selectedWord = _.findWhere(phraseWords, {word: clickWord}); + + if (selectedWord.isWrong) { // user selected the word that needs to be replaced + // show multiple choice selection + Session.set("replaceEnabled", false); + } else { // user selected a word that doesn't need to be replaced + + if(!Session.get("pointDeduction")) { // halve possible score for question + Session.set("pointDeduction", CHALLENGE_PROGRESS_CORRECT/2); + } + // @TODO -- see the console log. + console.log("TODO: add prompt/animation for wrong choice"); + } } }); @@ -58,6 +90,24 @@ setupReplaceWrongWord = function(lesson) { } aReplaceWrongWord = function(phrase) { + var answerScore = aMultipleChoice(phrase); + var pointDeduction = Session.get("pointDeduction"); + +// deduct points for not selecting wrong word in phrase on first try + if (answerScore == CHALLENGE_PROGRESS_CORRECT) { + answerScore -= pointDeduction; + + } + return answerScore; +} + +// ---------------------- +// Private functions +// ---------------------- + +// @TODO -- generic MC template base +// check if multiple choice answer is correct +function aMultipleChoice(phrase) { var checkedChoices = _.where(Session.get("choices"), { checked: true }); @@ -67,7 +117,7 @@ aReplaceWrongWord = function(phrase) { var expectedAnswers = Session.get("expectedAnswers"); - +// @TODO -- look into using isCorrect fields to make below more readable var unionCheckExpect = _.union(checkedChoices, expectedAnswers); var answerCorrect = false; @@ -86,49 +136,93 @@ aReplaceWrongWord = function(phrase) { CHALLENGE_PROGRESS_WRONG; } -// ---------------------- -// Private functions -// ---------------------- - function pickChoices(lesson) { var phraseIndex = Session.get("phraseIndex"); var phrase = lesson.phrases[phraseIndex]; - var expectedAnswers = new Array(); - // expectedAnswers.push(phrase.answer); // base this RWW question on random existing FIB question var possibleQuestions = _.where(lesson.phrases, {qType:QTYPE.FILL_IN_BLANK}); var question = _.sample(possibleQuestions, 1).pop(); - // console.log(question); - function toWordObj (choice) { - return { - word: choice, - isWrong: false - }; +// setup question data + setupPhraseWords(question); + + var wrongAnswers = question.wrongChoices; + var rightAnswers = new Array(); + rightAnswers.push(question.answer); + + setupMultipleChoice(wrongAnswers, rightAnswers); + + + Session.set("replaceEnabled", true); + Session.set("pointDeduction", 0); // used for wrong selection of incorrect word +} + +// helper +function toChoiceObj (correct, word) { + return { + displayedWord: word, + isCorrect: correct, + checked: false }; - // select an incorrect word to put into the phrase - var wrongWord = { - word : _.sample(question.wrongChoices, 1).pop(), - isWrong : true - }; +} - // add attributes to phrasewords - console.log(question.vnPhraseLower); - var phraseWords = s.words(question.vnPhraseLower); - console.log(phraseWords); +// @TODO -- generic MC template base +// both inputs are lists of words/phrases +function setupMultipleChoice(wrongAnswers, rightAnswers) { + // + Session.set("expectedAnswers", rightAnswers); + + // setup choice object creation functions + var toRightChoice = _.partial(toChoiceObj, true); + var toWrongChoice = _.partial(toChoiceObj, false); - phraseWords = _.map(phraseWords, toWordObj); +// setup multiple choices: + wrongAnswers = _.sample(wrongAnswers, CHOICE_NUM - rightAnswers.length); - phraseWords.push(wrongWord); - phraseWords =phraseWords.concat(_.map(s.words(question.vnPhraseUpper), toWordObj)); + rightAnswers = _.map(rightAnswers, toRightChoice); + wrongAnswers = _.map(wrongAnswers, toWrongChoice); + + + var choices = wrongAnswers.concat(rightAnswers); + choices = _.shuffle(choices); + + Session.set("choices", choices); +} + +// helper +function toWordObj (wrongVal, choice) { + return { + word: choice, + isWrong: wrongVal + }; +}; + +// used to create displayed phraseWords objects +function setupPhraseWords(question) { + + var toWrongWordObj = _.partial(toWordObj, true); + var toCorrectWordObj = _.partial(toWordObj, false); - var choices = new Array(); + var wrongWord = toWrongWordObj( _.sample(question.wrongChoices, 1).pop()); + + // add attributes to phrasewords + var phraseWords = s.words(question.vnPhraseLower); - Session.set("choices", choices); - Session.set("expectedAnswers", expectedAnswers); + phraseWords = _.map(phraseWords, toCorrectWordObj); + + // put all word objects of phrase into a single array + phraseWords.push(wrongWord); + phraseWords =phraseWords.concat(_.map(s.words(question.vnPhraseUpper), toCorrectWordObj)); Session.set("phraseWords", phraseWords); - + + var englishTranslation = question.english[0]; + Session.set("englishTranslation", englishTranslation); } + +// template helper helper +function getReplaceEnabled(){ + return Session.get("replaceEnabled"); +} \ No newline at end of file diff --git a/server/lessons/BASIC_1.js b/server/lessons/BASIC_1.js index 9754432..af8276b 100644 --- a/server/lessons/BASIC_1.js +++ b/server/lessons/BASIC_1.js @@ -13,8 +13,8 @@ BASIC_1 = { vnPhraseLower: "Tôi", vnPhraseUpper: "phụ nữ.", answer: "là", - wrongChoices: ["la", "lá", "lã"] - + wrongChoices: ["la", "lá", "lã"], + english: ["I am a woman", "I'm a woman"] }, { qType: QTYPE.MULTIPLE_CHOICES_MULTIPLE_ANSWERS, vietnamese: ["Đàn ông", "Đàn ông2!!!"], From da74cc7ed3d609e0360cff3f21fe545b95649667 Mon Sep 17 00:00:00 2001 From: sa Date: Mon, 31 Aug 2015 17:29:54 -0700 Subject: [PATCH 09/11] initial commit of wrong word shake animation --- client/stylesheets/animations.scss | 10 +++ .../utilities/_animation_key_frames.scss | 62 +++++++++++++++++++ .../challenge/types/q_replace_wrong_word.html | 2 +- .../challenge/types/q_replace_wrong_word.js | 8 ++- 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/client/stylesheets/animations.scss b/client/stylesheets/animations.scss index e614a3b..c873fea 100644 --- a/client/stylesheets/animations.scss +++ b/client/stylesheets/animations.scss @@ -1,6 +1,16 @@ +@import "utilities/animation_key_frames"; + .animate-flicker { -webkit-animation: flickerAnimation 3s infinite; -moz-animation: flickerAnimation 3s infinite; -o-animation: flickerAnimation 3s infinite; animation: flickerAnimation 3s infinite; } + +.animate-shake-wrong { + color : red; + animation: shakeWrongAnimation 0.4s ease 0s 2 alternate; + -o-animation: shakeWrongAnimation 0.4s ease 0s 2 alternate; + -moz-animation: shakeWrongAnimation 0.4s ease 0s 2 alternate; + -webkit-animation: shakeWrongAnimation 0.4s ease 0s 2 alternate; +} \ No newline at end of file diff --git a/client/stylesheets/utilities/_animation_key_frames.scss b/client/stylesheets/utilities/_animation_key_frames.scss index 817dc02..1252b78 100644 --- a/client/stylesheets/utilities/_animation_key_frames.scss +++ b/client/stylesheets/utilities/_animation_key_frames.scss @@ -1,5 +1,65 @@ /* Animations */ +@keyframes shakeWrongAnimation { + 0% { + transform: rotate(0deg); + } + 33% { + transform: rotate(7deg); + } + 66% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-7deg); + } +} + +@-o-keyframes shakeWrongAnimation { + 0% { + transform: rotate(0deg); + } + 33% { + transform: rotate(7deg); + } + 66% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-7deg); + } +} + +@-moz-keyframes shakeWrongAnimation { + 0% { + transform: rotate(0deg); + } + 33% { + transform: rotate(7deg); + } + 66% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-7deg); + } +} + +@-webkit-keyframes shakeWrongAnimation { + 0% { + transform: rotate(0deg); + } + 33% { + transform: rotate(7deg); + } + 66% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-7deg); + } +} + @keyframes flickerAnimation { 0% { opacity: 1; @@ -47,3 +107,5 @@ opacity: 1; } } + + diff --git a/client/template/challenge/types/q_replace_wrong_word.html b/client/template/challenge/types/q_replace_wrong_word.html index 6861eda..735562d 100644 --- a/client/template/challenge/types/q_replace_wrong_word.html +++ b/client/template/challenge/types/q_replace_wrong_word.html @@ -5,7 +5,7 @@
    {{#each phraseWords}}
  • -
    {{word}}
    +
    {{word}}
  • {{/each}} diff --git a/client/template/challenge/types/q_replace_wrong_word.js b/client/template/challenge/types/q_replace_wrong_word.js index 4c55c02..6a6f6ec 100644 --- a/client/template/challenge/types/q_replace_wrong_word.js +++ b/client/template/challenge/types/q_replace_wrong_word.js @@ -21,7 +21,9 @@ Template.qReplaceWrongWord.helpers({ showMultipleChoice: function() { return !getReplaceEnabled(); }, - + animationEnabled: function (){ + return Session.get("animationEnabled"); + } }); @@ -76,6 +78,8 @@ Template.qReplaceWrongWord.events({ } // @TODO -- see the console log. console.log("TODO: add prompt/animation for wrong choice"); + Session.set("animationEnabled", true); + } } @@ -156,6 +160,8 @@ function pickChoices(lesson) { Session.set("replaceEnabled", true); + Session.set("animationEnabled", false); + Session.set("pointDeduction", 0); // used for wrong selection of incorrect word } From 330d0217c5f952bbfa719fca6f739cfdf17720ec Mon Sep 17 00:00:00 2001 From: sa Date: Mon, 31 Aug 2015 18:19:40 -0700 Subject: [PATCH 10/11] implemented wrong word selected animation --- .../challenge/types/q_replace_wrong_word.js | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/client/template/challenge/types/q_replace_wrong_word.js b/client/template/challenge/types/q_replace_wrong_word.js index 6a6f6ec..eeb8418 100644 --- a/client/template/challenge/types/q_replace_wrong_word.js +++ b/client/template/challenge/types/q_replace_wrong_word.js @@ -21,9 +21,9 @@ Template.qReplaceWrongWord.helpers({ showMultipleChoice: function() { return !getReplaceEnabled(); }, - animationEnabled: function (){ - return Session.get("animationEnabled"); - } + // animationEnabled: function (){ + // return Session.get("animationEnabled"); + // } }); @@ -76,15 +76,22 @@ Template.qReplaceWrongWord.events({ if(!Session.get("pointDeduction")) { // halve possible score for question Session.set("pointDeduction", CHALLENGE_PROGRESS_CORRECT/2); } - // @TODO -- see the console log. - console.log("TODO: add prompt/animation for wrong choice"); - Session.set("animationEnabled", true); + selectedWord.animationEnabled = true; + + Session.set("phraseWords", phraseWords); } - } + }, + + "animationend .phraseWord" : disableAnimation, + "oAnimationEnd .phraseWord" : disableAnimation, + "webkitAnimationEnd .phraseWord" : disableAnimation, + + }); + // ---------------------- // Public functions // ---------------------- @@ -109,6 +116,18 @@ aReplaceWrongWord = function(phrase) { // Private functions // ---------------------- +function disableAnimation(ev) { + var phraseWords = Session.get("phraseWords"); + + var clickWord = ev.target.textContent; + var selectedWord = _.findWhere(phraseWords, {word: clickWord}); + + selectedWord.animationEnabled = false; + + Session.set("phraseWords", phraseWords); + +} + // @TODO -- generic MC template base // check if multiple choice answer is correct function aMultipleChoice(phrase) { @@ -160,7 +179,7 @@ function pickChoices(lesson) { Session.set("replaceEnabled", true); - Session.set("animationEnabled", false); + // Session.set("animationEnabled", false); Session.set("pointDeduction", 0); // used for wrong selection of incorrect word } @@ -202,7 +221,8 @@ function setupMultipleChoice(wrongAnswers, rightAnswers) { function toWordObj (wrongVal, choice) { return { word: choice, - isWrong: wrongVal + isWrong: wrongVal, + animationEnabled: false }; }; From 3babed9ae7055650eea46233275835b0422b98cb Mon Sep 17 00:00:00 2001 From: sa Date: Mon, 31 Aug 2015 22:01:09 -0700 Subject: [PATCH 11/11] initial commit of RWW style modifications --- client/stylesheets/challenge.scss | 11 +++++++++++ .../challenge/types/q_replace_wrong_word.html | 9 +++++---- .../template/challenge/types/q_replace_wrong_word.js | 11 ++--------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/client/stylesheets/challenge.scss b/client/stylesheets/challenge.scss index 5dbfb96..435d9d3 100644 --- a/client/stylesheets/challenge.scss +++ b/client/stylesheets/challenge.scss @@ -172,6 +172,17 @@ color: #bbbbbb; margin-right: 10px; } + +/* Replace Wrong Word Choices*/ +.phraseWord.phraseWordClickable { + &:hover { + background-color: $very-light-gray; + } +} + +.phraseWord.wrong { + color: $light-green; +} /* Report area */ #feedback-area { diff --git a/client/template/challenge/types/q_replace_wrong_word.html b/client/template/challenge/types/q_replace_wrong_word.html index 735562d..1f32bb1 100644 --- a/client/template/challenge/types/q_replace_wrong_word.html +++ b/client/template/challenge/types/q_replace_wrong_word.html @@ -1,11 +1,13 @@ diff --git a/client/template/challenge/types/q_replace_wrong_word.js b/client/template/challenge/types/q_replace_wrong_word.js index eeb8418..c829424 100644 --- a/client/template/challenge/types/q_replace_wrong_word.js +++ b/client/template/challenge/types/q_replace_wrong_word.js @@ -62,9 +62,8 @@ Template.qReplaceWrongWord.events({ }, - "click .phraseWord": function (ev) { + "click .phraseWordClickable": function (ev) { var phraseWords = Session.get("phraseWords"); - var clickWord = ev.target.textContent; var selectedWord = _.findWhere(phraseWords, {word: clickWord}); @@ -78,17 +77,14 @@ Template.qReplaceWrongWord.events({ } selectedWord.animationEnabled = true; - Session.set("phraseWords", phraseWords); } }, - "animationend .phraseWord" : disableAnimation, "oAnimationEnd .phraseWord" : disableAnimation, "webkitAnimationEnd .phraseWord" : disableAnimation, - - + }); @@ -118,14 +114,12 @@ aReplaceWrongWord = function(phrase) { function disableAnimation(ev) { var phraseWords = Session.get("phraseWords"); - var clickWord = ev.target.textContent; var selectedWord = _.findWhere(phraseWords, {word: clickWord}); selectedWord.animationEnabled = false; Session.set("phraseWords", phraseWords); - } // @TODO -- generic MC template base @@ -163,7 +157,6 @@ function pickChoices(lesson) { var phraseIndex = Session.get("phraseIndex"); var phrase = lesson.phrases[phraseIndex]; - // base this RWW question on random existing FIB question var possibleQuestions = _.where(lesson.phrases, {qType:QTYPE.FILL_IN_BLANK}); var question = _.sample(possibleQuestions, 1).pop();