All computer source code presented on this page, unless it includes attribution to another author, is provided by Ed Halley under the Artistic License. Use such code freely and without any expectation of support. I would like to know if you make anything cool with the code, or need questions answered.
python/
    bindings.py
    boards.py
    buzz.py
    caches.py
    cards.py
    constraints.py
    csql.py
    english.py
    getch.py
    getopts.py
    gizmos.py
    goals.py
    improv.py
    interpolations.py
    namespaces.py
    nihongo.py
    nodes.py
    octalplus.py
    patterns.py
    physics.py
    pids.py
    pieces.py
    quizzes.py
    recipes.py
    relays.py
    romaji.py
    ropen.py
    sheets.py
    stores.py
    strokes.py
    subscriptions.py
    svgbuild.py
    testing.py
    things.py
    timing.py
    ucsv.py
    useful.py
    uuid.py
    vectors.py
    weighted.py
java/
    CSVReader.java
    CSVWriter.java
    GlobFilenameFilter.java
    RegexFilenameFilter.java
    StringBufferOutputStream.java
    ThreadSet.java
    Throttle.java
    TracingThread.java
    Utf8ConsoleTest.java
    droid/
        ArrangeViewsTouchListener.java
        DownloadFileTask.java
perl/
    CVQM.pm
    Kana.pm
    Typo.pm
cxx/
    CCache.h
    equalish.cpp
Download cards.py

__all__ = [ 'Card', 'Deck' ]

import ucsv as csv
import random
import time

class Card (object):

    def __init__(self, sides, **kwargs):
        if hasattr(sides, 'items'):
            self.names = [ pair[0] for pair in sides.items() if pair[1] ]
            self.sides = [ pair[1] for pair in sides.items() if pair[1] ]
        else:
            self.names = [ ('SIDE%d' % n) for n in range(len(sides)) ]
            self.sides = sides
        self.index = dict(zip( self.names, range(len(self.names)) ))
        self.stats = kwargs
        self.shown = 0

    def show(self, side):
        if side in self.index: side = self.index[side]
        if side < 0 or side >= len(self.sides):
            raise ValueError, 'index out of range'
        self.shown = side

    def side(self, side):
        if side in self.index: side = self.index[side]
        if side < 0 or side >= len(self.sides):
            return None
        return self.sides[side]

    def has(self, side):
        return side in self.index

    def stat(self, var, val=None):
        if var in self.stats:
            if val is not None:
                self.stats[var] = val
            return self.stats[var]
        return val

    def __repr__(self):
        t = 'Card(names=%r, sides=%r, stats=%r)' % (self.names, self.sides, self.stats)
        return t

    def __str__(self):
        return u'%s' % self.sides[self.shown]

class Deck (object):

    def __init__(self, cards=None):
        if cards is None: cards = [ ]
        self.cards = cards
        self.heads = [ ]

    def __len__(self):
        return len(self.cards)

    def __nonzero__(self):
        return len(self.cards) > 0

    def __getitem__(self, n):
        return self.cards[n]

    def shuffle(self):
        random.shuffle(self.cards)

    def discard(self, count=1):
        count = min(count, len(self.cards))
        self.cards = self.cards[:-count]

    def keep(self, count=1):
        count = min(count, len(self.cards))
        self.cards = self.cards[:count]

    def loadCsv(self, filename, stats=None):
        if stats is None: stats = { }
        if isinstance(stats, (list,set,tuple)):
            stats = dict(zip( stats, [0,]*len(stats) ))
        f = file(filename, 'rb')
        c = csv.DictReader(f)
        for row in c:
            if not self.heads: self.heads = c.fieldnames
            s = { }
            for stat in stats:
                s[stat] = row.pop(stat, None) or stats[stat]
            c = Card(row, **s)
            if c.sides:
                self.cards.append(c)
        f.close()
        return len(self.cards)

    def saveCsv(self, filename):
        f = file(filename, 'wb')
        c = csv.DictWriter(f, self.heads, restval=u'')
        for card in self.cards:
            row = card.stats.copy()
            for n in range(len(card.sides)):
                row[card.names[n]] = card.sides[n]
            c.writerow(row)
        f.close()
        return len(self.cards)

    def similar(self, common=None, varies=None, tested=None):
        if common is None: common = self.heads
        if isinstance(common, (list, tuple, set)):
            common = random.choice(common)
        match = None
        tries = 0
        while not match and tries < 100:
            tries += 1
            match = random.choice(self.cards)
            if not match.has(tested): continue
            if not match.has(varies): continue
            if not match.has(common): continue
            match = match.side(common)
        if not match: return None
        hand = [ card for card in self.cards
                 if card.side(common) == match
                 and card.has(varies)
                 and card.has(tested) ]
        if not hand: return None
        return Deck(hand)

def __quiz(deck, common, varies, tested, count=4):
    hand = None
    tries = 0
    while not hand:
        tries += 1
        if tries > 100: return
        hand = deck.similar(common, varies, tested)
        if not hand: continue
        if len(hand) < count:
            hand = None
    hand.shuffle()
    hand.keep(count)
    quiz = random.randrange(0, count)
    print 'QUIZ:', hand[quiz].side(tested), '-'
    for n in range(count):
        card = hand[n]
        print u"(%s):  %s" % (chr(ord('a')+n),
                           card.side(varies))
    hit = False
    choices = ', '.join( [ '(%s)' % chr(ord('a')+n) for n in range(count) ] )
    guess = raw_input('Answer %s, or (Enter) to skip: ' % choices)
    if len(guess) > 0:
        hand[quiz].stats['SEEN'] += 1
        hand[quiz].stats['LAST'] = int(time.time())
        guess = ord(guess[0])-ord('a')
        if guess == quiz:
            print 'Correct!'
            hit = True
        else:
            print 'Incorrect.'
        hand[quiz].stats['HITS'] += 1
    shown = [ hand[quiz].side(x) for x in hand[quiz].names if x != common ]
    print ' = '.join(shown)
    return hit

if __name__ == '__main__':

    filename = 'vocab.csv'
    d = Deck()
    d.loadCsv(filename, 'SEEN,HITS,LAST'.split(','))
    dirty = False

    import os
    import sys
    import romaji
    romaji.__utf8__()

    args = sys.argv
    args.pop(0)
    while args:

        arg = args.pop(0)

        if arg in ['-q','--quiz']:

            count = 10
            try:
                count = args.pop(0)
                count = int(count)
            except:
                pass
            quizzes = [ {'common':'POS','varies':'KANA','tested':'ENGLISH'},
                        {'common':'POS','varies':'ENGLISH','tested':'KANJI'},
                        {'common':'POS','varies':'KANJI','tested':'KANA'},
                        {'common':'POS','varies':'KANA','tested':'KANJI'} ]
            for n in range(count):
                print
                q = random.choice(quizzes)
                __quiz(d, **q)
            dirty = True

        if arg in ['-a','--add']:
            dirty = True

    if dirty:
        os.rename(filename, filename+'~')
        d.saveCsv(filename)
        print 'Updated %s (%d cards).' % (filename, len(d.cards))


Contact Ed Halley by email at ed@halley.cc.
Text, code, layout and artwork are Copyright © 1996-2013 Ed Halley.
Copying in whole or in part, with author attribution, is expressly allowed.
Any references to trademarks are illustrative and are controlled by their respective owners.
Make donations with PayPal - it's fast, free and secure!