Pygame - Pong!

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Ich habe mich mal an pygame versucht und ein einfaches Pong gebaut. Scheint sogar relativ schnell zu laufen.

Da ich noch nicht viel Erfahrung mit Python habe würde ich mich über Feedback natürlich sehr freuen.

Aber nun zum Code:

Code: Alles auswählen

#!/usr/bin/env python
"""Pong implemented in python/pygame"""
import sys
import time
import math

import pygame

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

class Rect(object):
    """Represents a rectangle"""

    def __init__(self, left, top, width, height):
        self.left = left
        self.top = top
        self.width = width
        self.height = height

    def get_right(self):
        return self.left + self.width

    def set_right(self, right):
        self.width = right-self.left

    right = property(get_right, set_right)

    def get_bottom(self):
        return self.top + self.height

    def set_bottom(self, bottom):
        self.height = bottom - self.top
    
    bottom = property(get_bottom, set_bottom)

    def __getitem__(self, i):
        if i == 0:
            return int(self.left)
        if i == 1:
            return int(self.top)
        if i == 2:
            return int(self.width)
        if i == 3:
            return int(self.height)

    def __len__(self):
        return 4

class Ball(pygame.sprite.Sprite):
    """Renders the ball and does the corresponding logic"""
    
    def __init__(self, pong):
        pygame.sprite.Sprite.__init__(self)
        self.pong = pong
        self.image = pygame.Surface((20, 20), pygame.HWSURFACE)
        self.image.fill(BLACK)
        pygame.draw.circle(self.image, (255, 255, 255), (10, 10), 10)
        self.image.set_colorkey(BLACK)
        self.rect = Rect(self.pong.width/2.0, self.pong.height/2.0, 20, 20)
        self.vx = 0.6
        self.vy = 0.8
    
    def update(self, t):
        newrect = Rect(self.rect.left + self.vx * t, self.rect.top + self.vy*t,
                self.rect.width, self.rect.height)
        # hit userpanel?
        if self.rect.left >= self.pong.userpanel.rect.right\
            and newrect.left <= self.pong.userpanel.rect.right\
            and self.rect.top + ((self.rect.left\
                - self.pong.userpanel.rect.right)\
                / (self.rect.left - newrect.left))*t*self.vy\
                < self.pong.userpanel.rect.bottom\
            and self.rect.top + ((self.rect.left\
                - self.pong.userpanel.rect.right)\
                / (self.rect.left - newrect.left))*t*self.vy\
                > self.pong.userpanel.rect.top:
            self.rect.left = 2 * self.pong.userpanel.rect.right - newrect.left
            self.vx *= -1
        # hit ai panel
        elif self.rect.right <= self.pong.aipanel.rect.left\
            and newrect.right >= self.pong.aipanel.rect.left\
            and self.rect.top + ((self.rect.right\
                - self.pong.aipanel.rect.right)\
                / (self.rect.right - newrect.right)) * t * self.vy\
                < self.pong.aipanel.rect.bottom\
            and self.rect.top + ((self.rect.right\
                - self.pong.aipanel.rect.right)\
                / (self.rect.right - newrect.right)) * t * self.vy\
                > self.pong.aipanel.rect.top:
            self.rect.right = 2 * self.pong.aipanel.rect.left - newrect.right
            self.vx *= -1
        # hit left?
        elif newrect.left <= 0:
            self.pong.score.player2 += 1
            self.rect.left = -newrect.left
            self.vx *= -1
        # hit right?
        elif newrect.right >= self.pong.width:
            self.pong.score.player1 += 1
            self.vx *= -1
        else:
            self.rect.left = newrect.left
        # hit top?
        if newrect.top <= 0:
            self.vy *= -1
            self.rect.top = -newrect.top
        # hit bottom?
        elif newrect.bottom >= self.pong.height:
            self.vy *= -1
            self.rect.bottom = 2*self.pong.height-(newrect.bottom)
        # hit nothing...
        else:
            self.rect.top = newrect.top

class Panel(pygame.sprite.Sprite):
    """Base Panel"""
    
    def __init__(self, pong, x):
        pygame.sprite.Sprite.__init__(self)
        self.pong = pong
        self.image = pygame.Surface((10, 100), pygame.HWSURFACE)
        self.image.fill(WHITE)
        self.rect = pygame.Rect(x, 10, 10, 100)
    
    def move(self, y):
        self.rect.top = y - self.rect.height/2

class UserPanel(Panel):
    """User Controlled Panel"""
    
    def __init__(self, pong, x):
        Panel.__init__(self, pong, x)

    def update(self, t):
        self.move(pygame.mouse.get_pos()[1])

class AIPanel(Panel):
    """Computer Controlled Panel"""

    def __init__(self, pong, x):
        Panel.__init__(self, pong, x)
        self.speed = 0.8
        self.move(pong.height/2)

    def update(self, t):
        diff = self.pong.ball.rect.top - self.rect.centery
        if diff > self.speed*t:
            diff = self.speed*t
        elif diff*-1 > self.speed*t:
            diff = self.speed*t*-1
        if diff > 0 and diff < 1:
            diff = 1
        elif diff < 0 and diff > -1:
            diff = -1
        self.move(math.ceil(self.rect.centery+diff))

class Score(pygame.sprite.Sprite):
    """Displays the current score"""

    def __init__(self, pong):
        pygame.sprite.Sprite.__init__(self)
        self.pong = pong
        self.image = pygame.Surface((200, 80), pygame.HWSURFACE)
        self.rect = pygame.Rect(pong.width/2-100, 10, 200, 80)
        self.font = pygame.font.SysFont("Monospaceh", 32)
        self.font.set_bold(True)
        self.font.set_italic(False)
        self._player1 = 0
        self._player2 = 0
        colonpos = 100-self.font.size(":")[0]/2
        self.image.blit(self.font.render(":", 1, (255, 255, 255)),
                (colonpos, 0))
        self.render_player1()
        self.render_player2()

    def get_player1(self):
        return self._player1
    
    def set_player1(self, score):
        self._player1 = score
        self.render_player1()
    
    player1 = property(get_player1, set_player1)

    def render_player1(self):
        self.image.fill(BLACK, (0, 0, 90, 80))
        pos = 90-self.font.size(str(self._player1))[0]
        self.image.blit(self.font.render(str(self._player1), 2, WHITE),
                (pos, 0))
    
    def get_player2(self):
        return self._player2
    
    def set_player2(self, score):
        self._player2 = score
        self.render_player2()
    
    player2 = property(get_player2, set_player2)
    
    def render_player2(self):
        self.image.fill(BLACK, (110, 0, 100, 80))
        self.image.blit(self.font.render(str(self._player2), 2, WHITE),
                (110, 0))


class Pong(object):
    """The pong game class"""
    
    def __init__(self, width=800, height=600, fullscreen=False):
        
        pygame.display.init()
        pygame.font.init()
        self.width = width
        self.height = height
        modes = pygame.HWSURFACE | pygame.DOUBLEBUF
        if fullscreen:
            modes |= pygame.FULLSCREEN
        self.screen = pygame.display.set_mode((width, height), modes)
        pygame.display.set_caption('PyPong')
        self.screen.fill(BLACK)
        
        self.clock = pygame.time.Clock()
        self.ball = Ball(self) 
        self.userpanel = UserPanel(self, 10)
        self.aipanel = AIPanel(self, width-20)
        self.score = Score(self)
        self.sprites = pygame.sprite.OrderedUpdates((self.score,
            self.userpanel, self.aipanel,self.ball))

        self.main()
    
    def main(self):
        i = 0
        print ""
        while 1:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return
                elif event.type == pygame.KEYUP:
                    if event.key == pygame.K_ESCAPE:
                        return
            self.tick(self.clock.tick(250))
            # for some reason sdl does busy waiting using clock.tick()
            # this takes away the load from the cpu
            time.sleep(0.000005)
            i += 1
            if i % 100 == 0:
                print "\033[1F%s FPS" % str(
                        round(self.clock.get_fps())).rjust(8," ")

    def tick(self, t):
        if t == 0:
            return
        self.sprites.update(t)
        self.sprites.clear(self.screen, 
                lambda surf,rect: surf.fill((0,0,0), rect))
        pygame.display.update(self.sprites.draw(self.screen))


if __name__ == "__main__":
    # psyco seems to be useless here O_o
    #try:
    #    import psyco
    #    print "Using psyco!"
    #except ImportError:
    #    print "Psyco not avaible"
    APP = sys.argv.pop(0)
    if(len(sys.argv) == 0):
        Pong()
    elif(len(sys.argv) == 2):
        Pong(int(sys.argv[0]), int(sys.argv[1]))
    elif(len(sys.argv) == 3):
        Pong(int(sys.argv[0]), int(sys.argv[1]), sys.argv[2]=="fullscreen")
    else:
        print "Usage: %s [width height [fullscreen]]" % APP
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Nett! Aber ich werde das Gefühl nicht los, dass man die Schlagrichtung nicht beeinflussen kann und der Computergegner sich so bewegt, dass er immer den Ball trifft ;)
BlackJack

Warum wurde denn hier eine `Rect`-Klasse definiert? `pygame` bietet doch auch schon eine an, die den Quelltext an einigen Stellen sicher vereinfachen kann. Zum Beispiel kann man ein `pygame.Rect` verschieben oder eine verschobene Kopie bekommen und es gibt eine Methode mit der man überprüfen kann, ob ein `Rect` mit einem anderen kollidiert. Das liesse sich beim Kollisionstest mit dem Ball benutzen.

Ob ein Wert zwischen zwei anderen liegt, kann man in Python etwas kürzer und IMHO verständlicher ohne ``and`` schreiben:

Code: Alles auswählen

#         if diff > 0 and diff < 1: 
#             diff = 1 
#         elif diff < 0 and diff > -1: 
#             diff = -1 
        if 0 < diff < 1:
            diff = 1
        elif -1 < diff < 0:
            diff = -1
Beim Anzeigen der Framerate ist der Aufruf von `str()` überflüssig, das ist im Format '%s' schon enthalten. Das abschneiden der Nachkommestellen und formatieren mit Leerzeichen kann man auch mit einem Format lösen:

Code: Alles auswählen

                print "\033[1F%8d FPS" % self.clock.get_fps()
Psyco bringt bei dem Programm nicht viel, weil die meiste Zeit auf den Benutzer gewartet wird und die aufwändigen Teile, also die Grafik, sowieso in C programmiert sind.
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Y0Gi hat geschrieben:Nett! Aber ich werde das Gefühl nicht los, dass man die Schlagrichtung nicht beeinflussen kann und der Computergegner sich so bewegt, dass er immer den Ball trifft ;)
Hm, der Computergegner sollte nicht immer treffen. Kann jedoch vorkommen wenn die Framerate sehr hoch ist.

Zum Beeinflussen, ja ist derzeit nicht möglich. Wie genau stellst du dir das denn vor?
BlackJack hat geschrieben:Warum wurde denn hier eine `Rect`-Klasse definiert? `pygame` bietet doch auch schon eine an, die den Quelltext an einigen Stellen sicher vereinfachen kann.
Die Pygame Klasse arbeitet nur mit Integers, um den Ball unabhängig von der Framerate zu machen brauche ich jedoch Float Präzision.
BlackJack hat geschrieben: Zum Beispiel kann man ein `pygame.Rect` verschieben oder eine verschobene Kopie bekommen und es gibt eine Methode mit der man überprüfen kann, ob ein `Rect` mit einem anderen kollidiert. Das liesse sich beim Kollisionstest mit dem Ball benutzen.
Meine derzeitige Kollisionserkennung arbeitet derzeit etwas anders und zumindest theoretisch besser als es mit dem Prüfen der Rects möglich wäre. Derzeit sehe ich das Panel sowie die derzeitige und zukünftige Position als Strecken an und überprüfe ob diese sich kreuzen. Theoretisch müsste ich das ganze noch Rekursiv machen (der Ball könnte ja in einem Frame von zwei Orten abprallen). Die Methode mit den Rects wäre aber Definitiv schneller.

Vielen dank für den Tipp mit dem Vergleichen und Formatieren, das wusste ich nicht.
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

veers hat geschrieben:Zum Beeinflussen, ja ist derzeit nicht möglich. Wie genau stellst du dir das denn vor?
Nun, ist es nicht so, dass bei Pong der Ball in einem spitzeren Winkel vom schläger abprallt, je weiter außen er den Schläger berührt? Genau damit kann man ja ein wenig "zielen". Oder war das nur bei Arkanoid und Konsorten so? Ich glaube nicht.
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Y0Gi hat geschrieben:
veers hat geschrieben:Zum Beeinflussen, ja ist derzeit nicht möglich. Wie genau stellst du dir das denn vor?
Nun, ist es nicht so, dass bei Pong der Ball in einem spitzeren Winkel vom schläger abprallt, je weiter außen er den Schläger berührt? Genau damit kann man ja ein wenig "zielen". Oder war das nur bei Arkanoid und Konsorten so? Ich glaube nicht.
Hm kann sein, muss das mal bei ner anderen Implementierung ansehen. Wiederspricht aber zumindest meiner Logik. Was es gäbe wäre natürlich ein gewisser "Drall" wenn die Kugel auf das Panel trifft, Fällt mir aber gerade nicht ein wie ich das einfach Simulieren könnte (:
Antworten