Créer un jeu pierre-feuille-ciseaux en Python
Découvrez comment créer un jeu pierre-feuille-ciseaux en Python : règles, hasard, classe Joueur et pistes d’évolution. Suivez une méthode simple et testable.

Créer un jeu pierre-feuille-ciseaux en Python : du premier tour aux évolutions
Un jeu de pierre-feuille-ciseaux paraît presque trop simple pour être un projet de programmation. Trois choix, quelques règles, et un ordinateur qui tire au hasard : l'essentiel tient en quelques lignes. Justement, c'est un bon point de départ. On peut se concentrer sur l'essentiel - saisie utilisateur, conditions, hasard et organisation - sans devoir se lancer dans une interface compliquée.
Le projet reprend ce classique en version solo : le joueur affronte l'ordinateur. On utilise random pour choisir le coup adverse et une classe Player/Joueur pour regrouper les informations du joueur. L'exercice marche parce qu'il force à clarifier des choses simples, mais importantes : qu'est-ce qu'on entend par « 33 % de chances » ? Que fait-on en cas d'égalité ? Et à partir de quand une classe sert vraiment à quelque chose ?
L'objectif, ici, n'est pas de gonfler un mini-jeu en architecture « sérieuse » : c'est de poser des bases claires, pour pouvoir faire évoluer le code sans le casser à chaque nouvelle fonctionnalité.
Ce que le projet apprend vraiment
Le projet d'origine propose une partie à un joueur contre l'ordinateur. Le joueur saisit son choix, l'ordinateur choisit au hasard, puis le programme affiche le résultat. Une classe sert à stocker les infos du joueur - même si, pour l'instant, il n'y a pas grand-chose dedans. L'auteur évoque aussi une version à deux joueurs plus tard.
Ce périmètre est pratique pour un premier exercice : chaque partie correspond à une notion de programmation facile à repérer. La saisie du joueur est une entrée. Le coup de l'ordinateur vient du hasard. La comparaison des choix se fait avec des conditions. Et une classe permet de regrouper des données liées.
Le hasard n'est pas une stratégie
En Python, random permet de tirer un élément dans une liste. Si l'ordinateur choisit uniformément entre pierre, feuille et ciseaux, chaque option a une probabilité d'un tiers à chaque tour. Mais ça ne veut pas dire que, « sur toute la partie », le joueur gagne forcément à 33 %.
Il faut distinguer le résultat d'un tour et celui d'une partie. Sur un tour, il y a trois issues possibles : victoire du joueur, victoire de l'ordinateur, ou égalité. Si le choix du joueur est fixé et que l'ordinateur tire uniformément, ces trois issues ont chacune une probabilité d'un tiers.
En revanche, si on rejoue après une égalité jusqu'à obtenir un gagnant, on écarte les égalités. Parmi les tours qui finissent par un vainqueur, le joueur et l'ordinateur se retrouvent à égalité de probabilité (50 % chacun) dans ce modèle symétrique.
Cette nuance paraît théorique, mais dès que vous affichez des statistiques, elle compte. Dire « le joueur a 33 % de chances de gagner » sans préciser si on parle d'un tour ou d'une partie, c'est le genre de phrase qui peut induire en erreur.
Une classe utile seulement si elle porte une vraie raison
Créer une classe Joueur peut être un bon exercice en programmation orientée objet. Mais une classe ne rend pas le code automatiquement « meilleur ». Si la classe ne sert qu'à stocker un nom qui n'est jamais réutilisé, autant mettre ça en variable simple.
La classe devient intéressante quand elle regroupe des informations qui évoluent ensemble : nom, nombre de victoires, défaites et égalités, par exemple. Elle peut aussi contenir des méthodes liées à ces données. Autrement dit : une classe n'est pas une obligation « par principe », c'est un choix de structure.
Une classe est pertinente quand elle clarifie le modèle du jeu. Sur un prototype très court, une structure plus légère peut suffire.
Le projet est présenté dans l'article d'origine sur DEV. Pour vérifier le comportement de random côté Python, voir aussi la documentation officielle du module random.
Concevoir une partie sans ambiguïté
Avant de coder, le plus rentable est de fixer le déroulement. Les mini-jeux échouent rarement à cause d'un algorithme compliqué. Ils deviennent plutôt confus quand les règles implicites ne sont pas décidées : une égalité termine-t-elle la partie ? Et si l'utilisateur tape une valeur inconnue, on redemande ou on « punit » ? La partie se joue en un seul tour, ou jusqu'à ce qu'il y ait un gagnant ?
Pour une première version, choisissez une règle simple : le joueur et l'ordinateur choisissent, le programme affiche les deux choix, puis indique victoire, défaite ou égalité. Ensuite seulement, vous décidez si vous en restez là (un seul tour) ou si vous rejouez après une égalité.
Ces deux formats sont valables, mais l'expérience n'est pas la même.
Écrire les règles une seule fois
La règle centrale est cyclique : pierre bat ciseaux, ciseaux battent feuille, feuille bat pierre. Oui, vous pouvez le coder avec des if imbriqués. Ça marche, mais la logique devient vite pénible à relire.
Une autre approche consiste à expliciter la relation gagnant/perdant via une table. Par exemple : pierre bat ciseaux, ciseaux battent feuille et feuille bat pierre. Le programme vérifie d'abord si les deux choix sont identiques. Sinon, il regarde dans cette relation pour déterminer le vainqueur.
Ce style garde les règles visibles et facilite l'ajout de tests. Et si vous changez plus tard la mécanique du jeu, vous ne devrez pas réécrire toute la logique.
Normaliser la saisie
Le joueur peut taper Pierre, pierre (avec un espace), ou PIERRE. Retirer les espaces et convertir en minuscules évite de traiter ces variantes comme des choix différents. Et si l'entrée est inconnue, il faut la refuser clairement - pas l'interpréter comme une défaite « par défaut ».
Séparer les responsabilités
Même pour un exercice court, on peut distinguer trois rôles : demander le choix du joueur, produire le choix de l'ordinateur, puis calculer le résultat. Cela n'impose pas d'avoir plusieurs fichiers ni une « grosse » architecture. Ça empêche juste la boucle principale de devenir un mélange de saisie, règles et affichage.
Cette séparation aide aussi au débogage : si le jeu annonce n'importe quoi, vous regardez d'abord la fonction qui compare. Si l'ordinateur semble toujours choisir la pierre, vous vérifiez le tirage aléatoire. Si une entrée est rejetée à tort, vous testez la validation.
Écrire une version simple en Python
Voici une implémentation pédagogique possible. Elle reprend les idées décrites plus haut : jeu solo, tirage de l'ordinateur avec random, et données de joueur. Elle ne cherche pas à reproduire mot pour mot un code existant.
import random
CHOIX = ("pierre", "feuille", "ciseaux")
class Joueur:
def __init__(self, nom):
self.nom = nom
self.victoires = 0
self.defaites = 0
self.egalites = 0
def determiner_resultat(choix_joueur, choix_ordinateur):
if choix_joueur == choix_ordinateur:
return "egalite"
victoires = {
"pierre": "ciseaux",
"feuille": "pierre",
"ciseaux": "feuille",
}
if victoires[choix_joueur] == choix_ordinateur:
return "victoire"
return "defaite"
def demander_choix():
while True:
choix = input("Votre choix (pierre, feuille, ciseaux) : ")
choix = choix.strip().lower()
if choix in CHOIX:
return choix
print("Choix invalide. Essayez à nouveau.")
def jouer():
joueur = Joueur("Joueur")
while True:
choix_joueur = demander_choix()
choix_ordinateur = random.choice(CHOIX)
resultat = determiner_resultat(choix_joueur, choix_ordinateur)
print(f"L'ordinateur a choisi : {choix_ordinateur}")
if resultat == "victoire":
joueur.victoires += 1
print("Vous gagnez cette manche.")
elif resultat == "defaite":
joueur.defaites += 1
print("L'ordinateur gagne cette manche.")
else:
joueur.egalites += 1
print("Égalité : on rejoue.")
continue
print(
f"Bilan : {joueur.victoires} victoire(s), "
f"{joueur.defaites} défaite(s), "
f"{joueur.egalites} égalité(s)."
)
break
jouer()
Pourquoi ce découpage marche
CHOIX centralise les valeurs autorisées. Si plus tard vous ajoutez une interface, vous n'aurez pas à disperser les options dans plusieurs conditions.
demander_choix() gère la saisie et la validation. determiner_resultat() contient la règle du jeu. Et jouer() assemble tout : coordination et mise à jour des statistiques.
La classe Joueur stocke ici un nom et trois compteurs. C'est déjà une bonne utilisation. Si vous ne gérez qu'une seule manche sans score ni état, vous pourriez supprimer la classe et raccourcir encore le programme. Le bon choix dépend de la prochaine fonctionnalité que vous visez.
Et la boucle de saisie évite un piège fréquent : quitter dès que l'utilisateur tape une faute de frappe. Ici, l'ordinateur ne choisit son coup qu'après une saisie valide, ce qui rend la règle d'interaction cohérente : une entrée invalide ne « consomme » pas un tour.
Vérifier la logique avec des cas simples
Avant d'ajouter des options, testez les neuf cas possibles : les trois égalités et les six confrontations « distinctes ». Vous pouvez commencer à la main, puis ensuite écrire des tests automatisés si vous voulez.
Vérifiez en particulier :
- pierre bat ciseaux, mais perd contre feuille ;
- feuille bat pierre, mais perd contre ciseaux ;
- ciseaux bat feuille, mais perd contre pierre ;
- deux choix identiques donnent une égalité ;
- une valeur invalide est rejetée avant le calcul du résultat.
Sur un petit jeu, ça peut sembler « trop », mais ça évite de chercher un problème partout quand vous modifiez une règle ou un mode.
Faire évoluer le jeu sans le compliquer
Une fois la version de base en place, les idées arrivent vite : score, plusieurs manches, noms personnalisés, interface graphique, ou mode à deux joueurs. Le bon ordre, c'est d'ajouter une chose à la fois, et de vérifier qu'elle répond à un besoin clair. Si une fonctionnalité ne change ni l'usage ni l'apprentissage, elle peut attendre.
Ajouter plusieurs manches
Pour jouer en plusieurs manches, déplacez la logique de « manche » dans une boucle qui continue jusqu'à ce que quelqu'un atteigne un score cible. Il faut décider si les égalités comptent comme des manches, ce qui se passe après une nouvelle partie, et comment annoncer la fin.
À ce stade, les compteurs de Joueur prennent du sens : afficher le score après chaque tour devient naturel. Et si vous voulez conserver l'historique entre deux lancements du programme, il faudra une persistance. Pour un exercice, un fichier JSON suffit souvent ; une base de données serait généralement un excès.
Passer à deux joueurs
Le mode à deux joueurs ne se résume pas à remplacer le choix aléatoire par une deuxième saisie. Le point sensible, c'est d'éviter que le premier joueur voie ce que le second a choisi.
En terminal, une solution simple consiste à demander au deuxième joueur de saisir son coup « pendant » que le premier ne voit pas l'écran, ou à faire une confirmation de prêt. En interface graphique, il faut réfléchir au flux : le choix doit rester masqué tant que le joueur n'a pas terminé sa saisie.
Ici, Joueur devient vraiment utile : chaque participant a son nom et ses statistiques. Mais les règles de manche peuvent rester communes. Pas besoin de faire une classe différente pour l'humain et l'ordinateur si, au fond, ils font la même chose.
Terminal ou interface graphique
Une interface graphique est tentante, mais elle ajoute un paquet de sujets qui n'ont pas grand-chose à voir avec les règles : mise en page, événements, états visuels, erreurs d'affichage. Si votre but est d'apprendre Python et de structurer la logique, le terminal va plus vite.
Si, en revanche, vous voulez partager le jeu ou travailler le confort utilisateur, une interface devient pertinente. Dans ce cas, gardez la logique du jeu séparée de l'affichage : le même calcul de résultat devrait pouvoir être utilisé depuis un terminal, une page web ou une application, sans réécrire toute la logique.
Le principe est simple : commencez par un jeu qui marche et dont les règles sont vérifiables. Ajoutez une couche supplémentaire seulement si elle répond à un besoin réel. Une architecture très « propre » pour un écran unique peut coûter plus de temps qu'elle n'en économise.
Conclusion
Un jeu de pierre-feuille-ciseaux est un exercice intéressant parce qu'il relie des notions de base à un résultat immédiat : saisir, tirer au hasard, appliquer des règles, puis éventuellement compter un score. L'idée du projet d'origine garde aussi une bonne ouverture : vous pouvez améliorer progressivement, par exemple en enrichissant la classe de joueur ou en passant à un mode à deux.
Le point important n'est pas le nombre de lignes : c'est la clarté. Décidez ce que vous faites en cas d'égalité, distinguez la probabilité d'un tour de celle d'une partie, et n'ajoutez une classe que si elle structure vraiment vos données.
Pour aller plus loin, reprenez le code et ajoutez une partie en trois manches. Faites-le sans toucher à la fonction qui détermine le vainqueur : c'est souvent le meilleur moyen de vérifier que votre logique de règles est suffisamment indépendante pour évoluer proprement.