import pygame
import math

pygame.init()
W, H = 800, 600
screen = pygame.display.set_mode((W, H))
pygame.display.set_caption("Pygame3D Escape Teacher‑Patrol")
clock = pygame.time.Clock()

# maze 1=wall 0=path 2=cabinet hide
maze = [
    [1,1,1,1,1,1,1,1,1,1,1,1],
    [1,0,0,0,1,0,0,0,0,0,0,1],
    [1,0,1,0,1,0,1,1,1,1,0,1],
    [1,0,1,0,0,2,0,0,0,1,0,1],
    [1,0,1,1,1,1,1,1,0,1,0,1],
    [1,2,0,0,0,0,0,0,0,0,0,1],
    [1,1,1,1,1,1,1,1,1,1,0,1],
    [1,0,0,0,0,0,0,0,0,0,0,1],
    [1,1,1,1,1,1,1,1,1,1,1,1]
]
MAP_W = len(maze[0])
MAP_H = len(maze)

# Player
px, py = 1.5, 1.5
p_angle = 0
speed = 0.04
rot_speed = 0.03

# Teacher patrol points 巡逻点位
patrol_points = [(9.5,7.5), (2.5,7.5), (2.5,1.5), (8.5,1.5)]
patrol_index = 0
is_chasing = False   # 是否在追逐玩家
tx, ty = patrol_points[0][0], patrol_points[0][1]
t_speed_patrol = 0.008
t_speed_chase = 0.012
caught = False
escaped = False
hiding = False

FOV = math.pi / 3
HALF_FOV = FOV / 2
NUM_RAYS = W//2
MAX_DEPTH = 20
SCREEN_DIST = (W//2)/math.tan(HALF_FOV)
DELTA_ANGLE = FOV / NUM_RAYS
SCALE = W//NUM_RAYS
wall_depth_buffer = [0]*NUM_RAYS

def ray_cast():
    for ray in range(NUM_RAYS):
        ang = p_angle - HALF_FOV + ray * DELTA_ANGLE
        sin_a = math.sin(ang)
        cos_a = math.cos(ang)
        depth = 0
        hit_wall = False
        while not hit_wall and depth < MAX_DEPTH:
            depth += 0.02
            x = px + cos_a * depth
            y = py + sin_a * depth
            ix = int(x)
            iy = int(y)
            if 0<=ix<MAP_W and 0<=iy<MAP_H:
                if maze[iy][ix]==1:
                    hit_wall=True
            else:
                hit_wall=True
        depth *= math.cos(p_angle - ang)
        wall_depth_buffer[ray] = depth
        h = SCREEN_DIST/(depth+0.0001)
        shade = 255 - min(255,int(depth*40))
        color = (shade,shade,shade)
        pygame.draw.rect(screen,color,(ray*SCALE, H//2 - h//2, SCALE, h))

def draw_teacher_sprite():
    dx = tx - px
    dy = ty - py
    dist = math.hypot(dx, dy)
    if dist < 0.01:
        return
    sprite_angle = math.atan2(dy, dx)
    delta_angle = sprite_angle - p_angle
    while delta_angle > math.pi: delta_angle -= 2*math.pi
    while delta_angle < -math.pi: delta_angle += 2*math.pi
    if abs(delta_angle) > HALF_FOV +0.2:
        return
    dist_corrected = dist * math.cos(delta_angle)
    sprite_h = SCREEN_DIST/(dist_corrected+0.0001)
    sprite_w = sprite_h *0.6
    screen_x = W/2 + (delta_angle/HALF_FOV)*(W/2)
    left = int(screen_x - sprite_w/2)
    top = int(H//2 - sprite_h//2)
    ray_idx = int((screen_x/W)*NUM_RAYS)
    if 0<=ray_idx<NUM_RAYS:
        if wall_depth_buffer[ray_idx] < dist_corrected:
            return
    # 追逐红色，巡逻黄色
    if is_chasing:
        body_color = (180,20,20)
    else:
        body_color = (190,160,0)
    pygame.draw.rect(screen,body_color,(left,top,int(sprite_w),int(sprite_h)))
    eye_size = int(sprite_w*0.15)
    pygame.draw.circle(screen,(255,255,255),(int(left+sprite_w*0.35),int(top+sprite_h*0.25)),eye_size)
    pygame.draw.circle(screen,(255,255,255),(int(left+sprite_w*0.65),int(top+sprite_h*0.25)),eye_size)

def line_of_sight(x1,y1,x2,y2):
    """两点之间有没有墙，True=视线畅通"""
    steps = 30
    for i in range(steps):
        t = i/steps
        x = x1 + (x2-x1)*t
        y = y1 + (y2-y1)*t
        ix = int(x)
        iy = int(y)
        if 0<=ix<MAP_W and 0<=iy<MAP_H:
            if maze[iy][ix]==1:
                return False
    return True

def teacher_can_move(x,y):
    ix=int(x)
    iy=int(y)
    if 0<=ix<MAP_W and 0<=iy<MAP_H:
        return maze[iy][ix] != 1
    return False

def update_teacher():
    global tx,ty,caught,is_chasing,patrol_index
    dx_pl = px - tx
    dy_pl = py - ty
    dist_player = math.hypot(dx_pl, dy_pl)
    can_see_player = line_of_sight(tx,ty,px,py) and not hiding

    if can_see_player and dist_player <7.0:
        is_chasing = True
    # 看不见玩家，退出追逐
    if is_chasing and (not can_see_player or dist_player>9.0):
        is_chasing = False

    if is_chasing:
        speed = t_speed_chase
        if dist_player>0.4:
            nx = tx + (dx_pl/dist_player)*speed
            ny = ty + (dy_pl/dist_player)*speed
            if teacher_can_move(nx,ty): tx=nx
            if teacher_can_move(tx,ny): ty=ny
        if dist_player <0.35:
            caught = True
    else:
        # 巡逻模式，走向下一个巡逻点
        tar_x, tar_y = patrol_points[patrol_index]
        dx_t = tar_x - tx
        dy_t = tar_y - ty
        d = math.hypot(dx_t, dy_t)
        if d>0.2:
            nx = tx + (dx_t/d)*t_speed_patrol
            ny = ty + (dy_t/d)*t_speed_patrol
            if teacher_can_move(nx,ty): tx=nx
            if teacher_can_move(tx,ny): ty=ny
        else:
            # 到达点位，切换下一个巡逻点
            patrol_index = (patrol_index+1) % len(patrol_points)

def can_move(x,y):
    ix=int(x)
    iy=int(y)
    if 0<=ix<MAP_W and 0<=iy<MAP_H:
        return maze[iy][ix]!=1
    return False

def check_hide_state():
    global hiding
    ix = int(px)
    iy = int(py)
    if 0<=ix<MAP_W and 0<=iy<MAP_H:
        hiding = (maze[iy][ix]==2)
    else:
        hiding = False

def draw_minimap():
    for y,row in enumerate(maze):
        for x,val in enumerate(row):
            if val ==1:
                c=(80,80,80)
            elif val ==2:
                c=(120,100,80)
            else:
                c=(220,220,220)
            pygame.draw.rect(screen,c,(x*20,y*20,18,18))
    pygame.draw.circle(screen,(0,0,255),(int(px*20),int(py*20)),4)
    if is_chasing:
        tc = (255,0,0)
    else:
        tc = (220,180,0)
    pygame.draw.circle(screen,tc,(int(tx*20),int(ty*20)),4)

run=True
font = pygame.font.Font(None,28)

while run:
    screen.fill((40,40,40))
    pygame.draw.rect(screen,(70,70,70),(0,0,W,H//2))
    pygame.draw.rect(screen,(110,110,110),(0,H//2,W,H//2))

    for e in pygame.event.get():
        if e.type == pygame.QUIT:
            run=False
    keys = pygame.key.get_pressed()

    if not caught and not escaped:
        if keys[pygame.K_LEFT]:
            p_angle -= rot_speed
        if keys[pygame.K_RIGHT]:
            p_angle += rot_speed

        if keys[pygame.K_w]:
            nx = px + math.cos(p_angle)*speed
            ny = py + math.sin(p_angle)*speed
            if can_move(nx,py): px=nx
            if can_move(px,ny): py=ny
        if keys[pygame.K_s]:
            nx = px - math.cos(p_angle)*speed
            ny = py - math.sin(p_angle)*speed
            if can_move(nx,py): px=nx
            if can_move(px,ny): py=ny
        if keys[pygame.K_a]:
            nx = px + math.cos(p_angle-math.pi/2)*speed
            ny = py + math.sin(p_angle-math.pi/2)*speed
            if can_move(nx,py): px=nx
            if can_move(px,ny): py=ny
        if keys[pygame.K_d]:
            nx = px + math.cos(p_angle+math.pi/2)*speed
            ny = py + math.sin(p_angle+math.pi/2)*speed
            if can_move(nx,py): px=nx
            if can_move(px,ny): py=ny

        check_hide_state()
        if int(px)==10 and int(py)==7:
            escaped=True
        update_teacher()
        ray_cast()
        draw_teacher_sprite()

    draw_minimap()

    if caught:
        txt = font.render("Caught! Press R to restart",True,(255,30,30))
    elif escaped:
        txt = font.render("Escaped! Press R to restart",True,(30,255,30))
    elif hiding:
        txt = font.render("Cabinet Hide! Teacher can't see you",True,(80,200,255))
    elif is_chasing:
        txt = font.render("Teacher spotted you! Run away!",True,(255,60,60))
    else:
        txt = font.render("WASD move, LEFT/RIGHT rotate. Teacher is patrolling",True,(255,255,255))
    screen.blit(txt,(10,H-35))

    if keys[pygame.K_r]:
        px,py =1.5,1.5
        tx, ty = patrol_points[0][0], patrol_points[0][1]
        patrol_index =0
        is_chasing = False
        caught=False
        escaped=False
        hiding=False

    pygame.display.flip()
    clock.tick(60)
pygame.quit()