Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/typingturtle.py
diff options
context:
space:
mode:
Diffstat (limited to 'typingturtle.py')
-rwxr-xr-xtypingturtle.py266
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()