diff options
Diffstat (limited to 'plugins/single_player.py')
-rwxr-xr-x | plugins/single_player.py | 744 |
1 files changed, 744 insertions, 0 deletions
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 ]") |