Pong på 200 rader

I våras satte en kollega igång med att koda ”lunar lander” och jag tänkte att något sådant vore ju kul att prova på. Men jag satte ribban lite lägre och siktade på Pong.

Pong är ett arkadspel som  från början tillverkades av Atari där två platta ”rackets” (eller direkt översatt paddlar) slår en boll (läs kvadrat) fram och tillbaka över skärmen i ett försök att få motståndaren att släppa förbi bollen. Givetvis är detta en typ av elektronisk pingis, det borde vara uppenbart för alla.

Jag skrev det hela med hjälp av python och bibliotekt pygame. Jag har försökt att vara strukturerad och utnyttja den objektorienterade naturen i python men det kan ha blivit lite rörigt trots mina goda avsikter.

Min implementation av Pong

Min implementation av Pong

import os, sys
import time, random
import pygame
from pygame.locals import *

class game:
  scoreP1 = 0
  scoreP2 = 0
  hold = 30
  def __init__(self):
    self.disp = pygame.display.set_mode((640,480))
    pygame.display.set_caption('Pong')
    pygame.mouse.set_visible(0)
    self.font = pygame.font.Font(None,40)
    self.screen = pygame.Surface(self.disp.get_size())
    self.screen = self.screen.convert()
    self.screen.fill((0,0,0))
    self.disp.blit(self.screen, (0,0))
    pygame.display.flip()
    self.disp.blit(self.screen, (0,0))
    pygame.display.flip()
    self.b = ball(self.disp)
    self.p1 = paddle(self.disp)
    self.p1.setpos((10, self.screen.get_height() / 2))
    self.p2 = paddle(self.disp)
    self.p2.setpos((self.screen.get_width() - 20, self.screen.get_height() / 2))
  def drawScore(self):
    self.tP1 = self.font.render(str(self.scoreP1), 1 , (255, 255, 255))
    scoreP1pos = self.tP1.get_rect(centerx= 200)
    self.disp.blit(self.tP1, scoreP1pos)
    self.tP2 = self.font.render(str(self.scoreP2), 1 , (255, 255, 255))
    scoreP2pos = self.tP2.get_rect(centerx=self.screen.get_width() - 200)
    self.disp.blit(self.tP2, scoreP2pos)
  def play(self, keys):
    if "P1_UP" in keys:
      self.p1.move(-5)
    if "P1_DOWN" in keys:
      self.p1.move(5)
    if "P2_UP" in keys:
      self.p2.move(-5)
    if "P2_DOWN" in keys:
      self.p2.move(5)
    if self.hold > 0:
      self.hold = self.hold - 1
    else:
      self.b.move()
    self.disp.blit(self.screen, (0,0))
    self.p1.draw()
    self.p2.draw()
    self.cRes = self.b.detectCollision()
    if self.cRes != 0:
      if self.cRes == 1:
        self.scoreP2 = self.scoreP2 + 1
      if self.cRes == 2:
        self.scoreP1 = self.scoreP1 + 1
      self.b.reset()
      self.hold = 30

    self.b.draw()
    self.drawScore()
    pygame.display.flip()

class GameObject:
  x = 0
  y = 0
  size = 0
  def __init__(self, screen):
    self.world = screen
  def gfxInit(self,size):
    self.gfx = pygame.Surface(size)
    self.gfx = self.gfx.convert()
  def setpos(self, coordinates):
    if len(coordinates) == 2:
      self.x = coordinates[0]
      self.y = coordinates[1]
  def getpos(self):
    return (self.x, self.y)
  def draw(self):
    self.world.blit(self.gfx, (self.x, self.y), self.gfx.get_rect())

class ball(GameObject):
  def __init__(self, screen):
    self.world = screen
    self.gfxInit((12,12))
    self.gfx.fill((255,255,255))
    self.reset()
  def reset(self):
    self.x = int(self.world.get_width() / 2)
    self.y = int(self.world.get_height() / 2)
    self.bounce = 0
    self.speed = 1
    #set initial directions
    self.dirY = random.choice([-1, 1])
    self.dirX = random.choice([-1, 1])
  def move(self):
    self.x = int(self.x + (5 * self.speed * self.dirX))
    self.y = int(self.y + (5 * self.speed * self.dirY))

  def detectCollision(self):
    #collision with top or bottom wall
    if (self.y < 5) or (self.y > self.world.get_height()-15):
      self.dirY = self.dirY * -1
      return 0
    #collision with "goal" area
    if self.x < 5:
      return 1
    elif self.x > (self.world.get_width() - 15):
      return 2
    #collision with paddle
    else:
      if (self.world.get_at((self.x-1, self.y +  0))[0] == 255) \
      or (self.world.get_at((self.x-1, self.y + 12))[0] == 255) \
      or (self.world.get_at((self.x + 13, self.y +  0))[0] == 255) \
      or (self.world.get_at((self.x + 13, self.y + 12))[0] == 255):
        self.dirX = self.dirX * -1
        #increase speed every tenth paddle bounce
        if self.bounce >= 11:
          self.speed = self.speed + 0.2
          self.bounce = 0
        else:
          self.bounce = self.bounce + 1
    return 0

class paddle(GameObject):
  def __init__(self, screen):
    self.world = screen
    self.gfxInit((12, 80))
    self.gfx.fill((255,255,255))
    self.size = 80
  def move(self, distance):
    self.y += distance
    if self.y < 0:
      self.y = 0
    if self.y > (self.world.get_height() - self.size):
      self.y = (self.world.get_height() - self.size)
if not pygame.font:
  print "Font not available"
if not pygame.mixer:
  print "Mixer not available"

pygame.init()

random.seed(time.time())
quit = False
pong = game()
keyList = []
tOld =  time.time()
tCurr = time.time()
#Main game loop
while quit != True:
  while (tCurr - tOld) < 0.012:
    time.sleep(0.001)
    tCurr = time.time()

  tOld = tCurr
  for event in pygame.event.get():
    if event.type == QUIT:
      quit = True
      break
    elif event.type == KEYDOWN:
      if event.key == K_ESCAPE:
        quit = True
        break
      if event.key == K_UP:
        keyList.append('P2_UP')
      if event.key == K_DOWN:
        keyList.append('P2_DOWN')
      if event.key == K_w:
        keyList.append('P1_UP')
      if event.key == K_s:
        keyList.append('P1_DOWN')
    elif event.type == KEYUP:
      if event.key == K_UP:
        keyList.remove('P2_UP')
      if event.key == K_DOWN:

        keyList.remove('P2_DOWN')
      if event.key == K_w:
        keyList.remove('P1_UP')
      if event.key == K_s:
        keyList.remove('P1_DOWN')
  pong.play(keyList)

  tCurr = time.time()

Min variant saknar dock för tillfället funktionen som gör att bollens vinkel ändras beroende på var på paddeln den träffar. För att använda koden här ovan krävs att pygame installeras. pygame används för att hantera grafik och tangentbordtryckningar. I övrigt har jag försökt arbeta med objektorienterade metoder så gott det går. GameObject är grundklassen för objekten paddle och ball som hanterar respektive objekt. game-klassen initierar all grafik och alla objekt, paddlar skapas för de båda spelarna (p1 och p2) och bollen (b) placeras i centrum av planen. game’s funktion play hanterar uppdateringar av poäng, uppdateringar av skärmen samt flyttar på objekten.

Huvudloopen ligger i toppnivå i botten av filen (logiskt….kanske inte), den hanterar all timing och registrerar knapptryckningar och kallar periodiskt på play-funktionen för att uppdatera spelplanen.

Ifall någon vill ha pygame förklarat i mer detalj kan jag tänka mig att göra ett mer detaljerat inlägg.

Etiketter: , ,

Kommentera

Fyll i dina uppgifter nedan eller klicka på en ikon för att logga in:

WordPress.com Logo

Du kommenterar med ditt WordPress.com-konto. Logga ut / Ändra )

Twitter-bild

Du kommenterar med ditt Twitter-konto. Logga ut / Ändra )

Facebook-foto

Du kommenterar med ditt Facebook-konto. Logga ut / Ändra )

Google+ photo

Du kommenterar med ditt Google+-konto. Logga ut / Ändra )

Ansluter till %s


%d bloggare gillar detta: