diff options
Diffstat (limited to 'plugins/_flashcard.py')
-rwxr-xr-x | plugins/_flashcard.py | 728 |
1 files changed, 728 insertions, 0 deletions
diff --git a/plugins/_flashcard.py b/plugins/_flashcard.py new file mode 100755 index 0000000..e1869ac --- /dev/null +++ b/plugins/_flashcard.py @@ -0,0 +1,728 @@ +# Modified to play flashcards from .xml decks in flashcard folder +# Currently +# * Displaying Categories in set language, +# * Pick English Questions + + +import pygame +from pygame import * + +import random +from layout import * +from frontend import hex2rgb +from datetime import datetime +import time +import threading + +from xmlio import Xmlio +import xml.etree.ElementTree as ET +from path import path + +import os + +__PLUGIN_NAME__ = 'flashcard' +QUIZSOURCE = '/home/olpc/Activities/ImageQuizPlus.activity/flashcards/flashcards' +ICONSOURCE = '/home/olpc/Activities/ImageQuizPlus.activity/images' + +class CurrentQuestion: + id = 0 + imgfn = u'' + map = u'' + cat = 0 + subcat = 0 + text = u'' + answer = u'' + answer_link = '' + hint = '' + more = '' + sound = '' + image = '' + +class Answer: + display = True + display_line = False + sound_enabled = True + + link = u'' + text = u'' + + img_found_count = 0 + img_notfound_count = 0 +# imgfn_found = ["Emotes/face-grin.png", "Emotes/face-devil-grin.png", "Emotes/face-glasses.png"] +# imgfn_notfound = ["Emotes/face-monkey.png", "Emotes/face-crying.png"] + imgfn_found = ["Emotes/face-grin.png"] + imgfn_notfound = ["Emotes/face-devil-grin.png"] + random.shuffle(imgfn_found) + random.shuffle(imgfn_notfound) + + found_straight = 0 + straight_count = 0 + icon_left = 0 + bg_icons = pygame.Surface((500, 32)) + bg_icons_straight = pygame.Surface((500, 32)) + + def __init__(self): + self.sound_found = sf.sound_load("accessed.wav") + self.sound_notfound = sf.sound_load("sorry.wav") + + def reset_points(self): + self.found_straight = 0 + self.straight_count = 0 + self.icon_left = 0 + + self.bg_icons.fill(hex2rgb(Layout().Toolbar.background)) + self.bg_icons_straight.fill(hex2rgb(Layout().Toolbar.background)) + + sf.display_surface(self.bg_icons, (540, 10), "toolbar") + sf.display_surface(self.bg_icons_straight, (538, 50), "toolbar") + sf.refresh() + + def add_straight(self): + # 5 in a row -> special reward + self.straight_count += 1 + im, im_rect = sf.image_load("images/%s" % "Icons/bulb.png") + self.bg_icons_straight.blit(im, ((self.straight_count-1) * (im_rect[2] + 4), 0)) + sf.display_surface(self.bg_icons_straight, (538, 50), "toolbar") + sf.refresh() + + def display_icon(self, icon_name): + if icon_name == "found": + self.found_straight += 1 + if self.found_straight == 5: + # Found Straight 5! + self.bg_icons.fill(hex2rgb(Layout().Toolbar.background)) + sf.display_surface(self.bg_icons, (540, 10), "toolbar") + self.add_straight() + self.icon_left = 0 + self.found_straight = 1 + + fn = self.imgfn_found[self.img_found_count % len(self.imgfn_found)] + self.img_found_count += 1 + + elif icon_name == "not-found": + self.found_straight = 0 + fn = self.imgfn_notfound[self.img_notfound_count % len(self.imgfn_notfound)] + self.img_notfound_count += 1 + + img, img_rect = sf.image_load("images/%s" % fn) + self.bg_icons.blit(img, (self.icon_left, 0)) + + sf.display_surface(self.bg_icons, (540, 10), "toolbar") + sf.refresh() + + self.icon_left += img_rect[2] + 4 + + class PlaySoundThread (threading.Thread): + def set(self, sound, interval): + self.sound = sound + self.i = interval + + def run(self): + time.sleep(self.i) + self.sound.play() + + def play_sound(self, i): + if self.sound_enabled: + t = self.PlaySoundThread() + if i == 1: t.set(self.sound_found, 0) + else: t.set(self.sound_notfound, 0.5) + t.start() + + def display_answer(self): + if self.display_line: sf.draw_lines() + if self.display == False: return False + + # Get Top Right Spot of list + print "showing answer" + map_arr = Q.question.map.split(", ") + + # Widths & Heights + bubble_width = 400 + bubble_height = 300 + textfield_width = 270 + textfield_height = 200 + + # Extremas of the Polygone will be saved in here: + x_max = 0 + y_max = 0 + x_min = 1000 + y_min = 1000 + + # Extract Extremas from the polygon + o = [] + a = "x" + for s in map_arr: + if a == "x": a = s + else: + # Take care of border pixels + if int(a) > x_max: x_max = int(a) + if int(s) > y_max: y_max = int(s) + if int(a) < x_min: x_min = int(a) + if int(s) < y_min: y_min = int(s) + a = "x" + + # Set x and y for the Answer Bubble + y_med = (y_min + y_max) / 2 + x_max -= 5 + + y_med = 150 + x_max = 400 + + x_max += Layout().Question.x + Layout().Question.Image.x + 2 + y_med += Layout().Question.y + Layout().Question.Image.y + 2 + +# sf.draw_polygon() + + # Draw Answer Bubble Image & Text + im, im_rect = sf.image_load('images/bubble.gif') + + text_arr = self.text.split(' ') + cur_line = '' + + cur_x = 0 + cur_y = 0 + + # 'textfield' contains the answer text + textfield = pygame.Surface((textfield_width, textfield_height)) + textfield.fill((255, 255, 255)) + + # Make line breaks after reaching width of 'textfield' + for t in text_arr: + cur_line = "%s %s" % (cur_line, t) + + font = pygame.font.Font(None, 38) + n_text = font.render(cur_line, 1, (0,0,0)) + textpos = n_text.get_rect() +# #print cur_line, + + x,y,w,h = list(textpos) + if w > (textfield_width): + textfield.blit(text, (cur_x, cur_y)) + cur_line = t + cur_y += 30 + written = 1 + else: + written = 0 + text = n_text + +# #print textpos + + # Draw leftovers on textfield + if written == 0: + textfield.blit(n_text, (cur_x, cur_y)) + else: + font = pygame.font.Font(None, 38) + n_text = font.render(cur_line, 1, (0,0,0)) + textfield.blit(n_text, (cur_x, cur_y)) + +# #print "draw" + + # Draw on Screen + sf.display_surface(im, (x_max, y_med)) + sf.display_surface(textfield, (x_max+25, y_med+20)) + + pygame.display.update() + + +class Questions: + global path + _cat_id = -1 + lang_id = 0 + lang_name = 0 + in_question = False + + played = 0 + count_won = 0 + count_lost = 0 + questions = [] + + def load(self, lang_id, lang_name): + self.lang_id = lang_id + self.lang_name = lang_name + self.question = CurrentQuestion() + self.load_categories() + + def load_categories(self): + #here we need to get the names of folders and files in the flashcard folder + #make a list 'self.categories' with each entry a list containing: + #cat_id, text (name of category), count, parent_id, base_parent_id + #sorted alphabetically by text (name of category) + decks = path(QUIZSOURCE) + self.categories = [] + for f in decks.files('*.xml'): + tree = Xmlio(f) + root = tree.getroot() + count = len(root) + catid = 1 + parent = len(self.categories) + self.categories.append([catid, f, count, parent, 0]) + self.categories.sort() + + def question_pick(self): + # Check if Questions are left to play + if self.played >= len(self.questions): + # Game / Cat Finished! + return False + + # Okay, Next One! + #print 'next', self.questions[self.played] + self.question.id = self.questions[self.played][0] +# self.question.imgfn = self.questions[self.played][1] + self.question.imgfn = 'bubble.gif' + self.question.map = self.questions[self.played][2] + self.question.cat = self.questions[self.played][3] + self.question.subcat = self.questions[self.played][4] + self.question.text = self.questions[self.played][5] + self.question.answer = self.questions[self.played][6] + self.question.answer_link = self.questions[self.played][7] + self.question.hint = self.questions[self.played][8] + self.question.more = self.questions[self.played][9] + self.question.sound = self.questions[self.played][10] + self.question.image = self.questions[self.played][11] + + A.text = self.question.answer + A.link = self.question.answer_link + + + self.played += 1 + + return True + + def load_questions(self, cat_id): + #print 'cat_id = ', cat_id + # 2. Load Questions + self._cat_id = cat_id[0] + self._subcat_id = cat_id[1] + + self.played = 0 + self.count_won = 0 + self.count_lost = 0 + + #ImageQuiz allows user to select one deck, all decks in category, or all decks + #Assume self.categories is built correctly + files = [] + for cat in self.categories: + if self._cat_id < 0: #all selected + files.append(cat) + elif self._cat_id == cat[3] and self._subcat_id == 0: #all subcategories selected + files.append(cat) + #print 'cat', cat[3], cat[0] + elif self._cat_id == cat[3] and self._subcat_id == cat[0]: #specific subcategory within category selected + files.append(cat) + #print 'subcat', cat[3], cat[0], cat + flashcards = [] + for file in files: + #print 'file', file + pathname = file[1] + #print 'loading', pathname + '\n' + tree = Xmlio(pathname) + deck = tree.getroot() + self.deck = deck + for card in deck: + cardid = int(card.get('id')) + question = card.findtext('question') + question_node = card.find('question') + if question_node: + sound = question_node.findtext('sound') + hint = question_node.findtext('hint') + else: + sound = '' + hint = '' + image = '' + answer_node = card.find('answer') + answer = card.findtext('answer') + if answer_node: + temp = answer_node.findtext('image') + if temp: + image = temp[:-4] + '.png' + else: + image = '' + more = answer_node.findtext('more') + else: + more = '' + image = '' + cat = file[3] + subcat = file[0] + flashcard = [cardid,'','',cat, subcat, question, answer, +'', hint, more, sound, image] + flashcards.append(flashcard) + random.shuffle(flashcards) + self.questions = flashcards + #print 'number of questions=', len(self.questions) + +def show_answer(): + A.display_answer() + +def finished(): + sf.refresh() + sf.add_text_item("You have finished this category!", (180, 80)) + sf.add_text_item("Won: %i" % Q.count_won, (220, 120)) + sf.add_text_item("Lost: %i" % Q.count_lost, (220, 160)) + pygame.display.update() +# ask_category(130) + + +def startgame(db_cat_id): + #print "flashcard" + #print "* Starting Game, Category:", db_cat_id + #print "** Loading Questions" + + Q.count_won = 0 + Q.count_won = 0 + Q.shown = datetime.now() + A.reset_points() + + Q.load_questions(db_cat_id) + display_points() + + sf.clear_text_items() + next_question() + + +def ask_subcat(c): + y = 110 + #print 'subcat=', c + sf.add_text_item("Subcategory:", (580,y)) + y += 50 + + sf.add_text_item('All', (600,y), startgame, (c, 0), True) + + i = 0 + for q in Q.categories: + if q[3] == c: + y += 50 + sf.add_text_item("%s (%s)" % (q[1][1], q[2]), (600,y), startgame, (q[3], q[0]), True) + i += 1 + + #print c + +def ask_category(offset_y = 0): + Q.load(__SERVICES__.locale.lang_id, __SERVICES__.locale.lang_name) + +# sf.clear_text_items() + + i = 1 + y = 110 + offset_y + + sf.add_text_item("Next Category:", (280,y)) + + y += 50 + sf.add_text_item('All', (300,y), startgame, (0, 0), True) + + for q in Q.categories: + #print 'q', q + if q[0] == 1: + fname = q[1].name + sf.add_text_item("%s (%s)" % (fname, q[2]), (300,y + 50 * q[3]), startgame, (q[3], q[0]), True) + i += 1 + + display_points() + +def display_points(): + format = TextFormat(None, 30) + max_q = len(Q.questions) + +# sf.display_tooltip("%i" % (Q.count_won), (480, 14), format) +# sf.display_tooltip("%i" % Q.count_lost, (480, 42), format) + + if max_q == 0: + sf.display_line(0, True) + sf.display_line(0, False) + else: + sf.display_line(100 * Q.count_won / max_q, True) + sf.display_line(100 * Q.count_lost / max_q, False) + pygame.display.update() + +def picture_tl(): + global images + if Q.in_question: + #print images[0], Q.question.image + #print 'tl' + if images[0] == Q.question.image: + response = True + else: + response = False + delete_reacts(response) + +def picture_tr(): + global images + if Q.in_question: + #print 'tr' + #print images[1], Q.question.image + if images[1] == Q.question.image: + response = True + else: + response = False + delete_reacts(response) + +def picture_ll(): + global images + if Q.in_question: + #print 'll' + #print images[2], Q.question.image + if images[2] == Q.question.image: + response = True + else: + response = False + delete_reacts(response) + +def picture_lr(): + global images + if Q.in_question: + #print 'lr' + #print images[3], Q.question.image + if images[3] == Q.question.image: + response = True + else: + response = False + delete_reacts(response) + +def delete_reacts(response): + if response: + correct_answer() + else: + wrong_answer() + return True + +def getpossibles(teststr): + #there may be multiple correct answers separated by a '/' + lst = [] + test = '' + for i in range(len(teststr)): + if teststr[i] != '/': + test = test + teststr[i] + else: + lst.append(test) + test = '' + if len(test) > 0: + lst.append(test) + return lst + +def checkanswer(response): + possibles = getpossibles(Q.question.answer) + return response.strip() in possibles + +def self_test(): + show_answer() + #show two clickable items: smiley face, sad face + image_right, xy = sf.image_load("images/Emotes/face-grin.png") + sf.display_surface(image_right, (750,100)) + image_wrong, xy = sf.image_load("images/Emotes/face-devil-grin.png") + sf.display_surface(image_wrong, (750,150)) + pygame.display.update() + +def play_query_again(): + global query + if Q.in_question: + #print 'play_query' + query.play() + while pygame.mixer.get_busy(): + clock.tick(30) + +def next_question(): + #print "* Next Question" + global images + global query + global rtr, rtl, rll, rlr + + # Select Category + if Q._cat_id == -1: + sf.clear_text_items() + sf.clear_question_frame(True) + ask_category() + #print 'ask_category' + return 1 + + # Pick only locale language's categories + if Q.question_pick(): + sf.clear_text_items() + Renyi = False + #print 'image = ', Q.question.image + Q.in_question = True + Q.starttime = time.time() + if Q.question.image and len(Q.question.image) > 0: + Renyi = True + candidate_images = [] + for question in Q.questions: + candidate_images.append(question[11]) + random.shuffle(candidate_images) + #print 'candidate_images', candidate_images + if Q.question.image in candidate_images[:4]: + images = candidate_images[:4] + else: + images = candidate_images[:3] + images.append(Q.question.image) + random.shuffle(images) + #print 'images', images + #load image + imagepath = path(QUIZSOURCE).joinpath('image') + #print 'imagepath', imagepath + image_tl, xy = sf.image_load(images[0],path = imagepath) + if image_tl == None: + image_tl, xy = sf.image_load(error.png, path = imagepath) + image_tr, xy = sf.image_load(images[1],path = imagepath) + if image_tr == None: + image_tr, xy = sf.image_load(error.png, path = imagepath) + image_ll, xy = sf.image_load(images[2],path = imagepath) + if image_ll == None: + image_ll, xy = sf.image_load(error.png, imagepath) + image_lr, xy = sf.image_load(images[3],path = imagepath) + if image_lr == None: + image_lr, xy = sf.image_load(error.png, path = imagepath) + #display image + sf.display_surface(image_tl, (250, 150)) + sf.display_surface(image_tr, (520,150)) + sf.display_surface(image_ll, (250,420)) + sf.display_surface(image_lr, (520, 420)) + pygame.display.update() + + #print 'sound = ', Q.question.sound + soundpath = path(QUIZSOURCE).joinpath('sound') + soundlst = getpossibles(soundpath) + random.shuffle(soundlst) + iconpath = path(ICONSOURCE) + if Q.question.sound and len(Q.question.sound) > 0: + temp = getpossibles(Q.question.sound) + random.shuffle(temp) + selected = temp[0] + print 'sounds', Q.question.sound, temp, selected + query = sf.sound_load(selected[:-4] + '.ogg', + path=soundpath) + query.play() + while pygame.mixer.get_busy(): + clock.tick(30) + #image_play, xy = sf.image_load("images/play.svg") + + image_play, xy = sf.image_load("play_button.png", path = iconpath) + sf.display_surface(image_play, (300,30)) + pygame.display.update() + + if not Renyi: + response = __SERVICES__.frontend.ask(Q.question.text) + #print 'response=', response + + if len(response) == 0 or len(Q.question.answer) == 0: + self_test() + else: + correct = checkanswer(response) + y = 100 + sf.add_text_item("ok", (750,y), next_question) + if correct: + correct_answer(False) + else: + wrong_answer(False) + + pass + else: + # game / cat finished! + Q.in_question = False + #print "finished" + Q._cat_id = -1 + finished() + pass + +def find_card(cardid): + for card in Q.deck: + if int(card.get('id')) == cardid: + return card + +def correct_answer(next=True): + correct_sounds = ['i-like-it.wav', 'ooh-yeah.wav', 'impressive.wav', 'dramatic_chord.wav', 'sweet.wav', + 'brain-yes.wav', 'mk-excellent.wav', 'that-was-cool.wav', 'bt-excellent.wav', 'groovy.wav', + 'yes-i-like-it.wav', 'burns-excellent.wav', 'oh-yeah.wav'] + #print 'correct answer next =', next, Q.in_question + if Q.in_question: + A.display_icon("found") + #A.play_sound(1) + Q.in_question = False + Q.count_won += 1 + display_points() + good = sf.sound_load(random.choice(correct_sounds)) + good.play() + while pygame.mixer.get_busy(): + clock.tick(30) + #update Leitner attributes + think = '%0.2f' % float( time.time() - Q.starttime) + print 'think',think + card = find_card(Q.question.id) + card.attrib['think'] = think + card.attrib['shown'] = Q.shown + count = int(card.attrib['count']) + count +=1 + card.attrib['count'] = str(count) + bin = int(card.attrib['bin']) + bin += 1 + card.attrib['bin'] = str(bin) + if next: + next_question() + else: + show_answer() + +def wrong_answer(next=True): + wrong_sounds = ['db_forgetaboutit.wav', 'alf_wrong.wav', 'doh.wav', 'sorry.wav', 'awh_man.wav', 'metal_clang_2.wav', + 'excuse_me.wav', 'negative.wav', 'bunny_awful.wav', 'gwarsh.wav', 'not.wav', 'haha.wav', 'oh_no.wav', + 'compute.wav', 'hoo-ah.wav'] + #print 'wrong answer next =', next, Q.in_question + if Q.in_question: + #update Leitner attributes + card = find_card(Q.question.id) + card.attrib['think'] = \ + '%0.2f' % float(time.time() - Q.starttime) + card.attrib['shown'] = Q.shown + card.attrib['bin'] = 1 + count = int(card.attrib['count']) + count += 1 + card.attrib['count'] = str(count) + A.display_icon("not-found") + #A.play_sound(0) + Q.in_question = False + Q.count_lost += 1 + display_points() + bad = sf.sound_load(random.choice(wrong_sounds)) + bad.play() + while pygame.mixer.get_busy(): + clock.tick(30) + if next: + next_question() + else: + show_answer() + +def click_on_flashcards(): + global selectedlanguage + #print 'flashcards selected' + selectedlanguage = 'flashcards' + Q.cat_id = -1 + next_question() +def load(): + global sf + sf = __SERVICES__.frontend; + + global Q + Q = Questions() + + global A + A = Answer() + + global clock + clock = pygame.time.Clock() + + global rtl, rtr, rll, rlr, knew_it, forgot_it, play_again + rtl = sf.add_react(50, 100, 320, 240, picture_tl) + #print 'rtl=', rtl + rtr = sf.add_react(400, 100, 320, 240, picture_tr) + #print 'rtr=', rtr + rll = sf.add_react(50, 360, 320, 240, picture_ll) + #print 'rll=', rll + rlr = sf.add_react(400, 360, 320, 240, picture_lr) + #print 'rlr=', rlr + knew_it = sf.add_react(750,100,50,50, correct_answer) + #print 'knew_it=', knew_it + forgot_it = sf.add_react(750,150,50,50, wrong_answer) + #print 'forgot_it=', forgot_it + play_again = sf.add_react(300,0,100,100, play_query_again) + #print 'play_again=', play_again + + sf.add_menu_item('/', 'Flashcards', click_on_flashcards) + +def close(): + pass |