diff plugins/plugin_misc_tarot.py @ 94:1eb5ccead43c

Tarot game: basic trick - plugin tarot: it's now possible to make a basic game until the end \o/. Score are calculated but not sent to players yet.
author Goffi <goffi@goffi.org>
date Tue, 01 Jun 2010 18:16:15 +0930
parents 2f87651a5ad8
children be206a3d1a9b
line wrap: on
line diff
--- a/plugins/plugin_misc_tarot.py	Sun May 30 15:33:08 2010 +0930
+++ b/plugins/plugin_misc_tarot.py	Tue Jun 01 18:16:15 2010 +0930
@@ -59,6 +59,61 @@
 "description": _("""Implementation of Tarot card game""")
 }
 
+suits_order = ['pique', 'coeur', 'trefle', 'carreau', 'atout'] #I have swith the usual order 'trefle' and 'carreau' because card are more easy to see if suit colour change (black, red, black, red)
+values_order = map(str,range(1,11))+["valet","cavalier","dame","roi"]
+
+class Card():
+    """This class is used to represent a car logically"""
+    #TODO: move this in a library in tools, and share this with frontends (e.g. card_game in wix use the same class)
+
+    def __init__(self, tuple_card):
+        """@param tuple_card: tuple (suit, value)"""
+        self.suit, self.value = tuple_card
+        self.bout = True if self.suit=="atout" and self.value in ["1","21","excuse"] else False
+        if self.bout or self.value == "roi":
+            self.points = 4.5
+        elif self.value == "dame":
+            self.points = 3.5
+        elif self.value == "cavalier":
+            self.points = 2.5
+        elif self.value == "valet":
+            self.points = 1.5
+        else:
+            self.points = 0.5
+
+    def get_tuple(self):
+        return (self.suit,self.value)
+
+    @staticmethod
+    def from_tuples(tuple_list):
+        result = []
+        for card_tuple in tuple_list:
+            result.append(Card(card_tuple))
+        return result
+
+    def __cmp__(self, other):
+        if other == None:
+            return 1
+        if self.suit != other.suit:
+            idx1 = suits_order.index(self.suit)
+            idx2 = suits_order.index(other.suit)
+            return idx1.__cmp__(idx2)
+        if self.suit == 'atout':
+            if self.value == other.value == 'excuse':
+                return 0
+            if self.value == 'excuse':
+                return -1
+            if other.value == 'excuse':
+                return 1
+            return int(self.value).__cmp__(int(other.value))
+        #at this point we have the same suit which is not 'atout'
+        idx1 = values_order.index(self.value)
+        idx2 = values_order.index(other.value)
+        return idx1.__cmp__(idx2)
+
+    def __str__(self):
+        return "[%s,%s]" % (self.suit, self.value)
+
 class Tarot():
 
     def __init__(self, host):
@@ -129,11 +184,114 @@
         contrat_elt.addChild(form.toElement())
         return contrat_elt
 
-    def __next_player(self, game_data):
-        """It's next player turn
-        Increment player number & return player name"""
-        pl_idx = game_data['current_player'] = (game_data['current_player'] + 1) % len(game_data['players'])
-        return game_data['players'][pl_idx]
+    def __next_player(self, game_data, next_pl = None):
+        """Increment player number & return player name
+        @param next_pl: if given, then next_player is forced to this one
+        """
+        if next_pl:
+            game_data['current_player'] = game_data['players'].index(next_pl)
+            return next_pl
+        else:
+            pl_idx = game_data['current_player'] = (game_data['current_player'] + 1) % len(game_data['players'])
+            return game_data['players'][pl_idx]
+
+    def __winner(self, game_data):
+        """give the nick of the player who win this trick"""
+        players_data = game_data['players_data']
+        first = game_data['first_player']
+        first_idx = game_data['players'].index(first) 
+        suit_asked = None
+        strongest = None
+        winner = None
+        for idx in [(first_idx + i) % 4 for i in range(4)]:
+            player = game_data['players'][idx]
+            card = players_data[player]['played']
+            if card.value == "excuse":
+                continue
+            if suit_asked == None:
+                suit_asked = card.suit
+            if (card.suit == suit_asked or card.suit == "atout") and card > strongest:
+                strongest = card
+                winner = player
+        assert (winner)
+        return winner
+
+    def __excuse_hack(self, game_data, played, winner):
+        """give a low card to other team and keep excuse if trick is lost"""
+        #TODO: manage the case where excuse is played on the last trick (and lost)
+        #TODO: gof: manage excuse (fool)
+        players_data = game_data['players_data']
+        excuse = Card(("atout","excuse"))
+        for player in game_data['players']:
+            if players_data[player]['wait_for_low']:
+                #the excuse owner has to give a card to somebody
+                if winner == player:
+                    #the excuse owner win the trick, we check if we have something to give
+                    for card in played:
+                        if card.points == 0.5:
+                            pl_waiting = players_data[player]['wait_for_low']
+                            played.remove(card)
+                            players_data[pl_waiting]['levees'].append(card)
+                            debug (_('Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner":player, "card_waited": card, "player_waiting":pl_waiting})
+                            break
+                return
+
+        if not excuse in played:
+            return
+        
+        excuse_player = None
+        for player in game_data['players']:
+            if players_data[player]['played'] == excuse:
+                excuse_player = player
+                break
+            
+        if excuse_player == winner:
+            return #the excuse player win the trick, nothing to do
+        
+        #first we remove the excuse from played cards
+        played.remove(excuse)
+        #then we give it back to the original owner 
+        owner_levees = players_data[excuse_player]['levees']
+        owner_levees.append(excuse)
+        #finally we give a low card to the trick winner
+        low_card = None
+        for card_idx in range(len(owner_levees)-1, -1, -1):
+            if owner_levees[card_idx].points == 0.5:
+                low_card = owner_levees[card_idx]
+                del owner_levees[card_idx]
+                players_data[winner]['levees'].append(low_card)
+                debug (_('Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner":excuse_player, "card_waited": low_card, "player_waiting":winner})
+                break
+        if not low_card: #The player has no low card yet
+            #TODO: manage case when player never win a trick with low card
+            players_data[excuse_player]['wait_for_low'] = winner
+            debug(_("%(excuse_owner)s keep the Excuse but has not card to give, %(winner)s is waiting for one") % {'excuse_owner':excuse_player, 'winner':winner})
+
+
+    def __calculate_scores(self, game_data):
+        """The game is finished, time to know who won :)"""
+        players_data = game_data['players_data']
+        levees = players_data[game_data['attaquant']]['levees']
+        score = 0
+        nb_bouts = 0
+        for card in levees:
+            if card.bout:
+                nb_bouts +=1
+            score += card.points
+        point_limit = None
+        if nb_bouts == 3:
+            point_limit = 36
+        elif nb_bouts == 2:
+            point_limit = 41
+        elif nb_bouts == 1:
+            point_limit = 51
+        else:
+            point_limit = 56
+        victory = (score >= point_limit)
+        debug (_('The attacker make %(points)i and need to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s): he %(victory)s') % {'points':score, 'point_limit':point_limit, 'nb_bouts': nb_bouts, 'plural': 's' if nb_bouts>1 else '', 'victory': 'won' if victory else 'lost'})
+        #pdb.set_trace()
+
+
 
     def createGame(self, room_jid_param, players, profile_key='@DEFAULT@'):
         """Create a new game"""
@@ -218,6 +376,7 @@
         players_data = game_data['players_data']
         current_player = game_data['current_player']
         game_data['stage'] = "init"
+        game_data['first_player'] = None #first player for the current trick
         hand = game_data['hand'] = {}
         hand_size = game_data['hand_size']
         chien = game_data['chien'] = []
@@ -234,6 +393,8 @@
             self.host.profiles[profile].xmlstream.send(mess)
             players_data[player]['contrat'] = None
             players_data[player]['levees'] = [] #cards won
+            players_data[player]['played'] = None #card on the table
+            players_data[player]['wait_for_low'] = None #Used when a player wait for a low card because of excuse
 
         pl_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(players) #the player after the dealer start
         player = players[pl_idx]
@@ -290,7 +451,7 @@
                     mess.firstChildElement().addChild(self.__ask_contrat())
                     self.host.profiles[profile].xmlstream.send(mess)
                 else:
-                    #TODO: manage "everybody pass" case
+                    #TODO: gof: manage "everybody pass" case
                     best_contrat = [None, "Passe"]
                     for player in game_data['players']:
                         contrat = players_data[player]['contrat']
@@ -314,7 +475,6 @@
             elif elt.name == 'chien': #we have received the chien
                 debug (_("tarot: chien received"))
                 data = {"attaquant":elt['attaquant']}
-                players_data = game_data['players_data']
                 game_data['stage'] = "ecart"
                 game_data['attaquant'] = elt['attaquant']
                 self.host.bridge.tarotGameShowCards(room_jid.userhost(), "chien", self.__xml_to_list(elt), data, profile)
@@ -324,10 +484,10 @@
                     #TODO: check validity of écart (no king, no oulder, cards must be in player hand)
                     #TODO: show atouts (trumps) if player put some in écart
                     assert (game_data['attaquant'] == elt['player']) #TODO: throw an xml error here
-                    players_data[elt['player']]['levees'].extend(self.__xml_to_list(elt))
+                    players_data[elt['player']]['levees'].extend(Card.from_tuples(self.__xml_to_list(elt)))
                     game_data['stage'] = "play"
                     next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players']) #the player after the dealer start
-                    next_player = game_data['players'][next_player_idx]
+                    game_data['first_player'] = next_player = game_data['players'][next_player_idx]
                     to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof:
                     mess = self.createGameElt(to_jid)
                     yourturn_elt = mess.firstChildElement().addElement('your_turn')
@@ -336,29 +496,48 @@
                     current_player = game_data['players'][game_data['current_player']]
                     #assert (elt['player'] == current_player) #TODO: throw xml error here
                     cards = self.__xml_to_list(elt)
-                    #TODO: check card validity and send error mess if necessary
-                    if mess_elt['type'] != 'groupchat':
+                    
+                    if mess_elt['type'] == 'groupchat':
+                        self.host.bridge.tarotGameCardsPlayed(room_jid.userhost(), elt['player'], self.__xml_to_list(elt), profile)
+                    else:
+                        #TODO: check card validity and send error mess if necessary
                         #the card played is ok, we forward it to everybody
-                        #first we remove it from the hand
+                        #first we remove it from the hand and put in on the table
                         game_data['hand'][current_player].remove(cards[0])
+                        players_data[current_player]['played'] = Card(cards[0])
 
                         #then we forward the message
                         mess = self.createGameElt(room_jid)
                         playcard_elt = mess.firstChildElement().addChild(elt)
                         self.host.profiles[profile].xmlstream.send(mess)
                     
+                        #Did everybody played ?
+                        played = [players_data[player]['played'] for player in game_data['players']]
+                        if not played.count(None):
+                            #everybody played
+                            winner = self.__winner(game_data)
+                            debug (_('The winner of this trick is %s') % winner)
+                            #the winner win the trick
+                            self.__excuse_hack(game_data, played, winner)
+                            players_data[elt['player']]['levees'].extend(played)
+                            #nothing left on the table
+                            for player in game_data['players']:
+                                players_data[player]['played'] = None
+                            if len(game_data['hand'][current_player]) == 0:
+                                #no card lef: the game is finished
+                                self.__calculate_scores(game_data)
+                                return
+                            #next player is the winner
+                            next_player = game_data['first_player'] = self.__next_player(game_data, winner)
+                        else:
+                            next_player = self.__next_player(game_data)
+
                         #finally, we tell to the next player to play
-                        next_player = self.__next_player(game_data)
                         to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof:
                         mess = self.createGameElt(to_jid)
                         yourturn_elt = mess.firstChildElement().addElement('your_turn')
                         self.host.profiles[profile].xmlstream.send(mess)
-                    else:
-                        self.host.bridge.tarotGameCardsPlayed(room_jid.userhost(), elt['player'], self.__xml_to_list(elt), profile)
 
-
-                    
-            
             elif elt.name == 'your_turn':
                 self.host.bridge.tarotGameYourTurn(room_jid.userhost(), profile)