diff src/plugins/plugin_misc_room_game.py @ 790:19262fb77230

plugin room game: improved docstrings, added '_' as prefix for internal methods names
author souliane <souliane@mailoo.org>
date Thu, 09 Jan 2014 10:28:25 +0100
parents bfabeedbf32e
children 23b0c949b86c
line wrap: on
line diff
--- 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
         <players /> 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)