view frontends/src/primitivus/card_game.py @ 517:59b32c04e105

plugin text commands: added /help command
author Goffi <goffi@goffi.org>
date Sat, 20 Oct 2012 19:22:51 +0200
parents 2a072735e459
children ca13633d3b6b
line wrap: on
line source

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
Primitivus: a SAT frontend
Copyright (C) 2009, 2010, 2011, 2012  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/>.
"""

import urwid
from urwid_satext import sat_widgets
from sat.tools.games import TarotCard
from sat_frontends.quick_frontend.quick_card_game import QuickCardGame
from sat_frontends.primitivus.xmlui import XMLUI

class CardDisplayer(urwid.Text):
    """Show a card"""
    signals = ['click']

    def __init__(self, card):
        self.__selected = False
        self.card = card
        urwid.Text.__init__(self, card.getAttrText())

    def selectable(self):
        return True

    def keypress(self, size, key):
        if key == ' ':
            self.select(not self.__selected)
            self._emit('click')
        return key

    def mouse_event(self, size, event, button, x, y, focus):
        if urwid.is_mouse_press(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.getAttrText()
        if self.__selected:
            attr+='_selected'
        self.set_text((attr,txt))
        self._invalidate()

    def isSelected(self):
        return self.__selected

    def getCard(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 == ' ':
                self._emit('click', None)
            return key

    def getSelected(self):
        """Return a list of selected cards"""
        _selected = []
        for wid in self.columns.widget_list:
            if isinstance(wid, CardDisplayer) and wid.isSelected():
                _selected.append(wid.getCard())
        return _selected

    def update(self, hand):
        """Update the hand displayed in this widget
        @param hand: list of Card"""
        del self.columns.widget_list[:]
        del self.columns.column_types[:]
        self.columns.widget_list.append(urwid.Text(''))
        self.columns.column_types.append(('weight',1))
        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.__onClick)
        self.columns.widget_list.append(urwid.Text(''))
        self.columns.column_types.append(('weight',1))
        self.columns.set_focus(1)

    def __onClick(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 getAttrText(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 = u'♠'
            color = 'black'
        elif self.suit == "trefle":
            suit = u'♣'
            color = 'black'
        elif self.suit == "coeur":
            suit = u'♥'
            color = 'red'
        elif self.suit == "carreau":
            suit = u'♦'
            color = 'red'
        if self.bout:
            color = 'special'
        return ('card_%s' % color,u"%s%s" % (value,suit))

    def getWidget(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 putCard(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.getAttrText() 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 CardGame(QuickCardGame,urwid.WidgetWrap):
    """Widget for card games"""
    
    def __init__(self, parent, referee, players, player_nick):
        QuickCardGame.__init__(self, parent, referee, players, player_nick)
        self.loadCards()
        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.onClick)
        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.tarotGameReady(player_nick, referee, profile_key = self.parent.host.profile)

    def loadCards(self):
        """Load all the cards in memory"""
        QuickCardGame.loadCards(self)
        for value in map(str,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 map(str,range(1,11))+["valet","cavalier","dame","roi"]:
                card = Card(suit,value)
                self.cards[card.suit, card.value]=card
                self.deck.append(card)

    def newGame(self, hand):
        """Start a new game, with given hand"""
        QuickCardGame.newGame(self, hand)
        self.hand_wid.update(self.hand)
        self.parent.host.redraw()
    
    def contratSelected(self, data):
        """Called when the contrat has been choosed
        @param data: form result"""
        contrat = data[0][1]
        QuickCardGame.contratSelected(self, contrat)
    
    def chooseContrat(self, xml_data):
        """Called when the player as to select his contrat
        @param xml_data: SàT xml representation of the form"""
        misc = {'callback': self.contratSelected}
        form = XMLUI(self.parent.host, xml_data, title = _('Please choose your contrat'), options = ['NO_CANCEL'], misc = misc)
        form.show()

    def showCards(self, game_stage, cards, data):
        """Display cards in the middle of the game (to show for e.g. chien ou poignée)"""
        QuickCardGame.showCards(self, game_stage, cards, data)
        self.center.widget_list[1] = urwid.Filler(Hand(self.to_show))
        self.parent.host.redraw()

    def myTurn(self):
        QuickCardGame.myTurn(self)

    def showScores(self, xml_data, winners, loosers):
        """Called when the player as to select hist contrat
        @param xml_data: SàT xml representation of the form"""
        def _new_game(ignore):
            self.resetRound()
            for location in ['top','left','bottom','right']:
                self.table.putCard(location, None)
            self.parent.host.redraw()
            self.parent.host.bridge.tarotGameReady(self.player_nick, self.referee, profile_key = self.parent.host.profile)
        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(self.parent.host, xml_data, title = title, options = ['NO_CANCEL'], misc={'callback':_new_game})
        form.show()

    def invalidCards(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"""
        QuickCardGame.invalidCards(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.notify(_('Cards played are invalid !'))
        self.parent.host.redraw()
    
    def cardsPlayed(self, player, cards):
        """A card has been played by player"""
        QuickCardGame.cardsPlayed(self, player, cards)
        self.table.putCard(self.getPlayerLocation(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 onClick(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.getSelected()) == 6:
                pop_up_widget = sat_widgets.ConfirmDialog(_("Do you put these cards in chien ?"), yes_cb=self.onEcartDone, no_cb=self.parent.host.removePopUp)
                self.parent.host.showPopUp(pop_up_widget)
        elif self.state == "play":
            card = card_wid.getCard()
            self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, [(card.suit, card.value)], profile_key = self.parent.host.profile)
            self.hand.remove(card)
            self.hand_wid.update(self.hand)
            self.state = "wait"

    def onEcartDone(self,button):
        """Called when player has finished is écart"""
        ecart = []
        for card in self.hand_wid.getSelected():
            ecart.append((card.suit, card.value))
            self.hand.remove(card)
        self.hand_wid.update(self.hand)
        self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, ecart, profile_key = self.parent.host.profile)
        self.state = "wait"
        self.parent.host.removePopUp()