PyProcessign key Problem

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
yoda
User
Beiträge: 14
Registriert: Montag 23. Juli 2018, 11:49

Ich versuche gerade dieses Video https://www.youtube.com/watch?v=cXgA1d_E-jY&t=750s in Python nachzuprogrammieren. Hier der Code:

Code: Alles auswählen

class Vogel:
    schwerkraft = 5 # Kraft, die Vogel runterzieht
    lift = -10 # Kraft, die Vogel hochzieht
    
    def __init__(self):
        self.y = 500 / 2
        self.x = 64
        self.geschwindigkeit = 0
    
    def hochfliegen(self):
        self.geschwindigkeit += Vogel.lift
    
    def show(self):
        fill(255)
        ellipse(self.x, self.y, 30, 30)
        
    def update(self):
        self.geschwindigkeit += Vogel.schwerkraft
        self.geschwindigkeit *= 0.9
        self.y += self.geschwindigkeit
        
        if self.y > height:
            self.y = height 
            self.geschwindigkeit = 0
            
        if self.y < 0:
            self.y = 0 

Code: Alles auswählen

from bird import *

vogel = Vogel()

def setup():
    size(500, 500)

def draw():
    background(51)
    vogel.show()
    if key == ' ':
        vogel.hochfliegen()
    vogel.update()
    
Leider funktioniert es nicht richtig: Sobald ich die Leertaste drücke, klebt der Kreis am oberen Bildschirmende. Witzigerweise kann ich dann Pfeiltaste nach unten drücken und er fällt wieder herunter (obwohl ich das nirgendwo programmiert habe :D). Hat jemand eine Idee, woran es liegen könnte?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Du schreibst hier was ab, was auf gruselige Weise globale Variablen mit globalem Bildschirmupdates mischt. So wie das hochfliegen Programmiert ist, wird der Vogel innerhalb von Bruchteilen von Sekunden hunderte Pixel nach oben fliegen.
yoda
User
Beiträge: 14
Registriert: Montag 23. Juli 2018, 11:49

Das Original (was funktioniert) sieht so aus:

Code: Alles auswählen

function Bird() {
  this.y = height/2;
  this.x = 64;

  this.gravity = 0.7;
  this.lift = -12;
  this.velocity = 0;

  this.show = function() {
    fill(255);
    ellipse(this.x, this.y, 32, 32);
  }

  this.up = function() {
    this.velocity += this.lift;
  }

  this.update = function() {
    this.velocity += this.gravity;
    // this.velocity *= 0.9;
    this.y += this.velocity;

    if (this.y > height) {
      this.y = height;
      this.velocity = 0;
    }

    if (this.y < 0) {
      this.y = 0;
      this.velocity = 0;
    }

  }

}
Was habe ich übersehen?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist Javascript und in einigen Details anders als Deine Python-Übersetzung.
yoda
User
Beiträge: 14
Registriert: Montag 23. Juli 2018, 11:49

Das ist mir klar, aber es sollte doch genauso funktionieren, oder?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ohne deinen gesamten Code zu sehen, der auch die Ereignisbehandlung macht, ist das schwierig nachzuvollziehen. Und deine Gravitation ist >7 mal staerker als im Original. Last but not least ist der gesamte Code (auch schon aus dem Original) fehlerhaft, da man fuer solche Berechnungen ja mit Werten wie zB 9.81 Metern/*SEKUNDE* arbeitet. Man muss also bei zB 60 Frames die Beschleunigung mit der verflossenen Zeit von ~0.016 Sekunden multiplizieren, bevor man sie auf die Geschwindigkeit addiert. Und genauso die Postion mit einem 60stel der Geschwindigkeit veraendern. Am besten misst man in jedem Frame einfach das Delta zum Frame davor, und gibt diese "elapsed" Zeit in die update-Methoden ein.
yoda
User
Beiträge: 14
Registriert: Montag 23. Juli 2018, 11:49

@__deets__ ich habe meinen gesamten Code oben gepostet. Mich wundert halt immer noch stark, warum ich den Kreis mit Drücken der Runter-Pfeiltaste nach unten bewegen kann.
Die Zahlen im Original funktionieren aber, daran kann es nicht liegen.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das ist der gesamte Code? Interessant. Ich habe mit processing mit Python keine Erfahrung. Das da mit globalen Variablen gearbeitet wird, kann ich mir fast nicht vorstellen. Hast du mal einen Tutorial link? Wenn ich danach suche, komme ich auf viele Dinge, die anders aussehen als das, was du da machst.
yoda
User
Beiträge: 14
Registriert: Montag 23. Juli 2018, 11:49

Das Video ist wie gesagt von hier: https://www.youtube.com/watch?v=cXgA1d_E-jY&t=750s. Processing-Infos findest du hier https://py.processing.org. Processing scheint für Python auch nicht sehr verbreitet zu sein. Ich arbeite nur damit, weil es dazu diese Youtube-Videos gibt (also zu der JavaScript-Version), die es für z.B. Pygame so nicht gibt :)
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ok, dann ist dein Problem das hier:

https://py.processing.org/reference/key.html

Es wird dich wahrscheinlich nicht besonders beeindrucken wenn ich das sage, aber dieses Vorgehen, dass da eine globale Variable 'key' existiert, und aus der liest du, da wird mir kotzuebel. Das ist wirklich ganz schlecht gemacht. (um das klarzustellen: dafuer kannst du ja nix, aber das spricht in meinen Augen nicht fuer py-processing...)

Wie dem auch sei: lies dir mal genau durch, was da in der Doku steht, und woher es dann kommt, dass du durch das druecken einer anderen Taste den Kreis wieder runterkommen lassen kannst. Es sollte dabei uebrigens auch voellig egal sein, ob das eine Pfeiltaste ist, oder Q oder W oder was auch immer.
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Hallo yoda,

freut mich zu lesen, dass du auch ein Fan von Daniel Shiffman "The Coding Train" bist.
Ich hatte ihn Anfang des Jahres entdeckt und war auch begeistert von seinen Videos die er mit Processing erstellt hat.
Da ich aber nur einige Wochen zuvor mit Python angefangen hatte, wollte ich nicht nach Processing (Java) wechseln.
Hatte dann auch einige Tage mit dem Python Modul für Processing experimentiert aber schnell festgestellt, dass das nicht so das wahre ist.
Stimme da voll und ganz __deets__ zu.
Nun, was blieb mir übrig und das ist das was ich dir von Herzen empfehlen kann: Arbeite dich ein wenig in Pygame ein
und programmiere die Challenges von Daniel in Python nach, es ist nicht schwer und ich habe eine Menge dadurch gelernt.
Natürlich hatte ich auch Flappy Bird nachprogrammiert und um dir zu zeigen, wie sowas aussehen kann, hier mal der Code.
Ist schon einige Monate alt, könnte mir vorstellen, eventuell einige Sachen heute anders zu machen. Man beachte bitte auch meine Signatur!

Code: Alles auswählen

import pygame as pg
import random
from pygame.locals import *
import os

BLACK = [0, 0, 0]
WHITE = [255, 255, 255]
RED = [255, 0, 0]


class Bird:

    def __init__(self, canvas):
        self.canvas = canvas
        self.y = self.canvas.height // 2
        self.x = 64
        self.gravity = 0.5
        self.lift = -10
        self.velocity = 0

    def show(self):
        pg.draw.circle(self.canvas.screen, BLACK, [int(self.x), int(self.y)], 10, 0)

    def up(self):
        self.velocity += self.lift

    def update(self):
        self.velocity += self.gravity
        self.velocity *= 0.9
        self.y += self.velocity
        if self.y > self.canvas.height:
            self.y = self.canvas.height
            self.velocity = 0
        if self.y < 0:
            self.y = 0
            self.velocity = 0
        self.y = int(self.y)


class Pipe:

    def __init__(self, canvas):
        self.canvas = canvas
        self.top = random.randint(100, self.canvas.height - 200)
        self.bottom = self.top + random.randint(100, 150)
        self.x = self.canvas.width + 10
        self.w = 20
        self.speed = 1
        self.highlight = False

    def hits(self, bird):
        if not self.highlight:
            if (bird.y <= self.top) or (bird.y >= self.bottom):
                if self.x <= bird.x <= self.x + 1:
                    self.highlight = True
                    return True

    def show(self):
        col = BLACK
        if self.highlight:
            col = RED
        pg.draw.rect(self.canvas.screen, col, [self.x, 0, self.w, self.top], 0)
        pg.draw.rect(self.canvas.screen, col, [self.x, self.bottom, self.w, self.canvas.height], 0)

    def update(self):
        self.x -= self.speed

    def offscreen(self):
        return self.x < 0 - self.w


class Canvas:

    def __init__(self, fullhd=False, width=800, height=800, mode=0):
        self.fullhd = fullhd
        self.width = width
        self.height = height
        self.mode = mode

        os.environ['SDL_VIDEO_CENTERED'] = '1'
        pg.init()
        self.clock = pg.time.Clock()
        if not self.fullhd:
            pg.display.set_caption('Flappy Bird')
            # icon_img = pg.image.load_extended('.\\icon.png')   # if there´s an icon
            # pg.display.set_icon(icon_img)
        self.screen = pg.display.set_mode([width, height], mode)
        self.screen.fill(WHITE)
        pg.display.update()
        # pg.key.set_repeat(0)    # set key repeat rate


def main():
    canvas = Canvas()  # windowed mode
    # canvas = Canvas(fullhd=True, width=1920, height=1080, mode=FULLSCREEN)  # fullscreen mode

    # mouse_pos = [0, 0]  #  not used in this game

    bird = Bird(canvas)
    pipes = [Pipe(canvas)]

    frame_count = 0
    pause = main_exit = False
    while not main_exit:
        canvas.clock.tick(60)  # set fps rate
        for event in pg.event.get():
            if event.type == QUIT:
                main_exit = pause = True
                break
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    main_exit = pause = True
                if event.key == K_UP or event.key == K_w:
                    bird.up()
                # if event.key == K_DOWN or event.key == K_s:
                #     pass
                # if event.key == K_LEFT or event.key == K_a:
                #     pass
                # if event.key == K_RIGHT or event.key == K_d:
                #     pass
                if event.key == K_p:
                    pause = not pause   # pause the action
            # elif event.type == KEYUP:
            #     pass
            # elif event.type == MOUSEMOTION:
            #     mouse_pos = pg.mouse.get_pos()
            #     button1, button2, button3 = pg.mouse.get_pressed()
            #     if button1:
            #         pass
            #     if button2:
            #         pass
            #     if button3:
            #         pass
            # elif event.type == MOUSEBUTTONDOWN:
            #     if event.button == 3:  # right mouse button
            #         pass
            #     if event.button == 4:  # mouse scroll up
            #         pass
            #     if event.button == 5:  # mouse scroll down
            #         pass

        # button1, button2, button3 = pg.mouse.get_pressed()
        # if button1:
        #     pass
        # if button2:
        #     pass
        # if button3:
        #     pass

        if not pause:
            frame_count += 1
            canvas.screen.fill(WHITE)  # clear surface

            for i in range(len(pipes)):
                pipes[i].show()
                pipes[i].update()
                if pipes[i].hits(bird):
                    print(" HIT")

            if pipes[0].offscreen():
                pipes = pipes[1:]

            bird.update()
            bird.show()

            if frame_count % 300 == 0:
                pipes.append(Pipe(canvas))

            pg.display.update()

    pg.quit()

if __name__ == '__main__':
    main()
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

@ThomasL: ich weiss, dass das alter Code ist. Trotzdem ein paar Anmerkungen:


- es ist ein beliebter Fehler, die Framerate als konstant anzunehmen. Die Doku sagt das recht klar: tick(fps) beschraenkt die Framerate nach *oben*, nicht aber nach unten. Was ja auch logisch ist. Dann aber will man im Grunde *immer* den Rueckgabewert von tick auswerten, um damit das Delta fuer den naechsten Schritt zu bestimmen.
- dieses Delta muss natuerlich dann in allen updates bezueglich Postion etc. verwandt werden.
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

__deets__ hat geschrieben: Sonntag 23. September 2018, 13:14 - es ist ein beliebter Fehler, die Framerate als konstant anzunehmen. Die Doku sagt das recht klar: tick(fps) beschraenkt die Framerate nach *oben*, nicht aber nach unten. Was ja auch logisch ist. Dann aber will man im Grunde *immer* den Rueckgabewert von tick auswerten, um damit das Delta fuer den naechsten Schritt zu bestimmen.
- dieses Delta muss natuerlich dann in allen updates bezueglich Postion etc. verwandt werden.
no problem, diese Thematik ist sogar sehr wichtig und durchaus komplex.
Ich hatte auch irgendwann mal im Rahmen dieser "Spielereien" diesen Link hier http://www.pygame.org/wiki/ConstantGameSpeed gebookmarked,
mich dann aber anderen interessanten Dingen wie Perceptrons in pure Python, dann mit Numpy, dann mit Cython, Pandas, Matplotlib, Scikit-Learn, Tensorflow, Keras beschäftigt.
Ist erst ein paar Monate her aber kommt mir vor, als wenn ich das in meiner Kindheit gemacht hätte.
Naja, softwareentwicklungstechnisch gesehen stimmt das ja.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ThomasL: Ein paar Anmerkungen zu dem Quelltext:

In der Hauptfunktion ist das ``for i in range(len(sequence)):``-Anti-Pattern. Man kann da direkt, ohne einen Umweg über einen Index über die `Pipe`-Objekte iterieren.

Vom Datentyp her wäre eine `collections.deque` passender für `pipes`.

Klassen die nur aus einer `__init__()` bestehen sind fast nie wirklich Klassen. Denn selbst Klassen die aus einer `__init__()` und einer zusätzlichen Methode bestehen, sind sehr oft einfach nur umständlich geschriebene Funktionen.

Das betrifft `Canvas`. Da werden zudem alle Argumente der `__init__()` an das Objekt gebunden, obwohl später nur zwei davon tatsächlich benötigt werden.

Man kann die ”Klasse” in einem ersten Schritt durch eine Funktion ersetzen, die ein von `collections.namedtuple` abgeleitetes Objekt zurück gibt das die Attribute `screen`, `width`, `height`, und `clock` besitzt. Wobei ich `clock` da auch gleich heraus nehmen würde, denn das gehört eigentlich nicht zu einer Leinwand und könnte problemlos ein lokaler Name in der Hauptfunktion sein.

Im nächsten Schritt kann man dann auch dieses `namedtuple` loswerden, denn das enthält redundante Daten. `screen` ist ein `Surface` und von diesen Objekten kann man die Höhe und Breite abfragen. Es macht wenig Sinn die gleichen Werte extern noch einmal zu speichern.

`Pipe.hits()` hat inkonsistente Rückgabewerte. Statt `True` und `None` sollte die Methode `True` und `False` liefern.

Die verschachtelten ``if``\s kann man alle zusammenfassen.

``bird.y <= self.top or bird.y >= self.bottom`` hätte ich wahrscheinlich eher als ``not self.top <= bird.y < self.bottom`` ausgedrückt.

Das `fullhd`-Argument ist ein bisschen komisch, denn man kann da `True` angeben, aber trotzdem eine Auflösung die nicht Full-HD ist.

Code: Alles auswählen

#!/usr/bin/env python3
import os
import random
from collections import deque

import pygame as pg
from pygame.locals import *

BLACK = [0, 0, 0]
WHITE = [255, 255, 255]
RED = [255, 0, 0]


def create_canvas(fullhd=False, width=800, height=800, mode=0):
    os.environ['SDL_VIDEO_CENTERED'] = '1'
    pg.init()
    if not fullhd:
        pg.display.set_caption('Flappy Bird')
    screen = pg.display.set_mode([width, height], mode)
    screen.fill(WHITE)
    pg.display.update()
    return screen


class Bird:
    
    GRAVITY = 0.5
    LIFT = -10

    def __init__(self, canvas):
        self.canvas = canvas
        self.y = self.canvas.get_height() // 2
        self.x = 64
        self.velocity = 0

    def show(self):
        pg.draw.circle(self.canvas, BLACK, [self.x, self.y], 10, 0)

    def up(self):
        self.velocity += self.LIFT

    def update(self):
        self.velocity = (self.velocity + self.GRAVITY) * 0.9
        self.y = int(self.y + self.velocity)
        if self.y > self.canvas.get_height():
            self.y = self.canvas.get_height()
            self.velocity = 0
        if self.y < 0:
            self.y = 0
            self.velocity = 0


class Pipe:
    
    WIDTH = 20
    SPEED = 1
    
    def __init__(self, canvas):
        self.canvas = canvas
        self.top = random.randint(100, self.canvas.get_height() - 200)
        self.bottom = self.top + random.randint(100, 150)
        self.x = self.canvas.get_width() + self.WIDTH // 2
        self.highlight = False

    def hits(self, bird):
        if (
            not self.highlight
            and not self.top <= bird.y < self.bottom
            and self.x <= bird.x <= self.x + 1
        ):
            self.highlight = True
            return True
        return False

    def show(self):
        color = RED if self.highlight else BLACK
        pg.draw.rect(self.canvas, color, [self.x, 0, self.WIDTH, self.top], 0)
        pg.draw.rect(
            self.canvas,
            color,
            [self.x, self.bottom, self.WIDTH, self.canvas.get_height()],
            0
        )

    def update(self):
        self.x -= self.SPEED

    def is_offscreen(self):
        return self.x < -self.WIDTH


def main():
    canvas = create_canvas()  # windowed mode
    # canvas = create_canvas(
    #     fullhd=True, width=1920, height=1080, mode=FULLSCREEN
    # )  # fullscreen mode

    bird = Bird(canvas)
    pipes = deque([Pipe(canvas)])

    clock = pg.time.Clock()
    frame_count = 0
    pause = main_exit = False
    while not main_exit:
        clock.tick(60)  # set fps rate
        for event in pg.event.get():
            if event.type == QUIT:
                main_exit = pause = True
                break
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    main_exit = pause = True
                if event.key == K_UP or event.key == K_w:
                    bird.up()
                if event.key == K_p:
                    pause = not pause   # pause the action

        if not pause:
            frame_count += 1
            canvas.fill(WHITE)  # clear surface

            for pipe in pipes:
                pipe.update()
                pipe.show()
                if pipe.hits(bird):
                    print(' HIT')

            if pipes[0].is_offscreen():
                pipes.popleft()

            bird.update()
            bird.show()

            if frame_count % 300 == 0:
                pipes.append(Pipe(canvas))

            pg.display.update()

    pg.quit()


if __name__ == '__main__':
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
yoda
User
Beiträge: 14
Registriert: Montag 23. Juli 2018, 11:49

Vielen Dank für die zahlreichen Antworten :) Ich werde mal in Pygame reinschauen, scheint ja auch gut zu gehen.
yoda
User
Beiträge: 14
Registriert: Montag 23. Juli 2018, 11:49

@ThomasL: Ich habe zuerst mit Tensorflow angefangen und beschäftige mich momentan reinforcement learning und würde das gerne mit sowas wie Processing bzw. Pygame anwenden. Falls du (oder andere hier) gute Ressourcen parat haben, die in diese Richtung gehen, würde ich mich freuen :)
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

__blackjack__ hat geschrieben: Sonntag 23. September 2018, 16:02 @ThomasL: Ein paar Anmerkungen zu dem Quelltext:
Ich wusste das du ein "paar Anmerkungen" für mich hast. <schmunzel> wie gesagt, Code aus meiner Python "Kindheit".
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Macht ja nix von wann der Code ist, und die Anmerkungen sind ja auch für andere die das hier lesen. :-)
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
ThomasL
User
Beiträge: 1366
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

@yoda:
Code Bullet macht witzige RL videos https://www.youtube.com/channel/UC0e3Qh ... h5VVpKHH9Q
Playlist zu Introduction to Reinforcement Learning by David Silver https://www.youtube.com/watch?v=2pWv7GO ... 2MfCFzFObQ
Siraj Raval hat viel mit RL gemacht https://www.youtube.com/channel/UCWN3xx ... Kwht9FuE5A
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Antworten