Tuesday, 10 August 2010

Flashcards for mshell

/* 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()

Posted via email from kwhitefoot's posterous

No comments:

Post a Comment

Followers