├── baseCards.json
├── README.md
├── refactor.js
└── spaced.js
/baseCards.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "side1": "airport",
4 | "side2": "el aeropuerto"
5 | },
6 | {
7 | "side1": "bank",
8 | "side2": "el banco"
9 | },
10 | {
11 | "side1": "beach",
12 | "side2": "la playa"
13 | }
14 | ]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Command Line Spaced Repetition
2 | =================
3 | This is a command line spaced repetition program written in Javascript using Node.js.
4 |
5 | Spaced repetition is a learning technique that incorporates increasing intervals of time between subsequent review of
6 | previously learned material in order to exploit the psychological spacing effect (from Wikipedia).
7 | It is very useful for learning a new language or specific sets of information.
8 |
9 | Basically information you know well is shown less often, information you have trouble is shown more often.
10 |
11 | The algorithm implemented in this app is the SM-2 algorithm used in the SuperMemo-2 software as well as the popular
12 | open source Anki software. The algorithm is described in detail here: http://www.supermemo.com/english/ol/sm2.htm
13 |
14 | To run
15 | ------
16 | - Make sure you have Node.js installed.
17 | - Clone the repo and create your word list (see baseCards.json)
18 | - On the command line run: node spaced.js
19 |
20 | Why?
21 | ------
22 | This will likely turn into a web app, this was an opportunity to start small and learn a little more about Node.js.
23 | On the plus side, it's very distraction free :)
24 |
25 | TODO:
26 | - record the history and all data to a real db, do something interesting with it
27 | - refactor
28 | - choose when to start or skip to answer without waiting 4 seconds?
29 | - choose input file from command line instead of hard coded
30 |
31 | License
32 | -----
33 | MIT
34 |
--------------------------------------------------------------------------------
/refactor.js:
--------------------------------------------------------------------------------
1 | // Spaced Repetition is an efficient learning system that attempts to quiz the
2 | // user with flash cards at specific intervals for maximum memory retention.
3 | // The quiz interval is determined by the (0-5) rating the user gives after seeing
4 | // each card, and increases or decreases depending on difficulty.
5 |
6 | // The algorithm implemented here is the SM-2 algorithm used in the SuperMemo-2
7 | // software as well as the popular open source Anki software.
8 | // The algorithm is described here: http://www.supermemo.com/english/ol/sm2.htm
9 |
10 | // Open Source MIT LICENSE
11 | // This code lives here: https://github.com/joedel/spaced-repetition/
12 | // Any feedback or tips to improve greatly appreciated.
13 |
14 | var fs = require('fs');
15 | var readline = require('readline');
16 |
17 | var cardFile = 'baseCards.json',
18 | quizList = [],
19 | quizTimer = 4000,
20 | today = new Date(),
21 | cards = [],
22 | cardCounter = 0,
23 | count = 0;
24 |
25 | today.setHours(0,0,0,0);
26 |
27 | console.log("Welcome to Command Line Spaced Repetition!\n" +
28 | "After each word please grade yourself as follows:\n" +
29 | "(0) What the heck was that? (No recognition)\n" +
30 | "(1) Wrong answer, but recognized the word.\n" +
31 | "(2) Wrong answer, but it was on the tip of my tongue!\n" +
32 | "(3) Got it right, but just barely.\n" +
33 | "(4) Got it right, had to think about it.\n" +
34 | "(5) Knew the answer immediately.");
35 |
36 | function readCardFile(file) {
37 | var data = fs.readFileSync(file);
38 | return JSON.parse(data);
39 | }
40 |
41 | function cardQuizCount() {
42 | var count = 0;
43 | for (var i=0; i 0) {
55 | console.log("You have " + count + " cards to go through.");
56 | getUserInput("Press enter to begin or 'exit' to quit: ", startStopQuiz);
57 | } else {
58 | console.log("No cards due. Come back tomorrow :)");
59 | }
60 | }
61 |
62 | function getUserInput(question, processInput, card) {
63 | var rl = readline.createInterface(process.stdin, process.stdout);
64 | rl.setPrompt(question);
65 | rl.prompt();
66 | rl.on('line', function(line) {
67 | rl.close();
68 | processInput(line, card);
69 | });
70 | }
71 |
72 | function startStopQuiz(line) {
73 | if (line.trim() === "exit") {
74 | return;
75 | } else {
76 | cardCounter = 0;
77 | getNextCard(cards[0]);
78 | }
79 | }
80 |
81 | function endOfCardList() {
82 | writeCardFile(cardFile); //Save progress to file
83 | var count = cardQuizCount();
84 | preQuiz(count); //restart quiz with any low grade cards
85 | }
86 |
87 | function getNextCard(card) {
88 | if (!card) {
89 | endOfCardList();
90 | return;
91 | }
92 | //defaults if new card
93 | if (!card.nextDate) { card.nextDate = today; }
94 | if (!card.prevDate) { card.prevDate = today; }
95 | if (!card.interval) { card.interval = 0; }
96 | if (!card.reps) { card.reps = 0; }
97 | if (!card.EF) { card.EF = 2.5; }
98 |
99 | var nextDate = new Date(card.nextDate); //convert to comparable date type
100 | if (nextDate <= today) {
101 | quizCard(card);
102 | } else {
103 | cardCounter++;
104 | getNextCard(cards[cardCounter]);
105 | }
106 | }
107 |
108 | function quizCard(card) {
109 | console.log("Side 1: " + card.side1);
110 | setTimeout(function() {
111 | console.log("Side 2: " + card.side2);
112 | getUserInput("Grade> ", parseCardGrade, card);
113 | }, quizTimer);
114 | }
115 |
116 | function parseCardGrade(line, card) {
117 | var grade = parseInt(line, 10);
118 | if (grade >= 0 && grade <= 5) {
119 | calcIntervalEF(card, grade);
120 | cardCounter++;
121 | getNextCard(cards[cardCounter]);
122 |
123 | } else { //Bad input
124 | getUserInput("Please enter 0-5 for... " + card.side2 + ": ", parseCardGrade, card);
125 | }
126 | }
127 |
128 | // SM-2:
129 | // EF (easiness factor) is a rating for how difficult the card is.
130 | // Grade: (0-2) Set reps and interval to 0, keep current EF (repeat card today)
131 | // (3) Set interval to 0, lower the EF, reps + 1 (repeat card today)
132 | // (4-5) Reps + 1, interval is calculated using EF, increasing in time.
133 | function calcIntervalEF(card, grade) {
134 | var oldEF = card.EF,
135 | newEF = 0,
136 | nextDate = new Date(today);
137 |
138 | if (grade < 3) {
139 | card.reps = 0;
140 | card.interval = 0;
141 | } else {
142 |
143 | newEF = oldEF + (0.1 - (5-grade)*(0.08+(5-grade)*0.02));
144 | if (newEF < 1.3) { // 1.3 is the minimum EF
145 | card.EF = 1.3;
146 | } else {
147 | card.EF = newEF;
148 | }
149 |
150 | card.reps = card.reps + 1;
151 |
152 | switch (card.reps) {
153 | case 1:
154 | card.interval = 1;
155 | break;
156 | case 2:
157 | card.interval = 6;
158 | break;
159 | default:
160 | card.interval = Math.round((card.reps - 1) * card.EF);
161 | break;
162 | }
163 | }
164 |
165 | if (grade === 3) {
166 | card.interval = 0;
167 | }
168 |
169 | nextDate.setDate(today.getDate() + card.interval);
170 | card.nextDate = nextDate;
171 | }
172 |
173 | function writeCardFile(cardFile) {
174 | fs.writeFileSync(cardFile, JSON.stringify(cards, null, 2));
175 | console.log("\nProgress saved back to file.");
176 | }
177 |
178 | cards = readCardFile(cardFile);
179 | count = cardQuizCount();
180 | preQuiz(count);
--------------------------------------------------------------------------------
/spaced.js:
--------------------------------------------------------------------------------
1 | // This is a command line spaced repetition program, useful for learning
2 | // or memorizing any material that could be on a flash card. Most commonly used
3 | // for learning new languages, or specific sets of data.
4 |
5 | // Spaced Repetition is an efficient learning system that attempts to quiz the
6 | // user with flash cards at specific intervals for maximum memory retention.
7 | // The quiz interval is determined by the (0-5) rating the user gives after seeing
8 | // each card, and increases or decreases depending on difficulty.
9 |
10 | // The algorithm implemented here is the SM-2 algorithm used in the SuperMemo-2
11 | // software as well as the popular open source Anki software.
12 | // The algorithm is described here: http://www.supermemo.com/english/ol/sm2.htm
13 |
14 | // Open Source MIT LICENSE
15 | // This code lives here: https://github.com/joedel/spaced-repetition/
16 | // Any feedback or tips to improve greatly appreciated.
17 |
18 | var fs = require('fs');
19 | var readline = require('readline');
20 |
21 | var cardFile = 'baseCards.json',
22 | quizList = [],
23 | quizTimer = 4000,
24 | today = new Date(),
25 | cards = [],
26 | cardCounter = 0;
27 |
28 | today.setHours(0,0,0,0);
29 |
30 | console.log("Welcome to Command Line Spaced Repetition!\n" +
31 | "After each word please grade yourself as follows:\n" +
32 | "(0) What the heck was that? (No recognition)\n" +
33 | "(1) Wrong answer, but recognized the word.\n" +
34 | "(2) Wrong answer, but it was on the tip of my tongue!\n" +
35 | "(3) Got it right, but just barely.\n" +
36 | "(4) Got it right, had to think about it.\n" +
37 | "(5) Knew the answer immediately.");
38 |
39 | function readCardFile(file) {
40 | fs.readFile(file, function(err, data) {
41 | if (err) throw err;
42 | cards = JSON.parse(data);
43 | var count = cardQuizCount();
44 | if (count) {
45 | console.log("You have " + count + " cards to go through today");
46 | getUserInput("Press Enter to Begin...", startStopQuiz);
47 | } else {
48 | console.log("There are no cards to quiz for today");
49 | }
50 | });
51 | }
52 |
53 | readCardFile(cardFile);
54 |
55 | function getUserInput(question, next, card) {
56 | var rl = readline.createInterface(process.stdin, process.stdout);
57 | rl.setPrompt(question);
58 | rl.prompt();
59 | rl.on('line', function(line) {
60 | rl.close();
61 | if (!card) {
62 | next(line);
63 | } else {
64 | next(line, card);
65 | }
66 | });
67 | }
68 |
69 | function startStopQuiz(line) {
70 | if (line.trim() === "exit") {
71 | return;
72 | } else {
73 | var count = cardQuizCount();
74 | if (count) {
75 | cardCounter = 0;
76 | getNextCard(cards[0]);
77 | }
78 | }
79 | }
80 |
81 | //Amount of cards up for quizzing today
82 | function cardQuizCount() {
83 | var count = 0;
84 | for (var i=0; i ", updateCard, card);
126 | }, quizTimer);
127 | }
128 |
129 | function updateCard(line, card) {
130 | var grade = parseInt(line, 10);
131 | if (grade <= 5 && grade >= 0) {
132 | calcIntervalEF(card, grade);
133 | cardCounter++;
134 | getNextCard(cards[cardCounter]);
135 |
136 | } else { //Bad input
137 | getUserInput("Please enter 0-5 for... " + card.side2 + ": ", updateCard, card);
138 | }
139 | }
140 |
141 | // Briefly the algorithm works like this:
142 | // EF (easiness factor) is a rating for how difficult the card is.
143 | // Grade: (0-2) Set reps and interval to 0, keep current EF (repeat card today)
144 | // (3) Set interval to 0, lower the EF, reps + 1 (repeat card today)
145 | // (4-5) Reps + 1, interval is calculated using EF, increasing in time.
146 | function calcIntervalEF(card, grade) {
147 | var oldEF = card.EF,
148 | newEF = 0,
149 | nextDate = new Date(today);
150 |
151 | if (grade < 3) {
152 | card.reps = 0;
153 | card.interval = 0;
154 | } else {
155 |
156 | newEF = oldEF + (0.1 - (5-grade)*(0.08+(5-grade)*0.02));
157 | if (newEF < 1.3) { // 1.3 is the minimum EF
158 | card.EF = 1.3;
159 | } else {
160 | card.EF = newEF;
161 | }
162 |
163 | card.reps = card.reps + 1;
164 |
165 | switch (card.reps) {
166 | case 1:
167 | card.interval = 1;
168 | break;
169 | case 2:
170 | card.interval = 6;
171 | break;
172 | default:
173 | card.interval = Math.ceil((card.reps - 1) * card.EF);
174 | break;
175 | }
176 | }
177 |
178 | if (grade === 3) {
179 | card.interval = 0;
180 | }
181 |
182 | nextDate.setDate(today.getDate() + card.interval);
183 | card.nextDate = nextDate;
184 | }
185 |
186 | function writeCardFile(cardFile) {
187 | fs.writeFile(cardFile, JSON.stringify(cards, null, 2), function(err) {
188 | if (err) throw err;
189 | console.log("\nProgress saved back to file.");
190 | });
191 | }
--------------------------------------------------------------------------------