changeset 144:80661755ea8d

Primitivus: Tarot card game implementation - quick frontend: card_game added - wix: card_game splitted with quick frontend - tools: new game library - primitivus: new card_game widget (not finished yet) - primitivus: SàT XML UI management: first draft
author Goffi <goffi@goffi.org>
date Mon, 26 Jul 2010 19:43:44 +0800 (2010-07-26)
parents 119f45746fde
children c8b231abfe96
files frontends/primitivus/card_game.py frontends/primitivus/chat.py frontends/primitivus/custom_widgets.py frontends/primitivus/primitivus frontends/primitivus/xmlui.py frontends/quick_frontend/quick_app.py frontends/quick_frontend/quick_card_game.py frontends/quick_frontend/quick_chat.py frontends/wix/card_game.py
diffstat 9 files changed, 557 insertions(+), 105 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/primitivus/card_game.py	Mon Jul 26 19:43:44 2010 +0800
@@ -0,0 +1,149 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Primitivus: a SAT frontend
+Copyright (C) 2009, 2010  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 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import urwid
+from tools.games import TarotCard
+from quick_frontend.quick_card_game import QuickCardGame
+from xmlui import XMLUI
+
+class Hand(urwid.WidgetWrap):
+    """Used to display several cards, and manage a hand"""
+
+    def __init__(self):
+        self.columns = urwid.Columns([])
+        urwid.WidgetWrap.__init__(self, self.columns)
+
+    def update(self, hand):
+        """Update the hand displayed in this widget"""
+        del self.columns.widget_list[:]
+        del self.columns.column_types[:]
+        for card in hand:
+            self.columns.widget_list.append(urwid.Text(card.getAttrText()))
+            self.columns.column_types.append(('weight',1))
+            
+
+        
+
+class Card(TarotCard):
+    """This class is used to represent a card, logically
+    and give a text representation with attributes"""
+
+    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))
+
+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.top_card_wid = urwid.Text('') 
+        self.center_cards_wid = urwid.Text(' - ') 
+        self.bottom_card_wid = urwid.Text('') 
+        center = 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')
+                             ])
+        body = urwid.Filler(center)
+        self.hand_wid = Hand()
+        self.main_frame = urwid.Frame(body,header=self.top, footer=self.hand_wid)
+        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 selectable(self):
+        return True
+    
+    def keypress(self, size, key):
+        return key
+        
+    def render(self, size, focus=False):
+        return self.display_widget(size, focus).render(size, focus)
+
+    def display_widget(self, size, focus):
+        (maxcol,maxrow) = size
+
+        return self.main_frame"""
+
--- a/frontends/primitivus/chat.py	Thu Jul 22 22:47:29 2010 +0800
+++ b/frontends/primitivus/chat.py	Mon Jul 26 19:43:44 2010 +0800
@@ -25,6 +25,7 @@
 import custom_widgets
 import time
 from tools.jid  import JID
+from card_game import CardGame
 
 
 class ChatText(urwid.FlowWidget):
@@ -79,8 +80,10 @@
         self.content = urwid.SimpleListWalker([])
         self.text_list = urwid.ListBox(self.content)
         self.chat_widget = urwid.Frame(self.text_list)
-        self.columns = urwid.Columns([('weight', 8, self.chat_widget)])
-        urwid.WidgetWrap.__init__(self, self.__getDecoration(self.columns))
+        self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)])
+        self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)])
+        self.pile = urwid.Pile([self.chat_colums])
+        urwid.WidgetWrap.__init__(self, self.__getDecoration(self.pile))
         self.setType(type)
         self.day_change = time.strptime(time.strftime("%a %b %d 00:00:00  %Y")) #struct_time of day changing time
         self.show_timestamp = True
@@ -91,7 +94,7 @@
     def keypress(self, size, key):
         if key == "meta p": #user wants to (un)hide the presents panel
             if self.type == 'group':
-                widgets = self.columns.widget_list
+                widgets = self.chat_colums.widget_list
                 if self.present_panel in widgets:
                     self.__removePresentPanel()
                 else:
@@ -135,7 +138,7 @@
         if type == 'one2one':
             self.historyPrint(profile=self.host.profile)
         elif type == 'group':
-            if len(self.columns.widget_list) == 1:
+            if len(self.chat_colums.widget_list) == 1:
                 present_widget = self.__buildPresentList()
                 self.present_panel = custom_widgets.VerticalSeparator(present_widget)
                 self.__appendPresentPanel()
@@ -144,10 +147,11 @@
         return custom_widgets.LabelLine(widget, custom_widgets.SurroundedText(unicode(self.target)))
 
     def showDecoration(self, show=True):
+        """Show/Hide the decoration around the chat window"""
         if show:
-            main_widget = self.__getDecoration(self.columns)
+            main_widget = self.__getDecoration(self.pile)
         else:
-            main_widget = self.columns
+            main_widget = self.pile
         self._w = main_widget
 
 
@@ -156,13 +160,28 @@
         return self.present_wid
    
     def __appendPresentPanel(self):
-        self.columns.widget_list.append(self.present_panel) 
-        self.columns.column_types.append(('weight', 2))
+        self.chat_colums.widget_list.append(self.present_panel) 
+        self.chat_colums.column_types.append(('weight', 2))
 
     def __removePresentPanel(self):
-        self.columns.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.columns
-        self.columns.widget_list.remove(self.present_panel)
-        del self.columns.column_types[-1]
+        self.chat_colums.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.chat_colums
+        self.chat_colums.widget_list.remove(self.present_panel)
+        del self.chat_colums.column_types[-1]
+    
+    def __appendGamePanel(self, widget):
+        assert (len(self.pile.widget_list) == 1)
+        self.pile.widget_list.insert(0,widget)
+        self.pile.item_types.insert(0,('weight', 1))
+        self.pile.widget_list.insert(1,urwid.Filler(urwid.Divider('-')))
+        self.pile.item_types.insert(1,('fixed', 1))
+        self.host.redraw()
+
+    def __removeGamePanel(self):
+        assert (len(self.pile.widget_list) == 3)
+        self.pile.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.chat_colums
+        del self.pile.widget_list[0]
+        del self.pile.item_types[0]
+        self.host.redraw()
 
     def setSubject(self, subject, wrap='space'):
         """Set title for a group chat"""
@@ -209,11 +228,25 @@
         self.content.append(ChatText(self, timestamp or None, my_jid, from_jid, msg))
         self.text_list.set_focus(len(self.content)-1)
         self.host.redraw()
+    
+    def startGame(self, game_type, referee, players):
+        """Configure the chat window to start a game"""
+        if game_type=="Tarot":
+            try:
+                self.tarot_wid = CardGame(self, referee, players, self.nick)
+                self.__appendGamePanel(self.tarot_wid)
+            except e:
+                self.host.debug()
+    
+    def getGame(self, game_type):
+        """Return class managing the game type"""
+        #TODO: check that the game is launched, and manage errors
+        if game_type=="Tarot":
+            return self.tarot_wid 
 
     #MENU EVENTS#
     def onTarotRequest(self, menu):
         if len(self.occupants) != 4:
-            self.host.debug()
             self.host.showPopUp(custom_widgets.Alert(_("Can't start game"), _("You need to be exactly 4 peoples in the room to start a Tarot game"), ok_cb=self.host.removePopUp)) 
         else:
             self.host.bridge.tarotGameCreate(self.id, list(self.occupants), self.host.profile)
--- a/frontends/primitivus/custom_widgets.py	Thu Jul 22 22:47:29 2010 +0800
+++ b/frontends/primitivus/custom_widgets.py	Mon Jul 26 19:43:44 2010 +0800
@@ -23,7 +23,6 @@
 from urwid.escape import utf8decode
 
 class Password(urwid.Edit):
-    toto=0
     """Edit box which doesn't show what is entered (show '*' or other char instead)"""
 
     def __init__(self, *args, **kwargs):
@@ -47,11 +46,6 @@
         super(Password,self).insert_text(text)
 
     def render(self, size, focus=False):
-        Password.toto+=1
-        if Password.toto==30:
-            import os,pdb
-            os.system('reset')
-            pdb.set_trace()
         return super(Password, self).render(size, focus)
 
 class AdvancedEdit(urwid.Edit):
--- a/frontends/primitivus/primitivus	Thu Jul 22 22:47:29 2010 +0800
+++ b/frontends/primitivus/primitivus	Mon Jul 26 19:43:44 2010 +0800
@@ -69,6 +69,14 @@
                  ('selected_menu', 'light gray,bold', 'dark green'),
                  ('menuitem', 'light gray,bold', 'dark red'),
                  ('menuitem_focus', 'light gray,bold', 'dark green'),
+                 ('card_neutral', 'dark gray', 'white', 'standout,underline'),
+                 ('card_neutral_selected', 'dark gray', 'dark green', 'standout,underline'),
+                 ('card_special', 'brown', 'white', 'standout,underline'),
+                 ('card_special_selected', 'brown', 'dark green', 'standout,underline'),
+                 ('card_red', 'dark red', 'white', 'standout,underline'),
+                 ('card_red_selected', 'dark red', 'dark green', 'standout,underline'),
+                 ('card_black', 'black', 'white', 'standout,underline'),
+                 ('card_black_selected', 'black', 'dark green', 'standout,underline'),
                  ]
             
 class ChatList(QuickChatList):
@@ -205,7 +213,7 @@
         if contact:
             assert(len(self.center_part.widget_list)==2)
             self.center_part.widget_list[1] = self.chat_wins[contact]
-            self.menu_roller.addMenu(_('Chat Menu'), self.chat_wins[contact].getMenu())
+            self.menu_roller.addMenu(_('Chat menu'), self.chat_wins[contact].getMenu())
 
     def onTextEntered(self, editBar):
         """Called when text is entered in the main edit bar"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/primitivus/xmlui.py	Mon Jul 26 19:43:44 2010 +0800
@@ -0,0 +1,196 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Primitivus: a SAT frontend
+Copyright (C) 2009, 2010  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 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import urwid
+import custom_widgets
+from xml.dom import minidom
+
+class Pairs(urwid.WidgetWrap):
+
+    def __init__(self, weight_0='1', weight_1='1'):
+        self.idx = 0
+        self.weight_0 = weight_0
+        self.weight_1 = weight_1
+        columns = urwid.Columns([urwid.Text(''), urwid.Text('')])
+        #XXX: empty Text hack needed because Pile doesn't support empty list
+        urwid.WidgetWrap.__init__(self,columns)
+
+    def append(self, widget):
+        pile = self._w.widget_list[self.idx]
+        if pile.__class__ == urwid.Text:
+            self._w.widget_list[self.idx] = urwid.Pile([widget])
+        else:
+            pile.widget_list.append(widget)
+            pile.item_types.append(('weight',getattr(self,'weight_'+str(self.idx))))
+        self.idx = (self.idx + 1) % 2
+        
+
+class XMLUI(urwid.WidgetWrap):
+    
+    def __init__(self, host, xml_data, title = None, options = [], misc={}):
+        self.host = host
+        self.title = title
+        self.options = options
+        self.misc = misc
+        self.ctrl_list = {}  # usefull to access ctrl
+        widget = self.constructUI(xml_data)
+        urwid.WidgetWrap.__init__(self,widget)
+
+    def __parseElems(self, node, parent):
+        """Parse elements inside a <layout> tags, and add them to the parent"""
+        for elem in node.childNodes:
+            if elem.nodeName != "elem":
+                message=_("Unmanaged tag")
+                error(message)
+                raise Exception(message)
+            id = elem.getAttribute("id")
+            name = elem.getAttribute("name")
+            type = elem.getAttribute("type")
+            value = elem.getAttribute("value") if elem.hasAttribute('value') else u''
+            if type=="empty":
+                ctrl = urwid.Text('') 
+            elif type=="text":
+                try:
+                    value = elem.childNodes[0].wholeText
+                except KeyError:
+                    warning (_("text node has no child !"))
+                ctrl = urwid.Text(value)
+            elif type=="label":
+                ctrl = urwid.Text(value+": ")
+            elif type=="string":
+                ctrl = urwid.Edit(edit_text = value)
+                self.ctrl_list[name] = ({'type':type, 'control':ctrl})
+            elif type=="password":
+                ctrl = custom_widgets.Password(edit_text = value)
+                self.ctrl_list[name] = ({'type':type, 'control':ctrl})
+            elif type=="textbox":
+                ctrl = urwid.Edit(edit_text = value, multiline=True)
+                self.ctrl_list[name] = ({'type':type, 'control':ctrl})
+            elif type=="list":
+                style=[] if elem.getAttribute("multi")=='yes' else ['single']
+                ctrl = custom_widgets.List(options=[option.getAttribute("value") for option in elem.getElementsByTagName("option")], style=style)
+                self.ctrl_list[name] = ({'type':type, 'control':ctrl})
+            elif type=="button":
+                callback_id = elem.getAttribute("callback_id")
+                ctrl = urwid.Button(value, on_press=self.onButtonPress)
+                ctrl.param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")])
+            else:
+                error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type)  #FIXME !
+                raise NotImplementedError
+            parent.append(ctrl)
+
+    def __parseChilds(self, current, elem, wanted = ['layout']):
+        """Recursively parse childNodes of an elemen
+        @param current: widget container with 'append' method
+        @param elem: element from which childs will be parsed
+        @param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant"""
+        for node in elem.childNodes:
+            if wanted and not node.nodeName in wanted:
+                raise Exception("Invalid XMLUI") #TODO: make a custom exception
+            if node.nodeName == "layout":
+                type = node.getAttribute('type')
+                if type == "tabs":
+                    raise NotImplementedError
+                    self.__parseChilds(current, None, node, ['category'])
+                else:
+                    if type == "vertical":
+                        pass
+                    elif type == "pairs":
+                        pairs = Pairs()
+                        current.append(pairs)
+                        current = pairs
+                    else:
+                        warning(_("Unknown layout, using default one"))
+                    self.__parseElems(node, current)
+            elif node.nodeName == "category":
+                raise NotImplementedError
+                """name = node.getAttribute('name') 
+                if not node.nodeName in wanted or not name or not isinstance(parent,wx.Notebook):
+                    raise Exception("Invalid XMLUI") #TODO: make a custom exception
+                notebook = parent
+                tab_panel = wx.Panel(notebook, -1) 
+                tab_panel.sizer = wx.BoxSizer(wx.VERTICAL)
+                tab_panel.SetSizer(tab_panel.sizer)
+                notebook.AddPage(tab_panel, name)
+                self.__parseChilds(tab_panel, None, node, ['layout'])"""
+
+            else:
+                message=_("Unknown tag")
+                error(message)
+                raise Exception(message) #TODO: raise a custom exception here
+
+    def constructUI(self, xml_data):
+        
+        list_box = urwid.ListBox(urwid.SimpleListWalker([]))
+        
+        cat_dom = minidom.parseString(xml_data.encode('utf-8'))
+        top= cat_dom.documentElement
+        self.type = top.getAttribute("type")
+        if not self.title:
+            self.title = top.getAttribute("title") #TODO: manage title
+        if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window']:
+            raise Exception("Invalid XMLUI") #TODO: make a custom exception
+
+        self.__parseChilds(list_box.body, cat_dom.documentElement)
+        
+        if self.type == 'form':
+            buttons = []
+            buttons.append(urwid.Button(_('Submit'),self.onFormSubmitted))
+            if not 'NO_CANCEL' in self.options:
+                buttons.append(urwid.Button(_('Cancel'),self.onFormCancelled))
+            max_len = max([len(button.get_label()) for button in buttons])
+            grid_wid = urwid.GridFlow(buttons,max_len+4,1,0,'center')
+            list_box.body.append(grid_wid)
+        
+        
+        return list_box
+
+    def show(self):
+        """Show the constructed UI"""
+        decorated = custom_widgets.LabelLine(self, custom_widgets.SurroundedText(self.title or '')) 
+        self.host.showPopUp(decorated)
+        self.host.redraw()
+
+
+    ##EVENTS##
+
+    def onButtonPress(self, button):
+        self.host.debug()
+
+    def onFormSubmitted(self, button):
+        data = []
+        for ctrl_name in self.ctrl_list:
+            ctrl = self.ctrl_list[ctrl_name]
+            if isinstance(ctrl['control'], custom_widgets.List):
+                data.append((ctrl_name, ctrl['control'].getSelectedValue()))
+            else:
+                data.append((ctrl_name, ctrl['control'].get_edit_text()))
+        if self.misc.has_key('action_back'): #FIXME FIXME FIXME: WTF ! Must be cleaned
+            raise NotImplementedError
+            self.host.debug()
+        elif self.misc.has_key('callback'):
+            self.misc['callback'](data)
+        else:
+            warning (_("The form data is not sent back, the type is not managed properly"))
+        self.host.removePopUp()
+    
+    def onFormCancelled(self, button):
+        self.host.removePopUp()
--- a/frontends/quick_frontend/quick_app.py	Thu Jul 22 22:47:29 2010 +0800
+++ b/frontends/quick_frontend/quick_app.py	Mon Jul 26 19:43:44 2010 +0800
@@ -297,7 +297,7 @@
             self.chat_wins[room_jid].getGame("Tarot").newGame(hand)
 
     def tarotChooseContrat(self, room_jid, xml_data, profile):
-        """Called when the player has too select his contrat"""
+        """Called when the player has to select his contrat"""
         if not self.check_profile(profile):
             return
         debug (_("Tarot: need to select a contrat"))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/quick_frontend/quick_card_game.py	Mon Jul 26 19:43:44 2010 +0800
@@ -0,0 +1,131 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+helper class for making a SAT frontend
+Copyright (C) 2009, 2010  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 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from logging import debug, info, error
+from tools.jid  import JID
+
+
+
+class QuickCardGame():
+    
+    def __init__(self, parent, referee, players, player_nick):
+        self.parent = parent
+        self.referee = referee
+        self.players = players
+        self.played = {}
+        for player in players:
+            self.played[player] = None
+        self.player_nick = player_nick
+        self.bottom_nick = unicode(self.player_nick)
+        idx = self.players.index(self.player_nick)
+        idx = (idx + 1) % len(self.players)
+        self.right_nick = unicode(self.players[idx])
+        idx = (idx + 1) % len(self.players)
+        self.top_nick = unicode(self.players[idx])
+        idx = (idx + 1) % len(self.players)
+        self.left_nick = unicode(self.players[idx])
+        self.bottom_nick = unicode(player_nick)
+        self.selected = [] #Card choosed by the player (e.g. during ecart)
+        self.hand_size = 13 #number of cards in a hand
+        self.hand = []
+        self.to_show = []
+        self.state = None
+
+    def loadCards(self):
+        """Load all the cards in memory
+        @param dir: directory where the PNG files are"""
+        self.cards={}
+        self.deck=[]
+        self.cards["atout"]={} #As Tarot is a french game, it's more handy & logical to keep french names
+        self.cards["pique"]={} #spade
+        self.cards["coeur"]={} #heart
+        self.cards["carreau"]={} #diamond
+        self.cards["trefle"]={} #club
+    
+    def newGame(self, hand):
+        """Start a new game, with given hand"""
+        assert (len(self.hand) == 0)
+        for suit, value in hand:
+            self.hand.append(self.cards[suit, value])
+        self.hand.sort()
+        self.state = "init"
+    
+    def contratSelected(self, contrat):
+        """Called when the contrat has been choosed
+        @param data: form result"""
+        self.parent.host.bridge.tarotGameContratChoosed(self.player_nick, self.referee, contrat or 'Passe', self.parent.host.profile)
+
+    def chooseContrat(self, xml_data):
+        """Called when the player as to select his contrat
+        @param xml_data: SàT xml representation of the form"""
+        raise NotImplementedError
+    
+    def showCards(self, game_stage, cards, data):
+        """Display cards in the middle of the game (to show for e.g. chien ou poignée)"""
+        self.to_show = []
+        for suit, value in cards:
+            self.to_show.append(self.cards[suit, value])
+            if game_stage == "chien" and data['attaquant'] == self.player_nick:
+                self.state = "wait_for_ecart"
+            else:
+                self.state = "chien"
+
+    def MyTurn(self):
+        """Called when we have to play :)"""
+        if self.state == "chien":
+            self.to_show = []
+        self.state = "play"
+
+    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"""
+        raise NotImplementedError
+    
+    def cardsPlayed(self, player, cards):
+        """A card has been played by player"""
+        if self.to_show:
+            self.to_show = []
+        pl_cards = []
+        if self.played[player] != None: #gof: à supprimer
+            for pl in self.played:
+                self.played[pl] = None
+        for suit, value in cards:
+            pl_cards.append(self.cards[suit, value])
+        self.played[player] = pl_cards[0]
+    
+    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"""
+
+        if phase == "play":
+            self.state = "play"
+        elif phase == "ecart":
+            self.state = "ecart"
+        else:
+            error ('INTERNAL ERROR: unmanaged game phase')
+        
+        for suit, value in played_cards:
+            self.hand.append(self.cards[suit, value])
+        
+        self.hand.sort()
+
--- a/frontends/quick_frontend/quick_chat.py	Thu Jul 22 22:47:29 2010 +0800
+++ b/frontends/quick_frontend/quick_chat.py	Mon Jul 26 19:43:44 2010 +0800
@@ -19,7 +19,7 @@
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-from logging import debug, info, error
+from logging import debug, info, warning, error
 from tools.jid  import JID
 
 
@@ -92,3 +92,12 @@
         """Print message in chat window. Must be implemented by child class"""
         raise NotImplementedError
     
+    def startGame(self, game_type, referee, players):
+        """Configure the chat window to start a game"""
+        #No need to raise an error as game are not mandatory
+        warning(_('startGame is not implemented in this frontend'))
+    
+    def getGame(self, game_type):
+        """Return class managing the game type"""
+        #No need to raise an error as game are not mandatory
+        warning(_('getGame is not implemented in this frontend'))
--- a/frontends/wix/card_game.py	Thu Jul 22 22:47:29 2010 +0800
+++ b/frontends/wix/card_game.py	Mon Jul 26 19:43:44 2010 +0800
@@ -27,6 +27,7 @@
 from logging import debug, info, error
 from tools.jid  import JID
 from tools.games import TarotCard
+from quick_frontend.quick_card_game import QuickCardGame
 from xmlui import XMLUI
 
 CARD_WIDTH = 74
@@ -34,16 +35,16 @@
 MIN_WIDTH = 950 #Minimum size of the panel
 MIN_HEIGHT = 500
 
-class wxCard(TarotCard):
+class WxCard(TarotCard):
     """This class is used to represent a card, graphically and logically"""
 
     def __init__(self, file):
         """@param file: path of the PNG file"""
         self.bitmap = wx.Image(file).ConvertToBitmap()
         root_name = os.path.splitext(os.path.basename(file))[0]
-        self.suit,self.value=root_name.split('_')
-        TarotCard.__init__(self, (self.suit, self.value))
-        print "Carte:",self.suit, self.value #, self.bout
+        suit,value = root_name.split('_')
+        TarotCard.__init__(self, (suit, value))
+        print "Carte:",suit, value #, self.bout
 
     def draw(self, dc, x, y):
         """Draw the card on the device context
@@ -53,32 +54,15 @@
         dc.DrawBitmap(self.bitmap, x, y, True)
 
 
-class CardPanel(wx.Panel):
+class CardPanel(QuickCardGame,wx.Panel):
     """This class is used to display the cards"""
 
     def __init__(self, parent, referee, players, player_nick):
+        QuickCardGame.__init__(self, parent, referee, players, player_nick)
         wx.Panel.__init__(self, parent)
-        self.parent = parent
-        self.referee = referee
-        self.players = players
-        self.played = {}
-        for player in players:
-            self.played[player] = None
-        self.player_nick = player_nick
-        self.bottom_nick = self.player_nick
-        idx = self.players.index(self.player_nick)
-        idx = (idx + 1) % len(self.players)
-        self.right_nick = self.players[idx]
-        idx = (idx + 1) % len(self.players)
-        self.top_nick = self.players[idx]
-        idx = (idx + 1) % len(self.players)
-        self.left_nick = self.players[idx]
-        self.bottom_nick = player_nick
         self.SetMinSize(wx.Size(MIN_WIDTH, MIN_HEIGHT))
-        self.load_cards("/home/goffi/dev/divers/images/cards/")
+        self.loadCards("/home/goffi/dev/divers/images/cards/")
         self.mouse_over_card = None #contain the card to highlight
-        self.selected = [] #Card choosed by the player (e.g. during ecart)
-        self.hand_size = 13 #number of cards in a hand
         self.visible_size = CARD_WIDTH/2 #number of pixels visible for cards
         self.hand = []
         self.to_show = []
@@ -90,33 +74,18 @@
         self.Bind(wx.EVT_LEFT_UP, self.onMouseClick)
         self.parent.host.bridge.tarotGameReady(player_nick, referee, profile_key = self.parent.host.profile)
 
-    def load_cards(self, dir):
+    def loadCards(self, dir):
         """Load all the cards in memory
         @param dir: directory where the PNG files are"""
-        self.cards={}
-        self.deck=[]
-        self.cards["atout"]={} #As Tarot is a french game, it's more handy & logical to keep french names
-        self.cards["pique"]={} #spade
-        self.cards["coeur"]={} #heart
-        self.cards["carreau"]={} #diamond
-        self.cards["trefle"]={} #club
+        QuickCardGame.loadCards(self)
         for file in glob.glob(dir+'/*_*.png'):
-            card = wxCard(file)
+            card = WxCard(file)
             self.cards[card.suit, card.value]=card
             self.deck.append(card)
-        """for value in map(str,range(1,22))+['excuse']:
-            self.idx_cards.append(self.cards["atout",value])
-        for suit in ["pique", "coeur", "carreau", "trefle"]:
-            for value in map(str,range(1,11))+["valet","cavalier","dame","roi"]:
-                self.idx_cards.append(self.cards[suit, value])"""  #XXX: no need to sort the cards !
 
     def newGame(self, hand):
         """Start a new game, with given hand"""
-        assert (len(self.hand) == 0)
-        for suit, value in hand:
-            self.hand.append(self.cards[suit, value])
-        self.hand.sort()
-        self.state = "init"
+        QuickCardGame.newGame(self, hand)
         self._recalc_ori()
         self.Refresh()
 
@@ -125,30 +94,14 @@
         @param data: form result"""
         debug (_("Contrat choosed"))
         contrat = data[0][1]
-        self.parent.host.bridge.tarotGameContratChoosed(self.player_nick, self.referee, contrat or 'Passe', self.parent.host.profile)
+        QuickCardGame.contratSelected(self, contrat)
 
     def chooseContrat(self, xml_data):
-        """Called when the player as to select hist contrat
+        """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)
 
-    def showCards(self, game_stage, cards, data):
-        """Display cards in the middle of the game (to show for e.g. chien ou poignée)"""
-        self.to_show = []
-        for suit, value in cards:
-            self.to_show.append(self.cards[suit, value])
-            if game_stage == "chien" and data['attaquant'] == self.player_nick:
-                self.state = "wait_for_ecart"
-            else:
-                self.state = "chien"
-
-    def MyTurn(self):
-        """Called when we have to play :)"""
-        if self.state == "chien":
-            self.to_show = []
-        self.state = "play"
-
     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"""
@@ -156,15 +109,7 @@
 
     def cardsPlayed(self, player, cards):
         """A card has been played by player"""
-        if self.to_show:
-            self.to_show = []
-        pl_cards = []
-        if self.played[player] != None: #gof: à supprimer
-            for pl in self.played:
-                self.played[pl] = None
-        for suit, value in cards:
-            pl_cards.append(self.cards[suit, value])
-        self.played[player] = pl_cards[0]
+        QuickCardGame.cardsPlayed(self, player, cards)
         self.Refresh()
 
     def invalidCards(self, phase, played_cards, invalid_cards):
@@ -172,25 +117,12 @@
         @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)
 
-        if phase == "play":
-            self.state = "play"
-        elif phase == "ecart":
-            self.state = "ecart"
-        else:
-            error ('INTERNAL ERROR: unmanaged game phase')
-        
-        for suit, value in played_cards:
-            self.hand.append(self.cards[suit, value])
-        
         self._recalc_ori()
         self.Refresh()
-        self.hand.sort()
         wx.MessageDialog(self, _("Cards played are invalid !"), _("Error"), wx.OK | wx.ICON_ERROR).ShowModal()
 
-
-
-
     def _is_on_hand(self, pos_x, pos_y):
         """Return True if the coordinate are on the hand cards"""
         if pos_x > self.orig_x and pos_y > self.orig_y \