{"spec_id":"maze-circular","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nmaze-circular: Circular Maze Puzzle\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 84/100 | Updated: 2026-05-20\n\"\"\"\n\nimport collections\nimport os\n\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nBRAND = \"#009E73\"  # Okabe-Ito pos 1 — entry\nGOAL_COLOR = \"#AE3030\"  # Okabe-Ito pos 5 — goal star\nPATH_COLOR = \"#2ABCCD\"  # Okabe-Ito pos 6 — solution path\n\nsns.set_theme(\n    style=\"white\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"text.color\": INK,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Maze parameters\nnp.random.seed(42)\nRINGS = 7\nSECTORS = 12\nSECTOR_ANGLE = 2 * np.pi / SECTORS\nTOTAL_CELLS = 1 + RINGS * SECTORS  # center (0) + ring cells\n\n\ndef cell_id(r, s):\n    \"\"\"Ring r (1..RINGS) sector s → cell index. Center = 0.\"\"\"\n    return 1 + (r - 1) * SECTORS + s\n\n\n# Geometric constants\nINNER_R = 0.12\nRING_W = (0.82 - INNER_R) / RINGS\nOUTER_R = INNER_R + RINGS * RING_W\nFRAME_R = OUTER_R + 0.04\nWALL_LW = 2.2\n\n# Union-Find with path compression\nparent = list(range(TOTAL_CELLS))\nuf_rank = [0] * TOTAL_CELLS\n\n\ndef find(x):\n    while parent[x] != x:\n        parent[x] = parent[parent[x]]\n        x = parent[x]\n    return x\n\n\ndef union(a, b):\n    ra, rb = find(a), find(b)\n    if ra == rb:\n        return False\n    if uf_rank[ra] < uf_rank[rb]:\n        ra, rb = rb, ra\n    parent[rb] = ra\n    if uf_rank[ra] == uf_rank[rb]:\n        uf_rank[ra] += 1\n    return True\n\n\n# Build walls: radial (ring-to-ring) and circular (sector-to-sector within ring)\nwalls = []\nfor r in range(RINGS):\n    for s in range(SECTORS):\n        c1 = 0 if r == 0 else cell_id(r, s)\n        c2 = cell_id(r + 1, s)\n        walls.append((\"radial\", r, s, c1, c2))\n\nfor r in range(1, RINGS + 1):\n    for s in range(SECTORS):\n        c1 = cell_id(r, s)\n        c2 = cell_id(r, (s + 1) % SECTORS)\n        walls.append((\"circular\", r, s, c1, c2))\n\nnp.random.shuffle(walls)\n\n# Kruskal's spanning tree — guarantees exactly one solution\npassages = set()\nadjacency = collections.defaultdict(set)\nfor wall in walls:\n    wtype, r, s, c1, c2 = wall\n    if union(c1, c2):\n        passages.add((wtype, r, s))\n        adjacency[c1].add(c2)\n        adjacency[c2].add(c1)\n\n# BFS from entry cell to center for solution path\nENTRY_SECTOR = 0\nentry_cell = cell_id(RINGS, ENTRY_SECTOR)\ngoal_cell = 0\n\nbfs_q = collections.deque([entry_cell])\nprev = {entry_cell: None}\nwhile bfs_q:\n    curr = bfs_q.popleft()\n    if curr == goal_cell:\n        break\n    for nxt in sorted(adjacency[curr]):\n        if nxt not in prev:\n            prev[nxt] = curr\n            bfs_q.append(nxt)\n\nsolution_path = []\nnode = goal_cell\nwhile node is not None:\n    solution_path.append(node)\n    node = prev.get(node)\nsolution_path.reverse()\n\n\ndef cell_center(cell):\n    \"\"\"(x, y) of cell center in normalized coordinates.\"\"\"\n    if cell == 0:\n        return 0.0, 0.0\n    idx = cell - 1\n    r = idx // SECTORS + 1\n    s = idx % SECTORS\n    radius = INNER_R + (r - 0.5) * RING_W\n    angle = (s + 0.5) * SECTOR_ANGLE\n    return radius * np.cos(angle), radius * np.sin(angle)\n\n\n# ---- Drawing ----\nfig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\nax.set_aspect(\"equal\")\n\n# Center zone\ncenter_circle = mpatches.Circle((0, 0), INNER_R, facecolor=ELEVATED_BG, edgecolor=INK, linewidth=WALL_LW, zorder=2)\nax.add_patch(center_circle)\n\n# Circular arc walls at each ring boundary r=1..RINGS (smooth vector arcs)\nfor r in range(1, RINGS + 1):\n    radius = INNER_R + r * RING_W\n    for s in range(SECTORS):\n        if (\"circular\", r, s) not in passages:\n            t1 = np.degrees(s * SECTOR_ANGLE)\n            t2 = np.degrees((s + 1) * SECTOR_ANGLE)\n            arc = mpatches.Arc(\n                (0, 0), 2 * radius, 2 * radius, theta1=t1, theta2=t2, color=INK, linewidth=WALL_LW, zorder=3\n            )\n            ax.add_patch(arc)\n\n# Outer boundary frame with entry gap\nentry_angle_mid = (ENTRY_SECTOR + 0.5) * SECTOR_ANGLE\ngap_half_rad = SECTOR_ANGLE * 0.32\ngap_start_deg = np.degrees(entry_angle_mid - gap_half_rad)\ngap_end_deg = np.degrees(entry_angle_mid + gap_half_rad)\nframe_arc = mpatches.Arc(\n    (0, 0),\n    2 * FRAME_R,\n    2 * FRAME_R,\n    theta1=gap_end_deg,\n    theta2=gap_start_deg + 360,\n    color=INK,\n    linewidth=WALL_LW + 0.5,\n    zorder=4,\n)\nax.add_patch(frame_arc)\n\n# Radial walls (smooth vector line segments)\nfor r in range(RINGS):\n    r_inner = INNER_R + r * RING_W\n    r_outer = INNER_R + (r + 1) * RING_W\n    for s in range(SECTORS):\n        if (\"radial\", r, s) not in passages:\n            angle = s * SECTOR_ANGLE\n            x1, y1 = r_inner * np.cos(angle), r_inner * np.sin(angle)\n            x2, y2 = r_outer * np.cos(angle), r_outer * np.sin(angle)\n            ax.plot([x1, x2], [y1, y2], color=INK, linewidth=WALL_LW, zorder=3, solid_capstyle=\"round\")\n\n# Solution path — seaborn lineplot traces BFS solution through cell centers\npx = [cell_center(c)[0] for c in solution_path]\npy = [cell_center(c)[1] for c in solution_path]\npath_df = pd.DataFrame({\"x\": px, \"y\": py})\nsns.lineplot(\n    data=path_df,\n    x=\"x\",\n    y=\"y\",\n    ax=ax,\n    color=PATH_COLOR,\n    linewidth=2.2,\n    alpha=0.65,\n    zorder=2,\n    label=\"Solution Path\",\n    sort=False,\n    estimator=None,\n)\n\n# Entry marker — seaborn scatterplot\nex = (FRAME_R + 0.07) * np.cos(entry_angle_mid)\ney = (FRAME_R + 0.07) * np.sin(entry_angle_mid)\nentry_df = pd.DataFrame({\"x\": [ex], \"y\": [ey]})\nsns.scatterplot(data=entry_df, x=\"x\", y=\"y\", ax=ax, color=BRAND, s=120, zorder=7, label=\"Entry Point\")\n\nax.annotate(\n    \"START\",\n    xy=(ex, ey),\n    xytext=(ex + 0.17, ey + 0.04),\n    fontsize=8,\n    fontweight=\"bold\",\n    ha=\"left\",\n    va=\"center\",\n    color=BRAND,\n    bbox={\"boxstyle\": \"round,pad=0.3\", \"facecolor\": ELEVATED_BG, \"edgecolor\": BRAND, \"alpha\": 0.9},\n    arrowprops={\"arrowstyle\": \"-\", \"color\": BRAND, \"lw\": 0.8},\n    zorder=7,\n)\n\n# Goal star at center\nax.text(0, 0, \"★\", fontsize=16, ha=\"center\", va=\"center\", color=GOAL_COLOR, fontweight=\"bold\", zorder=5)\n\n# Legend — combine seaborn-generated handles with manual goal entry\ngoal_handle = plt.Line2D(\n    [0], [0], marker=\"*\", color=\"w\", markerfacecolor=GOAL_COLOR, markersize=10, label=\"Goal (Center)\"\n)\nhandles, labels = ax.get_legend_handles_labels()\nhandles.append(goal_handle)\nlabels.append(\"Goal (Center)\")\nleg = ax.legend(\n    handles=handles,\n    labels=labels,\n    loc=\"upper right\",\n    fontsize=8,\n    framealpha=0.95,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n    fancybox=True,\n    borderpad=0.8,\n)\nfor text in leg.get_texts():\n    text.set_color(INK)\n\n# Clean axes\nax.set_xlim(-1.3, 1.3)\nax.set_ylim(-1.3, 1.3)\nax.set_xticks([])\nax.set_yticks([])\nax.set_xlabel(\"\")\nax.set_ylabel(\"\")\nfor spine in ax.spines.values():\n    spine.set_visible(False)\n\nax.set_title(\"maze-circular · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK, pad=10)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}