From 70da7ee7364ad224cf39709df1293192d786e6ed Mon Sep 17 00:00:00 2001 From: nojhan Date: Sat, 24 Mar 2012 00:11:18 +0100 Subject: [PATCH] IA pour le jeu du pendu --- README | 7 +- src/pendu_humain.py | 8 +- src/pendu_ordi_0.py | 144 ++++++++++++++++++++++++++++++++++ src/pendu_ordi_1.py | 164 +++++++++++++++++++++++++++++++++++++++ src/pendu_ordi_1_test.py | 74 ++++++++++++++++++ 5 files changed, 390 insertions(+), 7 deletions(-) create mode 100644 src/pendu_ordi_0.py create mode 100644 src/pendu_ordi_1.py create mode 100644 src/pendu_ordi_1_test.py diff --git a/README b/README index 078a9ce..5fd6cd8 100644 --- a/README +++ b/README @@ -5,12 +5,13 @@ Apprendre à programmer, sans se faire chier Jeux de base ------------ * Devine le numéro - CODÉ -* Pense à un numéro - CODÉ -* Donjon dont vous êtes le héro + * Pense à un numéro - CODÉ +* Simulateur de Mounty Hall - CODÉ * Jeu du pendu - CODÉ + * IA du pendu - CODÉ +* Donjon dont vous êtes le héro * Mastermind * Ordonner par permutations -* Simulateur de Mounty Hall - CODÉ Jeux de tableaux ---------------- diff --git a/src/pendu_humain.py b/src/pendu_humain.py index 7b533fb..3c46869 100644 --- a/src/pendu_humain.py +++ b/src/pendu_humain.py @@ -103,7 +103,7 @@ def play( secret_word ): """ Boucle de jeu principale, renvoie vrai si le joueur a gagné """ partial_word = "_" * len(secret_word) fails = 0 - used_letter = "" + used_letters = "" while fails < len( BOARDS_PIC ): display( BOARDS_PIC, partial_word, fails ) @@ -117,12 +117,12 @@ def play( secret_word ): if letter == secret_word: return True - if letter in used_letter: - print("Vous avez déjà essayé les lettres suivantes :",used_letter) + if letter in used_letters: + print("Vous avez déjà essayé les lettres suivantes :",used_letters) # Aller directement à l'itération suivante, sans compter les ratés. continue else: - used_letter += letter + used_letters += letter if letter in secret_word: partial_word = process( letter, partial_word, secret_word ) diff --git a/src/pendu_ordi_0.py b/src/pendu_ordi_0.py new file mode 100644 index 0000000..c0d2001 --- /dev/null +++ b/src/pendu_ordi_0.py @@ -0,0 +1,144 @@ + +import random + +import pendu_humain as pendu + +def is_compatible( word, partial_word ): + """ Teste si un mot complet est compatible avec un mot partiel. + Par exemple : + >>> from pendu_ordi import * + >>> is_compatible("hirondelle","_a____e__e") + False + >>> is_compatible("hirondelle","______e__e") + True + """ + if len(word) != len(partial_word): + return False + + for i in range(len(word)): + if partial_word[i] == "_": + continue + + elif word[i] != partial_word[i]: + return False + + return True + + +def compatible_words( words, partial_word ): + """ Filtre une liste de mots en ne gardant que les mots compatibles avec un mot partiel """ + compatibles = [] + for word in words: + if is_compatible( word, partial_word ): + compatibles.append( word ) + + return compatibles + + +def letters_in( words ): + letters = set() + for word in words: + for letter in word: + letters.add(letter) + return letters + + +def guess_letter( available_letters ): + # Enlève et retourne une lettre + return available_letters.pop() + + +def guess_letter_random( available_letters ): + # Converti le set en list... + letters = list( available_letters ) + # ... afin de pouvoir y tirer un élément au hasard. + letter = random.choice( letters ) + # Répercuter le changement sur les lettres disponibles. + available_letters.remove( letter ) + return letter + + +def guess_letter_random_vowels( available_letters ): + # Les accents comptent comme des lettres différentes ! + vowels = set( ['a','e','i','o','u','y','é','è','à','ù','ë','ê','â','ï'] ) + + # Lettres présentes à la fois dans les voyelles ET les lettres disponibles + available_vowels = available_letters & vowels + + # S'il y a au moins une voyelle dans les lettres disponibles + if len( available_vowels ) > 0: + # Les tirer en priorité + available_letters = available_vowels + + letters = list( available_letters ) + letter = random.choice( letters ) + available_letters.remove( letter ) + return letter + + +if __name__=="__main__": + + print("Entrez le nombre de lettres de votre mot") + word_size = int( input() ) + + used_letters = set() + partial_word = "_" * word_size + fails = 0 + + words = pendu.filter_wordsize( pendu.download_dic( "http://nojhan.net/aapssfc/data/french_dictionary.utf8" ), word_size ) + + while fails < len( pendu.BOARDS_PIC ): + pendu.display( pendu.BOARDS_PIC, partial_word, fails ) + + words = compatible_words( words, partial_word ) + print(len(words),"mots compatibles") + + # Construit la liste des lettres existantes dans les mots restants. + remaining_letters = letters_in( words ) + + # Ne garde que les lettres n'ayant pas déjà été utilisées + remaining_letters = remaining_letters - used_letters + print(len(remaining_letters),"lettres restantes") + + letter = guess_letter_random_vowels( remaining_letters ) + used_letters.add( letter ) + print("Je pense à la lettre : «",letter,"», est-elle présente dans le mot ? [o/n]") + answer = input() + answer.lower() + + if answer == "n": + fails += 1 + continue + else: + is_correct = False + while not is_correct: + print("Entrez le nouveau mot partiel :") + new_word = input() + new_word = new_word.lower() + + print("«",new_word,"», est-ce correct ? [o/n]") + answer = input() + + if len(new_word) != len(partial_word): + print("Le nombre de lettres ne correspond pas !") + is_correct = False + continue + + if answer.lower() == "o": + is_correct = True + + # Ici, on aurait put écrire : + # continue + #else: + # break + # mais cela aurait été inutile ! + + partial_word = new_word + + if "_" not in partial_word: + break + + if fails >= len( pendu.BOARDS_PIC ): + print("J'ai perdu :-(") + else: + print("J'ai gagné :-)") diff --git a/src/pendu_ordi_1.py b/src/pendu_ordi_1.py new file mode 100644 index 0000000..9df46ca --- /dev/null +++ b/src/pendu_ordi_1.py @@ -0,0 +1,164 @@ + +import random +import operator + +import pendu_humain as pendu + +def is_compatible( word, partial_word ): + """ Teste si un mot complet est compatible avec un mot partiel. + Par exemple : + >>> from pendu_ordi import * + >>> is_compatible("hirondelle","_a____e__e") + False + >>> is_compatible("hirondelle","______e__e") + True + """ + if len(word) != len(partial_word): + return False + + for i in range(len(word)): + if partial_word[i] == "_": + continue + + elif word[i] != partial_word[i]: + return False + + return True + + +def compatible_words( words, partial_word, used_letters ): + """ Filtre une liste de mots en ne gardant que les mots compatibles avec un mot partiel """ + compatibles = [] + for word in words: + # Ensemble des lettres utilisées dans le mot + word_letters = set(word) + # Les lettres disponibles sont celles qui ont été utilisées MOINS celles déjà devinées + false_letters = used_letters - set(partial_word.replace("_","")) + + # Si le mot est compatible ET qu'aucune des lettres utilisées n'y est + if is_compatible( word, partial_word ) and len( false_letters & word_letters ) == 0: + compatibles.append( word ) + + return compatibles + + +def guess_letter_frequency( words, used_letters, verbose = True ): + """ Choisi une lettre en fonction de sa fréquence dans la liste des mots compatibles disponibles """ + freqs = {} + for word in words: + for letter in word: + if letter not in used_letters: + # Si la lettre n'a pas déjà été rencontrée. + if letter not in freqs: + # Créer la clef correspondante dans le dictionnaire. + freqs[letter] = 1 + else: + # Incrémenter le compteur d'occurence. + freqs[letter] += 1 + + best_letter = best_letter_0( freqs ) + if verbose: + print("Lettre la plus probable : «",best_letter,"» avec",freqs[best_letter],"occurences") + return best_letter + + +def best_letter_0( freqs ): + # Première clef dans le dictionnaire + best_letter = list(freqs.keys())[0] + for letter in freqs: + if freqs[letter] > freqs[best_letter]: + best_letter = letter + return best_letter + + +def best_letter_1( freqs ): + # On pourrait aussi trier les clefs du dictionnaire selon la valeur des éléments associés + sorted_letters = sorted( freqs.items(), key=operator.itemgetter(1) ) + # Une fois triés par ordre croissant, la lettre la plus probable est en dernière + best_letter = sorted_letters[-1][0] + return best_letter + + +def ask_correct(): + print("Est-ce correct ? [o/n]") + answer = input() + answer = answer.lower() + + if answer == 'o': + return True + else: + return False + + +def ask_partial_word( partial_word ): + is_correct = False + while not is_correct: + print("Entrez le nouveau mot partiel :") + new_word = input() + new_word = new_word.lower() + + print("«",new_word,"»") + is_correct = ask_correct() + + if len(new_word) != len(partial_word): + print("Le nombre de lettres ne correspond pas !") + is_correct = False + + return new_word + + +def play( partial_word, words ): + used_letters = set() + fails = 0 + + while fails < len( pendu.BOARDS_PIC ): + pendu.display( pendu.BOARDS_PIC, partial_word, fails ) + + words = compatible_words( words, partial_word, used_letters ) + print(len(words),"mots compatibles") + + # S'il ne reste qu'un mot à tester, + if len( words ) == 1: + # on le propose directement. + print("Je pense au mot «",words[0],"»") + return ask_correct() + + elif len(words) == 0: + print("Je ne connais pas ce mot.") + return False + + letter = guess_letter_frequency( words, used_letters ) + used_letters.add( letter ) + print("Je pense à la lettre : «",letter,"»") + + if ask_correct(): + partial_word = ask_partial_word( partial_word ) + else: + fails += 1 + + # Si c'est la dernière chance mais qu'il reste trop de mots à tester + if fails == len(pendu.BOARDS_PIC) and len(words) > 1: + # on tente au hasard + print("Je pense au mot «",random.choice(words),"»") + return ask_correct() + + if "_" not in partial_word: + return True + + return False + + +if __name__=="__main__": + + print("Entrez votre mot secret") + word_size = len( input() ) + partial_word = "_" * word_size + + words = pendu.filter_wordsize( pendu.download_dic( "http://nojhan.net/aapssfc/data/french_dictionary.utf8" ), word_size ) + + won = play( partial_word, words ) + + if won: + print("J'ai gagné :-)") + else: + print("J'ai perdu :-(") diff --git a/src/pendu_ordi_1_test.py b/src/pendu_ordi_1_test.py new file mode 100644 index 0000000..ae3824b --- /dev/null +++ b/src/pendu_ordi_1_test.py @@ -0,0 +1,74 @@ + +import random + +import pendu_humain as pendu +import pendu_ordi_1 as AI + +def auto_play( secret_word, words ): + used_letters = set() + fails = 0 + partial_word = "_" * len(secret_word) + + while fails < len( pendu.BOARDS_PIC ): + words = AI.compatible_words( words, partial_word, used_letters ) + + if len( words ) == 1: + return secret_word == words[0] + + elif len(words) == 0: + return False + + letter = AI.guess_letter_frequency( words, used_letters, False ) + used_letters.add( letter ) + + if letter in secret_word: + partial_word = pendu.process( letter, partial_word, secret_word ) + else: + fails += 1 + + if fails == len(pendu.BOARDS_PIC) and len(words) > 1: + return secret_word == random.choice(words) + + if "_" not in partial_word: + return True + + return False + + +if __name__=="__main__": + + games = 100 + + all_words = pendu.download_dic( "http://nojhan.net/aapssfc/data/french_dictionary.utf8" ) + + all_played = 0 + all_won = 0 + lost_on = [] + + # Essais des mots de tailles variables + for word_size in range(1,20): + words = pendu.filter_wordsize( all_words, word_size ) + print(len(words),"mots de taille",word_size,"...") + + # Joue sur quelques mots tirés au hasard + played = 0 + won = 0 + for r in range(games): + secret_word = random.choice( words ) + if auto_play( secret_word, words ): + won += 1 + else: + lost_on.append( secret_word ) + played += 1 + + print("\tvictoires :",won,"/",played) + all_won += won + all_played += played + + print("Probabilité de gagner :",all_won/float(all_played)) + if len(lost_on) > 0: + print("Perte sur les mots :\n",", ".join(lost_on)) + + # Exercice : essayer tous les mots de chaques tailles + # et estimer ainsi la probabilité réelle de gagner +