/* Simplified Leitner. For use with decks from Flashcarddb.com exported with @ separators. Copyright 2010, Kevin Whitefoot License: use as you will so long as you maintain the credits. Language: mshell Method: See http://flashcarddb.com/leitner*/ use arrayuse filesuse iouse timeuse ui/* Initialize some tables, etc.See http://flashcarddb.com/leitnerDeckNumber Time until next repetitionOne NoneTwo 1 dayThree 3 daysFour 1 weekFive 1 month*/oneday = 24*60*60; // seconds in a dayexpiry = [0, 0, 1, 3, 7, 30];lsLoaded=0;lsLoadedWithDuplicates = 1; lsZeroLength = 2;lsTooManyStates = 3;lsTooFewStates = 4;/* debugSet verbosity higher to generate more logging output.*/verbosity=0;function verbose(n, s); if n < ..verbosity then print s; end;end;/*We don't need the precision of the UTC function so divide by seconds in a day. This reduces the size of the number so that it fits in an int (32 bit) and will be correctly written by io.format("%d"). Reducing the size also reduces the file size. Used to generate expiry times. */function today() return time.utc()/..oneday;end;function debug_isnum(n) if not isnum(n) then ui.msg("c is not a number: " + str(n)) elsif n<0 then ui.msg("negative: " + str(n)) end;end;/* Cards are simple data structures that can be read from an opentext file. A card has a front and a back. They are never modified bythe program.*/class Card front back // f is an open file function load(file) s = io.readln(file); i = index(s, "@"); front = substr(s, 0, i); back = substr(s, i+1); end // f is an open file function save(file); io.writeln(file, front + "@" + back); end // f is an open file function init(file) load(file) endend/*Cards is a list of Card instances in the order they were found in afile.The file name is generated from the set name by appending .cards.*/class Cards cards setname function count() return len(cards); end function filename() verbose(3,"Cards.filename: " +setname); return setname +".cards"; end function get(i) return cards[i]; end function add(c) append(cards, c); end // f is an open file function load() f = io.open(filename()); while 0 < io.avail(f) do c:Card = Card(f); add(c); end end function init(setname) this.setname = setname; cards = []; load(); verbose(3,io.format("count %d", len(cards))); end // f is an open file function save(f) c:Card = null; for c in cards do c.save(f); end end end/*The state of each card is stored in a card state object. Theseobjects are stored in a file that is updated every time a card changesstate. To save time the new stte is simply appended to the end ofthe file. When reading the file back later entries for the same card overwrite the newer ones. The state is then written out again so that the file does not grow indefinitely.*/class CardState card // index into array of cards. deck // number of the deck to which this card belongs. expires // date at which the card should be reviewed expressed as // seconds from the start of the epoch. function card(cards:Cards) return cards.get(card); end; function init(card, deck) verbose(3,"CardState.init " +str(card) + " " + str(deck)); this.card = card; this.deck = deck; expires = 0 end; function loadfromstr(s) verbose(3,"CardState.loadfromstr: " + s); a = split(s); card = num(a[0]); deck = num(a[1]); expires = num(a[2]); end; // f is an open file function load(file) s = io.readln(file); loadfromstr(s) end; // f is an open file function save(file) io.printf(file, "%d %d %d\n", card, deck, expires); end; function promote() if deck < 5 then deck += 1; end; verbose(4,io.format(" expires %d, time.utc() %d, ..expiry[deck] %d deck %d", expires, time.utc(), ..expiry[deck], deck)); expires = time.utc() + ..expiry[deck]; end; function demote() deck = 1; expires = 0; end;end;/**/class CardStates cards:Cards cardstates // array of CardState setname function get(i) return cardstates[i]; end; function card(i) cs:CardState = cardstates[i]; return cs.card(cards); end; function count() return cards.count(); end; function filename() verbose(3,"CardStates.filename: " +setname); return setname +".state"; end function savestate() verbose(1,"CardStates.savestate"); f = io.create(filename()); cs:CardState = null; for cs in cardstates do cs.save(f); end; io.close(f); end /* Returns a code indicating the result. It can happen that the user will update the cards file. In such a case the states file will have to be updated to match. */ function loadstate() verbose(1,"CardStates.loadstate"); f = io.open(filename(), false); cr = 0; // count read cu = 0; // count used lc = cards.count(); cardstates = array.create(lc, null); do s = io.readln(f); if s # null then cr += 1; // count how many we read so that we can tell if there // were duplicates cs:CardState = CardState(0,0); cs.loadfromstr(s); if cs.card < lc then // Note that we do not just append because we want to ensure // that later duplicates override the earlier ones if cardstates[cs.card] = null then cu += 1; end; // overwrite even if was not null. cardstates[cs.card] = cs; else // do nothing, discard state for non-existent card end; end; until s = null; io.close(f); // want to open it for writing again. verbose(3,io.format("counts %d %d %d", len(cardstates), cr, cu)); if cu < lc then // some cards do not have a state. This happens when the user // overwrites the cards file with a new longer (more lines) // file. return ..lsTooFewStates; elsif cr # cu then // some cards had duplicate state records; this is not a bug, it // happens because when we write a new state record we do just // that instead of rewriting the whole state file. When we start // the program we remove the duplicates and rewrite the file. return ..lsLoadedWithDuplicates; else return ..lsLoaded; end end; function cardstate(card): CardState verbose(2,io.format("CardStates.cardstate: %d %d",card, len(cardstates))); return cardstates[card]; end; function savecardstate(card) verbose(2,"CardStates.savecardstate: " +str(card)); f = io.append(filename()); //cs:CardState = cardstates[card]; cardstate(card).save(f); io.close(f); end; function promote(card) verbose(3,"CardStates.promote" +str(card)); cs:CardState = cardstates[card]; cs.promote(); savecardstate(card); end; function demote(card) verbose(3,"CardStates.demote " +str(card)); cs:CardState = cardstates[card]; cs.demote(); savecardstate(card); end; function initstate() // not found, first time verbose(1,"CardStates.initstate"); for i = 0 to cards.count()-1 do cs = CardState(i, 1); append(cardstates, cs); end; savestate(); end; /* Attempt to add states for cards added to the cards file and remove states that are no longer needed. At this stage the cardstates and cards list are both ordered by card number so all we have to do is look for nulls in the cardstates list. */ function qfixupstates() verbose(2,"CardStates.qfixupstates"); for i = 0 to len(cardstates)-1 do cs:CardState = cardstates[i]; if cs = null then // no state for this card so add one in deck 1. cardstates[i] = CardState(i, 1); end; end; end; function init(setname) verbose(2,"CardStates.init " + setname); this.setname = setname; cardstates = []; cards = Cards(setname); if files.exists(filename()) then r = loadstate(); verbose(3,"r: " + str(r)); if r = ..lsTooFewStates then qfixupstates(); elsif r = ..lsTooManyStates then qfixupstates(); elsif r = ..lsZeroLength then initstate(); elsif r = ..lsLoadedWithDuplicates then // Save the state to remove duplicates savestate(); elsif r = ..lsLoaded then // success, nothing to do else print "Unxpected result from loadstates: ", r; end; else initstate(); end; end function getnext(currentcard) verbose(2,"CardStates.getnext " + str(currentcard)); now = today();// time.utc(); c = count(); cc = currentcard; while cc+1 < c do cc += 1; e = cardstate(cc).expires; verbose(4, io.format("Expires %d, now %d", e, now)); if e <= now then //debug_isnum(cc); return cc; end end; return -1; endend class Flash_Set setname cardstates:CardStates currentcard currentdeck // Load from disk if possible. function init(name) setname = name; cardstates = CardStates(name); currentcard = -1; currentdeck = 1; end function getnext() verbose(2,"Flash_set.getnext"); c = cardstates.getnext(currentcard); if c#-1 then return c; end; // Ran off the end so check from beginning again in case any have been demoted. print "Studied all cards due today, checking for demoted cards."; currentcard = -1; return cardstates.getnext(currentcard); end function promote() cardstates.promote(currentcard) end; function demote() cardstates.demote(currentcard); end; function shownext() verbose(1,"Flash_set.shownext"); currentcard = getnext(); if currentcard = -1 then return true; end; //debug_isnum(currentcard); c:Card = cardstates.card(currentcard); print currentdeck, currentcard, c.front; // wait for user to click a key, don't care what. k=ui.cmd(); // show the back of the card; print c.back; // wait for user to respond k=ui.cmd(); correct = (k = 63497); // if known if correct then promote() else demote() end; return false; end; function showall() verbose(1, "Flash_set.showall"); do finished = shownext(); until finished; print "Studied all cards, nothing left to do today."; end;end;// mainui.keys(ui.strokes); // return keystrokesf:Flash_Set = Flash_Set("flashcards"); f.showall()
Tuesday, 10 August 2010
Flashcards for mshell
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment