plugins room_games, radiocol: better synchronization after a user joins a running game

This commit is contained in:
souliane 2014-01-15 23:01:23 +01:00
commit 9e6845472d
3 changed files with 62 additions and 30 deletions

View file

@ -224,8 +224,16 @@ class Radiocol(object):
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

View file

@ -145,7 +145,7 @@ class RoomGame(object):
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 @@ class RoomGame(object):
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 _signalPlayers(self, room_jid_s, recipients, profile):
"""Let these guys know that we are playing (they may not play themselves).
def setStatus(status):
for nick in new_nicks:
self.games[room_jid_s]['status'][nick] = status
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 @@ class RoomGame(object):
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 @@ class RoomGame(object):
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 @@ class RoomGame(object):
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

View file

@ -625,3 +625,6 @@ class Tarot(object):
error(_('Unmanaged error type: %s') % elt['type'])
else:
error(_('Unmanaged card game element: %s') % elt.name)
def getSyncData(self, room_jid_s):
return {}