Posts: 148
Threads: 34
Joined: May 2020
Dear deanhystad,
thanks a lot for your effort!
You helped me so much to get this game nearly finished.
May I ask for a last help?
My original idea was to dedicate the game to some people by drawing their individual name with the bricks.
I've got lists with the coordinates of the bricks - each name is stored in a brick_coord_list.
I changed the following lines (307-313) to implement the brick_coord_list - but I failed to get it running.
Is it possible to use just 2 sets of damaged bricks randomly?
I've got a green brick set and a blue brick set.
I changed the following lines (18, 128, 132, 133, 140-144).
(I noticed that the score doesn't work properly...)
It would be great if you could help me again...
Have a nice next week!
Greetings,
flash77
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))
# Define some image files
BALL_IMAGE = "ball.png"
PADDLE_IMAGE = "paddle.png"
BRICK_FILES = ("brick0.png", "brick1.png", "brick2.png")
BRICK_FILESB = ("bbrick0.png", "bbrick1.png", "bbrick2.png")
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 800
SCORE_POSITION = (SCREEN_WIDTH - 150, SCREEN_HEIGHT - 30)
def create_image(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(image_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(image_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()
IMAGES = {} # Dictionary of images. Similar colored bricks share images
def __init__(self, x, y, color=None, hits=None):
self.color = color #or BRICK_COLORS[row % len(BRICK_COLORS)]
hits = hits or random.choice((1, 1, 1, 2, 2, 3))
self.value = self.hits = max(1, min(3, hits))
super().__init__(self.get_image(), self.group)
#self.at(col * self.rect.width, row * self.rect.height * 2 + 60)
self.at(x, y)
def get_image(self):
"""Return an image based on my color and number of hits."""
images = self.IMAGES.get(self.color, None)
if images is None:
# Make brick images for this color
r = random.randint(1, 2)
if r == 1:
images = [create_image(image_file, self.color) for image_file in BRICK_FILES]
else:
images = [create_image(image_file, self.color) for image_file in BRICK_FILESB]
self.IMAGES[self.color] = images
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.get_image()
return 0
self.kill()
return self.hits
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.ymax:
# The got past the paddle
self.lives.kill()
self.active = False
elif pygame.sprite.spritecollide(self, self.paddle.group, False):
# 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)
while len(lives) > 0:
# here I would like to draw a name
# below it is just a test (I've got lists with full coordinates)
brick_coord_list = [
32, 64, 32, 96, 32, 128, 32, 160
]
i = 0
while i < len(brick_coord_list):
#for r, c in product(range(3 + level), range(10)):
Brick(brick_coord_list[i], brick_coord_list[i+1])
allsprites.add(Brick.group)
while len(lives) > 0 and len(Brick.group) > 0:
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()
Posts: 6,800
Threads: 20
Joined: Feb 2020
Feb-28-2022, 04:21 AM
(This post was last modified: Feb-28-2022, 04:21 AM by deanhystad.)
This is a colored rectangle brick that can have text.
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()
WIDTH = 50
HEIGHT = 20
def __init__(self, row, col, color=None, hits=None, text=None, text_color=(255, 255, 255), font=24):
super().__init__(pygame.Surface((self.WIDTH-2, self.HEIGHT-2)), self.group)
self.image.fill(color or BRICK_COLORS[row % len(BRICK_COLORS)])
if text is not None:
text = pygame.font.Font(None, font).render(text, 1, text_color)
x = (self.WIDTH - text.get_width()) // 2
y = (self.HEIGHT - text.get_height()) // 2
self.image.blit(text, (x, y))
hits = hits or random.choice((1, 1, 1, 2, 2, 3))
self.value = self.hits = max(1, min(3, hits))
self.at(col * self.WIDTH, row * self.HEIGHT * 2 + 60)
def __len__(self):
"""Return how many bricks remaining"""
return len(self.group)
def hit(self, score):
"""I was hit! Return score based on being hit. """
self.hits -= 1
if self.hits <= 0:
self.kill()
return self.value if self.hits <= 0 else 0 It would be easy to use an image file and blit the text on the image. HOWEVER, you cannot share image files like I did before. Just as each color required a different image, each text requires a different image.
Posts: 148
Threads: 34
Joined: May 2020
Dear deanhystad,
thanks a lot for your patience!
May I ask again a few questions?
I found a possibility to draw names (dedication) using the bricks.
Just copy/paste the full code and you will see the name "Marco" drawn with the bricks.
I just want to use png files for the bricks (that is easier for me to understand in opposite to color the bricks).
I've got 2 sets of png files:
A green set (3 pngs) and a blue set (3 pngs).
Each set contains the different appearence of the bricks (because of the hits).
I randomnly use the green and the blue bricks.
To achieve this I created a new class "BrickB" and 3 new pngs (bbrick0.png, bbrick1.png, bbrick2.png).
I wasn't able to merge Brick.group and BrickB.group - you can see what I did in the code.
Would you please be so kind and help me to clean up the code?
a) Just use pngs for the bricks and not to color them
b) How to merge Brick.group and BrickB.group
That would be great...
Greetings,
flash77
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))
# Define some image files
BALL_IMAGE = "ball.png"
PADDLE_IMAGE = "paddle.png"
BRICK_FILES = ("brick0.png", "brick1.png", "brick2.png")
BRICK_FILESB = ("bbrick0.png", "bbrick1.png", "bbrick2.png")
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 800
SCORE_POSITION = (SCREEN_WIDTH - 150, SCREEN_HEIGHT - 30)
def create_image(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(image_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(image_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()
IMAGES = {} # Dictionary of images. Similar colored bricks share images
def __init__(self, x, y, color=None, hits=None):
self.color = color # or BRICK_COLORS[row % len(BRICK_COLORS)]
hits = hits or random.choice((1, 1, 1, 2, 2, 3))
self.value = self.hits = max(1, min(3, hits))
super().__init__(self.get_image(), self.group)
# self.at(col * self.rect.width, row * self.rect.height * 2 + 60)
self.at(x, y)
def get_image(self):
"""Return an image based on my color and number of hits."""
images = self.IMAGES.get(self.color, None)
if images is None:
images = [create_image(image_file, self.color) for image_file in BRICK_FILES]
self.IMAGES[self.color] = images
# 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.get_image()
return 0
self.kill()
return self.hits
class BrickB(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()
IMAGES = {} # Dictionary of images. Similar colored bricks share images
def __init__(self, x, y, color=None, hits=None):
self.color = color # or BRICK_COLORS[row % len(BRICK_COLORS)]
hits = hits or random.choice((1, 1, 1, 2, 2, 3))
self.value = self.hits = max(1, min(3, hits))
super().__init__(self.get_image(), self.group)
# self.at(col * self.rect.width, row * self.rect.height * 2 + 60)
self.at(x, y)
def get_image(self):
"""Return an image based on my color and number of hits."""
images = self.IMAGES.get(self.color, None)
if images is None:
images = [create_image(image_file, self.color) for image_file in BRICK_FILESB]
self.IMAGES[self.color] = images
# 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.get_image()
return 0
self.kill()
return self.hits
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)) or (xhits := pygame.sprite.spritecollide(self.at(x2, y1), BrickB.group, False)):
self.dx = -self.dx
if (yhits := pygame.sprite.spritecollide(self.at(x1, y2), Brick.group, False)) or (yhits := pygame.sprite.spritecollide(self.at(x1, y2), BrickB.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.ymax:
# The got past the paddle
self.lives.kill()
self.active = False
elif pygame.sprite.spritecollide(self, self.paddle.group, False):
# 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)
while len(lives) > 0:
# here I draw a name with the bricks
brick_coord_list = [
32, 32, 32, 64, 32, 96, 32, 128, 32, 160, 32, 192, 64, 64, 96, 96, 128, 64, 160,
32, 160, 64, 160, 96, 160, 128, 160, 160, 160, 192, 224, 32, 224, 64, 224, 96, 224,
128, 224, 160, 224, 192, 256, 32, 256, 128, 288, 32, 288, 64, 288, 96, 288, 128,
288, 160, 288, 192, 352, 32, 352, 64, 352, 96, 352, 128, 352, 160, 352, 192, 384,
32, 384, 96, 384, 128, 416, 32, 416, 64, 416, 96, 416, 160, 448, 192, 512, 32, 512,
64, 512, 96, 512, 128, 512, 160, 512, 192, 544, 32, 544, 192, 576, 32, 576, 192, 640,
32, 640, 64, 640, 96, 640, 128, 640, 160, 640, 192, 672, 32, 672, 192, 704, 32, 704,
192, 736, 32, 736, 64, 736, 96, 736, 128, 736, 160, 736, 192
]
i = 0
while i < len(brick_coord_list):
r = random.randint(1, 2)
if r == 1:
Brick(brick_coord_list[i], brick_coord_list[i + 1])
else:
BrickB(brick_coord_list[i], brick_coord_list[i + 1])
i = i + 2
allsprites.add(Brick.group)
allsprites.add(BrickB.group)
while len(lives) > 0 and len(Brick.group) or len(lives) > 0 and len(BrickB.group) > 0:
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()
Posts: 6,800
Threads: 20
Joined: Feb 2020
Bricks and BricksB are identical.
I would change BRICKS_FILES to be this:
BRICK_FILES = (("brick0.png", "brick1.png", "brick2.png"), ("bbrick0.png", "bbrick1.png", "bbrick2.png")) Bricks can randomly pick a group of image files.
With all the image files there's not much reason for an image dictionary. I would modify the code so each Brick has their own images. This will also make it easy to write text on the bricks.
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.
image
"""
group = pygame.sprite.Group()
def __init__(self, x, y, color=None, hits=None, image_files=None):
color = color or random.choice(BRICK_COLORS)
hits = hits or random.choice((1, 1, 1, 2, 2, 3))
hits = max(0, min(len(image_files, hits)))
self.value = self.hits = hits
image_files = image_files or random.choice(BRICK_FILES)
self.images = [create_image(image_file, color) for image_file in image_files]
super().__init__(self.images[self.hits-1], self.group)
self.at(x, y)
def __len__(self):
"""Return how many bricks remaining"""
return len(self.group)
def hit(self):
"""
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]
return 0
self.kill()
return self.value This simplifies the brick creation. The main function doesn't do anything special for bricks.
while len(lives) > 0:
for coords in BRICK_COORDS:
Brick(*coords)
allsprites.add(Brick.group) And I modified the coordinates too.
BRICK_COORDS = (
(32, 32), (32, 64), (32, 96), (32, 128), (32, 160), (32, 192), (64, 64), (96, 96),
(128, 64), (160, 32), (160, 64), (160, 96), (160, 128), (160, 160), (160, 192), (224, 32),
(224, 64), (224, 96), (224, 128), (224, 160), (224, 192), (256, 32), (256, 128), (288, 32),
(288, 64), (288, 96), (288, 128), (288, 160), (288, 192), (352, 32), (352, 64), (352, 96),
(352, 128), (352, 160), (352, 192), (384, 32), (384, 96), (384, 128), (416, 32), (416, 64),
(416, 96), (416, 160), (448, 192), (512, 32), (512, 64), (512, 96), (512, 128), (512, 160),
(512, 192), (544, 32), (544, 192), (576, 32), (576, 192), (640, 32), (640, 64), (640, 96),
(640, 128), (640, 160), (640, 192), (672, 32), (672, 192), (704, 32), (704, 192), (736, 32),
(736, 64), (736, 96), (736, 128), (736, 160), (736, 192)) You could, if you wanted, make a different brick coords list for each level. This will be cleaner if the list is at the top of the file instead of buried inside a method down near the bottom.
Note: I have not run any of this code.
I thought you wanted to put names on the bricks. Do you still need help with that? I noticed your code does not have the create_image() function that lets you write text on the sprite.
Posts: 6,800
Threads: 20
Joined: Feb 2020
Mar-04-2022, 09:07 PM
(This post was last modified: Mar-04-2022, 09:07 PM by deanhystad.)
Seems like a lot of work making brick images just so you can put names on the bricks. In an earlier example I wrote text on rectangular bricks. In this example I do the same thing, but on brick images.
I also fixed a problem with the ball going through the paddle when it is moving really fast. This example also writes words on some of the bricks and makes random brick layouts.
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 = [(x*50, y*20+40) for y in range(6) for x in range(10)]
BRICK_WORDS = "I am very pleased with the way this game turned out now that I fixed the a paddle problem".split()
# Define some image files
BALL_IMAGE = "breakout_ball.png"
PADDLE_IMAGE = "breakout_paddle.png"
BRICK_FILES = ("breakout_brick.png", "breakout_brick2.png", "breakout_brick3.png")
SCREEN_WIDTH = 500
SCREEN_HEIGHT = 600
SCORE_POSITION = (SCREEN_WIDTH-150, SCREEN_HEIGHT-30)
def create_image(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(image_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(image_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
def set_text(self, text, font=18, color=TEXT_COLOR):
"""Draw text on the sprite"""
text = pygame.font.Font(None, font).render(text, 1, color)
x = (self.WIDTH - text.get_width()) // 2
y = (self.HEIGHT - text.get_height()) // 2
self.image.blit(text, (x, y))
# 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 at random.
"""
group = pygame.sprite.Group()
WIDTH = 50
HEIGHT = 20
def __init__(self, x, y, color=None, images=None, hits=None):
color = color or random.choice(BRICK_COLORS)
images = images or BRICK_FILES
hits = hits or random.choice((1, 1, 1, 2, 2, 3))
self.value = self.hits = max(1, min(3, hits))
self.images = [create_image(image, color) for image in images]
super().__init__(self.images[self.hits-1], self.group)
self.at(x, y)
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]
return 0
self.kill()
return self.value
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
def move(self, x):
"""Move to follow the cursor"""
self.centerx = 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):
sprite = 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=8):
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.ymax:
# 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)
while len(lives) > 0:
random.shuffle(BRICK_COORDS)
for coords in BRICK_COORDS[:30]:
Brick(*coords)
for brick, word in zip(Brick.group.sprites(), BRICK_WORDS):
brick.set_text(word)
allsprites.add(Brick.group)
while len(lives) > 0 and len(Brick.group) > 0:
clock.tick(40)
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()
Posts: 148
Threads: 34
Joined: May 2020
Dear deanhystad,
thanks a lot for you answer!
I prefer to draw the names of persons with the bricks - I made an Excel-Macro, which generates the coordinates for me. I've attached the macro code (if you are interested)...
I've got an error, which I can't fix (because I'm not experienced in it)...
BRICK_COORDS = (
(32, 32), (32, 64), (32, 96), (32, 128), (32, 160), (32, 192), (64, 64), (96, 96),
(128, 64), (160, 32), (160, 64), (160, 96), (160, 128), (160, 160), (160, 192), (224, 32),
(224, 64), (224, 96), (224, 128), (224, 160), (224, 192), (256, 32), (256, 128), (288, 32),
(288, 64), (288, 96), (288, 128), (288, 160), (288, 192), (352, 32), (352, 64), (352, 96),
(352, 128), (352, 160), (352, 192), (384, 32), (384, 96), (384, 128), (416, 32), (416, 64),
(416, 96), (416, 160), (448, 192), (512, 32), (512, 64), (512, 96), (512, 128), (512, 160),
(512, 192), (544, 32), (544, 192), (576, 32), (576, 192), (640, 32), (640, 64), (640, 96),
(640, 128), (640, 160), (640, 192), (672, 32), (672, 192), (704, 32), (704, 192), (736, 32),
(736, 64), (736, 96), (736, 128), (736, 160), (736, 192)) while len(lives) > 0:
random.shuffle(BRICK_COORDS)
for coords in BRICK_COORDS[:30]:
Brick(*coords) Traceback (most recent call last):
File "D:\Daten\Breakout neu 2\main.py", line 345, in <module>
main()
File "D:\Daten\Breakout neu 2\main.py", line 310, in main
random.shuffle(BRICK_COORDS)
File "C:\Users\...\AppData\Local\Programs\Python\Python310\lib\random.py", line 394, in shuffle
x[i], x[j] = x[j], x[i]
TypeError: 'tuple' object does not support item assignment Please help me out...
Have a nice weekend!
Attached Files
Thumbnail(s)
Posts: 6,800
Threads: 20
Joined: Feb 2020
You made BRICK_COORDS a tuple. Tuples are immutable. Make it a list instead
Posts: 148
Threads: 34
Joined: May 2020
Dear deanhystad,
I'm sorry for asking again...
I don't want to jangle your nerves...
In post 24 you said:
I would change BRICKS_FILES to be this:
BRICK_FILES = (("brick0.png", "brick1.png", "brick2.png"), ("bbrick0.png", "bbrick1.png", "bbrick2.png")) Bricks can randomly pick a group of image files.
I tried very long but I can't get along...
So I made the BRICK_COORDS as a list (as you said in post 27) and get back to an old state
for the Brick creation (line 307 - 321), because it is easier for me to understand.
What I'm trying to achieve is:
I've got 2 types of bricks:
BRICK_FILES is the green set.
BRICK_FILES = ("brick0.png", "brick1.png", "brick2.png")
BRICK_FILESB is the blue set.
BRICK_FILESB = ("bbrick0.png", "bbrick1.png", "bbrick2.png")
The numbers 0 to 2 (e.g. brick0.png", "brick1.png", "brick2.png) represent the state of the brick damage
- the brick looks more and more damaged if it takes hits.
The pictures should appear in sequence for the green set: brick0.png, brick1.png, brick2.png
The pictures should appear in sequence for the blue set: bbrick0.png, bbrick1.png, bbrick2.png
I would prefer to have 1 BRICK_FILES (as mentioned below) but I couldn't do it.
BRICK_FILES = (("brick0.png", "brick1.png", "brick2.png"), ("bbrick0.png", "bbrick1.png", "bbrick2.png")) I tried to use r = random.int(1,2) to differentiate what type of brick (blue or green) should be used.
Please be so kind and have a look at the code...
Thanks for your patience...
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")
BRICK_FILESB = ("bbrick0.png", "bbrick1.png", "bbrick2.png")
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 800
SCORE_POSITION = (SCREEN_WIDTH - 150, SCREEN_HEIGHT - 30)
def create_image(image_file, r, 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(image_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(image_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, r, color=None, hits=None):
self.color = color # or BRICK_COLORS[row % len(BRICK_COLORS)]
hits = hits or random.choice((1, 1, 1, 2, 2, 3))
self.value = self.hits = max(1, min(3, hits))
super().__init__(self.get_image(r), self.group)
# self.at(col * self.rect.width, row * self.rect.height * 2 + 60)
self.at(x, y)
def get_image(self, r):
"""Return an image based on my color and number of hits."""
if r == 1:
images = [create_image(image_file, r, self.color) for image_file in BRICK_FILES]
else:
images = [create_image(image_file, r, self.color) for image_file in BRICK_FILESB]
# 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.get_image()
return 0
self.kill()
return self.hits
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)
while len(lives) > 0:
# here I draw a name with the bricks
BRICK_COORDS = [
32, 32, 32, 64, 32, 96, 32, 128, 32, 160, 32, 192, 64, 64, 96, 96, 128, 64, 160,
32, 160, 64, 160, 96, 160, 128, 160, 160, 160, 192, 224, 32, 224, 64, 224, 96, 224,
128, 224, 160, 224, 192, 256, 32, 256, 128, 288, 32, 288, 64, 288, 96, 288, 128,
288, 160, 288, 192, 352, 32, 352, 64, 352, 96, 352, 128, 352, 160, 352, 192, 384,
32, 384, 96, 384, 128, 416, 32, 416, 64, 416, 96, 416, 160, 448, 192, 512, 32, 512,
64, 512, 96, 512, 128, 512, 160, 512, 192, 544, 32, 544, 192, 576, 32, 576, 192, 640,
32, 640, 64, 640, 96, 640, 128, 640, 160, 640, 192, 672, 32, 672, 192, 704, 32, 704,
192, 736, 32, 736, 64, 736, 96, 736, 128, 736, 160, 736, 192
]
i = 0
while i < len(BRICK_COORDS):
r = random.randint(1, 2)
Brick(BRICK_COORDS[i], BRICK_COORDS[i + 1], r)
i = i + 2
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(r)
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() File "D:\Daten\Breakout neu 2\main.py", line 355, in <module>
main()
File "D:\Daten\Breakout neu 2\main.py", line 335, in main
ball.move(r)
TypeError: Ball.move() takes 1 positional argument but 2 were given
t sRGB profile
libpng warning: iCCP: cHRM chunk does not match sRGB
libpng warning: iCCP: known incorrect sRGB profile
Posts: 6,800
Threads: 20
Joined: Feb 2020
The way you do the BRICK_COORDS makes them hard to work with. When you couldn't shuffle the BRICK_COORDS it was because BRICK_COORDS was a tuple, not because it contained tuples.
BRICK_COORDS = [(x1, y1), (x2, y2) ... (xN, yN)] # You can shuffle a list of tuples Then you could replace this:
i = 0
while i < len(BRICK_COORDS):
r = random.randint(1, 2)
Brick(BRICK_COORDS[i], BRICK_COORDS[i + 1], r)
i = i + 2 with this:
for coord in BRICK_COORDS:
Brick(*coord) Brick.__init__() is gets to randomly choose image files if none are specified:
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 image_files] # Making the images here
super().__init__(self.get_image(self.hits-1), self.group).at(x, y) Brick.get_image() goes away and Brick.hit() changes to:
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 And BRICK_FILES is defined as:
BRICK_FILES = (("brick0.png", "brick1.png", "brick2.png"), ("bbrick0.png", "bbrick1.png", "bbrick2.png") Do not bury BRICK_COORDS down in main, and you really don't want to put it inside a while loop. What you really, really don't want is to have a list of BRICK_COORDS at the top of the program and another in a while loop inside the main() function.
Posts: 148
Threads: 34
Joined: May 2020
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...
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()
|