"""
Demonstration Vibe-Coding:
Klicke wiederholt irgendwo auf die schwarze Fläche.
"""

import heapq
import random
import turtle

# Größe eines Schrittes im unsichtbaren Raster
STEP = 20

screen = turtle.Screen()
screen.setup(700, 500)
screen.bgcolor("#10141f")
screen.title("Zickzack-Labyrinth")

runner = turtle.Turtle()
runner.shape("turtle")
runner.color("#65ffb8")
runner.pensize(3)
runner.speed(0)
runner.penup()
runner.hideturtle()

# Bereits betretene Rasterpunkte
visited = {(0, 0)}

# Aktuelle Position im Raster
current = (0, 0)

# Animationszustand
route = []
moving = False
pending_target = None


def neighbors(point):
    """Alle waagrechten und senkrechten Nachbarpunkte."""
    x, y = point

    directions = [
        (x + 1, y),
        (x - 1, y),
        (x, y + 1),
        (x, y - 1),
    ]

    random.shuffle(directions)
    return directions


def heuristic(a, b):
    """Manhattan-Distanz für ein rechtwinkliges Raster."""
    return abs(a[0] - b[0]) + abs(a[1] - b[1])


def find_path(start, goal, blocked, margin=25):
    """
    Sucht mit A* einen Weg zum Ziel.

    Bereits besuchte Punkte werden nicht betreten.
    Die Suche bleibt in einem ausreichend großen Bereich.
    """
    min_x = min(start[0], goal[0]) - margin
    max_x = max(start[0], goal[0]) + margin
    min_y = min(start[1], goal[1]) - margin
    max_y = max(start[1], goal[1]) + margin

    queue = [(0, random.random(), start)]
    came_from = {}
    cost = {start: 0}

    while queue:
        _, _, point = heapq.heappop(queue)

        if point == goal:
            path = [goal]

            while path[-1] != start:
                path.append(came_from[path[-1]])

            path.reverse()
            return path

        for next_point in neighbors(point):
            x, y = next_point

            if not (min_x <= x <= max_x and min_y <= y <= max_y):
                continue

            if next_point in blocked and next_point != goal:
                continue

            new_cost = cost[point] + 1

            if next_point not in cost or new_cost < cost[next_point]:
                cost[next_point] = new_cost
                came_from[next_point] = point

                priority = new_cost + heuristic(next_point, goal)

                # Zufallswert sorgt für abwechslungsreiche Wege
                heapq.heappush(
                    queue,
                    (priority, random.random(), next_point),
                )

    return None


def possible_waypoints(start, goal):
    """
    Erzeugt Punkte, die einen kleinen Umweg verursachen können.
    """
    sx, sy = start
    gx, gy = goal

    middle_x = (sx + gx) // 2
    middle_y = (sy + gy) // 2

    candidates = []

    for distance in range(2, 7):
        candidates.extend([
            (middle_x + distance, middle_y),
            (middle_x - distance, middle_y),
            (middle_x, middle_y + distance),
            (middle_x, middle_y - distance),
        ])

    random.shuffle(candidates)
    return candidates


def create_route(start, goal):
    """
    Plant zunächst einen kleinen Umweg.

    Falls kein geeigneter Umweg möglich ist, wird ein direkter
    labyrinthartiger Weg gesucht.
    """
    for waypoint in possible_waypoints(start, goal):
        if waypoint in visited or waypoint == goal:
            continue

        first_part = find_path(start, waypoint, visited)

        if not first_part:
            continue

        blocked = visited | set(first_part[:-1])
        second_part = find_path(waypoint, goal, blocked)

        if second_part:
            complete_path = first_part + second_part[1:]

            direct_distance = heuristic(start, goal)

            # Nur kurze Umwege akzeptieren
            if len(complete_path) - 1 <= direct_distance + 12:
                return complete_path

    return find_path(start, goal, visited, margin=35)


def grid_to_screen(point):
    """Wandelt einen Rasterpunkt in Turtle-Koordinaten um."""
    return point[0] * STEP, point[1] * STEP


def screen_to_grid(x, y):
    """Rundet einen Mausklick auf den nächsten Rasterpunkt."""
    return round(x / STEP), round(y / STEP)


def animate():
    """Bewegt die Turtle Schritt für Schritt entlang der Route."""
    global current, moving

    if not route:
        moving = False
        start_pending_target()
        return

    next_point = route.pop(0)

    runner.goto(grid_to_screen(next_point))

    current = next_point
    visited.add(next_point)

    screen.ontimer(animate, 75)


def start_route(target):
    """Plant und startet die Bewegung zum angeklickten Ziel."""
    global route, moving

    if target == current:
        return

    if target in visited:
        print("Dieser Punkt wurde bereits besucht.")
        return

    planned_route = create_route(current, target)

    if not planned_route:
        print("Zu diesem Ziel wurde kein freier Weg gefunden.")
        return

    route = planned_route[1:]
    moving = True

    if not runner.isvisible():
        runner.goto(grid_to_screen(current))
        runner.showturtle()
        runner.pendown()

    animate()


def start_pending_target():
    """Startet den letzten Klick, der während der Bewegung erfolgte."""
    global pending_target

    if pending_target is not None:
        target = pending_target
        pending_target = None
        start_route(target)


def clicked(x, y):
    """Reagiert auf einen Mausklick."""
    global pending_target

    target = screen_to_grid(x, y)

    if moving:
        # Während der Bewegung wird der neueste Klick gespeichert.
        pending_target = target
    else:
        start_route(target)

print("Klicke wiederholt irgendwo auf die schwarze Fläche.")
screen.onclick(clicked)
screen.mainloop()
