comparison src/plugins/plugin_misc_room_game.py @ 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 1fe00f0c9a91
children 8f335c03eebb
comparison
equal deleted inserted replaced
824:c304ce32042b 825:e3f4d80f987d
143 """Important: do not add the referee to 'players' yet. For a 143 """Important: do not add the referee to 'players' yet. For a
144 <players /> message to be emitted whenever a new player is joining, 144 <players /> message to be emitted whenever a new player is joining,
145 it is necessary to not modify 'players' outside of _updatePlayers. 145 it is necessary to not modify 'players' outside of _updatePlayers.
146 """ 146 """
147 referee = room_jid_s + '/' + referee_nick 147 referee = room_jid_s + '/' + referee_nick
148 self.games[room_jid_s] = {'referee': referee, 'players': [], 'started': False} 148 self.games[room_jid_s] = {'referee': referee, 'players': [], 'started': False, 'status': {}}
149 self.games[room_jid_s].update(copy.deepcopy(self.game_init)) 149 self.games[room_jid_s].update(copy.deepcopy(self.game_init))
150 self.invitations.setdefault(room_jid_s, []) 150 self.invitations.setdefault(room_jid_s, [])
151 151
152 def _gameExists(self, room_jid_s, started=False): 152 def _gameExists(self, room_jid_s, started=False):
153 """Return True if a game has been initialized/started. 153 """Return True if a game has been initialized/started.
201 return 201 return
202 # this is better than set(nicks).difference(...) as it keeps the order 202 # this is better than set(nicks).difference(...) as it keeps the order
203 new_nicks = [nick for nick in nicks if nick not in self.games[room_jid_s]['players']] 203 new_nicks = [nick for nick in nicks if nick not in self.games[room_jid_s]['players']]
204 if len(new_nicks) == 0: 204 if len(new_nicks) == 0:
205 return 205 return
206 sync = self._gameExists(room_jid_s, True) and len(self.games[room_jid_s]['players']) > 0
207
208 def setStatus(status):
209 for nick in new_nicks:
210 self.games[room_jid_s]['status'][nick] = status
211
212 setStatus('desync' if sync else 'init')
206 self.games[room_jid_s]['players'].extend(new_nicks) 213 self.games[room_jid_s]['players'].extend(new_nicks)
207 self._signalPlayers(room_jid_s, [JID(room_jid_s)], profile) 214 self._synchronizeRoom(room_jid_s, [JID(room_jid_s)], profile)
208 215 if sync:
209 def _signalPlayers(self, room_jid_s, recipients, profile): 216 setStatus('init')
210 """Let these guys know that we are playing (they may not play themselves). 217
218 def _synchronizeRoom(self, room_jid_s, recipients, profile):
219 """Communicate the list of players to the whole room or only to some users,
220 also send the synchronization data to the players who recently joined the game.
211 @param room_jid_s: room userhost 221 @param room_jid_s: room userhost
212 @recipients: list of JIDs, the recipients of the message could be: 222 @recipients: list of JIDs, the recipients of the message could be:
213 - room JID 223 - room JID
214 - room JID + "/" + user nick 224 - room JID + "/" + user nick
225 @param profile
215 """ 226 """
216 if self._gameExists(room_jid_s, started=True): 227 if self._gameExists(room_jid_s, started=True):
217 element = self._createStartElement(self.games[room_jid_s]['players']) 228 element = self._createStartElement(self.games[room_jid_s]['players'])
218 else: 229 else:
219 element = self._createStartElement(self.games[room_jid_s]['players'], name="players") 230 element = self._createStartElement(self.games[room_jid_s]['players'], name="players")
220 elements = [(element, None, None)] 231 elements = [(element, None, None)]
221 for child in self.getSyncData(room_jid_s): 232
222 # TODO: sync data may be different and private to each player, 233 sync_args = []
223 # in that case send a separate message to the new players 234 sync_data = self.getSyncData(room_jid_s)
224 elements.append((child, None, None)) 235 for nick in sync_data:
236 user_jid = JID(room_jid_s + '/' + nick)
237 if user_jid in recipients:
238 user_elements = copy.deepcopy(elements)
239 for child in sync_data[nick]:
240 user_elements.append((child, None, None))
241 recipients.remove(user_jid)
242 else:
243 user_elements = [(child, None, None) for child in sync_data[nick]]
244 sync_args.append(([user_jid, user_elements], {'profile': profile}))
245
225 for recipient in recipients: 246 for recipient in recipients:
226 self._sendElements(recipient, elements, profile=profile) 247 self._sendElements(recipient, elements, profile=profile)
227 248 for args, kwargs in sync_args:
228 def getSyncData(self, room_jid_s): 249 self._sendElements(*args, **kwargs)
229 """This method may be overwritten by any child class. 250
230 @return: a list of child elements to be added for the game to be synchronized. 251 def getSyncData(self, room_jid_s, force_nicks=[]):
231 """ 252 """This method may (and should probably) be overwritten by a child class.
232 return [] 253 The synchronization data are returned for each player who has the state
254 'desync' or if he's been contained by force_nicks.
255 @param room_jid_s: room userhost
256 @param force_nicks: force the synchronization for this list of the nicks
257 @return: a mapping between player nicks and a list of child elements
258 to be sent by self._synchronizeRoom for the game to be synchronized.
259 """
260 return {}
233 261
234 def _invitePlayers(self, room, other_players, nick, profile): 262 def _invitePlayers(self, room, other_players, nick, profile):
235 """Invite players to a room, associated game may exist or not. 263 """Invite players to a room, associated game may exist or not.
236 @param room: wokkel.muc.Room instance 264 @param room: wokkel.muc.Room instance
237 @param other_players: list of JID userhosts to invite 265 @param other_players: list of JID userhosts to invite
378 profile_nick = room.occupantJID.resource 406 profile_nick = room.occupantJID.resource
379 if not self.isReferee(room_jid_s, profile_nick): 407 if not self.isReferee(room_jid_s, profile_nick):
380 return True # profile is not the referee 408 return True # profile is not the referee
381 if not self._checkJoinAuth(room_jid_s, user.entity.userhost() if user.entity else None, user.nick): 409 if not self._checkJoinAuth(room_jid_s, user.entity.userhost() if user.entity else None, user.nick):
382 # user not allowed but let him know that we are playing :p 410 # user not allowed but let him know that we are playing :p
383 self._signalPlayers(room_jid_s, [JID(room_jid_s + '/' + user.nick)], profile) 411 self._synchronizeRoom(room_jid_s, [JID(room_jid_s + '/' + user.nick)], profile)
384 return True 412 return True
385 if self.wait_mode == self.FOR_ALL: 413 if self.wait_mode == self.FOR_ALL:
386 # considering the last batch of invitations 414 # considering the last batch of invitations
387 batch = len(self.invitations[room_jid_s]) - 1 415 batch = len(self.invitations[room_jid_s]) - 1
388 if batch < 0: 416 if batch < 0:
464 return 492 return
465 (create, sync) = self._checkCreateGameAndInit(room_jid_s, profile) 493 (create, sync) = self._checkCreateGameAndInit(room_jid_s, profile)
466 if not create: 494 if not create:
467 if sync: 495 if sync:
468 debug(_('Synchronize game %s in %s for %s') % (self.name, room_jid_s, ', '.join(nicks))) 496 debug(_('Synchronize game %s in %s for %s') % (self.name, room_jid_s, ', '.join(nicks)))
469 # TODO: we should call a method to re-send the information to a player who left
470 # and joined the room again, currently: we may restart a whole new round...
471 self._updatePlayers(room_jid_s, nicks, profile) 497 self._updatePlayers(room_jid_s, nicks, profile)
472 return 498 return
473 self.games[room_jid_s]['started'] = True 499 self.games[room_jid_s]['started'] = True
474 self._updatePlayers(room_jid_s, nicks, profile) 500 self._updatePlayers(room_jid_s, nicks, profile)
475 if self.player_init == {}: 501 if self.player_init:
476 return 502 # specific data to each player (score, private data)
477 # specific data to each player 503 self.games[room_jid_s].setdefault('players_data', {})
478 status = {} 504 for nick in nicks:
479 players_data = {} 505 # The dict must be COPIED otherwise it is shared between all users
480 for nick in nicks: 506 self.games[room_jid_s]['players_data'][nick] = copy.deepcopy(self.player_init)
481 # The dict must be COPIED otherwise it is shared between all users
482 players_data[nick] = copy.deepcopy(self.player_init)
483 status[nick] = "init"
484 self.games[room_jid_s].update({'status': status, 'players_data': players_data})
485 507
486 def playerReady(self, player, referee, profile_key='@NONE@'): 508 def playerReady(self, player, referee, profile_key='@NONE@'):
487 """Must be called when player is ready to start a new game 509 """Must be called when player is ready to start a new game
488 @param player: the player nick in the room 510 @param player: the player nick in the room
489 @param referee: referee userhost 511 @param referee: referee userhost
581 elem.attributes.update(attrs) 603 elem.attributes.update(attrs)
582 if content is not None: 604 if content is not None:
583 elem.addContent(content) 605 elem.addContent(content)
584 self.host.profiles[profile].xmlstream.send(msg) 606 self.host.profiles[profile].xmlstream.send(msg)
585 607
586
587 def send(self, to_jid, elem=None, attrs=None, content=None, profile=None): 608 def send(self, to_jid, elem=None, attrs=None, content=None, profile=None):
588 """ 609 """
589 @param to_jid: recipient JID 610 @param to_jid: recipient JID
590 @param elem: domish.Element, unicode or a couple: 611 @param elem: domish.Element, unicode or a couple:
591 - domish.Element to be directly added as a child to the message 612 - domish.Element to be directly added as a child to the message