class Background(Sprite, ABC): def __init__(self, filename: str, top: float, speed: float, x: Optional[float] = None, mirror: bool = True): if x is None: x = 2 self.x = x * Const.game_height self.speed = speed self.image = CachedImage(filename, mirror and random.getrandbits(1)) self.height = self.image.get_height() * Const.pixel_size self.width = self.image.get_width() * Const.pixel_size self.y = top def update(self, context: Context, sprites: Sprites): self.x -= context.x_delta * self.speed def render(self, surface: pygame.Surface, size_factor: float): h = int(self.height * size_factor) w = int(self.width * size_factor) img = self.image.scale(w, h) l = int(self.x * size_factor) rect = pygame.Rect(0 if l >= 0 else abs(l), 0, w + min(0, l), h) surface.blit(img, (max(0, l), self.y * size_factor), rect) @property def box(self) -> GameRect: return GameRect(self.x, self.y, self.width, self.height) def can_delete(self) -> bool: return self.x <= -self.width
class Tartan(Sprite): def __init__(self): self.__x = 0 self.meters = Const.offset_meters self.image = CachedImage("res/img/noice.png") self.border = self.image.get_width() * Const.pixel_size self.random_line = [-1, 1] self.score = score.load_score() def update(self, context: Context, sprites: Sprites): self.__x -= context.x_delta self.meters = context.meters while self.__x < -self.border: self.__x += self.border self.random_line[0] -= context.x_delta if self.random_line[0] < 0 and random.random() < 0.05: self.random_line[0] = 3 * Const.game_height self.random_line[1] = random.randint(1, 2) def render(self, surface: pygame.Surface, size_factor: float): area = Const.tartan_area(surface) surface.fill((156, 67, 47), area) size = int(self.image.get_width() * Const.pixel_size * size_factor) img = self.image.scale(size, size) for i in range(0, surface.get_width() // size + 2): surface.blit(img, (i * size + self.__x * size_factor, area.top)) t = int((Const.tartan_top + Const.pixel_size) * size_factor) h = int(Const.tartan_height * 0.38 * size_factor) lines = [ t, t + h, t + 2 * h ] for y in lines: for x in range(0, 3 * Const.game_height): surface.fill((255, 200, 200), pygame.Rect( int((x + self.__x) * size_factor), y, int(size_factor * 1), int(size_factor * Const.pixel_size) )) m = 50 next_meter_in = m - (self.meters - Const.player_position) % m next_meter_str = str(int(((self.meters - Const.player_position) // m + 1) * m)) surface.fill((255, 200, 200), pygame.Rect( int(next_meter_in * size_factor), lines[0], int(size_factor * Const.pixel_size), h )) font = pygame.font.Font("res/arcade.ttf", h) img = font.render(next_meter_str, True, (255, 200, 200)) surface.blit(img, (int((next_meter_in - 2 * Const.pixel_size) * size_factor) - img.get_width(), int(lines[0] + h / 2 - img.get_height() * 0.45))) surface.fill((255, 200, 200), pygame.Rect( int(self.random_line[0] * size_factor), lines[self.random_line[1]], int(size_factor * Const.pixel_size), h )) PADDING = Const.pixel_size * 2 for i, (name, s) in reversed(list(enumerate(self.score))): left = s - (self.meters - Const.player_position) if -Const.game_height < left < Const.game_height * 3: text = f"{i + 1} {name}".rstrip() img = font.render(text, True, (255, 255, 255)) pixel = int(Const.pixel_size * size_factor) surface.fill((133, 94, 66), pygame.Rect( int(left * size_factor) - pixel, int(Const.tartan_top * size_factor - h), int(size_factor * Const.pixel_size) + 2 * pixel, h )) l = int((left - PADDING) * size_factor - img.get_width() // 2) surface.fill((106, 75, 53), pygame.Rect( max(0, l - pixel), int(Const.tartan_top * size_factor - h * 1.5) - pixel, img.get_width() + PADDING * 2 * size_factor + min(0, l) + 2 * pixel, img.get_height() + 2 * pixel )) surface.fill((133, 94, 66), pygame.Rect( max(0, l), int(Const.tartan_top * size_factor - h * 1.5), img.get_width() + PADDING * 2 * size_factor + min(0, l), img.get_height() )) surface.blit(img, ( int(left * size_factor - img.get_width() // 2), int(Const.tartan_top * size_factor - h * 1.5) )) def box(self) -> GameRect: return GameRect(0, 0, 1, 1) def type(self) -> Type: return Type.TARTAN
class Ball(Sprite, ABC): def __init__(self, x=None): if x is None: self.x = 2 * Const.game_height else: self.x = x self.image = CachedImage(self.filename()) self.diameter = self.image.get_width() * Const.pixel_size self.y = Const.game_height - self.diameter self.__vspeed = 0 self.bounced = False self.hit_bottom = False self.id = uuid.uuid4() def update(self, context: Context, sprites: Sprites): self.x -= context.x_delta if self.bounced: self.move_by_gravity(context) def render(self, surface: pygame.Surface, size_factor: float): rect = self.box.to_pygame(size_factor, False) if rect.width <= 0 or rect.height <= 0: return img = self.image.scale(rect.width, rect.height) surface.blit(img, (rect.left, rect.top)) def bounce(self, velocity: float): self.bounced = True self.__vspeed = -velocity / 2 def move_by_gravity(self, context: Context): # time in s t = context.time_factor / Const.fps # gravity in m/(s**2) a = context.gravity self.y = 1 / 2 * a * (t ** 2) + \ self.__vspeed * t + \ self.y self.__vspeed = a * t + self.__vspeed if self.y + self.diameter >= Const.game_height + self.diameter * 0.3 and not self.hit_bottom: self.__vspeed = -self.__vspeed * self.bounciness() self.hit_bottom = True elif self.y + self.diameter < Const.game_height + self.diameter * 0.3 and self.hit_bottom: self.hit_bottom = False @property def box(self) -> GameRect: offset = max(0, (self.y + self.diameter) - Const.game_height) y_delta = 0 if offset > self.diameter * 0.3: y_delta = offset - self.diameter * 0.3 offset = self.diameter * 0.3 return GameRect(self.x, self.y - y_delta, self.diameter, self.diameter - offset) def type(self) -> Type: return Type.BALL def can_delete(self) -> bool: return self.x <= -1 @abstractmethod def filename(self) -> str: pass @abstractmethod def bounciness(self) -> float: """ :return: by how much the velocity of the player is multiplied when he hits the ball """ pass @abstractmethod def immediate_speed_increase(self) -> float: pass @abstractmethod def desired_speed_increase(self) -> float: pass