comparison src/tools/plugins/games.py @ 683:75e4f5e2cc65

plugins radiocol, card_game, quiz: code factorization
author souliane <souliane@mailoo.org>
date Wed, 23 Oct 2013 12:45:13 +0200
parents
children c9792d0be499
comparison
equal deleted inserted replaced
682:2805fa3f4bdf 683:75e4f5e2cc65
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # SAT: a jabber client
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013 Jérôme Poisson (goffi@goffi.org)
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from logging import debug, warning, error
21 from twisted.words.protocols.jabber.jid import JID
22 from twisted.words.xish import domish
23 from time import time
24 """This library help manage general games (e.g. card games) and it can be used by plugins"""
25
26
27 class RoomGame(object):
28 """This class is used to help launching a MUC game."""
29
30 def __init__(self, host, plugin_info, ns_tag, player_init_data={}, options={}):
31 """
32 @param host
33 @param name: the name of this name
34 @param player_init_data: dictionary for initialization data, applicable to each player
35 @param options: dictionary for game options
36 """
37 self.host = host
38 self.name = plugin_info["import_name"]
39 self.ns_tag = ns_tag
40 self.player_init_data = player_init_data
41 self.collectiveGame = self.player_init_data is {}
42 self.options = options
43 self.games = {}
44 self.waiting_inv = {} # Invitation waiting for people to join to launch a game
45
46 # args: room_jid, referee, players, profile
47 host.bridge.addSignal("%sGo" % self.name, ".plugin", signature='ssass')
48
49 def prepareRoom(self, other_players, profile_key='@NONE@'):
50 """Prepare the room for a game: create it and invite players.
51 @param other_players: list for other players JID userhosts
52 """
53 debug(_('Preparing room for %s game') % self.name)
54 profile = self.host.memory.getProfileName(profile_key)
55 if not profile:
56 error(_("Unknown profile"))
57 return
58 _jid, xmlstream = self.host.getJidNStream(profile)
59 if other_players is None:
60 other_players = []
61 players = other_players[:]
62 players.append(_jid.userhost())
63
64 def roomJoined(room):
65 _room = room.occupantJID.userhostJID()
66 if self.collectiveGame is True or other_players == [] and _jid in [user.entity for user in room.roster.values()]:
67 self.createGame(_room.userhost(), None if self.collectiveGame is True else players, profile_key=profile)
68 else:
69 self.waiting_inv[_room] = (time(), players) # TODO: remove invitation waiting for too long, using the time data
70 for player in other_players:
71 self.host.plugins["XEP-0249"].invite(JID(player), room.occupantJID.userhostJID(), {"game": self.name}, profile)
72
73 def after_init(ignore):
74 room_name = "sat_%s_%s" % (self.name.lower(), self.host.plugins["XEP-0045"].getUniqueName(profile_key))
75 print "\n\n===> room_name:", room_name
76 muc_service = None
77 for service in self.host.memory.getServerServiceEntities("conference", "text", profile):
78 if not ".irc." in service.userhost():
79 #FIXME:
80 #This awfull ugly hack is here to avoid an issue with openfire: the irc gateway
81 #use "conference/text" identity (instead of "conference/irc"), there is certainly a better way
82 #to manage this, but this hack fill do it for test purpose
83 muc_service = service
84 break
85 if not muc_service:
86 error(_("Can't find a MUC service"))
87 return
88
89 d = self.host.plugins["XEP-0045"].join(JID("%s@%s" % (room_name, muc_service.userhost())), _jid.user, {}, profile)
90 d.addCallback(roomJoined)
91
92 client = self.host.getClient(profile)
93 if not client:
94 error(_('No client for this profile key: %s') % profile_key)
95 return
96 client.client_initialized.addCallback(after_init)
97
98 def userJoinedTrigger(self, room, user, profile):
99 """This trigger is used to check if we are waiting for people in this room,
100 and to create a game if everybody is here.
101 @room: wokkel.muc.Room object. room.roster is a dict{wokkel.muc.User.nick: wokkel.muc.User}
102 @user: wokkel.muc.User object. user.nick is a unicode and user.entity a JID
103 """
104 _room_jid = room.occupantJID.userhostJID()
105 if self.collectiveGame is True:
106 if _room_jid in self.games and self.games[_room_jid]["referee"] == room.occupantJID.full():
107 #we are in a radiocol room, let's start the party !
108 mess = self.createGameElt(JID(_room_jid + '/' + user.nick))
109 mess.firstChildElement().addChild(self.__create_started_elt())
110 self.host.profiles[profile].xmlstream.send(mess)
111 return True
112 if _room_jid in self.waiting_inv and len(room.roster) >= len(self.waiting_inv[_room_jid][1]):
113 expected_players = self.waiting_inv[_room_jid][1]
114 players = []
115 for player in expected_players:
116 for user in room.roster.values():
117 if user.entity is not None:
118 # check people identity
119 if user.entity.userhost() == player:
120 players.append(user.nick)
121 continue
122 else:
123 # TODO: how to check the identity with only a nickname??
124 if user.nick == JID(player).user:
125 players.append(user.nick)
126 if len(players) < len(expected_players):
127 # Someone here was not invited! He can stay but he doesn't play :p
128 return True
129 # When we have all people in the room, we create the game
130 del self.waiting_inv[_room_jid]
131 self.createGame(_room_jid.userhost(), players, profile_key=profile)
132 return True
133
134 def createGame(self, room_jid, players=None, profile_key='@NONE@'):
135 """Create a new game
136 @param room_jid: jid of the room
137 @param players: list of players nick (nick must exist in the room)
138 @param profile_key: %(doc_profile_key)s"""
139 debug(_("Creating %s game") % self.name)
140 room = JID(room_jid).userhost()
141 profile = self.host.memory.getProfileName(profile_key)
142 if not profile:
143 error(_("profile %s is unknown") % profile_key)
144 return
145 if room in self.games:
146 warning(_("%s game already started in room %s") % (self.name, room))
147 return
148 room_nick = self.host.plugins["XEP-0045"].getRoomNick(room, profile)
149 if not room_nick:
150 error('Internal error')
151 return
152 referee = room + '/' + room_nick
153 self.games[room] = {'referee': referee}
154 self.games[room].update(self.options)
155 if self.collectiveGame is True:
156 mess = self.createGameElt(JID(room))
157 mess.firstChildElement().addChild(self.__create_started_elt())
158 self.host.profiles[profile].xmlstream.send(mess)
159 return
160 # non collaborative game = individual data and messages
161 status = {}
162 players_data = {}
163 for player in players:
164 # The dict must be COPIED otherwise it is shared between all users
165 players_data[player] = self.player_init_data.copy()
166 status[player] = "init"
167 # each player send a message to all the others
168 mess = self.createGameElt(JID(room + '/' + player))
169 mess.firstChildElement().addChild(self.__create_started_elt(players))
170 self.host.profiles[profile].xmlstream.send(mess)
171 # specific data to each player
172 self.games[room].update({'players': players, 'status': status, 'players_data': players_data})
173
174 def createCollectiveGame(self, room_jid, profile_key='@NONE@'):
175 return self.createGame(self, room_jid, players=None, profile_key=profile_key)
176
177 def playerReady(self, player, referee, profile_key='@NONE@'):
178 """Must be called when player is ready to start a new game"""
179 profile = self.host.memory.getProfileName(profile_key)
180 if not profile:
181 error(_("profile %s is unknown") % profile_key)
182 return
183 debug('new player ready: %s' % profile)
184 mess = self.createGameElt(JID(referee))
185 ready_elt = mess.firstChildElement().addElement('player_ready')
186 ready_elt['player'] = player
187 self.host.profiles[profile].xmlstream.send(mess)
188
189 def newRound(self, room_jid, data, profile):
190 """Launch a new round (reinit the user data)"""
191 debug(_('new round for %s game') % self.name)
192 game_data = self.games[room_jid.userhost()]
193 players = game_data['players']
194 players_data = game_data['players_data']
195 game_data['stage'] = "init"
196
197 common_data, msg_elts = data if data is not None else (None, None)
198
199 if isinstance(msg_elts, dict):
200 for player in players:
201 to_jid = JID(room_jid.userhost() + "/" + player) # FIXME: gof:
202 mess = self.createGameElt(to_jid)
203 if isinstance(msg_elts[player], domish.Element):
204 mess.firstChildElement().addChild(msg_elts[player])
205 self.host.profiles[profile].xmlstream.send(mess)
206 elif isinstance(msg_elts, domish.Element):
207 mess = self.createGameElt(room_jid)
208 mess.firstChildElement().addChild(msg_elts)
209 self.host.profiles[profile].xmlstream.send(mess)
210 if common_data is not None:
211 for player in players:
212 players_data[player].update(common_data)
213
214 def createGameElt(self, to_jid, type_="normal"):
215 """Create a generic domish Element for the game"""
216 type_ = "normal" if to_jid.resource else "groupchat"
217 elt = domish.Element((None, 'message'))
218 elt["to"] = to_jid.full()
219 elt["type"] = type_
220 elt.addElement(self.ns_tag)
221 return elt
222
223 def __create_started_elt(self, players=None):
224 """Create a game "started" domish Element"""
225 started_elt = domish.Element((None, 'started'))
226 if players is None:
227 return started_elt
228 idx = 0
229 for player in players:
230 player_elt = domish.Element((None, 'player'))
231 player_elt.addContent(player)
232 player_elt['index'] = str(idx)
233 idx += 1
234 started_elt.addChild(player_elt)
235 return started_elt