comparison src/plugins/plugin_misc_quiz.py @ 361:141eeb7cd9e6

Quizz game: first draft
author Goffi <goffi@goffi.org>
date Sun, 12 Jun 2011 16:28:33 +0200
parents
children 208107419b17
comparison
equal deleted inserted replaced
360:6b5626c37909 361:141eeb7cd9e6
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 """
5 SAT plugin for managing Quiz game
6 Copyright (C) 2009, 2010, 2011 Jérôme Poisson (goffi@goffi.org)
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """
21
22 from logging import debug, info, warning, error
23 from twisted.words.xish import domish
24 from twisted.internet import protocol, defer, threads, reactor
25 from twisted.words.protocols.jabber import client, jid, xmlstream
26 from twisted.words.protocols.jabber import error as jab_error
27 from twisted.words.protocols.jabber.xmlstream import IQ
28 import random
29
30 from zope.interface import implements
31
32 from wokkel import disco, iwokkel, data_form
33 from sat.tools.xml_tools import dataForm2xml
34 from sat.tools.games import TarotCard
35
36 from time import time
37
38 try:
39 from twisted.words.protocols.xmlstream import XMPPHandler
40 except ImportError:
41 from wokkel.subprotocols import XMPPHandler
42
43 MESSAGE = '/message'
44 NS_QG = 'http://www.goffi.org/protocol/quiz'
45 QG_TAG = 'quiz'
46 QG_REQUEST = MESSAGE + '/' + QG_TAG + '[@xmlns="' + NS_QG + '"]'
47
48 PLUGIN_INFO = {
49 "name": "Quiz game plugin",
50 "import_name": "Quiz",
51 "type": "Game",
52 "protocols": [],
53 "dependencies": ["XEP-0045", "XEP-0249"],
54 "main": "Quiz",
55 "handler": "yes",
56 "description": _("""Implementation of Quiz game""")
57 }
58
59
60 class Quiz():
61
62 def __init__(self, host):
63 info(_("Plugin Quiz initialization"))
64 self.host = host
65 self.games={}
66 self.waiting_inv = {} #Invitation waiting for people to join to launch a game
67 host.bridge.addMethod("quizGameLaunch", ".communication", in_sign='ass', out_sign='', method=self.quizGameLaunch) #args: room_jid, players, profile
68 host.bridge.addMethod("quizGameCreate", ".communication", in_sign='sass', out_sign='', method=self.quizGameCreate) #args: room_jid, players, profile
69 host.bridge.addMethod("quizGameReady", ".communication", in_sign='sss', out_sign='', method=self.newPlayerReady) #args: player, referee, profile
70 host.bridge.addSignal("quizGameStarted", ".communication", signature='ssass') #args: room_jid, referee, players, profile
71 host.bridge.addSignal("quizGameNew", ".communication",
72 signature='sa{ss}s',
73 doc = { 'summary': 'Start a new game',
74 'param_0': "jid of game's room",
75 'param_1': "data of the game",
76 'param_2': '%(doc_profile)s'})
77 host.bridge.addSignal("quizGameQuestion", ".communication",
78 signature = 'sssis',
79 doc = { 'summary': "Send the current question",
80 'param_0': "jid of game's room",
81 'param_1': "question id",
82 'param_2': "question to ask",
83 'param_3': "timer",
84 'param_4': '%(doc_profile)s'})
85 host.trigger.add("MUC user joined", self.userJoinedTrigger)
86
87 def createGameElt(self, to_jid, type="normal"):
88 type = "normal" if to_jid.resource else "groupchat"
89 elt = domish.Element(('jabber:client','message'))
90 elt["to"] = to_jid.full()
91 elt["type"] = type
92 elt.addElement((NS_QG, QG_TAG))
93 return elt
94
95 def __game_data_to_xml(self, game_data):
96 """Convert a game data dict to domish element"""
97 game_data_elt = domish.Element(('','game_data'))
98 for data in game_data:
99 data_elt = domish.Element(('',data))
100 data_elt.addContent(game_data[data])
101 game_data_elt.addChild(data_elt)
102 return game_data_elt
103
104 def __xml_to_game_data(self, game_data_elt):
105 """Convert a domish element with game_data to a dict"""
106 game_data = {}
107 for data_elt in game_data_elt.elements():
108 game_data[data_elt.name] = unicode(data_elt)
109 return game_data
110
111 def __create_started_elt(self, players):
112 """Create a game_started domish element"""
113 started_elt = domish.Element(('','started'))
114 idx = 0
115 for player in players:
116 player_elt = domish.Element(('','player'))
117 player_elt.addContent(player)
118 player_elt['index'] = str(idx)
119 idx+=1
120 started_elt.addChild(player_elt)
121 return started_elt
122
123 def __ask_question(self, question_id, question, timer=30):
124 """Create a element for asking a question"""
125 question_elt = domish.Element(('','question'))
126 question_elt['id'] = question_id
127 question_elt['timer'] = str(timer)
128 question_elt.addContent(question)
129 return question_elt
130
131 def __start_play(self, room_jid, game_data, profile):
132 """Start the game (tell to the first player after dealer to play"""
133 game_data['stage'] = "play"
134 next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players']) #the player after the dealer start
135 game_data['first_player'] = next_player = game_data['players'][next_player_idx]
136 to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof:
137 mess = self.createGameElt(to_jid)
138 yourturn_elt = mess.firstChildElement().addElement('your_turn')
139 self.host.profiles[profile].xmlstream.send(mess)
140
141
142 def userJoinedTrigger(self, room, user, profile):
143 """This trigger is used to check if we are waiting people in this room,
144 and to create a game if everybody is here"""
145 _room_jid = room.occupantJID.userhostJID()
146 if _room_jid in self.waiting_inv and len(room.roster) == 4:
147 #When we have 4 people in the room, we create the game
148 #TODO: check people identity
149 players = room.roster.keys()
150 del self.waiting_inv[_room_jid]
151 self.quizGameCreate(_room_jid.userhost(), players, profile_key=profile)
152 return True
153
154 def quizGameLaunch(self, players, profile_key='@DEFAULT@'):
155 """Launch a game: helper method to create a room, invite players, and create the quiz game
156 @param players: list for players jid"""
157 debug(_('Launching quiz game'))
158 profile = self.host.memory.getProfileName(profile_key)
159 if not profile:
160 error(_("Unknown profile"))
161 return
162
163 def quizRoomJoined(room):
164 _room = room.occupantJID.userhostJID()
165 for player in players:
166 self.host.plugins["XEP-0249"].invite(jid.JID(player), room.occupantJID.userhostJID(), {"game":"Quiz"}, profile)
167 self.waiting_inv[_room] = (time(), players) #TODO: remove invitation waiting for too long, using the time data
168
169 def after_init(ignore):
170 room_name = "sat_quiz_%s" % self.host.plugins["XEP-0045"].getUniqueName(profile_key)
171 print "\n\n===> room_name:", room_name
172 muc_service = None
173 for service in self.host.memory.getServerServiceEntities("conference", "text", profile):
174 if not ".irc." in service.userhost():
175 #FIXME:
176 #This awfull ugly hack is here to avoid an issue with openfire: the irc gateway
177 #use "conference/text" identity (instead of "conference/irc"), there is certainly a better way
178 #to manage this, but this hack fill do it for test purpose
179 muc_service = service
180 break
181 if not muc_service:
182 error(_("Can't find a MUC service"))
183 return
184
185 _jid, xmlstream = self.host.getJidNStream(profile)
186 d = self.host.plugins["XEP-0045"].join(muc_service.userhost(), room_name, _jid.user, profile).addCallback(quizRoomJoined)
187
188 client = self.host.getClient(profile)
189 if not client:
190 error(_('No client for this profile key: %s') % profile_key)
191 return
192 client.client_initialized.addCallback(after_init)
193
194 def quizGameCreate(self, room_jid_param, players, profile_key='@DEFAULT@'):
195 """Create a new game
196 @param room_jid_param: jid of the room
197 @param players: list of players nick (nick must exist in the room)
198 @param profile_key: %(doc_profile_key)s"""
199 debug (_("Creating Quiz game"))
200 room_jid = jid.JID(room_jid_param)
201 profile = self.host.memory.getProfileName(profile_key)
202 if not profile:
203 error (_("profile %s is unknown") % profile_key)
204 return
205 if self.games.has_key(room_jid):
206 warning (_("Quiz game already started in room %s") % room_jid.userhost())
207 else:
208 room_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid.userhost(), profile)
209 if not room_nick:
210 error ('Internal error')
211 return
212 referee = room_jid.userhost() + '/' + room_nick
213 status = {}
214 players_data = {}
215 for player in players:
216 players_data[player] = {'score':0}
217 status[player] = "init"
218 self.games[room_jid.userhost()] = {'referee':referee, 'players':players, 'status':status, 'players_data':players_data, 'stage': None}
219 for player in players:
220 mess = self.createGameElt(jid.JID(room_jid.userhost()+'/'+player))
221 mess.firstChildElement().addChild(self.__create_started_elt(players))
222 self.host.profiles[profile].xmlstream.send(mess)
223
224 def newPlayerReady(self, player, referee, profile_key='@DEFAULT@'):
225 """Must be called when player is ready to start a new game"""
226 profile = self.host.memory.getProfileName(profile_key)
227 if not profile:
228 error (_("profile %s is unknown") % profile_key)
229 return
230 debug ('new player ready: %s' % profile)
231 mess = self.createGameElt(jid.JID(referee))
232 ready_elt = mess.firstChildElement().addElement('player_ready')
233 ready_elt['player'] = player
234 self.host.profiles[profile].xmlstream.send(mess)
235
236 def askQuestion(self, room_jid, profile):
237 mess = self.createGameElt(room_jid)
238 mess.firstChildElement().addChild(self.__ask_question("1", u"Quel est l'âge du capitaine ?"))
239 self.host.profiles[profile].xmlstream.send(mess)
240
241 def newGame(self, room_jid, profile):
242 """Launch a new round"""
243 debug (_('new Quiz game'))
244 game_data = self.games[room_jid.userhost()]
245 players = game_data['players']
246 players_data = game_data['players_data']
247 game_data['stage'] = "init"
248
249 for player in players:
250 players_data[player]['game_score'] = 0
251
252 new_game_data = {"instructions": _(u"""Bienvenue dans cette partie rapide de quizz, le premier à atteindre le score de 9 remporte le jeu
253
254 Attention, tu es prêt ?""")}
255
256 mess = self.createGameElt(room_jid)
257 mess.firstChildElement().addChild(self.__game_data_to_xml(new_game_data))
258 self.host.profiles[profile].xmlstream.send(mess)
259 reactor.callLater(10, self.askQuestion, room_jid, profile)
260
261 def quiz_game_cmd(self, mess_elt, profile):
262 from_jid = jid.JID(mess_elt['from'])
263 room_jid = jid.JID(from_jid.userhost())
264 game_elt = mess_elt.firstChildElement()
265 game_data = self.games[room_jid.userhost()]
266 players_data = game_data['players_data']
267
268 for elt in game_elt.elements():
269
270 if elt.name == 'started': #new game created
271 players = []
272 for player in elt.elements():
273 players.append(unicode(player))
274 self.host.bridge.quizGameStarted(room_jid.userhost(), from_jid.full(), players, profile)
275
276 elif elt.name == 'player_ready': #ready to play
277 player = elt['player']
278 status = self.games[room_jid.userhost()]['status']
279 nb_players = len(self.games[room_jid.userhost()]['players'])
280 status[player] = 'ready'
281 debug (_('Player %(player)s is ready to start [status: %(status)s]') % {'player':player, 'status':status})
282 if status.values().count('ready') == nb_players: #everybody is ready, we can start the game
283 self.newGame(room_jid, profile)
284
285 elif elt.name == 'game_data':
286 self.host.bridge.quizGameNew(room_jid.userhost(), self.__xml_to_game_data(elt), profile)
287
288 elif elt.name == 'question': #A question is asked
289 self.host.bridge.quizGameQuestion(room_jid.userhost(), elt["id"], unicode(elt), int(elt["timer"]), profile )
290
291 else:
292 error (_('Unmanaged game element: %s') % elt.name)
293
294 def getHandler(self, profile):
295 return QuizGameHandler(self)
296
297 class QuizGameHandler (XMPPHandler):
298 implements(iwokkel.IDisco)
299
300 def __init__(self, plugin_parent):
301 self.plugin_parent = plugin_parent
302 self.host = plugin_parent.host
303
304 def connectionInitialized(self):
305 self.xmlstream.addObserver(QG_REQUEST, self.plugin_parent.quiz_game_cmd, profile = self.parent.profile)
306
307 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
308 return [disco.DiscoFeature(NS_QG)]
309
310 def getDiscoItems(self, requestor, target, nodeIdentifier=''):
311 return []
312