Zahlenraten in Konsole

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
pyth0ndev
User
Beiträge: 8
Registriert: Montag 28. Januar 2013, 19:13

Hallo Leute,

ich wollte mal ein paar erfahrenere Programmierer unter euch fragen, ob es sinnvoll ist, ein seinen Hauptcode innerhalb einer Whileschleife ausführen zu lassen. Meine Frage ergibt sich aus einem aktuellen Script, indem man per Konsole eine Zahl erraten muss. Hier eine verkürzte Version zur Frage:

Code: Alles auswählen

# functions
def weiter():
        return True

def stop():
        return False

# main
weiterSpiel = weiter()

while weiterSpiel:
        
        # ...sonstiger Code
                
            
            option = str(input("Tippe ~w~ um weiterzuspielen oder ~b~ zum beenden: "))
                
            if option == "w":
                    weiterSpiel = weiter()
            else option == "b":
                    weiterSpiel = stop()
Ist diese Methode sinnvoll oder gibt es bessere Varianten?
Zuletzt geändert von pyth0ndev am Montag 18. März 2013, 16:32, insgesamt 2-mal geändert.
webspider
User
Beiträge: 485
Registriert: Sonntag 19. Juni 2011, 13:41

So funktionieren im Grunde genommen Spiele und GUIs. Schöner kann man es aber schon machen. Für eine Endlosschleife reicht ein ``while True:`` aus, mithilfe von ``break`` kann man die Schleife verlassen.
pyth0ndev
User
Beiträge: 8
Registriert: Montag 28. Januar 2013, 19:13

break und continue kenne ich, jedoch kann ich damit nur die Schleife beenden in der ich mich gerade befinde, jedoch habe ich noch eine zweite für ein Optionsmenü eingebaut:

Code: Alles auswählen

import random

# functions
def weiter():
        return True

def stop():
        return False

# main
print("Errate eine zufällige Nummer zwischen 0 und 100. Viel Glück!\n")

weiterSpiel = weiter()

while weiterSpiel:
        
        geheimeZahl = random.randrange(0,101)
        eingabe = 0
        i = 0
        
        while eingabe != geheimeZahl:
               
                eingabe = int(input("Raten Sie: "))
        
                if eingabe < geheimeZahl:
                        print("Zu klein")                       
        
                elif eingabe > geheimeZahl:
                        print("Zu gross")
        
                i = i + 1
                
        print(" ")
        if i == 1:
                print("Super, Sie haben in nur einem Versuch geschafft.")
        else:
                print("Super, Sie haben es in ", i, "Versuchen geschafft.")
        print(" ")

        while True:
                optionEnde = str(input("Tippe ~w~ um weiterzuspielen oder ~b~ zum beenden: "))
                print(" ")
                
                if optionEnde == "w":
                        weiterSpiel = weiter()
                        break
                elif optionEnde == "b":              
                        weiterSpiel = stop()
                        break
                else:
                        continue
        
Die Schleife vom Menü ist damit abgebrochen, aber die Hauptschleife und damit das Programm läuft noch.
Zuletzt geändert von Hyperion am Sonntag 17. März 2013, 14:42, insgesamt 1-mal geändert.
Grund: Code auf Python-Code umgestellt
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Bitte nutze doch die Python-Code-Tags bzw. das Attribut ``python`` im Code-Tags.

Du solltest das alles besser in Funktionen unterteilen! Was sucht denn das Menü *in* der Hauptschleife des Programms? Dadurch erhältst Du auf Dauer endlos langen Spaghetti-Code, der auf Dauer nicht mehr wartbar bleibt.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@pyth0ndev: Die Funktionen `weiter()` und `stop()` sind reichlich überflüssig. Stattdessen sollte der Hauptcode besser in einer Funktion verschwinden. Also keinen Kommentar ``# main`` sondern eine Funktion mit dem Namen.

Die innere ``while``-Schleife von der Du sprichst, könnte auch in eine Funktion ausgelagert werden, die dann `True` oder `False` zurück gibt, je nach dem was der Benutzer eingegeben hat. Dann kannst Du damit den Tipp von webspider umsetzen und je nach Ergebnis der Funktion die Hauptschleife mit ``break`` verlassen.

Das manuelle hochzählen von `i` kann man mit `itertools.count()` und einer ``for``-Schleife ersetzen. Dann braucht man `eingabe` auch nicht vor der Schleife an irgendeinen Wert binden, der eigentlich gar nicht verwendet wird. Beziehungsweise ist der im Moment verwendete Wert ungünstig. Überleg mal warum. Diese Schleife dann auch wieder mit einem ``break`` verlassen.

In der letzten Schleife ist der ``else``-Zweig mit dem ``continue`` sinnfrei. Das passiert an der Stelle sowieso. Ebenso sinnfrei ist das Umwandeln vom Rückgabewert von `input()` in eine Zeichenkette, weil das bereits eine Zeichenkette *ist*. Die Zeichenkette mit einem Leerzeichen bei den `print()`-Aufrufen, die nur eine Leerzeile ausgeben sollen ist auch überflüssig. Einfach leer lassen die Klammern.

Dann ist die Einrückung zu weit — Konvention sind vier Leerzeichen pro Ebene und die Namen halten sich nicht alle an den Style Guide for Python Code.

Statt `randrange()` könnte man `randint()` verwenden und die beiden Grenzen als Konstanten definieren. Dann kann man die sowohl für die Ausgabe der Aufgabe für den Benutzer als auch für das ziehen einer Zahl direkt verwenden und ganz einfach anpassen, ohne dass man die Werte an mindestens zwei Stellen im Programm ändern muss.

Benutzer geben gerne mal etwas falsches ein — darum könnte man sich auch kümmern. Sowohl wenn der Benutzer etwas anderes als eine Zahl eingibt, als auch wenn eine eingegebene Zahl nicht im passenden Bereich ist. Da wären für den Test die Konstanten wieder nützlich.
pyth0ndev
User
Beiträge: 8
Registriert: Montag 28. Januar 2013, 19:13

Danke erstmal für die schnelle und ausführliche Hilfe. Sieht erstmal nach einem großen Berg an Kritik aus, mit dem sich der Code verbessern lässt, aber Übung macht bekanntlich den Meister.

Ich versuche den Code nochmal mit mithilfe Eurer Ideen zur Verbesserung zu überarbeiten. Sollten Schwierigkeiten auftreten, werde ich mich wieder melden ;)

Und Danke nochmals für die großartige Unterstützung.
pyth0ndev
User
Beiträge: 8
Registriert: Montag 28. Januar 2013, 19:13

Ich habe jetzt das Programm in Funktionen unterteilt, jedoch weiß kann ich mit return nicht break bzw. continue zurückzugeben, um die while-Schleife zu beenden

Code: Alles auswählen

# imports
from random import *
import itertools

# functions
def random():
    global gesucht
    gesucht = randint(0,101)
    return gesucht

def count(): # Darf bei Falscheingabe nicht weiter zählen!
    counter = itertools.count(1)
    for i in counter:
        break
        
    print('==================================================')
    if i == 1:
        print('Es war ein Versuch nötig.')
    else:
        print(i, ' Versuche Waren nötig.')

def check(zahl):
    if zahl == gesucht:
        print('Sie wissen die gesuchte Zahl.')
        return False
    elif zahl < 0 or zahl > 100: # Counter darf nicht weiter zählen!
        print(zahl, 'Liegt nicht zwischen 0 und 100.')
    elif zahl < gesucht:
        print('Die Zahl ist größer.')
        return True
    elif zahl > gesucht:
        print('Die Zahl ist kleiner.')
        return True
    else:
        print('Geben Sie eine Zahl ein!')
        # Funktionierende Fehlermeldung fehlt // Schleife geht weiter // Bei return False würde Sie abgebrochen werden

def menu(menuEingabe): 
    if menuEingabe == 'w':
        return True
    elif menuEingabe == 'b':
        return False
    # Funktionierende Fehlermeldung fehlt

def main():
    while True:   
        eingabe = int(input('Geben Sie eine Zahl ein: '))

        if check(eingabe) == False:
            break
        else:
            continue

# ---   Hauptprogramm   --- #
print("Errate eine zufällige Nummer zwischen 0 und 100. Viel Glück!\n")
while True:
    random()
    main()
    count()

    print()
    menuEingabe = input('Tippe ~w~ um weiterzuspielen oder ~b~ zum beenden: ')
    print()
    
    if menu(menuEingabe) == False:
        break
    elif menu(menuEingabe) == True:
        continue  
Zuletzt geändert von pyth0ndev am Montag 18. März 2013, 16:49, insgesamt 3-mal geändert.
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

break bzw. continue kann man nicht zurückgeben. Du kannst allerdings für break z.B. True und für continue False zurückgeben, und dann je nachdem in main() entscheiden, wie fortgefahren werden soll.
pyth0ndev
User
Beiträge: 8
Registriert: Montag 28. Januar 2013, 19:13

Ich habe jetzt meinen Code soweit es mir möglich war, überarbeitet. Jetzt weiß ich nur noch nicht, wie ich den Counter richtig einbauen kann. Zudem kann ich die Falscheingaben in durch die Fehlerausgabe nicht beheben. Dabei soll eine Fehler-Meldung ausgegeben werden, jedoch darf der Counter in diesem Falle nicht weiterzählen.

Habt Ihr sonst noch etwas am Code auszusetzen? Danke für Eure Hilfe im Vorraus :)

Code: Alles auswählen

# imports
from random import *
import itertools

# functions
def random():
    global gesucht
    gesucht = 5#randint(0,101)
    return gesucht

def count(): # Darf bei Falscheingabe nicht weiter zählen!
    counter = itertools.count(1)
    for i in counter:
        break
        
    print('==================================================')
    if i == 1:
        print('Es war ein Versuch nötig.')
    else:
        print(i, ' Versuche Waren nötig.')

def check(zahl):
    if zahl == gesucht:
        print('Sie wissen die gesuchte Zahl.')
        return False
    elif zahl < 0 or zahl > 100: # Counter darf nicht weiter zählen!
        print(zahl, 'Liegt nicht zwischen 0 und 100.')
    elif zahl < gesucht:
        print('Die Zahl ist größer.')
        return True
    elif zahl > gesucht:
        print('Die Zahl ist kleiner.')
        return True
    else:
        print('Geben Sie eine Zahl ein!')
        # Funktionierende Fehlermeldung fehlt // Schleife geht weiter // Bei return False würde Sie abgebrochen werden

def menu(menuEingabe): 
    if menuEingabe == 'w':
        return True
    elif menuEingabe == 'b':
        return False
    # Funktionierende Fehlermeldung fehlt

def main():
    while True:   
        eingabe = int(input('Geben Sie eine Zahl ein: '))

        if check(eingabe) == False:
            break
        else:
            continue

# ---   Hauptprogramm   --- #
print("Errate eine zufällige Nummer zwischen 0 und 100. Viel Glück!\n")
while True:
    random()
    main()
    count()

    print()
    menuEingabe = input('Tippe ~w~ um weiterzuspielen oder ~b~ zum beenden: ')
    print()
    
    if menu(menuEingabe) == False:
        break
    elif menu(menuEingabe) == True:
        continue
    else:
        # Programm macht setzt trotz Falscheingabe die Schleife fort     
BlackJack

@pyth0ndev: Ich versuche gerade einzelne Verbesserungsvorschläge zu formulieren, mir fällt aber irgendwie nur einer ein: Komplett wegwerfen und neu anfangen. Die Aufteilung auf Funktionen ist *so* nicht sinnvoll. Das funktioniert doch alles hinten und vorne nicht.

Funktionen sollten abgeschlossene Teilaufgaben isoliert lösen. Dabei kann man Argumente übergeben und Rückgabewerte zurück geben. `random()` verletzt das durch ``global`` (das Schlüsselwort bitte ganz schnell vergessen) und der Rückgabewert wird überhaupt nicht verwendet.

`count()` enthält eine Schleife die nur einmal durchlaufen wird, weil alles was drin steht ein ``break`` ist. Da hätte man auch gleich ``i = 1`` schreiben können. In dieser Schleife müssten die Werte erfragt werden und so weiter.

`check()` prüft nicht nur sondern gibt auch Text aus und Ergebnisse zurück und zwar `True`, `False`, oder `None`. Das heisst `None` eigentlich nicht, denn der letzte ``else``-Zweig wird niemals ausgeführt werden, weil alle Möglichkeiten von den ``elif``\s davor schon ausgeschöpft werden. Ausserdem kann beim einzigen Aufruf gar nichts übergeben werden was keine Zahl ist, denn dann hätte die Umwandlung mit `int()` ja schon nicht geklappt. Es werden ausserdem Prüfungen auf gültige Eingaben mit Prüfungen, welche die Spiellogik betreffen vermischt. Daraus resultiert dann auch das Problem mit dem Zähler. Solange der Benutzer keine gültige Eingabe gemacht hat, braucht man mit dem Spiel auch nicht weitermachen (wenn ungültige Eingaben nicht als Versuch zählen sollen).

`menu()` ist als Funktion unsinnig. Ausserdem ist der Name unpassend.

`main()` wäre die Hauptfunktion, also da wo der Hauptprogramm-Code enthalten ist — der steht aber nach dem Kommentar dass es sich um das Hauptprogramm handelt auf Modulebene.

Beide Verwendungen von ``continue`` sind sinnfrei, weil dass an der Stelle sowieso passieren würde.

Explizite Vergleiche mit den `bool`-Literalen `True` und `False` sind schlechter Stil, weil vollkommen überflüssig. Man hat ja sowieso schon einen Wahrheitswert und beim Vergleich kommt nur *wieder* ein Wahrheitswert heraus. Einzig die Negation mit ``not`` macht an der Stelle wirklich sinn, wenn man auf das Gegenteil vom vorhandenen Wahrheitswert testen möchte.
Antworten