simpler Kopfrechner mit Pygame

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Hallo zusammen,

ich habe diesmal versucht, einen einfachen Kopfrechner mit Pygame darzustellen.
Hab schon folgendes Grundgerüst.

time.sleep(3) habe ich eingebaut, damit er nach der Eingabe und Bestätigung mit ENTER nicht sofort zur nächsten Aufgabe springt und man etwas länger die Ausgabe "richtig!" oder "falsch!" sehen kann. Das Problem ist, während er "schläft" nimmt er die Zeichen von der Tastatur auf, die dann bei der nächsten Aufgabe angezeigt werden. Woher kommt das? Ich dachte, die Zeichen von der Tastatur würden erst beim nächsten Schleifendurchlauf (der inneren while-Schleife) aufgenommen?

Vielen Dank im Voraus!

Code: Alles auswählen

import pygame, sys
from pygame.locals import *
import random
import time

pygame.init()
clock = pygame.time.Clock()

DISPLAYSURF = pygame.display.set_mode((1000, 300))
pygame.display.set_caption("Kopfrechner")

WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLUE = (0, 0, 128)

fontObj = pygame.font.Font('freesansbold.ttf', 50)

while True:

    zahl_1 = random.randint(1, 101)
    zahl_2 = random.randint(1, 101)
    ergebnis = zahl_1 + zahl_2
    eingabe = ""
    eingabe_abgeschlossen = False

    while eingabe_abgeschlossen != True:
        DISPLAYSURF.fill(WHITE)

        anzeige = "{0} + {1} = ".format(zahl_1, zahl_2)

        SurfaceObj_anzeige = fontObj.render(anzeige, True, GREEN, BLUE)
        DISPLAYSURF.blit(SurfaceObj_anzeige, (200, 50))

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RETURN:
                    eingabe_abgeschlossen = True
                    if ergebnis == int(eingabe):
                        anzeige_richtig_falsch = fontObj.render("richtig!", True, GREEN, BLUE)
                        DISPLAYSURF.blit(anzeige_richtig_falsch, (600, 50))
                    else:
                        anzeige_richtig_falsch = fontObj.render("falsch!", True, GREEN, BLUE)
                        DISPLAYSURF.blit(anzeige_richtig_falsch, (600, 50))
                elif event.key == pygame.K_BACKSPACE:
                    eingabe = eingabe[:-1]
                else:
                    eingabe = eingabe + event.unicode

        anzeige_eingabe = fontObj.render(eingabe, True, GREEN, BLUE)
        DISPLAYSURF.blit(anzeige_eingabe, (450, 50))

        pygame.display.flip()
        clock.tick(30)

    time.sleep(5)
poldi
User
Beiträge: 20
Registriert: Sonntag 19. April 2020, 08:35

Ich kenne Pygame nicht so genau aber meistens läuft die Peripherie wie Maus, Tastatur etc. im eigenen Thread.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

poldi hat geschrieben: Donnerstag 26. November 2020, 19:38 Ich kenne Pygame nicht so genau aber meistens läuft die Peripherie wie Maus, Tastatur etc. im eigenen Thread.
Steile These, und genau andersherum verhält es sich. Ereignisse kommen üblicherweise nur im main Thread an. In dem auch die GUI läuft.

@sam123: pygame hat eine Event-queue, und darin laufen Ereignisse auf, während dein Programm was anderes macht. Es wäre ja sonst auch dramatisch wenn zb auf ein key press das key release nicht empfangen würde, weil du gerade etwas anderes machst. Wenn du Ereignisse für eine gewisse Zeit ignorieren willst, dann musst du sie so lange einfach wegwerfen, bis der Zeitpunkt gekommen ist, ab dem du sie wieder verarbeiten willst.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sam123: benutze keine *-Importe. Soweit ich das sehe, benutzt Du die auch gar nicht.
Nur Konstanten werden komplett gross geschrieben, DISPLAYSURF ist aber keine Konstante.
Variablennamen werden komplett klein geschrieben. Bei fontObj ist das Obj auch total nichtssagend, weil alles ein Objekt ist.
Definiere Funktionen. sys.exit hat in einem sauberen Programm nichts verloren, vor allem nicht so tief verschachtelt. Im Eventloop wird auch viel zu viel gemacht.
Statt einem time.sleep solltest Du einen event-Loop weiter laufen lassen, vor allem um auf QUIT reagieren zu können.

Code: Alles auswählen

import pygame
import random
import time

WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLUE = (0, 0, 128)


def event_loop(eingabe):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            return eingabe, pygame.QUIT
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RETURN:
                return eingabe, pygame.K_RETURN
            elif event.key == pygame.K_BACKSPACE:
                eingabe = eingabe[:-1]
            else:
                eingabe = eingabe + event.unicode
    return eingabe, None


def play_round(display):
    zahl_1 = random.randint(1, 101)
    zahl_2 = random.randint(1, 101)
    ergebnis = zahl_1 + zahl_2
    aufgabe = "{0} + {1} = ".format(zahl_1, zahl_2)
    eingabe = ""
    while True:
        display.fill(WHITE)
        text = font.render(aufgabe, True, GREEN, BLUE)
        display.blit(text, (200, 50))

        eingabe, status = event_loop(eingabe)
        text = font.render(eingabe, True, GREEN, BLUE)
        display.blit(text, (450, 50))

        if status == pygame.K_RETURN:
            text = "richtig!" if ergebnis == int(eingabe) else "falsch!"
            text = font.render(text, True, GREEN, BLUE)
            display.blit(text, (600, 50))

        pygame.display.flip()
        if status is not None:
            return status
        clock.tick(30)

def pause():
    end = time.monotonic() + 5
    while time.monotonic() < end:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return pygame.QUIT
        clock.tick(30)
    return None


def main():
    pygame.init()
    pygame.display.set_caption("Kopfrechner")
    font = pygame.font.Font('freesansbold.ttf', 50)
    clock = pygame.time.Clock()

    display = pygame.display.set_mode((1000, 300))
    while True:
        status = play_round(display)
        if status == pygame.QUIT:
            break
        status = plause()
        if status == pygame.QUIT:
            break
    pygame.quit()

if __name__ == '__main__':
    main()
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

danke für die Tips und den ausführlichen Code!

Ich musste bei den Funktionen play_round() und pause() die Variablen font und clock mit übergeben. Sonst lief das Programm bei mir nicht.
Ich muss ehrlich sagen, das Verständnis für das Zusammenspiel ist noch nicht wirklich da. Bin nunmal Anfänger. Muss noch viel üben :-)
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Hallo zusammen.
Ich versuche mit sehr einfachen Beispielen den obigen Code zu verstehen.
Das Beispiel unten läuft auch, aber das Problem ist, ich kann das Fenster nicht schließen, zum Beenden.
Die Zeile mit pygame.quit() wird bei Pycharm auch mit anderer Farbe hinterlegt angezeigt und Pycharm sagt auch "this code is unreachable".
Woran liegt das?

Code: Alles auswählen

import pygame
import random

WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLUE = (0, 0, 128)

def schreibe_zahl(font, display, clock):
    x_Koord_text = random.randint(50, 100)
    y_Koord_text = random.randint(50, 100)
    zufallszahl = random.randint(1, 10)

    display.fill(WHITE)
    pygame.draw.rect(display, BLUE, (50, 50, 100, 100), 3)
    text_zufallszahl = font.render(str(zufallszahl), True, GREEN, BLUE)
    display.blit(text_zufallszahl, (x_Koord_text, y_Koord_text))
    pygame.display.flip()
    clock.tick(5)

def main():
    pygame.init()
    pygame.display.set_caption("Zufallszahl")
    font = pygame.font.Font('freesansbold.ttf', 50)
    clock = pygame.time.Clock()
    display = pygame.display.set_mode((1000, 300))

    while True:

        schreibe_zahl(font, display, clock)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                break

    pygame.quit()

if __name__ == '__main__':
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sam123: Mit ``break`` wird die Schleife abgebrochen in der das steht. Das ist die ``for``-Schleife, nicht die ``while``-Schleife.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Mist, war ja total offensichtlich :(
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Wenn ich die beiden Funktionen schreibe_zahl und zeichne_kreis im unteren Beispiel einzeln aufrufe, d.h. die andere jeweils auskommentiere, laufen die "Animationen" unterschiedlich schnell ab. Wenn beide Funktionen da sind, dann laufen beide Animationen langsam. Ist auch irgedwie veständlich, dass die langsamere Funktion die schnellere abbremst. Aber geht es irgendwie gleichzeitig mit unterschiedlichen Geschwindigkeiten?

Code: Alles auswählen

import pygame
import random

WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLUE = (0, 0, 128)
RED = (255,0,0)
YELLOW = (255,255,0)

def schreibe_zahl(font, display, clock):
    x_Koord = random.randint(50, 100)
    y_Koord = random.randint(50, 100)
    zufallszahl = random.randint(1, 10)

    pygame.draw.rect(display, WHITE, (50, 50, 100, 100))
    pygame.draw.rect(display, BLUE, (50, 50, 100, 100),1)
    text_zufallszahl = font.render(str(zufallszahl), True, GREEN, BLUE)
    display.blit(text_zufallszahl, (x_Koord, y_Koord))
    pygame.display.flip()
    clock.tick(5)

def zeichne_kreis(display, clock):
    x_Koord = random.randint(200, 300)
    y_Koord = random.randint(50, 150)

    farbenliste = [GREEN, BLUE, RED, YELLOW]
    zufallzahl_farbe = random.randint(0,3)
    farbe = farbenliste[zufallzahl_farbe]

    pygame.draw.circle(display, farbe, (x_Koord, y_Koord), 10)
    pygame.display.flip()
    clock.tick(300)

def main():
    pygame.init()
    pygame.display.set_caption("Zufallszahl")
    font = pygame.font.Font('freesansbold.ttf', 40)
    clock = pygame.time.Clock()
    display = pygame.display.set_mode((1000, 300))
    display.fill(WHITE)

    status = None
    while status != pygame.QUIT:

        schreibe_zahl(font, display, clock)
        zeichne_kreis(display,clock)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                status = pygame.QUIT

    pygame.quit()

if __name__ == '__main__':
    main()
Noch eine Frage: Warum färbt er den Code jezt nur zweifarbig ein?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Natürlich darf es pro while-Schleifendurchgang nur ein display.flip und nur ein clock.tick geben.
Und wenn man etwas langsameres haben will, dann darf man die eine Sache nur alle 60 Durchläufe aktualisieren.
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

@Sam123: ich bin mir unsicher ob ich Deine Frage richtig verstanden habe. Ich würde die Framerate fest vorgeben. Das heißt clock.tick(zahl) muss aus den beiden Funktionen gelöscht werden und stattdessen in der while-Schleife von der main - Funktion eingesetzt werden. Damit z.B. die Zahlen langsamer als die Kreise gezeichnet werden, gebe ich hier vor, dass die Zahlen nur jedes 20. Frame gezeichnet werden sollen:

Code: Alles auswählen

import pygame
import random

WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLUE = (0, 0, 128)
RED = (255,0,0)
YELLOW = (255,255,0)

def schreibe_zahl(font, display, clock):
    x_Koord = random.randint(50, 100)
    y_Koord = random.randint(50, 100)
    zufallszahl = random.randint(1, 10)

    pygame.draw.rect(display, WHITE, (50, 50, 100, 100))
    pygame.draw.rect(display, BLUE, (50, 50, 100, 100),1)
    text_zufallszahl = font.render(str(zufallszahl), True, GREEN, BLUE)
    display.blit(text_zufallszahl, (x_Koord, y_Koord))
    pygame.display.flip()


def zeichne_kreis(display, clock):
    x_Koord = random.randint(200, 300)
    y_Koord = random.randint(50, 150)

    farbenliste = [GREEN, BLUE, RED, YELLOW]
    zufallzahl_farbe = random.randint(0,3)
    farbe = farbenliste[zufallzahl_farbe]

    pygame.draw.circle(display, farbe, (x_Koord, y_Koord), 10)
    pygame.display.flip()


def main():
    pygame.init()
    pygame.display.set_caption("Zufallszahl")
    font = pygame.font.Font('freesansbold.ttf', 40)
    clock = pygame.time.Clock()
    display = pygame.display.set_mode((1000, 300))
    display.fill(WHITE)

    speed = 60
    zaehler = 0
    status = None
    while status != pygame.QUIT:
        clock.tick(speed)
	
        if zaehler == (20 or 40 or 60):
            schreibe_zahl(font, display, clock)
        
        zeichne_kreis(display,clock)

        zaehler += 1
        if zaehler == 60:
            zaehler = 0
	
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                status = pygame.QUIT

    pygame.quit()

if __name__ == '__main__':
    main()
Zuletzt geändert von Domroon am Donnerstag 3. Dezember 2020, 20:33, insgesamt 1-mal geändert.
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

ja genau, das display.flip() sollte auch in die main-funktion wandern ;) habe ich oben jetzt nicht berücksichtigt
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Domroon: or funktioniert nicht so, wie Du hoffst. Hast Du Dir mal das Ergebnis des Ausdrucks (20 or 40 or 60) angeschaut? Dafür gibt es den in-Operator. Zudem wird zaehler bei der Abfrage niemals 60.
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

@Sirius3: ich habe den von mit modifizierten Code vor dem posten getestet und er funktioniert wie beschrieben ;)
Es geht sicherlich deutlich eleganter, aber das ist die Änderung die mir eingefallen ist.
Der Zähler wird 60. Bei jedem Durchlauf der while-Schleife erhöhe ich "zaehler" um einen:

Code: Alles auswählen

while status != pygame.QUIT:
        clock.tick(speed)
	
        if zaehler == (20 or 40 or 60):
            schreibe_zahl(font, display, clock)
        
        zeichne_kreis(display,clock)

        zaehler += 1
        if zaehler == 60:
            zaehler = 0
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Beim Programmierern gibt es so oft den Fall, dass etwas scheinbar funktioniert und trotzdem falsch ist. Lass dir mal zaehler ausgeben:

Code: Alles auswählen

while status != pygame.QUIT:
        clock.tick(speed)
	
        if zaehler == (20 or 40 or 60):
            print(zaehler)
            schreibe_zahl(font, display, clock)
        
        zeichne_kreis(display,clock)

        zaehler += 1
        if zaehler == 60:
            zaehler = 0
Sam123
User
Beiträge: 35
Registriert: Freitag 20. November 2020, 12:25

Super, danke! Läuft jetzt genau wie ich wollte.

Code: Alles auswählen

while_zaehler = 0
while status != pygame.QUIT:
        clock.tick(300)

        zeichne_kreis(display, clock)

        while_zaehler += 1

        if while_zaehler in [10, 20, 100]:
            print(while_zaehler)
            schreibe_zahl(font, display, clock)

        if while_zaehler == 100:
            while_zaehler = 0
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

@Sirius3: Okay Fehler erkannt ;) "Or" hat wirklich nicht funktioniert und 60 wurde auch nie erreicht. Folgende main Funktion habe ich nun korrigiert:

Code: Alles auswählen

def main():
    pygame.init()
    pygame.display.set_caption("Zufallszahl")
    font = pygame.font.Font('freesansbold.ttf', 40)
    clock = pygame.time.Clock()
    display = pygame.display.set_mode((1000, 300))
    display.fill(WHITE)

    speed = 60
    zaehler = 0
    status = None
    while status != pygame.QUIT:
        clock.tick(speed)
        
        zwanzigste = [20, 40, 60]
        if zaehler in zwanzigste:
            print(zaehler)
            schreibe_zahl(font, display, clock)
        
        zeichne_kreis(display,clock)

        zaehler += 1
        if zaehler > 60:
            zaehler = 0
	
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                status = pygame.QUIT

    pygame.quit()

if __name__ == '__main__':
    main()
Danke Sirius3 ;)
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

@Sam123: Super freut mich ;)
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Domroon: damit läuft jetzt der Zähler jetzt unrund, zwischen Frame 20 und 40, bzw. 40 und 60 liegen jeweils 20 Frames, zwischen 60 und 20 aber 21.
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

@Sirius: Ach stimmt :D Okay dann müsste ich doch einfach nur den Zähler anstatt auf 0 dann auf 1 setzen. Denn bislang habe ich die Null mitgezählt :D

Code: Alles auswählen

    speed = 60
    zaehler = 1
    status = None
    while status != pygame.QUIT:
        clock.tick(speed)
        print(zaehler)
        zwanzigste = [20, 40, 60]
        if zaehler in zwanzigste:
            schreibe_zahl(font, display, clock)
        
        zeichne_kreis(display,clock)

        zaehler += 1
        if zaehler > 60:
            zaehler = 1
Antworten