├── 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 | } --------------------------------------------------------------------------------