# HG changeset patch # User Goffi # Date 1307910855 -7200 # Node ID 208107419b1725b984695a175f0ab6c93e569d25 # Parent 141eeb7cd9e6d73e54c6c0edbe7986631a48aa36 Quiz game: buzzer, timer, answer management diff -r 141eeb7cd9e6 -r 208107419b17 frontends/src/bridge/DBus.py --- a/frontends/src/bridge/DBus.py Sun Jun 12 16:28:33 2011 +0200 +++ b/frontends/src/bridge/DBus.py Sun Jun 12 22:34:15 2011 +0200 @@ -185,6 +185,9 @@ def quizGameReady(self, player, referee, profile_key='@DEFAULT@'): return self.db_comm_iface.quizGameReady(player, referee, profile_key) + def quizGameAnswer(self, player, referee, answer, profile_key='@DEFAULT@'): + return self.db_comm_iface.quizGameAnswer(player, referee, answer, profile_key) + def sendFile(self, to, path, profile_key='@DEFAULT@'): return self.db_comm_iface.sendFile(to, path, profile_key) diff -r 141eeb7cd9e6 -r 208107419b17 frontends/src/quick_frontend/quick_app.py --- a/frontends/src/quick_frontend/quick_app.py Sun Jun 12 16:28:33 2011 +0200 +++ b/frontends/src/quick_frontend/quick_app.py Sun Jun 12 22:34:15 2011 +0200 @@ -66,6 +66,11 @@ self.bridge.register("quizGameStarted", self.quizGameStarted) self.bridge.register("quizGameNew", self.quizGameNew) self.bridge.register("quizGameQuestion", self.quizGameQuestion) + self.bridge.register("quizGamePlayerBuzzed", self.quizGamePlayerBuzzed) + self.bridge.register("quizGamePlayerSays", self.quizGamePlayerSays) + self.bridge.register("quizGameAnswerResult", self.quizGameAnswerResult) + self.bridge.register("quizGameTimerExpired", self.quizGameTimerExpired) + self.bridge.register("quizGameTimerRestarted", self.quizGameTimerRestarted) self.bridge.register("subscribe", self.subscribe) self.bridge.register("paramUpdate", self.paramUpdate) self.bridge.register("contactDeleted", self.contactDeleted) @@ -377,7 +382,6 @@ def quizGameStarted(self, room_jid, referee, players, profile): if not self.check_profile(profile): - print "gof: NOT CHECK PROFILE", profile return debug (_("Quiz Game Started \o/")) if self.chat_wins.has_key(room_jid): @@ -399,6 +403,41 @@ if self.chat_wins.has_key(room_jid): self.chat_wins[room_jid].getGame("Quiz").quizGameQuestion(question_id, question, timer) + def quizGamePlayerBuzzed(self, room_jid, player, pause, profile): + """Called when a player pushed the buzzer""" + if not self.check_profile(profile): + return + if self.chat_wins.has_key(room_jid): + self.chat_wins[room_jid].getGame("Quiz").quizGamePlayerBuzzed(player, pause) + + def quizGamePlayerSays(self, room_jid, player, text, delay, profile): + """Called when a player say something""" + if not self.check_profile(profile): + return + if self.chat_wins.has_key(room_jid): + self.chat_wins[room_jid].getGame("Quiz").quizGamePlayerSays(player, text, delay) + + def quizGameAnswerResult(self, room_jid, player, good_answer, score, profile): + """Called when a player say something""" + if not self.check_profile(profile): + return + if self.chat_wins.has_key(room_jid): + self.chat_wins[room_jid].getGame("Quiz").quizGameAnswerResult(player, good_answer, score) + + def quizGameTimerExpired(self, room_jid, profile): + """Called when nobody answered the question in time""" + if not self.check_profile(profile): + return + if self.chat_wins.has_key(room_jid): + self.chat_wins[room_jid].getGame("Quiz").quizGameTimerExpired() + + def quizGameTimerRestarted(self, room_jid, time_left, profile): + """Called when the question is not answered, and we still have time""" + if not self.check_profile(profile): + return + if self.chat_wins.has_key(room_jid): + self.chat_wins[room_jid].getGame("Quiz").quizGameTimerRestarted(time_left) + def _subscribe_cb(self, answer, data): entity, profile = data if answer: diff -r 141eeb7cd9e6 -r 208107419b17 frontends/src/wix/quiz_game.py --- a/frontends/src/wix/quiz_game.py Sun Jun 12 16:28:33 2011 +0200 +++ b/frontends/src/wix/quiz_game.py Sun Jun 12 22:34:15 2011 +0200 @@ -71,14 +71,18 @@ def loadImages(self, dir): """Load all the images needed for the game @param dir: directory where the PNG files are""" + x_player = 24 for name, sub_dir, filename, x, y, zindex, transparent in [("background", "backgrounds", "blue_background.png", 0, 0, 0, False), - ("joueur0", "characters/zombie", "zombie.png", 24, 170, 5, True), - ("joueur1", "characters/nerd", "nerd2.png", 209, 170, 5, True), - ("joueur2", "characters/zombie", "zombie.png", 392, 170, 5, True), - ("joueur3", "characters/zombie", "zombie.png", 578, 170, 5, True), + ("joueur0", "characters/zombie", "zombie.png", x_player+0*184, 170, 5, True), + ("joueur1", "characters/nerd", "nerd2.png", x_player+1*184, 170, 5, True), + ("joueur2", "characters/zombie", "zombie.png", x_player+2*184, 170, 5, True), + ("joueur3", "characters/zombie", "zombie.png", x_player+3*184, 170, 5, True), ("foreground", "foreground", "foreground.png", 0, 0, 10, True)]: self.graphic_elts[name] = GraphicElement(os.path.join(dir, sub_dir, filename), x = x, y = y, zindex=zindex, transparent=transparent) + self.right_image = wx.Image(os.path.join(dir, "right.png")).ConvertToBitmap() + self.wrong_image = wx.Image(os.path.join(dir, "wrong.png")).ConvertToBitmap() + def fullPaint(self, device_context): """Paint all the game on the given dc @param device_context: wx.DC""" @@ -86,16 +90,18 @@ elements.sort() for elem in elements: elem.draw(device_context) - #device_context.DrawArc(39,127, 39, 127, _font = wx.Font(65, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) device_context.SetFont(_font) device_context.SetTextForeground(wx.BLACK) - x = 100 - for score in [0,1,4,9]: - device_context.DrawText("%d" % score, x, 355) - x+=184 + for i in range(4): + answer = self.parent.players_data[i]["answer"] + score = self.parent.players_data[i]["score"] + if answer == None: + device_context.DrawText("%d" % score, 100 + i*184, 355) + else: + device_context.DrawBitmap(self.right_image if answer else self.wrong_image, 39+i*184, 348, True) if self.parent.time_origin: @@ -104,25 +110,18 @@ center_x = 760 center_y = 147 origin = self.parent.time_origin - current = time() + current = self.parent.time_pause or time() limit = self.parent.time_limit - print "limit:", limit total = limit - origin left = self.parent.time_left = max(0,limit - current) device_context.SetBrush(wx.RED_BRUSH if left/total < 1/4.0 else wx.WHITE_BRUSH) - print "left:",left if left: #we now draw the timer - print "total - left:", total - left angle = ((-2*pi)*((total-left)/total) + (pi/2)) - print "angle:", angle*57.3 x = center_x + radius * cos(angle) y = center_y - radius * sin(angle) - print "x: %s, y:%s" % (x, y) device_context.DrawArc(center_x, center_y-radius, x, y, center_x, center_y) - - def onPaint(self, event): dc = wx.PaintDC(self) self.fullPaint(dc) @@ -134,28 +133,62 @@ def __init__(self, parent, referee, players, player_nick): wx.Panel.__init__(self, parent) + self.referee = referee + self.player_nick = player_nick + self.players = players self.time_origin = None #set to unix time when the timer start self.time_limit = None self.time_left = None + self.time_pause = None + self.last_answer = None self.parent = parent self.SetMinSize(wx.Size(WIDTH, HEIGHT)) self.SetSize(wx.Size(WIDTH, HEIGHT)) self.base = BaseWindow(self) - self.question = wx.TextCtrl(self.base, -1, "", pos=(168,17), size=(613, 94), style=wx.TE_MULTILINE | wx.TE_READONLY) - self.reponse = wx.TextCtrl(self.base, -1, pos=(410,569), size=(342, 21), style=wx.TE_PROCESS_ENTER) + self.question = wx.TextCtrl(self, -1, pos=(168,17), size=(613, 94), style=wx.TE_MULTILINE | wx.TE_READONLY) + self.answer = wx.TextCtrl(self, -1, pos=(410,569), size=(342, 21), style=wx.TE_PROCESS_ENTER) + self.players_data = [{}, {}, {}, {}] + for i in range(4): + self.players_data[i]['bubble'] = wx.TextCtrl(self, -1, pos=(39+i*184, 120), size=(180, 56), style=wx.TE_MULTILINE | wx.TE_READONLY) + self.players_data[i]['bubble'].Hide() + self.players_data[i]['answer'] = None #True if the player gave a good answer + self.players_data[i]['score'] = 0 + self.answer.Bind(wx.EVT_TEXT_ENTER, self.answered) self.parent.host.bridge.quizGameReady(player_nick, referee, profile_key = self.parent.host.profile) self.state = None + + def answered(self, event): + """Called when the player gave an answer in the box""" + self.last_answer = self.answer.GetValue() + self.answer.Clear() + if self.last_answer: + self.parent.host.bridge.quizGameAnswer(self.player_nick, self.referee, self.last_answer, profile_key = self.parent.host.profile) + + def quizGameTimerExpired(self): + """Called when nobody answered the question in time""" + self.question.SetValue(_(u"Quel dommage, personne n'a trouvé la réponse\n\nAttention, la prochaine question arrive...")) + + def quizGameTimerRestarted(self, time_left): + """Called when nobody answered the question in time""" + timer_orig = self.time_limit - self.time_origin + self.time_left = time_left + self.time_limit = time() + time_left + self.time_origin = self.time_limit - timer_orig + self.time_pause = None + self.__timer_refresh() def startTimer(self, timer=60): """Start the timer to answer the question""" - def _refresh(): - self.Refresh() - if self.time_left: - wx.CallLater(1000, _refresh) self.time_left = timer self.time_origin = time() self.time_limit = self.time_origin + timer - _refresh() + self.time_pause = None + self.__timer_refresh() + + def __timer_refresh(self): + self.Refresh() + if self.time_left: + wx.CallLater(1000, self.__timer_refresh) def quizGameNew(self, data): """Start a new game, with given hand""" @@ -168,4 +201,43 @@ @param question: question to ask""" self.question.ChangeValue(question) self.startTimer(timer) + self.last_answer = None + self.answer.Clear() + + def quizGamePlayerBuzzed(self, player, pause): + """Called when the player pushed the buzzer + @param player: player who pushed the buzzer + @param pause: should we stop the timer ?""" + if pause: + self.time_pause = time() + + def quizGamePlayerSays(self, player, text, delay): + """Called when the player says something + @param player: who is talking + @param text: what the player says""" + if player != self.player_nick and self.last_answer: + #if we are not the player talking, and we have an answer, that mean that our answer has not been validated + #we can put it again in the answering box + self.answer.SetValue(self.last_answer) + idx = self.players.index(player) + bubble = self.players_data[idx]['bubble'] + bubble.SetValue(text) + bubble.Show() + self.Refresh() + wx.CallLater(delay * 1000, bubble.Hide) + def quizGameAnswerResult(self, player, good_answer, score): + """Result of the just given answer + @param player: who gave the answer + @good_answer: True if the answer is right + @score: dict of score""" + player_idx = self.players.index(player) + self.players_data[player_idx]['answer'] = good_answer + for _player in score: + _idx = self.players.index(_player) + self.players_data[_idx]['score'] = score[_player] + def removeAnswer(): + self.players_data[player_idx]['answer'] = None + self.Refresh() + wx.CallLater(2000, removeAnswer) + self.Refresh() diff -r 141eeb7cd9e6 -r 208107419b17 src/plugins/plugin_misc_quiz.py --- a/src/plugins/plugin_misc_quiz.py Sun Jun 12 16:28:33 2011 +0200 +++ b/src/plugins/plugin_misc_quiz.py Sun Jun 12 22:34:15 2011 +0200 @@ -67,21 +67,56 @@ host.bridge.addMethod("quizGameLaunch", ".communication", in_sign='ass', out_sign='', method=self.quizGameLaunch) #args: room_jid, players, profile host.bridge.addMethod("quizGameCreate", ".communication", in_sign='sass', out_sign='', method=self.quizGameCreate) #args: room_jid, players, profile host.bridge.addMethod("quizGameReady", ".communication", in_sign='sss', out_sign='', method=self.newPlayerReady) #args: player, referee, profile + host.bridge.addMethod("quizGameAnswer", ".communication", in_sign='ssss', out_sign='', method=self.playerAnswer) host.bridge.addSignal("quizGameStarted", ".communication", signature='ssass') #args: room_jid, referee, players, profile host.bridge.addSignal("quizGameNew", ".communication", signature='sa{ss}s', doc = { 'summary': 'Start a new game', - 'param_0': "jid of game's room", - 'param_1': "data of the game", + 'param_0': "room_jid: jid of game's room", + 'param_1': "game_data: data of the game", 'param_2': '%(doc_profile)s'}) host.bridge.addSignal("quizGameQuestion", ".communication", signature = 'sssis', doc = { 'summary': "Send the current question", - 'param_0': "jid of game's room", - 'param_1': "question id", - 'param_2': "question to ask", - 'param_3': "timer", + 'param_0': "room_jid: jid of game's room", + 'param_1': "question_id: question id", + 'param_2': "question: question to ask", + 'param_3': "timer: timer", + 'param_4': '%(doc_profile)s'}) + host.bridge.addSignal("quizGamePlayerBuzzed", ".communication", + signature = 'ssbs', + doc = { 'summary': "A player just pressed the buzzer", + 'param_0': "room_jid: jid of game's room", + 'param_1': "player: player who pushed the buzzer", + 'param_2': "pause: should the game be paused ?", + 'param_3': '%(doc_profile)s'}) + host.bridge.addSignal("quizGamePlayerSays", ".communication", + signature = 'sssis', + doc = { 'summary': "A player just pressed the buzzer", + 'param_0': "room_jid: jid of game's room", + 'param_1': "player: player who pushed the buzzer", + 'param_2': "text: what the player say", + 'param_3': "delay: how long, in seconds, the text must appear", 'param_4': '%(doc_profile)s'}) + host.bridge.addSignal("quizGameAnswerResult", ".communication", + signature = 'ssba{si}s', + doc = { 'summary': "Result of the just given answer", + 'param_0': "room_jid: jid of game's room", + 'param_1': "player: player who gave the answer", + 'param_2': "good_answer: True if the answer is right", + 'param_3': "score: dict of score with player as key", + 'param_4': '%(doc_profile)s'}) + host.bridge.addSignal("quizGameTimerExpired", ".communication", + signature = 'ss', + doc = { 'summary': "Nobody answered the question in time", + 'param_0': "room_jid: jid of game's room", + 'param_1': '%(doc_profile)s'}) + host.bridge.addSignal("quizGameTimerRestarted", ".communication", + signature = 'sis', + doc = { 'summary': "Nobody answered the question in time", + 'param_0': "room_jid: jid of game's room", + 'param_1': "time_left: time left before timer expiration", + 'param_2': '%(doc_profile)s'}) host.trigger.add("MUC user joined", self.userJoinedTrigger) def createGameElt(self, to_jid, type="normal"): @@ -108,6 +143,37 @@ game_data[data_elt.name] = unicode(data_elt) return game_data + def __answer_result_to_signal_args(self, answer_result_elt): + """Parse answer result element and return a tuple of signal arguments + @param answer_result_elt: answer result element + @return: (player, good_answer, score)""" + score = {} + for score_elt in answer_result_elt.children: + score[score_elt['player']] = int(score_elt['score']) + return (answer_result_elt['player'], answer_result_elt['good_answer'] == str(True), score) + + def __answer_result(self, player_answering, good_answer, game_data): + """Convert a domish an answer_result element + @param player_answering: player who gave the answer + @param good_answer: True is the answer is right + @param game_data: data of the game""" + players_data = game_data['players_data'] + score = {} + for player in game_data['players']: + score[player] = players_data[player]['score'] + + answer_result_elt = domish.Element(('','answer_result')) + answer_result_elt['player'] = player_answering + answer_result_elt['good_answer'] = str(good_answer) + + for player in score: + score_elt = domish.Element(('',"score")) + score_elt['player'] = player + score_elt['score'] = str(score[player]) + answer_result_elt.addChild(score_elt) + + return answer_result_elt + def __create_started_elt(self, players): """Create a game_started domish element""" started_elt = domish.Element(('','started')) @@ -120,7 +186,7 @@ started_elt.addChild(player_elt) return started_elt - def __ask_question(self, question_id, question, timer=30): + def __ask_question(self, question_id, question, timer): """Create a element for asking a question""" question_elt = domish.Element(('','question')) question_elt['id'] = question_id @@ -133,7 +199,7 @@ game_data['stage'] = "play" next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players']) #the player after the dealer start game_data['first_player'] = next_player = game_data['players'][next_player_idx] - to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof: + to_jid = jid.JID(room_jid.userhost()+"/"+next_player) mess = self.createGameElt(to_jid) yourturn_elt = mess.firstChildElement().addElement('your_turn') self.host.profiles[profile].xmlstream.send(mess) @@ -233,10 +299,78 @@ ready_elt['player'] = player self.host.profiles[profile].xmlstream.send(mess) - def askQuestion(self, room_jid, profile): + def playerAnswer(self, player, referee, answer, profile_key='@DEFAULT@'): + """Called when a player give an answer""" + profile = self.host.memory.getProfileName(profile_key) + if not profile: + error (_("profile %s is unknown") % profile_key) + return + debug ('new player answer (%(profile)s): %(answer)s' % {'profile':profile, 'answer':answer}) + mess = self.createGameElt(jid.JID(referee)) + answer_elt = mess.firstChildElement().addElement('player_answer') + answer_elt['player'] = player + answer_elt.addContent(answer) + self.host.profiles[profile].xmlstream.send(mess) + + def timerExpired(self, room_jid, profile): + """Called when nobody answered the question in time""" + game_data = self.games[room_jid.userhost()] + game_data['stage'] = 'expired' + mess = self.createGameElt(room_jid) + expired_elt = mess.firstChildElement().addElement('timer_expired') + self.host.profiles[profile].xmlstream.send(mess) + reactor.callLater(4, self.askQuestion, room_jid, profile) + + def pauseTimer(self, room_jid): + """Stop the timer and save the time left""" + game_data = self.games[room_jid.userhost()] + left = max(0, game_data["timer"].getTime() - time()) + game_data['timer'].cancel() + game_data['time_left'] = int(left) + game_data['previous_stage'] = game_data['stage'] + game_data['stage'] = "paused" + + def restartTimer(self, room_jid, profile): + """Restart a timer with the saved time""" + game_data = self.games[room_jid.userhost()] + assert(game_data['time_left'] != None) mess = self.createGameElt(room_jid) - mess.firstChildElement().addChild(self.__ask_question("1", u"Quel est l'âge du capitaine ?")) + restarted_elt = mess.firstChildElement().addElement('timer_restarted') + restarted_elt["time_left"] = str(game_data['time_left']) + self.host.profiles[profile].xmlstream.send(mess) + game_data["timer"] = reactor.callLater(game_data['time_left'], self.timerExpired, room_jid, profile) + game_data["time_left"] = None + game_data['stage'] = game_data['previous_stage'] + del game_data['previous_stage'] + + def askQuestion(self, room_jid, profile): + """Ask a new question""" + game_data = self.games[room_jid.userhost()] + game_data['stage'] = "question" + game_data['question_id'] = "1" + timer = 30 + mess = self.createGameElt(room_jid) + mess.firstChildElement().addChild(self.__ask_question(game_data['question_id'], u"Quel est l'âge du capitaine ?", timer)) self.host.profiles[profile].xmlstream.send(mess) + game_data["timer"] = reactor.callLater(timer, self.timerExpired, room_jid, profile) + game_data["time_left"] = None + + def checkAnswer(self, room_jid, player, answer, profile): + """Check if the answer given is right""" + game_data = self.games[room_jid.userhost()] + players_data = game_data['players_data'] + good_answer = game_data['question_id'] == "1" and answer=="42" + players_data[player]['score'] += 1 if good_answer else -1 + players_data[player]['score'] = min(9, max(0, players_data[player]['score'])) + + mess = self.createGameElt(room_jid) + mess.firstChildElement().addChild(self.__answer_result(player, good_answer, game_data)) + self.host.profiles[profile].xmlstream.send(mess) + + if good_answer: + reactor.callLater(4, self.askQuestion, room_jid, profile) + else: + reactor.callLater(4, self.restartTimer, room_jid, profile) def newGame(self, room_jid, profile): """Launch a new round""" @@ -287,7 +421,44 @@ elif elt.name == 'question': #A question is asked self.host.bridge.quizGameQuestion(room_jid.userhost(), elt["id"], unicode(elt), int(elt["timer"]), profile ) + + elif elt.name == 'player_answer': + player = elt['player'] + pause = game_data['stage'] == 'question' #we pause the game only if we are have a question at the moment + #we first send a buzzer message + mess = self.createGameElt(room_jid) + buzzer_elt = mess.firstChildElement().addElement('player_buzzed') + buzzer_elt['player'] = player + buzzer_elt['pause'] = str(pause) + self.host.profiles[profile].xmlstream.send(mess) + if pause: + self.pauseTimer(room_jid) + #and we send the player answer + mess = self.createGameElt(room_jid) + _answer = unicode(elt) + say_elt = mess.firstChildElement().addElement('player_says') + say_elt['player'] = player + say_elt.addContent(_answer) + say_elt['delay'] = "3" + reactor.callLater(2, self.host.profiles[profile].xmlstream.send, mess) + reactor.callLater(6, self.checkAnswer, room_jid, player, _answer, profile=profile) + elif elt.name == 'player_buzzed': + self.host.bridge.quizGamePlayerBuzzed(room_jid.userhost(), elt["player"], elt['pause'] == str(True), profile) + + elif elt.name == 'player_says': + self.host.bridge.quizGamePlayerSays(room_jid.userhost(), elt["player"], unicode(elt), int(elt["delay"]), profile) + + elif elt.name == 'answer_result': + player, good_answer, score = self.__answer_result_to_signal_args(elt) + self.host.bridge.quizGameAnswerResult(room_jid.userhost(), player, good_answer, score, profile) + + elif elt.name == 'timer_expired': + self.host.bridge.quizGameTimerExpired(room_jid.userhost(), profile) + + elif elt.name == 'timer_restarted': + self.host.bridge.quizGameTimerRestarted(room_jid.userhost(), int(elt['time_left']), profile) + else: error (_('Unmanaged game element: %s') % elt.name) diff -r 141eeb7cd9e6 -r 208107419b17 src/plugins/plugin_misc_tarot.py --- a/src/plugins/plugin_misc_tarot.py Sun Jun 12 16:28:33 2011 +0200 +++ b/src/plugins/plugin_misc_tarot.py Sun Jun 12 22:34:15 2011 +0200 @@ -736,7 +736,7 @@ players_data[player]['played'] = None if len(game_data['hand'][current_player]) == 0: #no card lef: the game is finished - to_jid = jid.JID(room_jid.userhost()) #FIXME: gof: + to_jid = room_jid mess = self.createGameElt(to_jid) chien_elt = mess.firstChildElement().addChild(self.__give_scores(*self.__calculate_scores(game_data))) self.host.profiles[profile].xmlstream.send(mess) @@ -750,7 +750,7 @@ next_player = self.__next_player(game_data) #finally, we tell to the next player to play - to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof: + to_jid = jid.JID(room_jid.userhost()+"/"+next_player) mess = self.createGameElt(to_jid) yourturn_elt = mess.firstChildElement().addElement('your_turn') self.host.profiles[profile].xmlstream.send(mess)