Skip to content

Commit

Permalink
Choose sequence card type (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
zenby committed Feb 28, 2019
1 parent 4878ca0 commit 7db4317
Show file tree
Hide file tree
Showing 11 changed files with 413 additions and 0 deletions.
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const cardIndex = require('./types/choose_options/index');
const infoIndex = require('./types/info/index');
const chooseOptionsIndex = require('./types/choose_options/index');
const chooseSequenceIndex = require('./types/choose_sequence/index');

module.exports = {
choose_options: {
Expand All @@ -12,4 +13,7 @@ module.exports = {
order_items: {
card: chooseOptionsIndex,
},
choose_sequence: {
card: chooseSequenceIndex,
},
};
Binary file added types/choose_sequence/ choose_sequence.apkg
Binary file not shown.
68 changes: 68 additions & 0 deletions types/choose_sequence/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!-- @format -->

# Choose sequence

This card shows some description text and number of options. Some of them should be chosen for the correct answer, others - not but it should be done in the right order.

## Logic

On rendering card:

1. All the options are shuffled to change the order of them on each try;
1. There are displayed proposed options as buttons which may be pushed;
1. If user selects one of the button the active `???` placeholder will be replaced with selected button's text and button will become disabled. The replacing order corresponds the order of `???` in question;
1. If user decides to cancel already selected option he should click (touch) desired placeholder in the card question. Clicked placeholder will be replaced with `???` and corresponding button become enabled.
1. Check button allows checking if the current set is correct. All correct answers are marked with `green` color, incorrect or unselected options will be marked with `red` color. Correct order sequence number will be displayed on the left of the button.

## Example:

```
> Choose correct numbers to get 12 and make the equation correct:
> ??? / 4 + ??? = 12
- [ ] 12
- [ ] 9
- [ ] 7
- [ ] 3
```

Should give a card with shuffled 4 buttons and a single button to check.

<img alt="Idea of choose sequence card type" src="./images/3.png">
<img alt="Idea of choose sequence card type" src="./images/2.png">
<img alt="Idea of choose sequence card type" src="./images/1.png">

## Example card

- The amount of placeholders `???` should be less than amount of proposed answers.
- The format of the card considers first `n` options as the correct sequence answer, where `n` - is the amount of `???` placeholders in the card question. For the card example below correct sequence of the answers will be `12` then `9`.
- It might be helpful to know that card `question` and `answers` should be described in html format. Every usage of `<code>` and `<pre>` tag at these fields will display inline and multiline code fragment respectively. Multiline code fragment saves formatting - tabs and new line symbols.

```javascript
{
type: 'choose_sequence',
lang: 'en',
tags: ['mathematics'],
card: {
question: 'Choose correct numbers to get <code>12</code> and make the equations correct:<br/>\
<pre>??? / 4 + ??? = 12</pre>',
answers: [
{
text: '12'
},
{
text: '9'
},
{
text: '3'
},
{
text: '7'
}
],
comment: '<pre>12 / 4 + 9 = 12</pre>'
}
}

```

<img alt="Demo of choose sequence card type" src="./images/4.gif">
48 changes: 48 additions & 0 deletions types/choose_sequence/example.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
type: "choose_sequence",
lang: "en",
tags: ["development", "javascript"],
card: {
question: "Turn the arguments of a method into a array:<br/>\
<pre>function myArray() {\n\
var a = ???;\n\
a = Array.prototype.???.call(a);\n\
return a;\n\
}</pre>",
answers: [
{
text: "arguments"
},
{
text: "slice"
},
{
text: "splice"
},
{
text: "argv"
},
{
text: "args"
},
{
text: "sort"
},
{
text: "toArray"
}
],
comment: "The arguments of a function can be accessed with the arguments keyword, but arguments only returns a pseudo-array.\
Before applying Array methods, arguments needs to be converted to an actual Array, as follows:\
<pre>function sortedArgs() {\n\
// pseudo-array of arguments\n\
var a = arguments\n\
// turn a into a proper Array:\n\
a = Array.prototype.slice.call(a)\n\
// we can now use Array methods:\n\
return a.sort();\n\
}</pre>\
Example of use:\
<code>sortedArgs(3,1,2) // [1,2,3]</code>"
}
}
Binary file added types/choose_sequence/images/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added types/choose_sequence/images/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added types/choose_sequence/images/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added types/choose_sequence/images/4.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
232 changes: 232 additions & 0 deletions types/choose_sequence/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
const getUniqueId = require('../../utils/getUniqueId');

module.exports = ({ card, tags }) => {
const cardId = getUniqueId(card.question);
const cardGuessesId = `${cardId}guesses`;

const front = `
<div id="${cardId}_wrapper">
<div id="question"></div>
<div class="questions-wrapper"></div>
</div>
<button id="${cardId}_checkBtn">Check</button>
<script>
var card = ${JSON.stringify(card)};
var emptyGuessMark = '???';
var emptyGuessMarkRegexp = /[?]{3}/g;
if (!window.memoryCards) {
window.memoryCards = {};
}
var question = document.querySelector('#question');
question.innerHTML = window.memoryCards['${cardGuessesId}'] || card.question.replace(emptyGuessMarkRegexp, getEmptyGuessItem());
var questionContainer = document.querySelector('.questions-wrapper');
var cardAnswers = getCorrectCardAnswers(card.answers);
questionContainer.innerHTML = window.memoryCards['${cardId}'] || cardAnswers
.sort(function () { return Math.random() - 0.5; })
.map(function (el) {
return '<button class="answer_options"'
+ 'data-answer="' + (el.sequenceNumber !== undefined ? el.sequenceNumber : '') + '" '
+ '>'
+ el.text
+ '</button>';
})
.join('<br />');
function getCorrectAnswersAmount(question) {
return ((question || '').match(emptyGuessMarkRegexp) || []).length;
}
function getEmptyGuessItem() {
return '<span class="guess">' + emptyGuessMark + '</span>';
}
function getCorrectCardAnswers(answers) {
var correctAnswersAmount = getCorrectAnswersAmount(card.question);
for (var i = 0; i < correctAnswersAmount; i++) {
answers[i].sequenceNumber = i + 1;
}
return answers;
}
var guesses = question.getElementsByClassName('guess');
var buttons = questionContainer.querySelectorAll('button');
function showCurrentActiveGuess() {
[].forEach.call(guesses, function(el) {
el.classList.remove('active-guess');
});
for (var i = 0; i < guesses.length; i++) {
if (guesses[i].innerHTML === emptyGuessMark) {
guesses[i].classList.add('active-guess');
break;
}
}
}
function checkAnswer() {
var correctAnswers = cardAnswers
.filter(function (el) {
return el.sequenceNumber !== undefined;
})
.sort(function (a, b) {
return a.sequenceNumber - b.sequenceNumber;
});
[].forEach.call(guesses, function (el, index) {
el.classList.add(el.innerHTML === correctAnswers[index].text ? 'correct-guess' : 'incorrect-guess');
});
[].forEach.call(buttons, function (el) {
el.innerHTML += '<span class="answer-mark">' + el.getAttribute('data-answer') + '</span>';
});
contentWrapper.classList.add('checked');
}
var checkBtn = document.querySelector('#${cardId}_checkBtn');
checkBtn.addEventListener('click', checkAnswer);
var contentWrapper = document.querySelector('#${cardId}_wrapper');
contentWrapper.addEventListener('click', function (ev) {
if (ev.target.tagName === 'BUTTON' && question.innerHTML.indexOf(emptyGuessMark) !== -1) {
question.innerHTML = question.innerHTML.replace(emptyGuessMark, ev.target.innerHTML);
ev.target.classList.add('selected-option');
ev.target.disabled = true;
showCurrentActiveGuess();
}
if (ev.target.tagName === 'SPAN' && ev.target.classList.contains('guess')) {
if (question.innerHTML.indexOf(ev.target.outerHTML) !== -1) {
question.innerHTML = question.innerHTML.replace(ev.target.outerHTML, getEmptyGuessItem());
if (ev.target.innerHTML !== emptyGuessMark) {
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].innerHTML === ev.target.innerHTML) {
buttons[i].classList.remove('selected-option');
buttons[i].disabled = false;
break;
}
}
}
showCurrentActiveGuess();
}
}
window.memoryCards['${cardId}'] = questionContainer.innerHTML;
window.memoryCards['${cardGuessesId}'] = question.innerHTML;
});
showCurrentActiveGuess();
window.memoryCards['${cardId}'] = questionContainer.innerHTML;
</script>
<style>
html {
font-size: 150%;
}
@media only screen and (max-device-width: 600px) {
html {
font-size: 70%;
}
}
#${cardId}_wrapper.checked {
border: 1px solid lightgray;
pointer-events: none;
}
#question {
font-size: 1rem;
}
.questions-wrapper {
margin: 0 auto;
width: 200px;
text-align: left;
font-size: 1rem;
}
.comments-wrapper {
text-align: left;
font-size: 1rem;
}
.answer_options {
position: relative;
width: 200px;
margin-bottom: 5px;
border-radius: 5px;
}
.guess {
background-color: rgb(215, 235, 228);
border-radius: 2px;
padding: 2px;
}
.active-guess {
background-color: rgb(175, 218, 233);
}
.correct-guess {
background-color: rgb(132, 233, 162);
}
.incorrect-guess {
background-color: rgb(226, 107, 107);
}
.selected-option {
background-color: rgba(202, 202, 202, 0.2);
border-color: rgba(202, 202, 202, 0.2);
}
.answer-mark {
opacity: 1;
left: 5px;
position: absolute;
color: green;
}
code {
padding: 2px;
background-color: #f8f8f8;
border-radius: 3px;
font-size: 0.85rem;
margin: 0;
border: 1px solid #ccc;
}
div pre {
overflow: overlay;
text-align: left;
white-space: pre-wrap;
tab-size: 4;
padding: 10px;
display: block;
background-color: #f8f8f8;
border-radius: 3px;
border: 1px solid #ccc;
font-size: 0.85rem;
margin: 10px;
}
</style>
`;

const back = `
<div class="comments-wrapper">${card.comment}</div>
<script>
checkAnswer();
delete window.memoryCards['${cardId}'];
delete window.memoryCards['${cardGuessesId}'];
</script>
`;

return {
front,
back,
tags: tags || [],
};
};
Loading

0 comments on commit 7db4317

Please sign in to comment.