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 []