Mar-06-2022, 09:03 AM
Dear deanhystad,
I'm sorry for asking again.
I made the changes you mentioned.
You said "Brick.get_image() goes away":
In Brick class in line 139 I found a "self.get_image(self.hits - 1)"...
How should I replace it?
I would prefer to use just the Brick-pngs, the paddle-png and the ball-png, because the error below occurs and
I think that is easier to understand instead of specify a color.
Please be so kind and have a look at the code...
(I'm sorry for annoying again.)
I wish you a pleasant sunday...
I'm sorry for asking again.
I made the changes you mentioned.
You said "Brick.get_image() goes away":
In Brick class in line 139 I found a "self.get_image(self.hits - 1)"...
How should I replace it?
I would prefer to use just the Brick-pngs, the paddle-png and the ball-png, because the error below occurs and
I think that is easier to understand instead of specify a color.
Please be so kind and have a look at the code...
(I'm sorry for annoying again.)
I wish you a pleasant sunday...
Traceback (most recent call last): File "C:\Users\...\AppData\Local\Programs\Python\Python310\lib\site-packages\PIL\Image.py", line 2957, in open fp.seek(0) AttributeError: 'tuple' object has no attribute 'seek' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "D:\Daten\Breakout neu 2\main.py", line 338, in <module> main() File "D:\Daten\Breakout neu 2\main.py", line 305, in main Brick(*coord) File "D:\Daten\Breakout neu 2\main.py", line 138, in __init__ self.images = [create_image(file, color) for file in BRICK_FILES] # Making the images here File "D:\Daten\Breakout neu 2\main.py", line 138, in <listcomp> self.images = [create_image(file, color) for file in BRICK_FILES] # Making the images here File "D:\Daten\Breakout neu 2\main.py", line 41, in create_image image = Image.open(file).convert("RGB") File "C:\Users\...\AppData\Local\Programs\Python\Python310\lib\site-packages\PIL\Image.py", line 2959, in open fp = io.BytesIO(fp.read()) AttributeError: 'tuple' object has no attribute 'read'
import random, math, pygame from PIL import Image from itertools import product # Define colors used by the game. TEXT_COLOR = (255, 255, 255) BACKGROUND = (0, 0, 200) FOREGROUND = (0, 0, 0) # Recolor image pixels that are this color TRANSPARENT = (255, 255, 255) # Make image pixels this color transparent BALL_COLOR = (220, 220, 220) PADDLE_COLOR = (255, 255, 0) BRICK_COLORS = ((255, 0, 0), (255, 50, 0), (255, 100, 0), (255, 150, 0), (255, 200, 0), (255, 255, 0)) BRICK_COORDS = [(32, 32), (64, 32), (96, 32), (160, 32), (288, 32), (320, 32), (352, 32), (416, 32), (448, 32), (480, 32), (576, 32), (608, 32), (640, 32), (32, 64), (160, 64), (288, 64), (352, 64), (416, 64), (480, 64), (576, 64), (640, 64), (32, 96), (160, 96), (288, 96), (352, 96), (416, 96), (480, 96), (576, 96), (640, 96), (32, 128), (64, 128), (96, 128), (160, 128), (288, 128), (352, 128), (416, 128), (448, 128), (480, 128), (576, 128), (608, 128), (640, 128), (32, 160), (160, 160), (288, 160), (352, 160), (416, 160), (448, 160), (576, 160), (640, 160), (32, 192), (160, 192), (288, 192), (352, 192), (416, 192), (480, 192), (576, 192), (640, 192), (32, 224), (160, 224), (192, 224), (224, 224), (288, 224), (320, 224), (352, 224), (416, 224), (512, 224), (576, 224), (640, 224)] # Define some image files BALL_IMAGE = "ball.png" PADDLE_IMAGE = "paddle.png" BRICK_FILES = (("brick0.png", "brick1.png", "brick2.png"), ("bbrick0.png", "bbrick1.png", "bbrick2.png")) SCREEN_WIDTH = 800 SCREEN_HEIGHT = 800 SCORE_POSITION = (SCREEN_WIDTH - 150, SCREEN_HEIGHT - 30) def create_image(file, color=None): """ Create image from a file. If color is specified, replace all FOREGROUND pixels with color pixels. Modify image so TRANSPARENT colored pixels are transparent. """ if color: # Recolor the image image = Image.open(file).convert("RGB") for xy in product(range(image.width), range(image.height)): if image.getpixel(xy) == FOREGROUND: image.putpixel(xy, color) image = pygame.image.fromstring(image.tobytes(), image.size, "RGB") else: image = pygame.image.load(file) image.set_colorkey(TRANSPARENT) return image.convert() class EnhancedSprite(pygame.sprite.Sprite): """ Sprite with image and rectangle. I expose some of my rectangle's properties. """ def __init__(self, image, group=None, **kwargs): super().__init__(**kwargs) self.image = image self.rect = image.get_rect() if group is not None: group.add(self) def at(self, x, y): """Convenience method for setting my position""" self.x = x self.y = y return self # Properties below expose properties of my rectangle so you can use # self.x = 10 or self.centery = 30 instead of self.rect.x = 10 @property def x(self): return self.rect.x @x.setter def x(self, value): self.rect.x = value @property def y(self): return self.rect.y @y.setter def y(self, value): self.rect.y = value @property def centerx(self): return self.rect.centerx @centerx.setter def centerx(self, value): self.rect.centerx = value @property def centery(self): return self.rect.centery @centery.setter def centery(self, value): self.rect.centery = value @property def right(self): return self.rect.right @right.setter def right(self, value): self.rect.right = value @property def bottom(self): return self.rect.bottom @bottom.setter def bottom(self, value): self.rect.bottom = value class Brick(EnhancedSprite): """ A target for the ball. After I take some number of hits I die. Number of hits I can take is in range 1 to 3. Hits is randomly selected if not specified. Specify brick color using (R, G, B) format. If color not specified a color is selected based on the row. """ group = pygame.sprite.Group() def __init__(self, x, y, image_files=None, color=None, hits=None): color = color or random.choice(BRICK_COLORS) hits = hits or random.choice((1, 1, 1, 2, 2, 3)) self.value = self.hits = max(1, min(3, hits)) image_files = image_files or random.choice(BRICK_FILES) # Randomly picking files self.images = [create_image(file, color) for file in BRICK_FILES] # Making the images here super().__init__(self.get_image(self.hits - 1), self.group).at(x, y) #def get_image(self): #"""Return an image based on my color and number of hits.""" #images = [create_image(image_file, self.color) for image_file in BRICK_FILES] # Make brick images for this color #return images[self.hits - 1] def __len__(self): """Return how many bricks remaining""" return len(self.group) def hit(self, score): """ I was hit! Update my appearance or die based on my hit total. Return score based on being hit. """ self.hits -= 1 if self.hits > 0: self.image = self.images[self.hits - 1] # Images are not shared anymore. Each brick has their own. return 0 self.kill() return self.value # This was hits, but hits is 0 class Paddle(EnhancedSprite): """The sprite the player moves around to redirect the ball""" group = pygame.sprite.Group() def __init__(self, bottom): super().__init__(create_image(PADDLE_IMAGE, PADDLE_COLOR), self.group) self.bottom = bottom self.xmin = self.rect.width // 2 # Compute paddle x range. self.xmax = SCREEN_WIDTH - self.xmin def move(self, x): """Move to follow the cursor. Clamp to window bounds""" self.centerx = max(self.xmin, min(self.xmax, x)) class LifeCounter(): """Keep track of lives count. Display lives remaining using ball image""" def __init__(self, x, y, count=5): self.x, self.y = x, y self.image = create_image(BALL_IMAGE, BALL_COLOR) self.spacing = self.image.get_width() + 5 self.group = pygame.sprite.Group() self.reset(count) def reset(self, count): """Reset number of lives""" self.count = count for c in range(count - 1): EnhancedSprite(self.image, self.group).at(self.x + c * self.spacing, self.y) def __len__(self): """Return number of lives remaining""" return self.count def kill(self): """Reduce number of lives""" if self.count > 1: self.group.sprites()[-1].kill() self.count = max(0, self.count - 1) class Ball(EnhancedSprite): """Ball bounces around colliding with walls, paddles and bricks""" group = pygame.sprite.Group() def __init__(self, paddle, lives, speed=10): super().__init__(create_image(BALL_IMAGE, BALL_COLOR), self.group) self.paddle = paddle self.lives = lives self.speed = speed self.dx = self.dy = 0 self.xmax = SCREEN_WIDTH - self.rect.width self.ymax = self.paddle.bottom - self.rect.height self.reset(0) def reset(self, score=None): """Reset for a new game""" self.active = False if score is not None: self.score = score def start(self): """Start moving the ball in a random direction""" angle = random.random() - 0.5 # Launch angle limited to about +/-60 degrees self.dx = int(self.speed * math.sin(angle)) self.dy = -int(self.speed * math.cos(angle)) self.active = True def move(self): """Update the ball position. Check for collisions with bricks, walls and the paddle""" if not self.active: # Sit on top of the paddle self.centerx = self.paddle.centerx self.bottom = self.paddle.y - 2 return self # Did I hit some bricks? Update the bricks and the score x1, y1 = self.x, self.y x2, y2 = x1 + self.dx, y1 + self.dy if (xhits := pygame.sprite.spritecollide(self.at(x2, y1), Brick.group, False)): self.dx = -self.dx if (yhits := pygame.sprite.spritecollide(self.at(x1, y2), Brick.group, False)): self.dy = -self.dy if (hits := set(xhits) or set(yhits)): for brick in hits: self.score += brick.hit(self.score) # Did I hit a wall? if x2 <= 0 or x2 >= self.xmax: self.dx = -self.dx hits = True if y2 <= 0: self.dy = abs(self.dy) hits = True # Did I hit or get past the paddle? if y2 >= self.paddle.y: # Did it get past the paddle? if self.x > self.paddle.right or self.right < self.paddle.x: self.lives.kill() self.active = False else: # I hit the paddle. Compute angle of reflection bangle = math.atan2(-self.dx, self.dy) # Ball angle of approach pangle = math.atan2(self.centerx - self.paddle.centerx, 30) # Paddle angle rangle = (pangle - bangle) / 2 # Angle of reflection self.dx = math.sin(rangle) * self.speed self.dy = -math.cos(rangle) * self.speed hits = True if hits: self.at(x1, y1) else: self.at(x2, y2) def main(): """Play game until out of lives or out of bricks""" def displayText(text, font, pos=None, color=TEXT_COLOR): text = font.render(text, 1, color) if pos is None: pos = ((SCREEN_WIDTH - text.get_width()) // 2, (SCREEN_HEIGHT - text.get_height()) // 2) screen.blit(text, pos) pygame.init() screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("Breakout") clock = pygame.time.Clock() allsprites = pygame.sprite.Group() score_font = pygame.font.Font(None, 34) try: level = 1 lives = LifeCounter(10, SCREEN_HEIGHT - 30) paddle = Paddle(bottom=SCREEN_HEIGHT - 40) ball = Ball(paddle, lives) allsprites.add(paddle.group, lives.group, ball.group) for coord in BRICK_COORDS: Brick(*coord) allsprites.add(Brick.group) while len(lives) > 0 and len(Brick.group): clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT: raise SystemExit elif event.type == pygame.MOUSEMOTION: paddle.move(event.pos[0]) elif event.type == pygame.MOUSEBUTTONUP: if not ball.active: ball.start() ball.move() screen.fill(BACKGROUND) displayText(f"Score: {ball.score}", font=score_font, pos=SCORE_POSITION) allsprites.draw(screen) pygame.display.flip() if len(lives) == 0: displayText("Game over", font=pygame.font.Font(None, 74)) elif len(Brick.group) == 0: level += 1 displayText(f"Level {level}", font=pygame.font.Font(None, 74)) ball.speed *= 1.25 ball.reset(ball.score) pygame.display.flip() pygame.time.wait(3000) finally: pygame.quit() if __name__ == "__main__": main()