diff options
author | Wade Brainerd <wadetb@gmail.com> | 2008-11-20 23:16:10 (GMT) |
---|---|---|
committer | Wade Brainerd <wadetb@gmail.com> | 2008-11-20 23:16:10 (GMT) |
commit | 3613818bd495525defe448789efe73e6438ee775 (patch) | |
tree | 4fa569dffc9e5dbba6394210a960653aba924de4 /typingturtle.py | |
parent | e3e3449a83546a579e9a77b906ee6ffc7015bca0 (diff) |
Home row lesson started - it's a lot of work! Some other small bug fixes, thinking about code reorganization & layout issues.
Diffstat (limited to 'typingturtle.py')
-rwxr-xr-x | typingturtle.py | 266 |
1 files changed, 168 insertions, 98 deletions
diff --git a/typingturtle.py b/typingturtle.py index 10c88f2..3459e7a 100755 --- a/typingturtle.py +++ b/typingturtle.py @@ -17,7 +17,7 @@ """Typing Turtle - Interactive typing tutor for the OLPC XO.""" # Import standard Python modules. -import logging, os, math, time, copy, json, locale, datetime +import logging, os, math, time, copy, json, locale, datetime, random from gettext import gettext as _ # Set up localization. @@ -43,53 +43,64 @@ import keyboard # Paragraph symbol unicode character. PARAGRAPH_CODE = u'\xb6' +# Maximium width of a text line in text lesson mode. +LINE_LENGTH = 80 + +# Requirements for earning medals. +# Words per minute goals came from http://en.wikipedia.org/wiki/Words_per_minute. +MEDALS = [ + { 'name': 'bronze', 'wpm': 25, 'accuracy': 75 }, + { 'name': 'silver', 'wpm': 35, 'accuracy': 85 }, + { 'name': 'gold', 'wpm': 45, 'accuracy': 95 } +] + class MedalScreen(gtk.EventBox): def __init__(self, medal, activity): gtk.EventBox.__init__(self) - + self.modify_bg(gtk.STATE_NORMAL, self.get_colormap().alloc_color('#ffffff')) - + self.medal = medal self.activity = activity - + cert0 = gtk.Label() cert0.set_markup("<span size='35000'><b><i>" + _('Certificate of\nAchievement') + "</i></b></span>") - + cert1 = gtk.Label() cert1.set_markup("<span size='18000'>" + (_('This certifies that on <i><b><u>%(date)s</u></b></i>,\n<i><b><u>%(nick)s</u></b></i> earned a %(type)s medal\nin Typing Turtle lesson <i><b><u>%(lesson)s</u></b></i>.') % medal) + "</span>") - + wpmlabel = gtk.Label() wpmlabel.set_markup("<span size='18000'>" + (_('<b>Words Per Minute:</b> %(wpm)d') % medal) + "</span>" ) - + accuracylabel = gtk.Label() accuracylabel.set_markup("<span size='15000'>" + (_('<b>Accuracy:</b> %(accuracy)d%%') % medal) + "</span>" ) - + statbox = gtk.HBox() statbox.pack_start(wpmlabel, True) statbox.pack_start(accuracylabel, True) - + oklabel = gtk.Label() oklabel.set_markup("<span size='10000'>" + _('Go Back') + '</span>') okbtn = gtk.Button() okbtn.add(oklabel) okbtn.connect('clicked', self.ok_cb) - + btnbox = gtk.HBox() btnbox.pack_start(okbtn, True, False) - + vbox = gtk.VBox() - + vbox.pack_start(cert0, True, False, 0) vbox.pack_start(cert1, False, False, 0) vbox.pack_start(gtk.HSeparator(), False, False, 20) vbox.pack_start(statbox, False, False, 0) vbox.pack_start(gtk.HSeparator(), False, False, 20) vbox.pack_start(btnbox, False, False, 40) - + self.add(vbox) - + self.show_all() def ok_cb(self, widget): @@ -98,55 +109,56 @@ class MedalScreen(gtk.EventBox): class LessonScreen(gtk.VBox): def __init__(self, lesson, activity): gtk.VBox.__init__(self) - + self.lesson = lesson self.activity = activity - + # Build the user interface. title = gtk.Label() title.set_markup("<span size='20000'><b>" + lesson['name'] + "</b></span>") title.set_alignment(1.0, 0.0) - + stoplabel = gtk.Label(_('Go Back')) stopbtn = gtk.Button() stopbtn.add(stoplabel) stopbtn.connect('clicked', self.stop_cb) - + # TODO- These will be replaced by graphical displays using gtk.DrawingArea. self.wpmlabel = gtk.Label() self.accuracylabel = gtk.Label() - + #self.wpmarea = gtk.DrawingArea() #self.wpmarea.connect('expose-event', self.wpm_expose_cb) #self.accuracyarea = gtk.DrawingArea() #self.accuracyarea.connect('expose-event', self.accuracy_expose_cb) - + hbox = gtk.HBox() hbox.pack_start(stopbtn, False, False, 10) hbox.pack_start(self.wpmlabel, True, False, 10) hbox.pack_start(self.accuracylabel, True, False, 10) hbox.pack_end(title, False, False, 10) - + # Set up font styles. self.tagtable = gtk.TextTagTable() instructions_tag = gtk.TextTag('instructions') - instructions_tag.props.size = 10000 - + #instructions_tag.props.size = 10000 + instructions_tag.props.justification = gtk.JUSTIFY_CENTER + self.tagtable.add(instructions_tag) text_tag = gtk.TextTag('text') text_tag.props.family = 'Monospace' self.tagtable.add(text_tag) - + correct_copy_tag = gtk.TextTag('correct-copy') correct_copy_tag.props.family = 'Monospace' correct_copy_tag.props.foreground = '#0000ff' self.tagtable.add(correct_copy_tag) - + incorrect_copy_tag = gtk.TextTag('incorrect-copy') incorrect_copy_tag.props.family = 'Monospace' incorrect_copy_tag.props.foreground = '#ff0000' self.tagtable.add(incorrect_copy_tag) - + # Set up the scrolling lesson text view. self.lessonbuffer = gtk.TextBuffer(self.tagtable) self.lessontext = gtk.TextView(self.lessonbuffer) @@ -158,48 +170,50 @@ class LessonScreen(gtk.VBox): self.lessonscroll = gtk.ScrolledWindow() self.lessonscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) self.lessonscroll.add(self.lessontext) - + frame = gtk.Frame() frame.add(self.lessonscroll) - + self.keyboard = keyboard.Keyboard(self.activity) self.keyboard.set_layout(keyboard.DEFAULT_LAYOUT) - + activity.add_events(gtk.gdk.KEY_PRESS_MASK) activity.connect('key-press-event', self.key_press_cb) - + self.pack_start(hbox, False, False, 10) self.pack_start(frame, True, True) self.pack_start(self.keyboard, True) - + self.show_all() - + self.begin_lesson() - def begin_lesson(self): - self.total_keys = 0 - self.correct_keys = 0 - self.incorrect_keys = 0 - - self.start_time = None - - self.next_step_idx = 0 - self.advance_step() - def update_stats(self): self.total_time = time.time() - self.start_time if self.total_time >= 1.0: - self.wpm = 60 * (self.correct_keys / 10) / self.total_time + self.wpm = 60 * (self.correct_keys / 5) / self.total_time else: self.wpm = 1.0 self.accuracy = 100.0 * self.correct_keys / self.total_keys - + self.accuracylabel.set_markup(_('<b>Accuracy:</b> %(accuracy)d%%') % { 'accuracy' : int(self.accuracy) } ) self.wpmlabel.set_markup(_('<b>WPM:</b> %(wpm)d') % { 'wpm': int(self.wpm) } ) - def wrap_line(self, line): - LINE_LENGTH = 80 + def begin_lesson(self): + self.lesson_finished = False + + self.medal = None + + self.total_keys = 0 + self.correct_keys = 0 + self.incorrect_keys = 0 + + self.start_time = None + + self.next_step_idx = 0 + self.advance_step() + def wrap_line(self, line): words = line.split(' ') new_lines = [] cur_line = '' @@ -229,41 +243,44 @@ class LessonScreen(gtk.VBox): # Clear step related variables. self.step = None self.step_type = None - + self.text = None self.line = None self.line_marks = None - + self.key_expected = None - + # End lesson if this is the last step. if self.next_step_idx >= len(self.lesson['steps']): - self.finish_lesson() + self.lesson_finished = True + self.show_lesson_report() return # TODO - Play 'step finished' sound here. self.step = self.lesson['steps'][self.next_step_idx] self.next_step_idx = self.next_step_idx + 1 - - self.step_type = self.step['type'] + + if len(self.step['text']) == 1: + self.step_type = 'key' + else: + self.step_type = 'text' if self.step_type == 'text': # Clear the text buffer and output the instructions. self.lessonbuffer.set_text('') self.lessonbuffer.insert_with_tags_by_name( - self.lessonbuffer.get_end_iter(), self.step['instructions'] + '\n\n', 'instructions') + self.lessonbuffer.get_end_iter(), '\n' + self.step['instructions'] + '\n\n', 'instructions') - # Replace newlines with paragraph marks. self.text = unicode(self.step['text']) # Split text into lines. - self.lines = self.text.split('\n') + self.lines = self.text.splitlines(True) - # Append paragraph codes. - self.lines = [l + PARAGRAPH_CODE for l in self.lines] + # Substitute paragraph codes. + self.lines = [l.replace('\n', PARAGRAPH_CODE) for l in self.lines] - # TODO: Split by line length in addition to by paragraphs. + # Split by line length in addition to by paragraphs. for i in range(0, len(self.lines)): line = self.lines[i] if len(line) > 30: @@ -288,11 +305,11 @@ class LessonScreen(gtk.VBox): # Clear the text buffer and output the instructions. self.lessonbuffer.set_text('') self.lessonbuffer.insert_with_tags_by_name( - self.lessonbuffer.get_end_iter(), self.step['instructions'] + '\n\n', 'instructions') - + self.lessonbuffer.get_end_iter(), '\n' + self.step['instructions'] + '\n\n', 'instructions') + # Prepare the key. - self.key_expected = unicode(self.step['text'][0]) - + self.key_expected = unicode(self.step['text'][0]).replace('\n', PARAGRAPH_CODE) + # Hilite the key on the virtual keyboard. self.keyboard.clear_hilite() key = self.keyboard.find_key_by_letter(self.key_expected) @@ -302,7 +319,7 @@ class LessonScreen(gtk.VBox): def begin_line(self): self.line = self.lines[self.line_idx] self.line_mark = self.line_marks[self.line_idx] - + self.char_idx = 0 self.hilite_next_key() @@ -311,22 +328,28 @@ class LessonScreen(gtk.VBox): # Ignore hotkeys. if event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.MOD1_MASK): return - + # Extract information about the key pressed. key = gtk.gdk.keyval_to_unicode(event.keyval) if key != 0: key = chr(key) key_name = gtk.gdk.keyval_name(event.keyval) - + + # Simply wait for a return keypress on the lesson finished screen. + if self.lesson_finished: + if key_name == 'Return': + self.end_lesson() + return + # Convert Return keys to paragraph symbols. if key_name == 'Return': key = PARAGRAPH_CODE - + print "key_press_cb: key=%s key_name=%s event.keyval=%d" % (key, key_name, event.keyval) - + # Timer starts with first keypress. if not self.start_time: self.start_time = time.time() - + # Handle backspace by deleting text and optionally moving up lines. if self.step_type == 'text': if key_name == 'BackSpace': @@ -334,43 +357,43 @@ class LessonScreen(gtk.VBox): if self.char_idx == 0 and self.line_idx > 0: self.line_idx -= 1 self.begin_line() - + self.char_idx = len(self.line) - + # Then delete the current character. if self.char_idx > 0: self.char_idx -= 1 - + iter = self.lessonbuffer.get_iter_at_mark(self.line_mark) iter.forward_chars(self.char_idx) - + iter_end = iter.copy() iter_end.forward_char() - + self.lessonbuffer.delete(iter, iter_end) # Process normal key presses. elif key != 0: - + # Check to see if they pressed the correct key. if key == self.line[self.char_idx]: tag_name = 'correct-copy' self.correct_keys += 1 self.total_keys += 1 - + else: # TODO - Play 'incorrect key' sound here. - + tag_name = 'incorrect-copy' self.incorrect_keys += 1 self.total_keys += 1 - + # Insert the key into the bufffer. iter = self.lessonbuffer.get_iter_at_mark(self.line_mark) iter.forward_chars(self.char_idx) - + self.lessonbuffer.insert_with_tags_by_name(iter, key, tag_name) - + # Advance to the next character (or else). self.char_idx += 1 if self.char_idx >= len(self.line): @@ -381,9 +404,9 @@ class LessonScreen(gtk.VBox): self.begin_line() self.update_stats() - + self.hilite_next_key() - + elif self.step_type == 'key': # Check to see if they pressed the correct key. @@ -422,16 +445,11 @@ class LessonScreen(gtk.VBox): self.lessontext.grab_focus() # Scroll the TextView so the cursor is on screen. - mark = self.lessonbuffer.get_insert() - self.lessontext.scroll_to_mark(mark, 0.1) + self.lessontext.scroll_to_mark(self.lessonbuffer.get_insert(), 0.1) - def finish_lesson(self): - self.activity.pop_screen() - + def show_lesson_report(self): self.update_stats() - #self.add_text(_('Congratulations! You finished the lesson in %(time)d seconds.\n\n') % - # { 'time': int(self.total_time) } ) - + lesson_name = self.lesson['name'] # Add to the lesson history. @@ -445,28 +463,30 @@ class LessonScreen(gtk.VBox): # Show the medal screen, if one should be given. got_medal = None - for medal_name in ['bronze', 'silver', 'gold']: - medal = self.lesson['medals'][medal_name] + for medal in MEDALS: if self.wpm >= medal['wpm'] and self.accuracy >= medal['accuracy']: - got_medal = medal_name + got_medal = medal['name'] if got_medal: # Award the medal. medal = { 'lesson': lesson_name, - 'type': medal_name, + 'type': got_medal, 'date': datetime.date.today().strftime('%B %d, %Y'), 'nick': self.activity.owner.props.nick, 'time': self.total_time, 'wpm': report['wpm'], 'accuracy': report['accuracy'] } + self.medal = medal + # Compare this medal with any existing medals for this lesson. + # Only record the best one. add_medal = True if self.activity.data['medals'].has_key(lesson_name): old_medal = self.activity.data['medals'][lesson_name] - order = 'bronze silver gold' + order = ' '.join([m['name'] for m in MEDALS]) add_idx = order.index(medal['type']) old_idx = order.index(old_medal['type']) @@ -478,13 +498,63 @@ class LessonScreen(gtk.VBox): elif medal['accuracy'] == old_medal['accuracy']: if medal['wpm'] < old_medal['wpm']: add_medal = False - + if add_medal: self.activity.data['medals'][lesson_name] = medal self.activity.mainscreen.update_medals() - - # Show the new medal (regardless of whether it was recorded). - self.activity.push_screen(MedalScreen(medal, self.activity)) + + # Display results to the user. + text = '\n' + + congrats = [ + _('Good job!'), + _('Well done!'), + _('Nice work!'), + _('Way to go!') + ] + text += random.choice(congrats) + '\n\n' + + text += _('You finished the lesson in %(time)d seconds, with %(errors)d errors.\n\n') % \ + { 'time': int(self.total_time), 'errors': self.incorrect_keys } + text += _('Your words per minute (WPM) was %(wpm)d, and your accuracy was %(accuracy)d%%.\n\n') % \ + report + + if self.medal: + # TODO: Play medal sound here. + + text += _('Congratulations! You earned a %(type)s medal!\n\nPress Enter to see your certificate.') % \ + medal + + else: + # Comment on what the user needs to do better. + need_wpm = report['wpm'] < self.lesson['medals']['bronze']['wpm'] + need_accuracy = report['accuracy'] < self.lesson['medals']['bronze']['accuracy'] + + if need_accuracy and need_wpm: + text += _('You need to practice this lesson more before moving on. If you are having a hard time, ' + 'repeat the earlier lessons until you have mastered them completely before trying this one ' + 'again.\n\n') + + elif need_accuracy: + text += _('You almost got a medal! Next time, try not to make as many errors!\n\n') + + elif need_wpm: + text += _('You almost got a medal! Next time, try to type a little faster!\n\n') + + text += _('Press Enter to return to the main screen.') + + self.lessonbuffer.set_text('') + self.lessonbuffer.insert_with_tags_by_name( + self.lessonbuffer.get_end_iter(), + text, + 'instructions') + + def end_lesson(self): + self.activity.pop_screen() + + # Show the new medal if there was one. + if self.medal: + self.activity.push_screen(MedalScreen(self.medal, self.activity)) def stop_cb(self, widget): self.activity.pop_screen() |