Anfängerproblem mit While-Schleife

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
kartodis
User
Beiträge: 3
Registriert: Mittwoch 8. Januar 2020, 12:00

Mittwoch 8. Januar 2020, 12:10

Hallo zusammen,

wie ihr gleich sehen werdet, bin ich noch ganz frisch in Sachen Python. Ich habe in jungen Jahren mal ein paar erste Erfahrungen mit Basic gemacht, aber in den letzten 30 Jahren dann nichts mehr. Jetzt beginne ich gerade mit Python und stoße schon auf ein erstes Problem. Ich hoffe ich bin hier richtig mit meiner Frage. Ansonsten bitte ich kurz um einen Hinweis, wohin ich mich wenden kann.

Problem:

Ich möchte für ein Kartenspiel ein kleines Programm schreiben, mit dem ich die Punkte der beiden Spieler, Runde für Runde addieren möchte. Der Spieler, welcher am Ende zuerst die 100 Punkte Marke erreicht hat, hat verloren.

Script

Code: Alles auswählen

# Programm zum Zusammenzählen von Punkten in einem Kartenspiel

# Eingabe der Namen

print("Bitte geben Sie den Namen des ersten Spielers ein:")
s1 = input()

print("Bitte geben Sie den Namen des zweiten Spielers ein:")
s2 = input()

# Startwerte
p1 = 0
p2 = 0 

# Schleife

while p1 <= 100 or p2 <= 100:
    print("Wie viele Punkte hat", s1, "in dieser Runde gemacht?")
    pr1 = input()
    pri1 =int(pr1)
    
    print("Wie viele Punkte hat", s2, "in dieser Runde gemacht?")
    pr2 = input()
    pri2 =int(pr2)
    
    p1 = p1 + pri1 
    p2 = p2 + pri2 
    
    print(s1, "hat insgesamt", p1," Punkte")
    print(s2, "hat insgesamt", p2," Punkte")
    
print("Das Spiel ist beendet.")
Frage:

Das Script funktioniert soweit, worauf ich als blutiger Anfänger schon mal stolz bin. Allerdings stoppt die Schleife nicht beim Erreichen von 100 Punkten. Es wird wohl an der Einleitung der While-Schleife und dem Befehl "or" liegen. Wie aber genau, finde ich leider nicht raus.

Vielen Dank schon mal vorab für die Hilfe.

Schöne Grüße

Frank
Zuletzt geändert von kartodis am Mittwoch 8. Januar 2020, 12:35, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 8091
Registriert: Mittwoch 14. Oktober 2015, 14:29

Mittwoch 8. Januar 2020, 12:21

Bitte in Zukunft Code-Tags verwenden, damit man die in Python relevanten Einrückungen erkennen kann. Dazu im vollstaendigen Editor den </>-Knopf druecken, nachdem man den Quelltext markiert hat.

Und zu deinem Problem: ja, das or ist falsch. Denn so stoppt es ja nur, wenn *beide* Punktestaende ueber 100 sind. Es muss stattdessen ein and sein.
kartodis
User
Beiträge: 3
Registriert: Mittwoch 8. Januar 2020, 12:00

Mittwoch 8. Januar 2020, 12:40

__deets__ hat geschrieben:
Mittwoch 8. Januar 2020, 12:21
Bitte in Zukunft Code-Tags verwenden, damit man die in Python relevanten Einrückungen erkennen kann. Dazu im vollstaendigen Editor den </>-Knopf druecken, nachdem man den Quelltext markiert hat.

Und zu deinem Problem: ja, das or ist falsch. Denn so stoppt es ja nur, wenn *beide* Punktestaende ueber 100 sind. Es muss stattdessen ein and sein.
Vielen Dank für die Antwort. Hab den Code jetzt entsprechend mit den Tags versehen.

"and" war für mich nicht logisch, weil ich das Spiel ja beenden möchte, wenn entweder Spieler 1 ODER Spieler 2 die 100 Punkte erreicht. "and" klang für mich so, als müsste sowohl Spieler 1 als auch Spieler 2 die 100 erreicht haben.

Wieder was gelernt. Danke schön!
__deets__
User
Beiträge: 8091
Registriert: Mittwoch 14. Oktober 2015, 14:29

Mittwoch 8. Januar 2020, 13:01

Das ist aber nicht wirklich was du geschrieben hast. Was du geschrieben hast ist

WAEHREND spieler 1 weniger als 100 Punkte hat ODER spieler 2 weniger als 100 Punkte hat:

Und das ist dann ja auch bei rausgekommen.

Wenn du das mit oder beibehalten willst, dann kannst du das nach "de morgan" umformen, und aus dem UND ein ODER machen:

WAEHREND NICHT(spieler 1 mehr als 100 Punkte ODER spieler 2 mehr als 100 Punkte hat):

Das doofe daran ist das NICHT, das auch geklammert werden muss. Was fuer die wirklich direkte Umsetzung noetig waere, und in anderen Sprachen auch existiert, waere ein until/BIS

BIS spieler 1 mehr als 100 Punkte ODER spieler 2 mehr als 100 Punkte hat:

Das waere dann

Code: Alles auswählen

until p1 > 100 or p2 > 100:
     ...
Fuer if waere das uebrigens "unless". Aber beides gibt es in Python nicht, Ruby aber kennt zumindest unless. Ich persoenlich mache aber lieber das not/NICHT, denn dann muss ich mir nur zwei Kontrollfluss-Schluesselwoerter merken.
Zuletzt geändert von __deets__ am Mittwoch 8. Januar 2020, 15:02, insgesamt 1-mal geändert.
Grund: WENN zu WAEHREND
kartodis
User
Beiträge: 3
Registriert: Mittwoch 8. Januar 2020, 12:00

Mittwoch 8. Januar 2020, 13:18

Ein voller Denkfehler meinerseits. Mein Kopf ging davon aus, dass die Schleife den Abbruch des Programms beinhaltet. Es ist ja aber genau umgedreht. So lange beide Spieler unter 101 bleiben, läuft die Schleife weiter.

Danke für deine ausführliche Erklärung.
Benutzeravatar
__blackjack__
User
Beiträge: 5993
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Donnerstag 9. Januar 2020, 10:41

@kartodis: BASIC vor 30 Jahren auf dem PC könnte ja noch GW-BASIC gewesen sein. Das hat ja schon nicht mehr die Einschränkung, dass bei Namen nur die ersten beiden Zeichen signifikant sind, aber es war aus verschiedenen Gründen unüblich das auszunutzen. Also hier die gute Nachricht: Man ist bei Python nicht mehr darauf beschränkt ein oder zweibuchstabige Namen zu verwenden! Wenn man Spieler meint sollte man `spieler` schreiben und nicht `s` und bei Punkten `punkte` und nicht `p`.

Kommentare sollten dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da ja bereits als Code, sondern *warum* er das *so* macht, sofern das nicht offensichtlich ist. Insbesondere der ``# Schleife``-Kommentar vor der ``while``-Schleife ist überflüssig, aber auch ``# Eingabe der Namen`` ist etwas was dem Leser nichts sagt was er da nicht auch am Code ablesen kann. Der erste Kommentar würd einen guten Docstring für das Modul abgeben.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst, und nur aufgerufen wird wenn das Modul als Programm ausgeführt wird, aber nicht wenn es importiert wird.

Ein paar von den kurzen kryptischen Namen wird man los wenn man nicht jedes noch so kleine Zwischenergebnis an einen Namen bindet.

”Magischen” Zahlen sollte man einen Konstantennamen spendieren, damit der Leser weiss was die bedeuten und damit man sie einfacher ändern kann.

Erster Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
"""
Programm zum Zusammenzählen von Punkten in einem Kartenspiel
"""

GEWINNPUNKTZAHL = 100

def main():
    print("Bitte geben Sie den Namen des ersten Spielers ein:")
    spieler_1 = input()
    print("Bitte geben Sie den Namen des zweiten Spielers ein:")
    spieler_2 = input()

    punkte_1 = punkte_2 = 0
    while punkte_1 <= GEWINNPUNKTZAHL and punkte_2 <= GEWINNPUNKTZAHL:
        print("Wie viele Punkte hat", spieler_1, "in dieser Runde gemacht?")
        punkte_1 += int(input())

        print("Wie viele Punkte hat", spieler_2, "in dieser Runde gemacht?")
        punkte_2 += int(input())

        print(spieler_1, "hat insgesamt", punkte_1, " Punkte.")
        print(spieler_2, "hat insgesamt", punkte_2, " Punkte.")

    print("Das Spiel ist beendet.")


if __name__ == "__main__":
    main()
Was hier noch unschön ist sind die Codewiederholungen und die nummerierten Namen. Nummerierte Namen sind in der Regel ein Warnzeichen das man sich entweder bessere Namen ausdenken will, oder das man gar keine einzelnen Namen haben will, sondern eine Datenstruktur. Oft eine Liste. Wenn man statt der nummerierten Namen Listen für Spielernamen und Punkte verwendet, dann wird man nicht nur die Codewiederholungen los, sondern kann das auch sehr einfach auf drei oder mehr Spieler erweitern in dem man einfach nur eine Konstante ändert, welche die Spieleranzahl enthält.

Code: Alles auswählen

#!/usr/bin/env python3
"""
Programm zum Zusammenzählen von Punkten in einem Kartenspiel
"""

SPIELERANZAHL = 2
GEWINNPUNKTZAHL = 100


def main():
    spielernamen = [
        input(f"Bitte geben Sie den Namen des {i}. Spielers ein:\n")
        for i in range(1, SPIELERANZAHL + 1)
    ]
    spielerpunkte = [0] * SPIELERANZAHL

    while all(punkte <= GEWINNPUNKTZAHL for punkte in spielerpunkte):
        for i in range(SPIELERANZAHL):
            print(
                "Wie viele Punkte hat",
                spielernamen[i],
                "in dieser Runde gemacht?",
            )
            spielerpunkte[i] += int(input())

        for name, punkte in zip(spielernamen, spielerpunkte):
            print(name, "hat insgesamt", punkte, " Punkte.")

    print("Das Spiel ist beendet.")


if __name__ == "__main__":
    main()
An der Lösung ist noch unschön, das zusammengehörende Daten — Namen und Punkte — nicht zusammengefasst sondern in zwei parallelen Datenstrukturen gespeichert sind. Das ist Stand GW-BASIC, oder was die meisten Heimcomputer vor 30 Jahren gemacht haben. 1991 kam dann mit DOS 5 nicht mehr GW-BASIC sondern QBasic und das hatte die Möglichkeit eigene Datentypen aus den vorhandenen zusammen zu setzen. Das sind in Python Klassen. Was ich auch noch ändern würde ist die Sprache für die Namen, denn im deutschen kommt es leider zu häufig vor das Einzahl und Mehrzahl das gleiche Wort benutzen. Bei `spieler` weiss man so nicht ob das *ein* Spieler ist, oder ein Container mit *allen* Spielern. Bei `player` vs. `players` ist das eindeutiger.

Code: Alles auswählen

#!/usr/bin/env python3
"""
Programm zum Zusammenzählen von Punkten in einem Kartenspiel
"""

PLAYER_COUNT = 2
WINNING_POINT_COUNT = 100


class Player:
    def __init__(self, name):
        self.name = name
        self.points = 0


def main():
    players = [
        Player(input(f"Bitte geben Sie den Namen des {i}. Spielers ein:\n"))
        for i in range(1, PLAYER_COUNT + 1)
    ]

    while all(player.points <= WINNING_POINT_COUNT for player in players):
        for player in players:
            print(
                "Wie viele Punkte hat", player.name, "in dieser Runde gemacht?"
            )
            player.points += int(input())

        for player in players:
            print(player.name, "hat insgesamt", player.points, " Punkte.")

    print("Das Spiel ist beendet.")


if __name__ == "__main__":
    main()
In QBasic würde das beispielsweise so aussehen:

Code: Alles auswählen

DECLARE FUNCTION WirHabenEinenGewinner% ()

CONST Spieleranzahl = 2, Gewinnpunktzahl = 100

TYPE SpielerTyp
  Spielername AS STRING * 80
  Punkte AS INTEGER
END TYPE

DIM SHARED Spieler(Spieleranzahl) AS SpielerTyp

DIM i AS INTEGER, Punkte AS INTEGER

FOR i = 1 TO Spieleranzahl
  PRINT "Bitte geben sie den Namen des "; STR$(i) + ". Spielers ein:";
  INPUT " ", Spieler(i).Spielername
NEXT

DO UNTIL WirHabenEinenGewinner
  FOR i = 1 TO Spieleranzahl
    PRINT "Wie viele Punkte hat "; RTRIM$(Spieler(i).Spielername);
    PRINT " in dieser Runde gemacht:";
    INPUT " ", Punkte
    Spieler(i).Punkte = Spieler(i).Punkte + Punkte
  NEXT
  FOR i = 1 TO Spieleranzahl
    PRINT "Spieler "; RTRIM$(Spieler(i).Spielername); " hat insgesamt";
    PRINT Spieler(i).Punkte; "Punkte."
    PRINT
  NEXT
LOOP

PRINT "Das Spiel ist beendet!"

FUNCTION WirHabenEinenGewinner%
  DIM i AS INTEGER, r AS INTEGER

  FOR i = 1 TO Spieleranzahl
    IF Spieler(i).Punkte > Gewinnpunktzahl THEN
      r = -1
      EXIT FOR
    END IF
  NEXT
  WirHabenEinenGewinner = r
END FUNCTION
long long ago; /* in a galaxy far far away */
Blunti
User
Beiträge: 2
Registriert: Montag 20. Januar 2020, 09:10

Freitag 31. Januar 2020, 13:21

__blackjack__ hat geschrieben:
Donnerstag 9. Januar 2020, 10:41
@kartodis: BASIC vor 30 Jahren auf dem PC könnte ja noch GW-BASIC gewesen sein. Das hat ja schon nicht mehr die Einschränkung, dass bei Namen nur die ersten beiden Zeichen signifikant sind, aber es war aus verschiedenen Gründen unüblich das auszunutzen. Also hier die gute Nachricht: Man ist bei Python nicht mehr darauf beschränkt ein oder zweibuchstabige Namen zu verwenden! Wenn man Spieler meint sollte man `spieler` schreiben und nicht `s` und bei Punkten `punkte` und nicht `p`.

Kommentare sollten dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da ja bereits als Code, sondern *warum* er das *so* macht, sofern das nicht offensichtlich ist. Insbesondere der ``# Schleife``-Kommentar vor der ``while``-Schleife ist überflüssig, aber auch ``# Eingabe der Namen`` ist etwas was dem Leser nichts sagt was er da nicht auch am Code ablesen kann. Der erste Kommentar würd einen guten Docstring für das Modul abgeben.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst, und nur aufgerufen wird wenn das Modul als Programm ausgeführt wird, aber nicht wenn es importiert wird.

Ein paar von den kurzen kryptischen Namen wird man los wenn man nicht jedes noch so kleine Zwischenergebnis an einen Namen bindet.

”Magischen” Zahlen sollte man einen Konstantennamen spendieren, damit der Leser weiss was die bedeuten und damit man sie einfacher ändern kann.

Erster Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
"""
Programm zum Zusammenzählen von Punkten in einem Kartenspiel
"""

GEWINNPUNKTZAHL = 100

def main():
    print("Bitte geben Sie den Namen des ersten Spielers ein:")
    spieler_1 = input()
    print("Bitte geben Sie den Namen des zweiten Spielers ein:")
    spieler_2 = input()

    punkte_1 = punkte_2 = 0
    while punkte_1 <= GEWINNPUNKTZAHL and punkte_2 <= GEWINNPUNKTZAHL:
        print("Wie viele Punkte hat", spieler_1, "in dieser Runde gemacht?")
        punkte_1 += int(input())

        print("Wie viele Punkte hat", spieler_2, "in dieser Runde gemacht?")
        punkte_2 += int(input())

        print(spieler_1, "hat insgesamt", punkte_1, " Punkte.")
        print(spieler_2, "hat insgesamt", punkte_2, " Punkte.")

    print("Das Spiel ist beendet.")


if __name__ == "__main__":
    main()
Was hier noch unschön ist sind die Codewiederholungen und die nummerierten Namen. Nummerierte Namen sind in der Regel ein Warnzeichen das man sich entweder bessere Namen ausdenken will, oder das man gar keine einzelnen Namen haben will, sondern eine Datenstruktur. Oft eine Liste. Wenn man statt der nummerierten Namen Listen für Spielernamen und Punkte verwendet, dann wird man nicht nur die Codewiederholungen los, sondern kann das auch sehr einfach auf drei oder mehr Spieler erweitern in dem man einfach nur eine Konstante ändert, welche die Spieleranzahl enthält.

Code: Alles auswählen

#!/usr/bin/env python3
"""
Programm zum Zusammenzählen von Punkten in einem Kartenspiel
"""

SPIELERANZAHL = 2
GEWINNPUNKTZAHL = 100


def main():
    spielernamen = [
        input(f"Bitte geben Sie den Namen des {i}. Spielers ein:\n")
        for i in range(1, SPIELERANZAHL + 1)
    ]
    spielerpunkte = [0] * SPIELERANZAHL

    while all(punkte <= GEWINNPUNKTZAHL for punkte in spielerpunkte):
        for i in range(SPIELERANZAHL):
            print(
                "Wie viele Punkte hat",
                spielernamen[i],
                "in dieser Runde gemacht?",
            )
            spielerpunkte[i] += int(input())

        for name, punkte in zip(spielernamen, spielerpunkte):
            print(name, "hat insgesamt", punkte, " Punkte.")

    print("Das Spiel ist beendet.")


if __name__ == "__main__":
    main()
An der Lösung ist noch unschön, das zusammengehörende Daten — Namen und Punkte — nicht zusammengefasst sondern in zwei parallelen Datenstrukturen gespeichert sind. Das ist Stand GW-BASIC, oder was die meisten Heimcomputer vor 30 Jahren gemacht haben. 1991 kam dann mit DOS 5 nicht mehr GW-BASIC sondern QBasic und das hatte die Möglichkeit eigene Datentypen aus den vorhandenen zusammen zu setzen. Das sind in Python Klassen. Was ich auch noch ändern würde ist die Sprache für die Namen, denn im deutschen kommt es leider zu häufig vor das Einzahl und Mehrzahl das gleiche Wort benutzen. Bei `spieler` weiss man so nicht ob das *ein* Spieler ist, oder ein Container mit *allen* Spielern. Bei `player` vs. `players` ist das eindeutiger.

Code: Alles auswählen

#!/usr/bin/env python3
"""
Programm zum Zusammenzählen von Punkten in einem Kartenspiel
"""

PLAYER_COUNT = 2
WINNING_POINT_COUNT = 100


class Player:
    def __init__(self, name):
        self.name = name
        self.points = 0


def main():
    players = [
        Player(input(f"Bitte geben Sie den Namen des {i}. Spielers ein:\n"))
        for i in range(1, PLAYER_COUNT + 1)
    ]

    while all(player.points <= WINNING_POINT_COUNT for player in players):
        for player in players:
            print(
                "Wie viele Punkte hat", player.name, "in dieser Runde gemacht?"
            )
            player.points += int(input())

        for player in players:
            print(player.name, "hat insgesamt", player.points, " Punkte.")

    print("Das Spiel ist beendet.")


if __name__ == "__main__":
    main()
In QBasic würde das beispielsweise so aussehen:

Code: Alles auswählen

DECLARE FUNCTION WirHabenEinenGewinner% ()

CONST Spieleranzahl = 2, Gewinnpunktzahl = 100

TYPE SpielerTyp
  Spielername AS STRING * 80
  Punkte AS INTEGER
END TYPE

DIM SHARED Spieler(Spieleranzahl) AS SpielerTyp

DIM i AS INTEGER, Punkte AS INTEGER

FOR i = 1 TO Spieleranzahl
  PRINT "Bitte geben sie den Namen des "; STR$(i) + ". Spielers ein:";
  INPUT " ", Spieler(i).Spielername
NEXT

DO UNTIL WirHabenEinenGewinner
  FOR i = 1 TO Spieleranzahl
    PRINT "Wie viele Punkte hat "; RTRIM$(Spieler(i).Spielername);
    PRINT " in dieser Runde gemacht:";
    INPUT " ", Punkte
    Spieler(i).Punkte = Spieler(i).Punkte + Punkte
  NEXT
  FOR i = 1 TO Spieleranzahl
    PRINT "Spieler "; RTRIM$(Spieler(i).Spielername); " hat insgesamt";
    PRINT Spieler(i).Punkte; "Punkte."
    PRINT
  NEXT
LOOP

PRINT "Das Spiel ist beendet!"

FUNCTION WirHabenEinenGewinner%
  DIM i AS INTEGER, r AS INTEGER

  FOR i = 1 TO Spieleranzahl
    IF Spieler(i).Punkte > Gewinnpunktzahl THEN
      r = -1
      EXIT FOR
    END IF
  NEXT
  WirHabenEinenGewinner = r
END FUNCTION
Bei mir funktioniert das irgendwie nicht :-(
Benutzeravatar
__blackjack__
User
Beiträge: 5993
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Sonntag 2. Februar 2020, 00:09

Bitte nicht den ganzen Beitrag zitieren – der steht doch davor schon mal in diesem Thema.

*Was* funktioniert nicht und wie funktioniert das nicht? Bitte genau beschreiben was Du gemacht hast, was daraufhin passiert ist, und wie das von Deinen Erwartungen abweicht. Wenn Fehlermeldungen, dann diese bitte 1:1 hier rein kopieren.
long long ago; /* in a galaxy far far away */
nezzcarth
User
Beiträge: 996
Registriert: Samstag 16. April 2011, 12:47

Sonntag 2. Februar 2020, 11:18

Leicht Off-Topic:
__deets__ hat geschrieben:
Mittwoch 8. Januar 2020, 13:01
Fuer if waere das uebrigens "unless". Aber beides gibt es in Python nicht, Ruby aber kennt zumindest unless.
Ruby hat auch 'until'. Ich nehme an, das kommt durch den Perl Einfluss (die Postix-Loops hat man sich aber dann wohl doch gespart). Zumindest bei Perl gilt die Verwendung von "until" aber (laut Perl::Critic) als eher schlechter Stil. Dass man in Python keine "versteckte" Negation hat und seine Operatoren explizit hin schreiben muss, finde ich zur Sprache passend.
Benutzeravatar
kbr
User
Beiträge: 1144
Registriert: Mittwoch 15. Oktober 2008, 09:27

Sonntag 2. Februar 2020, 12:52

Ein "do until" lässt sich auch in Python nachempfinden, wobei hier die Freiheit besteht, die break condition nicht unbedingt erst am Schleifenende zu prüfen.

Code: Alles auswählen

while True:
    # do something
    if condition or other_condition:
        break
nezzcarth
User
Beiträge: 996
Registriert: Samstag 16. April 2011, 12:47

Sonntag 2. Februar 2020, 13:34

kbr hat geschrieben:
Sonntag 2. Februar 2020, 12:52
Ein "do until" lässt sich auch in Python nachempfinden, wobei hier die Freiheit besteht, die break condition nicht unbedingt erst am Schleifenende zu prüfen.
Wobei 'do…until' ja noch einmal etwas anderes ist. In Perl (und ich glaube abgewandelt auch in Ruby) gibt es aber sowohl 'until' als auch 'do … until' (Prüfung am Ende, einer oder mehr Durchläufe).

EDIT: Für den ganzen Schleifenzoo finde ich folgende Tabelle ganz informativ: https://en.wikipedia.org/wiki/Control_f ... ence_table
Antworten