view libervia/tui/game_tarot.py @ 4178:cf0ea77f9537

plugin XEP-0277, doc: conflict is now checked and avoided if `user_friendly_id_suffix` is `False`, doc updated
author Goffi <goffi@goffi.org>
date Tue, 05 Dec 2023 13:39:03 +0100
parents b620a8e882e1
children 0d7bb4df2343
line wrap: on
line source

#!/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()