"""
HUB WORLD - Jahrmarkt Edition  (Pyxel 512x512)
Steuerung: Pfeiltasten/WASD bewegen, ENTER betreten, SPACE/OBEN schiessen, ESC zurueck
Minispiele: POOL | RAKETE | ZIRKUS | SCHIESSEN
"""
import pyxel, random, math
 
W,H,FPS = 512,512,30
BK=0;NV=1;PU=2;GR=3;BR=4;DK=5;GY=6;WH=7
RD=8;OR=9;YL=10;LG=11;BL=12;IN=13;PK=14;PE=15
 
S_LOBBY="lobby";S_WATER="water";S_SPACE="space"
S_DUCK="duck";S_CIRCUS="circus"
S_OVER="over";S_WIN="win";S_INTRO="intro"
 
FC = lambda: pyxel.frame_count
FLAG_COLORS = [RD,YL,BL,OR,PK,LG,PU]

SND_SHOOT = 0
SND_HIT   = 1
SND_HURT  = 2
SND_WIN   = 3
SND_OVER  = 4
SND_SELECT= 5
SND_SPLASH= 6

def setup_sounds():
    pyxel.sounds[SND_SHOOT].set(
        notes="C3E3", tones="PP", volumes="33", effects="FN", speed=7)
    pyxel.sounds[SND_HIT].set(
        notes="C3E3G3", tones="SSS", volumes="333", effects="NNN", speed=8)
    pyxel.sounds[SND_HURT].set(
        notes="A1F1", tones="NN", volumes="33", effects="FN", speed=12)
    pyxel.sounds[SND_WIN].set(
        notes="C3E3G3C4E4G4", tones="TTTTTT", volumes="444444", effects="NNNNNN", speed=10)
    pyxel.sounds[SND_OVER].set(
        notes="G2E2C2G1C1", tones="TTTTT", volumes="44444", effects="FFFFN", speed=14)
    pyxel.sounds[SND_SELECT].set(
        notes="C3G3C4", tones="PPP", volumes="333", effects="NNN", speed=9)
    pyxel.sounds[SND_SPLASH].set(
        notes="C2C1", tones="NN", volumes="33", effects="FN", speed=15)


def overlap(ax,ay,aw,ah,bx,by,bw,bh):
    if ax+aw <= bx: return False
    if bx+bw <= ax: return False
    if ay+ah <= by: return False
    if by+bh <= ay: return False
    return True

def _portal_positions():
    cx,cy,r = 256,270,170
    angles = [-90,-90+90,-90+180,-90+270]
    specs = [
        (S_WATER, BL,  "POOL"),
        (S_SPACE, NV,  "RAKETE"),
        (S_CIRCUS,OR,  "ZIRKUS"),
        (S_DUCK,  GR,  "SCHIESSEN"),
    ]
    out = []
    for i,(sid,col,label) in enumerate(specs):
        a = math.radians(angles[i])
        x = int(cx+math.cos(a)*r)-32
        y = int(cy+math.sin(a)*r)-28
        out.append({"id":sid,"x":x,"y":y,"w":64,"h":56,"col":col,"label":label})
    return out

PORTALS = _portal_positions()

def _flags(y,spacing=26):
    for i in range(0,W+spacing,spacing):
        col = FLAG_COLORS[(i//spacing)%len(FLAG_COLORS)]
        x = i+int(math.sin(FC()*0.04+i*0.25)*3)
        pyxel.tri(x,y,x-6,y+11,x+6,y+11,col)
    pyxel.line(0,y,W,y,WH)

def _clown(x,y):
    pyxel.rect(x-4,y+15,9,4,RD)
    pyxel.rect(x+9,y+15,9,4,RD)
    pyxel.rect(x+1,y+10,4,6,WH)
    pyxel.rect(x+9,y+10,4,6,WH)
    pyxel.rect(x,y+6,14,6,BL)
    pyxel.rect(x,y+6,7,6,RD)
    pyxel.circ(x+2,y+6,2,YL)
    pyxel.circ(x+7,y+6,2,YL)
    pyxel.circ(x+12,y+6,2,YL)
    pyxel.circ(x+7,y+2,6,PE)
    pyxel.rect(x+3,y-7,8,7,PU)
    pyxel.rect(x+1,y-8,12,3,PU)
    pyxel.circ(x+7,y-9,3,YL)
    pyxel.pset(x+5,y+1,BK)
    pyxel.pset(x+9,y+1,BK)
    pyxel.circ(x+7,y+3,2,RD)
    pyxel.pset(x+4,y+6,BK)
    pyxel.pset(x+10,y+6,BK)
    pyxel.line(x+5,y+7,x+9,y+7,BK)

def _person(x,y,col):
    """Einfache Menschenfigur"""
    pyxel.circ(x+3,y,3,PE)
    pyxel.rect(x,y+4,7,8,col)
    pyxel.rect(x,y+12,3,7,col)
    pyxel.rect(x+4,y+12,3,7,col)
    pyxel.rect(x-2,y+5,3,5,PE)
    pyxel.rect(x+6,y+5,3,5,PE)

def _balloon(x,y,col):
    """Luftballon"""
    pyxel.circ(x,y,7,col)
    pyxel.pset(x+2,y-3,WH)
    pyxel.line(x,y+7,x+int(math.sin(FC()*0.04+x*0.1)*3),y+22,GY)

def _draw_portal_image(p):
    x,y,sid = p["x"],p["y"],p["id"]
    ix,iy,iw,ih = x+4,y+4,56,44
    if sid == S_WATER:
        pyxel.rect(ix,iy,iw,ih,BL)
        for row in range(ih//6):
            pyxel.rect(ix,iy+row*6,iw,3,NV if row%2==0 else BL)
        pyxel.rect(ix+10,iy+20,22,8,GY)
        pyxel.tri(ix+28,iy+12,ix+22,iy+20,ix+32,iy+20,WH)
        pyxel.pset(ix+30,iy+24,RD)
        for wi in range(0,iw,8):
            off = int(math.sin(FC()*0.12+wi*0.4)*2)
            pyxel.rect(ix+wi,iy+off,4,4,LG)
    elif sid == S_SPACE:
        pyxel.rect(ix,iy,iw,ih,NV)
        for si in range(12):
            sx2 = (si*17+3)%iw+ix
            sy2 = (si*13+5)%ih+iy
            pyxel.pset(sx2,sy2,WH)
        rx,ry = ix+22,iy+8
        pyxel.tri(rx+4,ry,rx,ry+10,rx+8,ry+10,WH)
        pyxel.rect(rx,ry+10,8,14,BL)
        pyxel.rect(rx+2,ry+12,4,5,IN)
        if FC()%6 < 3:
            pyxel.tri(rx+2,ry+24,rx+6,ry+24,rx+4,ry+30,OR)
        pyxel.circ(ix+44,iy+30,6,BR)
    elif sid == S_CIRCUS:
        pyxel.rect(ix,iy,iw,ih,RD)
        for ci in range(0,iw,10): pyxel.rect(ix+ci,iy,5,ih,PK)
        for bi in range(3):
            bx2 = ix+8+bi*18
            by2 = iy+10+int(math.sin(FC()*0.1+bi*2)*14)
            col = [YL,BL,OR][bi]
            pyxel.circ(bx2,by2,6,col)
            pyxel.circb(bx2,by2,6,WH)
        pyxel.rect(ix+16,iy+ih-10,24,8,BR)
        pyxel.rectb(ix+16,iy+ih-10,24,8,YL)
    elif sid == S_DUCK:
        pyxel.rect(ix,iy,iw,ih,GR)
        pyxel.rect(ix,iy,iw,14,BL)
        pyxel.circ(ix+28,iy+28,12,WH)
        pyxel.circ(ix+28,iy+28,8,RD)
        pyxel.circ(ix+28,iy+28,4,WH)
        pyxel.circ(ix+28,iy+28,2,RD)
        pyxel.circ(ix+8,iy+32,5,YL)
        pyxel.circ(ix+13,iy+29,4,OR)
        pyxel.rect(ix+16,iy+27,3,2,OR)

def _bude(p,scores,active=False):
    x,y,w,h,col = p["x"],p["y"],p["w"],p["h"],p["col"]
    pulse = int(math.sin(FC()*0.14)*2) if active else 0
    pyxel.rect(x+4,y+4,w+10,h+20,DK)
    pyxel.rect(x-2,y,w+4,h+16,WH)
    dach_h = 18
    pyxel.rect(x-6,y-dach_h,w+12,dach_h,col)
    stripe = YL if col != YL else WH
    for i in range(0,w+12,10): pyxel.rect(x-6+i,y-dach_h,5,dach_h,stripe)
    for i in range(0,w+12,8):  pyxel.tri(x-6+i,y,x-2+i,y-dach_h//2,x+2+i,y,col)
    pyxel.rect(x-2,y+h+12,w+4,4,col)
    pyxel.rectb(x-2-pulse,y-pulse,w+4+pulse*2,h+16+pulse*2,YL if active else DK)
    _draw_portal_image(p)
    lbl = p["label"]
    pyxel.text(x+w//2-len(lbl)*2,y+h+17,lbl,YL if active else WH)
    pyxel.text(x+w//2-10,y+h+26,f"HS:{scores[p['id']]}",GY)
    for i in range(0,w+12,10):
        pyxel.circ(x-6+i,y-dach_h-3,2,FLAG_COLORS[((i//10)+FC()//6)%len(FLAG_COLORS)])


# ── INTRO / TITELBILD ──────────────────────────────────────────────────────
class Intro:
    def __init__(self):
        self.t = 0

    def update(self):
        self.t += 1
        if pyxel.btnp(pyxel.KEY_RETURN) or pyxel.btnp(pyxel.KEY_SPACE):
            pyxel.play(0,SND_SELECT)
            return S_LOBBY
        return S_INTRO

    def draw(self):
        # Himmel mit Farbverlauf
        for i in range(H):
            if i < H//2:
                c = NV if i < H//4 else IN
                pyxel.line(0,i,W,i,c)
        # Wiese
        pyxel.rect(0,H-110,W,110,GR)
        # Wiesen-Details
        for i in range(0,W,18):
            pyxel.tri(i,H-110,i+9,H-122,i+18,H-110,LG)

        # RIESENRAD GROSS IM ZENTRUM
        cx,cy,rr = W//2, H//2-10, 110
        # Stützen
        pyxel.rect(cx-5,cy+rr,10,90,BR)
        pyxel.rect(cx-60,cy+rr+85,120,10,BR)
        # Diagonale Streben
        pyxel.line(cx,cy+rr,cx-55,cy+rr+85,BR)
        pyxel.line(cx,cy+rr,cx+55,cy+rr+85,BR)
        # Äusserer Ring
        pyxel.circb(cx,cy,rr,YL)
        pyxel.circb(cx,cy,rr-3,OR)
        pyxel.circb(cx,cy,rr-6,YL)
        # Speichen + Gondeln
        for i in range(12):
            a = i*math.pi/6 + FC()*0.015
            ex = int(cx+math.cos(a)*rr)
            ey = int(cy+math.sin(a)*rr)
            pyxel.line(cx,cy,ex,ey,YL)
            gx = ex-5; gy = ey-5
            pyxel.rect(gx,gy,10,10,FLAG_COLORS[i%len(FLAG_COLORS)])
            pyxel.rectb(gx,gy,10,10,WH)
        # Mittelnabe
        pyxel.circ(cx,cy,10,WH)
        pyxel.circ(cx,cy,6,YL)
        pyxel.circ(cx,cy,3,OR)

        # Zirkuszelt links
        tx,ty = 95,H-110
        pyxel.tri(tx-60,ty,tx,ty-80,tx+60,ty,RD)
        for i in range(-60,60,20):
            pyxel.tri(tx+i,ty,tx+i+10,ty-(80-abs(i)*80//60)//2,tx+i+20,ty,YL if (i//20)%2==0 else PK)
        pyxel.rect(tx-60,ty,120,10,WH)
        pyxel.circ(tx,ty-80,4,YL)

        # Karussell rechts
        kx,ky = W-95,H-110
        pyxel.rect(kx-3,ky-5,6,45,BR)
        pyxel.rect(kx-22,ky+35,44,8,BR)
        pyxel.tri(kx-32,ky,kx,ky-28,kx+32,ky,PU)
        pyxel.tri(kx-20,ky,kx,ky-16,kx+20,ky,YL)
        for i in range(6):
            a = i*math.pi/3+FC()*0.03
            hx = int(kx+math.cos(a)*26); hy = int(ky+math.sin(a)*8)
            pyxel.line(kx,ky-2,hx,hy+6,GY)
            pyxel.rect(hx-3,hy,7,10,FLAG_COLORS[i%len(FLAG_COLORS)])
        pyxel.circ(kx,ky-28,4,YL)

        # Viele Luftballons überall
        balloon_data = [
            (50,  80,  RD),  (110, 55,  YL),  (170, 90,  BL),
            (230, 60,  OR),  (300, 75,  PK),  (360, 50,  LG),
            (420, 85,  PU),  (470, 65,  RD),  (30,  130, YL),
            (490, 120, BL),  (140, 110, OR),  (390, 100, PK),
        ]
        for bx,by,bc in balloon_data:
            ox = int(math.sin(FC()*0.03+bx*0.05)*5)
            oy = int(math.cos(FC()*0.04+bx*0.03)*4)
            _balloon(bx+ox,by+oy,bc)

        # Viele Leute am Boden
        people = [
            (30,  H-30, RD), (55,  H-30, BL), (80,  H-30, YL),
            (150, H-30, PK), (175, H-30, OR), (200, H-30, PU),
            (310, H-30, BL), (335, H-30, RD), (360, H-30, LG),
            (430, H-30, YL), (455, H-30, PK), (480, H-30, OR),
        ]
        for px2,py2,pc in people:
            _person(px2,py2,pc)

        # 2 Clowns
        _clown(120,H-46)
        _clown(360,H-46)

        # Lichterkette oben
        for i in range(0,W,14):
            pyxel.circ(i,12+int(math.sin(FC()*0.04+i*0.2)*3),3,FLAG_COLORS[(i//14+FC()//6)%len(FLAG_COLORS)])
        pyxel.line(0,12,W,12,GY)

        # Lichterkette in der Mitte (Bogen)
        for i in range(0,W,12):
            bx2 = i
            by2 = int(H//2+80+math.sin(i*math.pi/W)*(-30)+math.sin(FC()*0.04+i*0.15)*2)
            pyxel.circ(bx2,by2,2,FLAG_COLORS[(i//12+FC()//8)%len(FLAG_COLORS)])

        # Flaggen
        _flags(H-112,20)

        # Titel
        title = "JAHRMARKT"
        pulse = 1 if (FC()//8)%2==0 else 0
        for i,ch in enumerate(title):
            pyxel.text(W//2-len(title)*4+i*8,78+pulse,ch,FLAG_COLORS[i%len(FLAG_COLORS)])
        sub = "HUB WORLD EDITION"
        pyxel.text(W//2-len(sub)*2,94,sub,WH)

        # Blinkender Hinweis
        if (FC()//15)%2==0:
            msg = "ENTER / SPACE druecken zum Start"
            pyxel.text(W//2-len(msg)*2,H-14,msg,YL)


# ── LOBBY ──────────────────────────────────────────────────────────────────
class Lobby:
    def __init__(self,scores):
        self.scores = scores
        self.px = W//2-7; self.py = 270; self.ap = None

    def update(self):
        sp = 3
        if pyxel.btn(pyxel.KEY_LEFT)  or pyxel.btn(pyxel.KEY_A): self.px -= sp
        if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.KEY_D): self.px += sp
        if pyxel.btn(pyxel.KEY_UP)    or pyxel.btn(pyxel.KEY_W): self.py -= sp
        if pyxel.btn(pyxel.KEY_DOWN)  or pyxel.btn(pyxel.KEY_S): self.py += sp
        self.px = max(0,min(W-14,self.px))
        self.py = max(50,min(H-20,self.py))
        self.ap = None
        for p in PORTALS:
            if overlap(self.px,self.py,14,14,p["x"],p["y"],p["w"],p["h"]):
                self.ap = p; break
        if self.ap and pyxel.btnp(pyxel.KEY_RETURN):
            pyxel.play(0,SND_SELECT)
            return self.ap["id"]
        return S_LOBBY

    def draw(self):
        # Hintergrund
        pyxel.rect(0,0,W,H,GR)

        # Wiesen-Grasdetails
        for i in range(0,W,16):
            pyxel.tri(i,H-22,i+8,H-32,i+16,H-22,LG)

        # RIESENRAD (rechts)
        cx,cy,rr = 420,195,65
        pyxel.rect(cx-4,cy+rr,8,80,BR)
        pyxel.rect(cx-30,cy+rr+75,60,8,BR)
        pyxel.line(cx,cy+rr,cx-28,cy+rr+75,BR)
        pyxel.line(cx,cy+rr,cx+28,cy+rr+75,BR)
        pyxel.circb(cx,cy,rr,YL); pyxel.circb(cx,cy,rr-2,OR)
        for i in range(10):
            a = i*math.pi/5+FC()*0.018
            pyxel.line(cx,cy,int(cx+math.cos(a)*rr),int(cy+math.sin(a)*rr),YL)
            gx = int(cx+math.cos(a)*rr)-5; gy = int(cy+math.sin(a)*rr)-5
            pyxel.rect(gx,gy,10,10,FLAG_COLORS[i%len(FLAG_COLORS)])
            pyxel.rectb(gx,gy,10,10,WH)
        pyxel.circ(cx,cy,7,WH); pyxel.circ(cx,cy,4,YL)

        # KARUSSELL (links)
        kx,ky = 75,210
        pyxel.rect(kx-3,ky+18,6,55,BR)
        pyxel.rect(kx-22,ky+68,44,8,BR)
        pyxel.tri(kx-30,ky+20,kx,ky-10,kx+30,ky+20,RD)
        pyxel.tri(kx-20,ky+20,kx,ky,kx+20,ky+20,YL)
        for i in range(6):
            a = i*math.pi/3+FC()*0.025
            hx = int(kx+math.cos(a)*28); hy = int(ky+20+math.sin(a)*8)
            pyxel.line(kx,ky+18,hx,hy+8,BR)
            pyxel.rect(hx-4,hy,9,12,FLAG_COLORS[i%len(FLAG_COLORS)])
            pyxel.rectb(hx-4,hy,9,12,WH)
        pyxel.circ(kx,ky-10,5,YL)

        # SCHAUKEL (mitte-links)
        sx,sy = 168,100
        pyxel.rect(sx-2,sy,4,70,BR); pyxel.rect(sx+74,sy,4,70,BR)
        pyxel.rect(sx-2,sy,80,5,BR)
        for i in range(4):
            ox = sx+10+i*18
            sw = int(math.sin(FC()*0.06+i*1.1)*12)
            pyxel.line(ox,sy+5,ox+sw,sy+45,GY)
            pyxel.line(ox+8,sy+5,ox+8+sw,sy+45,GY)
            pyxel.rect(ox-2+sw,sy+43,14,5,BR)
            pyxel.circ(ox+4+sw,sy+40,4,FLAG_COLORS[i%len(FLAG_COLORS)])

        # ZUCKERWATTE-STAND
        zx,zy = 230,340
        pyxel.rect(zx,zy,40,28,YL); pyxel.rectb(zx,zy,40,28,BR)
        pyxel.text(zx+3,zy+10,"Zucker",RD)
        for i in range(3):
            wx2 = zx+5+i*13
            wy2 = zy-18+int(math.sin(FC()*0.05+i)*3)
            pyxel.circ(wx2,wy2,8,PK); pyxel.circ(wx2+5,wy2-3,6,PK)
            pyxel.rect(wx2+1,wy2,3,18,BR)

        # LOSBUDE
        lx,ly = 340,345
        pyxel.rect(lx,ly,55,32,WH); pyxel.rectb(lx,ly,55,32,OR)
        pyxel.rect(lx,ly-14,55,14,OR)
        for i in range(0,55,10): pyxel.rect(lx+i,ly-14,5,14,YL)
        pyxel.text(lx+6,ly+5,"LOSE!",RD)
        pyxel.text(lx+6,ly+16,"Gl.rad",GY)

        # VIELE LUFTBALLONS überall auf der Wiese
        lobby_balloons = [
            (18,  400, RD),  (38,  370, YL),  (58,  410, BL),
            (140, 390, OR),  (160, 365, PK),
            (310, 385, LG),  (330, 360, PU),  (350, 395, RD),
            (460, 375, YL),  (480, 400, BL),  (500, 360, OR),
            (100, 430, PK),  (200, 420, LG),  (400, 425, PU),
            (255, 150, RD),  (270, 130, YL),  (240, 145, BL),
        ]
        for bx,by,bc in lobby_balloons:
            ox = int(math.sin(FC()*0.03+bx*0.07)*4)
            oy = int(math.cos(FC()*0.04+bx*0.05)*3)
            _balloon(bx+ox,by+oy,bc)

        # Leute auf der Wiese
        lobby_people = [
            (20,  H-30, YL), (110, H-30, RD), (190, H-30, BL),
            (370, H-30, OR), (450, H-30, PK), (490, H-30, LG),
        ]
        for px2,py2,pc in lobby_people:
            _person(px2,py2,pc)

        # LICHTERKETTEN: horizontal oben
        for i in range(0,W,14):
            pyxel.circ(i,8+int(math.sin(FC()*0.04+i*0.2)*3),3,
                       FLAG_COLORS[(i//14+FC()//6)%len(FLAG_COLORS)])
        pyxel.line(0,8,W,8,GY)

        # LICHTERKETTEN: zweite Reihe
        for i in range(0,W,14):
            pyxel.circ(i,26+int(math.sin(FC()*0.04+i*0.2+1)*3),3,
                       FLAG_COLORS[(i//14+2+FC()//6)%len(FLAG_COLORS)])
        pyxel.line(0,26,W,26,GY)

        # LICHTERKETTEN zwischen den Buden (Bögen)
        for i in range(len(PORTALS)):
            p1 = PORTALS[i]; p2 = PORTALS[(i+1)%len(PORTALS)]
            x1 = p1["x"]+p1["w"]//2; y1 = p1["y"]+p1["h"]//2
            x2 = p2["x"]+p2["w"]//2; y2 = p2["y"]+p2["h"]//2
            for s in range(18):
                t = s/18
                bx2 = int(x1+(x2-x1)*t)
                by2 = int(y1+(y2-y1)*t-math.sin(t*math.pi)*40)
                pyxel.circ(bx2,by2,2,FLAG_COLORS[(s+FC()//8)%len(FLAG_COLORS)])

        # Zusätzliche Lichterkette um den Rand
        for i in range(0,W,16):
            pyxel.circ(i,H-24+int(math.sin(FC()*0.05+i*0.3)*2),2,
                       FLAG_COLORS[(i//16+FC()//7)%len(FLAG_COLORS)])

        for p in PORTALS: _bude(p,self.scores,active=(p is self.ap))
        _clown(self.px,self.py)

        pyxel.rect(0,H-22,W,22,BK)
        msg = ">>> ENTER: Bude betreten! <<<" if self.ap else "Laufe zu einer Jahrmarktbude!"
        pyxel.text(W//2-len(msg)*2,H-16,msg,YL if self.ap else WH)
        pyxel.text(4,H-8,"ESC=Ende",GY)


# ── POOL ───────────────────────────────────────────────────────────────────
class WaterGame:
    def __init__(self):
        self.px = W//2; self.py = H-40
        self.sharks = []
        for _ in range(8):
            dx = random.uniform(2,4)
            if random.random() < 0.5: dx = -dx
            self.sharks.append({"x":random.randint(0,W-28),
                                 "y":random.randint(-H,-20),
                                 "dx":dx,
                                 "phase":random.uniform(0,6)})
        self.bubbles = [{"x":random.randint(0,W),"y":random.randint(0,H)} for _ in range(30)]
        self.timer = FPS*22; self.score = 0

    def update(self):
        if pyxel.btn(pyxel.KEY_LEFT)  or pyxel.btn(pyxel.KEY_A): self.px -= 3
        if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.KEY_D): self.px += 3
        if pyxel.btn(pyxel.KEY_UP)    or pyxel.btn(pyxel.KEY_W): self.py -= 4
        else: self.py += 1.4
        if pyxel.btnp(pyxel.KEY_UP) or pyxel.btnp(pyxel.KEY_W): pyxel.play(0,SND_SPLASH)
        self.px = max(0,min(W-14,int(self.px)))
        self.py = max(0,min(H-14,self.py))
        t = self.timer
        for sh in self.sharks:
            sp = 1.0+((FPS*22-t)/(FPS*22))*1.5
            sh["y"] += 2.2*sp
            sh["x"] += sh["dx"]+math.sin(t*0.05+sh["phase"])*1.5
            if sh["x"] < -28: sh["x"] = -28
            if sh["x"] > W:   sh["x"] = W
            if sh["y"] > H:
                sh["y"] = random.randint(-120,-20)
                sh["x"] = random.randint(0,W-28)
            if overlap(int(self.px),int(self.py),14,14,int(sh["x"]),int(sh["y"]),28,12):
                pyxel.play(0,SND_OVER)
                return S_OVER,self.score
        for b in self.bubbles:
            b["y"] -= 0.6
            if b["y"] < 0: b["y"] = H; b["x"] = random.randint(0,W)
        self.timer -= 1
        self.score = (FPS*22-self.timer)//FPS
        if self.py < 18:
            pyxel.play(0,SND_WIN)
            return S_WIN,self.score
        if self.timer <= 0:
            pyxel.play(0,SND_OVER)
            return S_OVER,self.score
        return S_WATER,self.score

    def draw(self):
        pyxel.rect(0,0,W,H,NV)
        for i in range(H//20): pyxel.rect(0,i*20,W,10,BL if i%2==0 else NV)
        for b in self.bubbles:
            pyxel.circb(int(b["x"]),int(b["y"]),3,BL)
            pyxel.pset(int(b["x"]),int(b["y"]),WH)
        for i in range(0,W,14):
            pyxel.rect(i,18+int(math.sin(FC()*0.12+i*0.25)*5),14,12,LG)
        pyxel.text(W//2-48,4,"~ OBERFLAECHE - ZIEL! ~",WH)
        for sh in self.sharks:
            sx,sy = int(sh["x"]),int(sh["y"])
            pyxel.rect(sx,sy,28,12,GY)
            pyxel.tri(sx+20,sy-10,sx+14,sy,sx+24,sy,WH)
            pyxel.pset(sx+22,sy+5,RD)
            pyxel.tri(sx,sy+6,sx-8,sy+4,sx-8,sy+10,GY)
        _clown(int(self.px),int(self.py))
        pyxel.rect(0,H-22,W,22,NV)
        t = self.timer//FPS
        col = RD if t < 8 else (YL if t < 14 else WH)
        pyxel.text(4,H-18,f"Zeit:{t}s",col)
        pyxel.text(80,H-18,f"Punkte:{self.score}",WH)
        pyxel.text(200,H-18,"W=Schwimmen!",GY)


# ── RAKETE ─────────────────────────────────────────────────────────────────
class SpaceGame:
    def __init__(self):
        self.px = W//2-8; self.py = H-36
        self.bullets = []; self.asts = []
        self.stars = []
        for _ in range(70):
            self.stars.append({"x":random.randint(0,W),"y":random.randint(0,H),"sp":random.uniform(0.5,1.8)})
        self.score = 0; self.lives = 3; self.cd = 0; self.t = 0

    def update(self):
        if pyxel.btn(pyxel.KEY_LEFT)  or pyxel.btn(pyxel.KEY_A): self.px -= 3
        if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.KEY_D): self.px += 3
        self.px = max(0,min(W-16,self.px))
        self.cd -= 1
        fire = pyxel.btn(pyxel.KEY_SPACE) or pyxel.btn(pyxel.KEY_UP)
        if fire and self.cd <= 0:
            self.bullets.append({"x":self.px+7,"y":self.py}); self.cd = 7
            pyxel.play(0,SND_SHOOT)
        new_b = []
        for b in self.bullets:
            b["y"] -= 7
            if b["y"] > 0: new_b.append(b)
        self.bullets = new_b
        for st in self.stars:
            st["y"] += st["sp"]
            if st["y"] > H: st["y"] = 0; st["x"] = random.randint(0,W)
        self.t += 1
        if self.t % max(10,32-self.score//2) == 0:
            self.asts.append({"x":random.randint(0,W-14),"y":-14,
                              "vy":random.uniform(1.8,3.5),"r":random.randint(6,13)})
        for a in self.asts: a["y"] += a["vy"]
        hit_a = set(); hit_b = set()
        for i,a in enumerate(self.asts):
            for j,b in enumerate(self.bullets):
                if overlap(int(b["x"])-2,int(b["y"])-2,4,4,
                           int(a["x"])-a["r"],int(a["y"])-a["r"],a["r"]*2,a["r"]*2):
                    hit_a.add(i); hit_b.add(j); self.score += 1
        self.asts    = [a for i,a in enumerate(self.asts)    if i not in hit_a]
        self.bullets = [b for i,b in enumerate(self.bullets) if i not in hit_b]
        if hit_a: pyxel.play(0,SND_HIT)
        for a in list(self.asts):
            if overlap(self.px,self.py,16,18,
                       int(a["x"])-a["r"],int(a["y"])-a["r"],a["r"]*2,a["r"]*2):
                self.lives -= 1; self.asts.remove(a)
                pyxel.play(0,SND_HURT)
                if self.lives <= 0:
                    pyxel.play(0,SND_OVER)
                    return S_OVER,self.score
                break
        self.asts = [a for a in self.asts if a["y"] < H]
        if self.score >= 20:
            pyxel.play(0,SND_WIN)
            return S_WIN,self.score
        return S_SPACE,self.score

    def draw(self):
        pyxel.rect(0,0,W,H,NV)
        for st in self.stars:
            pyxel.pset(int(st["x"]),int(st["y"]),WH if st["sp"] > 1.2 else GY)
        for a in self.asts:
            pyxel.circ(int(a["x"]),int(a["y"]),a["r"],BR)
            pyxel.circb(int(a["x"]),int(a["y"]),a["r"],GY)
        for b in self.bullets: pyxel.rect(int(b["x"]),int(b["y"]),3,8,YL)
        x,y = self.px,self.py
        pyxel.tri(x+8,y,x,y+18,x+16,y+18,BL)
        pyxel.tri(x+8,y+8,x+4,y+18,x+12,y+18,IN)
        pyxel.pset(x+8,y+10,WH)
        if FC()%4 < 2: pyxel.tri(x+5,y+18,x+11,y+18,x+8,y+28,OR)
        pyxel.rect(0,0,W,18,NV)
        lives_str = "v" * self.lives
        pyxel.text(4,5,f"Score:{self.score}/20  Leben:{lives_str}  SPC=Feuer",WH)


# ── SCHIESSEN ──────────────────────────────────────────────────────────────
class DuckGame:
    def __init__(self):
        self.bullets = []; self.ducks = []
        self.score = 0; self.lives = 5
        self.t = 0; self.cd = 0; self.cx = W//2

    def update(self):
        if pyxel.btn(pyxel.KEY_LEFT)  or pyxel.btn(pyxel.KEY_A): self.cx = max(20,self.cx-4)
        if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.KEY_D): self.cx = min(W-20,self.cx+4)
        self.cd -= 1
        fire = pyxel.btn(pyxel.KEY_SPACE) or pyxel.btn(pyxel.KEY_UP)
        if fire and self.cd <= 0:
            self.bullets.append({"x":self.cx,"y":H-50}); self.cd = 10
            pyxel.play(0,SND_SHOOT)
        new_b = []
        for b in self.bullets:
            b["y"] -= 8
            if b["y"] > 0: new_b.append(b)
        self.bullets = new_b
        self.t += 1
        interval = max(18,55-self.score*2)
        if self.t % interval == 0:
            fl = random.random() < 0.5
            spd = random.uniform(2,4)
            self.ducks.append({
                "x":-30 if fl else W+10,
                "y":60+random.randint(0,3)*90,
                "dx":spd if fl else -spd,
                "dy":random.uniform(-0.3,0.3),
                "alive":True,"dead_t":0})
        for d in self.ducks:
            if d["alive"]: d["x"] += d["dx"]; d["y"] += d["dy"]
            else: d["dead_t"] += 1
        for d in list(self.ducks):
            if not d["alive"]: continue
            for b in list(self.bullets):
                if overlap(int(b["x"])-2,int(b["y"])-2,4,4,
                           int(d["x"])-10,int(d["y"])-8,24,16):
                    d["alive"] = False; self.score += 1
                    pyxel.play(0,SND_HIT)
                    if b in self.bullets: self.bullets.remove(b)
                    break
        missed = [d for d in self.ducks if d["alive"] and (d["x"] < -60 or d["x"] > W+60)]
        self.lives -= len(missed)
        if missed: pyxel.play(0,SND_HURT)
        self.ducks = [d for d in self.ducks
                      if not (d["alive"] and (d["x"] < -60 or d["x"] > W+60))]
        self.ducks = [d for d in self.ducks if d["dead_t"] < 15]
        if self.lives <= 0:
            pyxel.play(0,SND_OVER)
            return S_OVER,self.score
        if self.score >= 15:
            pyxel.play(0,SND_WIN)
            return S_WIN,self.score
        return S_DUCK,self.score

    def draw(self):
        pyxel.rect(0,0,W,H,GR); pyxel.rect(0,0,W,50,BL)
        for i in range(0,W,30):
            pyxel.circ(i+int(math.sin(FC()*0.02+i)*3),20,10,WH)
        for xp in [80,200,320,440]:
            pyxel.circ(xp,130,14,RD); pyxel.circ(xp,130,9,WH); pyxel.circ(xp,130,5,RD)
        pyxel.rect(0,55,W,8,BR)
        for row in range(4): pyxel.line(0,60+row*90,W,60+row*90,BR)
        for d in self.ducks:
            dx,dy = int(d["x"]),int(d["y"])
            if d["alive"]:
                pyxel.circ(dx,dy,8,YL)
                hx = dx+(6 if d["dx"] > 0 else -6)
                pyxel.circ(hx,dy-4,5,OR)
                bx2 = dx+(8 if d["dx"] > 0 else -12)
                pyxel.rect(bx2,dy-6,5,3,OR)
                ex = dx+(7 if d["dx"] > 0 else -5)
                pyxel.pset(ex,dy-5,BK)
            else:
                for ii in range(4):
                    a = ii*math.pi/2+d["dead_t"]*0.2
                    pyxel.pset(dx+int(math.cos(a)*8),dy+int(math.sin(a)*8),YL)
        for b in self.bullets: pyxel.rect(int(b["x"])-1,int(b["y"])-4,3,8,WH)
        pyxel.rect(self.cx-14,H-40,28,28,GY)
        pyxel.rect(self.cx-3,H-52,6,16,DK)
        pyxel.rectb(self.cx-14,H-40,28,28,WH)
        pyxel.rect(0,H-22,W,22,BK)
        lives_str = "|" * self.lives
        pyxel.text(4,H-18,f"Score:{self.score}/15  Leben:{lives_str}  SPC=Schuss",WH)


# ── ZIRKUS ─────────────────────────────────────────────────────────────────
class CircusGame:
    def __init__(self):
        self.px = W//2-12; self.py = H-50
        self.balls = []; self.score = 0; self.lives = 3; self.t = 0

    def update(self):
        if pyxel.btn(pyxel.KEY_LEFT)  or pyxel.btn(pyxel.KEY_A): self.px -= 5
        if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.KEY_D): self.px += 5
        self.px = max(0,min(W-28,self.px))
        self.t += 1
        if self.t % max(18,48-self.score//3) == 0:
            col = random.choice([RD,YL,BL,OR,PK,LG])
            self.balls.append({"x":random.randint(10,W-20),"y":-10,
                               "vy":random.uniform(2,5),"col":col,"good":True})
        if self.t % 55 == 0:
            self.balls.append({"x":random.randint(10,W-20),"y":-10,
                               "vy":random.uniform(2,4),"col":DK,"good":False})
        for b in self.balls: b["y"] += b["vy"]
        missed = [b for b in self.balls if b["y"] > H and b["good"]]
        self.balls = [b for b in self.balls if b["y"] < H]
        self.lives -= len(missed)
        if missed: pyxel.play(0,SND_HURT)
        for b in list(self.balls):
            if overlap(self.px,self.py,28,14,int(b["x"])-10,int(b["y"])-10,20,20):
                if b["good"]:
                    self.score += 1
                    pyxel.play(0,SND_HIT)
                else:
                    self.lives -= 1
                    pyxel.play(0,SND_HURT)
                self.balls.remove(b)
        if self.lives <= 0:
            pyxel.play(0,SND_OVER)
            return S_OVER,self.score
        if self.score >= 25:
            pyxel.play(0,SND_WIN)
            return S_WIN,self.score
        return S_CIRCUS,self.score

    def draw(self):
        pyxel.rect(0,0,W,H,RD)
        for i in range(0,W,36): pyxel.rect(i,0,18,H,PK)
        _flags(8,18)
        for i,sx in enumerate([80,256,430]):
            a = math.sin(FC()*0.04+i*2)*0.4
            ex = int(sx+math.sin(a)*100)
            pyxel.line(sx,0,ex,H-60,YL)
        for b in self.balls:
            pyxel.circ(int(b["x"]),int(b["y"]),10,b["col"])
            pyxel.circb(int(b["x"]),int(b["y"]),10,WH)
            lbl = "X" if not b["good"] else "o"
            pyxel.text(int(b["x"])-3,int(b["y"])-3,lbl,WH)
        pyxel.rect(self.px,self.py,28,14,BR)
        pyxel.rectb(self.px,self.py,28,14,YL)
        pyxel.text(self.px+6,self.py+3,"[]",WH)
        pyxel.rect(0,H-22,W,22,BK)
        lives_str = "o" * self.lives
        pyxel.text(4,H-18,f"Score:{self.score}/25  Leben:{lives_str}  X=SCHLECHT",WH)


# ── GAME CONTROLLER ────────────────────────────────────────────────────────
class Game:
    def __init__(self):
        pyxel.init(W,H,title="Jahrmarkt Hub World",fps=FPS)
        setup_sounds()
        self.scores = {S_WATER:0,S_SPACE:0,S_DUCK:0,S_CIRCUS:0}
        self.state = S_INTRO
        self.scene = Intro()
        pyxel.run(self.update,self.draw)

    def _enter(self,sid):
        self.state = sid
        if   sid == S_INTRO:  self.scene = Intro()
        elif sid == S_LOBBY:  self.scene = Lobby(self.scores)
        elif sid == S_WATER:  self.scene = WaterGame()
        elif sid == S_SPACE:  self.scene = SpaceGame()
        elif sid == S_DUCK:   self.scene = DuckGame()
        elif sid == S_CIRCUS: self.scene = CircusGame()

    def update(self):
        if pyxel.btnp(pyxel.KEY_ESCAPE):
            if self.state != S_LOBBY: self._enter(S_LOBBY)
            else: pyxel.quit()
            return
        if self.state in (S_OVER,S_WIN):   
            if pyxel.btnp(pyxel.KEY_RETURN): self._enter(S_LOBBY)
            return
        result = self.scene.update()
        if isinstance(result,tuple):
            new_state,score = result
            self.scores[self.state] = max(self.scores.get(self.state,0),score)
            if new_state in (S_OVER,S_WIN):
                self.state = new_state
            elif new_state != self.state:
                self._enter(new_state)
        elif result != self.state:
            self._enter(result)

    def draw(self):
        pyxel.cls(BK)
        if self.state in (S_OVER,S_WIN): self._drw_end()
        else: self.scene.draw()

    def _drw_end(self):
        win = self.state == S_WIN
        pyxel.rect(60,160,392,180,NV if win else BK)
        pyxel.rectb(60,160,392,180,YL if win else RD)
        pyxel.rect(64,164,384,34,OR if win else RD)
        for i in range(0,390,26):
            pyxel.tri(60+i,164,60+i+13,198,60+i+26,164,PK if win else YL)
        msg = "LEVEL GESCHAFFT!" if win else "GAME OVER"
        pyxel.text(W//2-(len(msg)*4)//2,170,msg,YL if win else WH)
        if win:
            for i in range(8): pyxel.circ(80+i*46,200,5,FLAG_COLORS[i%len(FLAG_COLORS)])
        pyxel.text(70,210,"Highscores:",YL)
        pyxel.text(70,224,f"Pool:{self.scores[S_WATER]}  Rakete:{self.scores[S_SPACE]}",WH)
        pyxel.text(70,238,f"Zirkus:{self.scores[S_CIRCUS]}  Schiessen:{self.scores[S_DUCK]}",WH)
        pyxel.text(70,268,"ENTER: Zurueck zur Lobby",GY)

if __name__ == "__main__":
    Game()