import pyxel
import random

WIDTH = 128
HEIGHT = 128

player_x = 20
player_y = 94
player_vy = 0
gravity_dir = 1
on_ground = False

GRAVITY = 0.35
JUMP_FORCE = -4.5
speed = 2.0
score = 0
world_score = 0
distance = 0
game_over = False
world = 0  # 0=Schnee  1=Feuer  2=Dschungel

obstacles = []
collectibles = []
particles = []
spawn_timer = 0
collect_timer = 0
portal_active = False
portal = [WIDTH + 30, HEIGHT - 22, 10, 16]

snow_flakes = []
leaves = []
fire_bg = []


def init_effects():
    global snow_flakes, leaves, fire_bg
    snow_flakes = [[float(random.randint(0, WIDTH)), float(random.randint(0, HEIGHT)), random.random()*0.4+0.3] for _ in range(25)]
    leaves      = [[float(random.randint(0, WIDTH)), float(random.randint(0, HEIGHT)), random.random()*0.5+0.2, random.choice([3,11])] for _ in range(18)]
    fire_bg     = [[float(random.randint(0, WIDTH)), float(random.randint(0, HEIGHT)), random.random()*0.6+0.2, random.choice([8,9,10])] for _ in range(20)]


def setup_sounds():
    # .set(notes, tones, volumes, effects, speed)
    # Jede Note = 2 Zeichen (z.B. "c3"), tones/volumes/effects = 1 Zeichen pro Note

    # Sound 0: Collect (3 Noten)
    pyxel.sounds[0].set("e3a3c4","ttt","657","nvv",7)

    # Sound 1: Gravity Flip – Whoosh / Luftzug
    # Noise absteigend mit Fade simuliert einen Whoosh
    pyxel.sounds[1].set("g2e2c2a1f1","nnnnn","76543","nnnff",3)

    # Sound 2: Game Over (4 Noten)
    pyxel.sounds[2].set("c3a2f2c2","ssss","6543","nnnf",14)

    # Sound 3: Schneewelt Melodie (16 Noten) – langsamer: speed 18
    pyxel.sounds[3].set("a3c4e4c4a3c4e4c4g3a3c4a3f3g3a3g3","tttttttttttttttt","4554455445544554","nnnnnnnnnnnnnnnn",18)
    # Sound 4: Schneewelt Bass (16 Noten) – langsamer: speed 18
    pyxel.sounds[4].set("a1a1a1a1a1a1a1a1g1g1g1g1f1f1f1f1","ssssssssssssssss","3333333333333333","nnnnnnnnnnnnnnnn",18)
    # Sound 5: Feuerwelt Melodie (16 Noten) – langsamer: speed 14
    pyxel.sounds[5].set("c3c3e3c3d3c3a2c3c3c3e3g3e3d3c3a2","pppppppppppppppp","6464646464646464","nnnnnnnnnnnnnnnn",14)
    # Sound 6: Feuerwelt Bass (16 Noten) – langsamer: speed 14
    pyxel.sounds[6].set("c2c2e2c2d2c2c2c2c2c2e2g2e2d2c2c2","nnnnnnnnnnnnnnnn","4444444444444444","nnnnnnnnnnnnnnnn",14)
    # Sound 7: Dschungel Melodie (16 Noten) – langsamer: speed 20
    pyxel.sounds[7].set("f3a3c4a3f3d3c3d3f3a3c4e4c4a3f3d3","tttttttttttttttt","5454545454545454","nvnvnvnvnvnvnvnv",20)
    # Sound 8: Dschungel Bass (16 Noten) – langsamer: speed 20
    pyxel.sounds[8].set("f1f1f1f1d1d1d1d1f1f1f1f1c1c1c1c1","ssssssssssssssss","3333333333333333","nnnnnnnnnnnnnnnn",20)

    # Musik-Sequenzen (music[n]: list of sound-indices per channel)
    # music[0] = Schneewelt: Kanal0=Sound3, Kanal1=Sound4, Kanal2+3 leer
    pyxel.musics[0].set([3], [4], [], [])
    # music[1] = Feuerwelt
    pyxel.musics[1].set([5], [6], [], [])
    # music[2] = Dschungelwelt
    pyxel.musics[2].set([7], [8], [], [])


def play_music():
    pyxel.playm(world, loop=True)


def reset():
    global player_y, player_vy, gravity_dir, speed, score, world_score, distance
    global game_over, portal_active, world, spawn_timer, collect_timer
    player_y = 94;  player_vy = 0;  gravity_dir = 1
    speed = 2.0;    score = 0;      world_score = 0;  distance = 0
    spawn_timer = 0; collect_timer = 0
    obstacles.clear(); collectibles.clear(); particles.clear()
    game_over = False;  portal_active = False;  world = 0
    init_effects()
    play_music()


def flip_gravity():
    global gravity_dir, player_vy, player_y
    gravity_dir *= -1
    player_vy = 0.0   # Velocity zurücksetzen – verhindert Durchfliegen durch Decke/Boden
    # Spieler leicht von der Wand wegbewegen damit er nicht feststeckt
    if gravity_dir == 1 and player_y <= 6:
        player_y = 7.0
    elif gravity_dir == -1 and player_y >= HEIGHT - 12:
        player_y = float(HEIGHT - 13)
    pyxel.play(3, 1)   # Kanal 3 für SFX


def jump():
    global player_vy
    if on_ground:
        player_vy = JUMP_FORCE * gravity_dir


def spawn_obstacle():
    w = random.randint(5, 9)
    h = random.randint(7, 13)
    y = HEIGHT - h - 7 if random.random() < 0.5 else 7
    obstacles.append([float(WIDTH+10), float(y), float(w), float(h)])


def spawn_collectible():
    collectibles.append([float(WIDTH + random.randint(20,80)), float(random.randint(22, HEIGHT-22)), False])


def spawn_portal():
    portal[0] = float(WIDTH + 20)
    portal[1] = float(HEIGHT - 22 if gravity_dir == 1 else 8)


def update_player():
    global player_y, player_vy, on_ground
    player_vy += GRAVITY * gravity_dir
    player_y  += player_vy
    if gravity_dir == 1:
        if player_y >= HEIGHT - 10:
            player_y = HEIGHT - 10;  player_vy = 0;  on_ground = True
        else:
            on_ground = False
    else:
        if player_y <= 4:
            player_y = 4;  player_vy = 0;  on_ground = True
        else:
            on_ground = False

           
def update_effects():
    if world == 0:
        for s in snow_flakes:
            s[1] += s[2]
            if s[1] > HEIGHT:
                s[1] = 0.0;  s[0] = float(random.randint(0, WIDTH))
    elif world == 1:
        for fb in fire_bg:
            fb[1] -= fb[2]
            if fb[1] < -2:
                fb[1] = float(HEIGHT+2);  fb[0] = float(random.randint(0, WIDTH))
    else:
        for lf in leaves:
            lf[0] -= lf[2] + speed*0.3
            lf[1] += (random.random()-0.5)*0.4
            if lf[0] < -5:
                lf[0] = float(WIDTH + random.randint(0,20));  lf[1] = float(random.randint(0,HEIGHT))


def update_particles():
    for p in particles:
        p[0] += p[2];  p[1] += p[3];  p[3] += 0.12;  p[4] -= 1
    particles[:] = [p for p in particles if p[4] > 0]


def add_particles(x, y):
    cols = [7,12,6] if world==0 else ([8,9,10] if world==1 else [3,11,7])
    for _ in range(9):
        particles.append([float(x)+3, float(y)+3, random.uniform(-2,2), random.uniform(-2.5,0.3), 14, random.choice(cols)])


def check_collisions():
    global game_over, score, world_score, world, portal_active
    px2 = player_x + 16
    py2 = player_y + 8

    for o in obstacles:
        if player_x < o[0]+o[2] and px2 > o[0] and player_y < o[1]+o[3] and py2 > o[1]:
            game_over = True
            pyxel.stop()           # Musik stoppen
            pyxel.play(3, 2)       # Game-Over-Sound auf Kanal 3
            return

    for d in collectibles:
        if not d[2] and player_x < d[0]+7 and px2 > d[0] and player_y < d[1]+7 and py2 > d[1]:
            d[2] = True;  score += 1;  world_score += 1
            pyxel.play(2, 0)       # Collect-Sound auf Kanal 2
            add_particles(d[0], d[1])

    if portal_active:
        if player_x < portal[0]+portal[2] and px2 > portal[0] and player_y < portal[1]+portal[3] and py2 > portal[1]:
            world += 1;  portal_active = False;  world_score = 0
            collectibles.clear();  obstacles.clear()
            play_music()           # Neue Weltmusik starten


def update():
    global spawn_timer, collect_timer, distance, speed, portal_active

    if game_over:
        if pyxel.btnp(pyxel.KEY_R) or pyxel.btnp(pyxel.GAMEPAD1_BUTTON_START):
            reset()
        return

    if pyxel.btnp(pyxel.KEY_SPACE) or pyxel.btnp(pyxel.GAMEPAD1_BUTTON_A):
        jump()
    if pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.GAMEPAD1_BUTTON_B):
        flip_gravity()

    update_player()

    spawn_timer += 1;  collect_timer += 1
    if spawn_timer >= max(28, 50 - int(speed*3)):
        spawn_timer = 0;  spawn_obstacle()
    if collect_timer >= 35:
        collect_timer = 0;  spawn_collectible()

    for o in obstacles:    o[0] -= speed
    for d in collectibles: d[0] -= speed
    obstacles[:]    = [o for o in obstacles    if o[0] > -20]
    collectibles[:] = [d for d in collectibles if d[0] > -20]

    update_particles()
    update_effects()
    check_collisions()

    distance += 1
    threshold = 10 if world == 0 else 15
    if world_score >= threshold and not portal_active and world < 2:
        portal_active = True;  spawn_portal()
    if portal_active:
        portal[0] -= speed
    if distance % 400 == 0:
        speed = min(speed + 0.3, 6.0)


# ── DRAW ─────────────────────────────────────────────────────────────

def draw_motorcycle(px, py):
    mc_body  = 7  if world==0 else (9  if world==1 else 11)
    mc_dark  = 6  if world==0 else (8  if world==1 else 3)
    mc_glass = 12

    def my(o):
        return py + (9 - o) if gravity_dir == -1 else py + o

    # Auspuff-Rauchspur
    pyxel.pset(px-3, my(5), 5)
    pyxel.pset(px-5, my(4), 13)
    pyxel.pset(px-5, my(6), 13)
    pyxel.pset(px-7, my(5), 5)

    # Hinterrad
    pyxel.rectb(px+0,  my(4), 5, 5, 13)
    pyxel.rect (px+1,  my(5), 3, 3, 0)
    pyxel.pset (px+2,  my(6), 13)

    # Auspuffrohr
    pyxel.line(px+3, my(7), px+9, my(7), mc_dark)
    pyxel.pset(px+3, my(8), mc_dark)

    # Rahmen / Chassis
    pyxel.rect(px+4,  my(4), 9, 3, mc_body)
    pyxel.rect(px+5,  my(3), 5, 2, mc_dark)

    # Kettendetails
    pyxel.pset(px+4, my(7), mc_dark)
    pyxel.line(px+5, my(7), px+8, my(7), 0)

    # Fahrer-Körper
    pyxel.rect(px+6,  my(1), 3, 3, mc_dark)
    pyxel.rect(px+7,  my(3), 3, 2, mc_body)

    # Fahrer-Helm
    pyxel.rect (px+8,  my(0), 4, 4, mc_dark)
    pyxel.rectb(px+8,  my(0), 4, 4, 7)
    pyxel.rect (px+9,  my(1), 2, 2, mc_glass)

    # Frontverkleidung
    pyxel.rect(px+12, my(2), 3, 5, mc_dark)
    pyxel.pset(px+13, my(2), mc_glass)
    pyxel.pset(px+13, my(4), mc_glass)

    # Vorderrad
    pyxel.rectb(px+13, my(4), 5, 5, 13)
    pyxel.rect (px+14, my(5), 3, 3, 0)
    pyxel.pset (px+15, my(6), 13)

    # Vordergabel
    pyxel.line(px+12, my(3), px+14, my(5), mc_dark)


def draw():
    # ── Hintergrund ──────────────────────────────────────────────────
    if world == 0:
        pyxel.cls(1)
        for i in range(0, WIDTH, 13):
            pyxel.pset(i, (i*7) % (HEIGHT-20), 7)
        pyxel.rect(0, HEIGHT-7, WIDTH, 7, 6)
        pyxel.rect(0, 0, WIDTH, 5, 6)
        for s in snow_flakes:
            pyxel.pset(int(s[0]), int(s[1]), 7)
    elif world == 1:
        pyxel.cls(0)
        pyxel.rect(0, HEIGHT-7, WIDTH, 7, 8)
        for i in range(0, WIDTH, 4):  pyxel.pset(i,  HEIGHT-7, 9)
        pyxel.rect(0, 0, WIDTH, 5, 8)
        for i in range(2, WIDTH, 4):  pyxel.pset(i,  4, 9)
        for fb in fire_bg:            pyxel.pset(int(fb[0]), int(fb[1]), int(fb[3]))
    else:
        pyxel.cls(3)
        pyxel.rect(0, HEIGHT-7, WIDTH, 7, 4)
        for i in range(0, WIDTH, 3):  pyxel.pset(i,  HEIGHT-7, 11)
        pyxel.rect(0, 0, WIDTH, 5, 4)
        for i in range(1, WIDTH, 3):  pyxel.pset(i,  4, 11)
        for lf in leaves:             pyxel.pset(int(lf[0]), int(lf[1]), int(lf[3]))

    # ── Hindernisse ───────────────────────────────────────────────────
    for o in obstacles:
        ox,oy,ow,oh = int(o[0]),int(o[1]),int(o[2]),int(o[3])
        c1,c2 = (12,7) if world==0 else ((8,9) if world==1 else (4,11))
        pyxel.rect(ox, oy, ow, oh, c1)
        pyxel.rectb(ox, oy, ow, oh, c2)

    # ── Sammelobjekte ─────────────────────────────────────────────────
    for d in collectibles:
        if d[2]: continue
        cx,cy = int(d[0]),int(d[1])
        if world == 0:   # ❄ Schneeflocke
            # Vertikale Achse
            pyxel.line(cx+3, cy+0, cx+3, cy+6, 7)
            # Horizontale Achse
            pyxel.line(cx+0, cy+3, cx+6, cy+3, 7)
            # Diagonalen
            pyxel.pset(cx+1, cy+1, 6)
            pyxel.pset(cx+5, cy+1, 6)
            pyxel.pset(cx+1, cy+5, 6)
            pyxel.pset(cx+5, cy+5, 6)
            # Achsen-Enden (kleine Äste)
            pyxel.pset(cx+2, cy+1, 12)
            pyxel.pset(cx+4, cy+1, 12)
            pyxel.pset(cx+2, cy+5, 12)
            pyxel.pset(cx+4, cy+5, 12)
            # Mitte Cyan
            pyxel.pset(cx+3, cy+3, 12)
        elif world == 1: # 🔥 Flamme
            pyxel.pset(cx+3, cy,   9)
            pyxel.line(cx+2, cy+1, cx+4, cy+1, 8)
            pyxel.line(cx+1, cy+2, cx+5, cy+2, 9)
            pyxel.line(cx+1, cy+3, cx+5, cy+3, 8)
            pyxel.line(cx+2, cy+4, cx+4, cy+4, 10)
        else:            # 🍃 Blatt – gelber Umriss, ovale Blattform
            # Stiel (braun)
            pyxel.pset(cx+3, cy+7, 4)
            pyxel.pset(cx+4, cy+6, 4)
            # Gelber Umriss (10=gelb) – hebt sich von grünem BG ab
            pyxel.pset(cx+3, cy+0, 10)          # obere Spitze
            pyxel.line(cx+2, cy+1, cx+5, cy+1, 10)
            pyxel.pset(cx+1, cy+2, 10)
            pyxel.pset(cx+6, cy+2, 10)
            pyxel.pset(cx+1, cy+3, 10)
            pyxel.pset(cx+6, cy+3, 10)
            pyxel.pset(cx+1, cy+4, 10)
            pyxel.pset(cx+6, cy+4, 10)
            pyxel.line(cx+2, cy+5, cx+5, cy+5, 10)
            pyxel.pset(cx+3, cy+6, 10)          # untere Spitze
            # Dunkelgrüne Füllung (3)
            pyxel.line(cx+2, cy+2, cx+5, cy+2, 3)
            pyxel.line(cx+2, cy+3, cx+5, cy+3, 3)
            pyxel.line(cx+2, cy+4, cx+5, cy+4, 3)
            # Hellgrüne Mittelader (11)
            pyxel.line(cx+3, cy+1, cx+3, cy+5, 11)
            # Seitenäderchen
            pyxel.pset(cx+2, cy+2, 11)
            pyxel.pset(cx+4, cy+3, 11)
            pyxel.pset(cx+2, cy+4, 11)

    # ── Portal ────────────────────────────────────────────────────────
    if portal_active:
        px,py = int(portal[0]),int(portal[1])
        pw,ph = int(portal[2]),int(portal[3])
        col = 11 if (pyxel.frame_count//4)%2==0 else 9
        bg  = 1 if world==0 else (0 if world==1 else 3)
        pyxel.rectb(px, py, pw, ph, col)
        pyxel.rect(px+1, py+1, pw-2, ph-2, bg)
        pyxel.pset(px+pw//2, py+ph//2, 7)

    # ── Motorrad-Spieler ──────────────────────────────────────────────
    draw_motorcycle(player_x, int(player_y))

    # ── Partikel ──────────────────────────────────────────────────────
    for p in particles:
        pyxel.pset(int(p[0]), int(p[1]), int(p[5]))

    # ── HUD ───────────────────────────────────────────────────────────
    wnames = ["ICE WORLD","FIRE WORLD","JUNGLE"]
    wcols  = [12, 8, 11]
    pyxel.text(2, 2,  wnames[world], wcols[world])
    pyxel.text(2, 10, "Score:" + str(score), 7)
    if not portal_active and world < 2:
        threshold = 10 if world == 0 else 15
        remaining = threshold - world_score
        if remaining > 0:
            label = "Snowflakes:" if world == 0 else "Fires:"
            pyxel.text(2, 18, label + str(remaining), 13)

    hint_col = 12 if world==0 else (9 if world==1 else 11)  # Cyan(Eis) / Orange(Feuer) / Grün=11(Dschungel)
    pyxel.text(2, HEIGHT-10, "A=Jump  B=Flip", hint_col)

    # ── Game Over ─────────────────────────────────────────────────────
    if game_over:
        # Farben je nach Welt:
        # Welt 0 (Eis)    → Blautöne:  bg=1(dunkelblau), frame=12(cyan),   inner=6(hellblau)
        # Welt 1 (Feuer)  → Rottöne:   bg=2(dunkelrot),  frame=8(rot),     inner=9(orange)
        # Welt 2 (Dschungel)→ Grüntöne:bg=3(dunkelgrün), frame=11(grün),   inner=3(grün2)
        if world == 0:
            col_bg, col_frame, col_inner, col_banner, col_txt, col_btn, col_btn_txt = 1, 12, 6, 12, 1, 6, 12
        elif world == 1:
            col_bg, col_frame, col_inner, col_banner, col_txt, col_btn, col_btn_txt = 2, 8, 9, 8, 7, 9, 2
        else:
            col_bg, col_frame, col_inner, col_banner, col_txt, col_btn, col_btn_txt = 3, 11, 3, 11, 0, 3, 11

        pyxel.rect (14, 42, 100, 48, col_bg)
        pyxel.rectb(14, 42, 100, 48, col_frame)
        pyxel.rectb(15, 43,  98, 46, col_inner)
        # Titelbalken
        pyxel.rect (14, 42, 100, 10, col_banner)
        pyxel.text (35, 45, "GAME  OVER!", col_txt)
        # Stats
        pyxel.text(24, 56, "Score:  " + str(score), 7)
        pyxel.text(24, 64, "World:  " + ["Ice","Fire","Jungle"][world], col_inner)
        # Retry-Button
        pyxel.rect (24, 74, 76, 9, col_btn)
        pyxel.text (29, 76, "R / START = Retry", col_btn_txt)


# ── START ─────────────────────────────────────────────────────────────
pyxel.init(WIDTH, HEIGHT, title="Gravity Rider")
setup_sounds()
init_effects()
reset()
pyxel.run(update, draw)