Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_misc_tarot.py @ 4071:4b842c1fb686
refactoring: renamed `sat` package to `libervia.backend`
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 02 Jun 2023 11:49:51 +0200 |
parents | sat/plugins/plugin_misc_tarot.py@524856bd7b19 |
children | 26b7ed2817da |
comparison
equal
deleted
inserted
replaced
4070:d10748475025 | 4071:4b842c1fb686 |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 | |
4 # SAT plugin for managing French Tarot game | |
5 # Copyright (C) 2009-2021 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 libervia.backend.core.i18n import _ | |
21 from libervia.backend.core.constants import Const as C | |
22 from libervia.backend.core.log import getLogger | |
23 | |
24 log = getLogger(__name__) | |
25 from twisted.words.xish import domish | |
26 from twisted.words.protocols.jabber import jid | |
27 from twisted.internet import defer | |
28 from wokkel import data_form | |
29 | |
30 from libervia.backend.memory import memory | |
31 from libervia.backend.tools import xml_tools | |
32 from sat_frontends.tools.games import TarotCard | |
33 import random | |
34 | |
35 | |
36 NS_CG = "http://www.goffi.org/protocol/card_game" | |
37 CG_TAG = "card_game" | |
38 | |
39 PLUGIN_INFO = { | |
40 C.PI_NAME: "Tarot cards plugin", | |
41 C.PI_IMPORT_NAME: "Tarot", | |
42 C.PI_TYPE: "Misc", | |
43 C.PI_PROTOCOLS: [], | |
44 C.PI_DEPENDENCIES: ["XEP-0045", "XEP-0249", "ROOM-GAME"], | |
45 C.PI_MAIN: "Tarot", | |
46 C.PI_HANDLER: "yes", | |
47 C.PI_DESCRIPTION: _("""Implementation of Tarot card game"""), | |
48 } | |
49 | |
50 | |
51 class Tarot(object): | |
52 def inherit_from_room_game(self, host): | |
53 global RoomGame | |
54 RoomGame = host.plugins["ROOM-GAME"].__class__ | |
55 self.__class__ = type( | |
56 self.__class__.__name__, (self.__class__, RoomGame, object), {} | |
57 ) | |
58 | |
59 def __init__(self, host): | |
60 log.info(_("Plugin Tarot initialization")) | |
61 self._sessions = memory.Sessions() | |
62 self.inherit_from_room_game(host) | |
63 RoomGame._init_( | |
64 self, | |
65 host, | |
66 PLUGIN_INFO, | |
67 (NS_CG, CG_TAG), | |
68 game_init={ | |
69 "hand_size": 18, | |
70 "init_player": 0, | |
71 "current_player": None, | |
72 "contrat": None, | |
73 "stage": None, | |
74 }, | |
75 player_init={"score": 0}, | |
76 ) | |
77 self.contrats = [ | |
78 _("Passe"), | |
79 _("Petite"), | |
80 _("Garde"), | |
81 _("Garde Sans"), | |
82 _("Garde Contre"), | |
83 ] | |
84 host.bridge.add_method( | |
85 "tarot_game_launch", | |
86 ".plugin", | |
87 in_sign="asss", | |
88 out_sign="", | |
89 method=self._prepare_room, | |
90 async_=True, | |
91 ) # args: players, room_jid, profile | |
92 host.bridge.add_method( | |
93 "tarot_game_create", | |
94 ".plugin", | |
95 in_sign="sass", | |
96 out_sign="", | |
97 method=self._create_game, | |
98 ) # args: room_jid, players, profile | |
99 host.bridge.add_method( | |
100 "tarot_game_ready", | |
101 ".plugin", | |
102 in_sign="sss", | |
103 out_sign="", | |
104 method=self._player_ready, | |
105 ) # args: player, referee, profile | |
106 host.bridge.add_method( | |
107 "tarot_game_play_cards", | |
108 ".plugin", | |
109 in_sign="ssa(ss)s", | |
110 out_sign="", | |
111 method=self.play_cards, | |
112 ) # args: player, referee, cards, profile | |
113 host.bridge.add_signal( | |
114 "tarot_game_players", ".plugin", signature="ssass" | |
115 ) # args: room_jid, referee, players, profile | |
116 host.bridge.add_signal( | |
117 "tarot_game_started", ".plugin", signature="ssass" | |
118 ) # args: room_jid, referee, players, profile | |
119 host.bridge.add_signal( | |
120 "tarot_game_new", ".plugin", signature="sa(ss)s" | |
121 ) # args: room_jid, hand, profile | |
122 host.bridge.add_signal( | |
123 "tarot_game_choose_contrat", ".plugin", signature="sss" | |
124 ) # args: room_jid, xml_data, profile | |
125 host.bridge.add_signal( | |
126 "tarot_game_show_cards", ".plugin", signature="ssa(ss)a{ss}s" | |
127 ) # args: room_jid, type ["chien", "poignée",...], cards, data[dict], profile | |
128 host.bridge.add_signal( | |
129 "tarot_game_cards_played", ".plugin", signature="ssa(ss)s" | |
130 ) # args: room_jid, player, type ["chien", "poignée",...], cards, data[dict], profile | |
131 host.bridge.add_signal( | |
132 "tarot_game_your_turn", ".plugin", signature="ss" | |
133 ) # args: room_jid, profile | |
134 host.bridge.add_signal( | |
135 "tarot_game_score", ".plugin", signature="ssasass" | |
136 ) # args: room_jid, xml_data, winners (list of nicks), loosers (list of nicks), profile | |
137 host.bridge.add_signal( | |
138 "tarot_game_invalid_cards", ".plugin", signature="ssa(ss)a(ss)s" | |
139 ) # args: room_jid, game phase, played_cards, invalid_cards, profile | |
140 self.deck_ordered = [] | |
141 for value in ["excuse"] + list(map(str, list(range(1, 22)))): | |
142 self.deck_ordered.append(TarotCard(("atout", value))) | |
143 for suit in ["pique", "coeur", "carreau", "trefle"]: | |
144 for value in list(map(str, list(range(1, 11)))) + ["valet", "cavalier", "dame", "roi"]: | |
145 self.deck_ordered.append(TarotCard((suit, value))) | |
146 self.__choose_contrat_id = host.register_callback( | |
147 self._contrat_choosed, with_data=True | |
148 ) | |
149 self.__score_id = host.register_callback(self._score_showed, with_data=True) | |
150 | |
151 def __card_list_to_xml(self, cards_list, elt_name): | |
152 """Convert a card list to domish element""" | |
153 cards_list_elt = domish.Element((None, elt_name)) | |
154 for card in cards_list: | |
155 card_elt = domish.Element((None, "card")) | |
156 card_elt["suit"] = card.suit | |
157 card_elt["value"] = card.value | |
158 cards_list_elt.addChild(card_elt) | |
159 return cards_list_elt | |
160 | |
161 def __xml_to_list(self, cards_list_elt): | |
162 """Convert a domish element with cards to a list of tuples""" | |
163 cards_list = [] | |
164 for card in cards_list_elt.elements(): | |
165 cards_list.append((card["suit"], card["value"])) | |
166 return cards_list | |
167 | |
168 def __ask_contrat(self): | |
169 """Create a element for asking contrat""" | |
170 contrat_elt = domish.Element((None, "contrat")) | |
171 form = data_form.Form("form", title=_("contrat selection")) | |
172 field = data_form.Field( | |
173 "list-single", | |
174 "contrat", | |
175 options=list(map(data_form.Option, self.contrats)), | |
176 required=True, | |
177 ) | |
178 form.addField(field) | |
179 contrat_elt.addChild(form.toElement()) | |
180 return contrat_elt | |
181 | |
182 def __give_scores(self, scores, winners, loosers): | |
183 """Create an element to give scores | |
184 @param scores: unicode (can contain line feed) | |
185 @param winners: list of unicode nicks of winners | |
186 @param loosers: list of unicode nicks of loosers""" | |
187 | |
188 score_elt = domish.Element((None, "score")) | |
189 form = data_form.Form("form", title=_("scores")) | |
190 for line in scores.split("\n"): | |
191 field = data_form.Field("fixed", value=line) | |
192 form.addField(field) | |
193 score_elt.addChild(form.toElement()) | |
194 for winner in winners: | |
195 winner_elt = domish.Element((None, "winner")) | |
196 winner_elt.addContent(winner) | |
197 score_elt.addChild(winner_elt) | |
198 for looser in loosers: | |
199 looser_elt = domish.Element((None, "looser")) | |
200 looser_elt.addContent(looser) | |
201 score_elt.addChild(looser_elt) | |
202 return score_elt | |
203 | |
204 def __invalid_cards_elt(self, played_cards, invalid_cards, game_phase): | |
205 """Create a element for invalid_cards error | |
206 @param list_cards: list of Card | |
207 @param game_phase: phase of the game ['ecart', 'play']""" | |
208 error_elt = domish.Element((None, "error")) | |
209 played_elt = self.__card_list_to_xml(played_cards, "played") | |
210 invalid_elt = self.__card_list_to_xml(invalid_cards, "invalid") | |
211 error_elt["type"] = "invalid_cards" | |
212 error_elt["phase"] = game_phase | |
213 error_elt.addChild(played_elt) | |
214 error_elt.addChild(invalid_elt) | |
215 return error_elt | |
216 | |
217 def __next_player(self, game_data, next_pl=None): | |
218 """Increment player number & return player name | |
219 @param next_pl: if given, then next_player is forced to this one | |
220 """ | |
221 if next_pl: | |
222 game_data["current_player"] = game_data["players"].index(next_pl) | |
223 return next_pl | |
224 else: | |
225 pl_idx = game_data["current_player"] = ( | |
226 game_data["current_player"] + 1 | |
227 ) % len(game_data["players"]) | |
228 return game_data["players"][pl_idx] | |
229 | |
230 def __winner(self, game_data): | |
231 """give the nick of the player who win this trick""" | |
232 players_data = game_data["players_data"] | |
233 first = game_data["first_player"] | |
234 first_idx = game_data["players"].index(first) | |
235 suit_asked = None | |
236 strongest = None | |
237 winner = None | |
238 for idx in [(first_idx + i) % 4 for i in range(4)]: | |
239 player = game_data["players"][idx] | |
240 card = players_data[player]["played"] | |
241 if card.value == "excuse": | |
242 continue | |
243 if suit_asked is None: | |
244 suit_asked = card.suit | |
245 if (card.suit == suit_asked or card.suit == "atout") and card > strongest: | |
246 strongest = card | |
247 winner = player | |
248 assert winner | |
249 return winner | |
250 | |
251 def __excuse_hack(self, game_data, played, winner): | |
252 """give a low card to other team and keep excuse if trick is lost | |
253 @param game_data: data of the game | |
254 @param played: cards currently on the table | |
255 @param winner: nick of the trick winner""" | |
256 # TODO: manage the case where excuse is played on the last trick (and lost) | |
257 players_data = game_data["players_data"] | |
258 excuse = TarotCard(("atout", "excuse")) | |
259 | |
260 # we first check if the Excuse was already played | |
261 # and if somebody is waiting for a card | |
262 for player in game_data["players"]: | |
263 if players_data[player]["wait_for_low"]: | |
264 # the excuse owner has to give a card to somebody | |
265 if winner == player: | |
266 # the excuse owner win the trick, we check if we have something to give | |
267 for card in played: | |
268 if card.points == 0.5: | |
269 pl_waiting = players_data[player]["wait_for_low"] | |
270 played.remove(card) | |
271 players_data[pl_waiting]["levees"].append(card) | |
272 log.debug( | |
273 _( | |
274 "Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation" | |
275 ) | |
276 % { | |
277 "excuse_owner": player, | |
278 "card_waited": card, | |
279 "player_waiting": pl_waiting, | |
280 } | |
281 ) | |
282 return | |
283 return | |
284 | |
285 if excuse not in played: | |
286 # the Excuse is not on the table, nothing to do | |
287 return | |
288 | |
289 excuse_player = None # Who has played the Excuse ? | |
290 for player in game_data["players"]: | |
291 if players_data[player]["played"] == excuse: | |
292 excuse_player = player | |
293 break | |
294 | |
295 if excuse_player == winner: | |
296 return # the excuse player win the trick, nothing to do | |
297 | |
298 # first we remove the excuse from played cards | |
299 played.remove(excuse) | |
300 # then we give it back to the original owner | |
301 owner_levees = players_data[excuse_player]["levees"] | |
302 owner_levees.append(excuse) | |
303 # finally we give a low card to the trick winner | |
304 low_card = None | |
305 # We look backward in cards won by the Excuse owner to | |
306 # find a low value card | |
307 for card_idx in range(len(owner_levees) - 1, -1, -1): | |
308 if owner_levees[card_idx].points == 0.5: | |
309 low_card = owner_levees[card_idx] | |
310 del owner_levees[card_idx] | |
311 players_data[winner]["levees"].append(low_card) | |
312 log.debug( | |
313 _( | |
314 "Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation" | |
315 ) | |
316 % { | |
317 "excuse_owner": excuse_player, | |
318 "card_waited": low_card, | |
319 "player_waiting": winner, | |
320 } | |
321 ) | |
322 break | |
323 if not low_card: # The player has no low card yet | |
324 # TODO: manage case when player never win a trick with low card | |
325 players_data[excuse_player]["wait_for_low"] = winner | |
326 log.debug( | |
327 _( | |
328 "%(excuse_owner)s keep the Excuse but has not card to give, %(winner)s is waiting for one" | |
329 ) | |
330 % {"excuse_owner": excuse_player, "winner": winner} | |
331 ) | |
332 | |
333 def __draw_game(self, game_data): | |
334 """The game is draw, no score change | |
335 @param game_data: data of the game | |
336 @return: tuple with (string victory message, list of winners, list of loosers)""" | |
337 players_data = game_data["players_data"] | |
338 scores_str = _("Draw game") | |
339 scores_str += "\n" | |
340 for player in game_data["players"]: | |
341 scores_str += _( | |
342 "\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i" | |
343 ) % { | |
344 "player": player, | |
345 "score_game": 0, | |
346 "total_score": players_data[player]["score"], | |
347 } | |
348 log.debug(scores_str) | |
349 | |
350 return (scores_str, [], []) | |
351 | |
352 def __calculate_scores(self, game_data): | |
353 """The game is finished, time to know who won :) | |
354 @param game_data: data of the game | |
355 @return: tuple with (string victory message, list of winners, list of loosers)""" | |
356 players_data = game_data["players_data"] | |
357 levees = players_data[game_data["attaquant"]]["levees"] | |
358 score = 0 | |
359 nb_bouts = 0 | |
360 bouts = [] | |
361 for card in levees: | |
362 if card.bout: | |
363 nb_bouts += 1 | |
364 bouts.append(card.value) | |
365 score += card.points | |
366 | |
367 # We do a basic check on score calculation | |
368 check_score = 0 | |
369 defenseurs = game_data["players"][:] | |
370 defenseurs.remove(game_data["attaquant"]) | |
371 for defenseur in defenseurs: | |
372 for card in players_data[defenseur]["levees"]: | |
373 check_score += card.points | |
374 if game_data["contrat"] == "Garde Contre": | |
375 for card in game_data["chien"]: | |
376 check_score += card.points | |
377 assert score + check_score == 91 | |
378 | |
379 point_limit = None | |
380 if nb_bouts == 3: | |
381 point_limit = 36 | |
382 elif nb_bouts == 2: | |
383 point_limit = 41 | |
384 elif nb_bouts == 1: | |
385 point_limit = 51 | |
386 else: | |
387 point_limit = 56 | |
388 if game_data["contrat"] == "Petite": | |
389 contrat_mult = 1 | |
390 elif game_data["contrat"] == "Garde": | |
391 contrat_mult = 2 | |
392 elif game_data["contrat"] == "Garde Sans": | |
393 contrat_mult = 4 | |
394 elif game_data["contrat"] == "Garde Contre": | |
395 contrat_mult = 6 | |
396 else: | |
397 log.error(_("INTERNAL ERROR: contrat not managed (mispelled ?)")) | |
398 assert False | |
399 | |
400 victory = score >= point_limit | |
401 margin = abs(score - point_limit) | |
402 points_defenseur = (margin + 25) * contrat_mult * (-1 if victory else 1) | |
403 winners = [] | |
404 loosers = [] | |
405 player_score = {} | |
406 for player in game_data["players"]: | |
407 # TODO: adjust this for 3 and 5 players variants | |
408 # TODO: manage bonuses (petit au bout, poignée, chelem) | |
409 player_score[player] = ( | |
410 points_defenseur | |
411 if player != game_data["attaquant"] | |
412 else points_defenseur * -3 | |
413 ) | |
414 players_data[player]["score"] += player_score[ | |
415 player | |
416 ] # we add score of this game to the global score | |
417 if player_score[player] > 0: | |
418 winners.append(player) | |
419 else: | |
420 loosers.append(player) | |
421 | |
422 scores_str = _( | |
423 "The attacker (%(attaquant)s) makes %(points)i and needs to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s%(separator)s%(bouts)s): (s)he %(victory)s" | |
424 ) % { | |
425 "attaquant": game_data["attaquant"], | |
426 "points": score, | |
427 "point_limit": point_limit, | |
428 "nb_bouts": nb_bouts, | |
429 "plural": "s" if nb_bouts > 1 else "", | |
430 "separator": ": " if nb_bouts != 0 else "", | |
431 "bouts": ",".join(map(str, bouts)), | |
432 "victory": "wins" if victory else "looses", | |
433 } | |
434 scores_str += "\n" | |
435 for player in game_data["players"]: | |
436 scores_str += _( | |
437 "\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i" | |
438 ) % { | |
439 "player": player, | |
440 "score_game": player_score[player], | |
441 "total_score": players_data[player]["score"], | |
442 } | |
443 log.debug(scores_str) | |
444 | |
445 return (scores_str, winners, loosers) | |
446 | |
447 def __invalid_cards(self, game_data, cards): | |
448 """Checks that the player has the right to play what he wants to | |
449 @param game_data: Game data | |
450 @param cards: cards the player want to play | |
451 @return forbidden_cards cards or empty list if cards are ok""" | |
452 forbidden_cards = [] | |
453 if game_data["stage"] == "ecart": | |
454 for card in cards: | |
455 if card.bout or card.value == "roi": | |
456 forbidden_cards.append(card) | |
457 # TODO: manage case where atouts (trumps) are in the dog | |
458 elif game_data["stage"] == "play": | |
459 biggest_atout = None | |
460 suit_asked = None | |
461 players = game_data["players"] | |
462 players_data = game_data["players_data"] | |
463 idx = players.index(game_data["first_player"]) | |
464 current_idx = game_data["current_player"] | |
465 current_player = players[current_idx] | |
466 if idx == current_idx: | |
467 # the player is the first to play, he can play what he wants | |
468 return forbidden_cards | |
469 while idx != current_idx: | |
470 player = players[idx] | |
471 played_card = players_data[player]["played"] | |
472 if not suit_asked and played_card.value != "excuse": | |
473 suit_asked = played_card.suit | |
474 if played_card.suit == "atout" and played_card > biggest_atout: | |
475 biggest_atout = played_card | |
476 idx = (idx + 1) % len(players) | |
477 has_suit = ( | |
478 False | |
479 ) # True if there is one card of the asked suit in the hand of the player | |
480 has_atout = False | |
481 biggest_hand_atout = None | |
482 | |
483 for hand_card in game_data["hand"][current_player]: | |
484 if hand_card.suit == suit_asked: | |
485 has_suit = True | |
486 if hand_card.suit == "atout": | |
487 has_atout = True | |
488 if hand_card.suit == "atout" and hand_card > biggest_hand_atout: | |
489 biggest_hand_atout = hand_card | |
490 | |
491 assert len(cards) == 1 | |
492 card = cards[0] | |
493 if card.suit != suit_asked and has_suit and card.value != "excuse": | |
494 forbidden_cards.append(card) | |
495 return forbidden_cards | |
496 if card.suit != suit_asked and card.suit != "atout" and has_atout: | |
497 forbidden_cards.append(card) | |
498 return forbidden_cards | |
499 if ( | |
500 card.suit == "atout" | |
501 and card < biggest_atout | |
502 and biggest_hand_atout > biggest_atout | |
503 and card.value != "excuse" | |
504 ): | |
505 forbidden_cards.append(card) | |
506 else: | |
507 log.error(_("Internal error: unmanaged game stage")) | |
508 return forbidden_cards | |
509 | |
510 def __start_play(self, room_jid, game_data, profile): | |
511 """Start the game (tell to the first player after dealer to play""" | |
512 game_data["stage"] = "play" | |
513 next_player_idx = game_data["current_player"] = ( | |
514 game_data["init_player"] + 1 | |
515 ) % len( | |
516 game_data["players"] | |
517 ) # the player after the dealer start | |
518 game_data["first_player"] = next_player = game_data["players"][next_player_idx] | |
519 to_jid = jid.JID(room_jid.userhost() + "/" + next_player) # FIXME: gof: | |
520 self.send(to_jid, "your_turn", profile=profile) | |
521 | |
522 def _contrat_choosed(self, raw_data, profile): | |
523 """Will be called when the contrat is selected | |
524 @param raw_data: contains the choosed session id and the chosen contrat | |
525 @param profile_key: profile | |
526 """ | |
527 try: | |
528 session_data = self._sessions.profile_get(raw_data["session_id"], profile) | |
529 except KeyError: | |
530 log.warning(_("session id doesn't exist, session has probably expired")) | |
531 # TODO: send error dialog | |
532 return defer.succeed({}) | |
533 | |
534 room_jid = session_data["room_jid"] | |
535 referee_jid = self.games[room_jid]["referee"] | |
536 player = self.host.plugins["XEP-0045"].get_room_nick(room_jid, profile) | |
537 data = xml_tools.xmlui_result_2_data_form_result(raw_data) | |
538 contrat = data["contrat"] | |
539 log.debug( | |
540 _("contrat [%(contrat)s] choosed by %(profile)s") | |
541 % {"contrat": contrat, "profile": profile} | |
542 ) | |
543 d = self.send( | |
544 referee_jid, | |
545 ("", "contrat_choosed"), | |
546 {"player": player}, | |
547 content=contrat, | |
548 profile=profile, | |
549 ) | |
550 d.addCallback(lambda ignore: {}) | |
551 del self._sessions[raw_data["session_id"]] | |
552 return d | |
553 | |
554 def _score_showed(self, raw_data, profile): | |
555 """Will be called when the player closes the score dialog | |
556 @param raw_data: nothing to retrieve from here but the session id | |
557 @param profile_key: profile | |
558 """ | |
559 try: | |
560 session_data = self._sessions.profile_get(raw_data["session_id"], profile) | |
561 except KeyError: | |
562 log.warning(_("session id doesn't exist, session has probably expired")) | |
563 # TODO: send error dialog | |
564 return defer.succeed({}) | |
565 | |
566 room_jid_s = session_data["room_jid"].userhost() | |
567 # XXX: empty hand means to the frontend "reset the display"... | |
568 self.host.bridge.tarot_game_new(room_jid_s, [], profile) | |
569 del self._sessions[raw_data["session_id"]] | |
570 return defer.succeed({}) | |
571 | |
572 def play_cards(self, player, referee, cards, profile_key=C.PROF_KEY_NONE): | |
573 """Must be call by player when the contrat is selected | |
574 @param player: player's name | |
575 @param referee: arbiter jid | |
576 @cards: cards played (list of tuples) | |
577 @profile_key: profile | |
578 """ | |
579 profile = self.host.memory.get_profile_name(profile_key) | |
580 if not profile: | |
581 log.error(_("profile %s is unknown") % profile_key) | |
582 return | |
583 log.debug( | |
584 _("Cards played by %(profile)s: [%(cards)s]") | |
585 % {"profile": profile, "cards": cards} | |
586 ) | |
587 elem = self.__card_list_to_xml(TarotCard.from_tuples(cards), "cards_played") | |
588 self.send(jid.JID(referee), elem, {"player": player}, profile=profile) | |
589 | |
590 def new_round(self, room_jid, profile): | |
591 game_data = self.games[room_jid] | |
592 players = game_data["players"] | |
593 game_data["first_player"] = None # first player for the current trick | |
594 game_data["contrat"] = None | |
595 common_data = { | |
596 "contrat": None, | |
597 "levees": [], # cards won | |
598 "played": None, # card on the table | |
599 "wait_for_low": None, # Used when a player wait for a low card because of excuse | |
600 } | |
601 | |
602 hand = game_data["hand"] = {} | |
603 hand_size = game_data["hand_size"] | |
604 chien = game_data["chien"] = [] | |
605 deck = self.deck_ordered[:] | |
606 random.shuffle(deck) | |
607 for i in range(4): | |
608 hand[players[i]] = deck[0:hand_size] | |
609 del deck[0:hand_size] | |
610 chien.extend(deck) | |
611 del (deck[:]) | |
612 msg_elts = {} | |
613 for player in players: | |
614 msg_elts[player] = self.__card_list_to_xml(hand[player], "hand") | |
615 | |
616 RoomGame.new_round(self, room_jid, (common_data, msg_elts), profile) | |
617 | |
618 pl_idx = game_data["current_player"] = (game_data["init_player"] + 1) % len( | |
619 players | |
620 ) # the player after the dealer start | |
621 player = players[pl_idx] | |
622 to_jid = jid.JID(room_jid.userhost() + "/" + player) # FIXME: gof: | |
623 self.send(to_jid, self.__ask_contrat(), profile=profile) | |
624 | |
625 def room_game_cmd(self, mess_elt, profile): | |
626 """ | |
627 @param mess_elt: instance of twisted.words.xish.domish.Element | |
628 """ | |
629 client = self.host.get_client(profile) | |
630 from_jid = jid.JID(mess_elt["from"]) | |
631 room_jid = jid.JID(from_jid.userhost()) | |
632 nick = self.host.plugins["XEP-0045"].get_room_nick(client, room_jid) | |
633 | |
634 game_elt = mess_elt.firstChildElement() | |
635 game_data = self.games[room_jid] | |
636 is_player = self.is_player(room_jid, nick) | |
637 if "players_data" in game_data: | |
638 players_data = game_data["players_data"] | |
639 | |
640 for elt in game_elt.elements(): | |
641 if not is_player and (elt.name not in ("started", "players")): | |
642 continue # user is in the room but not playing | |
643 | |
644 if elt.name in ( | |
645 "started", | |
646 "players", | |
647 ): # new game created and/or players list updated | |
648 players = [] | |
649 for player in elt.elements(): | |
650 players.append(str(player)) | |
651 signal = ( | |
652 self.host.bridge.tarot_game_started | |
653 if elt.name == "started" | |
654 else self.host.bridge.tarot_game_players | |
655 ) | |
656 signal(room_jid.userhost(), from_jid.full(), players, profile) | |
657 | |
658 elif elt.name == "player_ready": # ready to play | |
659 player = elt["player"] | |
660 status = self.games[room_jid]["status"] | |
661 nb_players = len(self.games[room_jid]["players"]) | |
662 status[player] = "ready" | |
663 log.debug( | |
664 _("Player %(player)s is ready to start [status: %(status)s]") | |
665 % {"player": player, "status": status} | |
666 ) | |
667 if ( | |
668 list(status.values()).count("ready") == nb_players | |
669 ): # everybody is ready, we can start the game | |
670 self.new_round(room_jid, profile) | |
671 | |
672 elif elt.name == "hand": # a new hand has been received | |
673 self.host.bridge.tarot_game_new( | |
674 room_jid.userhost(), self.__xml_to_list(elt), profile | |
675 ) | |
676 | |
677 elif elt.name == "contrat": # it's time to choose contrat | |
678 form = data_form.Form.fromElement(elt.firstChildElement()) | |
679 session_id, session_data = self._sessions.new_session(profile=profile) | |
680 session_data["room_jid"] = room_jid | |
681 xml_data = xml_tools.data_form_2_xmlui( | |
682 form, self.__choose_contrat_id, session_id | |
683 ).toXml() | |
684 self.host.bridge.tarot_game_choose_contrat( | |
685 room_jid.userhost(), xml_data, profile | |
686 ) | |
687 | |
688 elif elt.name == "contrat_choosed": | |
689 # TODO: check we receive the contrat from the right person | |
690 # TODO: use proper XEP-0004 way for answering form | |
691 player = elt["player"] | |
692 players_data[player]["contrat"] = str(elt) | |
693 contrats = [players_data[p]["contrat"] for p in game_data["players"]] | |
694 if contrats.count(None): | |
695 # not everybody has choosed his contrat, it's next one turn | |
696 player = self.__next_player(game_data) | |
697 to_jid = jid.JID(room_jid.userhost() + "/" + player) # FIXME: gof: | |
698 self.send(to_jid, self.__ask_contrat(), profile=profile) | |
699 else: | |
700 best_contrat = [None, "Passe"] | |
701 for player in game_data["players"]: | |
702 contrat = players_data[player]["contrat"] | |
703 idx_best = self.contrats.index(best_contrat[1]) | |
704 idx_pl = self.contrats.index(contrat) | |
705 if idx_pl > idx_best: | |
706 best_contrat[0] = player | |
707 best_contrat[1] = contrat | |
708 if best_contrat[1] == "Passe": | |
709 log.debug(_("Everybody is passing, round ended")) | |
710 to_jid = jid.JID(room_jid.userhost()) | |
711 self.send( | |
712 to_jid, | |
713 self.__give_scores(*self.__draw_game(game_data)), | |
714 profile=profile, | |
715 ) | |
716 game_data["init_player"] = (game_data["init_player"] + 1) % len( | |
717 game_data["players"] | |
718 ) # we change the dealer | |
719 for player in game_data["players"]: | |
720 game_data["status"][player] = "init" | |
721 return | |
722 log.debug( | |
723 _("%(player)s win the bid with %(contrat)s") | |
724 % {"player": best_contrat[0], "contrat": best_contrat[1]} | |
725 ) | |
726 game_data["contrat"] = best_contrat[1] | |
727 | |
728 if ( | |
729 game_data["contrat"] == "Garde Sans" | |
730 or game_data["contrat"] == "Garde Contre" | |
731 ): | |
732 self.__start_play(room_jid, game_data, profile) | |
733 game_data["attaquant"] = best_contrat[0] | |
734 else: | |
735 # Time to show the chien to everybody | |
736 to_jid = jid.JID(room_jid.userhost()) # FIXME: gof: | |
737 elem = self.__card_list_to_xml(game_data["chien"], "chien") | |
738 self.send( | |
739 to_jid, elem, {"attaquant": best_contrat[0]}, profile=profile | |
740 ) | |
741 # the attacker (attaquant) get the chien | |
742 game_data["hand"][best_contrat[0]].extend(game_data["chien"]) | |
743 del game_data["chien"][:] | |
744 | |
745 if game_data["contrat"] == "Garde Sans": | |
746 # The chien go into attaquant's (attacker) levees | |
747 players_data[best_contrat[0]]["levees"].extend(game_data["chien"]) | |
748 del game_data["chien"][:] | |
749 | |
750 elif elt.name == "chien": # we have received the chien | |
751 log.debug(_("tarot: chien received")) | |
752 data = {"attaquant": elt["attaquant"]} | |
753 game_data["stage"] = "ecart" | |
754 game_data["attaquant"] = elt["attaquant"] | |
755 self.host.bridge.tarot_game_show_cards( | |
756 room_jid.userhost(), "chien", self.__xml_to_list(elt), data, profile | |
757 ) | |
758 | |
759 elif elt.name == "cards_played": | |
760 if game_data["stage"] == "ecart": | |
761 # TODO: show atouts (trumps) if player put some in écart | |
762 assert ( | |
763 game_data["attaquant"] == elt["player"] | |
764 ) # TODO: throw an xml error here | |
765 list_cards = TarotCard.from_tuples(self.__xml_to_list(elt)) | |
766 # we now check validity of card | |
767 invalid_cards = self.__invalid_cards(game_data, list_cards) | |
768 if invalid_cards: | |
769 elem = self.__invalid_cards_elt( | |
770 list_cards, invalid_cards, game_data["stage"] | |
771 ) | |
772 self.send( | |
773 jid.JID(room_jid.userhost() + "/" + elt["player"]), | |
774 elem, | |
775 profile=profile, | |
776 ) | |
777 return | |
778 | |
779 # FIXME: gof: manage Garde Sans & Garde Contre cases | |
780 players_data[elt["player"]]["levees"].extend( | |
781 list_cards | |
782 ) # we add the chien to attaquant's levées | |
783 for card in list_cards: | |
784 game_data["hand"][elt["player"]].remove(card) | |
785 | |
786 self.__start_play(room_jid, game_data, profile) | |
787 | |
788 elif game_data["stage"] == "play": | |
789 current_player = game_data["players"][game_data["current_player"]] | |
790 cards = TarotCard.from_tuples(self.__xml_to_list(elt)) | |
791 | |
792 if mess_elt["type"] == "groupchat": | |
793 self.host.bridge.tarot_game_cards_played( | |
794 room_jid.userhost(), | |
795 elt["player"], | |
796 self.__xml_to_list(elt), | |
797 profile, | |
798 ) | |
799 else: | |
800 # we first check validity of card | |
801 invalid_cards = self.__invalid_cards(game_data, cards) | |
802 if invalid_cards: | |
803 elem = self.__invalid_cards_elt( | |
804 cards, invalid_cards, game_data["stage"] | |
805 ) | |
806 self.send( | |
807 jid.JID(room_jid.userhost() + "/" + current_player), | |
808 elem, | |
809 profile=profile, | |
810 ) | |
811 return | |
812 # the card played is ok, we forward it to everybody | |
813 # first we remove it from the hand and put in on the table | |
814 game_data["hand"][current_player].remove(cards[0]) | |
815 players_data[current_player]["played"] = cards[0] | |
816 | |
817 # then we forward the message | |
818 self.send(room_jid, elt, profile=profile) | |
819 | |
820 # Did everybody played ? | |
821 played = [ | |
822 players_data[player]["played"] | |
823 for player in game_data["players"] | |
824 ] | |
825 if all(played): | |
826 # everybody has played | |
827 winner = self.__winner(game_data) | |
828 log.debug(_("The winner of this trick is %s") % winner) | |
829 # the winner win the trick | |
830 self.__excuse_hack(game_data, played, winner) | |
831 players_data[elt["player"]]["levees"].extend(played) | |
832 # nothing left on the table | |
833 for player in game_data["players"]: | |
834 players_data[player]["played"] = None | |
835 if len(game_data["hand"][current_player]) == 0: | |
836 # no card left: the game is finished | |
837 elem = self.__give_scores( | |
838 *self.__calculate_scores(game_data) | |
839 ) | |
840 self.send(room_jid, elem, profile=profile) | |
841 game_data["init_player"] = ( | |
842 game_data["init_player"] + 1 | |
843 ) % len( | |
844 game_data["players"] | |
845 ) # we change the dealer | |
846 for player in game_data["players"]: | |
847 game_data["status"][player] = "init" | |
848 return | |
849 # next player is the winner | |
850 next_player = game_data["first_player"] = self.__next_player( | |
851 game_data, winner | |
852 ) | |
853 else: | |
854 next_player = self.__next_player(game_data) | |
855 | |
856 # finally, we tell to the next player to play | |
857 to_jid = jid.JID(room_jid.userhost() + "/" + next_player) | |
858 self.send(to_jid, "your_turn", profile=profile) | |
859 | |
860 elif elt.name == "your_turn": | |
861 self.host.bridge.tarot_game_your_turn(room_jid.userhost(), profile) | |
862 | |
863 elif elt.name == "score": | |
864 form_elt = next(elt.elements(name="x", uri="jabber:x:data")) | |
865 winners = [] | |
866 loosers = [] | |
867 for winner in elt.elements(name="winner", uri=NS_CG): | |
868 winners.append(str(winner)) | |
869 for looser in elt.elements(name="looser", uri=NS_CG): | |
870 loosers.append(str(looser)) | |
871 form = data_form.Form.fromElement(form_elt) | |
872 session_id, session_data = self._sessions.new_session(profile=profile) | |
873 session_data["room_jid"] = room_jid | |
874 xml_data = xml_tools.data_form_2_xmlui( | |
875 form, self.__score_id, session_id | |
876 ).toXml() | |
877 self.host.bridge.tarot_game_score( | |
878 room_jid.userhost(), xml_data, winners, loosers, profile | |
879 ) | |
880 elif elt.name == "error": | |
881 if elt["type"] == "invalid_cards": | |
882 played_cards = self.__xml_to_list( | |
883 next(elt.elements(name="played", uri=NS_CG)) | |
884 ) | |
885 invalid_cards = self.__xml_to_list( | |
886 next(elt.elements(name="invalid", uri=NS_CG)) | |
887 ) | |
888 self.host.bridge.tarot_game_invalid_cards( | |
889 room_jid.userhost(), | |
890 elt["phase"], | |
891 played_cards, | |
892 invalid_cards, | |
893 profile, | |
894 ) | |
895 else: | |
896 log.error(_("Unmanaged error type: %s") % elt["type"]) | |
897 else: | |
898 log.error(_("Unmanaged card game element: %s") % elt.name) | |
899 | |
900 def get_sync_data_for_player(self, room_jid, nick): | |
901 return [] |