Pgrammablauf anders als erwartet

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
ThomasRandus
User
Beiträge: 3
Registriert: Donnerstag 8. Mai 2025, 16:12

Ich lerne gerade Python Programmierung. Ich habe zu einem Spiel das MS Gründer Steve Ballmer seinen Bewerbern gestellt hat ein kleines Programm geschrieben.

Ich verstehe nur leider die Logik des Programmablaufs nicht. Durch die Textausgabe ist mir aufgefallen,
dass nach:
"Gut gemacht, Du hast meine geheime Zahl in 9 Versuchen geraten"
gleich
"Bitte sage mir deine Schätzung"
kommt

D. h. : die folgenden Zeilen werden übersprungen und nicht ausgeführt
Und ich verstehe nicht warum.

Code: Alles auswählen

print(Game())
    z.append(result)
    print(z)
# Es soll hier einfach abgespeichert und ausgedruckt werden wie lange der Spieler in der jeweiligen Runde gebraucht hat die Zahl zu erraten.


Code: Alles auswählen

import random
z=[]
keepPlay='j'
while keepPlay=='j':
    def Game():
        x=random.randint(1,100)
        y=0
        i=0
        while True:
            print('Bitte sage mir deine Schätzung')
            y=int(input())
            i+=1
            if y>x:
                print('Niedriger - Deine Schätzung ist zu hoch')
            elif y<x:
                print('höher - Deine Schätzung ist zu niedrig')
            elif y==x:
                break
        if y==x:
            print('Gut gemacht, Du hast meine geheime Zahl in ' + str(i)+' Versuchen geraten')
        return i

    result=Game()
    print(Game())
    z.append(result)
    print(z)

    print('Möchtest Du erneut spielen (j/n)?')
    a=input()
    if a=='j':
        keepPlay='j'
    elif a=='n':
        break
    #else:
    #print('Bitte nur j oder n eingeben nichts weiter')

print('Ok Tschüss dann')
Der Code ist sicherlich so schön wie die ersten 5 selbstgedrehten Zigaretten ;) Ich habe auch etwas Probleme die Reihenfolge richtig zu bestimmen und die Funktion vom Programmablauf zu trennen.
Mein Ziel war: ich wollte das eigentliche Spiel in eine Funktion packen und das Programm sollte dann mehr aus einer While-Schleife bestehen dass die Funktion aufruft und ein paar Infos zurückgibt.

Also falls es da noch Hinweise gibt wie man das richtig strukturiert wäre ich dankbar. Habe doch gemerkt dass ich da noch Schwierigkeiten habe.

Grüße ans Forum

Zum Spiel noch:
Steve Ballmer hat in einem Interview erzählt dass er angeblich allen Bewerbern die gleiche Frage gestellt hat: I pick a number between 1-100. You can tell me a number, i tell you then if the number is higher or lower. If you pick the right number on the first try i give you 5 bucks, then 4, 3,2,1,0 then -1, -2. Want to play?. Der Part mit den $/Erwartungswerten habe ich jetzt nicht abgebildet ich wollte das Spiel selbst schreiben als Übung.
Sirius3
User
Beiträge: 18245
Registriert: Sonntag 21. Oktober 2012, 17:20

Erst mal die grundsätzlichen Anmerkungen: man definiert keine Funktionen innerhalb von Schleifen.
Einbuchstabige Namen sind schlecht, weil sie nichts aussagen. Was soll z, x y, i oder a sein?
Variablennamen und Funktionen schreibt man in Python komplett klein.
Strings stückelt man nicht mit + zusammen, sondern nutzt f-Strings.
Bei Deiner äußeren while-Schleife: wenn man einer Variable einen dummy-Wert zuweisen muß, damit sie losläuft, dann hat man eigentlich eine while-True-Schleife, die man an passender Stelle per break verlässt. Hast Du auch, weil `keepPlay` niemals einen anderen Wert als 'j' annimmt.
Der Wert y=0 wird niemals verwendet. Die beiden y==x-Vergleiche sind immer wahr. Da fragt man sich als Leser, hab ich was übersehen? Muß ich noch einen weiteren Fall berücksichtigen?
Dann fehlt bei allen Eingaben die Fehlerbehandlung. Die Aufgabenbeschreibung sagt, dass um Geld gespielt wird. Ist bei Dir anders.
Zum eigentlichen Problem: schau mal, wie oft die Funktion `Game` aufgerufen wird.
Benutzeravatar
__blackjack__
User
Beiträge: 13969
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ThomasRandus: Ergänzende Anmerkungen zu dem was Sirius3 schon geschrieben hat:

Wenn die `Game()`-Funktion dann aus der Schleife raus ist, sollte das Hauptprogramm selbst auch in eine Funktion wandern. Traditionell heisst die `main()`. Dann kann man sich sicher sein, dass man nicht aus versehen Variablen daraus in Funktionen verwendet.

Neben der Gross-/Kleinschreibung wäre es noch sinnvoll Funktionen (und später auch Methoden) nach der Tätigkeit zu benennen, die ausgeführt wird, damit der Leser weiss was die Funktion(/Methode) tut, und um die besser von eher passiven Werten unterscheiden zu können. `game` wäre beispielsweise eher ein Name für ein Objekt, welches den Zustand eines Spiels repräsentiert.

Die Abfrage ob weitergespielt werden soll, ist so komisch weil das ``else`` fehlt — der Benutzer kann ja was anderes als "j" oder "n" eingeben. Das sollte man entweder vereinfachen, oder etwas komplexer machen, so dass man tatsächlich den Effekt bekommt, der durch den auskommentierten ``else``-Zweig angedeutet wird.

`input()` könnte man den Prompt-Text als Argument übergeben und sich damit das extra `print()` davor sparen.

Wenn man irgendwo ein ``if``/``elif``-Konstrukt hat, sollte man immer überlegen was da in ein abschliessendes ``else`` rein sollte. Und wenn es nur ein ``assert False, "should never happen"`` ist. Denn wenn man einen Fehler gemacht hat, kann es halt doch passieren, und der Fall würde dann eventuell erst viel später im Code auffallen wenn man mit falschen Werten weiter macht. Nun könnte man denken, dass wenn man bei zwei Zahlen ``<``, ``>`` und ``==`` getestet hat, dass dann ein ``else`` nie eintreten kann — bei Gleitkommazahlen kann es diesen Fall aber tatsächlich geben:

Code: Alles auswählen

In [309]: a = 42.0; b = math.nan

In [310]: type(b)
Out[310]: float

In [311]: a < b
Out[311]: False

In [312]: a > b
Out[312]: False

In [313]: a == b
Out[313]: False
Zwischenstand (ungetestet):

Code: Alles auswählen

import random


def play_game():
    secret = random.randint(1, 100)
    trial_count = 0
    while True:
        #
        # TODO Gegen Fehleingaben sichern.  Kandidat für eine eigene Funktion.
        #
        guess = int(input("Bitte sage mir deine Schätzung:\n"))
        trial_count += 1
        if guess > secret:
            print("Niedriger - Deine Schätzung ist zu hoch.")
        elif guess < secret:
            print("Höher - Deine Schätzung ist zu niedrig.")
        else:
            assert guess == secret
            break

    if guess == secret:
        print(
            f"Gut gemacht, Du hast meine geheime Zahl in {trial_count}"
            f" Versuchen geraten."
        )

    return trial_count


def main():
    results = []
    while True:
        result = play_game()
        print(result)
        results.append(result)
        print(results)
        #
        # TODO Explizit nur "j" und "n" erlauben und Frage wiederholen, falls
        #      der Benutzer etwas anderes eingibt.  Das wäre ein Kandidat für
        #      eine eigene Funktion.
        #
        if input("Möchtest Du erneut spielen (j/n)?\n") == "n":
            break

    print("Ok, tschüss dann.")


if __name__ == "__main__":
    main()
Die Interview-Frage von Ballmer zielte aber nicht darauf ab, dass zu implementieren, sondern die Frage mit „ja“ oder „nein“ zu beantworten, ziemlich wahrscheinlich mit Begründung.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 13969
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ballmers Frage könnte man übrigens mit „ja“ beantworten. Dafür wäre es sinnvoller den Computer alleine spielen zu lassen, also eine Simulation zu programmieren, die wirklich viele Spiele auswertet, denn man möchte eher nicht selbst so oft perfekt spielen, dass man die Ergebnisse auswerten kann. Das dauert eeeewig. Für 5.000 Spiele braucht es auf einem PET 2001 bzw. den Nachfolgemodellen so zwischen 12 und 13 Minuten mit folgendem BASIC-Programm:

Code: Alles auswählen

10 S=RND(-TI):TI$="000000":N=5000:R=0:DIM H(7)
20 FOR I=1 TO N:GOSUB 100:R=R+6-C:H(C)=H(C)+1:NEXT
30 PRINT"GEWINN NACH";N;"SPIELEN:";R:PRINT
40 FOR I=1 TO 7:PRINT STR$(6-I);":";STR$(H(I)/N*100);"%":NEXT
50 PRINT:PRINT TI$:END
100 S=INT(RND(1)*100)+1:C=0:L=1:U=100
110 C=C+1:G=INT((U-L)/2)+L
120 IF G<S THEN L=G+1:GOTO 110
130 IF G>S THEN U=G-1:GOTO 110
140 RETURN
Beispiel für einen Programmlauf:

Code: Alles auswählen

RUN
GEWINN NACH 5000 SPIELEN: 1081

 5: .94%
 4: 2.12%
 3: 3.94%
 2: 8.4%
 1: 16.48%
 0: 31.46%
-1: 36.66%

001250

READY.
Spätestens wenn man die Prozentzahlen rundet, könnte einem ein Muster auffallen. 😎
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Kebap
User
Beiträge: 765
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Ich wundere mich schon ein wenig über das Ergebnis.
Also bei "ja" gehe ich vielleicht noch mit. Wobei 5 Dollar mir zu wenig wäre, da groß Gedanken drauf zu verwenden. Sagen wir mal 5 Millionen.
Anstatt da nun Brute-Force-mäßig tausende Simulationen durchzurattern und auszuwerten, denke ich mal ein wenig laut darüber nach.
Die eigentliche Frage ist ja, schafft man es durchschnittlich nach 5 Versuchen, die zufällige Zahl aus 100 rauszufinden? Sonst zahlt man drauf.
Die beste Strategie ist wohl, immer die Zahl in der Mitte zu raten, um die Anzahl der Möglichkeiten für die nächste Runde zu minimieren.
Das geht nicht in jedem Fall genau auf, weil es nicht immer eine ungerade Anzahl sein muss. Aber nur mal so überschlagsweise.
Runde 1 hat 100 Zahlen, Runde 2 noch 50, Runde 3 noch 25, bei 4 vielleicht 13, bei 5 dann 7, bei 6 noch 4, bei 7 noch 2, bei 8 ist man sicher.
Wobei ich einräumen muss, dass man auch nicht sofort draufzahlt, wenn man gelegentlich mal mehr als 5 Versuche braucht.
Erst wenn man regelmäßig mehr als 5 Versuche hat, wird sich der Gesamtgewinn im Schnitt der Null annhähern oder sogar Verlust werden.
Also nach 8 Runden hat man sicher die korrekte Zahl gefunden. Bleibt noch die Frage, wie oft man welche Runde erreicht.
Jedenfalls in dem Programmablauf von Blackjack sehe ich niemals Runde 8 erreicht, aber über 36% erreichen Runde 7.
Habe ich einen gedanklichen Fehler bis hierhin?
Die Rundung der Zahlen scheint darauf hinzudeuten, wieviele Zahlen man mit der binären Suche nach x Runden abdecken kann.
Seltsam, dass hier die gerade/ungerade Anzahl der verbleibenden Möglichkeiten nicht groß ins Gewicht fällt. Vielleicht irre ich ja dort.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Benutzeravatar
__blackjack__
User
Beiträge: 13969
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Bei 5 Millionen würde ich nein sagen wenn das auch impliziert, dass man im schlechtesten Fall eine Million bezahlen müsste. Die habe ich nicht mal eben so herumliegen. 🫤

Statt tausende Simulationen reichen ja auch 100 wenn man die Zahl nicht zufällig wählt, sondern das ganze einfach mal für jede Zahl von 1 bis 100 durchspielen lässt:

Code: Alles auswählen

10 TI$="000000":N=100:R=0:DIM H(7)
20 FOR I=1 TO N:GOSUB 100:R=R+6-C:H(C)=H(C)+1:NEXT
30 PRINT"GEWINN NACH";N;"SPIELEN:";R:PRINT
40 FOR I=1 TO 7:PRINT STR$(6-I);":";STR$(H(I)/N*100);"%":NEXT
50 PRINT:PRINT TI$:END
100 S=I:C=0:L=1:U=100
110 C=C+1:G=INT((U-L)/2)+L
120 IF G<S THEN L=G+1:GOTO 110
130 IF G>S THEN U=G-1:GOTO 110
140 RETURN
Ergebnis:

Code: Alles auswählen

RUN
GEWINN NACH 100 SPIELEN: 20

 5: 1%
 4: 2%
 3: 4%
 2: 8%
 1: 16%
 0: 32%
-1: 37%

000014

READY.
Bei mehr als 7 Versuchen für eine der möglichen geheimen Zahlen hätte das Programm mit einem BAD SUBSCRIPT ERROR abgebrochen, weil das Histogramm H() nur mit 7 DIMensioniert wurde. Und das sollte eigentlich auch klar sein, dass man nicht mehr als 7 Entscheidungen = Bits für Zahlen bis 100 braucht, also eigentlich sogar bis 127 — dann verschieben sich aber die Prozentzahlen ungünstig, so dass es sich dann nicht mehr lohnt.

Der gedankliche Fehler ist wahrscheinlich, dass Du in der ersten Runde die Zahlen 1 bis 100 hast. Die erste Runde ist in der Aufgabe aber der erste Versuch, also das man 50 sagt, und da hat man ja schon nur noch die Hälfte der Zahlen. Du hast das auf die Weise bloss um 1 verschoben, weil man bei Deinem 1 noch gar nicht richtig liegen kann, wenn man noch keine konkrete Zahl genannt hat.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
Kebap
User
Beiträge: 765
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Oh, wir spielen nur einmal? Gut, dann lieber keine Million. Danke für die ausführliche Erklärung. Also mal wieder einer dieser off-by-1 Fehler.

Klar, um in meiner Runde 8 anzukommen, braucht man ja nur 7 Entscheidungen zu treffen.

Noch nicht klar ist mir, warum die Prozentzahlen so gut aufgehen, obwohl ja 27 (teilweise sehr leichte) Zahlen gar nicht erreicht werden.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Benutzeravatar
__blackjack__
User
Beiträge: 13969
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Die Zahlen sind so glatt weil bei 100 Simulationen wo jede mögliche Zahl einmal geraten wird am Ende die Prozente durch anzahl/100*100 zustande kommen. Also letztendlich die Anzahl der Male die man den jeweiligen Betrag gewinnt/verliert. Einmal raten ist die 50, zweimal raten die 25 und die 75, dreimal raten sind 13, 37, 64, und 87, und so weiter.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 13969
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das ganze mal grafisch dargestellt welche Zahlen Verlust bringen (die roten) und die Wege dort hin:
Bild
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
narpfel
User
Beiträge: 690
Registriert: Freitag 20. Oktober 2017, 16:10

@__blackjack__: Du musst für deine Rechnung beachten, dass dein Gegenspieler die Zahlen nicht zufällig auswählen muss. Wenn deine Strategie deterministisch und/oder immer gleich ist, kann dein Gegenspieler absichtlich Zahlen wählen, die für dich schlecht sind.
Benutzeravatar
__blackjack__
User
Beiträge: 13969
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@narpfel: Danke für den Denkanstoss. Wir reden hier ja auch noch von Ballmer.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 13969
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Also wäre ein Programm sinnvoller, das für Ballmer die Zahlen ermittelt, die für ihn von Vorteil sind. Da BASIC zu langweilig und zu altmodisch ist, dies mal in FOTRAN:

Code: Alles auswählen

C DETERMINE THE NUMBERS BALLMER COULD CHOOSE FROM TO WIN THE GAME BECAUSE THEY
C NEED AT LEAST 7 GUESSES.
      PARAMETER (N=100)
      INTEGER PLAY,RES
      DIMENSION RES(N)
      M=0
      DO 10 I=1,N
        IF (PLAY(N,I).LT.7) GO TO 10
        M=M+1
        RES(M)=I
   10 CONTINUE
      WRITE(6,500)(RES(I), I=1,M)
  500 FORMAT(20I4)
      END
C PLAY THE NUMBER GUESSING GAME FOR NUMBERS FROM 1 TO 'N' AND THE GIVEN
C 'SECRET' NUMBER. RETURN THE NUMBER OF TRIALS NEEDED.
      INTEGER FUNCTION PLAY(N,SECRET)
      INTEGER SECRET,GUESS,LOWER,UPPER,COUNT
      LOWER=1
      UPPER=N
      COUNT=0
   10 GUESS=(UPPER-LOWER)/2 + LOWER
      COUNT=COUNT+1
      IF (SECRET-GUESS) 20,30,40
   20 UPPER=GUESS-1
      GO TO 10
   30 PLAY=COUNT
      RETURN
   40 LOWER=GUESS+1
      GO TO 10
      END
Ausgabe:

Code: Alles auswählen

   2   5   8  11  14  17  20  22  24  27  30  33  36  39  42  45  47  49  52  55
  58  61  64  67  70  72  74  77  80  83  85  87  90  93  96  98 100
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Sirius3
User
Beiträge: 18245
Registriert: Sonntag 21. Oktober 2012, 17:20

Man kann aus einer ganzen Reihe gleich guter Spielstrategien wählen, die alle bei wirklichen Zufallszahlen die Gewinnchancen maximieren, einem unfairen Spielleiter aber keine sicheren Zahlen übrig läßt.
Benutzeravatar
kbr
User
Beiträge: 1503
Registriert: Mittwoch 15. Oktober 2008, 09:27

Eine Binärsuche muß keineswegs in der Mitte des Suchraums starten, um zum Erfolg zu führen. Das mag im worst case die Zahl der Zugriffe erhöhen, aber ein Spielleiter (oder hier Ballmer) hat dann keine Chance im Vorfeld Zahlen zu bestimmen, die garantiert eine hohe Zahl an Zugriffen erfordern.
Benutzeravatar
__blackjack__
User
Beiträge: 13969
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kbr: Zugriffe erhöhen darf man nicht, dann verschlechtert man ja seine Chancen, weil es dann mehr Strafe kostet. Aber es gibt ja durchaus Variationen beim “Mitte“ bestimmen, die nichts an der Suchtiefe ändern. Einfachstes Beispiel: wie rundet man bei der Mitte . Ich hatte immer abgerundet. Man könnte auch das ”normale” Runden aus der Schule nehmen. Oder grundsätzlich immer aufrunden. Oder zufällig auf- oder abrunden. Das ändert nichts an der Suchtiefe, aber es ändert bei welchen Zahlen man die maximale Suchtiefe erreicht. Mal PLAY() so geändert, dass es immer aufrundet:

Code: Alles auswählen

C DETERMINE THE NUMBERS BALLMER COULD CHOOSE FROM TO WIN THE GAME BECAUSE THEY
C NEED AT LEAST 7 GUESSES.
      PARAMETER (N=100)
      INTEGER PLAY,RES
      DIMENSION RES(N)
      M=0
      DO 10 I=1,N
        IF (PLAY(N,I).LT.7) GO TO 10
        M=M+1
        RES(M)=I
   10 CONTINUE
      WRITE(6,500)(RES(I), I=1,M)
  500 FORMAT(20I4)
      END
C PLAY THE NUMBER GUESSING GAME FOR NUMBERS FROM 1 TO 'N' AND THE GIVEN
C 'SECRET' NUMBER. RETURN THE NUMBER OF TRIALS NEEDED.
      INTEGER FUNCTION PLAY(N,SECRET)
      INTEGER SECRET,GUESS,LOWER,UPPER,COUNT,TMP
      LOWER=1
      UPPER=N
      COUNT=0
   10 COUNT=COUNT+1
      IF (COUNT.EQ.8) STOP -1
      TMP=UPPER-LOWER
      GUESS=TMP/2 + LOWER
      IF (MOD(TMP,2).EQ.1) GUESS=GUESS+1
      IF (SECRET-GUESS) 20,30,40
   20 UPPER=GUESS-1
      GO TO 10
   30 PLAY=COUNT
      RETURN
   40 LOWER=GUESS+1
      GO TO 10
      END
Es sind nicht mehr komplett die gleichen Zahlen wie beim generellen abrunden:

Code: Alles auswählen

   1   3   5   8  11  14  16  18  21  24  27  29  31  34  37  40  43  46  49  52
  54  56  59  62  65  68  71  74  77  79  81  84  87  90  93  96  99
Sind zwar auch 37, aber nur 14 Übereinstimmungen mit dem generellen Abrunden.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
kbr
User
Beiträge: 1503
Registriert: Mittwoch 15. Oktober 2008, 09:27

@__blackjack__: Gewiss, was du zeigst ist letztlich eine Variation zum Thema: ein wenig Jitter um die Mitte. Etwas Jitter ganz zu Beginn würde aber auch bereits genügen. In der Praxis verwende ich einfach eine Integerdivison und mache mir weiter keine Gedanken. Das ist effizient genug und üblicherweise spiele ich auch nicht gegen Ballmer :)
Antworten