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.
# quizzes - a set of quiz-oriented minigames for the "quiz" pygame app

import vectors ; from vectors import *
import things ; from things import *
import pieces ; from pieces import *
import boards ; from boards import *
import goals ; from goals import *

import romaji ; from romaji import *

def pretty(roma):
    fixes = { 'SI': 'SHI', 'TI': 'CHI', 'HU': 'FU', 'TU': 'TSU', 'ZI': 'JI',
              'si': 'shi', 'ti': 'chi', 'hu': 'fu', 'tu': 'tsu', 'zi': 'ji',
    if roma in fixes:
        roma = fixes[roma]
    return roma

import random


class Quiz (Board):

    '''A generic base class for a number of quiz mini-games.
    Each minigame in this family allows the user to drag cards
    from one zone to another zone; typically it reacts in some
    way when the right conditions are met, then allows the app
    to segue to some other game.

    def __init__(self):
        super(Quiz, self).__init__()
        self.hint = ''
        self.font = App.resource('epkgobld.ttf', 84)
        self.zones = { }

    def make_zone(self, name, image, place):
        '''Most zones are just a static image, or None.'''
        if image:
            image = App.resource(image)
        zone = Zone(name, image, size=0.75)
        zone.follow(self) = place
        self.zones[name] = zone
        return zone

    def nearest_drop(self, thing, where):
        nearest = self.nearest_zone(where, test=None)
        if nearest:
            if nearest[0].is_style('trading'):
                # if dropping on a full zone that supports trading, move that
                # full zone's stuff onto the nearest empty trading zone instead
                stuff = nearest[0].followers()
                if stuff:
                    test = lambda z: not z.followers() and z.is_style('trading')
                    another = self.nearest_zone(where, test=test)
                    if another:
                        for thing in reversed(stuff):
                # just find a plain old empty zone instead of this full one
                test=lambda z: not z.followers()
                nearest = self.nearest_zone(where, test=test)
        if nearest:
            return nearest
        return super(Quiz, self).nearest_drop(thing, where)

    def xnearest_drop(self, thing, where):
        nearest = self.nearest_zone(where, test=lambda z: not z.followers())
        if nearest:
            return nearest
        return super(Quiz, self).nearest_drop(thing, where)

    def segue(self):
        '''We can proceed if the guilty card is idle on the guilty zone.'''
        if not 'guilty' in self.zones:
            return self
        guilty = self.zones['guilty']
        cards = guilty.followers()
        if not cards:
            return self
        if not cards[0].is_style('guilty'):
            return self
        if not cards[0].is_style('complete'):
        if not cards[0].is_idle():
            return self
        return None

    def make_card(self, front, back=None, size=0.75):
        if not back:
            back = front
        card = Card( (front, back), font=self.font, size=size )
        return card

    def make_tile(self, front, back=None, size=0.75):
        if not back:
            back = front
        tile = Ingot( (front, back), font=self.font, size=size )
        return tile


class IntroQuiz (Quiz):

    '''The very first quiz that starts the game.

    This requires the user to figure out the visual scheme used in all
    of the early quizzes:  drag a card to the right target zone.

    Once this quiz is accomplished, it's never seen again.

    def __init__(self):
        super(IntroQuiz, self).__init__()
        x = random.choice( [ -150, 150 ] )
        home = self.make_zone('home',
                               V(360, 150, 0))
        wrong = self.make_zone('wrong',
                               V(360+x, 220, 0))
        right = self.make_zone('guilty',
                               V(360-x, 220, 0))
        self.card = self.make_card(u'\u3007') # maru


class FlybyQuiz (Quiz):

    '''A card flies by, while a floating text string explains.'''

    def __init__(self, roma, explanation):
        super(FlybyQuiz, self).__init__()
        card = self.make_card(roma, kana(roma), size=1)
        card.follow(self) = V(-50, 175, 4)
        for x in range(5):
            card.move(V(85, 0, 0), relative=True, dt=0.7)
        card.wait(dt=2, todo=lambda g,c=card: c.dazzle())
        self.card = card # any of them
        self.explanation = card.font.render(explanation, 1, (0x00,0x00,0x00))

    def segue(self):
        for card in self.joins:
            if not card.is_idle():
                return self
        return None

class LineupQuiz (Quiz):

    '''Five cards arranged in a police line-up.  One card is a rogue.'''

    def __init__(self, lineup, rogues):
        super(LineupQuiz, self).__init__()
        self.hint = 'Which one does not belong?'
        # self.lineup gets card names, not cards
        self.rogue = random.choice(rogues)
        self.lineup = [ self.rogue ] + random.sample(lineup, 4)
        y = random.choice( [ -100, 100 ] )
        for i in range(len(self.lineup)):
            zone = self.make_zone('suspect%d' % i,
                                  V(100+130*i, 200-y, 0))
            card = self.make_card(kana(self.lineup[i]))
            if self.lineup[i] == self.rogue:
        i = random.choice(range(1, 5))
        self.make_zone('guilty', 'zone-red-x.png', V(100+130*i, 200+y, 0))

class MugshotQuiz (LineupQuiz):

    '''Just like LineupQuiz, except we see romaji for the desired kana.'''

    def __init__(self, lineup):
        lineup = list(lineup)
        rogue = lineup.pop()
        super(MugshotQuiz, self).__init__(lineup, (rogue,))
        self.hint = 'Which one looks right?'
        mugshot = self.make_tile(pretty(rogue))
        place = self.zones['guilty'].place
        self.zones['guilty'].set_image('zone-green-o.png', size=0.75)
        mugshot.set_style('draggable', False)
        mugshot.move(place + V(-130, 0, 0))

class OrderingQuiz (Quiz):

    '''A number of scrambled cards on a row of zones.  Reorder to pass.'''

    def __init__(self, lineup):
        super(OrderingQuiz, self).__init__()
        self.hint = 'Put these into the right order!'
        # self.lineup gets card names, not cards
        self.dazzled = False
        self.lineup = list(lineup)
        if len(lineup) < 2:
            raise ValueError, 'cannot matchup one item or less'
        if len(lineup) > 10:
            raise ValueError, 'cannot matchup more than ten items'
        self.mixup = list(lineup[:])
        while self.mixup == self.lineup:
        self.flip = y = random.choice( [ -100, 100 ] )
        self.matching = [ ]
        for i in range(len(self.lineup)):
            if i == 5: y = -y
            zone = self.make_zone('suspect%d' % i,
                                  V(100+130*(i % 5), 200-y, 0))
            card = self.make_card(kana(self.mixup[i]))
   = self.mixup[i]

    def segue(self):
        i = 0
        for zone in self.matching:
            cards = zone.followers()
            if not cards:
                return self
            card = cards[0]
            if != self.lineup[i]:
                return self
            if not card.is_idle():
                return self
            i += 1
        if not self.dazzled:
            self.dazzled = True
            i = 0
            for zone in self.matching:
                cards = zone.followers()
                card = cards[0]
                card.wait(dt=0.1*i,todo=lambda g,c=card: c.dazzle())
                i += 1
            return self
        return None

class MatchupQuiz (OrderingQuiz):

    '''Up to five cards to reorder, showing desired order with flipped cards.'''

    def __init__(self, lineup):
        super(MatchupQuiz, self).__init__(lineup)
        self.hint = 'Put these into the same order!'
        if len(lineup) > 5:
            raise ValueError, 'cannot matchup more than five'
        y = self.flip
        for i in range(len(self.lineup)):
            tile = self.make_tile(self.lineup[i])
            tile.set_style('draggable', False)
   = V(100+130*i, 200+y, 0)

_figures = { }

class SketchQuiz (Quiz):

    '''Draw the kana on a clingpad sketching board.'''

    def __init__(self, lineup):
        self.passed = False
        lineup = list(lineup)
        self.rogue = lineup.pop()
        super(SketchQuiz, self).__init__()
        self.mugshot = self.make_card(pretty(self.rogue))
        self.mugshot.set_style('draggable', False)
        self.mugshot.move(V(180, 200, 0))
        font = App.resource('epkgobld.ttf', 300)
        guide = font.render(kana(self.rogue), 1, (0x00,0x80,0x00))
        alpha = surfarray.pixels_alpha(guide)
        alpha /= Numeric.array(8).astype(Numeric.UInt8)
        self.clingpad = ClingPad(guide=guide)
        self.clingpad.set_style('draggable', False)
        self.clingpad.move(V(450, 200, 0))

    def event(self, event):
        super(SketchQuiz, self).event(event)
        if event.type == KEYDOWN:
            if event.key in [ K_KP_ENTER, K_RETURN ]:
                self.passed = True
            if event.key == K_F4:
                figure = self.clingpad.figure
                print str(figure)
                if not self.rogue in _figures:
                    _figures[self.rogue] = [ ]

    def segue(self):
        if self.passed:
            if self.clingpad.is_idle():
                if self.mugshot.is_idle():
                    return None
        return self


def _TestQ1():
    return MugshotQuiz(('ka','ki','ku','ke','ko'))

def _TestQ2():
    return LineupQuiz(('ka','ki','ku','ke','ko'), ('ta','chi','tsu','te','to'))

def _TestQ3():
    return SketchQuiz( (romaji.choice(family='hiragana'), ) )

def _TestQ4():
    return MatchupQuiz( ('ka', 'ki', 'ku', 'ke', 'ko') )

if __name__ == '__main__':
    app = App('Quiz Demos', (720, 420))
    Piece._shadows = True
    app.graph = { 'MugshotQuiz': _TestQ3,
                  'SketchQuiz': _TestQ2,
                  'LineupQuiz': _TestQ1,
                  'MatchupQuiz': _TestQ4,
                  } = _TestQ4( )

Contact Ed Halley by email at
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!