Jeu de cartes

Initialiser deux listes en variables globales enseigne et valeur contenant respectivement les familles et les valeurs des cartes. Les enseignes et les valeurs seront des objets de type str

In [1]:
from random import randint as rd

Les variables globales sont des constantes

In [2]:
enseigne = ["pique","trefle","coeur","carreau"]
valeur = ["2","3","4","5","6","7","8","9","10","V","D","R","As"]

ENSEIGNES = len(enseigne)
VALEURS = len(valeur)     
N = ENSEIGNES * VALEURS   # Le nombre total de cartes

Création d'un paquet de cartes. Écrire une fonction fabriquePaquet() qui ne prend pas d’argument et retourne un paquet complet de 52 cartes sous la forme d’une liste de tuples. Chaque tuple représente une carte (valeur, famille) .

In [3]:
def fabriquePaquet():
    '''
    Renvoie un paquet de carte rangé dans l'ordre des valeurs par enseigne
    sous la forme d'une liste. Chaque carte est représentée par un tuple
    '''
    paquet=[]
    for i in range(ENSEIGNES):
        for j in range(VALEURS):
            paquet.append((valeur[j],  enseigne[i]))
    return paquet

Mélanger le paquet de cartes. Écrire une fonction fisherYatesMelange(paquet) qui accepte en argument un paquet de cartes et retourne un paquet mélangé complet sous la forme d’une liste de tuples. Pour mélanger le paquet on utilisera l’algorithme de Fisher-Yates encore appelé Knuth shuffle.

In [4]:
def fisherYatesMelange(paquet): 
    '''
    Prend en argument un paquet de carte de type list et renvoie
    un paquet mélangé avec l'algo de Fisher Yates encore appelé
    Knuth Shuffle
    '''
    for i in range(1, len(paquet)):
        r = rd(0, i)
        paquet[i], paquet[r] = paquet[r], paquet[i]       #échange par affectation parallèle, possible car les éléments de
    return paquet                                         #la liste sont des objets non mutables  

Distribuer les cartes. Écrire une fonction distribuer(N, J, paquet) qui accepte en arguments le nombre de cartes à donner \((N)\) à chaque joueur \((J)\) avec \((N, J)\in \mathbb{N^2}\) d’un paquet préalablement mélangé. La fonction doit retourner la main de chaque joueur sous la forme d’un tableau à deux dimensions ainsi que le paquet avec les cartes restantes. La distribution pourra se faire en donnant les cartes une par une ou bien en donnant la totalité des cartes à chaque joueur.

In [5]:
def distribuer(N, J, paquet):
    '''
    Prend en argument le nombre entier N de cartes à distribuer, le nombre
    entier J de joueurs et un paquet de cartes de type list.
    Renvoie une liste 2 dimensions contenant les mains de chaque joueur
    '''
    mains = [[0]*N for l in range(J)]
    for l in range(J):
        for c in range(N):
            if len(paquet)>0:
                mains[l][c] = paquet.pop()
            else:
                return mains, None               #retourne les mains des joueurs si plus de carte dans le paquet
    return mains, paquet                    

Déterminer la carte possédant la plus grande valeur. Écrire une fonction meilleurCarte(liste) qui accepte en argument une liste de cartes ( chaque carte étant un tuple (valeur, famille) ) et renvoie sous la forme d’un tuple celle qui possède la plus grande valeur sans distinction de famille.

In [6]:
def meilleurCarte(liste):
    '''
    Prend en argument une liste de cartes (tuple) et retourne la meilleur carte
    '''
    meilleur_carte = liste[0]
    for carte in liste:                                    #attention carte est un tuple
        point_carte = valeur.index(carte[0])               #on cherche l'index de la valeur de la carte dans la liste valeur
        if point_carte > valeur.index(meilleur_carte[0]):  #plus l'index est élevé meilleur est la carte
            meilleur_carte = carte                         #on conserve la meilleur carte
    return meilleur_carte

Lancer le début de la partie. Écrire une fonction InitialisePartie(N, J) accepte en arguments le nombre de cartes à donner (N) à chaque joueur (J), prépare un paquet de cartes, le mélange et distribue les cartes.

In [7]:
def initialisePartie(N, J):
    paquet = fabriquePaquet()
    paquet = fisherYatesMelange(paquet)
    return distribuer(N, J, paquet)

Jouer Bataille Une fois le jeu initialisé, utiliser une fonction jouerBataille() (dont vous choisirez les éventuels arguments) permettant de comparer les cartes tirées par chaque joueur à chaque tour.

In [8]:
def jouerBataille(N, J):
    mains, paquet = initialisePartie(N,J)
    for carte in range(N):
        tour = []
        for joueur in range(J):
            print joueur, mains[joueur][carte]
            tour.append(mains[joueur][carte])
        print
        print "tour", carte
        print "meilleur carte", meilleurCarte(tour) 
        print
In [9]:
jouerBataille(3, 4)
0 ('V', 'pique')
1 ('4', 'carreau')
2 ('6', 'carreau')
3 ('8', 'pique')

tour 0
meilleur carte ('V', 'pique')

0 ('2', 'trefle')
1 ('V', 'coeur')
2 ('4', 'pique')
3 ('4', 'coeur')

tour 1
meilleur carte ('V', 'coeur')

0 ('9', 'pique')
1 ('D', 'coeur')
2 ('3', 'pique')
3 ('D', 'trefle')

tour 2
meilleur carte ('D', 'coeur')


Voila pour un début très simple. Si maintenant on veut ajouter quelques fonctionnalités à notre bataille, il va falloir diviser pour régner c'est à dire mettre en oeuvre une solution qui répond au problème posé tout en gardant un code modulable et facile à lire.

Une bataille plus élaborée

Chacun est libre d'implémenter les règles avec lesquelles il joue à la bataille.

Éléments de réflexion

A chaque tour il faut :

  • Poser les cartes sur la table : création d'une fonction table(mains) qui renvoie une liste des cartes posées sur la table, cette fonction retire les cartes des mains des joueurs et attribue la chaîne de caractères "-1" à un joueur qui n'a plus de carte.
  • Repérer la meilleur carte : fonction meilleurCarte
  • Le gagant du tour : fonction gagnantTour(table), on appelle table la liste des cartes posées sur la table de jeu
  • Vérifier une éventuelle bataille : création d'une fonction isBataille(table) renvoyant la liste des joueurs concernés
  • Réaliser la bataille : création d'une fonction bataille(mains, table) renvoyant la liste des cartes de la bataille
  • Ramasser les cartes : création d'une fonction ramasserCartes(table, joueur) ajoutant l'ensemble des cartes au paquet du joueur concerné
  • Vérifier si le jeu est terminé : création d'une fonction isWin(mains) qui renvoie True si le jeu est terminé et affiche l'indice du joueur gagnant

C'est parti (partie! mauvais jeu de mot)

  • Jouer un tour : création d'une fonction jouerTour() qui renvoie une liste des cartes posées sur la table, cette fonction retire les cartes des mains des joueurs.
In [10]:
def jouerTour(mains, bataille=0):
    '''
    Cette fonction prend en paramètre 
        * un tableau (liste de liste) des mains des joueurs
        * une liste optionnelle contenant les indices des joueurs participant à une éventuelle bataille
    elle renvoie : 
        * une liste des cartes posées sur la table
    '''
    if bataille == 0:                           #pas de bataille, le cas classique
        joueurs = mains
    else:
        joueurs = []                            #En cas de bataille
        for j in range(len(mains)):             #on parcourt les indices de la liste des mains
            if j in bataille:                   #si l'indice appartient à la liste des joueurs de la bataille
                joueurs.append(mains[j])        #on ajoute la main de ce joueur à la liste des mains de la bataille
            else:                               #sinon on ajoute une main vide pour avoir le même de nombre 
                joueurs.append([])              #de mains que de joueurs (plus facile de déterminer l'indice du gagnant)
    #dans tous les cas
    table = []                                  #liste des cartes posées sur la table
    for joueur in joueurs:                      #pour chaque joueur de la liste
        if len(joueur) != 0:                    #si il lui reste des cartes
            table.append(joueur[0])             #on pose une carte sur la table
            joueur.pop(0)                       #et on la retire de la main du jour
        else:
            table.append("-1")                  #si un joueur n'a plus de carte
    return table

Attention le paramètre mains de cette fonction est un objet mutable (passage par référence) je n'ai donc pas besoin de le retourner car il sera automatiquement modifier en dehors de la fonction. Il en sera toujours de même par la suite

  • On filtre les "-1" d'un joueur qui n'a plus de carte. En fait on aurait pu choisir l'option de retirer de la liste des joueurs le ou les joueurs n'ayant plus de carte. C'est un choix à faire dès le départ
In [11]:
def filtreTable(table):
    '''
    Permet de filtrer une liste de cartes de la table de jeu en supprimant les -1
    Renvoie une liste de cartes valides
    '''
    liste_carte = []
    for c in table:
        if c != "-1":
            liste_carte.append(c)
    return liste_carte
  • Le gagant du tour : fonction gagnantTour(table)
In [12]:
def gagnantTour(table):
    '''
    Cette fonction prend en argument la liste des cartes posées sur la table et renvoie un entier indiquant 
    l'indice de position du gagnant dans la liste des joueurs.
    '''
    cartes = filtreTable(table)          #on commence par filtrer les cartes valides
    if len(cartes) > 0:                  #si il reste des cartes
        carte = meilleurCarte(cartes)    #on cherche la meilleur
        gagnant = table.index(carte)     #et on récupère l'index du joueur à qui appartient cette carte avant le filtre
    else:
        return rd(0, len(table)-1)        #au cas ou il n'y a pas de carte valide (une bataille impossible à finir)
    return gagnant                        
  • Vérifier une éventuelle bataille : création d'une fonction isBataille(tour) renvoyant la liste des joueurs concernés
In [13]:
def isBataille(table):
    '''
    Cette fonction prend en paramètre une liste contenant les cartes posées sur la table pour un tour donné
    Elle renvoie la liste des joueurs concernés, il y a bataille si cette liste contient plus d'un joueur.
    '''
    liste_carte = []
    cartes = filtreTable(table)
    maxi = meilleurCarte(cartes)                          #on cherche la meilleur carte posée sur la table
    joueurs_bataille = []                                 #liste des joueurs pour la bataille 
    for j in range(len(table)):                           #pour chaque carte sur la table
        if table[j][0] != "-1" and table[j][0]==maxi[0]:  #on vérifie si elle est d'égale valeur à la meilleure carte
            joueurs_bataille.append(j)                    #Si oui on ajoute l'indice du joueur à la liste des batailleurs!
    return joueurs_bataille
  • Réaliser la bataille : création d'une fonction bataille(joueurs, mains) renvoyant les cartes de la bataille
In [17]:
def bataille(mains, joueurs):
    '''
    Cette fonction prend en paramètres:
        une liste d'entiers indiquant les joueurs concernés par la bataille,  
        une liste de l'ensemble des mains des joueurs 
    Elle renvoie la liste des cartes à ramasser.
    '''
    table=[]
    table.append(jouerTour(mains, joueurs))
    table.append(jouerTour(mains, joueurs))
    return table
  • Ramasser les cartes : création d'une fonction ramasserCartes(table, joueur) ajoutant l'ensemble des cartes au paquet du joueur concerné
In [14]:
def ramasserCartes(table, joueur, mains):
    '''
    Cette fonction prend en paramètres :
        * une liste des cartes posées sur la table
        * un entier indiquant le joueur gagnant
        * le tableau de l'ensemble des mains des joueurs
    Elle renvoie le tableau des mains mis à jour.
    '''
    table = fisherYatesMelange(table)      #on mélange les cartes ramassées sur la table
    for carte in table:
        if carte != "-1":
            mains[joueur].append(carte)    #on met à jour le tableau des mains
  • Vérifier si le jeu est terminé : création d'une fonction isWin(mains) qui renvoie True si le jeu est terminé et affiche l'indice du joueur gagnant
In [15]:
def isWin(mains):
    joueurs = len(mains)             #on compte le nombre de joueurs
    perdants = 0                     
    gagnant = 0
    for j in range(joueurs):         #pour chaque joueur
        if mains[j] == []:           #si il n'a plus de carte
            perdants +=1             #il appartient à la liste des perdants
        else:
            gagnant = j              #sinon c'est un gagnant potentiel
    if joueurs - perdants == 1:      #si il n' a plus qu'un seul joueur avec des cartes
        return gagnant               #c'est le gagnant
    else:
        return -1

Déroulement du jeu

  • Un jeu avec bataille unique (pas de n-ième bataille possible lors d'une bataille),
  • les cartes ramassées par le gagnant sont mélangées puis ajoutées à la fin de son paquet.
  • Si un des joueurs de la bataille n'a pas assez de cartes pour finir celle-ci, il est automatiquement considéré comme perdant.
  • Si aucun des joueurs de la bataille n'a assez de cartes pour finir, je vous laisse chercher dans le code la solution retenue
In [22]:
def jouerBataille(N, J):
    def affiche():                                                #une fonction interne pour afficher les résultats
        for elt in mains:                                         #on peut suivre le jeu à chaque tour
            print elt                                             #en affichant les cartes de tous les joueurs
        print
        
    mains, paquet = initialisePartie(N,J)
    coup = 0
    affiche()
    
    while (isWin(mains) == -1):                                   #tant qu'il n'y a pas de gagnant
        table = jouerTour(mains)                                  #les joueurs posent une carte sur la table
        joueurs_bataille = isBataille(table)                      #on récupère une liste de joueurs pour la bataille
        if len(joueurs_bataille) > 1:                             #si la liste contient au moins deux joueurs
            cartes_bataille = bataille(mains, joueurs_bataille)   #on récupère la liste des cartes de la bataille
            print "bataille", cartes_bataille
            gagnant = gagnantTour(cartes_bataille[-1])            #on cherche le gagnant de la bataille
            print "gagnant", gagnant
            for c in cartes_bataille:                             #on ajoute les cartes de la batailles à celles 
                table += c                                        #de la table
        else:                                                     #si pas de bataille
            gagnant = gagnantTour(table)                          #on cherche un gagnant
        ramasserCartes(table, gagnant, mains)                     #le gagnant ramasse les cartes
        coup += 1
        affiche()
    
    return isWin(mains), coup

    

Un exemple avec affichage des différents tours de jeu.
Question : Que peut-on afficher d'autre (de fort intéressant) avec la cette suite d'instructions ?

In [34]:
cartes = 3
joueurs = 3
stats = [0 for i in range(joueurs)]
for i in range(1):
    j, coup = jouerBataille(cartes, joueurs)
    stats[j] += 1
print(stats)
[('6', 'coeur'), ('As', 'pique'), ('5', 'carreau')]
[('2', 'coeur'), ('2', 'carreau'), ('2', 'trefle')]
[('R', 'carreau'), ('As', 'coeur'), ('8', 'pique')]

[('As', 'pique'), ('5', 'carreau')]
[('2', 'carreau'), ('2', 'trefle')]
[('As', 'coeur'), ('8', 'pique'), ('2', 'coeur'), ('6', 'coeur'), ('R', 'carreau')]

bataille [[('5', 'carreau'), '-1', ('8', 'pique')], ['-1', '-1', ('2', 'coeur')]]
gagnant 2
[]
[('2', 'trefle')]
[('6', 'coeur'), ('R', 'carreau'), ('2', 'carreau'), ('As', 'coeur'), ('5', 'carreau'), ('2', 'coeur'), ('8', 'pique'), ('As', 'pique')]

[]
[]
[('R', 'carreau'), ('2', 'carreau'), ('As', 'coeur'), ('5', 'carreau'), ('2', 'coeur'), ('8', 'pique'), ('As', 'pique'), ('2', 'trefle'), ('6', 'coeur')]

[0, 0, 1]

In [18]: