diff options
Diffstat (limited to 'plugins')
-rwxr-xr-x | plugins/_extract.py | 896 | ||||
-rwxr-xr-x | plugins/_flashcard.py | 728 | ||||
-rwxr-xr-x | plugins/_library.py | 198 | ||||
-rwxr-xr-x | plugins/_quizsocket.py | 247 | ||||
-rwxr-xr-x | plugins/dbtool.py | 99 | ||||
-rwxr-xr-x | plugins/demoplugin.py | 25 | ||||
-rwxr-xr-x | plugins/ink.py | 61 | ||||
-rwxr-xr-x | plugins/make.py | 422 | ||||
-rwxr-xr-x | plugins/multi_player.py | 552 | ||||
-rwxr-xr-x | plugins/path.py | 971 | ||||
-rwxr-xr-x | plugins/readme | 11 | ||||
-rwxr-xr-x | plugins/single_player.py | 744 | ||||
-rwxr-xr-x | plugins/tools.py | 100 |
13 files changed, 5054 insertions, 0 deletions
diff --git a/plugins/_extract.py b/plugins/_extract.py new file mode 100755 index 0000000..071a869 --- /dev/null +++ b/plugins/_extract.py @@ -0,0 +1,896 @@ +# 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 +import time +import threading +import shutil + +import xml.etree.ElementTree as ET +import os, sys + +__PLUGIN_NAME__ = 'flashcard' + +# reads and writes xml files +# output files indented + +# import xml.etree.ElementTree as ET + +#tree = Xmlio(path) +#elem = tree.getroot() +#tree.save(path) --- uses root of tree +#tree.save(path,root=elem) -- saves tree from supplied root element + +class Xmlio(): + + def __init__(self, path = '', root = ''): + if root: + if path: + self.root = root + self.tree = ET.ElementTree(self.root) + else: + self.root = ET.Element(root) + self.tree = ET.ElementTree(self.root) + else: + self.tree = ET.parse(path) + self.root = self.tree.getroot() + + def getroot(self, root = ''): + if not root: + return self.root + else: + return ET.Element(root) + + def indent(self, elem, level=0): + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + for elem in elem: + self.indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + def save(self, path, root = ''): + if not root: + root = self.root + self.indent(root) #prettyprint + ET.ElementTree(root).write(path) + +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: + _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 walk( self, root, recurse=0, pattern='*', return_folders=0 ): + import fnmatch, os, string + + # initialize + result = [] + + # must have at least root folder + try: + names = os.listdir(root) + except os.error: + return result + + # expand pattern + pattern = pattern or '*' + pat_list = string.splitfields( pattern , ';' ) + + # check each file + for name in names: + fullname = os.path.normpath(os.path.join(root, name)) + + # grab if it matches our pattern and entry type + for pat in pat_list: + if fnmatch.fnmatch(name, pat): + if os.path.isfile(fullname) or (return_folders and os.path.isdir(fullname)): + result.append(fullname) + continue + + # recursively scan other folders, appending results + if recurse: + if os.path.isdir(fullname) and not os.path.islink(fullname): + result = result + self.walk( fullname, recurse, pattern, return_folders ) + + return result + + def path_strip(self, pathname): + """pathname = path[0] + path[1] + path[2] - splits pathname into components""" + # get part after last /, if any + filename = os.path.basename(pathname) + #decktype determines whether it is a deck (.xml) or superdeck (.sbj) + decktype = filename[-4:] + #deckname strips off path + deckname = filename[:-4] + path = (pathname[:-len(filename)],deckname,decktype) + return path + + def load_categories(self): + global selectedlanguage + #here we need to get the names of folders and files in the flashcard folder + folder = 'flashcards/' + selectedlanguage + decks = self.walk(folder, 1, '*.xml') + print decks + #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) + self.categories = [] + folders = [] + for file in decks: + print file + tree = Xmlio(file) + root = tree.getroot() + count = len(root) + path = self.path_strip(file) + if path[0] in folders: + catid += 1 + parent = folders.index(path[0]) + 1 + else: + folders.append(path[0]) + parent = len(folders) + catid = 1 + self.categories.append([catid, path, count, parent, 0]) + self.categories.sort() + print selectedlanguage, len(self.categories) + + 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: + pathname = file[1][0] + file[1][1] + file[1][2] + print 'loading', pathname + '\n' + tree = Xmlio(pathname) + deck = tree.getroot() + 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) + print flashcard + random.shuffle(flashcards) + self.questions = flashcards + print 'number of questions=', len(self.questions) + +def create_flashcard(card): + 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 = '' + temp = sound.split('/') + print 'temp', temp + sound = [] + for item in temp: + print 'before', item + item = item[:-4] + '.ogg' + print 'after', item + sound.append(item) + print 'sound list is', sound + 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 = '' + temp = answer_node.findtext('more') + if temp: + more = temp + else: + more = '' + else: + more = '' + image = '' + cat = ' ' + subcat = ' ' + flashcard = [cardid,'','',cat, subcat, question, answer, '', hint, more, sound, image] + print flashcard + return (sound, image) + + +def extract(): + global selectedlanguage + #here we need to get the names of folders and files in the flashcard folder + folder = 'flashcards/' + selectedlanguage + print 'folder=', folder + decks = Q.walk(folder, 1, '*.xml') + print 'decks=', decks + for deck in decks: + pathname = deck + src = os.path.dirname(os.path.dirname(pathname)) + print 'loading', pathname + '\n' + tree = Xmlio(pathname) + deck = tree.getroot() + for card in deck: + sound, image = create_flashcard(card) + #copy image.jpg from image to img + print os.path.dirname(pathname) + print os.path.dirname(os.path.dirname(pathname)) + print 'copy image', src, image + try: + shutil.copyfile(src + '/image/' + image, src + '/img/' + image) + except: + print 'not found', pathname, image + #copy sound.mp3 from sound to snd + for item in sound: + print 'copy sound', src, item + try: + shutil.copyfile(src + '/sound/' + item, src + '/snd/' + item) + except: + print 'not found', pathname, item + +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 + 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): + global selectedlanguage + 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: + if q[0] == 1: + if q[1][0] != 'flashcards/' + selectedlanguage + '/': + name = q[1][0][len('flashcards/' + selectedlanguage + '/') - len(q[1][0]):-1] + sf.add_text_item("%s (%s)" % (name, q[2]), (300,y + 50 * q[3]), ask_subcat, q[3], True) + else: + name = q[1][1] + sf.add_text_item("%s (%s)" % (name, 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() + 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 + 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) + images = candidate_images[:3] + if Q.question.image in images: + images.append(candidate_images[3]) + else: + images.append(Q.question.image) + random.shuffle(images) + #load image + image_tl, xy = sf.image_load(images[0],path = 'flashcards/' + selectedlanguage + '/image') + if image_tl == None: + image_tl, xy = sf.image_load(error.png, path = 'flashcards/') + image_tr, xy = sf.image_load(images[1],path = 'flashcards/' + selectedlanguage + '/image') + if image_tr == None: + image_tr, xy = sf.image_load(error.png, path = 'flashcards/') + image_ll, xy = sf.image_load(images[2],path = 'flashcards/' + selectedlanguage + '/image') + if image_ll == None: + image_ll, xy = sf.image_load(error.png, path = 'flashcards/') + image_lr, xy = sf.image_load(images[3],path = 'flashcards/' + selectedlanguage + '/image') + if image_lr == None: + image_lr, xy = sf.image_load(error.png, path = 'flashcards/') + #display image + sf.display_surface(image_tl, (50, 100)) + sf.display_surface(image_tr, (400,100)) + sf.display_surface(image_ll, (50,360)) + sf.display_surface(image_lr, (400, 360)) + pygame.display.update() + + print 'sound = ', Q.question.sound + if Q.question.sound and len(Q.question.sound) > 0: + query = sf.sound_load(Q.question.sound[:-4] + '.ogg', path='flashcards/' + selectedlanguage + '/sound') + query.play() + while pygame.mixer.get_busy(): + clock.tick(30) + image_play, xy = sf.image_load("images/xclock.png") + sf.display_surface(image_play, (300,0)) + 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 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) + 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: + 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_russian_renyi_1(): + global selectedlanguage + print 'russian renyi_1 selected' + selectedlanguage = 'russian/renyi_1' + Q.cat_id = -1 + extract() + +def click_russian_renyi_2(): + global selectedlanguage + print 'russian renyi_2 selected' + selectedlanguage = 'russian/renyi_2' + Q.cat_id = -1 + extract() + +def click_russian_renyi_3(): + global selectedlanguage + print 'russian renyi_3 selected' + selectedlanguage = 'russian/renyi_3' + Q.cat_id = -1 + extract() + +def click_russian_renyi_4(): + global selectedlanguage + print 'russian renyi_4 selected' + selectedlanguage = 'russian/renyi_4' + Q.cat_id = -1 + extract() +def load(): + global sf + sf = __SERVICES__.frontend; + + global Q + Q = Questions() + + global A + A = Answer() + + global clock + clock = pygame.time.Clock() + + global button1 + global button2 + global button3 + global button4 + + 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_dir('/extract', "Extract") + button1 = sf.add_menu_item('/extract', 'Russian Renyi_1', click_russian_renyi_1) + print 'button', button1 + button2 = sf.add_menu_item('/extract', 'Russian Renyi_2', click_russian_renyi_2) + print 'button', button2 + button3 = sf.add_menu_item('/extract', 'Russian Renyi_3', click_russian_renyi_3) + print 'button', button3 + button4 = sf.add_menu_item('/extract', 'Russian Renyi_4', click_russian_renyi_4) + print 'button', button4 + + pass + + +def close(): + pass 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 diff --git a/plugins/_library.py b/plugins/_library.py new file mode 100755 index 0000000..60e6922 --- /dev/null +++ b/plugins/_library.py @@ -0,0 +1,198 @@ +__PLUGIN_NAME__ = 'library' +LIB = '/home/olpc/Activities/ImageQuizPlus.activity/flashcards/library' +OUTPATH = '/home/olpc/Activities/ImageQuizPlus.activity/flashcards/flashcards' + +from path import path +from xmlio import Xmlio +import xml.etree.ElementTree as ET + +''' + List of Services: + - ImageQuiz.py, Line 37: "Hook-In Services" + - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins#Overview +''' +# +# /media/SDCARD02 should be global for easy change +# top level menu items are folders in /media/SDCARD02/library +# change is to make this happen dynamically based on directory (using path.py) +# top level categories are folders in selected top level item +# sub categories are decks in selected category +# selected deck is copied to /home/olpc/ImageQuizPlus.activity/flashcards (adding Leitner attributes) +# if deck references image or sound files, they are copied to /home/olpc/ImageQuizPlus.activity/flashcards/image or sound. +# finish by returning to main menu +# +# strategy +# first get menu working without doing anything (print xxx.xml selected) +# next add print for image copied or sound copied +# debug source is /home/tonya/Desktop/ImageQuizPlus.activity/flashcards/library +# debug destination is /home/tonya/Desktop/ImageQuizPlus.activity/flashcards +# +# next actually copy deck adding Leitner attributes +# +# modify flashcard.py to use Leitner attributes (first ignore as in quiz mode) + +def clickOnItem2(): + #here we need to display list of decks already checked out + #print "Return to Library" + d = path(OUTPATH) + count = 0 + y = 110 + for item in d.files(): + brdr = False + sf.add_text_item(item.name, (300, y+50*count), remove_deck, (d, item.name), brdr) + count += 1 + +def remove_deck(s): + #print s[0], s[1], 'selected' + d = path(OUTPATH) + deck = d.joinpath(s[1]) + #print 'remove deck', deck + path.remove(deck) + #we need to redisplay menu with deck removed + sf.clear_text_items() + clickOnItem2() + +def clickOnItem1(): + #here we need to display categories (folders in selected folder) + #print "Demoplugin Menu Item 1" + #make path to folder + #here we need to use path to make a list of directories + brdr = False + d = path(LIB) + dsel = d.joinpath(sf.current_caption()) + count = 0 + y = 110 + for folder in dsel.dirs(): + #print folder, folder.name, len(folder.files()), len(folder.dirs()) + sf.add_text_item("%s (%s)" % (unicode(folder.name), str(len(folder.dirs()))), (300,y + 50 * count), + ask_subcat, (dsel, folder.name), brdr) + count += 1 + +def ask_subcat(s): + global sourcepath + #print s[0], s[1], 'selected' + brdr = True + sf.clear_text_items() + d = path(s[0]) + dsel = d.joinpath(s[1]) + #this is source for image and sound files + sourcepath = d + count = 0 + y = 70 + for f in dsel.listdir(): + #may be deck or folder + if f.isdir(): + cnt = len(f.listdir()) + brdr = False + else: + tree = Xmlio(f) + root = tree.getroot() + cnt = len(root) + brdr = True + if count < 12: + sf.add_text_item("%s (%s)" % (unicode(f.namebase), str(cnt)), (200,y + 50 * count), + make_local, (dsel,f.name), brdr) + elif count < 24: + sf.add_text_item("%s (%s)" % (unicode(f.namebase), str(cnt)), (450,y + 50 * (count - 12)), + make_local, (dsel,f.name), brdr) + else: + sf.add_text_item("%s (%s)" % (unicode(f.namebase), str(cnt)), (700,y + 50 * (count - 24)), + make_local, (dsel,f.name), brdr) + count += 1 + +def make_local(s): + global sourcepath + #this could be deck or folder + #print s[0], s[1], 'deck selected' + count = 0 + sel = path(s[0]) + selp = sel.joinpath(s[1]) + if selp.isdir(): + ask_subcat(s) + return + #print 'should be file', selp + deck = Xmlio(selp) + cards = deck.getroot() + outpath = path(OUTPATH) + fullpath = outpath.joinpath(s[1]) + #print fullpath + outpath = path(OUTPATH) + soundsource = path(sourcepath).joinpath('sound') + imagesource = path(sourcepath).joinpath('image') + outdeck = Xmlio(root = "quiz") + outcards = outdeck.getroot() + for card in cards: + #copy card to outcard + question_node = card.find('question') + if question_node: + temp = question_node.findtext('sound') + if temp: + sounds = temp.split('/') + for item in sounds: + item = item[:-4] + '.ogg' + #copy sounds here + source = soundsource.joinpath(item) + temp = outpath.joinpath('sound') + destination = temp.joinpath(item) + #print 'sound to copy', item, 'from', source, 'to', destination + path.copy(source,destination) + sound = '/'.join(sounds) + else: + sound = '' + hint = question_node.findtext('hint') + answer_node = card.find('answer') + if answer_node: + more = answer_node.findtext('more') + temp = answer_node.findtext('image') + if temp: + image = temp[:-4] + '.png' + else: + image = '' + count += 1 + outcard = ET.SubElement(outcards, 'card', id = str(count), bin = str(0), + count = str(0), shown = '', think = '') + question = ET.SubElement(outcard, 'question') + question.text = question_node.text + qsound = ET.SubElement(question, 'sound') + qsound.text = sound + #copy sound + #print 'sound', sound + qhint = ET.SubElement(question, 'hint') + qhint.text = hint + answer = ET.SubElement(outcard, 'answer') + answer.text = answer_node.text + aimage = ET.SubElement(answer, 'image') + aimage.text = image + #copy image + source = path(imagesource).joinpath(image) + temp = outpath.joinpath('image') + destination = temp.joinpath(image) + #print 'image to copy', image, 'from', source, 'to', destination + path.copy(source,destination) + amore = ET.SubElement(answer, 'more') + amore.text = more + + #write outdeck + outdeck.save(fullpath) + sf.clear_text_items() + +def debug(): + pass + +def load(): + global sf + sf = __SERVICES__.frontend; + +# #print __SERVICES__.db.query("SELECT * FROM xoquiz WHERE 1") + sf.add_menu_dir('/library', 'Library') + #here we need to use path to make a list of directories + d = path(LIB) + for folder in d.dirs(): + #print folder + if not folder[-3:] == 'bzr': + sf.add_menu_item('/library', folder.name, clickOnItem1) + #to remove completed decks + sf.add_menu_item('/library','return quiz', clickOnItem2) + +def close(): + pass diff --git a/plugins/_quizsocket.py b/plugins/_quizsocket.py new file mode 100755 index 0000000..12e9e07 --- /dev/null +++ b/plugins/_quizsocket.py @@ -0,0 +1,247 @@ +#! /usr/bin/env python + +#!/usr/bin/env python + +import select +import socket +import sys +import threading +import time + +class Interact: +# status = Status() + def __init__(self, status): + self.status = status + + def strip(self, str, what): + str = str.strip() + while len(str) > 0 and str[-1:] == what: str = str[:-1] + while len(str) > 0 and str[:1] == what: str = str[1:] + return str + + def process_input(self, s): + """ input comes as arg:param """ + s = self.strip(s, "'") + print "processing: %s" % s + + if len(s) == 0: return False + if ":" not in s: return False + + s1 = s[:s.index(":")] + s2 = s[s.index(":")+1:] + + + if s1 == 'get' and s2 == 'userlist': + return self.status.get_user_names() + + elif s1 == 'get' and s2 == 'status': + return self.status.get_status_line() + + elif s1 == 'reguser' and len(s2) > 1: + i = 1 + user_name = s2 + while self.status.add_user(user_name) == False: + user_name = "%s-%i" % (s2, i) + i += 1 + + print "interact: registered user '%s'" % s2 + return user_name + +class Server(threading.Thread): + + def __init__(self, host, port, status): + threading.Thread.__init__(self) + self.host = host + self.port = port + self.backlog = 5 + self.size = 1024 + self.server = None + self.threads = [] + self.interact = Interact(status) + + + def open_socket(self): + try: + self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.server.setblocking(0) + self.server.bind((self.host,self.port)) + self.server.listen(5) + except socket.error, (value,message): + if self.server: + self.server.close() + print "Could not open socket: " + message + if message == "Address already in use": + self.__init__(self.host, self.port + 5) + else: + sys.exit(1) + + def run(self): + self.open_socket() + input = [self.server] + self.running = True + while self.running: + inputready,outputready,exceptready = select.select(input,[],[],3) + + for s in inputready: + + if self.running and s == self.server: + # handle the server socket + print "start new client" + c = ClientServer(self.server.accept(), self.interact) + c.start() + self.threads.append(c) + + # close all threads + print "closing server.run()" + + def quit(self): + print "server quitting" + self.running = False + self.server.close() + for c in self.threads: + c.running = 0 + c.join() + +class ClientServer(threading.Thread): + def __init__(self,(client,address), interact): + threading.Thread.__init__(self) + self.client = client + self.client.setblocking(0) + self.address = address + self.size = 1024 + self.interact = interact + + def run(self): + self.running = 1 + while self.running: + try: + data = self.client.recv(self.size) + if self.running and data: + if self.running: + if data[:3] == "bye" and ":" in data: + self.interact.status.del_user(data[4:]) + data_back = "bye" + else: + data_back = self.interact.process_input(repr(data)) + else: + data_back = "bye" + + print "sending back:", data_back + self.client.send("%s" % data_back) + else: + self.client.close() + self.running = 0 + + except Exception, inst: + # socket is in non-blocking mode, so it raises a lot of exceptions + if inst.args[0] == 104: + # connection reset by peer + self.interact.status.del_user(data[4:]) + self.running = False + + elif inst.args[0] != 11: + print type(inst) + print inst.args + +# time.sleep(0.3) + +class Client(threading.Thread): + def __init__(self, host, port, username, nonet_function=None): + threading.Thread.__init__(self) + self.host = host + self.port = port + self.username = username + self.nonet_function = nonet_function + + def close(self): + print "sending bye to server" + try: + self.s.send("bye:%s" % self.username) + time.sleep(1) + except: pass + self.running = False + + def run(self): + host = self.host + port = self.port + size = 1024 + self.size = size + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + try: s.connect((host,port)) + except: + if self.nonet_function != None: self.nonet_function() + return False + + s.setblocking(0) + + self.s = s + + # Register Username + cmd = "reguser:%s" % self.username + self.username = self.talk(cmd) + print "received username: %s" % self.username + + self.running = True + while self.running: + time.sleep(2) +# print self.talk("get:userlist") + + s.close() + + def talk(self, s): +# cmd = "get:userlist" + print "< %s" % s + loop = True + while loop: + try: + self.s.send(s) + loop = False + except Exception, inst: + print type(inst) + print inst.args + +# errno, errstr = list(inst.args) + if inst.args[0] == 11: time.sleep(0.5) + else: + print "Host has quit" + self.running = False + return False + + loop = True + while loop: + try: + data = self.s.recv(self.size) + loop = False + return data + except Exception, inst: +# print type(inst) +# print inst.args + errno, errstr = list(inst.args) + if errno == 11: time.sleep(0.5) + else: + print "Host has quit" + self.running = False + return False + + + +if __name__ == "__main__": + host = "localhost" + port = 50056 + username = "chris" + + if len(sys.argv) > 1: + if sys.argv[1] == "0": + s = Server('', port) + s.start() + + time.sleep(10) + s.quit() + + print "ciao ciao" + + else: + c = Client(host, port, username) + c.start() diff --git a/plugins/dbtool.py b/plugins/dbtool.py new file mode 100755 index 0000000..db706c4 --- /dev/null +++ b/plugins/dbtool.py @@ -0,0 +1,99 @@ +''' + List of Services: + - ImageQuiz.py, Line 37: "Hook-In Services" + - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins#Overview +''' +import subprocess +from path import path +from sugar.activity import activity + +__PLUGIN_NAME__ = 'dbtool' + +DATADIR = path(activity.get_activity_root()) / 'data' +ACTIVITYDIR = path(activity.get_bundle_path()) +print 'directories', DATADIR, ACTIVITYDIR + +def dbdelete(): + sf.clear_text_items() + sf.add_text_item("Clear database", (280,110)) + cmd = "rm -rf *" + #cmd = "ls -l > " + DATADIR + "/iq.log" + subprocess.call(cmd, shell=True, cwd=DATADIR) + print "Clear database" + + +#TABLE 'categories' ('id','cat_id', 'lang_id', 'text', 'parent_id', 'base_parent_id') +#TABLE 'questions' ('id', 'image_id', 'sound_id', 'map', 'cat', 'subcat', 'answer_link', 'count_found', 'count notfound', 'box', 'time', 'day') +#TABLE 'quizlinks' ('id', 'quiz_id', 'question_id') +#TABLE 'catlinks' ('id', 'parent_id', 'child_id') +#TABLE 'Leitner' ('id', 'question_id', etc ) + +def dbcategories(): + sf.clear_text_items() + sf.add_text_item("Show categories", (280,110)) + cats = __SERVICES__.db.query("SELECT id, text FROM categories;") + count = 0 + for cat in cats: + count += 1 + catstr = str(cat[0]) + ' category: ' + str(cat[1]) + sf.add_text_item(catstr, (180 + 50, 110 +count * 50)) + print "Show categories" + +def dbquestions(count): + sf.clear_text_items() + sf.add_text_item("Show questions", (280,110)) + #allow user to go to next page + sf.add_text_item("next page", (400,110), dbquestions, count + 12, True) + q = "SELECT id, prompt, response, image_fn, sound_fn, map, answer_link FROM questions;" + questions = __SERVICES__.db.query(q) + if count < len(questions): + showpage(questions, count) + +def showpage(questions, count): + line = 0 + for i in range(12): + if count < len(questions): + question = questions[count] + qstr = str(question[0]) + ' prompt:' + question[1] + qstr = qstr + ' response:' + question[2] + qstr = qstr + ' img: ' + question[3] + qstr = qstr + ' snd: ' + question[4] + ' map ' + question[5] + qstr = qstr + ' answer_link: ' + question[6] + sf.add_text_item(qstr, (180 + 50, 160 + line * 25)) + else: + return True + count += 1 + line += 1 + return False + +def dbquizlinks(): + sf.clear_text_items() + sf.add_text_item("Show questions in quiz", (280,110)) + q = "SELECT id, quiz_id, question_id FROM quizlink;" + links = __SERVICES__.db.query(q) + count = 0 + for link in links: + count += 1 + qstr = str(link[0]) + ' quiz: ' + str(link[1]) + ' question ' + str(link[2]) + ');' + sf.add_text_item(qstr, (180 + 50, 110 + count * 25)) + + +def debug(): + pass + +def dbquestions1(): + dbquestions(0) + +def load(): + global sf + sf = __SERVICES__.frontend; + sf.add_menu_dir('/dbtool', 'DB Tool') + sf.add_menu_item('/dbtool', 'show categories', dbcategories) + sf.add_menu_item('/dbtool', 'show questions', dbquestions1) + sf.add_menu_item('/dbtool', 'show quizlinks', dbquizlinks) + #sf.add_menu_item('/dbtool', 'clear database', dbdelete) + + pass + +def close(): + pass diff --git a/plugins/demoplugin.py b/plugins/demoplugin.py new file mode 100755 index 0000000..a6f8093 --- /dev/null +++ b/plugins/demoplugin.py @@ -0,0 +1,25 @@ +__PLUGIN_NAME__ = 'demo plugin 1' + +''' + List of Services: + - ImageQuiz.py, Line 37: "Hook-In Services" + - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins#Overview +''' + +def clickOnItem1(): + print "Demoplugin Menu Item 1" + +def debug(): + pass + +def load(): + global sf + sf = __SERVICES__.frontend; + +# print __SERVICES__.db.query("SELECT * FROM xoquiz WHERE 1") +# sf.add_menu_dir('/demodir', 'Demo Directory') +# sf.add_menu_item('/', 'Demo Item 3', clickOnItem1) + pass + +def close(): + pass
\ No newline at end of file diff --git a/plugins/ink.py b/plugins/ink.py new file mode 100755 index 0000000..544bc78 --- /dev/null +++ b/plugins/ink.py @@ -0,0 +1,61 @@ +# ink.py +# +# B. Mayton <bmayton@cs.washington.edu> +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# -*- mode:python; tab-width:4; indent-tabs-mode:t; -*- + +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import random +import logging + +class Path: + + def __init__(self, inkstr=None): + self.__logger = logging.getLogger('Path') + self.points=[] + self.color = (0,0,1.0) + self.pen = 4 + self.uid = random.randint(0, 2147483647) + if inkstr: + try: + i=0 + parts = inkstr.split('#') + if len(parts) > 1: + params = parts[i].split(';') + self.uid = int(params[0]) + colorparts = params[1].split(',') + self.color = (float(colorparts[0]),float(colorparts[1]),float(colorparts[2])) + self.pen = float(params[2]) + i = i + 1 + pathstr = parts[i] + pointstrs = pathstr.split(';') + for pointstr in pointstrs: + pparts = pointstr.split(',') + if len(pparts) == 2: + self.add((int(pparts[0]), int(pparts[1]))) + except Exception, e: + self.__logger.debug('Could not unserialize ink string (old ink?)') + + def add(self, point): + self.points.append(point) + + def __str__(self): + s = str(self.uid) + ";" + s = s + str(self.color[0]) + "," + str(self.color[1]) + "," + str(self.color[2]) + ";" + s = s + str(self.pen) + "#" + for p in self.points: + s = s + str(int(p[0])) + "," + str(int(p[1])) + ";" + return s diff --git a/plugins/make.py b/plugins/make.py new file mode 100755 index 0000000..e6f6189 --- /dev/null +++ b/plugins/make.py @@ -0,0 +1,422 @@ +__PLUGIN_NAME__ = 'make' + +''' + List of Services: + - ImageQuiz.py, Line 37: "Hook-In Services" + - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins#Overview +''' + +import os, sys + +import pygame +from pygame.locals import * + +from sugar.activity import activity + +# the following line is not needed if pgu is installed +import sys; sys.path.insert(0, "..") + +from pgu import gui + +import pygst +pygst.require("0.10") +import gst + +import ink + +from path import path + +#set up paths to for adding images and sounds +DATAPATH = os.path.join(activity.get_activity_root(), "data") +ACTIVITYPATH = activity.get_bundle_path() +IMAGEPATH = os.path.join(DATAPATH, 'image') +SOUNDPATH = os.path.join(DATAPATH, 'sound') + +clock = pygame.time.Clock() + +class CurrentQuestion: + id = 0 + prompt = u'' + response = u'' + imgfn = u'' + sndfn = u'' + map = u'' + answer_link = u'' + +class EditDialog(gui.Dialog): + def __init__(self, editsave, cq): + max = 100 + title = gui.Label("Edit Question") + + t = gui.Table() + self.form = gui.Form() + + t.tr() + print 'cq.imgfn', len(cq.imgfn.split()), len(cq.imgfn), cq.imgfn + imgpath = path(IMAGEPATH) / 'blank.png' + if len(cq.imgfn) > 0: + temp = path(IMAGEPATH) / cq.imgfn + if temp.exists(): + imgpath = temp + print 'load image', imgpath.exists(), imgpath + self.img = pygame.image.load(imgpath) + t.td(gui.Image(self.img), align=-1, valign=-1, colspan = 8) + + if len(cq.map) > 0: + self.draw_map(cq.map) + + print 'table', t.getRows(), t.getColumns(), t.resize() + + t.tr() + t.td(gui.Label("")) + + t.tr() + t.td(gui.Label("Image: "), align = -1) + t.td(gui.Input(name = 'image', value = cq.imgfn, size = len(cq.imgfn) + 10), align = -1, colspan = 3) + imgBrowseButton = gui.Button("Browse...") + imgBrowseButton.connect(gui.CLICK, imgbrowse, cq) + t.td(imgBrowseButton) + + print 'table0', t.getRows(), t.getColumns(), t.resize() + + t.tr() + t.td(gui.Label("Clip: "), align = -1) + print 'clip', len(cq.sndfn), cq.sndfn + t.td(gui.Input(name = 'sound', value = cq.sndfn, size = len(cq.sndfn) + 10), align = -1, colspan = 3) + clipBrowseButton = gui.Button("Browse...") + clipBrowseButton.connect(gui.CLICK, clipbrowse, cq) + t.td(clipBrowseButton) + clipBrowseButton = gui.Button("Record") + clipBrowseButton.connect(gui.CLICK, record, cq) + t.td(clipBrowseButton) + clipBrowseButton = gui.Button("Stop") + clipBrowseButton.connect(gui.CLICK, stop, cq) + t.td(clipBrowseButton) + clipBrowseButton = gui.Button("Play") + clipBrowseButton.connect(gui.CLICK, play, cq) + t.td(clipBrowseButton) + + print 'table1', t.getRows(), t.getColumns(), t.resize() + + t.tr() + t.td(gui.Label("Prompt: "), align = -1) + sz = len(cq.prompt) + 10 + if sz > max: + sz = max + print 'prompt', sz, len(cq.prompt), cq.prompt + t.td(gui.Input(name = 'prompt', value = cq.prompt, size = sz), align = -1, colspan=6) + + print 'table2', t.getRows(), t.getColumns(), t.resize() + + t.tr() + t.td(gui.Label("Response: "), align = -1) + sz = len(cq.response) + 10 + if sz > max: + sz = max + print 'response',sz, len(cq.response), cq.response + t.td(gui.Input(name = 'response', value = cq.response, size = sz), align = -1, colspan=6) + + print 'table3', t.getRows(), t.getColumns(), t.resize() + + t.tr() + t.td(gui.Label("Answer_link: "), align = -1) + sz = len(cq.answer_link) + 10 + if sz > max: + sz = max + print 'answer_link', sz, len(cq.answer_link), cq.answer_link + t.td(gui.Input(name = 'answer_link', value = cq.answer_link, size = sz), align = -1, colspan=7) + + print 'table4', t.getRows(), t.getColumns(), t.resize() + + t.tr() + t.td(gui.Label("")) + + t.tr() + saveButton = gui.Button("Save") + saveButton.connect(gui.CLICK, editsave, cq) + t.td(saveButton,colspan=3) + + self.t = t + print 'tablef', self.t.getRows(), self.t.getColumns(), self.t.resize() + gui.Dialog.__init__(self,title,self.t) + + def draw_map(self, map): + color = (100,0,0) + pen = 4 + pts = [] + coords = [] + maplst = map.split(',') + for pt in maplst: + try: + coords.append(int(pt)) + except: + pass + i = 0 + while i < len(coords): + pts.append((coords[i],coords[i+1])) + i += 2 + pygame.draw.lines(self.img, color, True, pts, pen) + +def clickOnMake(): + # Select Category + cat_id = -1 + sf.clear_text_items() + sf.clear_question_frame(True) + print 'ask_category', cat_id + ask_category(cat_id) + +def ask_category(cat_id, offset_y = 0): + global c + global new_cat + + i = 1 + y = 110 + offset_y + + sf.clear_text_items() + sf.add_text_item("Next Category:", (280,y)) + + + new_cat = gui.Input(value="new", size = 20) + new_cat.connect(gui.ENTER,add_cat, 0) + c.add(new_cat, 0, 0) + app = sf.app() + app.init(c) + + + #first we need to query for categories + #might be nice to show them in alphabetical order + #when y gets too big, we should change x + if cat_id == -1: + cats = __SERVICES__.db.query("SELECT id, text FROM categories;") + print 'cats', len(cats), cats + for cat in cats: + print 'cat', cat[0], cat[1] + cat_id = cat[0] + category = cat[1] + try: + q = 'SELECT count(*) FROM quizlink WHERE quiz_id=%i' % cat_id + res1 = __SERVICES__.db.query(q) + except: + print 'query error', q + count = res1[0][0] + print 'count=', count + if count > 0: + # this is a quiz, display in green + y += 50 + sf.add_text_item("%s (%s)" % (category, count), (300,y), ask_question, cat_id) + else: + # this is a category - get number of children + q = 'SELECT count(*) FROM catlink WHERE parent_id=%i' % cat_id + res2 = __SERVICES__.db.query(q) + count = res2[0][0] + y += 50 + sf.add_text_item("%s (%s)" % (category, count), (300,y), ask_category, cat_id) + i += 1 + else: + #we need to pass a cat_id from the user's selection + print 'second level selection not implemented - must select quiz' + +def add_cat(params): + global new_cat + global CATID + #add cat to db with new_cat.value as text + CATID= __SERVICES__.db.add_cat(new_cat.value) + #create empty question to pass to startedit + cq = CurrentQuestion() + cq.id = -1 + cq.prompt = "" + cq.response = "" + cq.imgfn = "" + cq.sndfn = "" + cq.map = "" + cq.answer_link = "" + startedit(cq) + +def ask_question(cat_id, offset_y = 0): + global CATID + + print 'ask_question', cat_id, offset_y + + i = 1 + y = 110 + offset_y + CATID = cat_id + + + sf.clear_text_items() + sf.add_text_item("Select question:", (280,y)) + + #we need to query for questions + #when y gets too big, we should change x + try: + q = 'SELECT question_id FROM quizlink WHERE quiz_id=%i' % cat_id + print 'query=', q + res = __SERVICES__.db.query(q) + except: + print 'failure in query', q + print 'res', len(res), res + + count = 0 + for question in res: + count += 1 + try: + q = 'SELECT prompt, response, image_fn, sound_fn, map, answer_link FROM questions WHERE id = %i' % question[0] + print 'query=', q + res2 = __SERVICES__.db.query(q) + except: + print 'error in query', q + print 'res2', len(res2), res2 + res1 = res2[0] + cq = CurrentQuestion() + cq.id = question[0] + cq.prompt = res1[0] + cq.response = res1[1] + cq.imgfn = res1[2] + cq.sndfn = res1[3] + cq.map = res1[4] + cq.answer_link = res1[5] + if len(cq.imgfn) > 0: + ipath = path(IMAGEPATH) /cq.imgfn + if len(cq.imgfn)>0 and ipath.exists(): + y += 50 + image, xy = sf.image_load(ipath) + sf.display_surface(pygame.transform.scale(image,(128,128)), (300,y)) + y += 128 + if len(cq.prompt.strip()) > 0: + sf.add_text_item(cq.prompt,(300,y),startedit, cq) + else: + sf.add_text_item(cq.imgfn,(300,y),startedit, cq) + elif len(cq.prompt.strip()) > 0: + y += 50 + sf.add_text_item(cq.prompt,(300,y),startedit, cq) + else: + y += 50 + sf.add_text_item(cq.sndfn,(300,y), startedit, cq) + +def startedit(question, offset_y = 0): + global edit_d + + cq = question + + #if len(cq.imgfn) > 0 and path(cq.imgfn).exists: + #display cq.imgfn + #else: + #display 'no image' image + #display cq.imgfn entry as caption + #display audio controls (right of sound entry) + #if not (len(cq.imgfn) > 0 and path(cq.imgfn).exists()): + #grey 'play' control + #if len(cq.sndfn) > 0 and path(cq.sndfn) exists(): + #display audio controls (to right of sound entry) + #if sound recorded, ungrey 'play' control + #if sound path changed, reset 'play' control depending on whether path exists + + + i = 1 + print 'i=', i + y = 110 + offset_y + print 'y=', y + + print 'clear text items' + sf.clear_text_items() + + edit_d = EditDialog(editsave, cq) + edit_d.open() + +def editsave(cq): + global CATID + global edit_d + + form = edit_d.form + cq.prompt = form['prompt'].value + cq.response = form['response'].value + cq.imgfn = form['image'].value + cq.sndfn = form['sound'].value + cq.answer_link = form['answer_link'].value + + #if a new question, it needs to be inserted into database + #insert question + #add quizlink with quiz id and question id + + #insert updated question into database + q = "UPDATE questions SET prompt='%s' WHERE id=%i" % (cq.prompt, cq.id) + updatedb(q) + q = "UPDATE questions SET response = '%s' WHERE id = %i" % (cq.response, cq.id) + updatedb(q) + q = "UPDATE questions SET image_fn = '%s' WHERE id = %i" % (cq.imgfn, cq.id) + updatedb(q) + q = "UPDATE questions SET sound_fn = '%s' WHERE id = %i" % (cq.sndfn, cq.id) + updatedb(q) + q = "UPDATE questions SET map = '%s' WHERE id = %i" % (cq.map, cq.id) + updatedb(q) + q = "UPDATE questions SET answer_link = '%s' WHERE id = %i" % (cq.answer_link, cq.id) + updatedb(q) + + edit_d.close() + print 'edit_d closed', edit_d.t.getRows(), edit_d.t.getColumns() + edit_d.t.clear() + print 'edit_d closed and table cleared', edit_d.t.getRows(), edit_d.t.getColumns() + cat_id = CATID + ask_question(cat_id) + +def updatedb(q): + print 'updatedb' + __SERVICES__.db.commit(q) + +def imgbrowse(cq): + print 'imgbrowse not implemented' + +def clipbrowse(cq): + print 'clipbrowse not implemented' + +def record(cq): + global player + global fileout + fileout.set_property("location", os.path.join(SOUNDPATH, cq.sndfn)) + print player.get_state() + player.set_state(gst.STATE_PLAYING) + +def stop(cq): + player.set_state(gst.STATE_READY) + pygame.mixer.stop() + +def play(cq): + sound = sf.sound_load(os.path.join(SOUNDPATH, cq.sndfn)) + sound.play() + while pygame.mixer.get_busy(): + clock.tick(30) + +def debug(): + pass + +def load(): + global sf + global player + global fileout + global c + sf = __SERVICES__.frontend + #print __SERVICES__.db.query("SELECT text FROM categories") + #sf.add_menu_dir('/demodir', 'Demo Directory') + sf.add_menu_item('/', 'Make', clickOnMake) + #initialize audio record pipeline + player = gst.Pipeline("player") + source = gst.element_factory_make("alsasrc", "alsa-source") + player.add(source) + convert = gst.element_factory_make("audioconvert", "converter") + player.add(convert) + enc = gst.element_factory_make("vorbisenc", "vorbis-encoder") + player.add(enc) + create = gst.element_factory_make("oggmux", "ogg-create") + player.add(create) + fileout = gst.element_factory_make("filesink", "sink") + fileout.set_property("location", "test.ogg") + player.add(fileout) + gst.element_link_many(source, convert, enc, create, fileout) + + + #intialize gui + c = gui.Container(width = 400, height = 600) + app = sf.get_app() + app.init(c) + +def close(): + pass diff --git a/plugins/multi_player.py b/plugins/multi_player.py new file mode 100755 index 0000000..68acc63 --- /dev/null +++ b/plugins/multi_player.py @@ -0,0 +1,552 @@ +import pygame +from pygame import * +from layout import * +from frontend import hex2rgb +import threading +import time +import traceback + +__PLUGIN_NAME__ = 'multi player' +#import single_player + +class Status: + """ edited by the hosts """ + + game_state = '0' # 0=open_before_game, 1=..? + + status_line = 'statusline' + clients = [] + + class Client: + def __init__(self, name, status=''): + self.name = name + self.status = status + + def set_status_line(self, s1, s2): + return True + + def get_status_line(self): + self.status_line = "%s;;%s" % (self.game_state, self.get_user_names(";")) + return self.status_line + + def get_user_names(self, split_str=';;'): + usernames = [] + for c in self.clients: + usernames.append(c.name) + return split_str.join(usernames) + + def add_user(self, user_name): + for c in self.clients: + if c.name.upper() == user_name.upper(): return False + + new_client = self.Client(user_name) + self.clients.append(new_client) + return True + + def del_user(self, user_name): + print "deleting user %s" % user_name + new_clients = [] + for c in self.clients: + if c.name.upper() != user_name.upper(): + new_clients.append(c) + + self.clients = new_clients + return True + + +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 "* Starting Game, Category:", db_cat_id + print "** Loading Questions" + + Q.count_won = 0 + Q.count_won = 0 + A.reset_points() + + Q.load_questions(db_cat_id) + display_points() + + sf.clear_text_items() + next_question(0) + + +def ask_category(offset_y = 0, offset_x = 0, cat_id=0): + Q.load(__SERVICES__.locale.lang_id, __SERVICES__.locale.lang_name) + +# sf.clear_text_items() + + i = 1 + y = 110 + offset_y + x = 340 + offset_x + + sf.add_text_item("Category:", (x,y)) + y += 50 + x += 50 + sf.add_text_item('All', (x,y), game_host.set_category, 0, True) +# global game_host + for q in Q.categories: + y += 50 + sf.add_text_item("%s (%s)" % (q[1], q[2]), (x,y), game_host.set_category, 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) + +def click_on_next_question(): + next_question(0) + +def next_question(params): + print "* Next Question" + + # Select Category + if Q._cat_id == -1: + ask_category() + return 1 + + + # Pick only locale language's categories + if Q.question_pick(): + __SERVICES__.frontend.question_display(Q.question) + Q.in_question = True + + pass + else: + # game / cat finished! + in_question = False + print "finished" + Q._cat_id = -1 + finished() + pass + +def spot_found(params): + if Q.in_question: + print "found" + A.display_icon("found") + A.play_sound(1) + Q.in_question = False + Q.count_won += 1 + display_points() + show_answer() + +def spot_not_found(params): + print params + if Q.in_question: + print "not found" + A.display_icon("not-found") + A.play_sound(0) + Q.in_question = False + Q.count_lost += 1 + display_points() + show_answer() + return "ok" + + + + +class getStatus(threading.Thread): + running = True + + def set(self, client_talk, status_call_function, abort_function=None): + self.talk = client_talk + self.status_call_function = status_call_function + self.abort_function = abort_function + self.running = True + + def run(self): + while self.running: + time.sleep(2) + try: + self.status = self.talk('get:status') + self.status_call_function(self.status) + except: + traceback.print_exc() + print "getstatus aborted" + self.running = False + if self.abort_function != None: self.abort_function() + + +class waitForUsersToJoin(threading.Thread): + running = True + last_users = '' + + def set(self, talk, waittext_id, textid_connectedto=None): + self.talk = talk + self.waittext_id = waittext_id + self.textid_connectedto = textid_connectedto + + def display_server_quit(self): + self.running = False + + def run(self): + format = TextFormat() + font = pygame.font.Font(None, format.size) + bg = pygame.Surface((560, 30)) + + while self.running: + time.sleep(2) + + users = self.talk("get:userlist") + if users == False: return + + self.users = users.split(";;") + users = users.replace(";;", ", ") + + print self.users + print users + + text = font.render("> %s" % users, 1, hex2rgb(format.color)) + textpos = text.get_rect() + + bg.fill(hex2rgb(Layout().background)) + bg.blit(text, (0,0)) + sf.display_surface(bg, (48, 610)) + + pygame.display.update((48, 610, 560, 30)) + +class DrawTimer: + def __init__(self): + self.bg = pygame.Surface((100, 60)) + + def draw(self, time): + self.bg.fill(hex2rgb(Layout().Question.background)) + + format = TextFormat(None, 46, None) + font = pygame.font.Font(None, format.size) + text = font.render("%i sec" % time, 1, hex2rgb(format.color)) + + self.bg.blit(text, (0,0)) + sf.display_surface(self.bg, (150, 440)) + pygame.display.update() + +class MultiPlayerHost: + category = 0 + game_time = 0 + textid_timer = -1 + status = Status() + + def __init__(self, host, port): + self.host = host + self.port = port + self.socket = __import__("plugins/_quizsocket") + + print "starting server" + self.server = self.socket.Server('', port, self.status) + self.server.start() + + def close(self): + self.client.running = False + self.waituser.running = False + self.server.quit() + + def update_status(self, params): + print params + + def load(self): + print "Creating a new game" + sf.clear_question_frame(True) + + # Display some text + format = TextFormat(None, 40) + sf.add_text_item("New Multiplayer Game", (340, 45), None, None, False, format) + waittext_id = sf.add_text_item("Waiting for users to join...", (40, 570)) + + # let's spawn our own client now + self.client = self.socket.Client(self.host, self.port, Layout().user_name) + self.client.start() + + # Display "Waiting for users to join..." + self.waituser = waitForUsersToJoin() + self.waituser.set(self.client.talk, waittext_id, None) + self.waituser.start() + + # Constantly Get Status Updates from the Host +# self.getstatus = getStatus() +# self.getstatus.set(self.Spawner.client_talk, self.update_status, self.abort_net) +# self.getstatus.start() + + # Display Start Button + format = TextFormat(None, None, Colors().green) + sf.add_text_item("Start", (Layout().Question.x + Layout().Question.width - 150, 570), None, None, True, format) + + # Display Categories + ask_category() + self.set_category(0) + pygame.display.update() + + self.hookid_click = sf.add_event_hook("onclick", self.click) + + + # Draw Timer Option + img, rect = sf.image_load("images/xclock.png") + sf.display_surface(img, (40, 400)) + self.draw_timer = DrawTimer() + self.click_timer() + + + def click(self, params): + x, y = list(params) + if x > 40 and x < (40 + 96) and y > 400 and y < (400 + 96): + # Click on Clock + self.click_timer() + + def click_timer(self): + if self.game_time == 30: + self.game_time = 10 + elif self.game_time == 20: + self.game_time = 30 + elif self.game_time == 10: + self.game_time = 20 + elif self.game_time == 0: + self.game_time = 10 + +# self.Spawner.master_set_status("time:%i" % self.game_time) + self.draw_timer.draw(self.game_time) + + def set_category(self, param): + cat_id = int(param) + self.category = cat_id + + cat_name = 'All' + if cat_id > 0: + q = "SELECT text FROM categories WHERE cat_id=%i AND lang_id=%s" % (cat_id, __SERVICES__.locale.lang_id) + res = __SERVICES__.db.query(q) + print res + cat_name = res[0][0] + +# self.Spawner.master_set_status(u"cat:%s" % cat_name) + + bg = pygame.Surface((360, 30)) + bg.fill(hex2rgb(Layout().background)) + + format = TextFormat() + font = pygame.font.Font(None, format.size) + + text = font.render("%s" % cat_name, 1, hex2rgb(format.color)) + bg.blit(text, (0,0)) + sf.display_surface(bg, (480, 115)) + pygame.display.update() + +class MultiPlayerClient: + connected = False + game_time = 0 + cat_name = u'' + + socket = None + waittext_id = None + + format = TextFormat() + font = pygame.font.Font(None, format.size) + bg = pygame.Surface((560, 30)) + + def __init__(self, host, port): + self.host = host + self.port = port + self.connected = False + if self.socket == None: self.socket = __import__("plugins/_quizsocket") + + def close(self): + self.getstatus.running = False + self.client.close() + self.connected = False + + def load(self): + print "Joining a multiplayer game" + + sf.clear_question_frame(True) + + format = TextFormat(None, 40) + sf.add_text_item("Joining a Multiplayer Game", (340, 45), None, None, False, format) + pygame.display.update() + +# print self.connected + if self.connected == False: + host = sf.ask("host") + sf.refresh() + print "host: ", host + self.host = host + + if len(host) == 0: + return False + +# if host == "l": host = "localhost" + print "starting client" + textid_connectedto = sf.add_text_item("[connecting to %s]" % host, (340, 80)) + self.textid_connectedto = textid_connectedto + + self.client = self.socket.Client(self.host, self.port, Layout().user_name, self.nonet_function) + self.client.start() + + self.getstatus = getStatus() + self.getstatus.set(self.client.talk, self.update_status, self.nonet_function) + self.getstatus.start() + + def display_catname(self): + bg = pygame.Surface((300, 60)) + bg.fill(hex2rgb(Layout().Question.background)) + + format = TextFormat(None, 40) + font = pygame.font.Font(None, format.size) + text = font.render("Category: %s" % self.cat_name, 1, hex2rgb(format.color)) + + bg.blit(text, (0,0)) + sf.display_surface(bg, (350, 190)) + pygame.display.update() + + def update_status(self, params): + self.status = params + print "STATUS UPDATE:",params + + if params == False or len(params) == 0 or params == None or params == "None": + self.close() + return + + if self.connected == False: + self.connection_ok() + + n = params.split(";;") + + users = n[1].replace(";", ", ") + + text = self.font.render("> %s" % users, 1, hex2rgb(self.format.color)) + textpos = text.get_rect() + + self.bg.fill(hex2rgb(Layout().background)) + self.bg.blit(text, (0,0)) + sf.display_surface(self.bg, (48, 610)) + + pygame.display.update((48, 610, 560, 30)) + print params + +# for n in params: +# m = n.split(":") +# if m[0] == "time": +# if int(m[1]) != self.game_time: +# self.game_time = int(m[1]) +# self.draw_timer.draw(self.game_time) +# +# if m[0] == "cat": +# if m[1] != self.cat_name: +# self.cat_name = m[1] +# self.display_catname() + + + def connection_ok(self): + self.connected = True + sf.del_text_item(self.textid_connectedto) + + self.textid_connectedto = sf.add_text_item("[connected to %s]" % self.host, (340, 80)) + self.waittext_id = sf.add_text_item("Waiting for users to join...", (40, 570)) + + pygame.display.update() + + # Draw Timer Option + img, rect = sf.image_load("images/xclock.png") + sf.display_surface(img, (40, 400)) + self.draw_timer = DrawTimer() + + def nonet_function(self): + print "client net aborted, quitting MultiPlayer-Client" + + self.running = False + + if self.waittext_id != None: sf.del_text_item(self.waittext_id) + if self.textid_connectedto != None: sf.del_text_item(self.textid_connectedto) + + sf.add_text_item("Server quit the game", (340, 84)) + + # clear player + bg = pygame.Surface((560, 30)) + bg.fill(hex2rgb(Layout().background)) + sf.display_surface(bg, (48, 610)) + pygame.display.update() + + self.close() + + global game_client + game_client = None + + + + +def click_create(): + global game_client + global game_host + + if game_client == None and game_host == None: + game_host = MultiPlayerHost("", Layout().port) + sf.clear_text_items(False) + game_host.load() + + +def click_join(): + global game_client + global game_host + + if game_client == None and game_host == None: + game_client = '' + game_client = MultiPlayerClient("localhost", Layout().port) + + sf.clear_text_items() + + res = game_client.load() + if res == False: + game_client = None + +def load(): + global sf + sf = __SERVICES__.frontend; + + sp = __import__("plugins/single_player") + sp.sf = sf + + global Q + Q = sp.Questions() + + global A + A = sp.Answer() + + global game_host + game_host = None + + global game_client + game_client = None + + sf.add_menu_dir('/multi-player', 'Multiplayer', 1) + sf.add_menu_item('/multi-player', 'Create Game', click_create, 1) + sf.add_menu_item('/multi-player', 'Join Game', click_join, 2) + + __SERVICES__.add_service("next_question", next_question) + __SERVICES__.add_service("spot_found", spot_found) + __SERVICES__.add_service("spot_not_found", spot_not_found) + +def close(): + global game_host + global game_client + + if game_host != None: game_host.close() + if game_client != None: game_client.close() + diff --git a/plugins/path.py b/plugins/path.py new file mode 100755 index 0000000..01c2c04 --- /dev/null +++ b/plugins/path.py @@ -0,0 +1,971 @@ +""" path.py - An object representing a path to a file or directory. + +Example: + +from path import path +d = path('/home/guido/bin') +for f in d.files('*.py'): + f.chmod(0755) + +This module requires Python 2.2 or later. + + +URL: http://www.jorendorff.com/articles/python/path +Author: Jason Orendorff <jason.orendorff\x40gmail\x2ecom> (and others - see the url!) +Date: 9 Mar 2007 +""" + + +# TODO +# - Tree-walking functions don't avoid symlink loops. Matt Harrison +# sent me a patch for this. +# - Bug in write_text(). It doesn't support Universal newline mode. +# - Better error message in listdir() when self isn't a +# directory. (On Windows, the error message really sucks.) +# - Make sure everything has a good docstring. +# - Add methods for regex find and replace. +# - guess_content_type() method? +# - Perhaps support arguments to touch(). + +from __future__ import generators + +import sys, warnings, os, fnmatch, glob, shutil, codecs, md5 + +__version__ = '2.2' +__all__ = ['path'] + +# Platform-specific support for path.owner +if os.name == 'nt': + try: + import win32security + except ImportError: + win32security = None +else: + try: + import pwd + except ImportError: + pwd = None + +# Pre-2.3 support. Are unicode filenames supported? +_base = str +_getcwd = os.getcwd +try: + if os.path.supports_unicode_filenames: + _base = unicode + _getcwd = os.getcwdu +except AttributeError: + pass + +# Pre-2.3 workaround for booleans +try: + True, False +except NameError: + True, False = 1, 0 + +# Pre-2.3 workaround for basestring. +try: + basestring +except NameError: + basestring = (str, unicode) + +# Universal newline support +_textmode = 'r' +if hasattr(file, 'newlines'): + _textmode = 'U' + + +class TreeWalkWarning(Warning): + pass + +class path(_base): + """ Represents a filesystem path. + + For documentation on individual methods, consult their + counterparts in os.path. + """ + + # --- Special Python methods. + + def __repr__(self): + return 'path(%s)' % _base.__repr__(self) + + # Adding a path and a string yields a path. + def __add__(self, more): + try: + resultStr = _base.__add__(self, more) + except TypeError: #Python bug + resultStr = NotImplemented + if resultStr is NotImplemented: + return resultStr + return self.__class__(resultStr) + + def __radd__(self, other): + if isinstance(other, basestring): + return self.__class__(other.__add__(self)) + else: + return NotImplemented + + # The / operator joins paths. + def __div__(self, rel): + """ fp.__div__(rel) == fp / rel == fp.joinpath(rel) + + Join two path components, adding a separator character if + needed. + """ + return self.__class__(os.path.join(self, rel)) + + # Make the / operator work even when true division is enabled. + __truediv__ = __div__ + + def getcwd(cls): + """ Return the current working directory as a path object. """ + return cls(_getcwd()) + getcwd = classmethod(getcwd) + + + # --- Operations on path strings. + + isabs = os.path.isabs + def abspath(self): return self.__class__(os.path.abspath(self)) + def normcase(self): return self.__class__(os.path.normcase(self)) + def normpath(self): return self.__class__(os.path.normpath(self)) + def realpath(self): return self.__class__(os.path.realpath(self)) + def expanduser(self): return self.__class__(os.path.expanduser(self)) + def expandvars(self): return self.__class__(os.path.expandvars(self)) + def dirname(self): return self.__class__(os.path.dirname(self)) + basename = os.path.basename + + def expand(self): + """ Clean up a filename by calling expandvars(), + expanduser(), and normpath() on it. + + This is commonly everything needed to clean up a filename + read from a configuration file, for example. + """ + return self.expandvars().expanduser().normpath() + + def _get_namebase(self): + base, ext = os.path.splitext(self.name) + return base + + def _get_ext(self): + f, ext = os.path.splitext(_base(self)) + return ext + + def _get_drive(self): + drive, r = os.path.splitdrive(self) + return self.__class__(drive) + + parent = property( + dirname, None, None, + """ This path's parent directory, as a new path object. + + For example, path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib') + """) + + name = property( + basename, None, None, + """ The name of this file or directory without the full path. + + For example, path('/usr/local/lib/libpython.so').name == 'libpython.so' + """) + + namebase = property( + _get_namebase, None, None, + """ The same as path.name, but with one file extension stripped off. + + For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz', + but path('/home/guido/python.tar.gz').namebase == 'python.tar' + """) + + ext = property( + _get_ext, None, None, + """ The file extension, for example '.py'. """) + + drive = property( + _get_drive, None, None, + """ The drive specifier, for example 'C:'. + This is always empty on systems that don't use drive specifiers. + """) + + def splitpath(self): + """ p.splitpath() -> Return (p.parent, p.name). """ + parent, child = os.path.split(self) + return self.__class__(parent), child + + def splitdrive(self): + """ p.splitdrive() -> Return (p.drive, <the rest of p>). + + Split the drive specifier from this path. If there is + no drive specifier, p.drive is empty, so the return value + is simply (path(''), p). This is always the case on Unix. + """ + drive, rel = os.path.splitdrive(self) + return self.__class__(drive), rel + + def splitext(self): + """ p.splitext() -> Return (p.stripext(), p.ext). + + Split the filename extension from this path and return + the two parts. Either part may be empty. + + The extension is everything from '.' to the end of the + last path segment. This has the property that if + (a, b) == p.splitext(), then a + b == p. + """ + filename, ext = os.path.splitext(self) + return self.__class__(filename), ext + + def stripext(self): + """ p.stripext() -> Remove one file extension from the path. + + For example, path('/home/guido/python.tar.gz').stripext() + returns path('/home/guido/python.tar'). + """ + return self.splitext()[0] + + if hasattr(os.path, 'splitunc'): + def splitunc(self): + unc, rest = os.path.splitunc(self) + return self.__class__(unc), rest + + def _get_uncshare(self): + unc, r = os.path.splitunc(self) + return self.__class__(unc) + + uncshare = property( + _get_uncshare, None, None, + """ The UNC mount point for this path. + This is empty for paths on local drives. """) + + def joinpath(self, *args): + """ Join two or more path components, adding a separator + character (os.sep) if needed. Returns a new path + object. + """ + return self.__class__(os.path.join(self, *args)) + + def splitall(self): + r""" Return a list of the path components in this path. + + The first item in the list will be a path. Its value will be + either os.curdir, os.pardir, empty, or the root directory of + this path (for example, '/' or 'C:\\'). The other items in + the list will be strings. + + path.path.joinpath(*result) will yield the original path. + """ + parts = [] + loc = self + while loc != os.curdir and loc != os.pardir: + prev = loc + loc, child = prev.splitpath() + if loc == prev: + break + parts.append(child) + parts.append(loc) + parts.reverse() + return parts + + def relpath(self): + """ Return this path as a relative path, + based from the current working directory. + """ + cwd = self.__class__(os.getcwd()) + return cwd.relpathto(self) + + def relpathto(self, dest): + """ Return a relative path from self to dest. + + If there is no relative path from self to dest, for example if + they reside on different drives in Windows, then this returns + dest.abspath(). + """ + origin = self.abspath() + dest = self.__class__(dest).abspath() + + orig_list = origin.normcase().splitall() + # Don't normcase dest! We want to preserve the case. + dest_list = dest.splitall() + + if orig_list[0] != os.path.normcase(dest_list[0]): + # Can't get here from there. + return dest + + # Find the location where the two paths start to differ. + i = 0 + for start_seg, dest_seg in zip(orig_list, dest_list): + if start_seg != os.path.normcase(dest_seg): + break + i += 1 + + # Now i is the point where the two paths diverge. + # Need a certain number of "os.pardir"s to work up + # from the origin to the point of divergence. + segments = [os.pardir] * (len(orig_list) - i) + # Need to add the diverging part of dest_list. + segments += dest_list[i:] + if len(segments) == 0: + # If they happen to be identical, use os.curdir. + relpath = os.curdir + else: + relpath = os.path.join(*segments) + return self.__class__(relpath) + + # --- Listing, searching, walking, and matching + + def listdir(self, pattern=None): + """ D.listdir() -> List of items in this directory. + + Use D.files() or D.dirs() instead if you want a listing + of just files or just subdirectories. + + The elements of the list are path objects. + + With the optional 'pattern' argument, this only lists + items whose names match the given pattern. + """ + names = os.listdir(self) + if pattern is not None: + names = fnmatch.filter(names, pattern) + return [self / child for child in names] + + def dirs(self, pattern=None): + """ D.dirs() -> List of this directory's subdirectories. + + The elements of the list are path objects. + This does not walk recursively into subdirectories + (but see path.walkdirs). + + With the optional 'pattern' argument, this only lists + directories whose names match the given pattern. For + example, d.dirs('build-*'). + """ + return [p for p in self.listdir(pattern) if p.isdir()] + + def files(self, pattern=None): + """ D.files() -> List of the files in this directory. + + The elements of the list are path objects. + This does not walk into subdirectories (see path.walkfiles). + + With the optional 'pattern' argument, this only lists files + whose names match the given pattern. For example, + d.files('*.pyc'). + """ + + return [p for p in self.listdir(pattern) if p.isfile()] + + def walk(self, pattern=None, errors='strict'): + """ D.walk() -> iterator over files and subdirs, recursively. + + The iterator yields path objects naming each child item of + this directory and its descendants. This requires that + D.isdir(). + + This performs a depth-first traversal of the directory tree. + Each directory is returned just before all its children. + + The errors= keyword argument controls behavior when an + error occurs. The default is 'strict', which causes an + exception. The other allowed values are 'warn', which + reports the error via warnings.warn(), and 'ignore'. + """ + if errors not in ('strict', 'warn', 'ignore'): + raise ValueError("invalid errors parameter") + + try: + childList = self.listdir() + except Exception: + if errors == 'ignore': + return + elif errors == 'warn': + warnings.warn( + "Unable to list directory '%s': %s" + % (self, sys.exc_info()[1]), + TreeWalkWarning) + return + else: + raise + + for child in childList: + if pattern is None or child.fnmatch(pattern): + yield child + try: + isdir = child.isdir() + except Exception: + if errors == 'ignore': + isdir = False + elif errors == 'warn': + warnings.warn( + "Unable to access '%s': %s" + % (child, sys.exc_info()[1]), + TreeWalkWarning) + isdir = False + else: + raise + + if isdir: + for item in child.walk(pattern, errors): + yield item + + def walkdirs(self, pattern=None, errors='strict'): + """ D.walkdirs() -> iterator over subdirs, recursively. + + With the optional 'pattern' argument, this yields only + directories whose names match the given pattern. For + example, mydir.walkdirs('*test') yields only directories + with names ending in 'test'. + + The errors= keyword argument controls behavior when an + error occurs. The default is 'strict', which causes an + exception. The other allowed values are 'warn', which + reports the error via warnings.warn(), and 'ignore'. + """ + if errors not in ('strict', 'warn', 'ignore'): + raise ValueError("invalid errors parameter") + + try: + dirs = self.dirs() + except Exception: + if errors == 'ignore': + return + elif errors == 'warn': + warnings.warn( + "Unable to list directory '%s': %s" + % (self, sys.exc_info()[1]), + TreeWalkWarning) + return + else: + raise + + for child in dirs: + if pattern is None or child.fnmatch(pattern): + yield child + for subsubdir in child.walkdirs(pattern, errors): + yield subsubdir + + def walkfiles(self, pattern=None, errors='strict'): + """ D.walkfiles() -> iterator over files in D, recursively. + + The optional argument, pattern, limits the results to files + with names that match the pattern. For example, + mydir.walkfiles('*.tmp') yields only files with the .tmp + extension. + """ + if errors not in ('strict', 'warn', 'ignore'): + raise ValueError("invalid errors parameter") + + try: + childList = self.listdir() + except Exception: + if errors == 'ignore': + return + elif errors == 'warn': + warnings.warn( + "Unable to list directory '%s': %s" + % (self, sys.exc_info()[1]), + TreeWalkWarning) + return + else: + raise + + for child in childList: + try: + isfile = child.isfile() + isdir = not isfile and child.isdir() + except: + if errors == 'ignore': + continue + elif errors == 'warn': + warnings.warn( + "Unable to access '%s': %s" + % (self, sys.exc_info()[1]), + TreeWalkWarning) + continue + else: + raise + + if isfile: + if pattern is None or child.fnmatch(pattern): + yield child + elif isdir: + for f in child.walkfiles(pattern, errors): + yield f + + def fnmatch(self, pattern): + """ Return True if self.name matches the given pattern. + + pattern - A filename pattern with wildcards, + for example '*.py'. + """ + return fnmatch.fnmatch(self.name, pattern) + + def glob(self, pattern): + """ Return a list of path objects that match the pattern. + + pattern - a path relative to this directory, with wildcards. + + For example, path('/users').glob('*/bin/*') returns a list + of all the files users have in their bin directories. + """ + cls = self.__class__ + return [cls(s) for s in glob.glob(_base(self / pattern))] + + + # --- Reading or writing an entire file at once. + + def open(self, mode='r'): + """ Open this file. Return a file object. """ + return file(self, mode) + + def bytes(self): + """ Open this file, read all bytes, return them as a string. """ + f = self.open('rb') + try: + return f.read() + finally: + f.close() + + def write_bytes(self, bytes, append=False): + """ Open this file and write the given bytes to it. + + Default behavior is to overwrite any existing file. + Call p.write_bytes(bytes, append=True) to append instead. + """ + if append: + mode = 'ab' + else: + mode = 'wb' + f = self.open(mode) + try: + f.write(bytes) + finally: + f.close() + + def text(self, encoding=None, errors='strict'): + r""" Open this file, read it in, return the content as a string. + + This uses 'U' mode in Python 2.3 and later, so '\r\n' and '\r' + are automatically translated to '\n'. + + Optional arguments: + + encoding - The Unicode encoding (or character set) of + the file. If present, the content of the file is + decoded and returned as a unicode object; otherwise + it is returned as an 8-bit str. + errors - How to handle Unicode errors; see help(str.decode) + for the options. Default is 'strict'. + """ + if encoding is None: + # 8-bit + f = self.open(_textmode) + try: + return f.read() + finally: + f.close() + else: + # Unicode + f = codecs.open(self, 'r', encoding, errors) + # (Note - Can't use 'U' mode here, since codecs.open + # doesn't support 'U' mode, even in Python 2.3.) + try: + t = f.read() + finally: + f.close() + return (t.replace(u'\r\n', u'\n') + .replace(u'\r\x85', u'\n') + .replace(u'\r', u'\n') + .replace(u'\x85', u'\n') + .replace(u'\u2028', u'\n')) + + def write_text(self, text, encoding=None, errors='strict', linesep=os.linesep, append=False): + r""" Write the given text to this file. + + The default behavior is to overwrite any existing file; + to append instead, use the 'append=True' keyword argument. + + There are two differences between path.write_text() and + path.write_bytes(): newline handling and Unicode handling. + See below. + + Parameters: + + - text - str/unicode - The text to be written. + + - encoding - str - The Unicode encoding that will be used. + This is ignored if 'text' isn't a Unicode string. + + - errors - str - How to handle Unicode encoding errors. + Default is 'strict'. See help(unicode.encode) for the + options. This is ignored if 'text' isn't a Unicode + string. + + - linesep - keyword argument - str/unicode - The sequence of + characters to be used to mark end-of-line. The default is + os.linesep. You can also specify None; this means to + leave all newlines as they are in 'text'. + + - append - keyword argument - bool - Specifies what to do if + the file already exists (True: append to the end of it; + False: overwrite it.) The default is False. + + + --- Newline handling. + + write_text() converts all standard end-of-line sequences + ('\n', '\r', and '\r\n') to your platform's default end-of-line + sequence (see os.linesep; on Windows, for example, the + end-of-line marker is '\r\n'). + + If you don't like your platform's default, you can override it + using the 'linesep=' keyword argument. If you specifically want + write_text() to preserve the newlines as-is, use 'linesep=None'. + + This applies to Unicode text the same as to 8-bit text, except + there are three additional standard Unicode end-of-line sequences: + u'\x85', u'\r\x85', and u'\u2028'. + + (This is slightly different from when you open a file for + writing with fopen(filename, "w") in C or file(filename, 'w') + in Python.) + + + --- Unicode + + If 'text' isn't Unicode, then apart from newline handling, the + bytes are written verbatim to the file. The 'encoding' and + 'errors' arguments are not used and must be omitted. + + If 'text' is Unicode, it is first converted to bytes using the + specified 'encoding' (or the default encoding if 'encoding' + isn't specified). The 'errors' argument applies only to this + conversion. + + """ + if isinstance(text, unicode): + if linesep is not None: + # Convert all standard end-of-line sequences to + # ordinary newline characters. + text = (text.replace(u'\r\n', u'\n') + .replace(u'\r\x85', u'\n') + .replace(u'\r', u'\n') + .replace(u'\x85', u'\n') + .replace(u'\u2028', u'\n')) + text = text.replace(u'\n', linesep) + if encoding is None: + encoding = sys.getdefaultencoding() + bytes = text.encode(encoding, errors) + else: + # It is an error to specify an encoding if 'text' is + # an 8-bit string. + assert encoding is None + + if linesep is not None: + text = (text.replace('\r\n', '\n') + .replace('\r', '\n')) + bytes = text.replace('\n', linesep) + + self.write_bytes(bytes, append) + + def lines(self, encoding=None, errors='strict', retain=True): + r""" Open this file, read all lines, return them in a list. + + Optional arguments: + encoding - The Unicode encoding (or character set) of + the file. The default is None, meaning the content + of the file is read as 8-bit characters and returned + as a list of (non-Unicode) str objects. + errors - How to handle Unicode errors; see help(str.decode) + for the options. Default is 'strict' + retain - If true, retain newline characters; but all newline + character combinations ('\r', '\n', '\r\n') are + translated to '\n'. If false, newline characters are + stripped off. Default is True. + + This uses 'U' mode in Python 2.3 and later. + """ + if encoding is None and retain: + f = self.open(_textmode) + try: + return f.readlines() + finally: + f.close() + else: + return self.text(encoding, errors).splitlines(retain) + + def write_lines(self, lines, encoding=None, errors='strict', + linesep=os.linesep, append=False): + r""" Write the given lines of text to this file. + + By default this overwrites any existing file at this path. + + This puts a platform-specific newline sequence on every line. + See 'linesep' below. + + lines - A list of strings. + + encoding - A Unicode encoding to use. This applies only if + 'lines' contains any Unicode strings. + + errors - How to handle errors in Unicode encoding. This + also applies only to Unicode strings. + + linesep - The desired line-ending. This line-ending is + applied to every line. If a line already has any + standard line ending ('\r', '\n', '\r\n', u'\x85', + u'\r\x85', u'\u2028'), that will be stripped off and + this will be used instead. The default is os.linesep, + which is platform-dependent ('\r\n' on Windows, '\n' on + Unix, etc.) Specify None to write the lines as-is, + like file.writelines(). + + Use the keyword argument append=True to append lines to the + file. The default is to overwrite the file. Warning: + When you use this with Unicode data, if the encoding of the + existing data in the file is different from the encoding + you specify with the encoding= parameter, the result is + mixed-encoding data, which can really confuse someone trying + to read the file later. + """ + if append: + mode = 'ab' + else: + mode = 'wb' + f = self.open(mode) + try: + for line in lines: + isUnicode = isinstance(line, unicode) + if linesep is not None: + # Strip off any existing line-end and add the + # specified linesep string. + if isUnicode: + if line[-2:] in (u'\r\n', u'\x0d\x85'): + line = line[:-2] + elif line[-1:] in (u'\r', u'\n', + u'\x85', u'\u2028'): + line = line[:-1] + else: + if line[-2:] == '\r\n': + line = line[:-2] + elif line[-1:] in ('\r', '\n'): + line = line[:-1] + line += linesep + if isUnicode: + if encoding is None: + encoding = sys.getdefaultencoding() + line = line.encode(encoding, errors) + f.write(line) + finally: + f.close() + + def read_md5(self): + """ Calculate the md5 hash for this file. + + This reads through the entire file. + """ + f = self.open('rb') + try: + m = md5.new() + while True: + d = f.read(8192) + if not d: + break + m.update(d) + finally: + f.close() + return m.digest() + + # --- Methods for querying the filesystem. + + exists = os.path.exists + isdir = os.path.isdir + isfile = os.path.isfile + islink = os.path.islink + ismount = os.path.ismount + + if hasattr(os.path, 'samefile'): + samefile = os.path.samefile + + getatime = os.path.getatime + atime = property( + getatime, None, None, + """ Last access time of the file. """) + + getmtime = os.path.getmtime + mtime = property( + getmtime, None, None, + """ Last-modified time of the file. """) + + if hasattr(os.path, 'getctime'): + getctime = os.path.getctime + ctime = property( + getctime, None, None, + """ Creation time of the file. """) + + getsize = os.path.getsize + size = property( + getsize, None, None, + """ Size of the file, in bytes. """) + + if hasattr(os, 'access'): + def access(self, mode): + """ Return true if current user has access to this path. + + mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK + """ + return os.access(self, mode) + + def stat(self): + """ Perform a stat() system call on this path. """ + return os.stat(self) + + def lstat(self): + """ Like path.stat(), but do not follow symbolic links. """ + return os.lstat(self) + + def get_owner(self): + r""" Return the name of the owner of this file or directory. + + This follows symbolic links. + + On Windows, this returns a name of the form ur'DOMAIN\User Name'. + On Windows, a group can own a file or directory. + """ + if os.name == 'nt': + if win32security is None: + raise Exception("path.owner requires win32all to be installed") + desc = win32security.GetFileSecurity( + self, win32security.OWNER_SECURITY_INFORMATION) + sid = desc.GetSecurityDescriptorOwner() + account, domain, typecode = win32security.LookupAccountSid(None, sid) + return domain + u'\\' + account + else: + if pwd is None: + raise NotImplementedError("path.owner is not implemented on this platform.") + st = self.stat() + return pwd.getpwuid(st.st_uid).pw_name + + owner = property( + get_owner, None, None, + """ Name of the owner of this file or directory. """) + + if hasattr(os, 'statvfs'): + def statvfs(self): + """ Perform a statvfs() system call on this path. """ + return os.statvfs(self) + + if hasattr(os, 'pathconf'): + def pathconf(self, name): + return os.pathconf(self, name) + + + # --- Modifying operations on files and directories + + def utime(self, times): + """ Set the access and modified times of this file. """ + os.utime(self, times) + + def chmod(self, mode): + os.chmod(self, mode) + + if hasattr(os, 'chown'): + def chown(self, uid, gid): + os.chown(self, uid, gid) + + def rename(self, new): + os.rename(self, new) + + def renames(self, new): + os.renames(self, new) + + + # --- Create/delete operations on directories + + def mkdir(self, mode=0777): + os.mkdir(self, mode) + + def makedirs(self, mode=0777): + os.makedirs(self, mode) + + def rmdir(self): + os.rmdir(self) + + def removedirs(self): + os.removedirs(self) + + + # --- Modifying operations on files + + def touch(self): + """ Set the access/modified times of this file to the current time. + Create the file if it does not exist. + """ + fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0666) + os.close(fd) + os.utime(self, None) + + def remove(self): + os.remove(self) + + def unlink(self): + os.unlink(self) + + + # --- Links + + if hasattr(os, 'link'): + def link(self, newpath): + """ Create a hard link at 'newpath', pointing to this file. """ + os.link(self, newpath) + + if hasattr(os, 'symlink'): + def symlink(self, newlink): + """ Create a symbolic link at 'newlink', pointing here. """ + os.symlink(self, newlink) + + if hasattr(os, 'readlink'): + def readlink(self): + """ Return the path to which this symbolic link points. + + The result may be an absolute or a relative path. + """ + return self.__class__(os.readlink(self)) + + def readlinkabs(self): + """ Return the path to which this symbolic link points. + + The result is always an absolute path. + """ + p = self.readlink() + if p.isabs(): + return p + else: + return (self.parent / p).abspath() + + + # --- High-level functions from shutil + + copyfile = shutil.copyfile + copymode = shutil.copymode + copystat = shutil.copystat + copy = shutil.copy + copy2 = shutil.copy2 + copytree = shutil.copytree + if hasattr(shutil, 'move'): + move = shutil.move + rmtree = shutil.rmtree + + + # --- Special stuff from os + + if hasattr(os, 'chroot'): + def chroot(self): + os.chroot(self) + + if hasattr(os, 'startfile'): + def startfile(self): + os.startfile(self) + + diff --git a/plugins/readme b/plugins/readme new file mode 100755 index 0000000..5e6f5fe --- /dev/null +++ b/plugins/readme @@ -0,0 +1,11 @@ + How to use / write Plugins: + - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins + + List of Services: + - ImageQuiz.py, Line 37: "Hook-In Services" + - http://wiki.laptop.org/index.php?title=XO_ImageQuiz/Plugins#Overview + + Services can accessed via the Class __SERVICES__ + __SERVICES__.db.query(q) + __SERVICES__.db.commit(q) + __SERVICES__.frontend.add_menu_item(path, caption, onclick_function) diff --git a/plugins/single_player.py b/plugins/single_player.py new file mode 100755 index 0000000..ef0936e --- /dev/null +++ b/plugins/single_player.py @@ -0,0 +1,744 @@ +# Currently +# * Displaying Categories in set language, +# * Pick English Questions + +# To Do +# change to update Leitner info +# display questions based on box +# fix accessed wav + +# structure +# class CurrentQuestion +# class Answer +# class Questions + +# functions + +# show_answer +# finished +# startgame +# ask_subcat +# ask_category +# display_points +# click_on_ +# next_question +# next_question +# spot_found +# spot_not_found +# load + +import pygame +from pygame import * +from sugar.activity import activity +import os + +import random +from layout import * +from frontend import hex2rgb +import time +import threading + +__PLUGIN_NAME__ = 'single player' + +#set up paths to for adding images and sounds +DATAPATH = os.path.join(activity.get_activity_root(), "data") +ACTIVITYPATH = activity.get_bundle_path() +IMAGEPATH = os.path.join(DATAPATH, 'image') +SOUNDPATH = os.path.join(DATAPATH, 'sound') +ICONPATH = os.path.join(ACTIVITYPATH, 'images') +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'] +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'] + +clock = pygame.time.Clock() + +class CurrentQuestion: + id = 0 + prompt = u'' + response = u'' + imgfn = u'' + sndfn = u'' + map = u'' + answer_link = u'' + +class Imagequiz_question: + id = 0 + map = u'' + cat = 0 + subcat = 0 + text = u'' + answer = u'' + answer_link = u'' + +class Answer: + display = True + display_line = True + sound_enabled = True + + link = u'' + text = u'' + + img_found_count = 0 + img_notfound_count = 0 + imgfn_found = ["Emotes/face-grin.png"] + imgfn_notfound = ["Emotes/face-devil-grin.png"] + + 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 + 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 + + + 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: + + def __init__(self): + in_question = False + + played = 0 + count_won = 0 + count_lost = 0 + id = u'' + questions = [] + question = CurrentQuestion() + + 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! + self.question = self.questions[self.played] + self.id = self.question.id + A.text = self.question.response + A.link = self.question.answer_link + + self.played += 1 + return True + + def load_questions(self, cat_id): + self._cat_id = cat_id + + self.played = 0 + self.count_won = 0 + self.count_lost = 0 + + q = 'SELECT text FROM categories WHERE id = %i' % cat_id + res = __SERVICES__.db.query(q) + Q.id = res[0][0] + + #get list of questions (by question_id) + try: + q = "SELECT question_id FROM quizlink WHERE quiz_id = %i" % cat_id + questionlist = __SERVICES__.db.query(q) + except: + questionlist = [] + + #get actual questions + self.questions = [] + for questionid in questionlist: + q = 'SELECT * from questions WHERE id = %i' % questionid[0] + res = __SERVICES__.db.query(q) + question = CurrentQuestion() + question.id = res[0][0] + question.prompt = res[0][1] + question.response = res[0][2] + question.imgfn = res[0][3] + question.sndfn = res[0][4] + question.map = res[0][5] + question.answer_link=res[0][6] + self.questions.append(question) + + #present questions randomly + random.shuffle(self.questions) + +def show_answer(): + A.display_answer() + Q.in_question = True + next_question() + +def finished(): + sf.clear_text_items() + sf.clear_question_frame(True) + 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 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) + sf.add_text_item("this is the score " + str(Q.count_won * 10), (600,0)) + pygame.display.update() + +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,100)) + pygame.display.update() + #this should provide for click on image_right or image_wrong + response = True + return response + +def display_images(): + global images + candidate_images = [] + for question in Q.questions: + candidate_images.append(question.imgfn) + random.shuffle(candidate_images) + if Q.question.imgfn in candidate_images[:4]: + images = candidate_images[:4] + else: + images = candidate_images[:3] + images.append(Q.question.imgfn) + random.shuffle(images) + image_tl, xy = sf.image_load(images[0], path = IMAGEPATH) + image_tr, xy = sf.image_load(images[1], path = IMAGEPATH) + image_ll, xy = sf.image_load(images[2], path = IMAGEPATH) + image_lr, xy = sf.image_load(images[3], path = IMAGEPATH) + 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 + +def play_query_again(): + global query + if Q.in_question: + #print 'play_query' + query.play() + while pygame.mixer.get_busy(): + clock.tick(30) + +def play_sound(fn): + global query + query = sf.sound_load(Q.question.sndfn, path=SOUNDPATH) + image_play, xy = sf.image_load("play_button.png", path = ICONPATH) + sf.display_surface(image_play, (300,30)) + pygame.display.update() + query.play() + while pygame.mixer.get_busy(): + clock.tick(30) + +def picture_tl(): + global images + if Q.in_question: + if images[0] == Q.question.imgfn: + response = True + else: + response = False + delete_reacts(response) + +def picture_tr(): + global images + if Q.in_question: + if images[1] == Q.question.imgfn: + response = True + else: + response = False + delete_reacts(response) + +def picture_ll(): + global images + if Q.in_question: + if images[2] == Q.question.imgfn: + response = True + else: + response = False + delete_reacts(response) + +def picture_lr(): + global images + if Q.in_question: + if images[3] == Q.question.imgfn: + response = True + else: + response = False + delete_reacts(response) + +def delete_reacts(response): + if response: + correct_answer() + else: + wrong_answer() + return True + +def play_correct_response_sound(): + good = sf.sound_load(random.choice(CORRECT_SOUNDS)) + good.play() + while pygame.mixer.get_busy(): + pygame.time.wait(30) + +def play_wrong_response_sound(): + good = sf.sound_load(random.choice(WRONG_SOUNDS)) + good.play() + while pygame.mixer.get_busy(): + pygame.time.wait(30) + + +def update_leitner_attributes(): + #update Leitner attributes + #but need to get from db when loading questions + #need to save in db before next_question + #and update attributes actually in db + #finally display 'boxes' and 'points' + try: + print 'update Leitner attributes' + think = '%0.2f' % float( time.time() - Q.starttime) + Q.time = think + Q.count_found += 1 + if Q.box < 5: + Q.box += 1 + Q.date = sys.date() + except: + print 'Leitner update failed' + #now update database + try: + q = "UPDATE questions SET time = Q.time, count_found = Q.count_found, count_unfound = Q.count_unfound, box = Q.box, time = Q.time, date = Q.date WHERE id = Q.id" + res = __SERVICES__.db.query(q) + except: + print 'Leitner db update failed' + +def correct_answer(next=True): + if Q.in_question: + A.display_icon("found") + #A.play_sound(1) + Q.in_question = False + Q.count_won += 1 + display_points() + try: + play_correct_response_sound() + except: + print 'play_correct_response_sound failed' + update_leitner_attributes() + + if next: + next_question() + else: + show_answer() + +def wrong_answer(next=True): + if Q.in_question: + A.display_icon("not-found") + Q.in_question = False + Q.count_lost += 1 + display_points() + play_wrong_response_sound() + update_leitner_attributes() + if next: + next_question() + else: + show_answer() + +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.response) + return response.strip() in possibles + + +def startgame(db_cat_id): + + Q.count_won = 0 + A.reset_points() + + Q.load_questions(db_cat_id) + display_points() + + sf.clear_text_items() + next_question() + +def next_question(): + #Q._cat_id + #Q.questions + global reacts + + if Q.question_pick(): + clear_reacts() + Q.in_question = True + Q.starttime = time.time() + if len(Q.question.map) > 0: #this is an imagequiz question + iq = Imagequiz_question() + iq.id = Q.question.id + iq.imgfn = Q.question.imgfn + iq.map = Q.question.map + iq.cat = 0 + iq.subcat = 0 + iq.text = Q.question.prompt + iq.answer = Q.question.response + iq.answer_link = Q.question.answer_link + __SERVICES__.frontend.question_display(iq) + elif len(Q.question.imgfn) > 0 and len(Q.question.sndfn) > 0: #this is Renyi + print 'Renyi', Q.question.imgfn, Q.question.sndfn + #get four images (one is correct), shuffle them, put them in 'react' squares + set_reacts() + print 'display_images' + display_images() + print 'update pygame display' + pygame.display.update() + #load and play sound + play_sound(Q.question.sndfn) + elif len(Q.question.sndfn) > 0: #this in 'phrase' question + #load and play sound + print 'phrase question' + play_sound(Q.question.sndfn) + response = __SERVICES__.frontend.ask(Q.question.text) + 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) + else: # this is flashcard + print 'flashcard' + print 'prompt', len(Q.question.prompt), Q.question.prompt + print 'answer', len(Q.question.response), Q.question.response + response = __SERVICES__.frontend.ask(Q.question.prompt) + print 'response', len(response), response + if len(response) == 0 or len(Q.question.response) == 0: + correct = self_test() + else: + correct = checkanswer(response) + delete_reacts(correct) + next_question() + Q.in_question = True + else: + # game / cat finished + Q.in_question = False + print "finished" + Q._cat_id = -1 + finished() + +def spot_found(): + if Q.in_question: + A.display_icon("found") + play_correct_response_sound() + #A.play_sound(1) + Q.in_question = False + Q.count_won += 1 + display_points() + show_answer() + +def spot_not_found(): + if Q.in_question: + A.display_icon("not-found") + play_wrong_response_sound() + #A.play_sound(0) + Q.in_question = False + Q.count_lost += 1 + display_points() + show_answer() + +def set_reacts(): + global reacts + reacts = [] + reacts.append(sf.add_react(250, 150, 320, 240, picture_tl, stopsearch = True)) + reacts.append(sf.add_react(520, 150, 320, 240, picture_tr, stopsearch = True)) + reacts.append(sf.add_react(250, 420, 320, 240, picture_ll, stopsearch = True)) + reacts.append(sf.add_react(520, 420, 320, 240, picture_lr, stopsearch = True)) + reacts.append(sf.add_react(750,100,50,50, correct_answer, stopsearch = True)) + reacts.append(sf.add_react(750,150,50,50, wrong_answer, stopsearch = True)) + reacts.append(sf.add_react(300,30,100,100, play_query_again, stopsearch = True)) + +def clear_reacts(): + global reacts + for react in reacts: + sf.del_react(react) + +def click_on_pick_category(): + # Select Category + cat_id = -1 + sf.clear_text_items() + sf.clear_question_frame(True) + ask_category(cat_id) + +def ask_category(cat_id, offset_y = 0): + + i = 1 + y = 110 + offset_y + + sf.add_text_item("Next Category:", (280,y)) + + #first we need to query for categories + #might be nice to show them in alphabetical order + #when y gets too big, we should change x + if cat_id == -1: + q = 'SELECT id, text FROM categories' + res = __SERVICES__.db.query(q) + for cat in res: + cat_id = cat[0] + category = cat[1] + q = 'SELECT count(*) FROM quizlink WHERE quiz_id=%i' % cat_id + res1 = __SERVICES__.db.query(q) + count = res1[0][0] + if count > 0: + # this is a quiz, display in green + y += 50 + sf.add_text_item("%s (%s)" % (category, count), (300,y), startgame, cat_id, True) + else: + # this is a category - get number of children + q = 'SELECT count(*) FROM catlink WHERE parent_id=%i' % cat_id + res2 = __SERVICES__.db.query(q) + count = res2[0][0] + y += 50 + sf.add_text_item("%s (%s)" % (category, count), (300,y), ask_category, cat_id, True) + i += 1 + else: + #we need to pass a cat_id from the user's selection + print 'second level selection not implemented - must select quiz' + + +#initialization called by quiz.py +def load(): + global sf + sf = __SERVICES__.frontend; + + global Q + Q = Questions() + + global A + A = Answer() + + global button1 + global button2 + global button3 + + global reacts + reacts = [] + + sf.add_menu_item('/', 'Pick Category', click_on_pick_category, 0) + + sf.add_menu_dir('/options', "Options") + button1 = sf.add_menu_item('/options', 'Display Answer [ On ]', click_options_answer) + button2 = sf.add_menu_item('/options', 'Play Sound [ On ]', click_options_sound) + button3 = sf.add_menu_item('/options', 'Draw Line [ On ]', click_options_line) + + __SERVICES__.add_service("next_question", next_question) + __SERVICES__.add_service("spot_found", spot_found) + __SERVICES__.add_service("spot_not_found", spot_not_found) + +#clean close +def close(): + pass + +# handle options tab on main menu +def click_options_answer(): + if A.display: + A.display = False + sf.change_menu_item("change_caption", button1, "Display Answer [ Off ]") + else: + A.display = True + sf.change_menu_item("change_caption", button1, "Display Answer [ On ]") + +def click_options_line(): + if A.display_line: + A.display_line = False + sf.change_menu_item("change_caption", button3, "Draw Line [ Off ]") + else: + A.display_line = True + sf.change_menu_item("change_caption", button3, "Draw Line [ On ]") + +def click_options_sound(): + if A.sound_enabled: + A.sound_enabled = False + sf.change_menu_item("change_caption", button2, "Play Sound [ Off ]") + else: + A.sound_enabled = True + sf.change_menu_item("change_caption", button2, "Play Sound [ On ]") diff --git a/plugins/tools.py b/plugins/tools.py new file mode 100755 index 0000000..d453c2c --- /dev/null +++ b/plugins/tools.py @@ -0,0 +1,100 @@ +import sys +import pygame + +__PLUGIN_NAME__ = 'db tools' + +''' Mixed Functions ''' +def clickOnDBSave(): + pass + +def clickOnDBRevert(): + pass + +def clickOnDBWipe(): + pass + +def clickOnQuitYes(): + sys.exit(0) + +def clickOnToggleFS(): + pygame.display.toggle_fullscreen() + +def clickOnOptions_Exchange(): + print "Click on Options - Exchange" + +def event1(params): + print "Event 1 Started :-) params:", params + +def React1(): + print "React 1" + +def clickOnQuestionItem(params): + print params + + +# Click on Test() +def clickOnTest(): + print "Start Test" + sf.clear_question_items() + + # Clear the Frame and display Stuff + sf.question_frame_clear() + sf.question_frame_show_text('images/europe.gif',0,0) + sf.question_frame_show_image('images/europe.gif',0,50) + pygame.display.update() + + # Add Hook to onclick + # hook_id = sf.add_event_hook('onclick', event1) + # print "- added hook", hook_id + + +''' Click on Test2() ''' +def clickOnTest2(): + # Show last Question + # sf.question_display() + + # Add Reacts + # a = sf.add_react(0, 0, 100, 100, React1, False, True) + # b = sf.add_react(50, 50, 100, 100, React1, False, True) + + # Remove React a + # sf.del_react(a) + + x = sf.ask("Your name:") + print ">", x + + sf.add_question_item(x, (100, 100), clickOnQuestionItem, 0) + sf.add_question_item(x*2, (100, 150), clickOnQuestionItem, 1) + + sf.draw_question_items() + pygame.display.update() + +''' Init Part ''' +def load(): + global sf + sf = __SERVICES__.frontend; + + # Use DB like that: + # print __SERVICES__.db.query("SELECT * FROM xoquiz WHERE 1") + + # Menu /test +# sf.add_menu_dir('/test', 'Test') +# sf.add_menu_item('/test', 'Test()', clickOnTest,3) +# sf.add_menu_item('/test', 'Test2()', clickOnTest2,3) + + # Menu /options + sf.add_menu_dir('/options', 'Options') + sf.add_menu_item('/options', 'Toggle Fullscreen', clickOnToggleFS, 0) + + # Menu /options/database +# sf.add_menu_dir('/options/database', 'Database') +# sf.add_menu_item('/options/database', 'Save', clickOnDBSave, 0) +# sf.add_menu_item('/options/database', 'Revert', clickOnDBRevert, 1) +# sf.add_menu_item('/options/database', 'Wipe All', clickOnDBWipe, 2) + + # Menu /quit + sf.add_menu_dir('/quit', 'Quit', 5) + sf.add_menu_item('/quit', 'Yes, really Quit', clickOnQuitYes, 3) + +def close(): + pass
\ No newline at end of file |