Mercurial > libervia-backend
diff libervia/tui/game_tarot.py @ 4076:b620a8e882e1
refactoring: rename `libervia.frontends.primitivus` to `libervia.tui`
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 02 Jun 2023 16:25:25 +0200 |
parents | libervia/frontends/primitivus/game_tarot.py@26b7ed2817da |
children | 0d7bb4df2343 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libervia/tui/game_tarot.py Fri Jun 02 16:25:25 2023 +0200 @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 + + +# Libervia TUI +# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from libervia.backend.core.i18n import _ +import urwid +from urwid_satext import sat_widgets +from libervia.frontends.tools.games import TarotCard +from libervia.frontends.quick_frontend.quick_game_tarot import QuickTarotGame +from libervia.tui import xmlui +from libervia.tui.keys import action_key_map as a_key + + +class CardDisplayer(urwid.Text): + """Show a card""" + + signals = ["click"] + + def __init__(self, card): + self.__selected = False + self.card = card + urwid.Text.__init__(self, card.get_attr_text()) + + def selectable(self): + return True + + def keypress(self, size, key): + if key == a_key["CARD_SELECT"]: + self.select(not self.__selected) + self._emit("click") + return key + + def mouse_event(self, size, event, button, x, y, focus): + if urwid.is_mouse_event(event) and button == 1: + self.select(not self.__selected) + self._emit("click") + return True + + return False + + def select(self, state=True): + self.__selected = state + attr, txt = self.card.get_attr_text() + if self.__selected: + attr += "_selected" + self.set_text((attr, txt)) + self._invalidate() + + def is_selected(self): + return self.__selected + + def get_card(self): + return self.card + + def render(self, size, focus=False): + canvas = urwid.CompositeCanvas(urwid.Text.render(self, size, focus)) + if focus: + canvas.set_cursor((0, 0)) + return canvas + + +class Hand(urwid.WidgetWrap): + """Used to display several cards, and manage a hand""" + + signals = ["click"] + + def __init__(self, hand=[], selectable=False, on_click=None, user_data=None): + """@param hand: list of Card""" + self.__selectable = selectable + self.columns = urwid.Columns([], dividechars=1) + if on_click: + urwid.connect_signal(self, "click", on_click, user_data) + if hand: + self.update(hand) + urwid.WidgetWrap.__init__(self, self.columns) + + def selectable(self): + return self.__selectable + + def keypress(self, size, key): + + if CardDisplayer in [wid.__class__ for wid in self.columns.widget_list]: + return self.columns.keypress(size, key) + else: + # No card displayed, we still have to manage the clicks + if key == a_key["CARD_SELECT"]: + self._emit("click", None) + return key + + def get_selected(self): + """Return a list of selected cards""" + _selected = [] + for wid in self.columns.widget_list: + if isinstance(wid, CardDisplayer) and wid.is_selected(): + _selected.append(wid.get_card()) + return _selected + + def update(self, hand): + """Update the hand displayed in this widget + @param hand: list of Card""" + try: + del self.columns.widget_list[:] + del self.columns.column_types[:] + except IndexError: + pass + self.columns.contents.append((urwid.Text(""), ("weight", 1, False))) + for card in hand: + widget = CardDisplayer(card) + self.columns.widget_list.append(widget) + self.columns.column_types.append(("fixed", 3)) + urwid.connect_signal(widget, "click", self.__on_click) + self.columns.contents.append((urwid.Text(""), ("weight", 1, False))) + self.columns.focus_position = 1 + + def __on_click(self, card_wid): + self._emit("click", card_wid) + + +class Card(TarotCard): + """This class is used to represent a card, logically + and give a text representation with attributes""" + + SIZE = 3 # size of a displayed card + + def __init__(self, suit, value): + """@param file: path of the PNG file""" + TarotCard.__init__(self, (suit, value)) + + def get_attr_text(self): + """return text representation of the card with attributes""" + try: + value = "%02i" % int(self.value) + except ValueError: + value = self.value[0].upper() + self.value[1] + if self.suit == "atout": + if self.value == "excuse": + suit = "c" + else: + suit = "A" + color = "neutral" + elif self.suit == "pique": + suit = "♠" + color = "black" + elif self.suit == "trefle": + suit = "♣" + color = "black" + elif self.suit == "coeur": + suit = "♥" + color = "red" + elif self.suit == "carreau": + suit = "♦" + color = "red" + if self.bout: + color = "special" + return ("card_%s" % color, "%s%s" % (value, suit)) + + def get_widget(self): + """Return a widget representing the card""" + return CardDisplayer(self) + + +class Table(urwid.FlowWidget): + """Represent the cards currently on the table""" + + def __init__(self): + self.top = self.left = self.bottom = self.right = None + + def put_card(self, location, card): + """Put a card on the table + @param location: where to put the card (top, left, bottom or right) + @param card: Card to play or None""" + assert location in ["top", "left", "bottom", "right"] + assert isinstance(card, Card) or card == None + if [getattr(self, place) for place in ["top", "left", "bottom", "right"]].count( + None + ) == 0: + # If the table is full of card, we remove them + self.top = self.left = self.bottom = self.right = None + setattr(self, location, card) + self._invalidate() + + def rows(self, size, focus=False): + return self.display_widget(size, focus).rows(size, focus) + + def render(self, size, focus=False): + return self.display_widget(size, focus).render(size, focus) + + def display_widget(self, size, focus): + cards = {} + max_col, = size + separator = " - " + margin = max((max_col - Card.SIZE) / 2, 0) * " " + margin_center = max((max_col - Card.SIZE * 2 - len(separator)) / 2, 0) * " " + for location in ["top", "left", "bottom", "right"]: + card = getattr(self, location) + cards[location] = card.get_attr_text() if card else Card.SIZE * " " + render_wid = [ + urwid.Text([margin, cards["top"]]), + urwid.Text([margin_center, cards["left"], separator, cards["right"]]), + urwid.Text([margin, cards["bottom"]]), + ] + return urwid.Pile(render_wid) + + +class TarotGame(QuickTarotGame, urwid.WidgetWrap): + """Widget for card games""" + + def __init__(self, parent, referee, players): + QuickTarotGame.__init__(self, parent, referee, players) + self.load_cards() + self.top = urwid.Pile([urwid.Padding(urwid.Text(self.top_nick), "center")]) + # self.parent.host.debug() + self.table = Table() + self.center = urwid.Columns( + [ + ("fixed", len(self.left_nick), urwid.Filler(urwid.Text(self.left_nick))), + urwid.Filler(self.table), + ( + "fixed", + len(self.right_nick), + urwid.Filler(urwid.Text(self.right_nick)), + ), + ] + ) + """urwid.Pile([urwid.Padding(self.top_card_wid,'center'), + urwid.Columns([('fixed',len(self.left_nick),urwid.Text(self.left_nick)), + urwid.Padding(self.center_cards_wid,'center'), + ('fixed',len(self.right_nick),urwid.Text(self.right_nick)) + ]), + urwid.Padding(self.bottom_card_wid,'center') + ])""" + self.hand_wid = Hand(selectable=True, on_click=self.on_click) + self.main_frame = urwid.Frame( + self.center, header=self.top, footer=self.hand_wid, focus_part="footer" + ) + urwid.WidgetWrap.__init__(self, self.main_frame) + self.parent.host.bridge.tarot_game_ready( + self.player_nick, referee, self.parent.profile + ) + + def load_cards(self): + """Load all the cards in memory""" + QuickTarotGame.load_cards(self) + for value in list(map(str, list(range(1, 22)))) + ["excuse"]: + card = Card("atout", value) + self.cards[card.suit, card.value] = card + self.deck.append(card) + for suit in ["pique", "coeur", "carreau", "trefle"]: + for value in list(map(str, list(range(1, 11)))) + ["valet", "cavalier", "dame", "roi"]: + card = Card(suit, value) + self.cards[card.suit, card.value] = card + self.deck.append(card) + + def tarot_game_new_handler(self, hand): + """Start a new game, with given hand""" + if hand is []: # reset the display after the scores have been showed + self.reset_round() + for location in ["top", "left", "bottom", "right"]: + self.table.put_card(location, None) + self.parent.host.redraw() + self.parent.host.bridge.tarot_game_ready( + self.player_nick, self.referee, self.parent.profile + ) + return + QuickTarotGame.tarot_game_new_handler(self, hand) + self.hand_wid.update(self.hand) + self.parent.host.redraw() + + def tarot_game_choose_contrat_handler(self, xml_data): + """Called when the player has to select his contrat + @param xml_data: SàT xml representation of the form""" + form = xmlui.create( + self.parent.host, + xml_data, + title=_("Please choose your contrat"), + flags=["NO_CANCEL"], + profile=self.parent.profile, + ) + form.show(valign="top") + + def tarot_game_show_cards_handler(self, game_stage, cards, data): + """Display cards in the middle of the game (to show for e.g. chien ou poignée)""" + QuickTarotGame.tarot_game_show_cards_handler(self, game_stage, cards, data) + self.center.widget_list[1] = urwid.Filler(Hand(self.to_show)) + self.parent.host.redraw() + + def tarot_game_your_turn_handler(self): + QuickTarotGame.tarot_game_your_turn_handler(self) + + def tarot_game_score_handler(self, xml_data, winners, loosers): + """Called when the round is over, display the scores + @param xml_data: SàT xml representation of the form""" + if not winners and not loosers: + title = _("Draw game") + else: + title = _("You win \o/") if self.player_nick in winners else _("You loose :(") + form = xmlui.create( + self.parent.host, + xml_data, + title=title, + flags=["NO_CANCEL"], + profile=self.parent.profile, + ) + form.show() + + def tarot_game_invalid_cards_handler(self, phase, played_cards, invalid_cards): + """Invalid cards have been played + @param phase: phase of the game + @param played_cards: all the cards played + @param invalid_cards: cards which are invalid""" + QuickTarotGame.tarot_game_invalid_cards_handler( + self, phase, played_cards, invalid_cards + ) + self.hand_wid.update(self.hand) + if self._autoplay == None: # No dialog if there is autoplay + self.parent.host.bar_notify(_("Cards played are invalid !")) + self.parent.host.redraw() + + def tarot_game_cards_played_handler(self, player, cards): + """A card has been played by player""" + QuickTarotGame.tarot_game_cards_played_handler(self, player, cards) + self.table.put_card(self.get_player_location(player), self.played[player]) + self._checkState() + self.parent.host.redraw() + + def _checkState(self): + if isinstance( + self.center.widget_list[1].original_widget, Hand + ): # if we have a hand displayed + self.center.widget_list[1] = urwid.Filler( + self.table + ) # we show again the table + if self.state == "chien": + self.to_show = [] + self.state = "wait" + elif self.state == "wait_for_ecart": + self.state = "ecart" + self.hand.extend(self.to_show) + self.hand.sort() + self.to_show = [] + self.hand_wid.update(self.hand) + + ##EVENTS## + def on_click(self, hand, card_wid): + """Called when user do an action on the hand""" + if not self.state in ["play", "ecart", "wait_for_ecart"]: + # it's not our turn, we ignore the click + card_wid.select(False) + return + self._checkState() + if self.state == "ecart": + if len(self.hand_wid.get_selected()) == 6: + pop_up_widget = sat_widgets.ConfirmDialog( + _("Do you put these cards in chien ?"), + yes_cb=self.on_ecart_done, + no_cb=self.parent.host.remove_pop_up, + ) + self.parent.host.show_pop_up(pop_up_widget) + elif self.state == "play": + card = card_wid.get_card() + self.parent.host.bridge.tarot_game_play_cards( + self.player_nick, + self.referee, + [(card.suit, card.value)], + self.parent.profile, + ) + self.hand.remove(card) + self.hand_wid.update(self.hand) + self.state = "wait" + + def on_ecart_done(self, button): + """Called when player has finished his écart""" + ecart = [] + for card in self.hand_wid.get_selected(): + ecart.append((card.suit, card.value)) + self.hand.remove(card) + self.hand_wid.update(self.hand) + self.parent.host.bridge.tarot_game_play_cards( + self.player_nick, self.referee, ecart, self.parent.profile + ) + self.state = "wait" + self.parent.host.remove_pop_up()