# HG changeset patch # User souliane # Date 1389259705 -3600 # Node ID 19262fb77230cc300acc6772cc2120d518b8942a # Parent 0cb423500fbb3126a60899612fea0cbf25cfe8f0 plugin room game: improved docstrings, added '_' as prefix for internal methods names diff -r 0cb423500fbb -r 19262fb77230 src/plugins/plugin_misc_radiocol.py --- a/src/plugins/plugin_misc_radiocol.py Tue Jan 07 09:27:53 2014 +0100 +++ b/src/plugins/plugin_misc_radiocol.py Thu Jan 09 10:28:25 2014 +0100 @@ -87,7 +87,7 @@ def radiocolSongAdded(self, referee, song_path, profile): """This method is called by libervia when a song has been uploaded - @param room_jid_param: jid of the room + @param referee: JID of the referee in the room (room userhost + '/' + nick) @song_path: absolute path of the song added @param profile_key: %(doc_profile_key)s""" #XXX: this is a Q&D way for the proof of concept. In the future, the song should diff -r 0cb423500fbb -r 19262fb77230 src/plugins/plugin_misc_room_game.py --- a/src/plugins/plugin_misc_room_game.py Tue Jan 07 09:27:53 2014 +0100 +++ b/src/plugins/plugin_misc_room_game.py Thu Jan 09 10:28:25 2014 +0100 @@ -46,7 +46,14 @@ class RoomGame(object): - """This class is used to help launching a MUC game.""" + """This class is used to help launching a MUC game. + + Bridge methods callbacks: prepareRoom, playerReady, createGame + Triggered methods: userJoinedTrigger, userLeftTrigger + Also called from subclasses: newRound + + For examples of messages sequences, please look in sub-classes. + """ # Values for self.invite_mode (who can invite after the game creation) FROM_ALL, FROM_NONE, FROM_REFEREE, FROM_PLAYERS = xrange(0, 4) @@ -61,13 +68,28 @@ REQUEST = '%s/%s[@xmlns="%s"]' def __init__(self, host): + """For other plugin to dynamically inherit this class, it is necessary to not use __init__ but _init_. + The subclass itself must be initialized this way: + + class MyGame(object): + + def inheritFromRoomGame(self, host): + global RoomGame + RoomGame = host.plugins["ROOM-GAME"].__class__ + self.__class__ = type(self.__class__.__name__, (self.__class__, RoomGame, object), {}) + + def __init__(self, host): + self.inheritFromRoomGame(host) + RoomGame._init_(self, host, ...) + + """ self.host = host def _init_(self, host, plugin_info, ns_tag, game_init={}, player_init={}): """ @param host @param plugin_info: PLUGIN_INFO map of the game plugin - @ns_tag: couple (nameservice, tag) to construct the messages + @param ns_tag: couple (nameservice, tag) to construct the messages @param game_init: dictionary for general game initialization @param player_init: dictionary for player initialization, applicable to each player """ @@ -89,11 +111,11 @@ host.trigger.add("MUC user joined", self.userJoinedTrigger) host.trigger.add("MUC user left", self.userLeftTrigger) - def createOrInvite(self, room, other_players, profile): + def _createOrInvite(self, room, other_players, profile): """ This is called only when someone explicitly wants to play. The game must not be created if one already exists in the room, - or its creation could be postponed until all the expected players + and its creation could be postponed until all the expected players join the room (in that case it will be created from userJoinedTrigger). @param room: instance of wokkel.muc.Room @param other_players: list for other players JID userhosts @@ -102,49 +124,50 @@ room_jid_s = room.occupantJID.userhost() nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile) nicks = [nick] - if self.gameExists(room_jid_s): - if not self.checkJoinAuth(room_jid_s, user_jid.userhost(), nick): + if self._gameExists(room_jid_s): + if not self._checkJoinAuth(room_jid_s, user_jid.userhost(), nick): return - nicks.extend(self.invitePlayers(room, other_players, nick, profile)) - self.updatePlayers(room_jid_s, nicks, profile) + nicks.extend(self._invitePlayers(room, other_players, nick, profile)) + self._updatePlayers(room_jid_s, nicks, profile) else: - self.initGame(room_jid_s, nick) - (auth, waiting, missing) = self.checkWaitAuth(room, other_players) + self._initGame(room_jid_s, nick) + (auth, waiting, missing) = self._checkWaitAuth(room, other_players) nicks.extend(waiting) - nicks.extend(self.invitePlayers(room, missing, nick, profile)) + nicks.extend(self._invitePlayers(room, missing, nick, profile)) if auth: self.createGame(room_jid_s, nicks, profile) else: - self.updatePlayers(room_jid_s, nicks, profile) + self._updatePlayers(room_jid_s, nicks, profile) - def initGame(self, room_jid_s, referee_nick): + def _initGame(self, room_jid_s, referee_nick): """Important: do not add the referee to 'players' yet. For a message to be emitted whenever a new player is joining, - it is necessary to not modify 'players' outside of updatePlayers. + it is necessary to not modify 'players' outside of _updatePlayers. """ referee = room_jid_s + '/' + referee_nick self.games[room_jid_s] = {'referee': referee, 'players': [], 'started': False} self.games[room_jid_s].update(copy.deepcopy(self.game_init)) - def gameExists(self, room_jid_s, started=False): + def _gameExists(self, room_jid_s, started=False): """Return True if a game has been initialized/started. - @param started: if False, the game must be initialized only, + @param started: if False, the game must be initialized to return True, otherwise it must be initialized and started with createGame. @return: True if a game is initialized/started in that room""" return room_jid_s in self.games and (not started or self.games[room_jid_s]['started']) - def checkJoinAuth(self, room_jid_s, user_jid_s=None, nick="", verbose=False): + def _checkJoinAuth(self, room_jid_s, user_jid_s=None, nick="", verbose=False): """Checks if this profile is allowed to join the game. The parameter nick is used to check if the user is already a player in that game. When this method is called from userJoinedTrigger, nick is also used to check the user - identity instead of user_jid_s (see TODO remark below). + identity instead of user_jid_s (see TODO comment below). @param room_jid_s: the room hosting the game @param user_jid_s: JID userhost of the user @param nick: nick of the user + @return: True if this profile can join the game """ auth = False - if not self.gameExists(room_jid_s): + if not self._gameExists(room_jid_s): auth = False elif self.join_mode == self.ALL or self.isPlayer(room_jid_s, nick): auth = True @@ -166,7 +189,7 @@ debug(_("%s not allowed to join the game %s in %s") % (user_jid_s or nick, self.name, room_jid_s)) return auth - def updatePlayers(self, room_jid_s, nicks, profile): + def _updatePlayers(self, room_jid_s, nicks, profile): """Signal to the room that some players joined the game""" if nicks == []: return @@ -174,21 +197,21 @@ if len(new_nicks) == 0: return self.games[room_jid_s]['players'].extend(new_nicks) - self.signalPlayers(room_jid_s, [JID(room_jid_s)], profile) + self._signalPlayers(room_jid_s, [JID(room_jid_s)], profile) - def signalPlayers(self, room_jid_s, recipients, profile): + def _signalPlayers(self, room_jid_s, recipients, profile): """Let these guys know that we are playing (they may not play themselves).""" - if self.gameExists(room_jid_s, started=True): - element = self.createStartElement(self.games[room_jid_s]['players']) + if self._gameExists(room_jid_s, started=True): + element = self._createStartElement(self.games[room_jid_s]['players']) else: - element = self.createStartElement(self.games[room_jid_s]['players'], name="players") + element = self._createStartElement(self.games[room_jid_s]['players'], name="players") elements = [(element, None, None)] for child in self.getSyncData(room_jid_s): # TODO: sync data may be different and private to each player, # in that case send a separate message to the new players elements.append((child, None, None)) for recipient in recipients: - self.sendElements(recipient, elements, profile=profile) + self._sendElements(recipient, elements, profile=profile) def getSyncData(self, room_jid_s): """This method may be overwritten by any child class. @@ -196,7 +219,7 @@ """ return [] - def invitePlayers(self, room, other_players, nick, profile): + def _invitePlayers(self, room, other_players, nick, profile): """Invite players to a room, associated game may exist or not. @param room: wokkel.muc.Room instance @param other_players: list of JID userhosts to invite @@ -205,7 +228,7 @@ """ room_jid = room.occupantJID.userhostJID() room_jid_s = room.occupantJID.userhost() - if not self.checkInviteAuth(room_jid_s, nick): + if not self._checkInviteAuth(room_jid_s, nick): return [] self.invitations.setdefault(room_jid_s, []) # TODO: remove invitation waiting for too long, using the time data @@ -220,13 +243,18 @@ nicks.append(other_nick) return nicks - def checkInviteAuth(self, room_jid_s, nick, verbose=False): - """Checks if this profile is allowed to invite players""" + def _checkInviteAuth(self, room_jid_s, nick, verbose=False): + """Checks if this user is allowed to invite players + @param room_jid_s: room userhost + @param nick: user nick in the room + @param verbose: display debug message + @return: True if the user is allowed to invite other players + """ auth = False - if self.invite_mode == self.FROM_ALL or not self.gameExists(room_jid_s): + if self.invite_mode == self.FROM_ALL or not self._gameExists(room_jid_s): auth = True elif self.invite_mode == self.FROM_NONE: - auth = not self.gameExists(room_jid_s, started=True) + auth = not self._gameExists(room_jid_s, started=True) elif self.invite_mode == self.FROM_REFEREE: auth = self.isReferee(room_jid_s, nick) elif self.invite_mode == self.FROM_PLAYERS: @@ -236,22 +264,32 @@ return auth def isReferee(self, room_jid_s, nick): - """Checks if the player with this nick is the referee for the game in this room""" - if not self.gameExists(room_jid_s): + """Checks if the player with this nick is the referee for the game in this room" + @param room_jid_s: room userhost + @param nick: user nick in the room + @return: True if the user is the referee of the game in this room + """ + if not self._gameExists(room_jid_s): return False return room_jid_s + '/' + nick == self.games[room_jid_s]['referee'] def isPlayer(self, room_jid_s, nick): - """Checks if the player with this nick is a player for the game in this room. - Important: the referee is not in the 'players' list right after the game - initialization - check with isReferee to be sure nick is not a player. + """Checks if the user with this nick is a player for the game in this room. + @param room_jid_s: room userhost + @param nick: user nick in the room + @return: True if the user is a player of the game in this room """ - if not self.gameExists(room_jid_s): + if not self._gameExists(room_jid_s): return False + # Important: the referee is not in the 'players' list right after + # the game initialization, that's why we do also check with isReferee return nick in self.games[room_jid_s]['players'] or self.isReferee(room_jid_s, nick) - def checkWaitAuth(self, room, other_players, verbose=False): - """Check if we must wait before starting the game or not. + def _checkWaitAuth(self, room, other_players, verbose=False): + """Check if we must wait for other players before starting the game. + @param room: wokkel.muc.Room instance + @param other_players: list of the players without the referee + @param verbose: display debug message @return: (x, y, z) with: x: False if we must wait, True otherwise y: the nicks of the players that have been checked and confirmed @@ -270,13 +308,19 @@ return result def getUniqueName(self, muc_service="", profile_key='@DEFAULT@'): + """ + @param muc_service: you can leave empty to autofind the muc service + @param profile_key + @return: a unique name for a new room to be created + """ room = self.host.plugins["XEP-0045"].getUniqueName(muc_service, profile_key=profile_key) return "sat_%s_%s" % (self.name.lower(), room) if room != "" else "" - def prepareRoom(self, other_players=[], room_jid=None, profile_key='@NONE@'): - """Prepare the room for a game: create it and invite players. + def prepareRoom(self, other_players=[], room_jid_s=None, profile_key='@NONE@'): + """Prepare the room for a game: create it if it doesn't exist and invite players. @param other_players: list for other players JID userhosts - @param room_jid: JID userhost of the room to reuse or None to create a new room + @param room_jid_s: JID userhost of the room, or None to generate a unique name + @param profile_key """ debug(_('Preparing room for %s game') % self.name) profile = self.host.memory.getProfileName(profile_key) @@ -286,29 +330,29 @@ def roomJoined(room): """@param room: instance of wokkel.muc.Room""" - self.createOrInvite(room, other_players, profile) + self._createOrInvite(room, other_players, profile) - def afterClientInit(room_jid): + def afterClientInit(room_jid_s): """Create/join the given room, or a unique generated one if no room is specified. - @param room_jid: room to join + @param room_jids: userhost of the room to join """ - if room_jid is not None and room_jid != "": # a room name has been specified - if room_jid in self.host.plugins["XEP-0045"].clients[profile].joined_rooms: - roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid]) + if room_jid_s is not None and room_jid_s != "": # a room name has been specified + if room_jid_s in self.host.plugins["XEP-0045"].clients[profile].joined_rooms: + roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid_s]) return else: - room_jid = self.getUniqueName(profile_key=profile_key) - if room_jid == "": + room_jid_s = self.getUniqueName(profile_key=profile_key) + if room_jid_s == "": return user_jid = self.host.getJidNStream(profile)[0] - d = self.host.plugins["XEP-0045"].join(JID(room_jid), user_jid.user, {}, profile) + d = self.host.plugins["XEP-0045"].join(JID(room_jid_s), user_jid.user, {}, profile) d.addCallback(roomJoined) client = self.host.getClient(profile) if not client: error(_('No client for this profile key: %s') % profile_key) return - client.client_initialized.addCallback(lambda ignore: afterClientInit(room_jid)) + client.client_initialized.addCallback(lambda ignore: afterClientInit(room_jid_s)) def userJoinedTrigger(self, room, user, profile): """This trigger is used to check if the new user can take part of a game, @@ -321,9 +365,9 @@ profile_nick = room.occupantJID.resource if not self.isReferee(room_jid_s, profile_nick): return True # profile is not the referee - if not self.checkJoinAuth(room_jid_s, nick=user.nick): + if not self._checkJoinAuth(room_jid_s, nick=user.nick): # user not allowed but let him know that we are playing :p - self.signalPlayers(room_jid_s, [JID(room_jid_s + '/' + user.nick)], profile) + self._signalPlayers(room_jid_s, [JID(room_jid_s + '/' + user.nick)], profile) return True if self.wait_mode == self.FOR_ALL: # considering the last batch of invitations @@ -332,14 +376,14 @@ error("Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid_s)) return True other_players = self.invitations[room_jid_s][batch][1] - (auth, nicks, dummy) = self.checkWaitAuth(room, other_players) + (auth, nicks, dummy) = self._checkWaitAuth(room, other_players) if auth: del self.invitations[room_jid_s][batch] nicks.insert(0, profile_nick) # add the referee self.createGame(room_jid_s, nicks, profile_key=profile) return True # let the room know that a new player joined - self.updatePlayers(room_jid_s, [user.nick], profile) + self._updatePlayers(room_jid_s, [user.nick], profile) return True def userLeftTrigger(self, room, user, profile): @@ -368,27 +412,29 @@ self.invitations[room_jid_s][batch][1].append(user_jid) return True - def checkCreateGameAndInit(self, room_jid_s, profile): + def _checkCreateGameAndInit(self, room_jid_s, profile): """Check if that profile can create the game. If the game can be created - but is not initialized yet, this method will also do the initialization + but is not initialized yet, this method will also do the initialization. + @param room_jid_s: room userhost + @param profile @return: a couple (create, sync) with: - create: set to True to allow the game creation - sync: set to True to advice a game synchronization """ user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile) if not user_nick: - error('Internal error') + error('Internal error: profile %s has not joined the room %s' % (profile, room_jid_s)) return False, False - if self.gameExists(room_jid_s): - referee = self.isReferee(room_jid_s, user_nick) - if self.gameExists(room_jid_s, started=True): + if self._gameExists(room_jid_s): + is_referee = self.isReferee(room_jid_s, user_nick) + if self._gameExists(room_jid_s, started=True): warning(_("%s game already created in room %s") % (self.name, room_jid_s)) - return False, referee - elif not referee: + return False, is_referee + elif not is_referee: warning(_("%s game in room %s can only be created by %s") % (self.name, room_jid_s, user_nick)) return False, False else: - self.initGame(room_jid_s, user_nick) + self._initGame(room_jid_s, user_nick) return True, False def createGame(self, room_jid_s, nicks=[], profile_key='@NONE@'): @@ -403,16 +449,16 @@ if not profile: error(_("profile %s is unknown") % profile_key) return - (create, sync) = self.checkCreateGameAndInit(room_jid_s, profile) + (create, sync) = self._checkCreateGameAndInit(room_jid_s, profile) if not create: if sync: debug(_('Synchronize game %s in %s for %s') % (self.name, room_jid_s, ', '.join(nicks))) # TODO: we should call a method to re-send the information to a player who left # and joined the room again, currently: we may restart a whole new round... - self.updatePlayers(room_jid_s, nicks, profile) + self._updatePlayers(room_jid_s, nicks, profile) return self.games[room_jid_s]['started'] = True - self.updatePlayers(room_jid_s, nicks, profile) + self._updatePlayers(room_jid_s, nicks, profile) if self.player_init == {}: return # specific data to each player @@ -425,16 +471,26 @@ self.games[room_jid_s].update({'status': status, 'players_data': players_data}) def playerReady(self, player, referee, profile_key='@NONE@'): - """Must be called when player is ready to start a new game""" + """Must be called when player is ready to start a new game + @param player: the player nick in the room + @param referee: referee userhost + """ profile = self.host.memory.getProfileName(profile_key) if not profile: error(_("profile %s is unknown") % profile_key) return debug('new player ready: %s' % profile) + # TODO: we probably need to add the game and room names in the sent message self.send(JID(referee), 'player_ready', {'player': player}, profile=profile) def newRound(self, room_jid, data, profile): - """Launch a new round (reinit the user data)""" + """Launch a new round (reinit the user data) + @param room_jid: room userhost + @param data: a couple (common_data, msg_elts) with: + - common_data: backend initialization data for the new round + - msg_elts: dict to map each user to his specific initialization message + @param profile + """ debug(_('new round for %s game') % self.name) game_data = self.games[room_jid.userhost()] players = game_data['players'] @@ -454,8 +510,11 @@ for player in players: players_data[player].update(common_data) - def createGameElt(self, to_jid, type_="normal"): - """Create a generic domish Element for the game""" + def _createGameElt(self, to_jid): + """Create a generic domish Element for the game messages + @param to_jid: JID of the recipient + @return: the created element + """ type_ = "normal" if to_jid.resource else "groupchat" elt = domish.Element((None, 'message')) elt["to"] = to_jid.full() @@ -463,9 +522,13 @@ elt.addElement(self.ns_tag) return elt - def createStartElement(self, players=None, name="started"): - """Create a game "started" domish Element - @param name: element name (default: "started"). + def _createStartElement(self, players=None, name="started"): + """Create a domish Element listing the game users + @param players: list of the players + @param name: element name: + - "started" to signal the players that the game has been started + - "players" to signal the list of players when the game is not started yet + @return the create element """ started_elt = domish.Element((None, name)) if players is None: @@ -479,7 +542,7 @@ started_elt.addChild(player_elt) return started_elt - def sendElements(self, to_jid, data, profile=None): + def _sendElements(self, to_jid, data, profile=None): """ @param to_jid: recipient JID @param data: list of (elem, attr, content) with: @@ -494,7 +557,7 @@ if profile is None: error(_("Message can not be sent without a sender profile")) return - msg = self.createGameElt(to_jid) + msg = self._createGameElt(to_jid) for elem, attrs, content in data: if elem is not None: if isinstance(elem, domish.Element): @@ -519,7 +582,7 @@ @param content: unicode that is appended to the child content @param profile: the profile from which the message is sent """ - self.sendElements(to_jid, [(elem, attrs, content)], profile) + self._sendElements(to_jid, [(elem, attrs, content)], profile) def getHandler(self, profile): return RoomGameHandler(self)