Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO65
-rw-r--r--lessons/en_US/dummy.lesson13
-rw-r--r--lessons/en_US/homerow_intro.lesson2
-rw-r--r--lessons/en_US/long.lesson2
-rwxr-xr-xtypingturtle.py291
5 files changed, 189 insertions, 184 deletions
diff --git a/TODO b/TODO
index ac4fcc7..e9346ce 100644
--- a/TODO
+++ b/TODO
@@ -1,35 +1,32 @@
Typing Turtle
First Release
-- Write tLoser, Wes about developing artwork.
-+ Blinking key hilite in 'key' mode.
-+ WPM meter updated in 1sec timer.
+- Write to Loser, Wes about developing artwork.
- Draw incorrect characters in red.
- Support backspace, backspace to previous lines.
-+ Scrolling TextView in lesson.
-+ Missing spaces at the end of some lines thanks to dodgy word wrap.
+- Scrolling TextView in lesson.
+- Missing spaces at the end of some lines thanks to dodgy word wrap.
- Handle ends of line in a sane manner.
- Implement two step types: key learning and text copying.
- Split text into lines for long lessons.
- Try out an insensitive gtk.Entry instead of the gtk.Label.
-+ Write to authors of prior typing activity to inquire about layered artwork. Need to make a .xo file to demo.
-+ Better flow at the end of a level. Report the result on the Lesson screen: Need more work, Medal received, etc.
+- Better flow at the end of a level. Report the result on the Lesson screen: Need more work, Medal received, etc.
+- Ability of lessons to list medals in other lessons as prerequisites. Disable unavailable lessons.
+- Some sort of lesson sorting criteria.
++ Scroll lessons list to the first non-medaled lesson.
+ Status message on the main screen. "You unlocked a new lesson!" for example. Eventually have the turtle 'say' it.
-+ Scroll lessons list to the first non-medaled lesson?
-+ Implement a long text copying lesson and fix bugs in the scrolling and typing.
-+ Some sort of lesson sorting criteria.
-+ Ability of lessons to list medals in other lessons as prerequisites.
+- Implement a long text copying lesson and fix bugs in the scrolling and typing.
+ Graphical WPM and accuracy meters.
++ WPM meter updated in 1sec timer in addition to on keypress.
- Working medals assignment: "You got a medal!" popup, display next to lesson.
- Nice looking keyboard.
- Highlighted keyboard keys when pressed.
- Support for displaying modifier keys in Keyboard.
- Change key shown when modified is held.
- Indicate next key to press on keyboard.
++ Make medal WPM adjustable somehow? Perhaps a settable Goal WPM?
+ Highlight regions of keyboard, color by finger.
+ Allow lessons to choose between forcing correct keypresses and allowing incorrect ones (with support for Backspace).
-+ History screen: List of lessons completed with statistics.
-+ Progress screen: Line graphs of Accuracy, WPM over time.
+ Artwork and animations.
+ Background picture in main screen.
+ Speed meter picture?
@@ -41,13 +38,45 @@ First Release
+ Sound effects.
+ Welcome to the activity sound.
+ Speed up / slow down sounds when WPM crosses threshold: Slow, Medium, Fast.
- + Medal award sounds for each medal type: Bronze, Silver, Gold.
- + Incorrect key pressed sound.
+ + Medal award sounds for each medal type: None, Bronze, Silver, Gold. Applause sound.
+ + Incorrect key pressed tick sound.
+ Develop lessons.
+ Continue to develop lessons for all keys on the keyboard.
- + Develop 'focus' lessons e.g. fj.
- + Mark some lessons as "locational" versus "textual" and translate from the English keyboard to native. Ex: Home row, Left hand, Numbers, etc.
- + Give each lesson criteria for each medal type based on Accuracy, WPM.
+ - Develop 'focus' lessons e.g. fj.
+ - Mark some lessons as "locational" versus "textual" and translate from the English keyboard to native. Ex: Home row, Left hand, Numbers, etc.
+ - Give each lesson criteria for each medal type based on Accuracy, WPM.
Future Release
+ Goal support with progress reporting. WPM, Accuracy, Entire keyboard learned, etc.
++ Keymap showing skill per key. Star icons on keys that are mastered.
++ History screen: List of lessons completed with statistics.
++ Progress screen: Line graphs of Accuracy, WPM over time.
++ Lesson editor activity or mode.
++ Automatically generate lessons similar to 'home row' based on a list of keys.
+
+Balloon Game
+
++ Create BalloonGameScreen class (use gtk.Layout?).
++ Generate a list of random words, or read from lesson dictionary.
++ Score display.
++ Floating balloons with words on them. Random velocities, "floaty" look.
++ Balloon letters disappear when typed.
++ Balloon pops when word typed, score increased.
++ Rate of balloons increases over time.
++ Game finished popup, displays score and medal text.
+
+Paper Airplane Game
+
++ Create AirplaneGameScreen class (use gtk.Layout?).
++ Implement bottom-scrolling text display.
++ Airplane toss animation (or maybe takeoff from runway?). Starts with first letter.
++ Floating airplane momentum based on words per minute.
++ Scrolling background graphics.
++ Finish line graphics.
++ Game finished popup, displays score and medal text.
+
+Race Game
+
++ Create RaceGameScreen class (use gtk.Layout?).
++ Copy whatever Prakhar's got, finish it up.
++ Game finished popup, displays score and medal text.
diff --git a/lessons/en_US/dummy.lesson b/lessons/en_US/dummy.lesson
index f3049ea..ec780ef 100644
--- a/lessons/en_US/dummy.lesson
+++ b/lessons/en_US/dummy.lesson
@@ -1,16 +1,13 @@
{
- "name": "Dummy",
- "description": "Empty lesson for testing.",
- "medals": {
- "bronze": { "wpm": 0, "accuracy": 60 },
- "silver": { "wpm": 0, "accuracy": 60 },
- "gold": { "wpm": 0, "accuracy": 90 }
- },
+ "name": "Cheat Lesson",
+ "description": "Empty lesson for testing, automatically gives level 100.",
+ "level": 100,
+ "requiredlevel": 0,
"steps": [
{
"type": "key",
"instructions": "Type an x.",
- "text": "x"
+ "text": "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
]
}
diff --git a/lessons/en_US/homerow_intro.lesson b/lessons/en_US/homerow_intro.lesson
index 2320a46..8f674d8 100644
--- a/lessons/en_US/homerow_intro.lesson
+++ b/lessons/en_US/homerow_intro.lesson
@@ -1,6 +1,8 @@
{
"name": "Home Row",
"description": "Teaches the middle row of keys on the keyboard.",
+ "level": 1,
+ "requiredlevel": 0,
"steps": [
{
"instructions": "Welcome to the first Typing Turtle lesson! Place your fingers on the keyboard as shown below.\n\nWhen you are ready to begin, press the f key with your left index finger.",
diff --git a/lessons/en_US/long.lesson b/lessons/en_US/long.lesson
index c4379f1..d05610e 100644
--- a/lessons/en_US/long.lesson
+++ b/lessons/en_US/long.lesson
@@ -1,6 +1,8 @@
{
"name": "Punctuation Practice!",
"description": "Practice punctuation with a section of Pride and Prejudice.",
+ "level": 2,
+ "requiredlevel": 1,
"steps": [
{
"type": "text",
diff --git a/typingturtle.py b/typingturtle.py
index 3459e7a..b24e298 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, random
+import logging, os, math, time, copy, json, locale, datetime, random, re
from gettext import gettext as _
# Set up localization.
@@ -44,7 +44,7 @@ import keyboard
PARAGRAPH_CODE = u'\xb6'
# Maximium width of a text line in text lesson mode.
-LINE_LENGTH = 80
+LINE_WIDTH = 80
# Requirements for earning medals.
# Words per minute goals came from http://en.wikipedia.org/wiki/Words_per_minute.
@@ -214,27 +214,19 @@ class LessonScreen(gtk.VBox):
self.advance_step()
def wrap_line(self, line):
- words = line.split(' ')
+ words = re.split('(\W+)', line)
+
new_lines = []
cur_line = ''
for w in words:
# TODO: Handle single word longer than a line.
- #if len(w) > LINE_LENGTH:
- # new_lines.append(cur_line)
- # while len(w):
- # new_lines.append(w[:LINE_LENGTH])
- # w = w[LINE_LENGTH:]
- # cur_line = ''
- if len(cur_line) + len(w) + 1 > LINE_LENGTH:
+ if not w.isspace() and len(cur_line) + len(w) > LINE_WIDTH:
if len(cur_line):
new_lines.append(cur_line)
- cur_line = ''
- cur_line += w + ' '
+ cur_line = ''
+ cur_line += w
if len(cur_line):
- # Remove trailing spaces from last line before adding.
- while cur_line[-1] == ' ':
- cur_line = cur_line[:-1]
new_lines.append(cur_line)
return new_lines
@@ -242,14 +234,11 @@ class LessonScreen(gtk.VBox):
def advance_step(self):
# 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.lesson_finished = True
@@ -261,61 +250,46 @@ class LessonScreen(gtk.VBox):
self.step = self.lesson['steps'][self.next_step_idx]
self.next_step_idx = self.next_step_idx + 1
- if len(self.step['text']) == 1:
- self.step_type = 'key'
- else:
- self.step_type = 'text'
+ # Clear the text buffer and output the instructions.
+ self.lessonbuffer.insert_with_tags_by_name(
+ self.lessonbuffer.get_end_iter(), '\n\n' + self.step['instructions'] + '\n', 'instructions')
- 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(), '\n' + self.step['instructions'] + '\n\n', 'instructions')
-
- self.text = unicode(self.step['text'])
-
- # Split text into lines.
- self.lines = self.text.splitlines(True)
-
- # Substitute paragraph codes.
- self.lines = [l.replace('\n', PARAGRAPH_CODE) for l in self.lines]
-
- # 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:
- self.lines[i:i+1] = self.wrap_line(line)
+ self.text = unicode(self.step['text'])
+
+ # Split text into lines.
+ self.lines = self.text.splitlines(True)
+
+ # Substitute paragraph codes.
+ self.lines = [l.replace('\n', PARAGRAPH_CODE) for l in self.lines]
+
+ # Split by line length in addition to by paragraphs.
+ for i in range(0, len(self.lines)):
+ line = self.lines[i]
+ if len(line) > LINE_WIDTH:
+ self.lines[i:i+1] = self.wrap_line(line)
+
+ # Center single line steps.
+ indent = ''
+ #if len(self.lines) == 1:
+ # indent = ' ' * ((LINE_LENGTH - len(self.lines[0]))/2)
- # Fill text buffer with text lines, each followed by room for the user to type.
- self.line_marks = {}
- line_idx = 0
- for l in self.lines:
- self.lessonbuffer.insert_with_tags_by_name(
- self.lessonbuffer.get_end_iter(), l.encode('utf-8') + '\n', 'text')
-
- self.line_marks[line_idx] = self.lessonbuffer.create_mark(None, self.lessonbuffer.get_end_iter(), True)
- self.lessonbuffer.insert_at_cursor('\n')
+ # Fill text buffer with text lines, each followed by room for the user to type.
+ self.line_marks = {}
+ line_idx = 0
+ for l in self.lines:
- line_idx += 1
-
- self.line_idx = 0
- self.begin_line()
-
- elif self.step_type == 'key':
- # Clear the text buffer and output the instructions.
- self.lessonbuffer.set_text('')
+ # Add the text to copy.
self.lessonbuffer.insert_with_tags_by_name(
- self.lessonbuffer.get_end_iter(), '\n' + self.step['instructions'] + '\n\n', 'instructions')
+ self.lessonbuffer.get_end_iter(), '\n' + indent + l.encode('utf-8') + '\n' + indent, 'text')
- # Prepare the key.
- self.key_expected = unicode(self.step['text'][0]).replace('\n', PARAGRAPH_CODE)
+ # Leave a marker where we will later insert text.
+ self.line_marks[line_idx] = self.lessonbuffer.create_mark(None, self.lessonbuffer.get_end_iter(), True)
+
+ line_idx += 1
+
+ self.line_idx = 0
+ self.begin_line()
- # Hilite the key on the virtual keyboard.
- self.keyboard.clear_hilite()
- key = self.keyboard.find_key_by_letter(self.key_expected)
- if key:
- key.set_hilite(True)
-
def begin_line(self):
self.line = self.lines[self.line_idx]
self.line_mark = self.line_marks[self.line_idx]
@@ -336,6 +310,7 @@ class LessonScreen(gtk.VBox):
# Simply wait for a return keypress on the lesson finished screen.
if self.lesson_finished:
+ # TODO: Wait a second first.
if key_name == 'Return':
self.end_lesson()
return
@@ -351,81 +326,64 @@ class LessonScreen(gtk.VBox):
self.start_time = time.time()
# Handle backspace by deleting text and optionally moving up lines.
- if self.step_type == 'text':
- if key_name == 'BackSpace':
- # Move to previous line if at the end of the current one.
- 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:
+ if key_name == 'BackSpace':
+ # Move to previous line if at the end of the current one.
+ if self.char_idx == 0 and self.line_idx > 0:
+ self.line_idx -= 1
+ self.begin_line()
- # 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
+ self.char_idx = len(self.line)
+
+ # Then delete the current character.
+ if self.char_idx > 0:
+ self.char_idx -= 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):
- self.line_idx += 1
- if self.line_idx >= len(self.lines):
- self.advance_step()
- else:
- self.begin_line()
+ iter_end = iter.copy()
+ iter_end.forward_char()
- self.update_stats()
-
+ self.lessonbuffer.delete(iter, iter_end)
+
self.hilite_next_key()
-
- elif self.step_type == 'key':
+
+ # Process normal key presses.
+ elif key != 0:
# Check to see if they pressed the correct key.
- if key == self.key_expected:
+ if key == self.line[self.char_idx]:
tag_name = 'correct-copy'
self.correct_keys += 1
self.total_keys += 1
- self.advance_step()
-
else:
# TODO - Play 'incorrect key' sound here.
-
+
tag_name = 'incorrect-copy'
self.incorrect_keys += 1
self.total_keys += 1
- self.update_stats()
+ # 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):
+ self.line_idx += 1
+ if self.line_idx >= len(self.lines):
+ self.advance_step()
+ else:
+ self.begin_line()
+ return
+ self.update_stats()
+
+ self.hilite_next_key()
+
return False
def hilite_next_key(self):
@@ -445,7 +403,7 @@ class LessonScreen(gtk.VBox):
self.lessontext.grab_focus()
# Scroll the TextView so the cursor is on screen.
- self.lessontext.scroll_to_mark(self.lessonbuffer.get_insert(), 0.1)
+ self.lessontext.scroll_to_mark(self.lessonbuffer.get_insert(), 0)
def show_lesson_report(self):
self.update_stats()
@@ -499,7 +457,12 @@ class LessonScreen(gtk.VBox):
if medal['wpm'] < old_medal['wpm']:
add_medal = False
- if add_medal:
+ if add_medal:
+ # Upgrade the player's level if needed.
+ if self.lesson['level'] > self.activity.data['level']:
+ self.activity.data['level'] = self.lesson['level']
+ self.activity.data['motd'] = _('You unlocked a new lesson!')
+
self.activity.data['medals'][lesson_name] = medal
self.activity.mainscreen.update_medals()
@@ -527,8 +490,8 @@ class LessonScreen(gtk.VBox):
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']
+ need_wpm = report['wpm'] < MEDALS[0]['wpm']
+ need_accuracy = report['accuracy'] < MEDALS[0]['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, '
@@ -562,18 +525,18 @@ class LessonScreen(gtk.VBox):
class MainScreen(gtk.VBox):
def __init__(self, activity):
gtk.VBox.__init__(self)
-
+
self.activity = activity
-
+
# Build background.
title = gtk.Label()
title.set_markup("<span size='40000'><b>" + _('Typing Turtle') + "</b></span>")
-
+
subtitle = gtk.Label()
subtitle.set_markup(_('Welcome to Typing Turtle! To begin, select a lesson from the list below.'))
-
+
spacer = gtk.HBox()
-
+
# Lessons header.
headerbox = gtk.VBox()
label = gtk.Label()
@@ -581,15 +544,15 @@ class MainScreen(gtk.VBox):
label.set_markup("<span size='large'><b>"+_('Available Lessons')+"</b></span>")
headerbox.pack_start(label, False)
headerbox.pack_start(gtk.HSeparator(), False)
-
+
# Build lessons list.
self.lessonbox = gtk.VBox()
self.lessonbox.set_spacing(10)
-
+
bundle_path = sugar.activity.activity.get_bundle_path()
code = locale.getlocale(locale.LC_ALL)[0]
path = bundle_path + '/lessons/' + code + '/'
-
+
# Find all .lesson files in ./lessons/en_US/ for example.
lessons = []
for f in os.listdir(path):
@@ -599,47 +562,57 @@ class MainScreen(gtk.VBox):
lessons.append(lesson)
finally:
fd.close()
-
+
+ lessons.sort(lambda x, y: x['level'] - y['level'])
+
for l in lessons:
label = gtk.Label()
label.set_alignment(0.0, 0.5)
label.set_markup("<span size='large'>" + l['name'] + "</span>\n" + l['description'])
-
+
btn = gtk.Button()
btn.lesson = l
btn.add(label)
btn.connect('clicked', self.lesson_clicked_cb)
-
+
medalimage = gtk.Image()
-
+
medalbtn = gtk.Button()
medalbtn.lesson = l
medalbtn.add(medalimage)
medalbtn.connect('clicked', self.medal_clicked_cb)
-
+
hbox = gtk.HBox()
hbox.pack_start(btn, True, True, 10)
hbox.pack_end(medalbtn, False, False)
-
+
+ hbox.button = btn
+ hbox.medalbutton = medalbtn
hbox.lesson = l
hbox.medalimage = medalimage
-
+
self.lessonbox.pack_start(hbox, False)
-
+
self.lessonscroll = gtk.ScrolledWindow()
self.lessonscroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
self.lessonscroll.add_with_viewport(self.lessonbox)
-
+
self.pack_start(title, False, True, 10)
self.pack_start(subtitle, False)
self.pack_start(spacer, False, False, 50)
self.pack_start(headerbox, False)
self.pack_start(self.lessonscroll, True)
-
+
self.update_medals()
def update_medals(self):
for l in self.lessonbox:
+ # Disable the lesson button unless available.
+ lesson_available = self.activity.data['level'] >= l.lesson['requiredlevel']
+ l.button.set_sensitive(lesson_available)
+ l.medalbutton.set_sensitive(lesson_available)
+
+ # Update the medal image.
medal_type = 'none'
if self.activity.data['medals'].has_key(l.lesson['name']):
medal_type = self.activity.data['medals'][l.lesson['name']]['type']
@@ -652,10 +625,10 @@ class MainScreen(gtk.VBox):
'gold': bundle+'/images/gold-medal.jpg'
}
l.medalimage.set_from_file(images[medal_type])
-
+
def lesson_clicked_cb(self, widget):
self.activity.push_screen(LessonScreen(widget.lesson, self.activity))
-
+
def medal_clicked_cb(self, widget):
if self.activity.data['medals'].has_key(widget.lesson['name']):
medal = self.activity.data['medals'][widget.lesson['name']]
@@ -669,29 +642,31 @@ class TypingTurtle(sugar.activity.activity.Activity):
def __init__ (self, handle):
sugar.activity.activity.Activity.__init__(self, handle)
self.set_title(_("Typing Turtle"))
-
+
self.build_toolbox()
-
+
self.screens = []
self.screenbox = gtk.VBox()
-
+
self.owner = presenceservice.get_instance().get_owner()
-
+
# All data which is saved in the Journal entry is placed in this dictionary.
self.data = {
+ 'motd': _('Welcome to Typing Turtle!'),
+ 'level': 0,
'history': [],
'medals': {}
}
-
+
# This has to happen last, because it calls the read_file method when restoring from the Journal.
self.set_canvas(self.screenbox)
-
+
# Start with the main screen.
self.mainscreen = MainScreen(self)
self.push_screen(self.mainscreen)
-
+
self.show_all()
-
+
# Hide the sharing button from the activity toolbar since we don't support sharing.
activity_toolbar = self.tbox.get_activity_toolbar()
activity_toolbar.share.props.visible = False
@@ -699,13 +674,13 @@ class TypingTurtle(sugar.activity.activity.Activity):
def build_toolbox(self):
self.tbox = sugar.activity.activity.ActivityToolbox(self)
self.tbox.show_all()
-
+
self.set_toolbox(self.tbox)
def push_screen(self, screen):
if len(self.screens):
self.screenbox.remove(self.screens[-1])
-
+
self.screenbox.pack_start(screen, True, True)
self.screens.append(screen)
@@ -721,7 +696,7 @@ class TypingTurtle(sugar.activity.activity.Activity):
def read_file(self, file_path):
if self.metadata['mime_type'] != 'text/plain':
return
-
+
fd = open(file_path, 'r')
try:
text = fd.read()
@@ -733,7 +708,7 @@ class TypingTurtle(sugar.activity.activity.Activity):
def write_file(self, file_path):
if not self.metadata['mime_type']:
self.metadata['mime_type'] = 'text/plain'
-
+
fd = open(file_path, 'w')
try:
text = json.write(self.data)