Outils pour utilisateurs

Outils du site


modele_jeu_type_damier

Modèle pour programmation d'un jeu de type "damier" (morpion, gomoku, ...)

Problématique

Les jeux sont déjà assez compliqué à programmer pour ce qui concerne leurs spécificités, il faut en plus programmer toute la machinerie qui permet de définir le nombre de joueurs, de savoir si c'est un joueur ordinateur ou humain, à chaque joueur de jouer, au programme principal de déceler au bon moment que le jeu est fini, de savoir qui a gagné, etc…

Bref, j'ai essayé de construire une trame toute prête qu'il est facile d'adapter au type de jeu qu'on veut.

Principes retenus

Cette trame fonctionne en console Python, mais elle peut être reprise facilement dans le cadre d'un jeu graphique.


Voici les principes retenus:

  • on choisit au début:
    • le nombre de joueurs
    • celui qui joue en 1er (on peut le définir au hasard)
    • quel joueur est ordinateur et quel joueur est humain
    • on peut donner un nom à chaque joueur (par défaut, c'est “joueur1”, “joueur2”, …)
    • pour l'exemple, on a donné une forme de pion à chaque joueur ('X' ou 'O'), mais il faut adapter en fonction du jeu.
  • chaque joueur est représenté par un thread (classe Joueur())
  • le thread joueur est actif en permanence: il démarre au début du jeu et ne s'arrête qu'à la fin
  • chaque joueur attend son tour de jouer, et donc tous les joueurs jouent dans un ordre déterminé
  • et quand il joue, le joueur est le seul à pouvoir accéder aux données partagés (grâce à un verrou)
  • à la fin de chaque coup joué par chaque joueur, le programme principal reprend la main pour vérifier l'état du jeu et, si c'est le cas, pour arrêter le jeu et désigner le gagnant.
  • le programme principal sait aussi calculer si c'est la fin d'un tour complet.
  • le nombre de joueurs possible est quelconque, à partir de 1 (1 ⇒ solitaire/patience). 2 par défaut.
  • pour un nombre de joueurs donnés, ils peuvent tous être humains ou tous être ordinateur et toutes les possibilités entre les 2.


Du fait de la programmation choisit, et en particulier grâce au fait qu'un joueur humain utilise les mêmes threads qu'un joueur ordinateur:

  • on peut faire jouer l'ordinateur contre lui-même et même à plus de 2 joueurs, et ainsi examiner le comportement et l'efficacité des algorithmes du jeu lui-même.
  • on peut aussi très facilement fournir à un joueur humain les propositions de coups que l'ordinateur aurait calculé pour lui-même.

Pour essayer le code ci-dessous

Le mieux pour comprendre comment ça marche est encore de l'essayer. Il ne demande qu'une console Python de type idle, et un copier-coller à partir du code ci-dessous.

Pour l'exemple donné dans le code ci-dessous:

  • j'ai ajouté des temporisations pour que l'affichage ne soit pas trop rapide
  • les seules actions menées par les joueurs sont d'imprimer que c'est à eux d'agir.
  • le programme principal arrête le jeu au bout de 5 secondes.

Ces différents éléments sont bien entendu à supprimer lors de l'adaptation du code à un vrai jeu.

Efficacité

Le code proposé ne consomme pas beaucoup de ressources et, pour un jeu donné, ce seront surtout les éléments spécifiques au jeu (recherche du coup le meilleur) qui feront les délais de réponses.

Par exemple, si on retire ce qu'on a ajouté uniquement pour faire tourner la démo (les temporisations et les impressions):

  • ⇒ Avec 4 joueurs, chaque coup joué fait 1/1000 de seconde (2400 tours de table en 10 secondes)!

Code proposé

Voilà le code proposé:


#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import threading
import time
import random
 
###############################################################################
class Joueur(threading.Thread):
 
    def __init__(self, nom, num, pion, typejoueur):
        threading.Thread.__init__(self)
        self.setName(nom)  # nom du joueur. Ex: "joueur1", "joueur2", ...
        self.num = num  # numéro du joueur. Ex: 0 pour joueur1, 1 pour joueur2, etc...
        self.pion = pion  # forme de pion affecté
        self.typejoueur = typejoueur  # type de joueur: 0 = ordinateur, 1 = humain
        self.stop = False  # drapeau pour stopper le thread à la demande du programme principal
 
    def run(self):
        # accès aux variables globales
        global verrou # verrou d'accès aux variables globales
        global okjoue  # drapeau donnée par le programme principal qui permet au joueur de jouer
        global cdcoups  # compteur de coups
        global premier  # désigne le numéro du joueur qui a joué en premier
        global nbjoueurs  # nombre de joueurs du jeu
 
        while not self.stop:  # tant que le jeu n'est pas terminé
 
            ##### => chaque joueur attend son tour pour jouer
            while True:
                # on prend le verrou d'accès aux variables globales
                verrou.acquire()
                if self.stop:
                    # jeu terminé. on sort de la boucle, mais en conservant le blocage du verrou
                    break
                if okjoue and (cdcoups+premier)%nbjoueurs==self.num:
                    # = ça y est, on peut jouer, mais on conserve le verrou jusqu'à la fin du coup
                    break
                # on libère le verrou pour que les autres joueurs accédent aussi aux variables globales
                verrou.release()
 
            ##### => le joueur en cours joue
 
            if not self.stop:
                if self.typejoueur==0:
                    # c'est un joueur "ordinateur" qui joue
                    time.sleep(0.1)
                    print self.getName() + " joue son coup avec le pion: '" + self.pion + "'"
                else:
                    # c'est un joueur "humain" qui joue
                    time.sleep(0.1)
                    print self.getName() + " joue son coup avec le pion: '" + self.pion + "'" 
 
            ##### => fin du coup du joueur en cours
 
            # le joueur repasse la main au programme principal après chaque coup
            okjoue = False
 
            # on libère le verrou d'accès aux variables globales
            verrou.release()
 
            # et fin du thread si c'est demandé (sinon, attente du prochain coup)
            if self.stop:
                break
 
    def stopper(self):
        self.stop = True
 
###############################################################################
 
print "Bonjour! En route pour le jeu!"
 
############################## => initialisation du jeu et des conditions de son démarrage
 
# nombre de joueurs
nbjoueurs = 2
 
# type de joueurs: 0=ordinateur, 1=humain; on doit avoir: len(typejoueurs)==nbjoueurs
# par défaut: tous à 0 (=ordinateur joue contre lui-même):
typejoueurs=[]
for i in xrange(0,nbjoueurs):
    typejoueurs.append(0)
 
# forme du pion affecté à chaque joueur. On doit avoir: len(pions)==nbjoueurs
pions = ['O','X']  # 
if nbjoueurs>2:
    pions=[]
    for i in range(0,nbjoueurs):
        pions.append("%s" % (i+1))
 
# définir celui qui commence. Si None, fixer au hasard, sinon, donner le numéro du joueur
premier = None  
if premier==None:
    premier = random.randint(0,nbjoueurs-1)
print "=====> c'est joueur"+str(premier+1)+" qui commence"
 
############################## => initialisation du programme
 
# création du verrou qui permettra le monopole d'accès aux variables globales (lecture-écriture)
verrou = threading.Lock()
 
# création du "compteur de coups" initialisé à -1 parce que c'est le programme principal qui commence
cdcoups = -1
 
# drapeau initialisé à True pour que le programme principal reprenne la main après chaque coup
#   (initialisé à -1 parce que c'est le programme principal qui commence)
okjoue = False
 
# creation de la liste des joueurs (NB: le joueur numéro 0 est appelé "joueur1")
joueurs = []
for i in xrange(0,nbjoueurs):
    j = Joueur("joueur%d" % (i+1), i, pions[i], typejoueurs[i])
    j.setDaemon(True)
    joueurs.append(j)
 
# lancement de tous les threads des joueurs
for i in xrange(0,nbjoueurs):
    joueurs[i].start()
 
##############################
# surveillance du jeu et attente condition de fin de partie
 
tps=time.time()
while True:
    # attente ququ'un joueur ait joué
    while True:
        verrou.acquire()
        if not okjoue:
            cdcoups+=1 #  on incrémente le compteur de coups du coup qui vient d'être joué
            # on sort de la boucle, mais le verrou reste bloqué pendant la surveillance
        verrou.release()
 
    # détection du départ d'un nouveau tour numéro ((cdcoups//nbjoueurs)+1) par (cdcoups%nbjoueurs==0)
    ch=""
    if cdcoups%nbjoueurs==0:
        print
        print u"=====> début du tour " + str((cdcoups//nbjoueurs)+1)
 
    # affichage de la grille
 
 
    # Après chaque coup, examiner si les conditions d'arrêt du jeu sont atteintes
    # ici, arrêt du jeu au bout de 5 secondes
    if (time.time()-tps)>5:
        verrou.release()
        break
 
    # permet au joueur suivant de jouer
    okjoue = True
    verrou.release()
    # et on boucle pour attendre jusqu'à ce que le joueur suivant ait joué
 
#############################
# fin du jeu
print
print "fin du jeu"
 
# arrêt de tous les threads
for i in xrange(0,nbjoueurs):
    joueurs[i].stopper()
 
# attente jusqu'à ce que tous les threads soient terminés
for i in xrange(0,nbjoueurs):
    joueurs[i].join()
    verrou.acquire()
    print "fin du thread " + joueurs[i].getName()
    verrou.release()
 
print u"A bientôt pour un prochain jeu!"


Voilà ce que ce code génère comme sortie:


Bonjour! En route pour le jeu!
=====> c'est joueur2 qui commence

=====> début du tour 1
joueur2 joue son coup avec le pion: 'X'
joueur1 joue son coup avec le pion: 'O'

=====> début du tour 2
joueur2 joue son coup avec le pion: 'X'
joueur1 joue son coup avec le pion: 'O'

=====> début du tour 3
joueur2 joue son coup avec le pion: 'X'
joueur1 joue son coup avec le pion: 'O'

=====> début du tour 4
joueur2 joue son coup avec le pion: 'X'
joueur1 joue son coup avec le pion: 'O'

...
...
...
=====> début du tour 18
joueur2 joue son coup avec le pion: 'X'
joueur1 joue son coup avec le pion: 'O'

=====> début du tour 19
joueur2 joue son coup avec le pion: 'X'
joueur1 joue son coup avec le pion: 'O'

=====> début du tour 20
joueur2 joue son coup avec le pion: 'X'
joueur1 joue son coup avec le pion: 'O'

=====> début du tour 21

fin du jeu
fin du thread joueur1
fin du thread joueur2
A bientôt pour un prochain jeu!

Developpements possibles

Il est facile d'adapter cette trame à des jeux comme le morpion (tic-tac-toe) ou le gomoku.

Au fur et à mesure que je l'adapterai à d'autres jeux, je le modifierai.

En fait, à chaque fois qu'on a plusieurs joueurs qui jouent chacun à leur tour avec des conditions d'arrêt du jeu et de désignation de vainqueur, la trame peut être adaptée.


Amusez-vous bien!

modele_jeu_type_damier.txt · Dernière modification: 2008/05/10 05:56 de tyrtamos

Outils de la page