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