Mercurial > libervia-backend
annotate plugins/plugin_misc_tarot.py @ 98:dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 03 Jun 2010 17:43:49 +0930 |
parents | c8518b9a8025 |
children | 63c9067a1499 |
rev | line source |
---|---|
88 | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | |
3 | |
4 """ | |
5 SAT plugin for managing xep-0045 | |
6 Copyright (C) 2009, 2010 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 os.path | |
29 import pdb | |
30 import random | |
31 | |
32 from zope.interface import implements | |
33 | |
91 | 34 from wokkel import disco, iwokkel, data_form |
35 from tools.xml_tools import XMLTools | |
88 | 36 |
37 from base64 import b64decode | |
38 from hashlib import sha1 | |
39 from time import sleep | |
40 | |
41 try: | |
42 from twisted.words.protocols.xmlstream import XMPPHandler | |
43 except ImportError: | |
44 from wokkel.subprotocols import XMPPHandler | |
45 | |
90 | 46 MESSAGE = '/message' |
47 NS_CG = 'http://www.goffi.org/protocol/card_game' | |
48 CG_TAG = 'card_game' | |
49 CG_REQUEST = MESSAGE + '/' + CG_TAG + '[@xmlns="' + NS_CG + '"]' | |
88 | 50 |
51 PLUGIN_INFO = { | |
52 "name": "Tarot cards plugin", | |
53 "import_name": "Tarot", | |
54 "type": "Misc", | |
55 "protocols": [], | |
56 "dependencies": ["XEP_0045"], | |
57 "main": "Tarot", | |
90 | 58 "handler": "yes", |
88 | 59 "description": _("""Implementation of Tarot card game""") |
60 } | |
61 | |
94 | 62 suits_order = ['pique', 'coeur', 'trefle', 'carreau', 'atout'] #I have swith the usual order 'trefle' and 'carreau' because card are more easy to see if suit colour change (black, red, black, red) |
63 values_order = map(str,range(1,11))+["valet","cavalier","dame","roi"] | |
64 | |
65 class Card(): | |
66 """This class is used to represent a car logically""" | |
67 #TODO: move this in a library in tools, and share this with frontends (e.g. card_game in wix use the same class) | |
68 | |
69 def __init__(self, tuple_card): | |
70 """@param tuple_card: tuple (suit, value)""" | |
71 self.suit, self.value = tuple_card | |
72 self.bout = True if self.suit=="atout" and self.value in ["1","21","excuse"] else False | |
73 if self.bout or self.value == "roi": | |
74 self.points = 4.5 | |
75 elif self.value == "dame": | |
76 self.points = 3.5 | |
77 elif self.value == "cavalier": | |
78 self.points = 2.5 | |
79 elif self.value == "valet": | |
80 self.points = 1.5 | |
81 else: | |
82 self.points = 0.5 | |
83 | |
84 def get_tuple(self): | |
85 return (self.suit,self.value) | |
86 | |
87 @staticmethod | |
88 def from_tuples(tuple_list): | |
89 result = [] | |
90 for card_tuple in tuple_list: | |
91 result.append(Card(card_tuple)) | |
92 return result | |
93 | |
94 def __cmp__(self, other): | |
95 if other == None: | |
96 return 1 | |
97 if self.suit != other.suit: | |
98 idx1 = suits_order.index(self.suit) | |
99 idx2 = suits_order.index(other.suit) | |
100 return idx1.__cmp__(idx2) | |
101 if self.suit == 'atout': | |
102 if self.value == other.value == 'excuse': | |
103 return 0 | |
104 if self.value == 'excuse': | |
105 return -1 | |
106 if other.value == 'excuse': | |
107 return 1 | |
108 return int(self.value).__cmp__(int(other.value)) | |
109 #at this point we have the same suit which is not 'atout' | |
110 idx1 = values_order.index(self.value) | |
111 idx2 = values_order.index(other.value) | |
112 return idx1.__cmp__(idx2) | |
113 | |
114 def __str__(self): | |
115 return "[%s,%s]" % (self.suit, self.value) | |
116 | |
88 | 117 class Tarot(): |
118 | |
119 def __init__(self, host): | |
120 info(_("Plugin Tarot initialization")) | |
121 self.host = host | |
122 self.games={} | |
91 | 123 self.contrats = [_('Passe'), _('Petite'), _('Garde'), _('Garde Sans'), _('Garde Contre')] |
90 | 124 host.bridge.addMethod("tarotGameCreate", ".communication", in_sign='sass', out_sign='', method=self.createGame) #args: room_jid, players, profile |
92 | 125 host.bridge.addMethod("tarotGameReady", ".communication", in_sign='sss', out_sign='', method=self.newPlayerReady) #args: player, referee, profile |
126 host.bridge.addMethod("tarotGameContratChoosed", ".communication", in_sign='ssss', out_sign='', method=self.contratChoosed) #args: player, referee, contrat, profile | |
127 host.bridge.addMethod("tarotGamePlayCards", ".communication", in_sign='ssa(ss)s', out_sign='', method=self.play_cards) #args: player, referee, cards, profile | |
90 | 128 host.bridge.addSignal("tarotGameStarted", ".communication", signature='ssass') #args: room_jid, referee, players, profile |
129 host.bridge.addSignal("tarotGameNew", ".communication", signature='sa(ss)s') #args: room_jid, hand, profile | |
92 | 130 host.bridge.addSignal("tarotGameChooseContrat", ".communication", signature='sss') #args: room_jid, xml_data, profile |
131 host.bridge.addSignal("tarotGameShowCards", ".communication", signature='ssa(ss)a{ss}s') #args: room_jid, type ["chien", "poignée",...], cards, data[dict], profile | |
93 | 132 host.bridge.addSignal("tarotGameCardsPlayed", ".communication", signature='ssa(ss)s') #args: room_jid, player, type ["chien", "poignée",...], cards, data[dict], profile |
92 | 133 host.bridge.addSignal("tarotGameYourTurn", ".communication", signature='ss') #args: room_jid, profile |
95 | 134 host.bridge.addSignal("tarotGameScore", ".communication", signature='ssasass') #args: room_jid, xml_data, winners (list of nicks), loosers (list of nicks), profile |
88 | 135 self.deck_ordered = [] |
92 | 136 for value in ['excuse']+map(str,range(1,22)): |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
137 self.deck_ordered.append(Card(("atout",value))) |
92 | 138 for suit in ["pique", "coeur", "carreau", "trefle"]: |
88 | 139 for value in map(str,range(1,11))+["valet","cavalier","dame","roi"]: |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
140 self.deck_ordered.append(Card((suit, value))) |
88 | 141 |
92 | 142 def createGameElt(self, to_jid, type="normal"): |
143 type = "normal" if to_jid.resource else "groupchat" | |
90 | 144 elt = domish.Element(('jabber:client','message')) |
145 elt["to"] = to_jid.full() | |
92 | 146 elt["type"] = type |
90 | 147 elt.addElement((NS_CG, CG_TAG)) |
148 return elt | |
149 | |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
150 def __card_list_to_xml(self, cards_list, elt_name): |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
151 """Convert a card list to domish element""" |
92 | 152 cards_list_elt = domish.Element(('',elt_name)) |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
153 for card in cards_list: |
90 | 154 card_elt = domish.Element(('','card')) |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
155 card_elt['suit'] = card.suit |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
156 card_elt['value'] = card.value |
92 | 157 cards_list_elt.addChild(card_elt) |
158 return cards_list_elt | |
90 | 159 |
92 | 160 def __xml_to_list(self, cards_list_elt): |
161 """Convert a domish element with cards to a list of tuples""" | |
162 cards_list = [] | |
163 for card in cards_list_elt.elements(): | |
164 cards_list.append((card['suit'], card['value'])) | |
165 return cards_list | |
90 | 166 |
167 def __create_started_elt(self, players): | |
168 """Create a game_started domish element""" | |
169 started_elt = domish.Element(('','started')) | |
170 idx = 0 | |
171 for player in players: | |
172 player_elt = domish.Element(('','player')) | |
173 player_elt.addContent(player) | |
174 player_elt['index'] = str(idx) | |
175 idx+=1 | |
176 started_elt.addChild(player_elt) | |
177 return started_elt | |
178 | |
91 | 179 def __ask_contrat(self): |
180 """Create a element for asking contrat""" | |
181 contrat_elt = domish.Element(('','contrat')) | |
182 form = data_form.Form('form', title=_('contrat selection')) | |
183 field = data_form.Field('list-single', 'contrat', options=map(data_form.Option, self.contrats), required=True) | |
184 form.addField(field) | |
185 contrat_elt.addChild(form.toElement()) | |
186 return contrat_elt | |
187 | |
95 | 188 def __give_scores(self, scores, winners, loosers): |
189 """Create an element to give scores | |
190 @param scores: unicode (can contain line feed) | |
191 @param winners: list of unicode nicks of winners | |
192 @param loosers: list of unicode nicks of loosers""" | |
193 | |
194 score_elt = domish.Element(('','score')) | |
195 form = data_form.Form('form', title=_('scores')) | |
196 for line in scores.split('\n'): | |
197 field = data_form.Field('fixed', value = line) | |
198 form.addField(field) | |
199 score_elt.addChild(form.toElement()) | |
200 for winner in winners: | |
201 winner_elt = domish.Element(('','winner')) | |
202 winner_elt.addContent(winner) | |
203 score_elt.addChild(winner_elt) | |
204 for looser in loosers: | |
205 looser_elt = domish.Element(('','looser')) | |
206 looser_elt.addContent(looser) | |
207 score_elt.addChild(looser_elt) | |
208 return score_elt | |
209 | |
94 | 210 def __next_player(self, game_data, next_pl = None): |
211 """Increment player number & return player name | |
212 @param next_pl: if given, then next_player is forced to this one | |
213 """ | |
214 if next_pl: | |
215 game_data['current_player'] = game_data['players'].index(next_pl) | |
216 return next_pl | |
217 else: | |
218 pl_idx = game_data['current_player'] = (game_data['current_player'] + 1) % len(game_data['players']) | |
219 return game_data['players'][pl_idx] | |
220 | |
221 def __winner(self, game_data): | |
222 """give the nick of the player who win this trick""" | |
223 players_data = game_data['players_data'] | |
224 first = game_data['first_player'] | |
225 first_idx = game_data['players'].index(first) | |
226 suit_asked = None | |
227 strongest = None | |
228 winner = None | |
229 for idx in [(first_idx + i) % 4 for i in range(4)]: | |
230 player = game_data['players'][idx] | |
231 card = players_data[player]['played'] | |
232 if card.value == "excuse": | |
233 continue | |
234 if suit_asked == None: | |
235 suit_asked = card.suit | |
236 if (card.suit == suit_asked or card.suit == "atout") and card > strongest: | |
237 strongest = card | |
238 winner = player | |
95 | 239 assert winner |
94 | 240 return winner |
241 | |
242 def __excuse_hack(self, game_data, played, winner): | |
95 | 243 """give a low card to other team and keep excuse if trick is lost |
244 @param game_data: data of the game | |
245 @param played: cards currently on the table | |
246 @param winner: nick of the trick winner""" | |
94 | 247 #TODO: manage the case where excuse is played on the last trick (and lost) |
248 #TODO: gof: manage excuse (fool) | |
249 players_data = game_data['players_data'] | |
250 excuse = Card(("atout","excuse")) | |
95 | 251 |
252 #we first check if the Excuse was already player | |
253 #and if somebody is waiting for a card | |
94 | 254 for player in game_data['players']: |
255 if players_data[player]['wait_for_low']: | |
256 #the excuse owner has to give a card to somebody | |
257 if winner == player: | |
258 #the excuse owner win the trick, we check if we have something to give | |
259 for card in played: | |
260 if card.points == 0.5: | |
261 pl_waiting = players_data[player]['wait_for_low'] | |
262 played.remove(card) | |
263 players_data[pl_waiting]['levees'].append(card) | |
264 debug (_('Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner":player, "card_waited": card, "player_waiting":pl_waiting}) | |
95 | 265 return |
94 | 266 return |
267 | |
268 if not excuse in played: | |
95 | 269 #the Excuse is not on the table, nothing to do |
94 | 270 return |
271 | |
95 | 272 excuse_player = None #Who has played the Excuse ? |
94 | 273 for player in game_data['players']: |
274 if players_data[player]['played'] == excuse: | |
275 excuse_player = player | |
276 break | |
277 | |
278 if excuse_player == winner: | |
279 return #the excuse player win the trick, nothing to do | |
280 | |
281 #first we remove the excuse from played cards | |
282 played.remove(excuse) | |
283 #then we give it back to the original owner | |
284 owner_levees = players_data[excuse_player]['levees'] | |
285 owner_levees.append(excuse) | |
286 #finally we give a low card to the trick winner | |
287 low_card = None | |
95 | 288 #We look backward in cards won by the Excuse owner to |
289 #find a low value card | |
94 | 290 for card_idx in range(len(owner_levees)-1, -1, -1): |
291 if owner_levees[card_idx].points == 0.5: | |
292 low_card = owner_levees[card_idx] | |
293 del owner_levees[card_idx] | |
294 players_data[winner]['levees'].append(low_card) | |
295 debug (_('Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner":excuse_player, "card_waited": low_card, "player_waiting":winner}) | |
296 break | |
297 if not low_card: #The player has no low card yet | |
298 #TODO: manage case when player never win a trick with low card | |
299 players_data[excuse_player]['wait_for_low'] = winner | |
300 debug(_("%(excuse_owner)s keep the Excuse but has not card to give, %(winner)s is waiting for one") % {'excuse_owner':excuse_player, 'winner':winner}) | |
301 | |
302 | |
303 def __calculate_scores(self, game_data): | |
95 | 304 """The game is finished, time to know who won :) |
305 @param game_data: data of the game | |
306 @return: tuple with (string victory message, list of winners, list of loosers)""" | |
94 | 307 players_data = game_data['players_data'] |
308 levees = players_data[game_data['attaquant']]['levees'] | |
309 score = 0 | |
310 nb_bouts = 0 | |
95 | 311 bouts = [] |
94 | 312 for card in levees: |
313 if card.bout: | |
314 nb_bouts +=1 | |
95 | 315 bouts.append(card.value) |
94 | 316 score += card.points |
95 | 317 |
318 #We now check if there is no bug in score calculation | |
319 check_score = 0 | |
320 defenseurs = game_data['players'][:] | |
321 defenseurs.remove(game_data['attaquant']) | |
322 for defenseur in defenseurs: | |
323 for card in players_data[defenseur]['levees']: | |
324 check_score+=card.points | |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
325 if game_data['contrat'] == "Garde Contre": |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
326 for card in game_data['chien']: |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
327 check_score+=card.points |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
328 if ( score + check_score != 91 ): |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
329 pdb.set_trace() |
95 | 330 assert (score + check_score == 91) |
331 | |
94 | 332 point_limit = None |
333 if nb_bouts == 3: | |
334 point_limit = 36 | |
335 elif nb_bouts == 2: | |
336 point_limit = 41 | |
337 elif nb_bouts == 1: | |
338 point_limit = 51 | |
339 else: | |
340 point_limit = 56 | |
95 | 341 if game_data['contrat'] == 'Petite': |
342 contrat_mult = 1 | |
343 elif game_data['contrat'] == 'Garde': | |
344 contrat_mult = 2 | |
345 elif game_data['contrat'] == 'Garde Sans': | |
346 contrat_mult = 4 | |
347 elif game_data['contrat'] == 'Garde Contre': | |
348 contrat_mult = 6 | |
349 else: | |
350 error(_('Internal error: contrat not managed (mispelled ?)')) | |
351 | |
94 | 352 victory = (score >= point_limit) |
95 | 353 margin = score - point_limit |
354 points_defenseur = (-margin + 25) * contrat_mult | |
355 winners = [] | |
356 loosers = [] | |
357 player_score = {} | |
358 for player in game_data['players']: | |
359 #TODO: adjust this for 3 and 5 players variants | |
360 #TODO: manage bonuses (petit au bout, poignée, chelem) | |
361 player_score[player] = points_defenseur if player != game_data['attaquant'] else points_defenseur * -3 | |
362 players_data[player]['score'] += player_score[player] #we add score of this game to the global score | |
363 if player_score[player] > 0: | |
364 winners.append(player) | |
365 else: | |
366 loosers.append(player) | |
94 | 367 |
95 | 368 scores_str = _('The attacker (%(attaquant)s) makes %(points)i and needs to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s: %(bouts)s): he %(victory)s') % {'attaquant':game_data['attaquant'], 'points':score, 'point_limit':point_limit, 'nb_bouts': nb_bouts, 'plural': 's' if nb_bouts>1 else '', 'bouts':','.join(map(str,bouts)), 'victory': 'win' if victory else 'loose'} |
369 scores_str+='\n' | |
370 for player in game_data['players']: | |
371 scores_str+=_("\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i") % {'player':player, 'score_game':player_score[player], 'total_score': players_data[player]['score']} | |
372 debug(scores_str) | |
373 | |
374 return (scores_str, winners, loosers) | |
94 | 375 |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
376 def __start_play(self, room_jid, game_data, profile): |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
377 """Start the game (tell to the first player after dealer to play""" |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
378 game_data['stage'] = "play" |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
379 next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players']) #the player after the dealer start |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
380 game_data['first_player'] = next_player = game_data['players'][next_player_idx] |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
381 to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof: |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
382 mess = self.createGameElt(to_jid) |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
383 yourturn_elt = mess.firstChildElement().addElement('your_turn') |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
384 self.host.profiles[profile].xmlstream.send(mess) |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
385 |
91 | 386 |
90 | 387 def createGame(self, room_jid_param, players, profile_key='@DEFAULT@'): |
88 | 388 """Create a new game""" |
389 debug (_("Creating Tarot game")) | |
90 | 390 room_jid = jid.JID(room_jid_param) |
88 | 391 profile = self.host.memory.getProfileName(profile_key) |
392 if not profile: | |
393 error (_("profile %s is unknown") % profile_key) | |
394 return | |
395 if False: #gof: self.games.has_key(room_jid): | |
90 | 396 warning (_("Tarot game already started in room %s") % room_jid.userhost()) |
88 | 397 else: |
93 | 398 room_nick = self.host.plugins["XEP_0045"].getRoomNick(room_jid.userhost(), profile) |
399 if not room_nick: | |
400 error ('Internal error') | |
401 return | |
402 referee = room_jid.userhost() + '/' + room_nick | |
90 | 403 status = {} |
91 | 404 players_data = {} |
90 | 405 for player in players: |
95 | 406 players_data[player] = {'score':0} |
90 | 407 status[player] = "init" |
95 | 408 self.games[room_jid.userhost()] = {'referee':referee, 'players':players, 'status':status, 'players_data':players_data, 'hand_size':18, 'init_player':0, 'current_player': None, 'contrat': None, 'stage': None} |
90 | 409 for player in players: |
410 mess = self.createGameElt(jid.JID(room_jid.userhost()+'/'+player)) | |
411 mess.firstChildElement().addChild(self.__create_started_elt(players)) | |
412 self.host.profiles[profile].xmlstream.send(mess) | |
413 | |
92 | 414 def newPlayerReady(self, player, referee, profile_key='@DEFAULT@'): |
90 | 415 """Must be called when player is ready to start a new game""" |
416 profile = self.host.memory.getProfileName(profile_key) | |
417 if not profile: | |
418 error (_("profile %s is unknown") % profile_key) | |
419 return | |
420 debug ('new player ready: %s' % profile) | |
421 mess = self.createGameElt(jid.JID(referee)) | |
91 | 422 ready_elt = mess.firstChildElement().addElement('player_ready') |
92 | 423 ready_elt['player'] = player |
91 | 424 self.host.profiles[profile].xmlstream.send(mess) |
425 | |
92 | 426 def contratChoosed(self, player, referee, contrat, profile_key='@DEFAULT@'): |
91 | 427 """Must be call by player when the contrat is selected |
92 | 428 @param player: player's name |
91 | 429 @param referee: arbiter jid |
430 @contrat: contrat choosed (must be the exact same string than in the give list options) | |
431 @profile_key: profile | |
432 """ | |
433 profile = self.host.memory.getProfileName(profile_key) | |
434 if not profile: | |
435 error (_("profile %s is unknown") % profile_key) | |
436 return | |
437 debug (_('contrat [%(contrat)s] choosed by %(profile)s') % {'contrat':contrat, 'profile':profile}) | |
438 mess = self.createGameElt(jid.JID(referee)) | |
439 contrat_elt = mess.firstChildElement().addElement(('','contrat_choosed'), content=contrat) | |
92 | 440 contrat_elt['player'] = player |
90 | 441 self.host.profiles[profile].xmlstream.send(mess) |
88 | 442 |
92 | 443 def play_cards(self, player, referee, cards, profile_key='@DEFAULT@'): |
444 """Must be call by player when the contrat is selected | |
445 @param player: player's name | |
446 @param referee: arbiter jid | |
447 @cards: cards played (list of tuples) | |
448 @profile_key: profile | |
449 """ | |
450 profile = self.host.memory.getProfileName(profile_key) | |
451 if not profile: | |
452 error (_("profile %s is unknown") % profile_key) | |
453 return | |
454 debug (_('Cards played by %(profile)s: [%(cards)s]') % {'profile':profile,'cards':cards}) | |
455 mess = self.createGameElt(jid.JID(referee)) | |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
456 playcard_elt = mess.firstChildElement().addChild(self.__card_list_to_xml(Card.from_tuples(cards), 'cards_played')) |
92 | 457 playcard_elt['player'] = player |
458 self.host.profiles[profile].xmlstream.send(mess) | |
88 | 459 |
92 | 460 def newGame(self, room_jid, profile): |
88 | 461 """Launch a new round""" |
462 debug (_('new Tarot game')) | |
463 deck = self.deck_ordered[:] | |
464 random.shuffle(deck) | |
91 | 465 game_data = self.games[room_jid.userhost()] |
466 players = game_data['players'] | |
467 players_data = game_data['players_data'] | |
468 current_player = game_data['current_player'] | |
92 | 469 game_data['stage'] = "init" |
94 | 470 game_data['first_player'] = None #first player for the current trick |
95 | 471 game_data['contrat'] = None |
91 | 472 hand = game_data['hand'] = {} |
473 hand_size = game_data['hand_size'] | |
474 chien = game_data['chien'] = [] | |
88 | 475 for i in range(4): #TODO: distribute according to real Tarot rules (3 by 3 counter-clockwise, 1 card at once to chien) |
476 hand[players[i]] = deck[0:hand_size] | |
477 del deck[0:hand_size] | |
92 | 478 chien.extend(deck) |
88 | 479 del(deck[:]) |
480 | |
481 for player in players: | |
90 | 482 to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof: |
483 mess = self.createGameElt(to_jid) | |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
484 mess.firstChildElement().addChild(self.__card_list_to_xml(hand[player], 'hand')) |
92 | 485 self.host.profiles[profile].xmlstream.send(mess) |
91 | 486 players_data[player]['contrat'] = None |
92 | 487 players_data[player]['levees'] = [] #cards won |
94 | 488 players_data[player]['played'] = None #card on the table |
489 players_data[player]['wait_for_low'] = None #Used when a player wait for a low card because of excuse | |
91 | 490 |
491 pl_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(players) #the player after the dealer start | |
492 player = players[pl_idx] | |
493 to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof: | |
494 mess = self.createGameElt(to_jid) | |
495 mess.firstChildElement().addChild(self.__ask_contrat()) | |
92 | 496 self.host.profiles[profile].xmlstream.send(mess) |
90 | 497 |
498 | |
499 def card_game_cmd(self, mess_elt, profile): | |
500 print "\n\nCARD GAME command received (profile=%s): %s" % (profile, mess_elt.toXml()) | |
93 | 501 from_jid = jid.JID(mess_elt['from']) |
502 room_jid = jid.JID(from_jid.userhost()) | |
90 | 503 game_elt = mess_elt.firstChildElement() |
92 | 504 game_data = self.games[room_jid.userhost()] |
505 players_data = game_data['players_data'] | |
506 | |
507 for elt in game_elt.elements(): | |
91 | 508 |
92 | 509 if elt.name == 'started': #new game created |
90 | 510 players = [] |
511 for player in elt.elements(): | |
512 players.append(unicode(player)) | |
93 | 513 self.host.bridge.tarotGameStarted(room_jid.userhost(), from_jid.full(), players, profile) |
91 | 514 |
92 | 515 elif elt.name == 'player_ready': #ready to play |
516 player = elt['player'] | |
90 | 517 status = self.games[room_jid.userhost()]['status'] |
518 nb_players = len(self.games[room_jid.userhost()]['players']) | |
519 status[player] = 'ready' | |
520 debug (_('Player %(player)s is ready to start [status: %(status)s]') % {'player':player, 'status':status}) | |
91 | 521 if status.values().count('ready') == nb_players: #everybody is ready, we can start the game |
92 | 522 self.newGame(room_jid, profile) |
88 | 523 |
90 | 524 elif elt.name == 'hand': #a new hand has been received |
92 | 525 self.host.bridge.tarotGameNew(room_jid.userhost(), self.__xml_to_list(elt), profile) |
91 | 526 |
527 elif elt.name == 'contrat': #it's time to choose contrat | |
528 form = data_form.Form.fromElement(elt.firstChildElement()) | |
529 xml_data = XMLTools.dataForm2xml(form) | |
92 | 530 self.host.bridge.tarotGameChooseContrat(room_jid.userhost(), xml_data, profile) |
91 | 531 |
92 | 532 elif elt.name == 'contrat_choosed': |
91 | 533 #TODO: check we receive the contrat from the right person |
92 | 534 #TODO: use proper XEP-0004 way for answering form |
535 player = elt['player'] | |
536 players_data[player]['contrat'] = unicode(elt) | |
91 | 537 contrats = [players_data[player]['contrat'] for player in game_data['players']] |
538 if contrats.count(None): | |
539 #not everybody has choosed his contrat, it's next one turn | |
540 player = self.__next_player(game_data) | |
541 to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof: | |
542 mess = self.createGameElt(to_jid) | |
543 mess.firstChildElement().addChild(self.__ask_contrat()) | |
92 | 544 self.host.profiles[profile].xmlstream.send(mess) |
91 | 545 else: |
94 | 546 #TODO: gof: manage "everybody pass" case |
91 | 547 best_contrat = [None, "Passe"] |
548 for player in game_data['players']: | |
549 contrat = players_data[player]['contrat'] | |
550 idx_best = self.contrats.index(best_contrat[1]) | |
551 idx_pl = self.contrats.index(contrat) | |
552 if idx_pl > idx_best: | |
553 best_contrat[0] = player | |
554 best_contrat[1] = contrat | |
555 debug (_("%(player)s win the bid with %(contrat)s") % {'player':best_contrat[0],'contrat':best_contrat[1]}) | |
95 | 556 game_data['contrat'] = best_contrat[1] |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
557 |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
558 if game_data['contrat'] == "Garde Sans" or game_data['contrat'] == "Garde Contre": |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
559 self.__start_play(room_jid, game_data, profile) |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
560 game_data['attaquant'] = best_contrat[0] |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
561 else: |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
562 #Time to show the chien to everybody |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
563 to_jid = jid.JID(room_jid.userhost()) #FIXME: gof: |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
564 mess = self.createGameElt(to_jid) |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
565 chien_elt = mess.firstChildElement().addChild(self.__card_list_to_xml(game_data['chien'], 'chien')) |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
566 chien_elt['attaquant'] = best_contrat[0] |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
567 self.host.profiles[profile].xmlstream.send(mess) |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
568 #the attacker (attaquant) get the chien |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
569 game_data['hand'][best_contrat[0]].extend(game_data['chien']) |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
570 del game_data['chien'][:] |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
571 |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
572 if game_data['contrat'] == "Garde Sans": |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
573 #The chien go into attaquant's (attacker) levees |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
574 players_data[best_contrat[0]]['levees'].extend(game_data['chien']) |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
575 del game_data['chien'][:] |
91 | 576 |
92 | 577 |
578 elif elt.name == 'chien': #we have received the chien | |
579 debug (_("tarot: chien received")) | |
580 data = {"attaquant":elt['attaquant']} | |
581 game_data['stage'] = "ecart" | |
582 game_data['attaquant'] = elt['attaquant'] | |
583 self.host.bridge.tarotGameShowCards(room_jid.userhost(), "chien", self.__xml_to_list(elt), data, profile) | |
584 | |
585 elif elt.name == 'cards_played': | |
586 if game_data['stage'] == "ecart": | |
587 #TODO: check validity of écart (no king, no oulder, cards must be in player hand) | |
588 #TODO: show atouts (trumps) if player put some in écart | |
589 assert (game_data['attaquant'] == elt['player']) #TODO: throw an xml error here | |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
590 list_cards = Card.from_tuples(self.__xml_to_list(elt)) |
95 | 591 #FIXME: gof: manage Garde Sans & Garde Contre cases |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
592 players_data[elt['player']]['levees'].extend(list_cards) #we add the chien to attaquant's levées |
95 | 593 for card in list_cards: |
594 game_data['hand'][elt['player']].remove(card) | |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
595 |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
596 self.__start_play(room_jid, game_data, profile) |
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
597 |
93 | 598 elif game_data['stage'] == "play": |
599 current_player = game_data['players'][game_data['current_player']] | |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
600 cards = Card.from_tuples(self.__xml_to_list(elt)) |
94 | 601 |
602 if mess_elt['type'] == 'groupchat': | |
603 self.host.bridge.tarotGameCardsPlayed(room_jid.userhost(), elt['player'], self.__xml_to_list(elt), profile) | |
604 else: | |
605 #TODO: check card validity and send error mess if necessary | |
93 | 606 #the card played is ok, we forward it to everybody |
94 | 607 #first we remove it from the hand and put in on the table |
93 | 608 game_data['hand'][current_player].remove(cards[0]) |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
609 players_data[current_player]['played'] = cards[0] |
93 | 610 |
611 #then we forward the message | |
612 mess = self.createGameElt(room_jid) | |
613 playcard_elt = mess.firstChildElement().addChild(elt) | |
614 self.host.profiles[profile].xmlstream.send(mess) | |
615 | |
94 | 616 #Did everybody played ? |
617 played = [players_data[player]['played'] for player in game_data['players']] | |
95 | 618 if all(played): |
619 #everybody has played | |
94 | 620 winner = self.__winner(game_data) |
621 debug (_('The winner of this trick is %s') % winner) | |
622 #the winner win the trick | |
623 self.__excuse_hack(game_data, played, winner) | |
624 players_data[elt['player']]['levees'].extend(played) | |
625 #nothing left on the table | |
626 for player in game_data['players']: | |
627 players_data[player]['played'] = None | |
628 if len(game_data['hand'][current_player]) == 0: | |
629 #no card lef: the game is finished | |
95 | 630 to_jid = jid.JID(room_jid.userhost()) #FIXME: gof: |
631 mess = self.createGameElt(to_jid) | |
632 chien_elt = mess.firstChildElement().addChild(self.__give_scores(*self.__calculate_scores(game_data))) | |
633 self.host.profiles[profile].xmlstream.send(mess) | |
94 | 634 return |
635 #next player is the winner | |
636 next_player = game_data['first_player'] = self.__next_player(game_data, winner) | |
637 else: | |
638 next_player = self.__next_player(game_data) | |
639 | |
93 | 640 #finally, we tell to the next player to play |
641 to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof: | |
642 mess = self.createGameElt(to_jid) | |
643 yourturn_elt = mess.firstChildElement().addElement('your_turn') | |
644 self.host.profiles[profile].xmlstream.send(mess) | |
645 | |
92 | 646 elif elt.name == 'your_turn': |
647 self.host.bridge.tarotGameYourTurn(room_jid.userhost(), profile) | |
91 | 648 |
95 | 649 elif elt.name == 'score': |
650 form_elt = elt.elements(name='x',uri='jabber:x:data').next() | |
651 winners = [] | |
652 loosers = [] | |
653 for winner in elt.elements(name='winner', uri=''): | |
654 winners.append(unicode(winner)) | |
655 for looser in elt.elements(name='looser', uri=''): | |
656 loosers.append(unicode(looser)) | |
657 form = data_form.Form.fromElement(form_elt) | |
658 xml_data = XMLTools.dataForm2xml(form) | |
659 self.host.bridge.tarotGameScore(room_jid.userhost(), xml_data, winners, loosers, profile) | |
660 | |
90 | 661 def getHandler(self, profile): |
662 return CardGameHandler(self) | |
88 | 663 |
90 | 664 class CardGameHandler (XMPPHandler): |
665 implements(iwokkel.IDisco) | |
666 | |
667 def __init__(self, plugin_parent): | |
668 self.plugin_parent = plugin_parent | |
669 self.host = plugin_parent.host | |
670 | |
671 def connectionInitialized(self): | |
672 self.xmlstream.addObserver(CG_REQUEST, self.plugin_parent.card_game_cmd, profile = self.parent.profile) | |
673 | |
674 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): | |
98
dd556233a1b1
Tarot Plugin: Garde Sans and Garde Contre are now managed
Goffi <goffi@goffi.org>
parents:
96
diff
changeset
|
675 return [disco.DiscoFeature(NS_CG)] |
90 | 676 |
677 def getDiscoItems(self, requestor, target, nodeIdentifier=''): | |
678 return [] | |
679 |