diff options
author | Wade Brainerd <wadetb@gmail.com> | 2009-02-20 07:50:20 (GMT) |
---|---|---|
committer | Wade Brainerd <wadetb@gmail.com> | 2009-02-20 07:50:20 (GMT) |
commit | 4e79303a6597103202bfdea7d680e49c2fa73cb7 (patch) | |
tree | b9741c9ff51648a6fc0dc221933999bdc819b66a /lessonbuilder.py | |
parent | accec322b99671fbf085471bc4b7018556e31733 (diff) |
Make lessonbuilder strings show up in POT.
Add Nepali translation.
Diffstat (limited to 'lessonbuilder.py')
-rwxr-xr-x | lessonbuilder.py | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/lessonbuilder.py b/lessonbuilder.py new file mode 100755 index 0000000..dae1213 --- /dev/null +++ b/lessonbuilder.py @@ -0,0 +1,566 @@ +#!/usr/bin/env python +# vi: sw=4 et +# Copyright 2008 by Kate Scheppke and Wade Brainerd. +# This file is part of Typing Turtle. +# +# Typing Turtle is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Typing Turtle is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Typing Turtle. If not, see <http://www.gnu.org/licenses/>. + +import os, sys, random, simplejson, locale, re, optparse +from gettext import gettext as _ + +# For modifier constants. +import gtk + +# Set up remote debugging. +#import dbgp.client +#dbgp.client.brkOnExcept(host='192.168.1.104', port=12900) + +# Import keyboard data. +import keyboard + +def error(s): + print "lessonbuilder: ERROR: ", s + print "The lesson could not be generated, exiting.\n\n" + sys.exit(1) + +CONGRATS = [ + _('Well done!'), + _('Good job.'), + _('Awesome!'), + _('Way to go!'), + _('Wonderful!'), + _('Nice work.'), + _('You did it!'), +] + +def get_congrats(): + return random.choice(CONGRATS) + ' ' + +HINTS = [ + _('Be careful to use the correct finger to press each key. Look at the keyboard below if you need help remembering.'), + _('Try to type at the same speed, all the time. As you get more comfortable you can increase the speed a little.') +] + +def get_hint(): + return random.choice(HINTS) + +FINGERS = { + 'LP': _('left little'), + 'LR': _('left ring'), + 'LM': _('left middle'), + 'LI': _('left index'), + 'LT': _('left thumb'), + 'RP': _('right little'), + 'RR': _('right ring'), + 'RM': _('right middle'), + 'RI': _('right index'), + 'RT': _('right thumb'), +} + +def make_all_triples(keys): + text = '' + for k in new_keys: + text += k + k + ' ' + k + ' ' + return text.strip() + +def make_all_doubles(keys): + text = '' + for k in new_keys: + text += k + k + ' ' + return text.strip() + +def make_random_triples(keys, count): + text = '' + for y in xrange(0, count): + k = random.choice(keys) + text += k + k + ' ' + k + ' ' + return text.strip() + +def make_random_doubles(keys, count): + text = '' + for y in xrange(0, count): + k = random.choice(keys) + text += k + k + ' ' + return text.strip() + +def make_jumbles(required_keys, keys, count, width): + text = '' + for y in range(0, count): + # Alternating between required and non-required. Is this too challenging to type? + for x in range(0, width/2): + text += random.choice(required_keys) + text += random.choice(keys) + text += ' ' + return text.strip() + +def make_all_pairs(keys): + text = '' + for k1 in keys: + for k2 in keys: + text += k1 + k2 + ' ' + for k2 in keys: + text += k2 + k1 + ' ' + return text.strip() + +def make_random_pairs(required_keys, keys, count): + text = '' + for y in xrange(0, count): + k1 = random.choice(required_keys) + k2 = random.choice(keys) + text += random.choice([k1 + k2, k2 + k1]) + ' ' + return text.strip() + +def make_all_joined_pairs(keys1, keys2): + text = '' + for k1 in keys1: + for k2 in keys2: + text += k1 + k2 + ' ' + for k2 in keys2: + text += k2 + k1 + ' ' + return text.strip() + +RE_WHITESPACE = re.compile('\s+', re.UNICODE) + +def load_wordlist(path): + try: + text = unicode(open(path, 'r').read()) + + # Split words by whitespace characters. + # This preserves partial punctuation in some words, which is + # intentional since we want to teach punctuation in its natural + # form. + words = RE_WHITESPACE.split(text) + + return words + + except: + return [] + +def get_pairs_from_wordlist(words): + print 'Calculating common pairs...' + + # Construct char_map, a map for each character c0 in words, giving the frequency of each other + # character c1 in words following c0. + char_map = {} + for word in words: + for i in xrange(0, len(word)-1): + c0 = word[i] + c1 = word[i+1] + + c0_map = char_map.setdefault(c0, {}) + c1_value = c0_map.setdefault(c1, 0) + c0_map[c1] = c1_value + 1 + + # Convert to list of pairs with probability. + pairs = [] + for c0, c0_map in char_map.items(): + for c1, c1_value in c0_map.items(): + pairs.append((c0+c1, c1_value)) + + # Sort by frequency. + #pairs.sort(cmp=lambda x,y: x[1] - y[1]) + + # Normalize the weights. + #total = 0.0 + #for p in pairs: + # total += p[1] + #pairs = [(p[0], p[1]/total) for p in pairs] + + return pairs + +def filter_pairs(pairs, required_keys, keys): + # Require at least one key from required_keys, and require that only + # keys be present. + good_pairs = [] + for p in pairs: + str = p[0] + if required_keys.find(str[0]) == -1 and required_keys.find(str[1]) == -1: + continue + if keys.find(str[0]) == -1 or keys.find(str[1]) == -1: + continue + good_pairs.append(p) + + # Re-normalize weights. + total = 0.0 + for p in good_pairs: + total += p[1] + good_pairs = [(p[0], p[1]/total) for p in good_pairs] + + return good_pairs + +def get_weighted_random_pair(pairs): + # TODO: I'm currently ignoring the weighting because it's preventing certain keys + # from ever appearing due to their unpopularity, for example j never appears in the + # home row lesson. + return random.choice(pairs) + #n = random.uniform(0, 1) + #for p in pairs: + # if n < p[1]: + # break + # n -= p[1] + #return p + +def make_weighted_wordlist_pairs(pairs, required_keys, keys, count): + good_pairs = filter_pairs(pairs, required_keys, keys) + + if len(good_pairs) == 0: + return make_random_pairs(required_keys, keys, count) + + text = '' + for y in xrange(0, count): + p = get_weighted_random_pair(good_pairs) + text += p[0] + ' ' + return text.strip() + +def filter_wordlist(words, all_keys, req_keys, minlen, maxlen, bad_words): + print 'Filtering word list...' + + # Uniquify words. + # TODO: Build a frequency table as with the pairs. + words = list(set(words)) + + # Filter word list based on variety of contraints. + good_words = [] + + unknown_re = re.compile('[^'+re.escape(all_keys)+']') + req_re = re.compile('['+re.escape(req_keys)+']') + + for word in words: + if len(word) < minlen or len(word) > maxlen: + continue + + # Check for letters that are not supported. + if unknown_re.search(word): + continue + + # Make sure at least one required letter is present. + if not req_re.search(word): + continue + + # Remove bad words. + if word in bad_words: + continue + + good_words.append(word) + + return good_words + +def make_random_words(words, required_keys, keys, count): + text = '' + for x in range(0, count): + text += random.choice(words) + ' ' + return text.strip() + +def make_step(instructions, mode, text): + step = {} + step['instructions'] = instructions + step['text'] = text + step['mode'] = mode + return step + +def build_game_words( + new_keys, base_keys, + words, bad_words): + + all_keys = new_keys + base_keys + + good_words = filter_wordlist(words=words, + all_keys=all_keys, req_keys=new_keys, + minlen=2, maxlen=8, + bad_words=bad_words) + + random.shuffle(good_words) + + return good_words[:200] + +def build_key_steps( + count, new_keys, base_keys, + words, bad_words): + + all_keys = new_keys + base_keys + + good_words = filter_wordlist(words=words, + all_keys=all_keys, req_keys=new_keys, + minlen=2, maxlen=8, + bad_words=bad_words) + + pairs = get_pairs_from_wordlist(good_words) + + steps = [] + + kb = keyboard.KeyboardData() + kb.set_layout(keyboard.OLPC_LAYOUT) + + keynames = new_keys[0] + if len(new_keys) >= 2: + for k in new_keys[1:-1]: + keynames += ', ' + k + keynames += _(' and %s keys') % new_keys[-1] + else: + keynames += _(' key') + + steps.append(make_step( + _('In this lesson, you will learn the %(keynames)s.\n\nPress the ENTER key when you are ready to begin!') \ + % { 'keynames': keynames }, + 'key', '\n')) + + for letter in new_keys: + key, state, group = kb.get_key_state_group_for_letter(letter) + + if not key: + error("There is no key combination in the current keymap for the '%s' letter. " % letter + \ + "Are you sure the keymap is set correctly?\n") + + try: + finger = FINGERS[key['key-finger']] + except: + error("The '%s' letter (scan code %x) does not have a finger assigned." % (letter, key['key-scan'])) + + if state == gtk.gdk.SHIFT_MASK: + # Choose the finger to press the SHIFT key with. + if key['key-finger'][0] == 'R': + shift_finger = FINGERS['LP'] + else: + shift_finger = FINGERS['RP'] + + instructions = _('Press and hold the SHIFT key with your %(finger)s finger, ') % { 'finger': shift_finger } + instructions += _('then press the %(letter)s key with your %(finger)s finger.') % { 'letter': letter, 'finger': finger } + + elif state == gtk.gdk.MOD5_MASK: + instructions = _('Press and hold the ALTGR key, ') + instructions += _('then press the %(letter)s key with your %(finger)s finger.') % { 'letter': letter, 'finger': finger } + + elif state == gtk.gdk.SHIFT_MASK | gtk.gdk.MOD5_MASK: + instructions = _('Press and hold the ALTGR and SHIFT keys, ') + instructions += _('then press the %(letter)s key with your %(finger)s finger.') % { 'letter': letter, 'finger': finger } + + else: + instructions = _('Press the %(letter)s key with your %(finger)s finger.') % { 'letter': letter, 'finger': finger } + + steps.append(make_step(instructions, 'key', letter)) + + steps.append(make_step( + get_congrats() + _('Practice typing the keys you just learned.'), + 'text', make_random_doubles(new_keys, count))) + + steps.append(make_step( + get_congrats() + _('Now put the keys together into pairs.'), + 'text', make_weighted_wordlist_pairs(pairs, new_keys, all_keys, count))) + + if len(good_words) == 0: + steps.append(make_step( + get_congrats() + _('Time to type jumbles.'), + 'text', make_jumbles(new_keys, all_keys, count, 5))) + + else: + steps.append(make_step( + get_congrats() + _('Time to type real words.'), + 'text', make_random_words(good_words, new_keys, all_keys, count))) + + text = '$report' + steps.append(make_step(text, 'key', ' ')) + + return steps + +def build_intro_steps(): + steps = [] + + text = '' + text += _('Hihowahyah! Ready to learn the secret of fast typing?\n') + text += _('Always use the correct finger to press each key!\n\n') + text += _('Now, place your hands on the keyboard just like the picture below.\n') + text += _('When you\'re ready, press the SPACE bar with your thumb!') + steps.append(make_step(text, 'key', ' ')) + + text = '' + text += _('Good job! The SPACE bar is used to insert spaces between words.\n\n') + text += _('Press the SPACE bar again with your thumb.') + steps.append(make_step(text, 'key', ' ')) + + text = '' + text += _('Now I\'ll teach you the second key, ENTER. ') + text += _('That\'s the big square key near your right little finger.\n\n') + text += _('Now, reach your little finger over and press ENTER.') + steps.append(make_step(text, 'key', '\n')) + + text = '' + text += _('Great! When typing, the ENTER key is used to begin a new line.\n\n') + text += _('Press the ENTER key again with your right little finger.') + steps.append(make_step(text, 'key', '\n')) + + text = '' + text = '$report' + steps.append(make_step(text, 'key', '\n')) + + return steps + +def build_text_step(path): + steps = [] + + instructions = _('Copy out the following text.') + + try: + text = unicode(open(path, 'r').read()) + except: + text = '' + + steps.append(make_step(instructions, 'text', text)) + + return steps + +def main(): + parser = optparse.OptionParser("usage: %prog [options]") + + parser.add_option("--title", dest="name", default="Generated", + help="Lesson title.") + parser.add_option("--desc", dest="desc", default="Default description.", + help="Lesson description. Use \\n to break lines.") + parser.add_option("--order", dest="order", type="int", metavar="N", default=0, + help="Order of this lesson in the index.") + parser.add_option("--seed", dest="seed", type="int", metavar="N", default=0x12345678, + help="Random seed.") + parser.add_option("--locale", dest="locale", + help="Lesson locale (overrides system setting).") + parser.add_option("--keys", dest="keys", metavar="KEYS", default='', + help="Keys to teach.") + parser.add_option("--base-keys", dest="base_keys", metavar="KEYS", default='', + help="Keys already taught prior to this lesson.") + parser.add_option("--length", dest="length", type="int", metavar="N", default=60, + help="Length of the lesson. Default 60.") + parser.add_option("--wordlist", dest="wordlist", metavar="FILE", + help="Text file containing words to use.") + parser.add_option("--badwordlist", dest="badwordlist", metavar="FILE", + help="Text file containing words *not* to use.") + parser.add_option("--text-file", dest="text_file", metavar="FILE", + help="Text file containing the lesson text.") + parser.add_option("--game", dest="game", metavar="TYPE", default='balloon', + help="Type of game to use. Currently just 'balloon'.") + parser.add_option("--output", dest="output", metavar="FILE", + help="Output file (*.lesson).") + + type_group = optparse.OptionGroup(parser, + 'Lesson Types', + 'You must pass a lesson type to control the kind of lesson created.') + type_group.add_option("--intro-lesson", dest="make_intro_lesson", action="store_true", + help="Generate the introductory lesson.") + type_group.add_option("--text-lesson", dest="make_text_lesson", action="store_true", + help="Generate a lesson from a text source such as a paragraph.") + type_group.add_option("--key-lesson", dest="make_key_lesson", action="store_true", + help="Generate a lesson to teach a specific set of keys.") + type_group.add_option("--game-lesson", dest="make_game_lesson", action="store_true", + help="Generate a lesson which plays a game.") + parser.add_option_group(type_group) + + medal_group = optparse.OptionGroup(parser, + 'Medal Requirements', + 'Pass these arguments to set medal requirements.') + medal_group.add_option("--bronze-wpm", dest="bronze_wpm", type="int", metavar="N", default=15, + help="Words per minute for a Bronze medal. Default 15.") + medal_group.add_option("--silver-wpm", dest="silver_wpm", type="int", metavar="N", default=20, + help="Words per minute for a Silver medal. Default 20.") + medal_group.add_option("--gold-wpm", dest="gold_wpm", type="int", metavar="N", default=25, + help="Words per minute for a Gold medal. Default 25.") + medal_group.add_option("--bronze-acc", dest="bronze_accuracy", type="int", metavar="N", default=70, + help="Accuracy for a Bronze medal. Default 70.") + medal_group.add_option("--silver-acc", dest="silver_accuracy", type="int", metavar="N", default=80, + help="Accuracy for a Silver medal. Default 80.") + medal_group.add_option("--gold-acc", dest="gold_accuracy", type="int", metavar="N", default=90, + help="Accuracy for a Gold medal. Default 90.") + medal_group.add_option("--bronze-score", dest="bronze_score", type="int", metavar="N", default=3000, + help="Game score for a Bronze medal. Default 3000.") + medal_group.add_option("--silver-score", dest="silver_score", type="int", metavar="N", default=4500, + help="Game score for a Silver medal. Default 4500.") + medal_group.add_option("--gold-score", dest="gold_score", type="int", metavar="N", default=6000, + help="Game score for a Gold medal. Default 6000.") + parser.add_option_group(medal_group) + + (options, args) = parser.parse_args() + + if not (options.make_intro_lesson or options.make_key_lesson or options.make_game_lesson): + parser.error('no lesson type given') + + if not options.output: + parser.error('no output file given') + + # Set up localization. + if options.locale: + locale.setlocale(locale.LC_ALL, options.locale) + else: + locale.setlocale(locale.LC_ALL, '') + + words = load_wordlist(options.wordlist) + + bad_words = [] + if options.badwordlist: + bad_words = load_wordlist(options.badwordlist) + + # Convert string arguments to Unicode. + options.name = unicode(options.name) + options.keys = unicode(options.keys) + options.base_keys = unicode(options.base_keys) + options.desc = unicode(options.desc.replace('\\n', '\n')) + + random.seed(options.seed) + + print "Building lesson '%s'..." % options.name + + lesson = {} + lesson['name'] = options.name + lesson['description'] = options.desc + lesson['order'] = options.order + + lesson['medals'] = [ + { 'name': 'bronze', 'wpm': options.bronze_wpm, 'accuracy': options.bronze_accuracy, 'score': options.bronze_score }, + { 'name': 'silver', 'wpm': options.silver_wpm, 'accuracy': options.silver_accuracy, 'score': options.silver_score }, + { 'name': 'gold', 'wpm': options.gold_wpm, 'accuracy': options.gold_accuracy, 'score': options.gold_score }, + ] + + if options.make_intro_lesson: + lesson['type'] = 'normal' + lesson['steps'] = build_intro_steps() + + elif options.make_key_lesson: + if not options.wordlist: + parser.error('no wordlist file given') + + lesson['type'] = 'normal' + lesson['steps'] = build_key_steps( + count=options.length, new_keys=options.keys, base_keys=options.base_keys, + words=words, bad_words=bad_words) + + elif options.make_text_lesson: + lesson['type'] = 'normal' + lesson['steps'] = build_text_step(options.text_file) + + elif options.make_game_lesson: + if not options.wordlist: + parser.error('no wordlist file given') + + lesson['type'] = options.game + lesson['length'] = options.length + lesson['words'] = build_game_words( + new_keys=options.keys, base_keys=options.base_keys, + words=words, bad_words=bad_words) + + text = simplejson.dumps(lesson, sort_keys=True, indent=4) + + open(options.output, 'w').write(text) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print "Ctrl-C detected, aborting." + sys.exit(1) + |