import pygame,pygame.gfxdraw,random,sys,math
from enum import Enum
from collections import deque
pygame.init()
W,WINH=1280,720
FPS=60
C=((210,190,160),(160,160,170),(100,90,80),(120,130,150),(200,200,210),(150,130,110),(10,10,15),(20,60,90),(80,70,50),(60,70,80),(180,200,150),(150,150,180),(50,50,70),(180,170,190),(255,200,220),(200,220,240),(100,120,100),(150,150,160),(200,200,180),(180,160,140),(130,130,120),(200,200,200),(10,10,20),(50,100,255),(120,80,40),(30,80,30),(180,120,80),(180,200,220),(255,180,100),(20,20,40),(80,20,20),(200,100,200),(100,80,120),(150,150,150),(100,80,60),(60,100,60),(255,255,200))
F=((235,220,190),(180,180,190),(120,100,90),(140,150,170),(220,220,230),(170,150,130),(10,10,15),(30,80,120),(100,90,70),(80,90,100),(200,220,170),(170,170,200),(70,70,90),(200,190,210),(255,220,240),(220,240,255),(120,140,120),(170,170,180),(220,220,200),(200,180,160),(150,150,140),(200,200,200),(20,20,30),(50,150,255),(160,100,60),(40,100,40),(200,140,100),(200,220,240),(255,200,120),(40,40,60),(120,40,40),(220,120,220),(120,100,140),(180,180,180),(120,100,80),(80,120,80),(240,240,220))
PL=(70,150,230);PG=(120,200,255);EX=(60,200,80);EG=(100,255,150);ED=(200,60,60);DG=(255,100,100)
INFO={
0:('Level 0','泛黄大厅','等级1','无边零售店仓库，无实体，孤立效应。'),
1:('Level 1','宜居地带','等级1','地下停车场，灯光不稳，物资箱。'),
2:('Level 2','管道噩梦','等级3','昏暗维护隧道，少量敌对实体。'),
3:('Level 3','发电站','等级4','黑暗走廊，机器噪音，Wi-Fi最强。'),
4:('Level 4','废弃办公室','等级1','空旷办公大楼，杏仁水高频，无实体。'),
5:('Level 5','恐怖旅馆','等级2','无尽酒店，爵士乐，超自然现象。'),
6:('Level 6','熄灯','等级?','绝对黑暗，任何光源失效，寂静。'),
7:('Level 7','深海恐惧症','等级4','无限海洋，蒸馏水，坠入开局。'),
8:('Level 8','岩洞系统','等级4','地下洞穴，杏仁水渗出，毒蜘蛛。'),
9:('Level 9','郊区','等级2','永恒深夜，路灯幽蓝，树木攻击。'),
10:('Level 10','丰裕','等级1','无边麦田，小麦可食，适合定居。'),
11:('Level 11','无垠城市','等级2','巨大无限城市，主要聚居地。'),
12:('Level 12','矩阵','等级5','故障效果，控制反转。'),
13:('Level 13','无限公寓','等级3','公寓楼，陷阱传送。'),
14:('Level 14','天堂','等级4','美丽但吸取生命。'),
15:('Level 15','未来走廊','等级2','金属走廊，发光带。'),
16:('Level 16','地形转变','等级4','地图30秒重绘。'),
17:('Level 17','航空母舰','等级3','巨大甲板与舱室。'),
18:('Level 18','回忆','等级?','显示统计数据。'),
19:('Level 19','阁楼','等级2','低矮通道，减速。'),
20:('Level 20','仓库/娱乐区','等级1','巨大仓库，游戏机。'),
43:('枢纽','中心走廊','等级0','连接众多层级的中转站。'),
44:('虚空','绝对虚无','等级5','空无，失去感知。'),
45:('蓝色通道','蓝色通道','等级2','无限蓝色通道，易迷失。'),
46:('破碎之地','破碎之地','等级4','地面塌陷重组。'),
47:('遗忘之森','遗忘之森','等级3','无尽森林，遗忘气息。'),
48:('铸造厂','铸造厂','等级4','工业铸造厂，高温危险。'),
49:('前厅','现实世界','等级0','后室对应的现实世界。'),
50:('永恒欢宴','无尽宴会','等级3','狂欢宴会，沉迷无法离开。'),
51:('黑暗地铁','黑暗地铁','等级4','无尽黑暗地铁，躲避列车。'),
52:('死亡之门','死亡之门','等级5','与死亡相关，终极审判。'),
53:('宇宙之心','宇宙之心','等级?','时空扭曲的神秘区域。'),
54:('悔恨','悔恨','等级4','无尽悔恨情绪，吞噬心智。'),
55:('弥留之际','弥留之际','等级5','生死边缘，生命波动。'),
56:('战地医院','战地医院','等级4','废弃医院，痛苦与死亡。'),
57:('Level Θ','Theta','等级?','未知，异常危险。'),
58:('结束层','最终出口','等级0','真正的出口，回归现实。')
}
def gf(s):return pygame.font.Font(None,s)
fs=gf(20);fm=gf(28);fl=gf(42);ft=gf(56)
def dist(a,b):return math.hypot(a[0]-b[0],a[1]-b[1])
def cl(v,l,h):return max(l,min(h,v))
def mt(bc,var=20):
 s=pygame.Surface((32,32));s.fill(bc)
 for _ in range(170):
  x=random.randint(0,31);y=random.randint(0,31);o=random.randint(-var//2,var//2)
  s.set_at((x,y),tuple(cl(bc[i]+o,0,255)for i in range(3)))
 for i in range(0,32,4):pygame.draw.line(s,(bc[0]-10,bc[1]-10,bc[2]-10),(0,i),(32,i),1)
 return s
TC={}
def gt(k,tp):
 key=(k,tp)
 if key in TC:return TC[key]
 wc=C[k%len(C)];fc=F[k%len(F)]
 tex=mt(wc,15)if tp=='wall'else mt(fc,20)
 TC[key]=tex;return tex
class Rm:
 def __init__(s,x,y,w,h):
  s.x=x;s.y=y;s.w=w;s.h=h;s.cx=x+w//2;s.cy=y+h//2
def genc(w,h,rc,rmn,rmx,cs=0):
 d=[[1]*w for _ in range(h)];rs=[]
 for _ in range(rc):
  rw=random.randint(rmn,rmx);rh=random.randint(rmn,rmx);rx=random.randint(1,w-rw-1);ry=random.randint(1,h-rh-1)
  nr=Rm(rx,ry,rw,rh)
  if not any(rx<r.x+r.w+1 and rx+rw+1>r.x and ry<r.y+r.h+1 and ry+rh+1>r.y for r in rs):
   rs.append(nr)
   for iy in range(ry,ry+rh):
    for ix in range(rx,rx+rw):d[iy][ix]=0
 for i in range(len(rs)-1):
  x1,y1=rs[i].cx,rs[i].cy;x2,y2=rs[i+1].cx,rs[i+1].cy
  if cs==0:
   for x in range(min(x1,x2),max(x1,x2)+1):d[y1][x]=0
   for y in range(min(y1,y2),max(y1,y2)+1):d[y][x2]=0
  else:
   for y in range(min(y1,y2),max(y1,y2)+1):d[y][x1]=0
   for x in range(min(x1,x2),max(x1,x2)+1):d[y2][x]=0
 return d,rs
def gen0(w,h):
 d,rs=genc(w,h,50,4,10,0);fur=[]
 for r in rs:
  if random.random()<0.3:
   fx=r.x+random.randint(1,r.w-2);fy=r.y+random.randint(1,r.h-2)
   if d[fy][fx]==0:d[fy][fx]=2;fur.append((fx,fy))
 return d,rs,fur
def gen1(w,h):
 d=[[1]*w for _ in range(h)]
 for y in range(2,h-2):
  for x in range(2,w-2):
   if (x%3!=0)or(y%3!=0):d[y][x]=0
 for _ in range(100):
  x=random.randint(3,w-3);y=random.randint(3,h-3)
  if random.random()<0.6:d[y][x]=0
 fur=[]
 for y in range(3,h-3,3):
  for x in range(3,w-3,3):
   if d[y][x]==0 and random.random()<0.5:d[y][x]=2;fur.append((x,y))
 sr=Rm(2,2,4,4);er=Rm(w-6,h-6,4,4)
 for iy in range(sr.y,sr.y+sr.h):
  for ix in range(sr.x,sr.x+sr.w):d[iy][ix]=0
 for iy in range(er.y,er.y+er.h):
  for ix in range(er.x,er.x+er.w):d[iy][ix]=0
 return d,[sr,er],fur
def gen2(w,h):
 d=[[1]*w for _ in range(h)]
 def sp(x,y,w,h):
  if w<4 or h<4:return
  if w>h:
   c=random.randint(x+2,x+w-2)
   for i in range(y,y+h):d[i][c]=d[i][c-1]=0
   sp(x,y,c-x,h);sp(c,y,w-(c-x),h)
  else:
   c=random.randint(y+2,y+h-2)
   for i in range(x,x+w):d[c][i]=d[c-1][i]=0
   sp(x,y,w,c-y);sp(x,c,w,h-(c-y))
 sp(2,2,w-4,h-4)
 for i in range(2,w-2):d[2][i]=d[h-3][i]=0
 for i in range(2,h-2):d[i][2]=d[i][w-3]=0
 st=(3,3);ex=(w-4,h-4);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen3(w,h):
 d,rs=genc(w,h,30,5,12,0);fur=[]
 for r in rs:
  if random.random()<0.2:
   fx=r.x+random.randint(1,r.w-2);fy=r.y+random.randint(1,r.h-2)
   if d[fy][fx]==0:d[fy][fx]=2;fur.append((fx,fy))
 return d,rs,fur
def gen4(w,h):
 d=[[1]*w for _ in range(h)]
 for y in range(2,h-2,2):
  for x in range(2,w-2,2):
   if random.random()<0.7:
    d[y][x]=0
    if random.random()<0.5:d[y+1][x]=0
    if random.random()<0.5:d[y][x+1]=0
 for i in range(2,w-2):d[2][i]=d[h-3][i]=0
 for i in range(2,h-2):d[i][2]=d[i][w-3]=0
 st=(3,3);ex=(w-4,h-4);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen5(w,h):
 d,rs=genc(w,h,40,3,6,random.choice([0,1]));fur=[]
 for r in rs:
  if random.random()<0.2:
   fx=r.x+random.randint(1,r.w-2);fy=r.y+random.randint(1,r.h-2)
   if d[fy][fx]==0:d[fy][fx]=2;fur.append((fx,fy))
 return d,rs,fur
def gen6(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(2,h-2):
  for x in range(2,w-2):
   if random.random()<0.3:d[y][x]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen7(w,h):
 d=[[1]*w for _ in range(h)]
 for y in range(h):
  for x in range(w):
   d[y][x]=2 if random.random()<0.6 else 0
 st=(2,2);ex=(w-3,h-3)
 for iy in range(st[1]-1,st[1]+2):
  for ix in range(st[0]-1,st[0]+2):d[iy][ix]=0
 for iy in range(ex[1]-1,ex[1]+2):
  for ix in range(ex[0]-1,ex[0]+2):d[iy][ix]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen8(w,h):
 d=[[1 if random.random()<0.45 else 0 for _ in range(w)]for _ in range(h)]
 for _ in range(4):
  nd=[row[:]for row in d]
  for y in range(1,h-1):
   for x in range(1,w-1):
    wc=sum(1 for dy in(-1,0,1)for dx in(-1,0,1)if d[y+dy][x+dx]==1)
    nd[y][x]=1 if wc>=5 else 0
  d=nd
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 for x in range(min(st[0],ex[0]),max(st[0],ex[0])+1):d[st[1]][x]=0
 for y in range(min(st[1],ex[1]),max(st[1],ex[1])+1):d[y][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen9(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(0,h,6):
  for x in range(w):d[y][x]=0
 for x in range(0,w,6):
  for y in range(h):d[y][x]=0
 for y in range(2,h-2,6):
  for x in range(2,w-2,6):
   if random.random()<0.6:
    for dy in(-1,0,1):
     for dx in(-1,0,1):
      if 0<=y+dy<h and 0<=x+dx<w:d[y+dy][x+dx]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 for dy in(-1,0,1):
  for dx in(-1,0,1):
   if 0<=st[1]+dy<h and 0<=st[0]+dx<w:d[st[1]+dy][st[0]+dx]=0
   if 0<=ex[1]+dy<h and 0<=ex[0]+dx<w:d[ex[1]+dy][ex[0]+dx]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen10(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(h):
  for x in range(w):
   if random.random()<0.1:d[y][x]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen11(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(0,h,4):
  for x in range(w):d[y][x]=0
 for x in range(0,w,4):
  for y in range(h):d[y][x]=0
 for y in range(2,h-2,4):
  for x in range(2,w-2,4):
   if random.random()<0.5:
    for dy in(-1,0,1):
     for dx in(-1,0,1):
      if 0<=y+dy<h and 0<=x+dx<w:d[y+dy][x+dx]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen12(w,h):
 d=[[1 if random.random()<0.4 else 0 for _ in range(w)]for _ in range(h)]
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen13(w,h):
 d=[[1]*w for _ in range(h)]
 for y in range(2,h-2,3):
  for x in range(2,w-2,3):
   if random.random()<0.7:
    d[y][x]=0
    if random.random()<0.3:d[y+1][x]=0
    if random.random()<0.3:d[y][x+1]=0
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen14(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(2,h-2):
  for x in range(2,w-2):
   if (x+y)%5==0:d[y][x]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen15(w,h):
 d=[[1]*w for _ in range(h)]
 for y in range(2,h-2):
  for x in range(2,w-2):
   if y==h//2:d[y][x]=0
   elif abs(x-w//2)<5:d[y][x]=0
 st=(2,h//2);ex=(w-3,h//2);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen16(w,h):
 d=[[1 if random.random()<0.3 else 0 for _ in range(w)]for _ in range(h)]
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen17(w,h):
 d=[[1]*w for _ in range(h)]
 for y in range(2,h-2,5):
  for x in range(2,w-2,5):
   d[y][x]=0;d[y+1][x]=0;d[y][x+1]=0
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen18(w,h):
 d=[[0]*w for _ in range(h)]
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen19(w,h):
 d=[[1]*w for _ in range(h)]
 for y in range(2,h-2):
  for x in range(2,w-2):
   if y%3==0 or x%3==0:d[y][x]=0
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen20(w,h):
 d=[[0]*w for _ in range(h)]
 for _ in range(30):
  x=random.randint(2,w-3);y=random.randint(2,h-3)
  if d[y][x]==0:d[y][x]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen43(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(h//2-2,h//2+3):
  for x in range(w):d[y][x]=0
 for x in range(w//2-2,w//2+3):
  for y in range(h):d[y][x]=0
 st=(2,h//2);ex=(w-3,h//2);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen44(w,h):
 d=[[0]*w for _ in range(h)]
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen45(w,h):
 d=[[1]*w for _ in range(h)]
 for y in range(h//2-1,h//2+2):
  for x in range(w):d[y][x]=0
 st=(2,h//2);ex=(w-3,h//2);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen46(w,h):
 d=[[1 if random.random()<0.6 else 0 for _ in range(w)]for _ in range(h)]
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen47(w,h):
 d=[[0]*w for _ in range(h)]
 for _ in range(150):
  x=random.randint(1,w-2);y=random.randint(1,h-2)
  if d[y][x]==0:d[y][x]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen48(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(2,h-2):
  for x in range(2,w-2):
   if (x%4==0)and(y%4==0):d[y][x]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen49(w,h):
 d=[[0]*w for _ in range(h)]
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen50(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(2,h-2,4):
  for x in range(2,w-2,4):
   if random.random()<0.3:d[y][x]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen51(w,h):
 d=[[1]*w for _ in range(h)]
 for y in range(2,h-2,3):
  for x in range(w):d[y][x]=0
 st=(2,h//2);ex=(w-3,h//2);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen52(w,h):
 d=[[1 if random.random()<0.7 else 0 for _ in range(w)]for _ in range(h)]
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen53(w,h):
 d=[[0]*w for _ in range(h)]
 cx,cy=w//2,h//2
 for dy in range(-5,6):
  for dx in range(-5,6):
   if abs(dx)<=3 and abs(dy)<=3:d[cy+dy][cx+dx]=0
   else:d[cy+dy][cx+dx]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen54(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(h):
  for x in range(w):
   if random.random()<0.2:d[y][x]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen55(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(2,h-2):
  for x in range(2,w-2):
   if (x+y)%3==0:d[y][x]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen56(w,h):
 d=[[1]*w for _ in range(h)];rs=[]
 for _ in range(20):
  rw=random.randint(3,6);rh=random.randint(3,6);rx=random.randint(2,w-rw-2);ry=random.randint(2,h-rh-2)
  nr=Rm(rx,ry,rw,rh)
  if not any(rx<r.x+r.w+1 and rx+rw+1>r.x and ry<r.y+r.h+1 and ry+rh+1>r.y for r in rs):
   rs.append(nr)
   for iy in range(ry,ry+rh):
    for ix in range(rx,rx+rw):d[iy][ix]=0
 for i in range(len(rs)-1):
  x1,y1=rs[i].cx,rs[i].cy;x2,y2=rs[i+1].cx,rs[i+1].cy
  for x in range(min(x1,x2),max(x1,x2)+1):d[y1][x]=0
  for y in range(min(y1,y2),max(y1,y2)+1):d[y][x2]=0
 st=(rs[0].cx,rs[0].cy)if rs else(2,2);ex=(rs[-1].cx,rs[-1].cy)if rs else(w-3,h-3)
 d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,rs,[]
def gen57(w,h):
 d=[[1 if random.random()<0.3 else 0 for _ in range(w)]for _ in range(h)]
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen58(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(h//2-2,h//2+3):
  for x in range(w):d[y][x]=0
 st=(2,h//2);ex=(w-3,h//2);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 for x in range(0,w,10):
  d[h//2-4][x]=2;d[h//2+4][x]=2
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
def gen_def(w,h):
 d=[[0]*w for _ in range(h)]
 for y in range(2,h-2):
  for x in range(2,w-2):
   if random.random()<0.2:d[y][x]=1
 st=(2,2);ex=(w-3,h-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
 return d,[Rm(st[0]-1,st[1]-1,3,3),Rm(ex[0]-1,ex[1]-1,3,3)],[]
GEN=[gen0,gen1,gen2,gen3,gen4,gen5,gen6,gen7,gen8,gen9,gen10,gen11,gen12,gen13,gen14,gen15,gen16,gen17,gen18,gen19,gen20]+[gen_def]*22+[gen43,gen44,gen45,gen46,gen47,gen48,gen49,gen50,gen51,gen52,gen53,gen54,gen55,gen56,gen57,gen58]
class IT(Enum):KEY=1;NOTE=2;ALMOND=3;TORCH=4;BANDAGE=5;PASS=6;LEVER=7;BAT=8;MED=9;MAP=10
class It:
 def __init__(s,x,y,t,e=''):
  s.x=x;s.y=y;s.type=t;s.extra=e;s.collected=0
 @staticmethod
 def col(t):return{(1):(200,180,100),(2):(220,220,180),(3):(180,220,255),(4):(255,220,100),(5):(255,180,180),(6):(200,200,200),(7):(180,180,220),(8):(255,200,50),(9):(255,100,100),(10):(200,200,255)}.get(t,(200,200,200))
 @staticmethod
 def em(t):return{1:"🔑",2:"📄",3:"🧊",4:"🔦",5:"🩹",6:"🔐",7:"🎛️",8:"🔋",9:"💊",10:"🗺️"}.get(t,"❓")
MT=["hunter","smiler","chaser","stalker","brute","crawler","phantom","spitter","lurker","shambler"]
class Mon:
 def __init__(s,x,y,t="hunter"):
  s.x=x;s.y=y;s.type=t;s.state="idle";s.mt=0;s.dir=random.choice([(0,1),(0,-1),(1,0),(-1,0)]);s.ac=0;s.hp=1;s.wind=0;s.isw=0
  s.dr,s.sp,s.cs,s.dmg,s.ar,s.col,s.wd=6,0.02,0.07,12,1.2,(180,50,50),12
  if t=="hunter":s.dr,s.sp,s.cs,s.dmg,s.ar,s.col,s.wd=6,0.02,0.07,12,1.5,(180,50,50),10
  elif t=="smiler":s.dr,s.sp,s.cs,s.dmg,s.ar,s.col,s.inv,s.wd=4,0.015,0.04,10,1.2,(255,200,200),1,12
  elif t=="chaser":s.dr,s.sp,s.cs,s.dmg,s.ar,s.col,s.wd=12,0.05,0.12,15,1.0,(200,0,0),8
  elif t=="stalker":s.dr,s.sp,s.cs,s.dmg,s.ar,s.col,s.wd=3,0.01,0.03,8,1.2,(80,80,80),15
  elif t=="brute":s.dr,s.sp,s.cs,s.dmg,s.ar,s.col,s.hp,s.wd=5,0.008,0.02,25,1.8,(150,50,150),3,20
  elif t=="crawler":s.dr,s.sp,s.cs,s.dmg,s.ar,s.col,s.wd=7,0.03,0.10,6,1.0,(100,200,100),8
  elif t=="phantom":s.dr,s.sp,s.cs,s.dmg,s.ar,s.col,s.wd=8,0.02,0.06,14,1.4,(200,200,255),10;s.tele=0
  elif t=="spitter":s.dr,s.sp,s.cs,s.dmg,s.ar,s.col,s.wd=9,0.01,0.02,18,3.0,(100,200,100),20
  elif t=="lurker":s.dr,s.sp,s.cs,s.dmg,s.ar,s.col,s.wd=5,0.015,0.04,10,1.2,(100,100,60),15;s.lurk=1
  elif t=="shambler":s.dr,s.sp,s.cs,s.dmg,s.ar,s.col,s.hp,s.wd=4,0.005,0.01,30,1.8,(80,40,40),2,25
  s.sp*=random.uniform(0.9,1.1);s.cs*=random.uniform(0.9,1.1)
 def upd(s,pp,dg,pl):
  dx=pp[0]-s.x;dy=pp[1]-s.y;d=math.hypot(dx,dy)
  if s.type=="phantom" and s.state=="chase" and d>8:
   s.tele-=1
   if s.tele<=0 and d>3:
    ang=random.uniform(0,6.283);r=random.uniform(2,4)
    nx=pp[0]+math.cos(ang)*r;ny=pp[1]+math.sin(ang)*r;gx,gy=int(round(nx)),int(round(ny))
    if 0<=gx<len(dg[0]) and 0<=gy<len(dg) and dg[gy][gx]==0:s.x,s.y=nx,ny
    s.tele=random.randint(60,180)
  see=s.los(pp,dg)
  if s.type=="smiler" and pl.sanity>60:see=0
  s.state="chase" if d<s.dr and see else "idle"
  if s.isw:
   s.wind-=1
   if s.wind<=0:s.isw=0;s.ac=30;return 1
   return 0
  if s.ac>0:s.ac-=1
  if d<s.ar and s.ac==0 and not s.isw:s.isw=1;s.wind=s.wd;return 0
  if s.state=="idle":
   s.mt-=1
   if s.mt<=0:s.dir=random.choice([(0,1),(0,-1),(1,0),(-1,0)]);s.mt=random.randint(20,80)
   s.tm(s.dir[0],s.dir[1],dg)
  elif s.state=="chase":
   if d>0.5:sx=dx/d*s.cs;sy=dy/d*s.cs;s.tm(sx,0,dg);s.tm(0,sy,dg)
  return 0
 def tm(s,dx,dy,dg):
  nx=s.x+dx;ny=s.y+dy;gx,gy=int(round(nx)),int(round(ny))
  if 0<=gx<len(dg[0]) and 0<=gy<len(dg) and (dg[gy][gx]==0 or dg[gy][gx]==2):s.x,s.y=nx,ny;return 1
  return 0
 def los(s,t,dg):
  x0,y0=int(round(s.x)),int(round(s.y));x1,y1=int(round(t[0])),int(round(t[1]))
  dx,dy=abs(x1-x0),-abs(y1-y0);sx=1 if x0<x1 else -1;sy=1 if y0<y1 else -1;e=dx+dy
  while 1:
   if (x0,y0)==(x1,y1):return 1
   if dg[y0][x0]==1 or dg[y0][x0]==2:return 0
   e2=2*e
   if e2>=dy:e+=dy;x0+=sx
   if e2<=dx:e+=dx;y0+=sy
 def dmg(s):s.hp-=1;return s.hp<=0
class Pl:
 def __init__(s,x,y):
  s.x,s.y,s.gx,s.gy=float(x),float(y),x,y
  s.mov=0;s.pr=0.0;s.frm=(0.0,0.0);s.to=(0.0,0.0)
  s.spd=MOVE_SPEED;s.q=deque();s.hold=0;s.cd=0
  s.sanity=SANITY_MAX;s.hp=100;s.maxhp=100;s.st=STAMINA_MAX;s.maxst=STAMINA_MAX
  s.inv=[];s.lim=8;s.torch=0;s.tt=0
  s.steps=0;s.ac=0;s.hf=0
  s.key=0;s.fpass=0;s.passw="";s.sprint=0
  s.levers=0;s.mk=0;s.ic=0;s.lc=0
  s.lid=0;s.att=0;s.af=0;s.at=0;s.freeze=0;s.sac_asked=0;s.sac_done=0
 def sm(s,dx,dy,dg,sprint=0,lid=0):
  if s.mov or s.cd>0:
   s.q.append((dx,dy));return
  nx,ny=s.gx+dx,s.gy+dy
  if 0<=nx<len(dg[0]) and 0<=ny<len(dg):
   cell=dg[int(ny)][int(nx)]
   if cell==0 or cell==2:
    spd=MOVE_SPEED_SPRINT if (sprint and s.st>5 and lid not in(7,20,26))else MOVE_SPEED
    if lid==7:spd*=0.4
    if lid==19:spd*=0.7
    if lid==20:spd*=1.2
    if lid==6:s.torch=0;s.tt=0
    s.frm=(s.x,s.y);s.to=(float(nx),float(ny));s.mov=1;s.pr=0.0;s.gx,s.gy=int(nx),int(ny);s.spd=spd;s.steps+=1;s.cd=MOVE_COOLDOWN
    if cell==2:s.sanity=max(0,s.sanity-0.02)
    return 1
   elif cell==3:
    return 'locked'
   elif cell==4:
    return 'password'
   elif cell==5:
    return 'lever'
  return 0
 def upd(s,dg,lid=0):
  if s.cd>0:s.cd-=1
  if s.mov:
   s.pr+=s.spd
   if s.pr>=1.0:
    s.pr=1.0;s.x,s.y=s.to;s.mov=0
    if s.q:dx,dy=s.q.popleft();s.sm(dx,dy,dg,s.sprint,lid)
   else:
    t=s.pr;t=t*t*(3-2*t);s.x=s.frm[0]+(s.to[0]-s.frm[0])*t;s.y=s.frm[1]+(s.to[1]-s.frm[1])*t
  if not s.sprint:s.st=min(s.maxst,s.st+STAMINA_REGEN)
  if lid==6:s.sanity=max(0,s.sanity-0.06)
  if lid==7:s.hp=max(0,s.hp-0.005)
  if lid==9 and random.random()<0.001:s.hp=max(0,s.hp-5)
  if lid==14:s.hp=max(0,s.hp-0.02)
  s.sanity=max(0,s.sanity-SANITY_DRAIN*(0.8+0.2*(1-s.sanity/SANITY_MAX)))
  if s.torch and lid!=6:s.tt-=1
  if s.tt<=0:s.torch=0
  if s.ac>0:s.ac-=1
  if s.hf>0:s.hf-=1
  if s.att:
   s.at-=1
   if s.at<=0:s.att=0;s.af=0
   else:s.af=(s.af+1)%5
 def pos(s):return(s.x,s.y)
 def cell(s):return(int(round(s.gx)),int(round(s.gy)))
 def use(s,idx):
  if idx<len(s.inv):
   t=s.inv.pop(idx)
   if t==IT.ALMOND:s.sanity=min(SANITY_MAX,s.sanity+25)
   elif t==IT.TORCH:
    if s.lid!=6:s.torch=1;s.tt=TORCH_DURATION
   elif t==IT.BANDAGE:s.hp=min(s.maxhp,s.hp+20)
   elif t==IT.KEY:s.key=1
   elif t==IT.PASS:s.fpass=1
   elif t==IT.LEVER:s.levers+=1
   elif t==IT.BAT:
    if s.torch and s.lid!=6:s.tt=min(TORCH_DURATION,s.tt+150)
    else:s.torch=1;s.tt=150
   elif t==IT.MED:s.hp=min(s.maxhp,s.hp+40)
   elif t==IT.MAP:pass
   s.ic+=1;return 1
  return 0
 def pick(s,its):
  cx,cy=s.cell()
  for it in its:
   if not it.collected and abs(it.x-cx)<=1 and abs(it.y-cy)<=1:
    if len(s.inv)<s.lim:
     if it.type==IT.KEY:s.key=1;it.collected=1;s.ic+=1;return 1
     if it.type==IT.PASS:s.fpass=1;s.passw=it.extra;it.collected=1;s.ic+=1;return 1
     if it.type==IT.LEVER:s.levers+=1;it.collected=1;s.ic+=1;return 1
     s.inv.append(it.type);it.collected=1;s.ic+=1;return 1
  return 0
class Lev:
 def __init__(s,lid):
  s.id=lid;s.tk=lid%len(GEN);d,rs,fu=GEN[s.tk](MAP_W,MAP_H);s.d=d;s.rs=rs;s.fu=fu
  s.w,s.h=len(d[0]),len(d)
  if rs:
   st=(rs[0].cx,rs[0].cy);ex=(rs[-1].cx,rs[-1].cy)
  else:st=(2,2);ex=(MAP_W-3,MAP_H-3);d[st[1]][st[0]]=d[ex[1]][ex[0]]=0
  s.st=st;s.ex=ex;s.ports=[]
  for i,(dx,dy)in enumerate([(2,0),(-2,0)]):
   px,py=ex[0]+dx,ex[1]+dy
   if 0<=px<s.w and 0<=py<s.h and d[py][px]==0:
    s.ports.append({'pos':(px,py),'type':'safe' if i==0 else 'danger'})
  if len(s.ports)<2:
   for dx,dy in [(0,2),(0,-2)]:
    px,py=ex[0]+dx,ex[1]+dy
    if 0<=px<s.w and 0<=py<s.h and d[py][px]==0:
     s.ports.append({'pos':(px,py),'type':'safe' if len(s.ports)==0 else 'danger'});break
  s.items=[];s.mons=[];s.ld=s.pd=s.lvd=[];s.chase=None;s.setup()
 def setup(s):
  base=[IT.ALMOND,IT.TORCH,IT.BANDAGE]
  if random.random()<0.2:base.append(IT.MED)
  if random.random()<0.15:base.append(IT.MAP)
  if s.id==4:base.extend([IT.ALMOND,IT.ALMOND])
  p=s.id%3
  if p==0:base.append(IT.KEY);s.plk()
  elif p==1:base.append(IT.LEVER);s.plev(1+(s.id%3))
  else:base.append(IT.PASS);s.ppass()
  cnt=max(6,10-s.id%4);s.spawn_items(base,cnt)
  if s.id==0:mc=0
  elif s.id in(1,4,10,20):mc=1
  elif s.id in(6,7,12,14,16):mc=5+s.id%3
  else:mc=3+(s.id%6)+(s.id//5)
  s.spawn_mons(mc)
  if s.id>5 and random.random()<0.2 and s.id!=0:s.place_chase()
 def spawn_items(s,types,cnt):
  for _ in range(cnt):
   x=random.randint(2,s.w-3);y=random.randint(2,s.h-3)
   if s.d[y][x]==0 and (x,y)!=s.st and (x,y)!=s.ex:
    t=random.choice(types);e=""
    if t==IT.PASS:e=str(random.randint(1000,9999))
    elif t==IT.NOTE:e="Clue"
    s.items.append(It(x,y,t,e))
 def spawn_mons(s,cnt):
  if cnt==0:return
  av=MT.copy()
  if s.id<5:av=[t for t in av if t not in["brute","phantom","spitter"]]
  if s.id==8:av=["crawler"]*3+["hunter"]
  for _ in range(cnt):
   for _ in range(100):
    x=random.randint(2,s.w-3);y=random.randint(2,s.h-3)
    if s.d[y][x]==0 and (x,y)!=s.st and (x,y)!=s.ex and dist((x,y),s.st)>6:
     s.mons.append(Mon(x,y,random.choice(av)));break
 def plk(s):
  ex,ey=s.ex
  for dx,dy in [(1,0),(-1,0),(0,1),(0,-1)]:
   nx,ny=ex+dx,ey+dy
   if 0<=nx<s.w and 0<=ny<s.h and s.d[ny][nx]==0:
    s.d[ny][nx]=3;s.ld.append((nx,ny));break
 def plev(s,req):
  ex,ey=s.ex
  for dx,dy in [(1,0),(-1,0),(0,1),(0,-1)]:
   nx,ny=ex+dx,ey+dy
   if 0<=nx<s.w and 0<=ny<s.h and s.d[ny][nx]==0:
    s.d[ny][nx]=5;s.lvd.append((nx,ny,req));break
 def ppass(s):
  ex,ey=s.ex
  for dx,dy in [(1,0),(-1,0),(0,1),(0,-1)]:
   nx,ny=ex+dx,ey+dy
   if 0<=nx<s.w and 0<=ny<s.h and s.d[ny][nx]==0:
    s.d[ny][nx]=4;s.pd.append((nx,ny));break
 def place_chase(s):
  ex,ey=s.ex
  for _ in range(50):
   x=random.randint(ex-5,ex+5);y=random.randint(ey-5,ey+5)
   if 0<=x<s.w and 0<=y<s.h and s.d[y][x]==0 and dist((x,y),s.st)>10:
    s.chase=Mon(x,y,"chaser");break
 def unlock_door(s,x,y,p):
  if (x,y) in s.ld and p.key:
   s.d[y][x]=0;s.ld.remove((x,y));return 1
  return 0
 def unlock_lever(s,x,y,p):
  for door in s.lvd:
   if door[0]==x and door[1]==y:
    if p.levers>=door[2]:
     s.d[y][x]=0;s.lvd.remove(door);return 1
    return 0
  return 0
 def unlock_pass(s,x,y,p,pi):
  if (x,y) in s.pd and p.fpass and p.passw==pi:
   s.d[y][x]=0;s.pd.remove((x,y));return 1
  return 0
 def theme(s):
  idx=s.tk;return C[idx],tuple(c-30 for c in C[idx]),F[idx]
class Story:
 def __init__(s):s.path=[];s.tmk=s.tic=0;s.sh=[]
 def next(s,lv,pl,pt):
  lid=lv.id;s.path.append(lid)
  br={}
  for i in range(59):
   safe=(i+1)%59;danger=(i+3)%59
   if i==0:safe=1;danger=1
   elif i==1:safe=2;danger=2
   elif i==2:safe=3;danger=3
   elif i==3:safe=5;danger=4
   elif i==4:safe=5;danger=6
   elif i==5:safe=6;danger=7
   elif i==6:safe=7;danger=8
   elif i==7:safe=8;danger=9
   elif i==8:safe=9;danger=10
   elif i==9:safe=10;danger=11
   elif i==10:safe=11;danger=12
   elif i==20:safe=21;danger=1
   elif i==21:safe=19;danger=22
   elif i==22:safe=0;danger=20
   elif i==23:safe=24;danger=0
   elif i==24:safe=25;danger=23
   elif i==25:safe=26;danger=24
   elif i==26:safe=27;danger=25
   elif i==27:safe=0;danger=23
   elif i==43:safe=44;danger=45
   elif i==44:safe=46;danger=43
   elif i==45:safe=47;danger=48
   elif i==46:safe=49;danger=50
   elif i==47:safe=51;danger=52
   elif i==48:safe=53;danger=54
   elif i==49:safe=55;danger=56
   elif i==50:safe=57;danger=0
   elif i==51:safe=43;danger=44
   elif i==52:safe=45;danger=46
   elif i==53:safe=47;danger=48
   elif i==54:safe=49;danger=50
   elif i==55:safe=51;danger=52
   elif i==56:safe=53;danger=54
   elif i==57:safe=55;danger=56
   elif i==58:safe=49;danger=43
   br[i]={'safe':safe,'danger':danger}
  nxt=br[lid]['safe'] if pt=='safe' else br[lid]['danger']
  s.tmk+=pl.mk;s.tic+=pl.ic;s.sh.append(pl.sanity)
  if lid==58 and pt=='safe':return None,'final_escape'
  if pl.sanity<20 and len(s.path)>=2:return None,'madness'
  if s.tmk>=30 and len(s.path)>=2:return None,'slayer'
  if s.tic>=40 and len(s.path)>=2:return None,'collector'
  if len(set(s.path))>=20:return None,'wanderer'
  if lid==19 and pt=='safe':return None,'escape'
  if 22 in s.path and len(s.path)>=3:return None,'die_survivor'
  if lid==27 and pl.sac_done:return None,'sacrifice'
  if lid==49:return None,'frontrooms'
  return nxt,None
 def ending(s,t,pl):
  if t=='final_escape':return "You have found the final exit. You are truly free."
  if t=='escape':return "You found the final exit! You are free from the Backrooms."
  if t=='madness':return "Your mind shattered. You become a permanent resident of the dark."
  if t=='slayer':return "You became a ruthless killer, but the horrors never end."
  if t=='collector':return "You gathered countless treasures, but they bring no comfort."
  if t=='wanderer':return "You have seen it all. You are the eternal wanderer."
  if t=='die_survivor':return "You cheated death itself. The Backrooms have finally let you go."
  if t=='sacrifice':return "You made the ultimate sacrifice and found redemption."
  if t=='frontrooms':return "You have returned to the real world. Welcome home."
  return "Your journey ends, but the Backrooms remain."
class Game:
 def __init__(s):
  s.scr=pygame.display.set_mode((W,WINH));pygame.display.set_caption("Backrooms 59 Levels")
  s.clock=pygame.time.Clock();s.running=1;s.state="playing"
  s.story=Story();s.lid=0;s.lv=Lev(s.lid);s.pl=Pl(s.lv.st[0],s.lv.st[1]);s.pl.lid=s.lid
  s.cx=s.cy=0;s.fl=s.wt=s.whisper=0;s.whisper_text="";s.hall=0
  s.inp=s.show_pass=s.pass_target="";s.keys={};s.tt=s.ttxt=0;s.gore="";s.ending=None
  s.vig=s.make_vig();s.noise=s.make_noise();s.frame=0;s.info_timer=0;s.show_info=1
 def make_vig(s):
  surf=pygame.Surface((W,WINH),pygame.SRCALPHA)
  for r in range(0,max(W,WINH)//2,2):
   a=int(80*(r/(max(W,WINH)//2)));pygame.draw.circle(surf,(0,0,0,cl(a,0,80)),(W//2,WINH//2),r)
  return surf
 def make_noise(s):
  surf=pygame.Surface((W,WINH))
  for _ in range(500):
   x=random.randint(0,W-1);y=random.randint(0,WINH-1);c=random.randint(30,80);surf.set_at((x,y),(c,c,c))
  surf.set_alpha(20);return surf
 def reset(s):s.__init__()
 def handle(s):
  for e in pygame.event.get():
   if e.type==pygame.QUIT:s.running=0
   if e.type==pygame.KEYDOWN:
    if s.state!="playing":
     if e.key==pygame.K_r:s.reset()
     if e.key==pygame.K_ESCAPE:s.running=0;continue
    if s.show_pass:
     if e.key==pygame.K_RETURN:
      if s.lv.unlock_pass(s.pass_target[0],s.pass_target[1],s.pl,s.inp):
       s.show_pass=0;s.whisper_text="Password correct!";s.wt=60
      else:s.whisper_text="Wrong password!";s.wt=60;s.inp=""
     elif e.key==pygame.K_BACKSPACE:s.inp=s.inp[:-1]
     elif e.key==pygame.K_ESCAPE:s.show_pass=0
     else:
      if e.unicode.isdigit() and len(s.inp)<8:s.inp+=e.unicode
     continue
    if e.key==pygame.K_e:s.pl.pick(s.lv.items)
    if e.key==pygame.K_SPACE:s.attack()
    if e.key in [pygame.K_1,pygame.K_2,pygame.K_3,pygame.K_4,pygame.K_5,pygame.K_6,pygame.K_7,pygame.K_8]:
     s.pl.use(e.key-pygame.K_1)
    if e.key in (pygame.K_UP,pygame.K_w):s.keys['up']=1
    if e.key in (pygame.K_DOWN,pygame.K_s):s.keys['down']=1
    if e.key in (pygame.K_LEFT,pygame.K_a):s.keys['left']=1
    if e.key in (pygame.K_RIGHT,pygame.K_d):s.keys['right']=1
    if e.key==pygame.K_i:s.show_info=not s.show_info
   if e.type==pygame.KEYUP:
    if e.key in (pygame.K_UP,pygame.K_w):s.keys['up']=0
    if e.key in (pygame.K_DOWN,pygame.K_s):s.keys['down']=0
    if e.key in (pygame.K_LEFT,pygame.K_a):s.keys['left']=0
    if e.key in (pygame.K_RIGHT,pygame.K_d):s.keys['right']=0
    if e.key in (pygame.K_LSHIFT,pygame.K_RSHIFT):s.pl.sprint=0
 def attack(s):
  if s.pl.att:return
  s.pl.att=1;s.pl.af=0;s.pl.at=8
  px,py=s.pl.pos()
  for m in s.lv.mons[:]:
   if dist((m.x,m.y),(px,py))<1.8:
    if m.dmg():
     s.lv.mons.remove(m);s.pl.mk+=1;s.whisper_text="Monster defeated!";s.wt=30
    else:s.whisper_text="Monster hit!";s.wt=20
    return
  if s.lv.chase and dist((s.lv.chase.x,s.lv.chase.y),(px,py))<1.8:
   if s.lv.chase.dmg():
    s.lv.chase=None;s.pl.mk+=1;s.whisper_text="Chaser defeated!";s.wt=30
   else:s.whisper_text="Chaser hit!";s.wt=20
 def move(s):
  if s.state!="playing" or s.show_pass or s.pl.mov:return
  keys=pygame.key.get_pressed();sp=keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]
  if sp and s.pl.st>5:s.pl.sprint=1;s.pl.st=max(0,s.pl.st-SPRINT_COST)
  else:s.pl.sprint=0
  dx=dy=0
  if s.keys.get('up',0):dy=-1
  elif s.keys.get('down',0):dy=1
  elif s.keys.get('left',0):dx=-1
  elif s.keys.get('right',0):dx=1
  if dx==0 and dy==0:s.pl.hold=0;return
  if s.pl.hold==0:
   s.try_move(dx,dy,sp);s.pl.hold=1
  elif s.pl.hold>=HOLD_DELAY:
   if s.pl.cd==0:s.try_move(dx,dy,sp)
   s.pl.hold=HOLD_DELAY
  else:s.pl.hold+=1
 def try_move(s,dx,dy,sp):
  r=s.pl.sm(dx,dy,s.lv.d,sp,s.lid)
  if r=='locked':s.whisper_text="Need a key!";s.wt=60
  elif r=='password':
   nx,ny=s.pl.gx+dx,s.pl.gy+dy;s.show_pass=1;s.pass_target=(nx,ny);s.inp=""
  elif r=='lever':
   nx,ny=s.pl.gx+dx,s.pl.gy+dy
   if s.lv.unlock_lever(nx,ny,s.pl):
    s.whisper_text=f"Lever activated! ({s.pl.levers})";s.wt=60
   else:s.whisper_text="Need more levers!";s.wt=60
 def upd(s):
  s.frame+=1
  if s.state!="playing":return
  s.move();s.pl.upd(s.lv.d,s.lid);pp=s.pl.pos()
  if s.lid==16 and s.frame%(FPS*30)==0:
   nd,_,_=gen_def(MAP_W,MAP_H);s.lv.d=nd;s.lv.w,s.lv.h=MAP_W,MAP_H
   s.lv.st=(2,2);s.lv.ex=(MAP_W-3,MAP_H-3)
   s.lv.d[s.lv.st[1]][s.lv.st[0]]=0;s.lv.d[s.lv.ex[1]][s.lv.ex[0]]=0
   s.lv.mons.clear();s.lv.spawn_mons(5)
  for m in s.lv.mons[:]:
   if m.upd(pp,s.lv.d,s.pl):
    s.pl.hp-=m.dmg;s.pl.hf=12
    if s.pl.hp<=0:s.state="gameover";s.gore="monster"
  if s.lv.chase and s.lv.chase.upd(pp,s.lv.d,s.pl):
   s.pl.hp-=s.lv.chase.dmg;s.pl.hf=12
   if s.pl.hp<=0:s.state="gameover";s.gore="chaser"
  cx,cy=s.pl.cell()
  for p in s.lv.ports:
   if (cx,cy)==p['pos']:
    nxt,ending=s.story.next(s.lv,s.pl,p['type'])
    if ending:
     s.ending=ending;s.state="win";return
    elif nxt is not None:
     s.lid=nxt;s.lv=Lev(s.lid);s.pl=Pl(s.lv.st[0],s.lv.st[1]);s.pl.lid=s.lid
     s.pl.levers=0;s.whisper_text=f"Entering Level {s.lid}";s.wt=90;s.ttxt=f"Level {s.lid}";s.tt=60
     s.show_info=1;s.info_timer=180
     if s.lid==27:s.pl.sac_asked=1
     return
  for m in s.lv.mons:
   if dist((m.x,m.y),(s.pl.x,s.pl.y))<5:s.pl.sanity=max(0,s.pl.sanity-0.06)
  if s.lv.chase and dist((s.lv.chase.x,s.lv.chase.y),(s.pl.x,s.pl.y))<8:s.pl.sanity=max(0,s.pl.sanity-0.08)
  if s.pl.sanity<=0:s.state="gameover";s.gore="sanity"
  if random.random()<0.003:s.fl=random.randint(15,40)
  if s.fl>0:s.fl-=1
  if s.pl.sanity<30 and random.random()<0.01:s.hall=1
  else:s.hall=0
  if s.wt>0:s.wt-=1
  else:s.whisper_text=""
  if s.tt>0:s.tt-=1
  if s.info_timer>0:s.info_timer-=1
  else:s.show_info=0
  px,py=s.pl.pos();s.cx=px*32-W//2;s.cy=py*32-WINH//2
 def render(s):
  s.scr.fill((60,60,70))
  ox,oy=-s.cx,-s.cy
  vr=1 if s.lid==6 else 12+(5 if (s.pl.torch and s.lid!=6)else 0)
  px,py=s.pl.pos();tk=s.lv.tk
  wt,ft=gt(tk,'wall'),gt(tk,'floor')
  for y in range(max(0,int(py-vr-1)),min(s.lv.h,int(py+vr+2))):
   for x in range(max(0,int(px-vr-1)),min(s.lv.w,int(px+vr+2))):
    d=math.hypot(x-px,y-py)
    if d>vr+1:continue
    rect=pygame.Rect(ox+x*32,oy+y*32,32,32)
    cell=s.lv.d[y][x]
    if cell in(0,2,3,4,5):s.scr.blit(ft,rect)
    else:s.scr.blit(wt,rect)
  for y in range(max(0,int(py-vr-1)),min(s.lv.h,int(py+vr+2))):
   for x in range(max(0,int(px-vr-1)),min(s.lv.w,int(px+vr+2))):
    d=math.hypot(x-px,y-py)
    if d>vr+1:continue
    rect=pygame.Rect(ox+x*32,oy+y*32,32,32)
    cell=s.lv.d[y][x]
    if cell==2:
     if s.lid==5:cols=[(200,180,100),(180,130,80),(220,200,150)];pygame.draw.rect(s.scr,random.choice(cols),rect,3)
     elif s.lid==10:pygame.draw.rect(s.scr,(200,220,50),rect,2)
     else:pygame.draw.rect(s.scr,(50,100,120),rect,2)
    elif cell==3:
     pygame.draw.rect(s.scr,(80,60,40),rect);pygame.draw.rect(s.scr,(200,180,100),rect,3)
     s.scr.blit(fs.render("🔒",1,(200,200,100)),(rect.x+6,rect.y+4))
    elif cell==4:
     pygame.draw.rect(s.scr,(70,70,90),rect);pygame.draw.rect(s.scr,(150,150,200),rect,3)
     s.scr.blit(fs.render("🔐",1,(200,200,150)),(rect.x+6,rect.y+4))
    elif cell==5:
     pygame.draw.rect(s.scr,(90,90,130),rect);pygame.draw.rect(s.scr,(200,200,255),rect,3)
     s.scr.blit(fs.render("🎛️",1,(200,200,255)),(rect.x+6,rect.y+4))
  for it in s.lv.items:
   if it.collected:continue
   if dist((it.x,it.y),(px,py))<=vr+1:
    rect=pygame.Rect(ox+it.x*32,oy+it.y*32,32,32)
    bc=It.col(it.type);gc=(min(255,bc[0]+80),min(255,bc[1]+80),min(255,bc[2]+80))
    g=pygame.Surface((32,32),pygame.SRCALPHA);pygame.draw.circle(g,(*gc,150),(16,16),16);s.scr.blit(g,rect)
    s.scr.blit(fs.render(It.em(it.type),1,(255,255,255)),(rect.x+6,rect.y+4))
  for m in s.lv.mons:
   if dist((m.x,m.y),(px,py))<=vr+1:
    rect=pygame.Rect(ox+m.x*32-16,oy+m.y*32-16,32,32)
    if m.isw:
     col=(255,50,50)if(s.frame//4)%2==0 else(255,255,255);r=20
    else:col=m.col;r=14
    if m.type=="smiler":
     if s.pl.sanity<60:
      pygame.gfxdraw.filled_circle(s.scr,rect.centerx,rect.centery,r,col if m.isw else(255,200,200))
      pygame.gfxdraw.aacircle(s.scr,rect.centerx,rect.centery,r,col if m.isw else(255,200,200))
      for i in range(-3,4):
       tx=rect.centerx+i*4;ty=rect.centery+4
       pygame.draw.rect(s.scr,(255,255,255),(tx-2,ty-2,4,4))
     else:pygame.gfxdraw.aacircle(s.scr,rect.centerx,rect.centery,r,(100,100,100))
    else:
     pygame.gfxdraw.filled_circle(s.scr,rect.centerx,rect.centery,r,col)
     pygame.gfxdraw.aacircle(s.scr,rect.centerx,rect.centery,r,col)
     dx,dy=px-m.x,py-m.y;ang=math.atan2(dy,dx)
     for side in(-1,1):
      ex=rect.centerx+math.cos(ang+side*0.6)*6;ey=rect.centery+math.sin(ang+side*0.6)*6
      pygame.gfxdraw.filled_circle(s.scr,int(ex),int(ey),3,(255,255,200))
      pygame.gfxdraw.aacircle(s.scr,int(ex),int(ey),3,(255,255,200))
     if m.hp>1:
      bw,bh=32,4;bx,by=rect.x,rect.y-8
      pygame.draw.rect(s.scr,(60,60,60),(bx,by,bw,bh))
      pygame.draw.rect(s.scr,(255,0,0),(bx,by,(bw*m.hp)//max(1,m.hp),bh))
  if s.lv.chase:
   m=s.lv.chase
   if dist((m.x,m.y),(px,py))<=vr+2:
    rect=pygame.Rect(ox+m.x*32-32,oy+m.y*32-32,64,64)
    g=pygame.Surface((64,64),pygame.SRCALPHA);pygame.draw.circle(g,(255,0,0,80),(32,32),32);s.scr.blit(g,rect)
    crect=pygame.Rect(ox+m.x*32-16,oy+m.y*32-16,32,32)
    pygame.gfxdraw.filled_circle(s.scr,crect.centerx,crect.centery,16,(200,0,0))
    pygame.gfxdraw.aacircle(s.scr,crect.centerx,crect.centery,16,(200,0,0))
    dx,dy=px-m.x,py-m.y;ang=math.atan2(dy,dx)
    for side in(-1,1):
     ex=crect.centerx+math.cos(ang+side*0.6)*8;ey=crect.centery+math.sin(ang+side*0.6)*8
     pygame.gfxdraw.filled_circle(s.scr,int(ex),int(ey),4,(255,50,50))
     pygame.gfxdraw.aacircle(s.scr,int(ex),int(ey),4,(255,50,50))
     pygame.gfxdraw.filled_circle(s.scr,int(ex),int(ey),2,(255,255,200))
  for p in s.lv.ports:
   ex,ey=p['pos']
   if dist((ex,ey),(px,py))<=vr+2:
    rect=pygame.Rect(ox+ex*32,oy+ey*32,32,32)
    safe=p['type']=='safe'
    col=EX if safe else ED;gc=EG if safe else DG
    g=pygame.Surface((64,64),pygame.SRCALPHA);pygame.draw.circle(g,(*gc,120),(32,32),32);s.scr.blit(g,(rect.x-16,rect.y-16))
    pygame.draw.rect(s.scr,col,rect);pygame.draw.rect(s.scr,(200,255,200)if safe else(255,200,200),rect.inflate(-4,-4),2)
    txt="SAFE"if safe else"DANGER"
    s.scr.blit(fs.render(txt,1,(0,80,0)if safe else(80,0,0)),(rect.x+4,rect.y+6))
  ppos=s.pl.pos();prect=pygame.Rect(ox+ppos[0]*32-16,oy+ppos[1]*32-16,32,32)
  g=pygame.Surface((64,64),pygame.SRCALPHA);pygame.draw.circle(g,(*PG,80),(32,32),32);s.scr.blit(g,(prect.x-16,prect.y-16))
  col=(255,0,0)if s.pl.hf>0 else PL
  pygame.gfxdraw.filled_circle(s.scr,prect.centerx,prect.centery,14,col)
  pygame.gfxdraw.aacircle(s.scr,prect.centerx,prect.centery,14,col)
  ex,ey=prect.centerx-4,prect.centery-3
  pygame.gfxdraw.filled_circle(s.scr,ex-2,ey,3,(255,255,255))
  pygame.gfxdraw.filled_circle(s.scr,ex+6,ey,3,(255,255,255))
  pygame.gfxdraw.filled_circle(s.scr,ex-1,ey+1,1,(0,0,0))
  pygame.gfxdraw.filled_circle(s.scr,ex+7,ey+1,1,(0,0,0))
  if s.pl.att:
   cx,cy=prect.centerx,prect.centery;r=24
   sa=-1.57+s.pl.af*0.2;ea=1.57+s.pl.af*0.2
   col=(255,255,200-s.pl.af*30)
   pygame.draw.arc(s.scr,col,(cx-r,cy-r,r*2,r*2),sa,ea,4)
   for i in range(3):
    ang=sa+(ea-sa)*(i+1)/4
    ex=cx+int(r*math.cos(ang));ey=cy+int(r*math.sin(ang))
    pygame.draw.line(s.scr,(255,255,150),(cx,cy),(ex,ey),2)
  fog=pygame.Surface((W,WINH),pygame.SRCALPHA);center=(W//2,WINH//2)
  rp=vr*32
  for r in range(rp,0,-5):
   a=int(120*(1-r/rp))if r<rp else 0
   if a>0:pygame.draw.circle(fog,(0,0,0,min(a,120)),center,r)
  pygame.draw.circle(fog,(0,0,0,160),center,rp+1,1)
  s.scr.blit(fog,(0,0))
  s.scr.blit(s.vig,(0,0))
  if s.pl.sanity<50:s.scr.blit(s.noise,(0,0))
  if s.lid==6:
   ov=pygame.Surface((W,WINH));ov.fill((0,0,0));ov.set_alpha(200);s.scr.blit(ov,(0,0))
   txt=fm.render("ABSOLUTE DARKNESS - All light fails",1,(200,50,50))
   s.scr.blit(txt,(W//2-txt.get_width()//2,100))
  if s.lid==1 and (s.frame//20)%2==0:
   ov=pygame.Surface((W,WINH));ov.fill((200,200,200));ov.set_alpha(30);s.scr.blit(ov,(0,0))
  if s.fl>0:
   ov=pygame.Surface((W,WINH));ov.fill((0,0,0));ov.set_alpha(random.randint(30,150));s.scr.blit(ov,(0,0))
  if s.hall:s.scr.blit(s.scr,(random.randint(-4,4),random.randint(-4,4)))
  s.ui()
  if s.show_info and s.lid in INFO:
   inf=INFO[s.lid];surf=pygame.Surface((W,150),pygame.SRCALPHA);surf.fill((10,10,20,220));s.scr.blit(surf,(0,0))
   tit=fl.render(inf[0],1,(255,255,200));s.scr.blit(tit,(20,10))
   al=fm.render(f"Alias: {inf[1]}  |  Difficulty: {inf[2]}",1,(200,200,200));s.scr.blit(al,(20,60))
   desc=inf[3].split('. ')
   for i,line in enumerate(desc[:2]):s.scr.blit(fs.render(line+('.'if i==0 else''),1,(180,180,200)),(20,90+i*25))
   s.scr.blit(fs.render("Press I to toggle info",1,(150,150,150)),(W-200,10))
  if s.show_pass:s.pass_ui()
  if s.tt>0:
   ov=pygame.Surface((W,WINH));ov.fill((0,0,0));ov.set_alpha(min(255,s.tt*4));s.scr.blit(ov,(0,0))
   txt=ft.render(s.ttxt,1,(255,255,255));s.scr.blit(txt,(W//2-txt.get_width()//2,WINH//2))
  if s.lid==27 and s.pl.sac_asked and not s.pl.sac_done:
   p=fm.render("The Smiler asks: Why are you here? (Press Y to sacrifice, N to lie)",1,(255,200,200))
   s.scr.blit(p,(W//2-p.get_width()//2,200))
  if s.state=="win":s.win_ui()
  elif s.state=="gameover":s.gameover_ui()
  pygame.display.flip()
 def ui(s):
  surf=pygame.Surface((W,110),pygame.SRCALPHA);surf.fill((10,10,15,200));s.scr.blit(surf,(0,WINH-110))
  bx,by=20,WINH-95;bw,bh=120,16
  r=s.pl.sanity/SANITY_MAX
  pygame.draw.rect(s.scr,(60,60,60),(bx,by,bw,bh),border_radius=4)
  col=(80,200,80)if r>0.3 else(230,80,80)
  pygame.draw.rect(s.scr,col,(bx+2,by+2,int((bw-4)*r),bh-4),border_radius=3)
  s.scr.blit(fs.render(f"Sanity {int(s.pl.sanity)}%",1,(255,255,255)),(bx+6,by+2))
  hx=bx+bw+10;hw=100
  pygame.draw.rect(s.scr,(60,60,60),(hx,by,hw,bh),border_radius=4)
  pygame.draw.rect(s.scr,(200,50,50),(hx+2,by+2,int((hw-4)*(s.pl.hp/s.pl.maxhp)),bh-4),border_radius=3)
  s.scr.blit(fs.render(f"HP {int(s.pl.hp)}%",1,(255,255,255)),(hx+6,by+2))
  sx=hx+hw+10;sw=100
  pygame.draw.rect(s.scr,(60,60,60),(sx,by,sw,bh),border_radius=4)
  pygame.draw.rect(s.scr,(100,180,255),(sx+2,by+2,int((sw-4)*(s.pl.st/s.pl.maxst)),bh-4),border_radius=3)
  s.scr.blit(fs.render(f"Stamina {int(s.pl.st)}%",1,(255,255,255)),(sx+6,by+2))
  ix=sx+sw+10
  s.scr.blit(fs.render(f"Lv {s.lid}",1,(230,220,200)),(ix,by))
  s.scr.blit(fs.render(f"Steps: {s.pl.steps}",1,(230,220,200)),(ix,by+20))
  if s.pl.levers>0:s.scr.blit(fs.render(f"Levers: {s.pl.levers}",1,(200,200,255)),(ix,by+40))
  if s.lid in INFO:s.scr.blit(fs.render(INFO[s.lid][1],1,(200,180,160)),(ix,by+60))
  invx,invy=20,WINH-65
  s.scr.blit(fs.render("Items [1-8]:",1,(230,220,200)),(invx,invy));invx+=120
  for i,t in enumerate(s.pl.inv):
   col=It.col(t);rect=pygame.Rect(invx+i*36,invy,32,28)
   pygame.draw.rect(s.scr,col,rect,border_radius=4);pygame.draw.rect(s.scr,(200,200,200),rect,1,border_radius=4)
   s.scr.blit(fs.render(It.em(t),1,(0,0,0)),(rect.x+6,rect.y+4))
   s.scr.blit(fs.render(str(i+1),1,(180,180,180)),(rect.x+22,rect.y+16))
  if s.pl.torch and s.lid!=6:s.scr.blit(fs.render(f"🔦 {s.pl.tt//60}s",1,(255,220,100)),(W-180,WINH-95))
  if s.pl.key:s.scr.blit(fs.render("🔑 Has Key",1,(200,200,100)),(W-180,WINH-75))
  if s.pl.sprint:s.scr.blit(fs.render("💨 Sprinting",1,(100,200,255)),(W-180,WINH-55))
  if s.wt>0:
   txt=fm.render(s.whisper_text,1,(230,80,80))
   s.scr.blit(txt,(W//2-txt.get_width()//2,80))
  tips="WASD move | Shift sprint | Space attack | E pickup | I info | R reset"
  s.scr.blit(fs.render(tips,1,(160,150,140)),(W-500,WINH-30))
 def pass_ui(s):
  ov=pygame.Surface((W,WINH),pygame.SRCALPHA);ov.fill((0,0,0,180));s.scr.blit(ov,(0,0))
  rect=pygame.Rect(W//2-180,WINH//2-70,360,100)
  pygame.draw.rect(s.scr,(50,50,60),rect,border_radius=8);pygame.draw.rect(s.scr,(200,200,200),rect,2,border_radius=8)
  s.scr.blit(fm.render("Enter Password (4-8 digits):",1,(255,255,255)),(rect.x+20,rect.y+15))
  s.scr.blit(fm.render("*"*len(s.inp),1,(100,200,255)),(rect.x+20,rect.y+55))
  s.scr.blit(fs.render("Enter to confirm  ESC to cancel",1,(180,180,180)),(rect.x+20,rect.y+80))
 def win_ui(s):
  ov=pygame.Surface((W,WINH));ov.fill((0,0,0));ov.set_alpha(180);s.scr.blit(ov,(0,0))
  txt=s.story.ending(s.ending,s.pl);lines=txt.split('. ');y=WINH//2-60
  for line in lines:
   if line:t=fm.render(line+'.',1,(200,255,200));s.scr.blit(t,(W//2-t.get_width()//2,y));y+=40
  tit=ft.render("🏁 Journey Ends",1,(100,255,150));s.scr.blit(tit,(W//2-tit.get_width()//2,y+20))
  s.scr.blit(fs.render("Press R to restart  ESC to quit",1,(180,180,180)),(W//2-200,y+80))
 def gameover_ui(s):
  ov=pygame.Surface((W,WINH));ov.fill((0,0,0));ov.set_alpha(200);s.scr.blit(ov,(0,0))
  t=ft.render("💀 You are lost...",1,(255,80,80));s.scr.blit(t,(W//2-t.get_width()//2,WINH//2-60))
  if s.gore=="sacrifice":r="You sacrificed yourself"
  elif s.gore=="lied":r="You lied to the Smiler"
  elif s.gore=="time_frozen":r="Time stopped"
  else:r="Sanity lost"if s.gore=="sanity"else"Health depleted"
  sub=fm.render(f"Reason: {r}",1,(200,200,200));s.scr.blit(sub,(W//2-sub.get_width()//2,WINH//2))
  s.scr.blit(fs.render("Press R to restart  ESC to quit",1,(180,180,180)),(W//2-200,WINH//2+50))
 def run(s):
  while s.running:
   s.handle();s.upd();s.render();s.clock.tick(FPS)
  pygame.quit();sys.exit()
MAP_W,MAP_H=60,60
CELL_SIZE=32
BASE_VIEW_RADIUS=12
TORCH_RADIUS=5
TORCH_DURATION=300
SANITY_MAX=100
SANITY_DRAIN=0.025
MOVE_SPEED=0.10
MOVE_SPEED_SPRINT=0.22
SPRINT_COST=0.5
STAMINA_MAX=100
STAMINA_REGEN=0.05
HOLD_DELAY=10
MOVE_COOLDOWN=3
if __name__=="__main__":Game().run()