Galgenraten

Du hast eine Idee für ein Projekt?
Antworten
AndreaC
User
Beiträge: 5
Registriert: Samstag 18. Januar 2025, 13:07

Ich habe ein kleines Ratespiel programmiert und würde mich über Lob und Kritik freuen.

Code: Alles auswählen

from random import randint

def filter_word(instring):
    result = ""
    for character in instring:
        if character in "abcdefghijklmnopqrstuvwxyzäöüß":
            result += character
    return result

list_book = []
n = 0
with open("tomsawyers-deu.txt") as file:
    for line in file:
        n += 1
        if 36 <= n <= 3549:
            sentence = line.strip().split()
            for word in sentence:
                list_book.append(filter_word(word.lower()))
# print(len(list_book))

guess_word = ""
while not (4 <= len(guess_word) <= 12):
    guess_word = list_book[randint(0, len(list_book) -1)].lower()
list_book = []
# print(guess_word)

points = 0
tries = 0

def display():
    result = ""
    for letter in guess_word:
        if letter in actual:
            result += letter
        else:
            result += "_"
        result += " "
    print("\n" + result, 10*" ", "Versuche:", tries, "Punkte:", points)

def game_over():
    if len(correct) == len(good):
        print("\nDas Wort >>>", guess_word, "<<< ist richtig, Du hast gewonnen !!!")
    else:
        print("\nDu hast verloren, geraten werden sollte:", guess_word)
    exit()

actual = set ()
correct = set ()
good = set ()
for letter in guess_word:
    correct.add(letter)
# print(correct)

while len(good) != len(correct):
    display()
    check = input("\nGib einen Buchstaben ein: ").lower()
    if len(check) == 0:
        game_over()
    
    # double input, but not empty word
    if check in actual:
        if len(good) > 0:
            points -= 5
    # first time right letter
    elif check in correct:
        points += 10
        good.add(check)
    # wrong, but not empty word
    elif len(good) > 0:
        points -= 3
 
    if points < 0:
        game_over()
    tries +=1
    actual.add(check)
    # print("actual=", actual)
    # print("good=", good)

game_over()
Gleichzeitig wüßte ich gerne, ob ich meine Wörtersammlung nicht irgendwie anders abspeichern kann - ich meine das Lesen geht ja schnell. sieht aber komisch aus
Benutzeravatar
__blackjack__
User
Beiträge: 13808
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@AndreaC: Es wurde nach Kritik gefragt. 😇

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Insbesondere das mischen von Hauptprogramm und Funktionsdefinitionen ist unübersichtlich.

Funktionen bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben. Wenn die einfach so ”magisch” in der Funktion auf Variablen zugreifen, weiss man nicht welche Funktion mit welchen Variablen arbeitet solange man nicht alle Funktionen bis ins Detail kennt.

Beim öffnen von Textdateien sollte man immer explizit die Kodierung angeben.

`list_book` ist ein komischer Name. Das ist keine Funktion die Bücher listet. Und falls das `list` für den Datentyp stehen sollte: Grunddatentypen haben in Namen nichts verloren. Den Datentyp ändert man im Laufe der Programmentwicklung gerne mal in etwas spezialisierteres, und dann sind entweder Namen falsch und irreführend, oder man muss schauen welche Namen man alle ändern muss. Hier würde sich beispielsweise eine Menge (`set`) statt einer Liste eignen, denn Worte können in dem Buch ja mehrfach vorkommen. Im Moment hat das mit der Liste einfluss auf die Wahrscheinlichkeit mit der ein Wort ausgewählt wird. Soll das so?

Das mit dem `n` ist unnötig umständlich geschrieben. `n` wird zu früh definiert, und eigentlich kann man sich initialisieren und manuell hochzählen ganz sparen wenn man die `enumerate()`-Funktion verwendet:

Code: Alles auswählen

        for n, line in enumerate(lines, 1):
            if 36 <= n <= 3549:
                ...
Wenn man keine Zeilen nach Zeile 3549 mehr verarbeiten möchte, wäre es sinnvoll auch tatsächlich mit der Schleife aufzuhören (``break``). Und die ersten 36 Zeilen könnte man in einer zusätzlichen Schleife vor dieser überlesen. Oder man verwendet gleich `itertools.islice()` und spart sich dieses `n` und eigene Tests ganz.

Ein `strip()` ist vor einem `split()` ohne Argumente nicht notwending:

Code: Alles auswählen

In [194]: "   ham  spam  \n".split()
Out[194]: ['ham', 'spam']
Statt später solange Worte zu ziehen bis die Länge innerhalb bestimmter Grenzen liegt, wäre es besser die Worte ausserhalb dieser Grenzen gleich auszufiltern. Die spätere ``while``-Schleife ist nämlich nicht dagegen abgesichert zu einer Endlosschleife zu werden wenn keine passenden Worte vorhanden sind.

Wiederholtes ``+=`` mit Zeichenketten in einer Schleife ist ineffizient und sollte nicht gemacht werden. Man sammelt die Teilzeichenketten besser in einer Liste oder schreibt einen Generatorausdruck und setzt das Ergebnis mit der `join()`-Methode auf Zeichenketten zusammen.

Warum wird `list_book` nach der Wahl eines Wortes wieder an eine leere Liste gebunden? So komische Sachen zur ”Speicherverwaltung” (?) sollte man nicht machen. Das bisschen Speicher sollte eigentlich nicht auffallen, und ansonsten sollte man einfach sauber(er) programmieren statt solche Hacks einzubauen. Wenn das ein lokaler Name in einer Funktion zum einlesen der Daten wäre, dann würde der Name automatisch am Ende der Funktion verschwinden und Speicher freigegeben der freigegeben werden kann.

Wenn man `randrange()` statt `randint()` verwendet, braucht man nicht 1 von der Länge abziehen. Wenn man `random.choice()` verwendet, braucht man sich mit der Länge überhaupt nicht zu beschäftigen und es wird lesbarer.

Das `lower()` nach der Zufallswahl aus einer Liste mit Worten in Kleinbuchstaben kann man sich sparen.

`actual`, `correct`, und `good` sind als Namen nicht gut. Tatsächlich, korrekt, und gut *was*? Und warum tatsächlich?

Die Schleife um `correct` zu füllen kann man sich sparen wenn man `set()` gleich mit dem Wort aufruft.

Es sollte doch nicht nur die Länge von `good` und `correct` gleich sein, sondern auch der *Inhalt* der beiden Mengen.

`check` ist keine Funktion und prüft nichts, sollte also wohl eher `letter` oder `character` heissen.

`exit()` gibt's eigentlich gar nicht wenn man das nicht aus `sys` importiert. Aber die Funktion sollte man sowieso nicht einfach in anderen Funktionen ausser der Hauptfunktion verwenden um sich um einen sauberen Programmablauf zu drücken. Statt `game_over()` innerhalb der Schleife aufzurufen, sollte man die einfach abbrechen und das Programm bis zum Ende der Hauptfunktion laufen lassen.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import random
from itertools import islice

ALPHABET = "abcdefghijklmnopqrstuvwxyzäöüß"


def filter_word(text):
    return "".join(character for character in text if character in ALPHABET)


def display(secret_word, tried_letters, tries, points):
    result = " ".join(
        letter if letter in tried_letters else "_" for letter in secret_word
    )
    print("\n" + result, 10 * " ", "Versuche:", tries, "Punkte:", points)


def game_over(secret_word, secret_word_letters, correctly_guessed_letters):
    if secret_word_letters == correctly_guessed_letters:
        print(
            "\nDas Wort >>>",
            secret_word,
            "<<< ist richtig, Du hast gewonnen!!!",
        )
    else:
        print("\nDu hast verloren, geraten werden sollte:", secret_word)


def main():
    with open("tomsawyers-deu.txt", encoding="utf-8") as lines:
        book_words = []
        for line in islice(lines, 35, 3550):
            words = (filter_word(word.lower()) for word in line.split())
            book_words.extend(word for word in words if 4 <= len(word) <= 12)

    secret_word = random.choice(book_words)

    points = 0
    tries = 0
    secret_word_letters = set(secret_word)
    tried_letters = set()
    correctly_guessed_letters = set()
    while correctly_guessed_letters != secret_word_letters:
        display(secret_word, tried_letters, tries, points)
        letter = input("\nGib einen Buchstaben ein: ").lower()
        if not letter:
            break
        #
        # double input, but not empty word
        #
        if letter in tried_letters:
            if correctly_guessed_letters:
                points -= 5
        #
        # first time right letter
        #
        elif letter in secret_word_letters:
            points += 10
            correctly_guessed_letters.add(letter)
        #
        # wrong, but not empty word
        #
        elif correctly_guessed_letters:
            points -= 3

        if points < 0:
            break

        tries += 1
        tried_letters.add(letter)

    game_over(secret_word, secret_word_letters, correctly_guessed_letters)


if __name__ == "__main__":
    main()
Die drei Todfeinde des Programmieres:
Sonnenlicht, frische Luft und das unerträgliche Gebrüll der Vögel.
AndreaC
User
Beiträge: 5
Registriert: Samstag 18. Januar 2025, 13:07

Das mit dem encoding="utf-8" muß ich weglassen, da sonst eine Fehlermeldung wegen einem nicht lesbaren Zeichen 0xe4 kommt, das wahrscheinlich in der deutschen Version meines Buches drinne ist (ich habe den Tom Sawyer auch in Englisch aber jetzt nicht getestet).
Danke für die Korrekturen
nezzcarth
User
Beiträge: 1720
Registriert: Samstag 16. April 2011, 12:47

AndreaC hat geschrieben: Sonntag 19. Januar 2025, 20:56 da sonst eine Fehlermeldung wegen einem nicht lesbaren Zeichen 0xe4 kommt,
Der Fehler deutet darauf hin, dass die Eingabedatei in ISO 8859-1 (/latin1) oder Windows-1252 kodiert sein könnte (der Buchstabe ist ein ä). Entweder musst du das verifizieren und angeben oder die Datei nach utf8 konvertieren; weglassen sollte man die Kodierungsangabe nicht, da sonst der Betriebssystems-Default genommen wird, was nicht wirklich robust und nicht portierbar ist.
Benutzeravatar
__blackjack__
User
Beiträge: 13808
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@AndreaC: Genau deswegen sollte man die Kodierung angeben. Bei Deinem System funktioniert das ohne Angabe, aber wenn man Programm und Textdatei beispielsweise auf ein aktuelles Linux-System kopiert, wird es dort auf die Nase fallen, denn dort wird in der Regel UTF-8 ”geraten” wenn man keine Kodierung angibt.
Die drei Todfeinde des Programmieres:
Sonnenlicht, frische Luft und das unerträgliche Gebrüll der Vögel.
Benutzeravatar
grubenfox
User
Beiträge: 574
Registriert: Freitag 2. Dezember 2022, 15:49

Im Zweifel mal chardet auf die Datei ansetzen. Das war bei mir immer erfolgreich (aber ich kann mich auch nur an ein oder zwei Einsätze bisher erinnern) ;)
Aber im aktuellen Fall bin ich da bei nezzcarth und vermute auch mal ISO 8859-1 bzw. Windows-1252
AndreaC
User
Beiträge: 5
Registriert: Samstag 18. Januar 2025, 13:07

ich soll was dazuschreiben, damit es bei mir nicht geht,
weil wenn ich es weglasse und es dann läuft,
gefällt euch das nicht oder es widerspricht irgendwelchen selbsterdachten Gesetzen? ok.
ich will doch mein Buch verwenden, warum soll ich das für Lunix umwandeln?
Benutzeravatar
sparrow
User
Beiträge: 4452
Registriert: Freitag 17. April 2009, 10:28

@AndreaC: Es geht nicht darum, dass es jemanden gefällt. Im Moment funktioniert es zufällig. Aber so programmiert man nicht.
Du sollst auch nicht etwas dazu schreiben, damit es bei dir nicht geht, sondern du sollst die Kodierung der Datei angeben, damit es _überall_ läuft.

Erster Schritt: Das Encoding herausfinden. Zum Beispiel über hier genannte Tools. Notepad++ rät unter Windows auch ganz gut und man kann die Kodierugen in dem GUI durchprobieren.

Da du aber Windows verwendest und es dort ohne Angabe funktioniert ist die Kodierung, wie hier bereits geschrieben, wahrscheinlich latin1. Das muss dann als Encoding beim Öffnen angegeben werden.
Benutzeravatar
__blackjack__
User
Beiträge: 13808
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@AndreaC: Du sollst das auch nicht speziell für Linux umwandeln, das gleiche Problem besteht auch bei MacOS und sogar bei Windows wenn da nicht die gleichen Einstellungen wie auf Deinem System sind.

Und Du musst natürlich auch nichts ändern. Du hast halt nach Kritik gefragt, und nicht angegebene Kodierung ist halt so etwas das einem aus Erfahrung gerne mal um die Ohren fliegt wenn sich etwas an der Umgebung ändert. Kann beispielsweise sein, dass auch Windows irgendwann mal für alle auf UTF-8 oder UTF-16 umstellt in künftigen Versionen.
Die drei Todfeinde des Programmieres:
Sonnenlicht, frische Luft und das unerträgliche Gebrüll der Vögel.
nezzcarth
User
Beiträge: 1720
Registriert: Samstag 16. April 2011, 12:47

AndreaC hat geschrieben: Montag 20. Januar 2025, 22:15 ich soll was dazuschreiben, damit es bei mir nicht geht,
weil wenn ich es weglasse und es dann läuft,
gefällt euch das nicht oder es widerspricht irgendwelchen selbsterdachten Gesetzen? ok.
Du hast doch nach Verbesserungsvorschlägen gefragt. Es handelt sich nicht um ein selbsterdachtes Gesetz, sondern um einen Erfahrungswert, den dir z.B. auch pylint anstreichen würde (https://pylint.readthedocs.io/en/latest ... oding.html) basierend auf folgendem PEP https://peps.python.org/pep-0597/. Es kommt auch nicht die Python-Polizei bei dir vorbei, wenn du dich nicht dran hältst. Aber es ist nun mal Best Practice. Insofern finde ich die Aussage, wir hätten uns da irgendwas ausgedacht, unangemessen.
Benutzeravatar
__blackjack__
User
Beiträge: 13808
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@nezzcarth: Das war ja keine Aussage sondern eine Frage.
Die drei Todfeinde des Programmieres:
Sonnenlicht, frische Luft und das unerträgliche Gebrüll der Vögel.
AndreaC
User
Beiträge: 5
Registriert: Samstag 18. Januar 2025, 13:07

wie kann ich meine Wörter speichern, wenn ich mich von dem Buch unabhängig machen will?
AndreaC
User
Beiträge: 5
Registriert: Samstag 18. Januar 2025, 13:07

Code: Alles auswählen

with open("tomsawyers-deu.txt", encoding="latin-1") as lines:
        book_words = []
        for line in islice(lines, 35, 3550):
            words = (filter_word(word.lower()) for word in line.split())
            book_words.extend(word for word in words if 4 <= len(word) <= 12)
jetzt kommt keine Fehlermeldung mehr...
Benutzeravatar
__blackjack__
User
Beiträge: 13808
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@AndreaC: Für einzelne Worte wäre das einfachste wohl eine Textdatei mit einem Wort pro Zeile.
Die drei Todfeinde des Programmieres:
Sonnenlicht, frische Luft und das unerträgliche Gebrüll der Vögel.
Benutzeravatar
snafu
User
Beiträge: 6801
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wird hier als Ausgangsquelle tatsächlich der Roman verwendet (https://de.wikipedia.org/wiki/Die_Abent ... Tom_Sawyer) und werden dadurch viele Wörter mehrfach gespeichert? In diesem Fall würde ich das Filtern, Speichern, Suchen etc. von vornherein mit einem Set durchführen lassen. Sets sind schneller als Listen beim Zugriff auf einzelne Elemente (hier: Wörter). Und Sets enthalten jedes Element nur einmal, da Dubletten nicht aufgenommen werden. Das vermeidet somit Redundanzen beim Speichern der Wörter.

Beim letztgenannten Code kann man z. B. einfach das Wörtchen "set" vor die Klammern schreiben:

Code: Alles auswählen

words = set(filter_word(word.lower()) for word in line.split())
... und statt extend() auf einer Liste würde man update() auf einem Set mit allen Wörtern anwenden:

Code: Alles auswählen

book_words.update(word for word in words if 4 <= len(word) <= 12)
Oder in einem Rutsch (ungetestet):

Code: Alles auswählen

candidates = set(word for line in lines for word in line.split() if 4 <= len(word) <= 12)
words = set("".join(filter(str.isalpha, word)).lower() for word in candidates)
Oder als weitere Alternative direkt mit regulären Ausdrücken auf dem gesamten Text arbeiten. Das entsprechende Pattern würde die komplette Filter-Logik inklusive Anzahl der Zeichen ersetzen. Man müsste am Ende nur noch die Treffer in Kleinbuchstaben umwandeln.
Benutzeravatar
__blackjack__
User
Beiträge: 13808
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@snafu: Hatte ich ja im Grunde auch schon angesprochen, es dann aber nicht umgesetzt, weil dass das Verhalten ändert, also die Wahrscheinlichkeit mit der ein Wort ausgewählt wird im Verhältnis zur Häufigkeit des Wortes im Text. Deshalb hatte ich es erst einmal bei der Liste belassen und die Frage gestellt ob das Verhalten so gewollt ist.

@all: Übrigens rate ich mal es handelt sich um die alte Version der Textdatei von Projekt Gutenberg — da ist der Vorspann vom Projekt, der noch nicht zum Buchtext gehört, genau 36 Zeilen lang. Dann braucht man die Kodierung des Textes nicht mehr raten, die steht da drin: ISO-8859-1

Hier der Vorspann:

Code: Alles auswählen

The Project Gutenberg EBook of Die Abenteuer Tom Sawyers, by Mark Twain

This eBook is for the use of anyone anywhere at no cost and with
almost no restrictions whatsoever.  You may copy it, give it away or
re-use it under the terms of the Project Gutenberg License included
with this eBook or online at www.gutenberg.org


Title: Die Abenteuer Tom Sawyers

Author: Mark Twain

Translator: H. Hellwag

Release Date: October 3, 2009 [EBook #30165]

Language: German

Character set encoding: ISO-8859-1

*** START OF THIS PROJECT GUTENBERG EBOOK DIE ABENTEUER TOM SAWYERS ***




Produced by Jens Sadowski




Transcriber's Note: This book was transcribed from the edition by
Verlag von Otto Hendel, Halle a. d. Saale, 1900.
Text that was s p a c e d - o u t in the original has been changed to use _italics_.
Double low quotation marks have been encoded as ",," and single low
quotation marks as ",", respectively.
Die drei Todfeinde des Programmieres:
Sonnenlicht, frische Luft und das unerträgliche Gebrüll der Vögel.
Benutzeravatar
snafu
User
Beiträge: 6801
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Eigentlich kann man die ``candidates`` auch als Generator schreiben und dann in der zweiten Zeile ``(...) for word in set(candidates)`` sagen. Letzteres würde ich dann doch als LC schreiben, damit ein zufälliger Zugriff via ``random`` möglich ist.

Dass Zugriff auf einzelne Elemente beim Set möglich ist, war natürlich Quatsch. Ich schiebe das mal auf die Uhrzeit. Für Index-Zugriffe sind Listen die geeignete Datenstruktur.
Antworten