comparison 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
comparison
equal deleted inserted replaced
789:0cb423500fbb 790:19262fb77230
44 "description": _("""Base class for MUC games""") 44 "description": _("""Base class for MUC games""")
45 } 45 }
46 46
47 47
48 class RoomGame(object): 48 class RoomGame(object):
49 """This class is used to help launching a MUC game.""" 49 """This class is used to help launching a MUC game.
50
51 Bridge methods callbacks: prepareRoom, playerReady, createGame
52 Triggered methods: userJoinedTrigger, userLeftTrigger
53 Also called from subclasses: newRound
54
55 For examples of messages sequences, please look in sub-classes.
56 """
50 57
51 # Values for self.invite_mode (who can invite after the game creation) 58 # Values for self.invite_mode (who can invite after the game creation)
52 FROM_ALL, FROM_NONE, FROM_REFEREE, FROM_PLAYERS = xrange(0, 4) 59 FROM_ALL, FROM_NONE, FROM_REFEREE, FROM_PLAYERS = xrange(0, 4)
53 # Values for self.wait_mode (for who we should wait before creating the game) 60 # Values for self.wait_mode (for who we should wait before creating the game)
54 FOR_ALL, FOR_NONE = xrange(0, 2) 61 FOR_ALL, FOR_NONE = xrange(0, 2)
59 66
60 MESSAGE = '/message' 67 MESSAGE = '/message'
61 REQUEST = '%s/%s[@xmlns="%s"]' 68 REQUEST = '%s/%s[@xmlns="%s"]'
62 69
63 def __init__(self, host): 70 def __init__(self, host):
71 """For other plugin to dynamically inherit this class, it is necessary to not use __init__ but _init_.
72 The subclass itself must be initialized this way:
73
74 class MyGame(object):
75
76 def inheritFromRoomGame(self, host):
77 global RoomGame
78 RoomGame = host.plugins["ROOM-GAME"].__class__
79 self.__class__ = type(self.__class__.__name__, (self.__class__, RoomGame, object), {})
80
81 def __init__(self, host):
82 self.inheritFromRoomGame(host)
83 RoomGame._init_(self, host, ...)
84
85 """
64 self.host = host 86 self.host = host
65 87
66 def _init_(self, host, plugin_info, ns_tag, game_init={}, player_init={}): 88 def _init_(self, host, plugin_info, ns_tag, game_init={}, player_init={}):
67 """ 89 """
68 @param host 90 @param host
69 @param plugin_info: PLUGIN_INFO map of the game plugin 91 @param plugin_info: PLUGIN_INFO map of the game plugin
70 @ns_tag: couple (nameservice, tag) to construct the messages 92 @param ns_tag: couple (nameservice, tag) to construct the messages
71 @param game_init: dictionary for general game initialization 93 @param game_init: dictionary for general game initialization
72 @param player_init: dictionary for player initialization, applicable to each player 94 @param player_init: dictionary for player initialization, applicable to each player
73 """ 95 """
74 self.host = host 96 self.host = host
75 self.name = plugin_info["import_name"] 97 self.name = plugin_info["import_name"]
87 self.ready_mode = self.FORCE # TODO: asking for confirmation is not implemented 109 self.ready_mode = self.FORCE # TODO: asking for confirmation is not implemented
88 110
89 host.trigger.add("MUC user joined", self.userJoinedTrigger) 111 host.trigger.add("MUC user joined", self.userJoinedTrigger)
90 host.trigger.add("MUC user left", self.userLeftTrigger) 112 host.trigger.add("MUC user left", self.userLeftTrigger)
91 113
92 def createOrInvite(self, room, other_players, profile): 114 def _createOrInvite(self, room, other_players, profile):
93 """ 115 """
94 This is called only when someone explicitly wants to play. 116 This is called only when someone explicitly wants to play.
95 The game must not be created if one already exists in the room, 117 The game must not be created if one already exists in the room,
96 or its creation could be postponed until all the expected players 118 and its creation could be postponed until all the expected players
97 join the room (in that case it will be created from userJoinedTrigger). 119 join the room (in that case it will be created from userJoinedTrigger).
98 @param room: instance of wokkel.muc.Room 120 @param room: instance of wokkel.muc.Room
99 @param other_players: list for other players JID userhosts 121 @param other_players: list for other players JID userhosts
100 """ 122 """
101 user_jid = self.host.getJidNStream(profile)[0] 123 user_jid = self.host.getJidNStream(profile)[0]
102 room_jid_s = room.occupantJID.userhost() 124 room_jid_s = room.occupantJID.userhost()
103 nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile) 125 nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile)
104 nicks = [nick] 126 nicks = [nick]
105 if self.gameExists(room_jid_s): 127 if self._gameExists(room_jid_s):
106 if not self.checkJoinAuth(room_jid_s, user_jid.userhost(), nick): 128 if not self._checkJoinAuth(room_jid_s, user_jid.userhost(), nick):
107 return 129 return
108 nicks.extend(self.invitePlayers(room, other_players, nick, profile)) 130 nicks.extend(self._invitePlayers(room, other_players, nick, profile))
109 self.updatePlayers(room_jid_s, nicks, profile) 131 self._updatePlayers(room_jid_s, nicks, profile)
110 else: 132 else:
111 self.initGame(room_jid_s, nick) 133 self._initGame(room_jid_s, nick)
112 (auth, waiting, missing) = self.checkWaitAuth(room, other_players) 134 (auth, waiting, missing) = self._checkWaitAuth(room, other_players)
113 nicks.extend(waiting) 135 nicks.extend(waiting)
114 nicks.extend(self.invitePlayers(room, missing, nick, profile)) 136 nicks.extend(self._invitePlayers(room, missing, nick, profile))
115 if auth: 137 if auth:
116 self.createGame(room_jid_s, nicks, profile) 138 self.createGame(room_jid_s, nicks, profile)
117 else: 139 else:
118 self.updatePlayers(room_jid_s, nicks, profile) 140 self._updatePlayers(room_jid_s, nicks, profile)
119 141
120 def initGame(self, room_jid_s, referee_nick): 142 def _initGame(self, room_jid_s, referee_nick):
121 """Important: do not add the referee to 'players' yet. For a 143 """Important: do not add the referee to 'players' yet. For a
122 <players /> message to be emitted whenever a new player is joining, 144 <players /> message to be emitted whenever a new player is joining,
123 it is necessary to not modify 'players' outside of updatePlayers. 145 it is necessary to not modify 'players' outside of _updatePlayers.
124 """ 146 """
125 referee = room_jid_s + '/' + referee_nick 147 referee = room_jid_s + '/' + referee_nick
126 self.games[room_jid_s] = {'referee': referee, 'players': [], 'started': False} 148 self.games[room_jid_s] = {'referee': referee, 'players': [], 'started': False}
127 self.games[room_jid_s].update(copy.deepcopy(self.game_init)) 149 self.games[room_jid_s].update(copy.deepcopy(self.game_init))
128 150
129 def gameExists(self, room_jid_s, started=False): 151 def _gameExists(self, room_jid_s, started=False):
130 """Return True if a game has been initialized/started. 152 """Return True if a game has been initialized/started.
131 @param started: if False, the game must be initialized only, 153 @param started: if False, the game must be initialized to return True,
132 otherwise it must be initialized and started with createGame. 154 otherwise it must be initialized and started with createGame.
133 @return: True if a game is initialized/started in that room""" 155 @return: True if a game is initialized/started in that room"""
134 return room_jid_s in self.games and (not started or self.games[room_jid_s]['started']) 156 return room_jid_s in self.games and (not started or self.games[room_jid_s]['started'])
135 157
136 def checkJoinAuth(self, room_jid_s, user_jid_s=None, nick="", verbose=False): 158 def _checkJoinAuth(self, room_jid_s, user_jid_s=None, nick="", verbose=False):
137 """Checks if this profile is allowed to join the game. 159 """Checks if this profile is allowed to join the game.
138 The parameter nick is used to check if the user is already 160 The parameter nick is used to check if the user is already
139 a player in that game. When this method is called from 161 a player in that game. When this method is called from
140 userJoinedTrigger, nick is also used to check the user 162 userJoinedTrigger, nick is also used to check the user
141 identity instead of user_jid_s (see TODO remark below). 163 identity instead of user_jid_s (see TODO comment below).
142 @param room_jid_s: the room hosting the game 164 @param room_jid_s: the room hosting the game
143 @param user_jid_s: JID userhost of the user 165 @param user_jid_s: JID userhost of the user
144 @param nick: nick of the user 166 @param nick: nick of the user
167 @return: True if this profile can join the game
145 """ 168 """
146 auth = False 169 auth = False
147 if not self.gameExists(room_jid_s): 170 if not self._gameExists(room_jid_s):
148 auth = False 171 auth = False
149 elif self.join_mode == self.ALL or self.isPlayer(room_jid_s, nick): 172 elif self.join_mode == self.ALL or self.isPlayer(room_jid_s, nick):
150 auth = True 173 auth = True
151 elif self.join_mode == self.INVITED: 174 elif self.join_mode == self.INVITED:
152 # considering all the batches of invitations 175 # considering all the batches of invitations
164 187
165 if not auth and (verbose or _DEBUG): 188 if not auth and (verbose or _DEBUG):
166 debug(_("%s not allowed to join the game %s in %s") % (user_jid_s or nick, self.name, room_jid_s)) 189 debug(_("%s not allowed to join the game %s in %s") % (user_jid_s or nick, self.name, room_jid_s))
167 return auth 190 return auth
168 191
169 def updatePlayers(self, room_jid_s, nicks, profile): 192 def _updatePlayers(self, room_jid_s, nicks, profile):
170 """Signal to the room that some players joined the game""" 193 """Signal to the room that some players joined the game"""
171 if nicks == []: 194 if nicks == []:
172 return 195 return
173 new_nicks = set(nicks).difference(self.games[room_jid_s]['players']) 196 new_nicks = set(nicks).difference(self.games[room_jid_s]['players'])
174 if len(new_nicks) == 0: 197 if len(new_nicks) == 0:
175 return 198 return
176 self.games[room_jid_s]['players'].extend(new_nicks) 199 self.games[room_jid_s]['players'].extend(new_nicks)
177 self.signalPlayers(room_jid_s, [JID(room_jid_s)], profile) 200 self._signalPlayers(room_jid_s, [JID(room_jid_s)], profile)
178 201
179 def signalPlayers(self, room_jid_s, recipients, profile): 202 def _signalPlayers(self, room_jid_s, recipients, profile):
180 """Let these guys know that we are playing (they may not play themselves).""" 203 """Let these guys know that we are playing (they may not play themselves)."""
181 if self.gameExists(room_jid_s, started=True): 204 if self._gameExists(room_jid_s, started=True):
182 element = self.createStartElement(self.games[room_jid_s]['players']) 205 element = self._createStartElement(self.games[room_jid_s]['players'])
183 else: 206 else:
184 element = self.createStartElement(self.games[room_jid_s]['players'], name="players") 207 element = self._createStartElement(self.games[room_jid_s]['players'], name="players")
185 elements = [(element, None, None)] 208 elements = [(element, None, None)]
186 for child in self.getSyncData(room_jid_s): 209 for child in self.getSyncData(room_jid_s):
187 # TODO: sync data may be different and private to each player, 210 # TODO: sync data may be different and private to each player,
188 # in that case send a separate message to the new players 211 # in that case send a separate message to the new players
189 elements.append((child, None, None)) 212 elements.append((child, None, None))
190 for recipient in recipients: 213 for recipient in recipients:
191 self.sendElements(recipient, elements, profile=profile) 214 self._sendElements(recipient, elements, profile=profile)
192 215
193 def getSyncData(self, room_jid_s): 216 def getSyncData(self, room_jid_s):
194 """This method may be overwritten by any child class. 217 """This method may be overwritten by any child class.
195 @return: a list of child elements to be added for the game to be synchronized. 218 @return: a list of child elements to be added for the game to be synchronized.
196 """ 219 """
197 return [] 220 return []
198 221
199 def invitePlayers(self, room, other_players, nick, profile): 222 def _invitePlayers(self, room, other_players, nick, profile):
200 """Invite players to a room, associated game may exist or not. 223 """Invite players to a room, associated game may exist or not.
201 @param room: wokkel.muc.Room instance 224 @param room: wokkel.muc.Room instance
202 @param other_players: list of JID userhosts to invite 225 @param other_players: list of JID userhosts to invite
203 @param nick: nick of the user who send the invitation 226 @param nick: nick of the user who send the invitation
204 @return: list of the invited players who were already in the room 227 @return: list of the invited players who were already in the room
205 """ 228 """
206 room_jid = room.occupantJID.userhostJID() 229 room_jid = room.occupantJID.userhostJID()
207 room_jid_s = room.occupantJID.userhost() 230 room_jid_s = room.occupantJID.userhost()
208 if not self.checkInviteAuth(room_jid_s, nick): 231 if not self._checkInviteAuth(room_jid_s, nick):
209 return [] 232 return []
210 self.invitations.setdefault(room_jid_s, []) 233 self.invitations.setdefault(room_jid_s, [])
211 # TODO: remove invitation waiting for too long, using the time data 234 # TODO: remove invitation waiting for too long, using the time data
212 self.invitations[room_jid_s].append((time(), other_players)) 235 self.invitations[room_jid_s].append((time(), other_players))
213 nicks = [nick] 236 nicks = [nick]
218 self.host.plugins["XEP-0249"].invite(player_jid, room_jid, {"game": self.name}, profile) 241 self.host.plugins["XEP-0249"].invite(player_jid, room_jid, {"game": self.name}, profile)
219 else: 242 else:
220 nicks.append(other_nick) 243 nicks.append(other_nick)
221 return nicks 244 return nicks
222 245
223 def checkInviteAuth(self, room_jid_s, nick, verbose=False): 246 def _checkInviteAuth(self, room_jid_s, nick, verbose=False):
224 """Checks if this profile is allowed to invite players""" 247 """Checks if this user is allowed to invite players
248 @param room_jid_s: room userhost
249 @param nick: user nick in the room
250 @param verbose: display debug message
251 @return: True if the user is allowed to invite other players
252 """
225 auth = False 253 auth = False
226 if self.invite_mode == self.FROM_ALL or not self.gameExists(room_jid_s): 254 if self.invite_mode == self.FROM_ALL or not self._gameExists(room_jid_s):
227 auth = True 255 auth = True
228 elif self.invite_mode == self.FROM_NONE: 256 elif self.invite_mode == self.FROM_NONE:
229 auth = not self.gameExists(room_jid_s, started=True) 257 auth = not self._gameExists(room_jid_s, started=True)
230 elif self.invite_mode == self.FROM_REFEREE: 258 elif self.invite_mode == self.FROM_REFEREE:
231 auth = self.isReferee(room_jid_s, nick) 259 auth = self.isReferee(room_jid_s, nick)
232 elif self.invite_mode == self.FROM_PLAYERS: 260 elif self.invite_mode == self.FROM_PLAYERS:
233 auth = self.isPlayer(room_jid_s, nick) 261 auth = self.isPlayer(room_jid_s, nick)
234 if not auth and (verbose or _DEBUG): 262 if not auth and (verbose or _DEBUG):
235 debug(_("%s not allowed to invite for the game %s in %s") % (nick, self.name, room_jid_s)) 263 debug(_("%s not allowed to invite for the game %s in %s") % (nick, self.name, room_jid_s))
236 return auth 264 return auth
237 265
238 def isReferee(self, room_jid_s, nick): 266 def isReferee(self, room_jid_s, nick):
239 """Checks if the player with this nick is the referee for the game in this room""" 267 """Checks if the player with this nick is the referee for the game in this room"
240 if not self.gameExists(room_jid_s): 268 @param room_jid_s: room userhost
269 @param nick: user nick in the room
270 @return: True if the user is the referee of the game in this room
271 """
272 if not self._gameExists(room_jid_s):
241 return False 273 return False
242 return room_jid_s + '/' + nick == self.games[room_jid_s]['referee'] 274 return room_jid_s + '/' + nick == self.games[room_jid_s]['referee']
243 275
244 def isPlayer(self, room_jid_s, nick): 276 def isPlayer(self, room_jid_s, nick):
245 """Checks if the player with this nick is a player for the game in this room. 277 """Checks if the user with this nick is a player for the game in this room.
246 Important: the referee is not in the 'players' list right after the game 278 @param room_jid_s: room userhost
247 initialization - check with isReferee to be sure nick is not a player. 279 @param nick: user nick in the room
248 """ 280 @return: True if the user is a player of the game in this room
249 if not self.gameExists(room_jid_s): 281 """
282 if not self._gameExists(room_jid_s):
250 return False 283 return False
284 # Important: the referee is not in the 'players' list right after
285 # the game initialization, that's why we do also check with isReferee
251 return nick in self.games[room_jid_s]['players'] or self.isReferee(room_jid_s, nick) 286 return nick in self.games[room_jid_s]['players'] or self.isReferee(room_jid_s, nick)
252 287
253 def checkWaitAuth(self, room, other_players, verbose=False): 288 def _checkWaitAuth(self, room, other_players, verbose=False):
254 """Check if we must wait before starting the game or not. 289 """Check if we must wait for other players before starting the game.
290 @param room: wokkel.muc.Room instance
291 @param other_players: list of the players without the referee
292 @param verbose: display debug message
255 @return: (x, y, z) with: 293 @return: (x, y, z) with:
256 x: False if we must wait, True otherwise 294 x: False if we must wait, True otherwise
257 y: the nicks of the players that have been checked and confirmed 295 y: the nicks of the players that have been checked and confirmed
258 z: the players that have not been checked or that are missing 296 z: the players that have not been checked or that are missing
259 """ 297 """
268 if not result[0] and (verbose or _DEBUG): 306 if not result[0] and (verbose or _DEBUG):
269 debug(_("Still waiting for %s before starting the game %s in %s") % (result[2], self.name, room.occupantJID.userhost())) 307 debug(_("Still waiting for %s before starting the game %s in %s") % (result[2], self.name, room.occupantJID.userhost()))
270 return result 308 return result
271 309
272 def getUniqueName(self, muc_service="", profile_key='@DEFAULT@'): 310 def getUniqueName(self, muc_service="", profile_key='@DEFAULT@'):
311 """
312 @param muc_service: you can leave empty to autofind the muc service
313 @param profile_key
314 @return: a unique name for a new room to be created
315 """
273 room = self.host.plugins["XEP-0045"].getUniqueName(muc_service, profile_key=profile_key) 316 room = self.host.plugins["XEP-0045"].getUniqueName(muc_service, profile_key=profile_key)
274 return "sat_%s_%s" % (self.name.lower(), room) if room != "" else "" 317 return "sat_%s_%s" % (self.name.lower(), room) if room != "" else ""
275 318
276 def prepareRoom(self, other_players=[], room_jid=None, profile_key='@NONE@'): 319 def prepareRoom(self, other_players=[], room_jid_s=None, profile_key='@NONE@'):
277 """Prepare the room for a game: create it and invite players. 320 """Prepare the room for a game: create it if it doesn't exist and invite players.
278 @param other_players: list for other players JID userhosts 321 @param other_players: list for other players JID userhosts
279 @param room_jid: JID userhost of the room to reuse or None to create a new room 322 @param room_jid_s: JID userhost of the room, or None to generate a unique name
323 @param profile_key
280 """ 324 """
281 debug(_('Preparing room for %s game') % self.name) 325 debug(_('Preparing room for %s game') % self.name)
282 profile = self.host.memory.getProfileName(profile_key) 326 profile = self.host.memory.getProfileName(profile_key)
283 if not profile: 327 if not profile:
284 error(_("Unknown profile")) 328 error(_("Unknown profile"))
285 return 329 return
286 330
287 def roomJoined(room): 331 def roomJoined(room):
288 """@param room: instance of wokkel.muc.Room""" 332 """@param room: instance of wokkel.muc.Room"""
289 self.createOrInvite(room, other_players, profile) 333 self._createOrInvite(room, other_players, profile)
290 334
291 def afterClientInit(room_jid): 335 def afterClientInit(room_jid_s):
292 """Create/join the given room, or a unique generated one if no room is specified. 336 """Create/join the given room, or a unique generated one if no room is specified.
293 @param room_jid: room to join 337 @param room_jids: userhost of the room to join
294 """ 338 """
295 if room_jid is not None and room_jid != "": # a room name has been specified 339 if room_jid_s is not None and room_jid_s != "": # a room name has been specified
296 if room_jid in self.host.plugins["XEP-0045"].clients[profile].joined_rooms: 340 if room_jid_s in self.host.plugins["XEP-0045"].clients[profile].joined_rooms:
297 roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid]) 341 roomJoined(self.host.plugins["XEP-0045"].clients[profile].joined_rooms[room_jid_s])
298 return 342 return
299 else: 343 else:
300 room_jid = self.getUniqueName(profile_key=profile_key) 344 room_jid_s = self.getUniqueName(profile_key=profile_key)
301 if room_jid == "": 345 if room_jid_s == "":
302 return 346 return
303 user_jid = self.host.getJidNStream(profile)[0] 347 user_jid = self.host.getJidNStream(profile)[0]
304 d = self.host.plugins["XEP-0045"].join(JID(room_jid), user_jid.user, {}, profile) 348 d = self.host.plugins["XEP-0045"].join(JID(room_jid_s), user_jid.user, {}, profile)
305 d.addCallback(roomJoined) 349 d.addCallback(roomJoined)
306 350
307 client = self.host.getClient(profile) 351 client = self.host.getClient(profile)
308 if not client: 352 if not client:
309 error(_('No client for this profile key: %s') % profile_key) 353 error(_('No client for this profile key: %s') % profile_key)
310 return 354 return
311 client.client_initialized.addCallback(lambda ignore: afterClientInit(room_jid)) 355 client.client_initialized.addCallback(lambda ignore: afterClientInit(room_jid_s))
312 356
313 def userJoinedTrigger(self, room, user, profile): 357 def userJoinedTrigger(self, room, user, profile):
314 """This trigger is used to check if the new user can take part of a game, 358 """This trigger is used to check if the new user can take part of a game,
315 create the game if we were waiting for him or just update the players list. 359 create the game if we were waiting for him or just update the players list.
316 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User} 360 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User}
319 """ 363 """
320 room_jid_s = room.occupantJID.userhost() 364 room_jid_s = room.occupantJID.userhost()
321 profile_nick = room.occupantJID.resource 365 profile_nick = room.occupantJID.resource
322 if not self.isReferee(room_jid_s, profile_nick): 366 if not self.isReferee(room_jid_s, profile_nick):
323 return True # profile is not the referee 367 return True # profile is not the referee
324 if not self.checkJoinAuth(room_jid_s, nick=user.nick): 368 if not self._checkJoinAuth(room_jid_s, nick=user.nick):
325 # user not allowed but let him know that we are playing :p 369 # user not allowed but let him know that we are playing :p
326 self.signalPlayers(room_jid_s, [JID(room_jid_s + '/' + user.nick)], profile) 370 self._signalPlayers(room_jid_s, [JID(room_jid_s + '/' + user.nick)], profile)
327 return True 371 return True
328 if self.wait_mode == self.FOR_ALL: 372 if self.wait_mode == self.FOR_ALL:
329 # considering the last batch of invitations 373 # considering the last batch of invitations
330 batch = len(self.invitations[room_jid_s]) - 1 374 batch = len(self.invitations[room_jid_s]) - 1
331 if batch < 0: 375 if batch < 0:
332 error("Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid_s)) 376 error("Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid_s))
333 return True 377 return True
334 other_players = self.invitations[room_jid_s][batch][1] 378 other_players = self.invitations[room_jid_s][batch][1]
335 (auth, nicks, dummy) = self.checkWaitAuth(room, other_players) 379 (auth, nicks, dummy) = self._checkWaitAuth(room, other_players)
336 if auth: 380 if auth:
337 del self.invitations[room_jid_s][batch] 381 del self.invitations[room_jid_s][batch]
338 nicks.insert(0, profile_nick) # add the referee 382 nicks.insert(0, profile_nick) # add the referee
339 self.createGame(room_jid_s, nicks, profile_key=profile) 383 self.createGame(room_jid_s, nicks, profile_key=profile)
340 return True 384 return True
341 # let the room know that a new player joined 385 # let the room know that a new player joined
342 self.updatePlayers(room_jid_s, [user.nick], profile) 386 self._updatePlayers(room_jid_s, [user.nick], profile)
343 return True 387 return True
344 388
345 def userLeftTrigger(self, room, user, profile): 389 def userLeftTrigger(self, room, user, profile):
346 """This trigger is used to update or stop the game when a user leaves. 390 """This trigger is used to update or stop the game when a user leaves.
347 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User} 391 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User}
366 batch = 0 # add to the first batch of invitations 410 batch = 0 # add to the first batch of invitations
367 if user_jid not in self.invitations[room_jid_s][batch][1]: 411 if user_jid not in self.invitations[room_jid_s][batch][1]:
368 self.invitations[room_jid_s][batch][1].append(user_jid) 412 self.invitations[room_jid_s][batch][1].append(user_jid)
369 return True 413 return True
370 414
371 def checkCreateGameAndInit(self, room_jid_s, profile): 415 def _checkCreateGameAndInit(self, room_jid_s, profile):
372 """Check if that profile can create the game. If the game can be created 416 """Check if that profile can create the game. If the game can be created
373 but is not initialized yet, this method will also do the initialization 417 but is not initialized yet, this method will also do the initialization.
418 @param room_jid_s: room userhost
419 @param profile
374 @return: a couple (create, sync) with: 420 @return: a couple (create, sync) with:
375 - create: set to True to allow the game creation 421 - create: set to True to allow the game creation
376 - sync: set to True to advice a game synchronization 422 - sync: set to True to advice a game synchronization
377 """ 423 """
378 user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile) 424 user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid_s, profile)
379 if not user_nick: 425 if not user_nick:
380 error('Internal error') 426 error('Internal error: profile %s has not joined the room %s' % (profile, room_jid_s))
381 return False, False 427 return False, False
382 if self.gameExists(room_jid_s): 428 if self._gameExists(room_jid_s):
383 referee = self.isReferee(room_jid_s, user_nick) 429 is_referee = self.isReferee(room_jid_s, user_nick)
384 if self.gameExists(room_jid_s, started=True): 430 if self._gameExists(room_jid_s, started=True):
385 warning(_("%s game already created in room %s") % (self.name, room_jid_s)) 431 warning(_("%s game already created in room %s") % (self.name, room_jid_s))
386 return False, referee 432 return False, is_referee
387 elif not referee: 433 elif not is_referee:
388 warning(_("%s game in room %s can only be created by %s") % (self.name, room_jid_s, user_nick)) 434 warning(_("%s game in room %s can only be created by %s") % (self.name, room_jid_s, user_nick))
389 return False, False 435 return False, False
390 else: 436 else:
391 self.initGame(room_jid_s, user_nick) 437 self._initGame(room_jid_s, user_nick)
392 return True, False 438 return True, False
393 439
394 def createGame(self, room_jid_s, nicks=[], profile_key='@NONE@'): 440 def createGame(self, room_jid_s, nicks=[], profile_key='@NONE@'):
395 """Create a new game - this can be called directly from a frontend 441 """Create a new game - this can be called directly from a frontend
396 and skips all the checks and invitation system, but the game must 442 and skips all the checks and invitation system, but the game must
401 debug(_("Creating %s game in room %s") % (self.name, room_jid_s)) 447 debug(_("Creating %s game in room %s") % (self.name, room_jid_s))
402 profile = self.host.memory.getProfileName(profile_key) 448 profile = self.host.memory.getProfileName(profile_key)
403 if not profile: 449 if not profile:
404 error(_("profile %s is unknown") % profile_key) 450 error(_("profile %s is unknown") % profile_key)
405 return 451 return
406 (create, sync) = self.checkCreateGameAndInit(room_jid_s, profile) 452 (create, sync) = self._checkCreateGameAndInit(room_jid_s, profile)
407 if not create: 453 if not create:
408 if sync: 454 if sync:
409 debug(_('Synchronize game %s in %s for %s') % (self.name, room_jid_s, ', '.join(nicks))) 455 debug(_('Synchronize game %s in %s for %s') % (self.name, room_jid_s, ', '.join(nicks)))
410 # TODO: we should call a method to re-send the information to a player who left 456 # TODO: we should call a method to re-send the information to a player who left
411 # and joined the room again, currently: we may restart a whole new round... 457 # and joined the room again, currently: we may restart a whole new round...
412 self.updatePlayers(room_jid_s, nicks, profile) 458 self._updatePlayers(room_jid_s, nicks, profile)
413 return 459 return
414 self.games[room_jid_s]['started'] = True 460 self.games[room_jid_s]['started'] = True
415 self.updatePlayers(room_jid_s, nicks, profile) 461 self._updatePlayers(room_jid_s, nicks, profile)
416 if self.player_init == {}: 462 if self.player_init == {}:
417 return 463 return
418 # specific data to each player 464 # specific data to each player
419 status = {} 465 status = {}
420 players_data = {} 466 players_data = {}
423 players_data[nick] = copy.deepcopy(self.player_init) 469 players_data[nick] = copy.deepcopy(self.player_init)
424 status[nick] = "init" 470 status[nick] = "init"
425 self.games[room_jid_s].update({'status': status, 'players_data': players_data}) 471 self.games[room_jid_s].update({'status': status, 'players_data': players_data})
426 472
427 def playerReady(self, player, referee, profile_key='@NONE@'): 473 def playerReady(self, player, referee, profile_key='@NONE@'):
428 """Must be called when player is ready to start a new game""" 474 """Must be called when player is ready to start a new game
475 @param player: the player nick in the room
476 @param referee: referee userhost
477 """
429 profile = self.host.memory.getProfileName(profile_key) 478 profile = self.host.memory.getProfileName(profile_key)
430 if not profile: 479 if not profile:
431 error(_("profile %s is unknown") % profile_key) 480 error(_("profile %s is unknown") % profile_key)
432 return 481 return
433 debug('new player ready: %s' % profile) 482 debug('new player ready: %s' % profile)
483 # TODO: we probably need to add the game and room names in the sent message
434 self.send(JID(referee), 'player_ready', {'player': player}, profile=profile) 484 self.send(JID(referee), 'player_ready', {'player': player}, profile=profile)
435 485
436 def newRound(self, room_jid, data, profile): 486 def newRound(self, room_jid, data, profile):
437 """Launch a new round (reinit the user data)""" 487 """Launch a new round (reinit the user data)
488 @param room_jid: room userhost
489 @param data: a couple (common_data, msg_elts) with:
490 - common_data: backend initialization data for the new round
491 - msg_elts: dict to map each user to his specific initialization message
492 @param profile
493 """
438 debug(_('new round for %s game') % self.name) 494 debug(_('new round for %s game') % self.name)
439 game_data = self.games[room_jid.userhost()] 495 game_data = self.games[room_jid.userhost()]
440 players = game_data['players'] 496 players = game_data['players']
441 players_data = game_data['players_data'] 497 players_data = game_data['players_data']
442 game_data['stage'] = "init" 498 game_data['stage'] = "init"
452 self.send(room_jid, msg_elts, profile=profile) 508 self.send(room_jid, msg_elts, profile=profile)
453 if common_data is not None: 509 if common_data is not None:
454 for player in players: 510 for player in players:
455 players_data[player].update(common_data) 511 players_data[player].update(common_data)
456 512
457 def createGameElt(self, to_jid, type_="normal"): 513 def _createGameElt(self, to_jid):
458 """Create a generic domish Element for the game""" 514 """Create a generic domish Element for the game messages
515 @param to_jid: JID of the recipient
516 @return: the created element
517 """
459 type_ = "normal" if to_jid.resource else "groupchat" 518 type_ = "normal" if to_jid.resource else "groupchat"
460 elt = domish.Element((None, 'message')) 519 elt = domish.Element((None, 'message'))
461 elt["to"] = to_jid.full() 520 elt["to"] = to_jid.full()
462 elt["type"] = type_ 521 elt["type"] = type_
463 elt.addElement(self.ns_tag) 522 elt.addElement(self.ns_tag)
464 return elt 523 return elt
465 524
466 def createStartElement(self, players=None, name="started"): 525 def _createStartElement(self, players=None, name="started"):
467 """Create a game "started" domish Element 526 """Create a domish Element listing the game users
468 @param name: element name (default: "started"). 527 @param players: list of the players
528 @param name: element name:
529 - "started" to signal the players that the game has been started
530 - "players" to signal the list of players when the game is not started yet
531 @return the create element
469 """ 532 """
470 started_elt = domish.Element((None, name)) 533 started_elt = domish.Element((None, name))
471 if players is None: 534 if players is None:
472 return started_elt 535 return started_elt
473 idx = 0 536 idx = 0
477 player_elt['index'] = str(idx) 540 player_elt['index'] = str(idx)
478 idx += 1 541 idx += 1
479 started_elt.addChild(player_elt) 542 started_elt.addChild(player_elt)
480 return started_elt 543 return started_elt
481 544
482 def sendElements(self, to_jid, data, profile=None): 545 def _sendElements(self, to_jid, data, profile=None):
483 """ 546 """
484 @param to_jid: recipient JID 547 @param to_jid: recipient JID
485 @param data: list of (elem, attr, content) with: 548 @param data: list of (elem, attr, content) with:
486 - elem: domish.Element, unicode or a couple: 549 - elem: domish.Element, unicode or a couple:
487 - domish.Element to be directly added as a child to the message 550 - domish.Element to be directly added as a child to the message
492 @param profile: the profile from which the message is sent 555 @param profile: the profile from which the message is sent
493 """ 556 """
494 if profile is None: 557 if profile is None:
495 error(_("Message can not be sent without a sender profile")) 558 error(_("Message can not be sent without a sender profile"))
496 return 559 return
497 msg = self.createGameElt(to_jid) 560 msg = self._createGameElt(to_jid)
498 for elem, attrs, content in data: 561 for elem, attrs, content in data:
499 if elem is not None: 562 if elem is not None:
500 if isinstance(elem, domish.Element): 563 if isinstance(elem, domish.Element):
501 msg.firstChildElement().addChild(elem) 564 msg.firstChildElement().addChild(elem)
502 else: 565 else:
517 and add it as a child to the message (see domish.Element.addElement) 580 and add it as a child to the message (see domish.Element.addElement)
518 @param attrs: dictionary of attributes for the new child 581 @param attrs: dictionary of attributes for the new child
519 @param content: unicode that is appended to the child content 582 @param content: unicode that is appended to the child content
520 @param profile: the profile from which the message is sent 583 @param profile: the profile from which the message is sent
521 """ 584 """
522 self.sendElements(to_jid, [(elem, attrs, content)], profile) 585 self._sendElements(to_jid, [(elem, attrs, content)], profile)
523 586
524 def getHandler(self, profile): 587 def getHandler(self, profile):
525 return RoomGameHandler(self) 588 return RoomGameHandler(self)
526 589
527 590