view libervia/tui/game_tarot.py @ 4202:b26339343076

core: use a user specific directory for PID file: default location of pid file is now specific to logged user, this allow to run several instances of Libervia by different users on the same machine without PID conflicts.
author Goffi <goffi@goffi.org>
date Sun, 14 Jan 2024 17:48:02 +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()