import pyxel
import random
import math
 
# --- Konstanten ---
WIDTH = 160
HEIGHT = 120
FPS = 30
 
BIRD_X = 30
BIRD_W = 10
BIRD_H = 15
 
GRAVITY = 0.35
FLAP_FORCE = -3.5
 
PIPE_W = 14
PIPE_SPEED = 1.8
 
MIN_PIPE_H = 12
 
GAP_ABSOLUTE_MIN = int(BIRD_H * 2.5)
GAP_EARLY_MIN = BIRD_H * 4
GAP_EARLY_MAX = BIRD_H * 6
GAP_LATE_MIN  = GAP_ABSOLUTE_MIN
GAP_LATE_MAX  = BIRD_H * 5
 
SPAWN_DIST = 110
 
DIFFICULTY_START    = 10
WOBBLE_CHANCE_SCORE = 15
 
WOBBLE_AMP_MAX    = 14
WOBBLE_SPEED_BASE = 0.012
WOBBLE_SPEED_GROW = 0.002
 
# --- Boost ---
BOOST_DURATION_F  = FPS * 5
BOOST_WAIT_MIN    = 10
BOOST_WAIT_MAX    = 15
BOOST_SPEED_MULT  = 2.5
 
BOOST_CLEAR = 1
BOOST_SPEED = 2
 
BOOST_COLORS = {BOOST_CLEAR: 10, BOOST_SPEED: 9}
BOOST_NAMES  = {BOOST_CLEAR: "GHOST!", BOOST_SPEED: "TURBO!"}
 
# Sound-Nummern
SND_FLAP   = 4
SND_TURBO  = 5
SND_GHOST  = 6
SND_DIE    = 7
MSC_MAIN   = 0
 
# --- Spielzustand ---
bird_y  = 0.0
bird_vy = 0.0
pipes   = []
score   = 0
state   = "title"
frame   = 0
bg_x    = 0.0
 
active_boost      = 0
boost_frames_left = 0
next_boost_score  = 0
 
 
def setup_sounds():
    # Sound 0: Melodie Teil 1
    pyxel.sound(0).set("e2e2c2g1 g1g1c2e2 d2d2d2g2 g2g2rr", "p", "5", "n", 20)
    # Sound 1: Melodie Teil 2
    pyxel.sound(1).set("c2c2e2g2 a2a2g2e2 f2f2e2d2 c2c2rr", "p", "5", "n", 20)
    # Sound 2: Bass Teil 1
    pyxel.sound(2).set("c1re1r g1rc1r", "t", "3", "n", 20)
    # Sound 3: Bass Teil 2
    pyxel.sound(3).set("f1ra1r c2rf1r", "t", "3", "n", 20)
    # Music 0: Melodie auf ch0, Bass auf ch1
    pyxel.music(0).set([0, 1], [2, 3], [], [])
 
    # Sound 4: Flattern
    pyxel.sound(SND_FLAP).set("c3", "n", "4", "f", 5)
 
    # Sound 5: TURBO – aufsteigender Sweep
    pyxel.sound(SND_TURBO).set("c2d2e2f2g2a2b2c3", "s", "76665554", "n", 6)
 
    # Sound 6: GHOST – weicher absteigender Sweep
    pyxel.sound(SND_GHOST).set("c3b2a2g2f2e2d2c2", "t", "54444333", "f", 8)
 
    # Sound 7: Tod
    pyxel.sound(SND_DIE).set("g2f2e2d2c2b1a1g1", "n", "76543210", "n", 6)
 
 
def get_gap():
    if score < DIFFICULTY_START:
        return random.randint(GAP_EARLY_MIN, GAP_EARLY_MAX)
    else:
        return random.randint(GAP_LATE_MIN, GAP_LATE_MAX)
 
 
def get_wobble_speed():
    if score < DIFFICULTY_START:
        return 0.0
    return WOBBLE_SPEED_BASE + (score - DIFFICULTY_START) * WOBBLE_SPEED_GROW
 
 
def should_wobble():
    if score < DIFFICULTY_START:
        return False
    if score >= WOBBLE_CHANCE_SCORE:
        return random.random() < 0.5
    return True
 
 
def make_pipe(x):
    gap     = get_gap()
    max_top = HEIGHT - MIN_PIPE_H - int(gap) - MIN_PIPE_H
    top_h   = random.randint(MIN_PIPE_H, max(MIN_PIPE_H, max_top))
    wobble  = get_wobble_speed() if should_wobble() else 0.0
    return {
        "x":            x,
        "top_h":        float(top_h),
        "gap":          gap,
        "passed":       False,
        "wobble_speed": wobble,
        "wobble_phase": random.uniform(0, math.pi * 2),
    }
 
 
def get_top_h(pipe):
    if pipe["wobble_speed"] == 0.0:
        return pipe["top_h"]
    t      = frame * pipe["wobble_speed"] + pipe["wobble_phase"]
    offset = math.sin(t) * WOBBLE_AMP_MAX
    top_h  = pipe["top_h"] + offset
    top_h  = max(MIN_PIPE_H, min(top_h, HEIGHT - MIN_PIPE_H - pipe["gap"]))
    return top_h
 
 
def schedule_next_boost():
    global next_boost_score
    next_boost_score = score + random.randint(BOOST_WAIT_MIN, BOOST_WAIT_MAX)
 
 
def activate_boost(kind):
    global active_boost, boost_frames_left
    active_boost      = kind
    boost_frames_left = BOOST_DURATION_F
    if kind == BOOST_SPEED:
        pyxel.play(1, SND_TURBO)
    else:
        pyxel.play(1, SND_GHOST)
 
 
def reset():
    global bird_y, bird_vy, pipes, score, state, frame, bg_x
    global active_boost, boost_frames_left, next_boost_score
    bird_y  = HEIGHT // 2
    bird_vy = 0.0
    score   = 0
    state   = "title"
    frame   = 0
    bg_x    = 0.0
    pipes   = [make_pipe(WIDTH + i * SPAWN_DIST) for i in range(3)]
    active_boost      = 0
    boost_frames_left = 0
    schedule_next_boost()
    pyxel.playm(MSC_MAIN, loop=True)
 
 
def update():
    global bird_y, bird_vy, pipes, score, state, frame, bg_x
    global active_boost, boost_frames_left
 
    frame += 1
 
    if state == "title":
        if pyxel.btnp(pyxel.KEY_SPACE) or pyxel.btnp(pyxel.KEY_UP):
            state = "playing"
        return
 
    if state == "dead":
        if pyxel.btnp(pyxel.KEY_SPACE) or pyxel.btnp(pyxel.KEY_R):
            reset()
            state = "playing"
        return
 
    # --- Boost-Timer ---
    if active_boost != 0:
        boost_frames_left -= 1
        if boost_frames_left <= 0:
            active_boost      = 0
            boost_frames_left = 0
            schedule_next_boost()
 
    # --- Vogel ---
    speed_mult = BOOST_SPEED_MULT if active_boost == BOOST_SPEED else 1.0
    if pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_SPACE):
        bird_vy = FLAP_FORCE * speed_mult
        pyxel.play(2, SND_FLAP)
    bird_vy += GRAVITY
    bird_y  += bird_vy * speed_mult
 
    if bird_y < 0:
        bird_y  = 0
        bird_vy = 0
 
    floor_y = HEIGHT - 8
    if bird_y + BIRD_H >= floor_y:
        bird_y  = float(floor_y - BIRD_H)
        bird_vy = 0.0
 
    # --- Hintergrund ---
    pipe_speed = PIPE_SPEED * (BOOST_SPEED_MULT if active_boost == BOOST_SPEED else 1.0)
    bg_x = (bg_x + pipe_speed) % WIDTH
 
    # --- Säulen bewegen ---
    for pipe in pipes:
        pipe["x"] -= pipe_speed
 
    # Recyclen
    leftmost  = min(pipes, key=lambda p: p["x"])
    rightmost = max(pipes, key=lambda p: p["x"])
    if leftmost["x"] + PIPE_W < -5:
        pipes.remove(leftmost)
        pipes.append(make_pipe(rightmost["x"] + SPAWN_DIST))
 
    # --- Punkte ---
    for pipe in pipes:
        if not pipe["passed"] and pipe["x"] + PIPE_W < BIRD_X:
            pipe["passed"] = True
            score += 1
            if active_boost == 0 and score >= next_boost_score:
                kind = BOOST_CLEAR if random.random() < 0.5 else BOOST_SPEED
                activate_boost(kind)
 
    # --- Kollision ---
    if active_boost not in (BOOST_CLEAR, BOOST_SPEED):
        bx1 = BIRD_X + 1
        bx2 = BIRD_X + BIRD_W - 1
        by1 = bird_y + 1
        by2 = bird_y + BIRD_H - 1
 
        for pipe in pipes:
            px1 = pipe["x"]
            px2 = pipe["x"] + PIPE_W
            if bx2 > px1 and bx1 < px2:
                top_h    = get_top_h(pipe)
                bottom_y = top_h + pipe["gap"]
                if (by1 < top_h) or (by2 > bottom_y):
                    pyxel.stop()
                    pyxel.play(0, SND_DIE)
                    state = "dead"
                    return
 
 
def draw_bg():
    pyxel.cls(2)
    for cx in range(0, WIDTH + 30, 50):
        ox = (cx - int(bg_x * 0.3)) % (WIDTH + 30) - 15
        pyxel.rect(ox, 20, 20, 6, 8)
        pyxel.rect(ox + 4, 16, 12, 6, 8)
    pyxel.rect(0, HEIGHT - 8, WIDTH, 8, 7)
    pyxel.rect(0, HEIGHT - 8, WIDTH, 2, 11)
 
 
def draw_pipe(pipe):
    x        = int(pipe["x"])
    top_h    = int(get_top_h(pipe))
    bottom_y = top_h + int(pipe["gap"])
    ghost    = active_boost in (BOOST_CLEAR, BOOST_SPEED)
    col_body = 1 if ghost else 3
    col_cap  = 1 if ghost else 4
    pyxel.rect(x, 0, PIPE_W, top_h, col_body)
    pyxel.rect(x - 1, top_h - 6, PIPE_W + 2, 6, col_cap)
    pyxel.rect(x, bottom_y, PIPE_W, HEIGHT - bottom_y, col_body)
    pyxel.rect(x - 1, bottom_y, PIPE_W + 2, 6, col_cap)
 
 
def draw_bird():
    bx = BIRD_X
    by = int(bird_y)
    pyxel.blt(bx, by, 0, 0, 48, 9, 16, 0)
 
 
def draw_boost_hud():
    if active_boost == 0:
        return
    col  = BOOST_COLORS[active_boost]
    name = BOOST_NAMES[active_boost]
    if boost_frames_left < FPS and (frame // 4) % 2 == 1:
        return
    tx = WIDTH - len(name) * 4 - 2
    pyxel.text(tx, 2, name, col)
    bar_w = int(40 * boost_frames_left / BOOST_DURATION_F)
    bx = WIDTH - 42
    pyxel.rect(bx, 10, 40, 3, 1)
    pyxel.rect(bx, 10, bar_w, 3, col)
 
 
def draw_box(x, y, w, h):
    pyxel.rect(x, y, w, h, 0)
    pyxel.rectb(x, y, w, h, 7)
    pyxel.rectb(x + 1, y + 1, w - 2, h - 2, 8)
 
 
def draw():
    draw_bg()
    for pipe in pipes:
        if pipe["x"] < WIDTH:
            draw_pipe(pipe)
    draw_bird()
    draw_boost_hud()
    pyxel.text(2, 2, f"Score: {score}", 0)
    pyxel.text(1, 1, f"Score: {score}", 7)
    if state == "title":
        draw_box(28, 38, 104, 44)
        pyxel.text(44, 44, "FLAPPY VOGEL", 13)
        pyxel.text(36, 56, "UP / SPACE: Flattern", 7)
        pyxel.text(38, 66, "Ab Score 10 wirds wild!", 9)
        pyxel.text(46, 74, "Viel Erfolg!", 8)
    if state == "dead":
        draw_box(28, 40, 104, 42)
        pyxel.text(50, 46, "GAME OVER", 14)
        pyxel.text(38, 56, f"Punkte: {score}", 7)
        pyxel.text(32, 68, "SPACE / R: Neustart", 9)
 
 
# --- Start ---
pyxel.init(WIDTH, HEIGHT, title="Flappy Vogel", fps=FPS)
pyxel.load("res.pyxres")
setup_sounds()
reset()
pyxel.run(update, draw)