def onAppStart(self): """ @note: `onAppStart` is not fully implemented in RTMPy, see ticket #138 """ # create Mr. Trivia if self.name.startswith("trivia"): # XXX: no shared object support yet (rtmpy ticket #46) #self.questions = self.getAttribute("askedQuestions") # XXX: setup dedicated logger for remoting client logging.basicConfig( level=logging.ERROR, datefmt='%Y-%m-%d %H:%M:%S:%z', format='%(asctime)s %(message)s' ) # create async AMF client self.remoting = HTTPRemotingService(self.gateway, logger=logging, user_agent=self.appAgent) self.service = self.remoting.getService(self.service_path)
class TriviaApplication(Application): """ Server-side Trivia application. """ client = TriviaClient appAgent = 'RTMP-Trivia/%s' % str(__version__) question_interval = 4 # sec hint_interval = 12 # sec total_hints = 3 def __init__(self, gateway='http://localhost:8000/gateway', service_path='trivia'): """ @param gateway: Remoting gateway URL. @type gateway: C{str} @param service_path: Remoting service path. @type service_path: C{str} """ self.gateway = gateway self.service_path = service_path self.questions = [] self.to_ask_questions = [] self.winner = False self.current_hint = 0 self.startup_time = 0 Application.__init__(self) def onConnect(self, client, username): """ Invoked when the client connects to the application for the first time. @param client: Reference to the connecting RTMP client. @type client: L{rtmpy.server.Client} @param username: User's name. @type username: C{str} """ log.msg("") log.msg(60 * "-") log.msg("New client connection for '%s' application from client: %s" % ( self.name, client.id)) log.msg("Flash Player: %s" % client.agent) log.msg("URI: %s" % client.uri) log.msg("Username: %s" % username) log.msg("") accepted_client = False # configure Trivia client if self.name.startswith("trivia"): # check for duplicate username if self._checkDuplicateName(username) == False: client.username = username client.trivia = False client.highscore = 0 # load questions, returns a Deferred accepted_client = self._load_questions() else: # XXX: return rejection message for client (duplicate username) error_msg = 'Duplicate username' else: accepted_client = True return accepted_client def onDisconnect(self, client): """ Invoked when the client disconnects from the application. @param client: Reference to the connecting RTMP client. @type client: L{rtmpy.server.Client} """ log.msg("Client '%s' has been disconnected from the application." % client.id) def onAppStart(self): """ @note: `onAppStart` is not fully implemented in RTMPy, see ticket #138 """ # create Mr. Trivia if self.name.startswith("trivia"): # XXX: no shared object support yet (rtmpy ticket #46) #self.questions = self.getAttribute("askedQuestions") # XXX: setup dedicated logger for remoting client logging.basicConfig( level=logging.ERROR, datefmt='%Y-%m-%d %H:%M:%S:%z', format='%(asctime)s %(message)s' ) # create async AMF client self.remoting = HTTPRemotingService(self.gateway, logger=logging, user_agent=self.appAgent) self.service = self.remoting.getService(self.service_path) def _gotStartupData(self, result): """ Handle loading of startup data. @param result: Remoting data returned from the server. @type result: C{list} @return: Boolean indicating whether the startup data was successfully loaded or not. @rtype: C{bool} """ self.questions = result # XXX: save the array in sharedobject (rtmpy ticket #46) #self.setAttribute("askedQuestions", self.questions) if len(self.questions) > 0: # start Mr. Trivia self._start_game() # tell the client it's ok to connect accepted = True else: # XXX: return rejection message for client error_msg = 'Could not load startup questions' accepted = False return accepted def _gotStartupError(self, failure): """ Handle errors while loading startup data. @param failure: @type failure: """ failure.printDetailedTraceback() # XXX: this currently returns 'NetConnection.Connect.Rejected' with # status message 'Authorization is required'. This should say something # like 'Error starting Mr. Trivia` instead (and possibly include the # failure). See rtmpy ticket #141. return False def _load_questions(self): """ Load questions at startup. @return: Either a Deferred (when loading the questions) or True (when the questions have previously been loaded into the application). """ # is it the first time if len(self.questions) == 0: # load the questions from database log.msg("Loading Trivia questions...") # load startup questions call = self.service.getQuestions() call.addCallback(self._gotStartupData) call.addErrback(self._gotStartupError) return call return True def _start_game(self): """ Start the game. """ if self.questions and len(self.questions) > 0: # store startup time self.startup_time = datetime.now() log.msg("Started Mr. Trivia with %s questions." % len(self.questions)) # TODO: load response record data #self.responseTimeRecord = result[1].items[0] #self.responseTimeRecord.username = result[2].items[0].username #log.msg("Current world record by '%s", 3, # self.responseTimeRecord.username + "' with " + # self.responseTimeRecord.responseTime + " seconds.", 38) # start with first question self._next_question() else: log.err("No questions found! Returned: %s" % ( str(self.questions))) def _next_question(self): """ Picks a random new question and notifies all connected clients. """ if len(self.to_ask_questions) == 0: #log.msg("Restarting...") self.to_ask_questions = self.questions[:] # pick a random question rnd_question_index = randint(1, len(self.to_ask_questions)) - 1 current_question = self.to_ask_questions[rnd_question_index] #log.msg("current_question: %s" % current_question) self.current_hint = 0 self.winner = False self.question_id = current_question.id self.question = current_question.question self.answer = current_question.answer #self.responseTimeRecord.time = 0 self.scrambled_answers = self._make_hints(self.answer, self.total_hints) #log.msg("scrambled_answers: %s" % pformat(self.scrambled_answers)) log.msg("TRIVIA : QUESTION ID %s - (%s/%s): %s | %s" % ( self.question_id, (len(self.questions) - len(self.to_ask_questions)) + 1, len(self.questions), self.question, self.answer)) # give a hint every x sec self.hint_generator = LoopingCall(self._give_hint) self.hint_generator.start(self.hint_interval) # send question to clients new_question = copy(current_question) del new_question.answer self._send_trivia_crew("newQuestion", new_question) del new_question # remove question from list self.to_ask_questions.pop(rnd_question_index) # XXX: save the list to disk (rtmpy ticket #46) #self.users_so.setProperty("askedQuestions", to_ask_questions) def _give_hint(self): """ Give new hint. """ if self.current_hint < self.total_hints: deHint = self.scrambled_answers[self.current_hint] self._send_trivia_crew("newHint", deHint, self.current_hint + 1) log.msg("TRIVIA: ///// HINT %s: %s" %( self.current_hint + 1, deHint)) self.current_hint += 1 else: # nobody guessed the right answer self._show_answer() def _show_answer(self): """ """ log.msg("TRIVIA: ///// ANSWER: %s" % self.answer) # check which clients need the answer self._send_trivia_crew("showAnswer", self.answer) # Mr. Trivia is the winner self.winner = True # stop giving hints self.hint_generator.stop() # start new question after few seconds self.question_generator = LoopingCall(self._start_new_question) self.question_generator.start(self.question_interval) def _send_trivia_crew(self, method, object1, object2=None): """ Invoke a method on all remote clients playing trivia. """ # check which clients need a question for i, client in self.clients.items(): """ if client.trivia: log.msg("client '%s' is playin trivia: '%s'" %s ( client.id, client.trivia)) """ # give the client a new question if object2: client.call(method, object1, object2) else: client.call(method, object1) def _start_new_question(self): """ Restart question generator. """ # stop startup question generator self.question_generator.stop() # give new question self._next_question() def _make_hints(self, answer, total_hints, hint_percentage=70, scrambler="*"): """ Create a list of hints for the answer to a question. """ chars = [] answer_list = [] total_chars = len(answer) if total_chars == 1: # single char answers return [scrambler, scrambler, answer] for char in range(total_chars): if answer[char] != " ": chars.append(char) min_chars = int(round((float(len(chars)) / 100) * float(hint_percentage))) selection = sample(chars, min_chars) #log.msg('min_chars %s' % min_chars) #log.msg('actual chars %s' % len(chars)) for hint in range(total_hints): hint_list = range(total_chars) total_hint = ( total_chars / total_hints ) * hint + 1 for p in range(total_chars): hint_list[p] = answer[p] if hint_list[p] != " ": hint_list[p]= scrambler for s in range(total_hint): try: if selection[s] == p: hint_list[p]= answer[p] except IndexError: pass answer_list.append("".join(hint_list)) return answer_list def _checkDuplicateName(self, name): """ Check for duplicate username among the application's connected clients. @return: Boolean indicating whether or not a client has a duplicate username. @rtype: C{bool} """ duplicate = False #log.msg('Checking duplicate username for: %s' % name) for i, client in self.clients.items(): if client.username and client.username == name: #log.msg("Found duplicate username for '%s'" % client.username) duplicate = True break return duplicate