{"spec_id":"maze-circular","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nmaze-circular: Circular Maze Puzzle\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-20\n\"\"\"\n\nimport os\nimport sys\nfrom collections import deque\n\n\n# Remove current directory from path to avoid importing local plotly.py\nsys.path = [p for p in sys.path if p not in (\"\", \".\", os.path.dirname(__file__))]\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Theme\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\"\nACCENT = \"#009E73\"  # Okabe-Ito position 1\n\n# Maze parameters — outer rings have more sectors than inner rings\nnp.random.seed(42)\nNUM_RINGS = 7\nSECTORS = [42, 36, 30, 24, 18, 12, 6]\n\n# Wall arrays: radial_walls[r][s] = wall clockwise from sector s in ring r\n#              ring_walls[r][s]   = wall between ring r and ring r+1 at sector s\nradial_walls = [[True] * SECTORS[r] for r in range(NUM_RINGS)]\nring_walls = [[True] * SECTORS[r] for r in range(NUM_RINGS - 1)]\n\n# Depth-first search maze generation — guarantees exactly one solution\nvisited = {(0, 0)}\ndfs_stack = [(0, 0)]\n\nwhile dfs_stack:\n    cr, cs = dfs_stack[-1]\n    n = SECTORS[cr]\n\n    neighbors = [(cr, (cs + 1) % n, \"cw\"), (cr, (cs - 1) % n, \"ccw\")]\n    if cr < NUM_RINGS - 1:\n        neighbors.append((cr + 1, int(cs * SECTORS[cr + 1] / n), \"in\"))\n    if cr > 0:\n        neighbors.append((cr - 1, int(cs * SECTORS[cr - 1] / n), \"out\"))\n\n    unvisited = [(r, s, d) for r, s, d in neighbors if (r, s) not in visited]\n\n    if unvisited:\n        nr, ns, d = unvisited[np.random.randint(len(unvisited))]\n        if d == \"cw\":\n            radial_walls[cr][cs] = False\n        elif d == \"ccw\":\n            radial_walls[cr][ns] = False\n        elif d == \"in\":\n            ring_walls[cr][cs] = False\n        else:\n            ring_walls[nr][ns] = False\n        visited.add((nr, ns))\n        dfs_stack.append((nr, ns))\n    else:\n        dfs_stack.pop()\n\n# Build undirected passage graph for BFS\ngraph = {}\n\nfor r in range(NUM_RINGS):\n    for s in range(SECTORS[r]):\n        if not radial_walls[r][s]:\n            a, b = (r, s), (r, (s + 1) % SECTORS[r])\n            graph.setdefault(a, []).append(b)\n            graph.setdefault(b, []).append(a)\n\nfor r in range(NUM_RINGS - 1):\n    for s in range(SECTORS[r]):\n        if not ring_walls[r][s]:\n            inner_s = int(s * SECTORS[r + 1] / SECTORS[r])\n            a, b = (r, s), (r + 1, inner_s)\n            graph.setdefault(a, []).append(b)\n            graph.setdefault(b, []).append(a)\n\n# BFS to find the unique solution path to the innermost ring\nbfs_queue = deque([((0, 0), [(0, 0)])])\nbfs_seen = {(0, 0)}\nsolution = []\n\nwhile bfs_queue:\n    cell, path = bfs_queue.popleft()\n    if cell[0] == NUM_RINGS - 1:\n        solution = path\n        break\n    for nb in graph.get(cell, []):\n        if nb not in bfs_seen:\n            bfs_seen.add(nb)\n            bfs_queue.append((nb, path + [nb]))\n\nsol_x, sol_y = [], []\nfor r, s in solution:\n    r_mid = NUM_RINGS - r - 0.5\n    angle = (s + 0.5) * 2 * np.pi / SECTORS[r]\n    sol_x.append(r_mid * np.cos(angle))\n    sol_y.append(r_mid * np.sin(angle))\nsol_x.append(0.0)\nsol_y.append(0.0)\n\n# Drawing constants\nOUTER_R = NUM_RINGS\nWALL_W = 3\n\nfig = go.Figure()\n\n# Ring arc walls — inner boundary arcs where wall exists\nfor r in range(NUM_RINGS - 1):\n    n = SECTORS[r]\n    inner_r = NUM_RINGS - r - 1\n    sa = 2 * np.pi / n\n    for s in range(n):\n        if ring_walls[r][s]:\n            theta = np.linspace(s * sa, (s + 1) * sa, 30)\n            fig.add_trace(\n                go.Scatter(\n                    x=inner_r * np.cos(theta),\n                    y=inner_r * np.sin(theta),\n                    mode=\"lines\",\n                    line={\"color\": INK, \"width\": WALL_W},\n                    showlegend=False,\n                    hoverinfo=\"skip\",\n                )\n            )\n\n# Outer boundary circle with entry gap at sector 0\ngap_start = 0.0\ngap_end = 2 * np.pi / SECTORS[0]\ntheta_outer = np.linspace(gap_end, gap_start + 2 * np.pi, 300)\nfig.add_trace(\n    go.Scatter(\n        x=OUTER_R * np.cos(theta_outer),\n        y=OUTER_R * np.sin(theta_outer),\n        mode=\"lines\",\n        line={\"color\": INK, \"width\": WALL_W + 1},\n        showlegend=False,\n        hoverinfo=\"skip\",\n    )\n)\n\n# Radial walls — spokes between ring boundaries\nfor r in range(NUM_RINGS):\n    r_out = NUM_RINGS - r\n    r_in = NUM_RINGS - r - 1\n    n = SECTORS[r]\n    for s in range(n):\n        if radial_walls[r][s]:\n            theta = (s + 1) * 2 * np.pi / n\n            fig.add_trace(\n                go.Scatter(\n                    x=[r_in * np.cos(theta), r_out * np.cos(theta)],\n                    y=[r_in * np.sin(theta), r_out * np.sin(theta)],\n                    mode=\"lines\",\n                    line={\"color\": INK, \"width\": WALL_W},\n                    showlegend=False,\n                    hoverinfo=\"skip\",\n                )\n            )\n\n# Solution path — hidden by default; click legend entry to reveal\nfig.add_trace(\n    go.Scatter(\n        x=sol_x,\n        y=sol_y,\n        mode=\"lines\",\n        name=\"Show Solution\",\n        line={\"color\": \"#C475FD\", \"width\": 4, \"dash\": \"dot\"},\n        opacity=0.85,\n        visible=\"legendonly\",\n    )\n)\n\n# Center goal circle\ngoal_r = 0.4\ntheta_g = np.linspace(0, 2 * np.pi, 60)\nfig.add_trace(\n    go.Scatter(\n        x=goal_r * np.cos(theta_g),\n        y=goal_r * np.sin(theta_g),\n        fill=\"toself\",\n        fillcolor=ACCENT,\n        line={\"color\": ACCENT, \"width\": 2},\n        showlegend=False,\n        hoverinfo=\"skip\",\n    )\n)\nfig.add_trace(\n    go.Scatter(\n        x=[0.0],\n        y=[0.0],\n        mode=\"markers\",\n        marker={\"symbol\": \"star\", \"size\": 20, \"color\": \"#DDCC77\", \"line\": {\"color\": ACCENT, \"width\": 2}},\n        showlegend=False,\n        hoverinfo=\"skip\",\n    )\n)\n\n# Entry arrow and labels\nentry_angle = 0.5 * 2 * np.pi / SECTORS[0]\nfig.add_annotation(\n    x=OUTER_R * np.cos(entry_angle),\n    y=OUTER_R * np.sin(entry_angle),\n    ax=(OUTER_R + 1.5) * np.cos(entry_angle),\n    ay=(OUTER_R + 1.5) * np.sin(entry_angle),\n    xref=\"x\",\n    yref=\"y\",\n    axref=\"x\",\n    ayref=\"y\",\n    showarrow=True,\n    arrowhead=2,\n    arrowsize=2,\n    arrowwidth=3,\n    arrowcolor=ACCENT,\n)\nfig.add_annotation(\n    x=(OUTER_R + 2.8) * np.cos(entry_angle),\n    y=(OUTER_R + 2.8) * np.sin(entry_angle),\n    text=\"START\",\n    showarrow=False,\n    xref=\"x\",\n    yref=\"y\",\n    font={\"size\": 22, \"color\": ACCENT, \"family\": \"Arial Black\"},\n)\nfig.add_annotation(\n    x=0.0,\n    y=-(OUTER_R + 1.5),\n    text=\"GOAL\",\n    showarrow=False,\n    xref=\"x\",\n    yref=\"y\",\n    font={\"size\": 22, \"color\": ACCENT, \"family\": \"Arial Black\"},\n)\n\n# Layout\nfig.update_layout(\n    title={\n        \"text\": \"maze-circular · python · plotly · anyplot.ai\",\n        \"font\": {\"size\": 16, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\n        \"showgrid\": False,\n        \"zeroline\": False,\n        \"showticklabels\": False,\n        \"scaleanchor\": \"y\",\n        \"scaleratio\": 1,\n        \"range\": [-(OUTER_R + 4.0), OUTER_R + 4.0],\n    },\n    yaxis={\"showgrid\": False, \"zeroline\": False, \"showticklabels\": False, \"range\": [-(OUTER_R + 4.0), OUTER_R + 4.0]},\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    showlegend=True,\n    legend={\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n        \"font\": {\"color\": INK_SOFT, \"size\": 10},\n        \"x\": 0.02,\n        \"y\": 0.98,\n        \"xanchor\": \"left\",\n        \"yanchor\": \"top\",\n    },\n    margin={\"l\": 50, \"r\": 50, \"t\": 100, \"b\": 50},\n)\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=600, height=600, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}