Seite 1 von 1

Python variable

Verfasst: Mittwoch 19. September 2018, 11:23
von Eleocraft
Ich will in Python(ohne Pygame)ein jump and run programmieren.
aus irgendeinem Grund ist die variable UP (zur Feststellung ob die Figur auf festem Boden steht oder nicht) immer auf 1.
kann mir jemand sagen was ich falsch gemacht habe?

Code: Alles auswählen

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





#hintergrund
himg = PhotoImage(file='Hintergrund.gif')
c.create_image(0, 0, anchor=NW, image=himg)

#Player
img = PhotoImage(file='Player.gif')
Player1 = c.create_image(100, 600, anchor=NW, image=img) 
img2 = PhotoImage(file='Player2.gif')
Player2 = c.create_image(100, 600, anchor=NW, image=img2)
c.itemconfig(Player2, state=HIDDEN)



def Player(event):
    key = event.keysym
    if key == "Left":
        c.move(Player1, -10, 0)
        c.move(Player2, -10, 0)
        c.itemconfig(Player1, state=HIDDEN)
        c.itemconfig(Player2, state=NORMAL)
    elif key == "Right":
        c.move(Player1, 10, 0)
        c.move(Player2, 10, 0)
        c.itemconfig(Player1, state=NORMAL)
        c.itemconfig(Player2, state=HIDDEN)
c.bind_all('<Key>', Player)

def Player_sp(event):
    UP = 1
    
    for i in range(50):
        c.move(Player1, 0, -2)
        c.move(Player2, 0, -2)
        screen.update()
        sleep(0.01)
    UP = 0
    
c.bind_all('<Up>', Player_sp)

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


while True:
    pos = c.coords(Player1)

    if pos[1] > 599:
        UP = 1
            if UP == 0:
        c.move(Player1, 0, 2)
        c.move(Player2, 0, 2)
    screen.update()
    sleep(0.01)
 

Re: Python variable

Verfasst: Mittwoch 19. September 2018, 14:38
von Sirius3
Bei ereignisgesteuerter Programmierung darf es keine Schleifen geben, die längere Zeit arbeiten, ohne die Kontrolle an die Hauptschleife zurückzugeben. Um eine gewisse Frame-Rate einzuhalten, kann man bei Tk after benutzen; screen.update ist dagegen nicht so toll.
Um komplexere GUI Programme zu schreiben, braucht man eigentlich auch objektorientierte Programmierung, um den Zustand des Spiels zwischen den Methoden teilen zu können.

Re: Python variable

Verfasst: Mittwoch 19. September 2018, 14:49
von ThomasL

Code: Alles auswählen

    if pos[1] > 599:
        UP = 1
            if UP == 0:
Wenn die if-Bedingung wahr ist, wird UP auf 1 gesetzt,
direkt danach wird gefragt, ob UP gleich 0 ist.
Kann diese Bedingung jemals wahr sein?
Außerdem stimmt da was mit der Einrückung nicht.

Re: Python variable

Verfasst: Donnerstag 20. September 2018, 10:11
von Eleocraft
thx: da war ein Leerzeichen zu viel...
funktioniert trotzdem nicht.

Re: Python variable

Verfasst: Donnerstag 20. September 2018, 10:15
von Eleocraft
Sirius3 hat geschrieben: Mittwoch 19. September 2018, 14:38 Bei ereignisgesteuerter Programmierung darf es keine Schleifen geben, die längere Zeit arbeiten, ohne die Kontrolle an die Hauptschleife zurückzugeben. Um eine gewisse Frame-Rate einzuhalten, kann man bei Tk after benutzen; screen.update ist dagegen nicht so toll.
Um komplexere GUI Programme zu schreiben, braucht man eigentlich auch objektorientierte Programmierung, um den Zustand des Spiels zwischen den Methoden teilen zu können.
was meinst du mit Tk after?

Re: Python variable

Verfasst: Donnerstag 20. September 2018, 10:18
von __deets__

Re: Python variable

Verfasst: Donnerstag 20. September 2018, 10:33
von Eleocraft
Problem gefunden: da der Player bei y:600 spawnt, wird die variable UP auf 1 gesetzt und nicht mehr zurück...

Code: Alles auswählen


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





#hintergrund
himg = PhotoImage(file='Hintergrund.gif')
c.create_image(0, 0, anchor=NW, image=himg)

#Player
img = PhotoImage(file='Player.gif')
Player1 = c.create_image(100, 600, anchor=NW, image=img) 
img2 = PhotoImage(file='Player2.gif')
Player2 = c.create_image(100, 600, anchor=NW, image=img2)
c.itemconfig(Player2, state=HIDDEN)



def Player(event):
    key = event.keysym
    if key == "Left":
        c.move(Player1, -10, 0)
        c.move(Player2, -10, 0)
        c.itemconfig(Player1, state=HIDDEN)
        c.itemconfig(Player2, state=NORMAL)
    elif key == "Right":
        c.move(Player1, 10, 0)
        c.move(Player2, 10, 0)
        c.itemconfig(Player1, state=NORMAL)
        c.itemconfig(Player2, state=HIDDEN)
c.bind_all('<Key>', Player)

def Player_sp(event):
    UP = 2
    
    for i in range(50):
        c.move(Player1, 0, -2)
        c.move(Player2, 0, -2)
        screen.update()
        sleep(0.01)
    UP = 0
    
c.bind_all('<Up>', Player_sp)

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

#Hauptschleife
while True:
    pos = c.coords(Player1)
    if pos[1] > 599:
        UP = 1
    if pos[1] < 600 and UP != 2:
        UP = 0
    if UP == 0:
        c.move(Player1, 0, 2)
        c.move(Player2, 0, 2)
    screen.update()
    sleep(0.01)

Re: Python variable

Verfasst: Freitag 21. September 2018, 15:12
von __blackjack__
@Eleocraft: Noch ein paar Anmerkungen zum Quelltext:

Um das „screen.update ist dagegen nicht so toll“ von Sirius3 noch mal zu vertiefen: Du hast das in der `Player_sp()`-Funktion stehen, die aufgerufen wird wenn der Benutzer eine Taste drückt. Während die Funktion läuft, sorgt das `update()` dafür das weitere Tasteneingaben verarbeitet werden, was dazu führen kann, das wieder die `Player_sp()`-Funktion aufgerufen wird. Tk garantiert nicht das man während der Abarbeitung eines Handlers wieder Handler auslösen kann ohne das komische Sachen passieren. Das ist also ein Programmierfehler.

Sternchenimporte sind Böse™. Damit holst Du Dir bei `tkinter` mehr als 100 Namen in das aktuelle Modul, von denen Du nur einen Bruchteil benötigst. Es ist schwerer nachvollziehbar wo Namen eigentlich herkommen, und es besteht die Gefahr von Namenskollisionen.

Kommentare sind dazu da dem Leser mitzuteilen *warum* etwas gemacht wird. Sofern das nicht klar ist. Denn *was* gemacht wird, steht ja bereits als Code dort. Sollte man dem Code das nicht entnehmen können, dann kann das auch an schlechten Namen liegen, die unpassend oder nichtssagend sind.

Namen sollten nicht kryptisch abgekürzt werden, sondern dem Leser klar vermitteln was der Wert bedeutet. Also keine einbuchstabigen Namen (ausser der Gültigkeitsbereich ist sehr begrenzt („comprehension“-Syntax, ``lambda``-Ausdrücke)) oder es ist eine der gängigen Ausnahmen wie beispielsweise `i`, `j`, `k` für ganze Zahlen die als Schleifenlaufvariable und/oder Index verwendet werden, oder `x`, `y`, `z` für Koordinaten.

`c` für ein `Canvas`-Objekt geht eher nicht, das sollte man `canvas` nennen (wenn einem nichts passenderes einfällt). `himg` hiesse besser `hintergrund_image` oder um in einer Sprache zu bleiben `background_image`.

Namen durchnummerieren ist ein Warnzeichen das man sich entweder passendere Namen aussuchen sollte, oder die Einzelwerte in einer Datenstruktur besser aufgehoben wären.

An die üblichen Schreibweisen und Namenskonventionen sollte man sich auch halten. Das sind kleinbuchstaben_mit_unterstrichen für alles ausser Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Funktionen und Methoden werden üblicherweise nach der Tätigkeit benannt die sie ausführen. `Player` ist keine Tätigkeit. Und `UP` ist keine Konstante. Zudem ist bei der Variablen nicht so wirklich leicht zu verstehen was die magischen Zahlwerte 0 und 1 die an sie gebunden werden, und 2 das wahrscheinlich auch an `UP` gebunden werden soll, aber nur an ein lokales `UP` in der `Player_sp()`-Funktion gebunden wird, eigentlich bedeuten sollen. Das wäre ein Fall wo man Konstanten für diese Zahlen definieren würde. Da die alle von einem Typ sind, am besten per `enum.Enum` um sie nicht mit anderen Zahlen verwechseln zu können. Da kann man dann sowohl den Zahlen einen sinnvollen Namen geben, als auch den Oberbegriff für diese drei Werte.

Wegen `UP` kommst Du übrigens IMHO hier schon nicht mehr ohne objektorientierte Programmierung aus, denn das muss aus der Funktion für den Sprung nach draussen kommuniziert werden, die ist aber eine Rückruffunktion.

Re: Python variable

Verfasst: Samstag 22. September 2018, 10:41
von __blackjack__
Mal schnell überarbeitet aber komplett ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from enum import auto, Enum
from functools import partial


class HorizontalDirection(Enum):
    LEFT = auto()
    RIGHT = auto()


class VerticalDirection(Enum):
    DOWNWARDS = auto()
    NONE = auto()
    UPWARDS = auto()


class Player:
    
    HORIZONTAL_MOVING_DISTANCE = 10
    VERTICAL_MOVING_DISTANCE = 2
    
    def __init__(self, canvas, left_image_id, right_image_id):
        self.canvas = canvas
        self.image_ids = [left_image_id, right_image_id]
        self.vertical_direction = VerticalDirection.DOWNWARDS
        self.canvas.itemconfig(left_image_id, state=tk.HIDDEN)
    
    @property
    def coordinates(self):
        return self.canvas.coords(self.image_ids[0])
    
    def set_direction(self, direction):
        if direction == HorizontalDirection.LEFT:
            states = [tk.HIDDEN, tk.NORMAL]
        elif direction == HorizontalDirection.RIGHT:
            states = [tk.NORMAL, tk.HIDDEN]
        else:
            raise ValueError(f'wrong direction {direction}')
        
        for image_id, state in zip(self.image_ids, states):
            self.canvas.itemconfig(image_id, state=state)
        
    def move(self, x_delta=0, y_delta=0):
        if x_delta < 0:
            self.set_direction(HorizontalDirection.LEFT)
        elif x_delta > 0:
            self.set_direction(HorizontalDirection.RIGHT)
        
        for image_id in self.image_ids:
            self.canvas.move(image_id, x_delta, y_delta)
    

def do_jump(player, ticks):
    if ticks > 0:
        player.move(y_delta=-Player.VERTICAL_MOVING_DISTANCE)
        player.canvas.after(10, do_jump, player, ticks - 1)
    else:
        player.vertical_direction = VerticalDirection.DOWNWARDS


def start_jump(player, _event=None):
    player.vertical_direction = VerticalDirection.UPWARDS
    do_jump(player, 50)
    

def do_tick(player):
    y_position = player.coordinates[1]
    if y_position > 599:
        player.vertical_direction = VerticalDirection.NONE
    
    if (
        y_position < 600
        and player.vertical_direction != VerticalDirection.UPWARDS
    ):
        player.vertical_direction = VerticalDirection.DOWNWARDS
    
    if player.vertical_direction == VerticalDirection.DOWNWARDS:
        player.move(y_delta=Player.VERTICAL_MOVING_DISTANCE)
    
    player.canvas.after(10, do_tick, player)
    

def main():
    root = tk.Tk()
    root.title('jump and run')
    
    canvas = tk.Canvas(root, width=1400, height=700)
    canvas.pack()

    background_image = tk.PhotoImage(file='Hintergrund.gif')
    canvas.create_image(0, 0, anchor=tk.NW, image=background_image)

    right_player_image = tk.PhotoImage(file='Player.gif')
    player_right_id = canvas.create_image(
        100, 600, anchor=tk.NW, image=right_player_image
    )
    left_player_image = tk.PhotoImage(file='Player2.gif')
    player_left_id = canvas.create_image(
        100, 600, anchor=tk.NW, image=left_player_image
    )
    player = Player(canvas, player_left_id, player_right_id)
    
    canvas.bind_all(
        '<Left>', lambda _e: player.move(-Player.HORIZONTAL_MOVING_DISTANCE)
    )
    canvas.bind_all(
        '<Right>', lambda _e: player.move(Player.HORIZONTAL_MOVING_DISTANCE)
    )
    canvas.bind_all('<Up>', partial(start_jump, player))

    do_tick(player)
    root.mainloop()


if __name__ == '__main__':
    main()