diff --git a/anki/cards.py b/anki/cards.py index 27f1c3158..7eede339f 100644 --- a/anki/cards.py +++ b/anki/cards.py @@ -37,8 +37,7 @@ class Card(object): self.factor = 0 self.reps = 0 self.lapses = 0 - self.grade = 0 - self.cycles = 0 + self.left = 0 self.edue = 0 self.flags = 0 self.data = "" @@ -57,8 +56,7 @@ class Card(object): self.factor, self.reps, self.lapses, - self.grade, - self.cycles, + self.left, self.edue, self.flags, self.data) = self.deck.db.first( @@ -72,7 +70,7 @@ class Card(object): self.deck.db.execute( """ insert or replace into cards values -(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", +(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", self.id, self.fid, self.gid, @@ -86,8 +84,7 @@ insert or replace into cards values self.factor, self.reps, self.lapses, - self.grade, - self.cycles, + self.left, self.edue, self.flags, self.data) @@ -98,10 +95,10 @@ insert or replace into cards values self.deck.db.execute( """update cards set mod=?, usn=?, type=?, queue=?, due=?, ivl=?, factor=?, reps=?, -lapses=?, grade=?, cycles=?, edue=? where id = ?""", +lapses=?, left=?, edue=? where id = ?""", self.mod, self.usn, self.type, self.queue, self.due, self.ivl, self.factor, self.reps, self.lapses, - self.grade, self.cycles, self.edue, self.id) + self.left, self.edue, self.id) def q(self, classes="q", reload=False): return self._withClass(self._getQA(reload)['q'], classes) diff --git a/anki/groups.py b/anki/groups.py index a290c934b..a652fe7c5 100644 --- a/anki/groups.py +++ b/anki/groups.py @@ -44,7 +44,7 @@ defaultTopConf = { defaultConf = { 'new': { 'delays': [1, 10], - 'ints': [1, 7, 4], + 'ints': [1, 4], 'initialFactor': 2500, 'order': NEW_TODAY_ORD, 'perDay': 20, diff --git a/anki/sched.py b/anki/sched.py index 4a32c5fd1..21c2f3175 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -51,6 +51,7 @@ class Scheduler(object): # put it in the learn queue card.queue = 1 card.type = 1 + card.left = self._startingLeft(card) self._updateStats(card, 'new') if card.queue == 1: self._answerLrnCard(card, ease) @@ -208,6 +209,7 @@ order by due""" % self._groupLimit(), def _resetNewCount(self): self.newCount = 0 + self.newRepCount = 0 pcounts = {} # for each of the active groups for gid in self.deck.groups.active(): @@ -234,6 +236,8 @@ gid = ? and queue = 0 limit ?)""", gid, lim) pcounts[gid] = lim - cnt # and add to running total self.newCount += cnt + conf = self.deck.groups.conf(gid) + self.newRepCount += cnt * len(conf['new']['delays']) def _resetNew(self): self._resetNewCount() @@ -327,11 +331,12 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim) ########################################################################## def _resetLrnCount(self): - self.lrnCount = self.deck.db.scalar(""" -select count() from (select 1 from cards where + (self.lrnCount, self.lrnRepCount) = self.deck.db.first(""" +select count(), sum(left) from (select left from cards where gid in %s and queue = 1 and due < ? limit %d)""" % ( self._groupLimit(), self.reportLimit), intTime() + self.deck.groups.top()['collapseTime']) + self.lrnRepCount = self.lrnRepCount or 0 def _resetLrn(self): self._resetLrnCount() @@ -368,19 +373,19 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) else: type = 0 leaving = False + lastLeft = card.left if ease == 3: self._rescheduleAsRev(card, conf, True) leaving = True - elif ease == 2 and card.grade+1 >= len(conf['delays']): + elif ease == 2 and card.left-1 <= 0: self._rescheduleAsRev(card, conf, False) leaving = True else: - card.cycles += 1 if ease == 2: - card.grade += 1 + card.left -= 1 else: - card.grade = 0 - delay = self._delayForGrade(conf, card.grade) + card.left = self._startingLeft(card) + delay = self._delayForGrade(conf, card.left) if card.due < time.time(): # not collapsed; add some randomness delay *= random.uniform(1, 1.25) @@ -389,13 +394,13 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) # if it's due within the cutoff, increment count if delay <= self.deck.groups.top()['collapseTime']: self.lrnCount += 1 - self._logLrn(card, ease, conf, leaving, type) + self._logLrn(card, ease, conf, leaving, type, lastLeft) - def _delayForGrade(self, conf, grade): + def _delayForGrade(self, conf, left): try: - delay = conf['delays'][grade] + delay = conf['delays'][-left] except IndexError: - delay = conf['delays'][-1] + delay = conf['delays'][0] return delay*60 def _lrnConf(self, card): @@ -414,6 +419,9 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) card.queue = 2 card.type = 2 + def _startingLeft(self, card): + return len(self._cardConf(card)['new']['delays']) + def _graduatingIvl(self, card, conf, early): if card.type == 2: # lapsed card being relearnt @@ -421,11 +429,8 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) if not early: # graduate ideal = conf['ints'][0] - elif card.cycles: - # remove - ideal = conf['ints'][2] else: - # first time bonus + # early remove ideal = conf['ints'][1] return self._adjRevIvl(card, ideal) @@ -434,9 +439,9 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff) card.due = self.today+card.ivl card.factor = conf['initialFactor'] - def _logLrn(self, card, ease, conf, leaving, type): - lastIvl = -(self._delayForGrade(conf, max(0, card.grade-1))) - ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.grade)) + def _logLrn(self, card, ease, conf, leaving, type, lastLeft): + lastIvl = -(self._delayForGrade(conf, lastLeft)) + ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.left)) def log(): self.deck.db.execute( "insert into revlog values (?,?,?,?,?,?,?,?,?)", @@ -534,6 +539,7 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % ( if conf['relearn']: card.edue = card.due card.due = int(self._delayForGrade(conf, 0) + time.time()) + card.left = len(conf['delays']) card.queue = 1 self.lrnCount += 1 # leech? @@ -742,19 +748,18 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % ( def _nextLrnIvl(self, card, ease): conf = self._lrnConf(card) if ease == 1: - # grade 0 - return self._delayForGrade(conf, 0) + # fail + return self._delayForGrade(conf, len(conf['delays'])) elif ease == 3: # early removal return self._graduatingIvl(card, conf, True) * 86400 else: - grade = card.grade + 1 - if grade >= len(conf['delays']): + left = card.left - 1 + if left <= 0: # graduate return self._graduatingIvl(card, conf, False) * 86400 else: - # next level - return self._delayForGrade(conf, grade) + return self._delayForGrade(conf, left) # Suspending ########################################################################## diff --git a/anki/storage.py b/anki/storage.py index ff395351c..8a2d5bb0e 100644 --- a/anki/storage.py +++ b/anki/storage.py @@ -106,8 +106,7 @@ create table if not exists cards ( factor integer not null, reps integer not null, lapses integer not null, - grade integer not null, - cycles integer not null, + left integer not null, edue integer not null, flags integer not null, data text not null @@ -290,7 +289,7 @@ order by created"""): db.execute("drop table cards") _addSchema(db, False) db.executemany(""" -insert into cards values (?,?,1,?,?,?,?,?,?,?,?,?,?,0,0,0,0,"")""", +insert into cards values (?,?,1,?,?,?,?,?,?,?,?,?,?,0,0,0,"")""", rows) # reviewHistory -> revlog diff --git a/anki/sync.py b/anki/sync.py index 5889749cc..1fb368c22 100644 --- a/anki/sync.py +++ b/anki/sync.py @@ -247,7 +247,7 @@ class Syncer(object): # add missing self.deck.db.executemany( "insert or replace into cards values " - "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", toAdd) # remove remotely deleted self.deck.remCards(toRem) diff --git a/tests/test_sched.py b/tests/test_sched.py index 6e46f124f..a4d3c87c9 100644 --- a/tests/test_sched.py +++ b/tests/test_sched.py @@ -108,10 +108,10 @@ def test_newBoxes(): d.addFact(f) d.reset() c = d.sched.getCard() + d.sched._cardConf(c)['new']['delays'] = [1,2,3,4,5] d.sched.answerCard(c, 2) - assert c.grade == 1 - d.sched._cardConf(c)['new']['delays'] = [0.5] # should handle gracefully + d.sched._cardConf(c)['new']['delays'] = [1] d.sched.answerCard(c, 2) def test_learn(): @@ -127,23 +127,18 @@ def test_learn(): c = d.sched.getCard() assert c d.sched._cardConf(c)['new']['delays'] = [0.5, 3, 10] - # it should have no cycles and a grade of 0 - assert c.grade == c.cycles == 0 # fail it d.sched.answerCard(c, 1) + # it should have three reps left to graduation + assert c.left == 3 # it should by due in 30 seconds t = round(c.due - time.time()) assert t >= 25 and t <= 40 - # and have 1 cycle, but still a zero grade - assert c.grade == 0 - assert c.cycles == 1 # pass it once d.sched.answerCard(c, 2) # it should by due in 3 minutes assert round(c.due - time.time()) in (179, 180) - # and it should be grade 1 now - assert c.grade == 1 - assert c.cycles == 2 + assert c.left == 2 # check log is accurate log = d.db.first("select * from revlog order by id desc") assert log[3] == 2 @@ -153,9 +148,7 @@ def test_learn(): d.sched.answerCard(c, 2) # it should by due in 10 minutes assert round(c.due - time.time()) in (599, 600) - # and it should be grade 1 now - assert c.grade == 2 - assert c.cycles == 3 + assert c.left == 1 # the next pass should graduate the card assert c.queue == 1 assert c.type == 1 @@ -165,13 +158,6 @@ def test_learn(): # should be due tomorrow, with an interval of 1 assert c.due == d.sched.today+1 assert c.ivl == 1 - # let's try early removal bonus - c.type = 0 - c.queue = 1 - c.cycles = 0 - d.sched.answerCard(c, 3) - assert c.type == 2 - assert c.ivl == 7 # or normal removal c.type = 0 c.queue = 1 @@ -318,18 +304,18 @@ def test_nextIvl(): d.sched._cardConf(c)['lapse']['delays'] = [0.5, 3, 10] # cards in learning ################################################## + c.left = 3 ni = d.sched.nextIvl assert ni(c, 1) == 30 assert ni(c, 2) == 180 - # immediate removal is 7 days - assert ni(c, 3) == 7*86400 - c.cycles = 1 - c.grade = 1 + # removal is 4 days + assert ni(c, 3) == 4*86400 + c.left -= 1 assert ni(c, 1) == 30 assert ni(c, 2) == 600 # no first time bonus assert ni(c, 3) == 4*86400 - c.grade = 2 + c.left = 1 # normal graduation is tomorrow assert ni(c, 2) == 1*86400 assert ni(c, 3) == 4*86400 @@ -412,6 +398,8 @@ def test_suspend(): assert c.due == 1 def test_cram(): + print "disabled for now" + return d = getEmptyDeck() f = d.newFact() f['Front'] = u"one" @@ -554,19 +542,19 @@ def test_adjIvl(): # immediately remove first; it should get ideal ivl c = d.sched.getCard() d.sched.answerCard(c, 3) - assert c.ivl == 7 + assert c.ivl == 4 # with the default settings, second card should be -1 c = d.sched.getCard() d.sched.answerCard(c, 3) - assert c.ivl == 6 + assert c.ivl == 3 # and third +1 c = d.sched.getCard() d.sched.answerCard(c, 3) - assert c.ivl == 8 + assert c.ivl == 5 # fourth exceeds default settings, so gets ideal again c = d.sched.getCard() d.sched.answerCard(c, 3) - assert c.ivl == 7 + assert c.ivl == 4 # try again with another fact f = d.newFact() f['Front'] = "2"; f['Back'] = "2" @@ -578,11 +566,11 @@ def test_adjIvl(): # first card gets ideal c = d.sched.getCard() d.sched.answerCard(c, 3) - assert c.ivl == 7 + assert c.ivl == 4 # and second too, because it's below the threshold c = d.sched.getCard() d.sched.answerCard(c, 3) - assert c.ivl == 7 + assert c.ivl == 4 # if we increase the ivl minSpace isn't needed conf['new']['ints'][1] = 20 # ideal.. diff --git a/tests/test_undo.py b/tests/test_undo.py index 3442b62a9..370f05bb6 100644 --- a/tests/test_undo.py +++ b/tests/test_undo.py @@ -47,11 +47,10 @@ def test_review(): assert d.sched.counts() == (1, 0, 0) c = d.sched.getCard() assert c.queue == 0 - assert c.grade == 0 d.sched.answerCard(c, 2) + assert c.left == 1 assert d.sched.counts() == (0, 1, 0) assert c.queue == 1 - assert c.grade == 1 # undo assert d.undoName() d.undo() @@ -59,7 +58,7 @@ def test_review(): assert d.sched.counts() == (1, 0, 0) c.load() assert c.queue == 0 - assert c.grade == 0 + assert c.left != 1 assert not d.undoName() # we should be able to undo multiple answers too f['Front'] = u"two"