Пример #1
0
class Serveur:
    """La classe serveur permet de gérer plusieurs connexions avec des clients en réseau,
    le lancement d'une partie de quizz et les communications avec les clients.

    :ivar int TEMPS_MAX: délai imparti de réponses en secondes
    :ivar int TAILLE_BLOC: taille des blocs pour la communication serveur/client
    :ivar socket sock: socket du serveur
    :ivar bool partie en cours: True si un processus partie() a été lancé
    :ivar Manager() manager: manager pour les données partagées
    :ivar dict connexions: dictionnaire pseudo:socket des clients connectés au serveur. 
    :ivar Queue data: queue utilisée pour transmettre les données envoyées par les joueurs au processus partie()
    """

    def __init__(self):
        # Initialisation de la socket serveur
        self.TEMPS_MAX=30
        self.TAILLE_BLOC=4096
        self.sock = socket(AF_INET, SOCK_STREAM)
        self.sock .setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
        self.sock.bind(("",8000))
        self.sock.listen(26)

         # Attributs
        self.partie_en_cours=Value('i', 0)
        self.manager= Manager()
        self.connexions = self.manager.dict()
        self.data = self.manager.Queue()

        print("Lancement du serveur de Quizz")
        self.run()

    def run(self):
        """Lance le serveur : lancement d'un processus handle_conn() dès qu'une connexion est reçue
        """
        while True:
            con, addr = self.sock.accept()
            process = mp.Process(target=self.handle_conn, args=(con,addr))
            process.daemon = False
            process.start()
        self.sock.close() # Fermeture du serveur

    def handle_conn(self, sockClient, addrClient):
        """Gestion d'une nouvelle connexion client au serveur : le nouveau client est intégré à la liste des connexions, et une alternance lecture/envoie est mise en place pour gérer les communications.

        :param socket sockClient: la socket du Client
        :param str addr: l'adress de connexion du Client

        :raises: Problème de requête
        """
        connected = True
        try:
            # Attribution du pseudo au client et ajout à la liste des connexions
            pseudo = sockClient.recv(self.TAILLE_BLOC)
            pseudo=pseudo.decode("ascii")
            while pseudo in self.connexions.keys() :
                pseudo+=str(random.randint(1,100))
            self.connexions[pseudo]=(sockClient,0)
            print("%s a rejoint le serveur" % pseudo)
            sockClient.sendall(pseudo.encode("ascii"))
    
            # Lancement des communications client/serveur
            while connected:
                msg = sockClient.recv(self.TAILLE_BLOC)
                msg=msg.decode('ascii')
                if len(msg)>0 :
                    nouvJoueur=msg[-1]=="!"
                    if msg[:-1] ==  "quit": # deconnexion côté client
                        print("%s a quitte le serveur :'(" %pseudo)
                        reponse="quit"
                        connected=False
                    elif msg == "start!": # un nouveau client lance une partie
                        # Si aucune partie en cours
                        if self.partie_en_cours.value==0 :
                            print("Lancement de la partie ...\n")
                            reponse="\nVous avez lance une partie :)"
                            self.partie_en_cours.acquire()
                            self.partie_en_cours.value=1
                            self.partie_en_cours.release()
                            game = mp.Process(target=self.partie, args=(pseudo,sockClient))
                            game.daemon = True
                            game.start()
                        # Si une partie a dejà été lancée auparavant
                        else :
                            reponse="Une partie est deja en cours, merci de patienter :/"
                    else : # message quelconque 
                        reponse="Veuillez patienter..."
                    if nouvJoueur :
                        print("data : ", msg)
                        self.envoyer(pseudo,reponse)
                    else :
                        self.data.put(pseudo)
                        self.data.put(msg)
                else :
                    print("%s est deconnecte :'(" %pseudo)
                    connected=False
        except:
            print("Problem in request ?")
        finally:
            del self.connexions[pseudo]
            for process in mp.active_children():
                msg="Le lanceur s'est deconnecte :/"
                rejouer="Que voulez vous faire ?\n"
                fin="Fin Partie"
                for pseudo in self.connexions.keys():
                    self.envoyer(pseudo,msg)
                    self.envoyer(pseudo,fin)
                    self.demander(pseudo,rejouer)
                    self.partie_en_cours.acquire()
                    self.partie_en_cours.value=0
                    self.partie_en_cours.release()
                print("Fin de la partie ...")
                print("Fermeture du processus ", process)
                process.terminate()
                process.join()
            sockClient.close()

    def partie(self, lanceur, sockLanceur):
        """Déroule la partie : envoie les questions aux joueurs et gère leurs réponses

        :param str lanceur: pseudo du joueur ayant lancé la partie
        :param socket sockLanceur: socket du lanceur

        :ivar dict joueurs: dictionnaire pseudo:score stockant les joueurs connectés
        :ivar bool fin: True si la partie doit être terminée plus tôt
        :ivar tab: liste des questions obtenue par la lecture du fichier .csv

        :raises: Deconnexion inattendue
        """
        # Récupération des joueurs connectés
        # connexions.put("Done")
        try :
            consigne="La partie va commencer o/ \nVous avez 30sec pour repondre a chaque question ... \nBonne chance :) \n"
            debut="Debut Partie"
            fin=False
            joueurs={} # joueurs[pseudo]=score
            print("Joueurs connectes : ")
            for pseudo in self.connexions.keys() :
                scoreJoueur=0
                joueurs[pseudo]=scoreJoueur
                self.envoyer(pseudo,consigne)
                self.envoyer(pseudo,debut)
                print(pseudo)
            nb_joueurs=len(joueurs)
            print("Au total, %s joueur(s) sont connectes" % nb_joueurs)
    
            # Récupération des questions
            tab = csv.reader(open("question_quizz.csv","r", encoding ="utf-8"), dialect='excel-tab')
            count = 0
            quest ={}
            for row in tab:
                if row[0] in quest.keys():
                    quest[row[0]].append([row[1],row[2]])
                    
                else:
                    quest[row[0]] = []
                    quest[row[0]].append([row[1],row[2]])
    
    
            # Choix du thème
            print("\nChoix du theme")
            choix_theme=random.sample(quest.keys(),3)
            msg="Entrer le theme de votre choix parmis ces trois : %s, %s et %s" %(choix_theme[0],choix_theme[1],choix_theme[2])
            if lanceur in self.connexions :
                self.demander(lanceur,msg)
                succes,pseudo,reponse = self.lire_queue()
                print(succes)
                print(reponse)
                if succes :
                    reponse=reponse[:-1].lower()
                    if reponse == "quit" :
                        print("Deconnexion inattendue")
                        del joueurs[lanceur]
                        fin=True
                    elif reponse in choix_theme :
                        print("%s a choisi le theme %s " % (pseudo,reponse))
                        theme = reponse
                    else:
                        theme = random.sample(quest.keys(),1)[0]
                        msg="Vous avez fait n'importe quoi alors on vous donne un theme aleatoire : %s" %theme
                        self.envoyer(lanceur,msg)
                        print("Theme aleatoire : ", theme)
                else :
                    print("Deconnexion inattendue")
                    del joueurs[lanceur]
                    fin=True
            else :
                print("Deconnexion inattendue")
                del joueurs[lanceur]
                fin=True # si le lanceur se deconnecte, la partie est terminee
    
            # Choix du nb de questions
            print("\nChoix du nombre de questions")
            msg="Combien de questions ? (max %d)\n" %len(quest[theme])
            if lanceur in self.connexions :
                self.demander(lanceur,msg)
                succes,pseudo,reponse = self.lire_queue()
                if succes :
                    if reponse == "quit\x00" :
                        print("Deconnexion inattendue")
                        del joueurs[lanceur]
                        fin=True
                    else :
                        try :
                            rep=int(reponse[:-1])
                        except :
                            rep=3
                            msg="Vous avez rentre un nombre incorrect ! Nombre de questions par default : %s" %rep
                            self.envoyer(lanceur,msg)
                            pass
                        if type(rep)==type(2) and rep<=len(quest[theme]) :
                            print("%s a choisi %s questions" % (pseudo,rep))
                            nb_quest=rep
                        else:
                            nb_quest=3
                            msg="Vous avez rentre un nombre incorrect ! Nombre de questions par default : %s" %nb_quest
                            self.envoyer(lanceur,msg)
                else :
                    print("Deconnexion inattendue")
                    del joueurs[lanceur]
                    fin=True
            else :
                print("Deconnexion inattendue")
                del joueurs[lanceur]
                fin=True
    
            # Selection des questions
            nb_quest_theme = [i for i in range(len(quest[theme]))]
            index_al = random.sample(nb_quest_theme, nb_quest)
            
            
            # Déroulé du quizz
            count=1
            print("\nLancement du Quizz avec %s joueur(s)" % nb_joueurs)
            for i in index_al : # parcourir la liste de questions
                # boucle pour poser la question à tous les joueurs
                for pseudo in joueurs.keys() :
                    V_F="\nQuestion %d de %d: Repondez par Vrai (V) ou Faux (F)" % (count,nb_quest) 
                    votre_rep="\nReponse:"
                    question=quest[theme][i][0][:-1]
                    self.envoyer(pseudo,V_F)
                    self.envoyer(pseudo,question)
                    self.demander(pseudo,votre_rep)
                print("Question %d posee" % count)
    
                # boucle pour attendre les réponses
                t = 0
                reponses={}
                debut=time()
                while(len(reponses)<nb_joueurs and t<self.TEMPS_MAX) : # temps écoulé ou tous les joueurs répondent
                    succes,pseudo,reponse = self.lire_queue()
                    if succes :
                        reponses[pseudo]=reponse[:-1]
                        if reponses[pseudo]!="quit" :
                            print("%s a repondu %s " % (pseudo,reponses[pseudo]))
                    else :
                        print("Deconnexion inattendue")
    
                nouvJoueurs={} # MAJ des joueurs en cas de déconnexion imprévue
                for pseudo in joueurs.keys() :
                    if pseudo in reponses :
                        repJoueur = reponses[pseudo]
                        if len(repJoueur)>0 :
                            if repJoueur == "quit" :
                                print("Deconnexion inattendue")
                                if pseudo==lanceur :
                                    del joueurs[lanceur]
                                    fin=True
                                    break
                            elif repJoueur=="none" :
                                print("%s n'a pas repondu a temps" % pseudo)
                                resultat="Temps ecoule :'("
                            elif repJoueur[0].capitalize() == quest[theme][i][1]:
                                joueurs[pseudo] +=1 # augmentation du score
                                resultat="Bravo o/"
                            else:
                                joueurs[pseudo] -=0.5
                                resultat="Perdu :/"
                        else:
                            print("Reponse invalide de %s" % pseudo)
                            resultat="Reponse invalide"
                    if pseudo in self.connexions :
                        self.envoyer(pseudo,resultat)
                        nouvJoueurs[pseudo]=joueurs[pseudo]
                    else :
                        nb_joueurs-=1
    
                # MAJ du nombre de joueur au cas où tous les joueurs soient deconnectes
                if nb_joueurs==0 :
                    msg="\nPlus de joueurs en jeu ! "
                    self.fin_partie(joueurs,msg)
                    print("Fin de la partie ...\n")
                    return 
                else :
                    joueurs=nouvJoueurs.copy()
                    count+=1
    
            # Creation du classement
            print("\nClassement des joueurs")
            classment = joueurs.items()
    
            classement_trie = sorted(classment, key=lambda x: x[1])
            classement_trie.reverse()
            pod = []
            for i in range(len(classement_trie)):
                pod.append("%d : %s avec %.1f point(s)" %(i+1, classement_trie[i][0], classement_trie[i][1]))
    
            # Affichage des scores et du classement final
            for pseudo in joueurs.keys():
                if joueurs[pseudo] == 0:
                    score_tot = "Bah alors on a pas reussi a marquer un seul point??"
                else:
                    score_tot="Bien joue ! Vous avez {0} point(s)." .format(joueurs[pseudo])
                self.envoyer(pseudo,score_tot)
    
                podium = "\nClassement de la partie :"
                self.envoyer(pseudo,podium)
                for i in pod:
                    self.envoyer(pseudo,i)
        except : 
            print("erreur dans la partie :/")
            pass
        finally :
            # Fin de la partie
            msg="\nMerci d'avoir joue ! :)"
            self.fin_partie(joueurs,msg)
            print("Fin de la partie ...\n")

    def retirer(self,joueurs,pseudo):
        """Retire un joueur du dictionnaire en cas de déconnexion

        :param dict joueurs: dictionnaire pseudo:score stockant les joueurs connectés
        :param str pseudo: pseudo du joueur à retirer

        :returns: True si tout s'est bien exécuté
        :rtype: bool
        """
        try :
            res=True
            del joueurs[pseudo]
            print("%s ne fait plus partie des joueurs" % pseudo)
        except :
            res=False
            print("%s a deja ete retire" % pseudo)
            pass
        return res

    def envoyer(self,pseudo,msg): # envoi d'un message à afficher au client
        """Envoie un message à un joueur, pas de réponse attendue

        :param str pseudo: pseudo du joueur avec lequel le serveur doit communiquer
        :param str msg: message à envoyer

        :returns: True si tout s'est bien exécuté
        :rtype: bool
        """
        res=False
        try :
            msg=msg.encode('ascii')
            sock=self.connexions[pseudo][0]
            sock.send(msg)
            res=True
        except :
            res=False
            pass
        return res

    def demander(self,pseudo,msg): # envoi d'un message attendant une réponse
        """Envoie un message à un joueur en précisant qu'une réponse est attendue

        :param str pseudo: pseudo du joueur avec lequel le serveur doit communiquer
        :param str msg: message à envoyer

        :returns: True si tout s'est bien exécuté
        :rtype: bool
        """
        res=False
        try :
            msg+='1'
            msg=msg.encode('ascii')
            sock=self.connexions[pseudo][0]
            sock.send(msg)
            res=True
        except :
            res=False
            pass
        return res

    def lire_queue(self) :
        """Lecture des données présentes dans la queue data.

        :returns: True si la queue a bien été lue, le pseudo du joueur ayant envoyé un message et le message reçu
        :rtype: bool,str,str
        
        :Example:

        >>> serveur = Serveur()
        >>> serveur.lire_queue()
        [True,'elise','Hello']

        """
        succes = False
        try:
            pseudo=self.data.get() # pseudo
            reponse=self.data.get() # message
            succes=True
        except :
            succes=False
            pass
        return succes,pseudo,reponse

    def fin_partie(self,joueurs,msg):
        """Fonction terminant proprement la partie : parcourt le dictionnaire des joueurs et envoie à chacun un message de cloture, puis demande s'ils veulent rejouer.

        :param dict joueurs: dictionnaire pseudo:score stockant les joueurs connectés
        :param str msg: message à envoyer aux joueurs
        """
        rejouer="Que voulez vous faire ?\n"
        fin="Fin Partie"
        for pseudo in joueurs.keys():
            self.envoyer(pseudo,msg)
            self.envoyer(pseudo,fin)
            self.demander(pseudo,rejouer)
        self.partie_en_cours.acquire()
        self.partie_en_cours.value=0
        self.partie_en_cours.release()