changeset 825:e3f4d80f987d

plugins room_games, radiocol: better synchronization after a user joins a running game
author souliane <souliane@mailoo.org>
date Wed, 15 Jan 2014 23:01:23 +0100
parents c304ce32042b
children 71f8e996f765
files src/plugins/plugin_misc_radiocol.py src/plugins/plugin_misc_room_game.py src/plugins/plugin_misc_tarot.py
diffstat 3 files changed, 63 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/src/plugins/plugin_misc_radiocol.py	Mon Feb 17 18:57:53 2014 +0100
+++ b/src/plugins/plugin_misc_radiocol.py	Wed Jan 15 23:01:23 2014 +0100
@@ -224,8 +224,16 @@
             else:
                 error(_('Unmanaged game element: %s') % elt.name)
 
-    def getSyncData(self, room_jid_s):
-        data = self.games[room_jid_s]['queue']
-        if len(data) == QUEUE_LIMIT:
-            data.append(domish.Element((None, 'no_upload')))
+    def getSyncData(self, room_jid_s, force_nicks=[]):
+        data = {}
+        status = self.games[room_jid_s]['status']
+        nicks = [nick for nick in status if status[nick] == 'desync']
+        for nick in force_nicks:
+            if nick not in nicks:
+                nicks.append(nick)
+        for nick in nicks:
+            if len(self.games[room_jid_s]['queue']) > 0:
+                data[nick] = copy.deepcopy(self.games[room_jid_s]['queue'])
+                if len(self.games[room_jid_s]['queue']) == QUEUE_LIMIT:
+                    data[nick].append(domish.Element(('', 'no_upload')))
         return data
--- a/src/plugins/plugin_misc_room_game.py	Mon Feb 17 18:57:53 2014 +0100
+++ b/src/plugins/plugin_misc_room_game.py	Wed Jan 15 23:01:23 2014 +0100
@@ -145,7 +145,7 @@
         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] = {'referee': referee, 'players': [], 'started': False, 'status': {}}
         self.games[room_jid_s].update(copy.deepcopy(self.game_init))
         self.invitations.setdefault(room_jid_s, [])
 
@@ -203,33 +203,61 @@
         new_nicks = [nick for nick in nicks if nick not in self.games[room_jid_s]['players']]
         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)
+        sync = self._gameExists(room_jid_s, True) and len(self.games[room_jid_s]['players']) > 0
+
+        def setStatus(status):
+            for nick in new_nicks:
+                self.games[room_jid_s]['status'][nick] = status
 
-    def _signalPlayers(self, room_jid_s, recipients, profile):
-        """Let these guys know that we are playing (they may not play themselves).
+        setStatus('desync' if sync else 'init')
+        self.games[room_jid_s]['players'].extend(new_nicks)
+        self._synchronizeRoom(room_jid_s, [JID(room_jid_s)], profile)
+        if sync:
+            setStatus('init')
+
+    def _synchronizeRoom(self, room_jid_s, recipients, profile):
+        """Communicate the list of players to the whole room or only to some users,
+        also send the synchronization data to the players who recently joined the game.
         @param room_jid_s: room userhost
         @recipients: list of JIDs, the recipients of the message could be:
         - room JID
         - room JID + "/" + user nick
+        @param profile
         """
         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")
         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))
+
+        sync_args = []
+        sync_data = self.getSyncData(room_jid_s)
+        for nick in sync_data:
+            user_jid = JID(room_jid_s + '/' + nick)
+            if user_jid in recipients:
+                user_elements = copy.deepcopy(elements)
+                for child in sync_data[nick]:
+                    user_elements.append((child, None, None))
+                recipients.remove(user_jid)
+            else:
+                user_elements = [(child, None, None) for child in sync_data[nick]]
+            sync_args.append(([user_jid, user_elements], {'profile': profile}))
+
         for recipient in recipients:
             self._sendElements(recipient, elements, profile=profile)
+        for args, kwargs in sync_args:
+            self._sendElements(*args, **kwargs)
 
-    def getSyncData(self, room_jid_s):
-        """This method may be overwritten by any child class.
-        @return: a list of child elements to be added for the game to be synchronized.
+    def getSyncData(self, room_jid_s, force_nicks=[]):
+        """This method may (and should probably) be overwritten by a child class.
+        The synchronization data are returned for each player who has the state
+        'desync' or if he's been contained by force_nicks.
+        @param room_jid_s: room userhost
+        @param force_nicks: force the synchronization for this list of the nicks
+        @return: a mapping between player nicks and a list of child elements
+        to be sent by self._synchronizeRoom for the game to be synchronized.
         """
-        return []
+        return {}
 
     def _invitePlayers(self, room, other_players, nick, profile):
         """Invite players to a room, associated game may exist or not.
@@ -380,7 +408,7 @@
             return True  # profile is not the referee
         if not self._checkJoinAuth(room_jid_s, user.entity.userhost() if user.entity else None, 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._synchronizeRoom(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
@@ -466,22 +494,16 @@
         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)
             return
         self.games[room_jid_s]['started'] = True
         self._updatePlayers(room_jid_s, nicks, profile)
-        if self.player_init == {}:
-            return
-        # specific data to each player
-        status = {}
-        players_data = {}
-        for nick in nicks:
-            # The dict must be COPIED otherwise it is shared between all users
-            players_data[nick] = copy.deepcopy(self.player_init)
-            status[nick] = "init"
-        self.games[room_jid_s].update({'status': status, 'players_data': players_data})
+        if self.player_init:
+            # specific data to each player (score, private data)
+            self.games[room_jid_s].setdefault('players_data', {})
+            for nick in nicks:
+                # The dict must be COPIED otherwise it is shared between all users
+                self.games[room_jid_s]['players_data'][nick] = copy.deepcopy(self.player_init)
 
     def playerReady(self, player, referee, profile_key='@NONE@'):
         """Must be called when player is ready to start a new game
@@ -583,7 +605,6 @@
                     elem.addContent(content)
         self.host.profiles[profile].xmlstream.send(msg)
 
-
     def send(self, to_jid, elem=None, attrs=None, content=None, profile=None):
         """
         @param to_jid: recipient JID
--- a/src/plugins/plugin_misc_tarot.py	Mon Feb 17 18:57:53 2014 +0100
+++ b/src/plugins/plugin_misc_tarot.py	Wed Jan 15 23:01:23 2014 +0100
@@ -625,3 +625,6 @@
                     error(_('Unmanaged error type: %s') % elt['type'])
             else:
                 error(_('Unmanaged card game element: %s') % elt.name)
+
+    def getSyncData(self, room_jid_s):
+        return {}