Erste Versuche eines Python Neulings

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
Alesch
User
Beiträge: 3
Registriert: Montag 30. März 2009, 21:26

Moinsen,

ich habe einfach mal angefangen mit einem Versuch ein kleines Spiel zu erstellen.

Ich würde gerne einfach mal wissen was an dem Script Müll ist und wo man evtl. anders ansetzen müsste. Geht demnach nicht unbedingt nur speziell um PyGame sonder viel mehr auch um Python.

Ich würde auch gerne wissen warum dieses "Game" schon 20% meiner CPU in Anspruch nimmt. ;)

Hier nun mein Script:

Code: Alles auswählen

import os, sys
import pygame

from helper import *

#
# Game-Logic-Functions
#
#
def pass_gametime(time, minutes):
    h = time[0]
    m = time[1]
    
    m +=  minutes
    
    if m >= 60: #next hour?
        div = m % (60)
        h = h + (m / (60))
        m = div
    
    if h >= 24: #next day?
        h = 0
    
    return [h, m]

#
# Init PyGame
#
#
pygame.init()
#screen = pygame.display.set_mode((0,0), pygame.FULLSCREEN)
screen = pygame.display.set_mode((640,480), 0)
clock = pygame.time.Clock()

gui_screen = pygame.Surface(screen.get_size()) # create gui-screen
gui_screen = gui_screen.convert()

game_screen = pygame.Surface(screen.get_size()) #create game screen
game_screen = game_screen.convert()

EVENT_GAMETIME = 1

#
# Game Vars And Objects
#
#
gui_font = load_font("gui.ttf", 32) # gui font
gui_fontcolor1 = 250, 250, 250 # gui fontcolor

gametime = [8, 0] # start gametime [hours, minutes]
pygame.time.set_timer(EVENT_GAMETIME, (1000 * 10))

#
# Game loop
#
#
while 1:
    time_passed = clock.tick(50)

    # handle user input
    for event in pygame.event.get():
        if event.type == EVENT_GAMETIME: # update the gametime
            gametime = pass_gametime(gametime, 10);
        
        if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE): # observe escape key
            sys.exit()
        
        if event.type == pygame.MOUSEBUTTONDOWN: # mousedown events
            gametime = pass_gametime(gametime, 17);
            
    # update game
    
    # update guiscreen
    text_gametime = gui_font.render("%02.f:%02.f Uhr" % (gametime[0], gametime[1]), 0, gui_fontcolor1)
    gui_screen.fill((0,0,0))
    gui_screen.blit(text_gametime, (15,5))
    
    # update gamescreen
    game_screen.fill((0,0,0))
    
    # render screens
    screen.blit(game_screen, (0,0))
    screen.blit(gui_screen, (0,0))
    
    # display
    pygame.display.flip()
und hier noch dei helper.py welche ich importiere:

Code: Alles auswählen

import os, sys
import pygame

def load_font(file, size):
    'Load\'s awesome fonts!'
    file = os.path.join('/Users/alex/Documents/Python/DerPlaner/data/fonts', file)
    try:
        font = pygame.font.Font(file, size)
    except pygame.error:
        print 'There\'s either an error in the program or a font\'s missing'
    return font 
BlackJack

@Alesch: Die Fehlerbehandlung in `helper.load_font()` ist unsinnig. Wenn ein Fehler auftritt, wird der abgefangen, die Meldung ausgegeben und dann kommt gleich danach der nächste Fehler weil der Name `font` im Fehlerfall ja gar nicht existiert. Die Funktion kann den Fehler doch auch gar nicht sinnvoll behandeln.

Ein hart kodierter Pfad in einer Funktion ist auch nicht so schön. Das Programm läuft so nur bei Dir.

Man kann Zeichenketten auch in " einfassen, dann braucht man einzelne ' nicht zu "escapen". Doctstrings werden in der Regel in """ eingefasst.

Sternchen-Importe sollte man vermeiden, weil man sich damit den Namensraum vollmüllen kann und schnell mal die übersicht verliert was aus welchem Modul kommt. Wenn man das regelmässig macht, also zum Beispiel auch in Modulen die man importiert, kann man auch gleich das Modulkonzept wegwerfen.

In der `pass_gametime()` kann man die Zuweisung an mehrere Namen und die `divmod()`-Funktion einsetzen. Ausserdem funktioniert die Funktion nicht richtig, wenn man Minuten im Wert von mehr als einer Stunde vor Mitternacht addiert. Ungetestet:

Code: Alles auswählen

pass_gametime(time, minutes_delta):
    if minutes_delta < 0:
        raise ValueError('delta must be positive')
    
    hours, minutes = time
    minutes += minutes_delta
    
    if minutes >= 60:
        hour_delta, minutes = divmod(minutes, 60)
        hours += hour_delta
    
    if hours >= 24:
        hours %= 24
    
    return (hours, minutes)
Den Code auf Modulebene sollte man möglichst auf Konstanten-, Klassen- und Funktionsdefinitionen beschränken und alles andere in einer Hauptfunktion verschwinden lassen und die dann so aufrufen:

Code: Alles auswählen

if __name__ == '__main__':
    main()
So kommt man nicht so leicht in Versuchung bei Funtkionen über (modul)globale Namen zu kommunizieren und kann das Modul auch importieren, ohne dass das Programm ausgeführt wird. Zum Beispiel um einzelne Funktionen zu testen.

Der `gui_screen` scheint mir unnötig zu sein!?

Den Ereignistyp 1 gibt es schon. Für eigene Ereignisse kann man nur Nummern zwischen `pygame.USEREVENT` und `pygame.NUMEVENTS` verwenden.

Eine Endlosschleife schreibt man idiomatischer als ``while True:``.
Alesch
User
Beiträge: 3
Registriert: Montag 30. März 2009, 21:26

Vielen Dank für deine Antwort. Die Welt der Python wird zunehmends klarer. ;)

Alles was du zu der Funktion "pass_gametime" gesagt hast, kann ich voll nachvollziehen. Das mit den Sternchen-Imports leuchtet mir auch ein.

Kann ich anstelle von

Code: Alles auswählen

from helper import *
ein

Code: Alles auswählen

import Helper
machen und in dem selben Verzeichnis eine Dateimit dem Namen "Helper.py" anlegen welche folgenden code enthält?

Code: Alles auswählen

import os, sys, pygame

class Helper:
    def pass_gametime(time, minutes_delta):
        return (hours, minutes)
Kann ich dann aus der "main()"-Funktion heraus mit Helper.pass_gametime() darauf zugreifen?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Dir fehlen viele Grundlagen. Arbeite mal das Tutorial durch und / oder lese einfach dazu ein paar andere gute Einführungen. Im wiki findest du dazu viele hilfreiche Sachen:

[wiki]Tutorial[/wiki]

Zu Deinen Fragen:
Wenn Deine Datei "Helper.py" heißt, dann musst Du auch

Code: Alles auswählen

import Helper
schreiben, da der Modulname dem Dateinamen bis auf das ".py" entspricht.

Wenn innerhalb des Moduls eine Klasse gleichen Namens steht, dann kannst Du auf diese folgendermaßen zugreifen:

Code: Alles auswählen

# erste Variante
import Helper
Helper.Helper.pass_gametime()

# oder folgendes:
# hier wird direkt auf eine "Komponente" des Moduls zugegriffen.
# das ist auch mit Funktionen, Variablen usw. möglich
from Helper import Helper
Helper.pass_gametime()
Die Frage ist aber: Wozu eine Klasse einfach in eine andere Datei auslagern? Das macht man häufig in Java so - in Python ist das nicht notwendig. Dort gleiedert man Module eher nach Funktionalität. Das kannst Du auch anhand der Standard-Lib nachvollziehen.

Im Modul random liegen eben alle möglichen Funktionen für Zufallszahlen, in "re" eben alles, was mit Regulären Ausdrücken zu tun hat.

Bevor Du also anfängst, mit eigenen Modulen zu spielen, solltest Du lieber erst einmal die Grundlagen lernen. Dadurch kommst Du dann auch in Kontakt zu den Standardmodulen, die vieles nützliches enthalten, was man immer brauchen kann.
Alesch
User
Beiträge: 3
Registriert: Montag 30. März 2009, 21:26

Ok, dann werde ich dem erstmal nachgehen. Ich danke euch trotzdem für die ausführlichen Antworten. Woanders hätte man sicher nur ein "lern erstmal Python" abbekommen. ;)

Danke!

Eine Frage noch vorab: Sind Python Programme oft in einer Datei geschrieben. Deine Antwort hört sich so an als würde man Klassen nicht in gesonderte Dateien auslagern sondern in der Hauptdatei deklarieren.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Alesch hat geschrieben: Eine Frage noch vorab: Sind Python Programme oft in einer Datei geschrieben. Deine Antwort hört sich so an als würde man Klassen nicht in gesonderte Dateien auslagern sondern in der Hauptdatei deklarieren.
Durchaus! Je nach Problem und Komplexität kann man durchaus ein komplettes Programm in einer Datei realisieren. Oftmals ist das imho sogar sinnvoll.

Ein wenig ist das natürlich auch immer eine Frage des persönlichen Geschmacks. Aber solange man keinen Grund sieht, Code in andere Dateien auszulagern (etwa weil bestimmte Code-Teile eher Bibliotheks-Charakter haben), sollte man das imho nicht tun.

Kleiner Tipp: Benutze ruhig die Such-Funktion hier im Board. Zu vielen Themen gibt es da schon Diskussionen, wo Du erkennen kannst, wo es einen allgemeinen Konsens gibt und wo man sich eher streitet.

Das könnte noch mal interessant sein:
http://www.python.org/dev/peps/pep-0020/
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Alesch hat geschrieben:Eine Frage noch vorab: Sind Python Programme oft in einer Datei geschrieben. Deine Antwort hört sich so an als würde man Klassen nicht in gesonderte Dateien auslagern sondern in der Hauptdatei deklarieren.
Jein. Klassen und Funktionen die thematisch zusammen gehören packt man auch in ein Modul. Bei kleineren übersichtlichen Skripten hat man aber oft auch nur eine einzige Datei.
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Du solltest dich auch nach dem Sinn der Klasse fragen. Was soll ein Helper Objekt sein? Einfach nur eine Sammlung von Funktionen? Dafür gibts Module oder, wenn wirklich eine Sammlung sein soll, ganz normale Listen. Und wenn schon müsste die Klasse so aussehen:

Code: Alles auswählen

class Helper(object):
    def pass_gametime(time, minutes):
        ....
    pass_gametime = staticmethod(pass_gametime)
Was man auch besser mit einem Dekorator ausdrücken kann. Ansonsten wird "time" als "self" Parameter der Methode interpretiert - da steckt keine Magie dahinter.

@Blackjack:

Wieso ist pass_gametime() vor Mitternacht bei minutes > 60 falsch? Ich seh daran nichts falsches. Deine Funktion macht genau das gleiche, nur eleganter und mittels divmod().
BlackJack

@str1442: Die Funktion vom OP hat die Stunden einfach auf 0 gesetzt wenn sie grösser als 23 waren, und nicht auf den Rest von Stunden durch 24.
Antworten