Загрузка данных


import pygame, math, random

pygame.init()
W, H = 1000, 650
GROUND_Y = 590
screen = pygame.display.set_mode((W, H))
pygame.display.set_caption("Angry Birds - 10 Levels")
clock = pygame.time.Clock()
font = pygame.font.SysFont("arial", 28, bold=True)
font_big = pygame.font.SysFont("arial", 64, bold=True)
font_small = pygame.font.SysFont("arial", 20)

GRAVITY = 0.45
SLING_X, SLING_Y = 150, GROUND_Y - 80
MAX_DRAG = 110
BIRD_RADIUS = 15

SKY_TOP = (135, 206, 250)
SKY_BOTTOM = (200, 230, 255)


def gradient_sky():
    surf = pygame.Surface((W, H))
    for y in range(H):
        t = y / H
        r = SKY_TOP[0] + (SKY_BOTTOM[0]-SKY_TOP[0]) * t
        g = SKY_TOP[1] + (SKY_BOTTOM[1]-SKY_TOP[1]) * t
        b = SKY_TOP[2] + (SKY_BOTTOM[2]-SKY_TOP[2]) * t
        pygame.draw.line(surf, (r, g, b), (0, y), (W, y))
    return surf

sky = gradient_sky()
clouds = [(random.randint(0, W), random.randint(30, 200), random.randint(40, 70)) for _ in range(6)]


# ---------- ГЕНЕРАЦИЯ УРОВНЕЙ ----------
def generate_level(n):
    random.seed(n * 17 + 5)
    blocks = []
    pigs = []
    num_towers = 2 + n // 3
    start_x = 550
    gap = (W - 80 - start_x) / max(1, num_towers - 1) if num_towers > 1 else 0

    for t in range(num_towers):
        tower_x = start_x + gap * t if num_towers > 1 else start_x + 150
        tower_h = random.randint(2, 2 + n // 2)
        bw, bh = 42, 40
        for level in range(tower_h):
            by = GROUND_Y - bh - level * bh
            blocks.append({"x": tower_x - bw/2, "y": by, "w": bw, "h": bh,
                            "hp": 30 + n * 4, "max_hp": 30 + n * 4, "vy": 0})
        # свинка на верху башни (не всегда)
        if random.random() < 0.85:
            pigs.append({"x": tower_x, "y": GROUND_Y - tower_h * bh - 18, "r": 18, "alive": True})

    # немного свинок прямо на земле между башнями
    extra_pigs = n // 3
    for _ in range(extra_pigs):
        px = random.randint(int(start_x - 60), W - 100)
        pigs.append({"x": px, "y": GROUND_Y - 18, "r": 18, "alive": True})

    birds_count = max(3, 3 + len(pigs) - n // 2)
    return {"blocks": blocks, "pigs": pigs, "birds": birds_count}


LEVELS = [generate_level(i) for i in range(10)]


def reset_level_state(level_index):
    lvl_data = generate_level(level_index)
    return {
        "blocks": lvl_data["blocks"],
        "pigs": lvl_data["pigs"],
        "birds_left": lvl_data["birds"],
        "birds_total": lvl_data["birds"],
    }


class Bird:
    def __init__(self):
        self.x, self.y = SLING_X, SLING_Y
        self.vx, self.vy = 0, 0
        self.launched = False
        self.trail = []
        self.stopped_timer = 0

    def launch(self, vx, vy):
        self.vx, self.vy = vx, vy
        self.launched = True

    def update(self):
        if not self.launched:
            return
        self.vy += GRAVITY
        self.x += self.vx
        self.y += self.vy
        self.trail.append((self.x, self.y))
        if len(self.trail) > 12:
            self.trail.pop(0)

        if self.y + BIRD_RADIUS > GROUND_Y:
            self.y = GROUND_Y - BIRD_RADIUS
            self.vy *= -0.35
            self.vx *= 0.8
            if abs(self.vy) < 1.5:
                self.vy = 0

        speed = math.hypot(self.vx, self.vy)
        if speed < 0.6 and self.y >= GROUND_Y - BIRD_RADIUS - 2:
            self.stopped_timer += 1
        else:
            self.stopped_timer = 0

    def draw(self, surf):
        for i, (tx, ty) in enumerate(self.trail):
            a = int(120 * (i / len(self.trail)))
            s = pygame.Surface((10, 10), pygame.SRCALPHA)
            pygame.draw.circle(s, (255, 255, 255, a), (5, 5), 4)
            surf.blit(s, (tx-5, ty-5))
        pygame.draw.circle(surf, (200, 30, 30), (int(self.x), int(self.y)), BIRD_RADIUS)
        pygame.draw.circle(surf, (150, 20, 20), (int(self.x), int(self.y)), BIRD_RADIUS, 2)
        # клюв
        pygame.draw.polygon(surf, (240, 180, 40), [
            (self.x + BIRD_RADIUS, self.y),
            (self.x + BIRD_RADIUS + 8, self.y - 4),
            (self.x + BIRD_RADIUS + 8, self.y + 4)])
        # глаз
        pygame.draw.circle(surf, (255, 255, 255), (int(self.x + 4), int(self.y - 5)), 5)
        pygame.draw.circle(surf, (0, 0, 0), (int(self.x + 5), int(self.y - 5)), 2)


def draw_block(surf, b):
    if b["hp"] <= 0:
        return
    rect = pygame.Rect(b["x"], b["y"], b["w"], b["h"])
    dmg_ratio = b["hp"] / b["max_hp"]
    base_color = (150, 100, 60)
    color = tuple(int(c * (0.5 + 0.5*dmg_ratio)) for c in base_color)
    pygame.draw.rect(surf, color, rect)
    pygame.draw.rect(surf, (90, 55, 30), rect, 2)
    for i in range(1, 3):
        yy = b["y"] + b["h"] * i / 3
        pygame.draw.line(surf, (90, 55, 30), (b["x"], yy), (b["x"]+b["w"], yy), 1)
    if dmg_ratio < 0.6:
        pygame.draw.line(surf, (60, 35, 20), (b["x"]+4, b["y"]+4), (b["x"]+b["w"]-4, b["y"]+b["h"]-4), 1)


def draw_pig(surf, p):
    if not p["alive"]:
        return
    x, y, r = p["x"], p["y"], p["r"]
    pygame.draw.circle(surf, (110, 180, 70), (int(x), int(y)), r)
    pygame.draw.circle(surf, (80, 140, 50), (int(x), int(y)), r, 2)
    pygame.draw.circle(surf, (255, 255, 255), (int(x-6), int(y-4)), 5)
    pygame.draw.circle(surf, (255, 255, 255), (int(x+6), int(y-4)), 5)
    pygame.draw.circle(surf, (0, 0, 0), (int(x-6), int(y-3)), 2)
    pygame.draw.circle(surf, (0, 0, 0), (int(x+6), int(y-3)), 2)
    pygame.draw.ellipse(surf, (90, 160, 60), (x-6, y+3, 12, 8))


def circle_rect_collision(cx, cy, cr, rect):
    closest_x = max(rect.x, min(cx, rect.x + rect.w))
    closest_y = max(rect.y, min(cy, rect.y + rect.h))
    dx, dy = cx - closest_x, cy - closest_y
    dist = math.hypot(dx, dy)
    if dist < cr:
        if dist == 0:
            dist = 0.01
            dx, dy = 1, 0
        return True, dx/dist, dy/dist, dist
    return False, 0, 0, 0


current_level = 0
state = reset_level_state(current_level)
bird = Bird()
dragging = False
level_complete_timer = 0
level_failed = False
game_won = False


def spawn_new_bird():
    global bird
    bird = Bird()


running = True
while running:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        if event.type == pygame.MOUSEBUTTONDOWN and not bird.launched and not level_failed and not game_won:
            mx, my = event.pos
            if math.hypot(mx - SLING_X, my - SLING_Y) < 60:
                dragging = True

        if event.type == pygame.MOUSEBUTTONUP and dragging:
            dragging = False
            mx, my = event.pos
            dx, dy = SLING_X - mx, SLING_Y - my
            dist = min(math.hypot(dx, dy), MAX_DRAG)
            if dist > 8:
                angle = math.atan2(dy, dx)
                power = dist / MAX_DRAG * 24
                bird.x, bird.y = SLING_X, SLING_Y
                bird.launch(math.cos(angle) * power, math.sin(angle) * power)
            
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_r and level_failed:
                state = reset_level_state(current_level)
                spawn_new_bird()
                level_failed = False
            if event.key == pygame.K_r and game_won:
                current_level = 0
                state = reset_level_state(current_level)
                spawn_new_bird()
                game_won = False

    mx, my = pygame.mouse.get_pos()

    if not level_failed and not game_won:
        if dragging:
            dx, dy = mx - SLING_X, my - SLING_Y
            dist = min(math.hypot(dx, dy), MAX_DRAG)
            angle = math.atan2(dy, dx)
            bird.x = SLING_X + math.cos(angle) * dist
            bird.y = SLING_Y + math.sin(angle) * dist
        else:
            bird.update()

            for b in state["blocks"]:
                if b["hp"] <= 0:
                    continue
                rect = pygame.Rect(b["x"], b["y"], b["w"], b["h"])
                hit, nx, ny, dist = circle_rect_collision(bird.x, bird.y, BIRD_RADIUS, rect)
                if hit and bird.launched:
                    speed = math.hypot(bird.vx, bird.vy)
                    b["hp"] -= speed * 4
                    bird.vx = nx * speed * 0.4
                    bird.vy = ny * speed * 0.4
                    overlap = BIRD_RADIUS - dist
                    bird.x += nx * overlap
                    bird.y += ny * overlap

            for p in state["pigs"]:
                if not p["alive"]:
                    continue
                if math.hypot(bird.x - p["x"], bird.y - p["y"]) < BIRD_RADIUS + p["r"]:
                    p["alive"] = False
                    bird.vx *= 0.6
                    bird.vy *= 0.6

            if bird.stopped_timer > 40 or bird.x > W + 50 or bird.x < -50:
                state["birds_left"] -= 1
                if all(not p["alive"] for p in state["pigs"]):
                    pass
                elif state["birds_left"] <= 0:
                    level_failed = True
                if not level_failed:
                    spawn_new_bird()

        if all(not p["alive"] for p in state["pigs"]) and level_complete_timer == 0:
            level_complete_timer = 1

        if level_complete_timer > 0:
            level_complete_timer += 1
            if level_complete_timer > 90:
                level_complete_timer = 0
                current_level += 1
                if current_level >= 10:
                    game_won = True
                else:
                    state = reset_level_state(current_level)
                    spawn_new_bird()

    # ---------- ОТРИСОВКА ----------
    screen.blit(sky, (0, 0))
    for (cx, cy, cr) in clouds:
        pygame.draw.ellipse(screen, (255, 255, 255), (cx, cy, cr*2, cr))
        pygame.draw.ellipse(screen, (255, 255, 255), (cx+cr*0.6, cy-8, cr*1.4, cr))

    pygame.draw.rect(screen, (90, 160, 60), (0, GROUND_Y, W, H - GROUND_Y))
    pygame.draw.rect(screen, (70, 130, 45), (0, GROUND_Y, W, 8))

    # рогатка
    pygame.draw.line(screen, (90, 60, 30), (SLING_X - 15, GROUND_Y), (SLING_X - 15, SLING_Y - 40), 8)
    pygame.draw.line(screen, (90, 60, 30), (SLING_X + 15, GROUND_Y), (SLING_X + 15, SLING_Y - 30), 8)

    if not bird.launched:
        pygame.draw.line(screen, (60, 40, 20), (SLING_X - 15, SLING_Y - 40), (bird.x, bird.y), 4)
        pygame.draw.line(screen, (60, 40, 20), (SLING_X + 15, SLING_Y - 30), (bird.x, bird.y), 4)

    for b in state["blocks"]:
        draw_block(screen, b)
    for p in state["pigs"]:
        draw_pig(screen, p)

    bird.draw(screen)

    if not bird.launched and not dragging:
        pygame.draw.line(screen, (60, 40, 20), (SLING_X - 15, SLING_Y - 40), (bird.x, bird.y), 4)
        pygame.draw.line(screen, (60, 40, 20), (SLING_X + 15, SLING_Y - 30), (bird.x, bird.y), 4)

    # траектория прицеливания
    if dragging:
        dx, dy = SLING_X - bird.x, SLING_Y - bird.y
        power = min(math.hypot(dx, dy), MAX_DRAG) / MAX_DRAG * 24
        angle = math.atan2(dy, dx)
        sx, sy = bird.x, bird.y
        svx, svy = math.cos(angle)*power, math.sin(angle)*power
        for i in range(18):
            svy += GRAVITY
            sx += svx
            sy += svy
            if i % 2 == 0:
                pygame.draw.circle(screen, (255, 255, 255), (int(sx), int(sy)), 3)

    # HUD
    pigs_alive = sum(1 for p in state["pigs"] if p["alive"])
    hud1 = font.render(f"Уровень {current_level + 1}/10", True, (40, 40, 40))
    hud2 = font.render(f"Свиней осталось: {pigs_alive}", True, (40, 100, 40))
    hud3 = font.render(f"Птиц: {state['birds_left']}", True, (150, 30, 30))
    screen.blit(hud1, (20, 15))
    screen.blit(hud2, (20, 50))
    screen.blit(hud3, (20, 85))

    for i in range(state["birds_left"]):
        pygame.draw.circle(screen, (200, 30, 30), (W - 30 - i*35, 35), 13)

    if level_complete_timer > 0:
        overlay = pygame.Surface((W, H), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 120))
        screen.blit(overlay, (0, 0))
        text = font_big.render("УРОВЕНЬ ПРОЙДЕН!", True, (255, 220, 60))
        screen.blit(text, (W//2 - text.get_width()//2, H//2 - 40))

    if level_failed:
        overlay = pygame.Surface((W, H), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 160))
        screen.blit(overlay, (0, 0))
        text = font_big.render("ПРОВАЛ", True, (255, 70, 70))
        screen.blit(text, (W//2 - text.get_width()//2, H//2 - 60))
        sub = font.render("Нажми R чтобы повторить уровень", True, (255, 255, 255))
        screen.blit(sub, (W//2 - sub.get_width()//2, H//2 + 10))

    if game_won:
        overlay = pygame.Surface((W, H), pygame.SRCALPHA)
        overlay.fill((0, 0, 0, 170))
        screen.blit(overlay, (0, 0))
        text = font_big.render("ВСЕ 10 УРОВНЕЙ ПРОЙДЕНЫ!", True, (100, 255, 130))
        screen.blit(text, (W//2 - text.get_width()//2, H//2 - 60))
        sub = font.render("Нажми R чтобы начать заново", True, (255, 255, 255))
        screen.blit(sub, (W//2 - sub.get_width()//2, H//2 + 10))

    pygame.display.flip()

pygame.quit()