time.sleep() alternative

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
Eleocraft
User
Beiträge: 10
Registriert: Mittwoch 19. September 2018, 11:16

ich versuche in Python nur mit den Basis Bibliotheken ein jump and run zu programmieren. das funktioniert bis jetzt auch gut, aber das Springen ist noch nicht so realistisch :roll: :roll: . der Player bewegt sich einfach nur konstant nach oben und nach unten, also wird nicht schneller/langsamer. das möchte ich gerne ändern. dazu brauche ich aber eine alternative zu time.sleep(), die ein delay von weniger als 0.01 Sek. erzeugen kann.

Hier ist mein code:

Code: Alles auswählen

#tkinter
from tkinter import *
from random import *
from time import *
screen = Tk()
screen.title('jump and run')
c = Canvas(screen, width=1400, height=700, bg='blue')
c.pack()


Level = 1
runter = 0
Plate_id = list()
levelend = c.create_rectangle(1400, 700, 2000, 710)


    
#Player

Player1 = c.create_rectangle(100, 640, 120, 690, fill='red')
Player2 = c.create_rectangle(100, 640, 120, 690, fill='green')
c.itemconfig(Player2, state=HIDDEN)


#Plate

def screen_plate(PX, PY, Länge):
    
    PL = c.create_rectangle(PX, PY, PX + Länge, PY - 10, fill='lime')
    Plate_id.append(PL)





class schwerkraft:

    def coords(ob):
        pos = c.coords(ob)
        x1 = pos[0]
        y1 = pos[1]
        x2 = pos[2]
        y2 = pos[3]
        return x1, y1, x2, y2

    def check(px1, py1, px2, py2, cx1, cy1, cx2, cy2):
        if py2 == cy1:
            if px1 > cx1 and px1 < cx2:
                colision = 1
            elif px2 < cx2 and px2 > cx1:
                colision = 1
            else:
                colision = 0
        else:
            colision = 0
        return colision


    def sprung(objekt, P2):
        
        haupt_colision = 0
        colision2 = 0
        px1, py1, px2, py2 = schwerkraft.coords(objekt)
        for I in range(len(Plate_id)-1, -1, -1):
            
            cx1, cy1, cx2, cy2 = schwerkraft.coords(Plate_id[I])
            colision = schwerkraft.check(px1, py1, px2, py2, cx1, cy1, cx2, cy2)
            if colision == 1:
                haupt_colision = 1
            if colision == 0:
                pass
            
        
        if haupt_colision == 1:
            for i in range(80):
                c.move(objekt, 0, -1)
                c.move(P2, 0, -1)
                screen.update()
                sleep(0.01)
            
            while colision2 == 0:
                c.move(objekt, 0, 1)
                c.move(P2, 0, 1)
                screen.update()
                sleep(0.01)
                px1, py1, px2, py2 = schwerkraft.coords(objekt)
                Levelcheck(levelend, Player1)
                for I in range(len(Plate_id)-1, -1, -1):

                    cx1, cy1, cx2, cy2 = schwerkraft.coords(Plate_id[I])
                    haupt_colision2 = schwerkraft.check(px1, py1, px2, py2, cx1, cy1, cx2, cy2)
                    if haupt_colision2 == 0:
                        pass
                    if haupt_colision2 == 1:
                        colision2 = 1
                if py1 > 705:
                    c.coords(objekt, 100, 630, 120, 680)
                    c.coords(P2, 100, 630, 120, 680)
            return 0

    def runter(objekt, P2):
        
        haupt_colision = 1
        colision2 = 0
        px1, py1, px2, py2 = schwerkraft.coords(objekt)
        for I in range(len(Plate_id)-1, -1, -1):
            
            cx1, cy1, cx2, cy2 = schwerkraft.coords(Plate_id[I])
            colision = schwerkraft.check(px1, py1, px2, py2, cx1, cy1, cx2, cy2)
            if colision == 1:
                haupt_colision = 0
            if colision == 0:
                pass
            
        
        if haupt_colision == 1:
            while colision2 == 0:
                c.move(objekt, 0, 1)
                c.move(P2, 0, 1)
                screen.update()
                sleep(0.01)
                px1, py1, px2, py2 = schwerkraft.coords(objekt)
                Levelcheck(levelend, Player1)
                for I in range(len(Plate_id)-1, -1, -1):
            
                    cx1, cy1, cx2, cy2 = schwerkraft.coords(Plate_id[I])
                    haupt_colision2 = schwerkraft.check(px1, py1, px2, py2, cx1, cy1, cx2, cy2)
                    if haupt_colision2 == 0:
                        pass
                    if haupt_colision2 == 1:
                        colision2 = 1
                if py1 > 705:
                    c.coords(objekt, 100, 630, 120, 680)
                    c.coords(P2, 100, 630, 120, 680)

        
def Levelup():
    if Level == 1:
        screen_plate(0, 700, 1400)
        screen_plate(200, 630, 30)
    if Level == 2:
        screen_plate(0, 700, 1400)
        screen_plate(500, 630, 30)
    if Level == 3:
        screen_plate(0, 700, 1400)
        screen_plate(1000, 630, 30)


                
def Levelcheck(levelend, Player):
    global Level
    px1, py1, px2, py2 = schwerkraft.coords(Player)
    cx1, cy1, cx2, cy2 = schwerkraft.coords(levelend)
    Levelcheck = schwerkraft.check(px1, py1, px2, py2, cx1, cy1, cx2, cy2)
    if Levelcheck == 1:
        for I in range(len(Plate_id)-1, -1, -1):
            c.delete(Plate_id[I])
        del Plate_id[:]
        Level += 1
        Levelup()
        print('levelup')
    


def move(event):

    global runter
    
    if event.keysym == 'Up':
        if runter != 1:
            runter = 1
            runter = schwerkraft.sprung(Player1, Player2)
    elif event.keysym == 'Right':
        c.move(Player1, 10, 0)
        c.move(Player2, 10, 0)
        c.itemconfig(Player1, state=NORMAL)
        c.itemconfig(Player2, state=HIDDEN)
        Levelcheck(levelend, Player1)
        if runter == 0:
            schwerkraft.runter(Player1, Player2)
    elif event.keysym == 'Left':
        c.move(Player1, -10, 0)
        c.move(Player2, -10, 0)
        c.itemconfig(Player1, state=HIDDEN)
        c.itemconfig(Player2, state=NORMAL)
        Levelcheck(levelend, Player1)
        if runter == 0:
            schwerkraft.runter(Player1, Player2)
c.bind_all('<Key>', move)



Levelup()



PS: die Level sind noch nicht fertig.
PPS: bitte nicht einfach nur sagen was ich falsch mache sondern wenn dann auch alternativen liefern.
Tholo
User
Beiträge: 177
Registriert: Sonntag 7. Januar 2018, 20:36

Code: Alles auswählen

import tkinter as tk
import random


def sleep_alternative():
    if tk:
        print("Gui's vertragen sich nicht mit time.sleep")
        print(random_zitate)
        url = "http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.after-method"
        print(url)
        print(f"Suche mal nach tkinter und after")
        with open("code_im_forum", "w") as code:
            code = str.replace("time.sleep", "tkinter.root.after")


def random_zitate():
    user_speech = {
            "Blackjack": "Gui und Sleep geht nicht", "Deets": "Gui ist Eventgesteuert",
            "ThomasL": "Schau in der Tk Docu",
            "Noisefloor": "das wurde schon mehrmals hier besprochen, Suche Tkinter und After Methode",
            "Sirius": "Sternchen importe sind böse" # oder war das TM bei BJ?
            }
    return random.choice(user_speech.items())
**ungetestet**
Hab heut nen Clown zum Mittag bekommen
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Eleocraft: Verzögerung in Tk-GUIs gehen wie Tholo schon schrieb über die `after()`-Methode die es auf jedem `Widget`-Objekt gibt.

Allerdings ist das gesamte gezeigte Programm zimelich überarbeitungsbedürftig. Sorry wenn das hart klingt, aber selbst wenn man nur die gröbsten Sachen repariert, muss man am Ende fast jede Zeile anfassen oder gar neu schreiben.

Sternchen-Importe sind Böse™. Und Du machst das auch noch mit *jedem* Modul. Du holst Dir da aus *jedem* Modul *alles* in den Namensraum Deines Moduls. Da wird es schwer nachzuvollziehen was woher kommt. Gerade bei `tkinter` sind das deutlich mehr als 100 Namen, von denen nur ein ganz kleiner Bruchteil tatsächlich benötigt wird. Und es werden so nicht nur die dokumentierten Namen aus den Modulen importiert, sondern auch das was diese Module ihrerseits importieren – was nicht dokumentiert ist. Zudem besteht die Gefahr von Namenskollisionen. Aus `random` wird zwar alles importiert, aber anscheinend nichts verwendet.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

``global`` hat in einem sauberen Programm nichts zu suchen. Schon gar nicht wenn man Klassen verwendet, denn dann gibt es für ``global`` so gar keine Ausrede mehr.

Bei GUI-Programmierung kommt man um Klassen nicht herum. Also *echte* Klassen, nicht der Missbrauch von ``class`` um einen Namensraum für Funktionen zu schaffen, wie bei der ”Klasse” `schwerkraft`. Namensräume für Funktionen sind Module in Python.

Der Code hält sich nicht an die in Python üblichen Namenskonventionen – alles wird klein_mit_unterstrichen geschrieben, ausgenommen Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Funktionen und Methoden werden üblicherweise nach der Tätigkeit benannt die sie durchführen. `screen_plate()` ist keine Tätigkeit, ebensowenig `coords()`, `sprung()`, oder `runter()`.

Der Code enthält sehr viele ”magische” Zahlen/Koordinaten, die man besser als Konstanten mit einem sprechenden Bezeichner definieren sollte. Dann versteht man besser was die Werte bedeuten und kann sie auch wesentlich weniger fehleranfällig verändern.

Einbuchstabige Namen sind selten gute Namen. Das globale `c` ist ganz sicher kein guter Name.

Umlaute oder irgendetwas anderes ausserhalb von ASCII sollte man nicht in Namen verwenden. Auch wenn Python selbst damit klar kommt, tut das bei weitem nicht jedes Werkzeug das Python-Code verarbeiten kann. Das Problem hätte man auch nicht wenn man konsequent englische Namen verwenden würde und nicht mal Deutsche und mal Englische.

Deine `coords()`-Funktion ist überflüssig. Die fragt die Koordinaten ab, bindet die Bestandteile an einzelne Namen, erstellt dann in der gleichen Reihenfolge ein Tupel mit den Bestandteilen – also im Grunde nur ein umständliches Umpacken der Ergebnisse von `Canvas.coords()`. Warum?

`collosion` schreibt man mit zwei `l`.

Die `check()`-Funktion gibt entweder 0 oder 1 zurück, je nach dem ob die Bedingungen darin wahr oder falsch sind. Dafür nimmt man keine Zahlen sondern `True` und `False`. Und das ist mit den verschachtelten ``if``/``else``-Konstrukten auch sehr umständlich ausgedrückt. Die Bedingungen selbst ergeben ja `True` oder `False`, also braucht man kein ``if``/``else`` sondern kann die Bedingungen entsprechend verknüpfen und gleich deren Ergebnis zurück geben. Damit wird diese Funktion zu einem Einzeiler:

Code: Alles auswählen

def check(px1, py1, px2, py2, cx1, cy1, cx2, cy2):
    return py2 == cy1 and (cx1 <= px1 < cx2 or cx2 >= px2 > cx1)
Ein besserer Name, der dem Leser verrät *was* da geprüft wird, wäre nicht schlecht. Und die Argumente `py1` und `cy2` werden übergeben, aber gar nicht verwendet. An der Stelle kann man sich auch die Frage stellen ob man da nicht besser die beiden Canvas-Objekt-IDs übergibt und erst *in* dieser Funktion die Koordinaten ermittelt, statt das an den Aufrufstellen jedes mal davor zu erledigen.

Ein ``if`` dessen Körper nur aus einem ``pass`` besteht ist sinnfrei. Das hätte nur einen Effekt wenn der Bedingungsausdruck Seiteneffekte hätte – was die im Code nicht haben, und auch eher nicht haben sollten. Die drei betroffenen ``if``-Konstrukte kann man also ersatzlos streichen, ohne das sich am Programmablauf irgend etwas ändert.

``for i in range(len(sequence)):`` nur im die Laufvariable dann als Index in `sequence` zu verwenden ist in Python ein „anti pattern“ weil man direkt über die Elemente in `sequence` iterieren kann, ohne den Umweg über einen Index. Du machst das ganze dann noch ein bisschen umständlicher in dem Du die Indizes rückwärts aufzählst, ohne dass das irgendwo Sinn ergibt. Und dafür verwendest Du dann nicht `reverse()` sondern jedes mal das nicht so wirklich leicht verständliche ``range(len(sequence)-1, -1, -1)``. Warum um Himmels Willen…

Namen sollten nicht zu weit von dem Punkt definiert werden an dem sie benötigt werden. `colision2` wird erst deutlich später in der Funktion für eine ``while``-Schleife verwendet, also sollte man das nicht am Anfang schon definieren.

Es macht keinen Sinn in der ersten Schleife in `sprung()` weiter zu gehen wenn man eine Kollision festgestellt hat, denn dadurch wird sich das Ergebnis ja nicht mehr ändern. Und auch hier gilt wieder: Keine Zahlen verwenden wenn man eigentlich `True` und `False` meint.

`haupt_colision` kann man sich sparen wenn man das aus dem folgenden ``if`` einfach in den ``if``-Zweig schreibt in dem `haupt_colision` auf 1 gesetzt wird.

Die `update()`-Methode von Tk sollte man nicht selbst verwenden. Das kann gefährlich sein, solange man nicht genau weiss auf was man da achten muss. Die Tk-Dokumentation rät davon dringend ab, sich selbst eine Hauptschleife basteln zu wollen.

`colision2` kann man durch leichtes Umstellen des Codes und eine ``while True:``-Schleife beseitigen, die an der entsprechenden Stelle durch ein ``break`` verlassen wird.

Und auch `haupt_colision2` ist überflüssig, weil der Wert ja nur an den Namen gebunden, und dann gleich in der nächsten Zeile in einer ``if``-Bedingung verwendet wird.

`sprung()` gibt entweder explizit eine 0 zurück oder implizit `None`. Das ist eine komische API, weil auch noch beide Werte im boole'schen Kontext `False` sind. Da will man wohl auch eher `False` und explizit `True` zurück geben wenn die Funktion bis zum Ende durchläuft.

Die `runter()`-Funktion enthält relativ viel Code der auch in `sprung()` steht. Solche Code-Wiederholungen verletzten das DRY-Prinzip („Don't Repeat Yourself“) und führen zu schwerer wart- und änderbarem Code, weil man immer alle Kopien ändern muss, und alle auf die gleiche Weise. Das macht unnötig Arbeit und ist fehleranfällig.

`sprung()` und `runter()` erwarten die Argumente `objekt` und `P2` und bekommen da immer `Player1` und `Player2` übergeben. Die Argumentnamen sind also schlecht. Da mit `Player1` und `Player2` auch immer so ziemlich das gleiche gemacht wird, sollte man die zu einem Objekt zusammenfassen. Und obwohl die Funktionen `Player1` als `objekt` übergeben bekommen, greifen sie trotzdem auf das globale `Player1` zu.

`LevelCheck()` enthält einen lokalen Namen `LevelCheck` – das ist verwirrend. Der Name wird aber auch gar nicht wirlklich gebraucht, weil es nur ein Zwischenergebnis ist das nur einmal und sofort in der nächsten Zeile verwendet wird.

Der globale `runter`-Wert ist verkehrt (oder der Name ist es), denn immer wenn das *nicht wahr* ist, wird der Spieler *runter* bewegt.

Vollkommen ungetesteter Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from time import sleep


def screen_plate(x, y, laenge):
    plate_ids.append(
        canvas.create_rectangle(x, y, x + laenge, y - 10, fill='lime')
    )


def schwerkraft_coords(obj):
    return canvas.coords(obj)


def schwerkraft_check(px1, _py1, px2, py2, cx1, cy1, cx2, _cy2):
    #
    # TODO Remove unused Arguments or even pass just the IDs and not the
    #   coordinates.
    # 
    return py2 == cy1 and (cx1 <= px1 < cx2 or cx2 >= px2 > cx1)


def move_down(player_1, player_2):
    while True:
        canvas.move(player_1, 0, 1)
        canvas.move(player_2, 0, 1)
        screen.update()  # FIXME Use `mainloop()`!
        sleep(0.01)
        check_level_end(level_end, player_1)

        px1, py1, px2, py2 = schwerkraft_coords(player_1)
        if py1 > 705:
            canvas.coords(player_1, 100, 630, 120, 680)
            canvas.coords(player_2, 100, 630, 120, 680)

        for plate_id in plate_ids:
            cx1, cy1, cx2, cy2 = schwerkraft_coords(plate_id)
            if schwerkraft_check(px1, py1, px2, py2, cx1, cy1, cx2, cy2):
                return False
    
    return True


def schwerkraft_sprung(player_1, player_2):
    px1, py1, px2, py2 = schwerkraft_coords(player_1)
    for plate_id in plate_ids:
        cx1, cy1, cx2, cy2 = schwerkraft_coords(plate_id)
        if schwerkraft_check(px1, py1, px2, py2, cx1, cy1, cx2, cy2):
            for _ in range(80):
                canvas.move(player_1, 0, -1)
                canvas.move(player_2, 0, -1)
                screen.update()  # FIXME Use `mainloop()`!
                sleep(0.01)
            return move_down(player_1, player_2)

    return True


def schwerkraft_runter(player_1, player_2):
    px1, py1, px2, py2 = schwerkraft_coords(player_1)
    for plate_id in plate_ids:
        cx1, cy1, cx2, cy2 = schwerkraft_coords(plate_id)
        if not schwerkraft_check(px1, py1, px2, py2, cx1, cy1, cx2, cy2):
            return move_down(player_1, player_2)
    
    return True

        
def level_up(level):
    if 1 <= level <= 3:
        screen_plate(0, 700, 1400)
        screen_plate([200, 500, 1000][level - 1], 630, 30)

                
def check_level_end(level_end, player):
    global level  # FIXME
    px1, py1, px2, py2 = schwerkraft_coords(player)
    cx1, cy1, cx2, cy2 = schwerkraft_coords(level_end)
    if schwerkraft_check(px1, py1, px2, py2, cx1, cy1, cx2, cy2):
        for plate_id in plate_ids:
            canvas.delete(plate_id)
        del plate_ids[:]
        level += 1
        level_up(level)
        print('levelup')
    


def move(event):
    global runter  # FIXME
    
    if event.keysym == 'Up':
        if not runter:
            runter = schwerkraft_sprung(player_1, player_2)
    elif event.keysym == 'Right':
        canvas.move(player_1, 10, 0)
        canvas.move(player_2, 10, 0)
        canvas.itemconfig(player_1, state=tk.NORMAL)
        canvas.itemconfig(player_2, state=tk.HIDDEN)
        check_level_end(level_end, player_1)
        # 
        # FIXME The value doesn't match the name – it's the opposite.
        # 
        if not runter:
            schwerkraft_runter(player_1, player_2)
    elif event.keysym == 'Left':
        canvas.move(player_1, -10, 0)
        canvas.move(player_2, -10, 0)
        canvas.itemconfig(player_1, state=tk.HIDDEN)
        canvas.itemconfig(player_2, state=tk.NORMAL)
        check_level_end(level_end, player_1)
        if not runter:
            schwerkraft_runter(player_1, player_2)

# 
# FIXME All this global stuff needs to disappear in functions and objects!
# 
screen = tk.Tk()
screen.title('jump and run')
canvas = tk.Canvas(screen, width=1400, height=700, bg='blue')
canvas.pack()

level = 1
runter = False
plate_ids = list()
level_end = canvas.create_rectangle(1400, 700, 2000, 710)

player_1 = canvas.create_rectangle(100, 640, 120, 690, fill='red')
player_2 = canvas.create_rectangle(100, 640, 120, 690, fill='green')
canvas.itemconfig(player_2, state=tk.HIDDEN)

canvas.bind_all('<Key>', move)

level_up(level)
#
# FIXME Missing a call to `screen.mainloop()`!
#
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Eleocraft
User
Beiträge: 10
Registriert: Mittwoch 19. September 2018, 11:16

okay das war jetzt ganz schön viel :? :?
ich weiß, dass ich keinen guten programmierstiel habe, aber Ich will das ganze programm nicht nochmal schreiben. Ich werde versuchen die gröbsten sachen bei meinem programm zu fixen.
trotzdem danke für die mühe.
In manchen dingen bin ich nicht komplett deiner meinung: z.b habe ich keine möglichkeit gefunden "global runter" zu ersetzen, auch wenn ich weiß, das man variablen nicht global machen sollte.
außerdem wird dir beim außfüren deines programmes auffallen, dass es nicht wirklich funktioniert :? :? und ich weiß nicht warum, dah ich schwierigkeiten habe den aufbau deines programmes auf die schnelle zu verstehen... nochmal trotzdem danke

die after methode ist genau was ich gesucht habe
ich poste nochmal eine version von meinem programm, sobald ich fertig bin... aber setzt keine zu hohen erwartungen :D :D :D
Antworten