{"spec_id":"maze-circular","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nmaze-circular: Circular Maze Puzzle\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-20\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_segment,\n    geom_text,\n    ggplot,\n    labs,\n    theme,\n    theme_void,\n)\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nGOAL_COLOR = \"#009E73\"  # Okabe-Ito position 1\n\n# Maze parameters\nnp.random.seed(42)\ndifficulty = \"medium\"\nn_rings = {\"easy\": 5, \"medium\": 7, \"hard\": 9}[difficulty]\nbase_sectors = [1, 6, 12, 18, 24, 30, 36, 42, 48, 54]\nsectors_per_ring = base_sectors[: n_rings + 1]\nring_width = 1.0\nradii = [i * ring_width for i in range(n_rings + 2)]\n\n# Initialize maze walls\nradial_walls = []\narc_walls = []\nfor ring in range(n_rings + 1):\n    n_sec = sectors_per_ring[min(ring, len(sectors_per_ring) - 1)]\n    radial_walls.append([True] * n_sec)\n    arc_walls.append([True] * n_sec)\n\n# DFS maze generation from center — neighbors computed inline (flat KISS structure)\nvisited = {(0, 0)}\nstack = [(0, 0)]\nwhile stack:\n    ring, sector = stack[-1]\n    n_sec = sectors_per_ring[min(ring, len(sectors_per_ring) - 1)]\n    nbrs = [(ring, (sector - 1) % n_sec, \"radial_prev\"), (ring, (sector + 1) % n_sec, \"radial_next\")]\n    if ring > 0:\n        n_inn = sectors_per_ring[min(ring - 1, len(sectors_per_ring) - 1)]\n        nbrs.append((ring - 1, int(sector * n_inn / n_sec), \"arc_inner\"))\n    if ring < n_rings:\n        n_out = sectors_per_ring[min(ring + 1, len(sectors_per_ring) - 1)]\n        s0 = int(sector * n_out / n_sec)\n        s1 = int((sector + 1) * n_out / n_sec)\n        for s in range(s0, s1):\n            nbrs.append((ring + 1, s % n_out, \"arc_outer\"))\n    unvisited = [(nr, ns, wt) for nr, ns, wt in nbrs if (nr, ns) not in visited]\n    if unvisited:\n        nr, ns, wt = unvisited[np.random.randint(len(unvisited))]\n        if wt == \"radial_prev\":\n            radial_walls[ring][sector] = False\n        elif wt == \"radial_next\":\n            radial_walls[ring][(sector + 1) % n_sec] = False\n        elif wt == \"arc_inner\":\n            arc_walls[ring - 1][ns] = False\n        else:\n            arc_walls[ring][sector] = False\n        visited.add((nr, ns))\n        stack.append((nr, ns))\n    else:\n        stack.pop()\n\n# Entry gap on outer ring (sector 0 — rightmost)\nn_outer = sectors_per_ring[min(n_rings, len(sectors_per_ring) - 1)]\nentry_sector = 0\nentry_angle_0 = 2 * np.pi * entry_sector / n_outer\ngap_half = np.pi / n_outer * 1.2  # 1.2× sector half-width for a prominent entry gap\n\n# Build wall segments tagged by ring depth for tapered stroke weight\nsegments = []\n\n# Outer boundary with prominent entry gap — outermost ring tag\nn_pts = 300\nfor i in range(n_pts):\n    t1 = 2 * np.pi * i / n_pts\n    t2 = 2 * np.pi * (i + 1) / n_pts\n    t_mid = (t1 + t2) / 2\n    if abs(t_mid - entry_angle_0) > gap_half and abs(t_mid - entry_angle_0 - 2 * np.pi) > gap_half:\n        r = radii[n_rings + 1]\n        segments.append(\n            {\n                \"x\": r * np.cos(t1),\n                \"y\": r * np.sin(t1),\n                \"xend\": r * np.cos(t2),\n                \"yend\": r * np.sin(t2),\n                \"ring\": n_rings + 1,\n            }\n        )\n\n# Arc walls between rings\nfor ring in range(n_rings):\n    n_sec = sectors_per_ring[min(ring, len(sectors_per_ring) - 1)]\n    r = radii[ring + 1]\n    for sec in range(n_sec):\n        if arc_walls[ring][sec]:\n            t1 = 2 * np.pi * sec / n_sec\n            t2 = 2 * np.pi * (sec + 1) / n_sec\n            n_sub = max(3, int(120 / n_sec))\n            for j in range(n_sub):\n                ta = t1 + (t2 - t1) * j / n_sub\n                tb = t1 + (t2 - t1) * (j + 1) / n_sub\n                segments.append(\n                    {\n                        \"x\": r * np.cos(ta),\n                        \"y\": r * np.sin(ta),\n                        \"xend\": r * np.cos(tb),\n                        \"yend\": r * np.sin(tb),\n                        \"ring\": ring + 1,\n                    }\n                )\n\n# Radial walls within each ring\nfor ring in range(n_rings + 1):\n    n_sec = sectors_per_ring[min(ring, len(sectors_per_ring) - 1)]\n    r_in, r_out = radii[ring], radii[ring + 1]\n    for sec in range(n_sec):\n        if radial_walls[ring][sec]:\n            t = 2 * np.pi * sec / n_sec\n            segments.append(\n                {\n                    \"x\": r_in * np.cos(t),\n                    \"y\": r_in * np.sin(t),\n                    \"xend\": r_out * np.cos(t),\n                    \"yend\": r_out * np.sin(t),\n                    \"ring\": ring,\n                }\n            )\n\nwalls_df = pd.DataFrame(segments)\n# Taper stroke weight: outer rings thicker, inner rings thinner — depth illusion\nmid_ring = n_rings // 2\nwalls_outer_df = walls_df[walls_df[\"ring\"] >= mid_ring]\nwalls_inner_df = walls_df[walls_df[\"ring\"] < mid_ring]\n\n# Entry gate: short highlighted arc inside the outer boundary at the entry gap\nentry_gate_segs = []\nr_gate = radii[n_rings + 1] * 0.965\ngate_span = gap_half\nn_gate = 8\nfor j in range(n_gate):\n    ta = entry_angle_0 - gate_span + 2 * gate_span * j / n_gate\n    tb = entry_angle_0 - gate_span + 2 * gate_span * (j + 1) / n_gate\n    entry_gate_segs.append(\n        {\"x\": r_gate * np.cos(ta), \"y\": r_gate * np.sin(ta), \"xend\": r_gate * np.cos(tb), \"yend\": r_gate * np.sin(tb)}\n    )\nentry_gate_df = pd.DataFrame(entry_gate_segs)\n\n# Entry and goal markers\nentry_angle_mid = 2 * np.pi * (entry_sector + 0.5) / n_outer\nentry_r = radii[n_rings + 1] + ring_width * 0.7\nentry_df = pd.DataFrame(\n    {\"x\": [entry_r * np.cos(entry_angle_mid)], \"y\": [entry_r * np.sin(entry_angle_mid)], \"label\": [\"START\"]}\n)\ngoal_df = pd.DataFrame({\"x\": [0.0], \"y\": [0.0], \"label\": [\"GOAL\"]})\n\n# Difficulty caption below the maze\ncaption_df = pd.DataFrame(\n    {\"x\": [0.0], \"y\": [-(radii[n_rings + 1] + ring_width * 0.95)], \"label\": [f\"{n_rings} rings · {difficulty}\"]}\n)\n\n# Plot\nplot = (\n    ggplot()\n    # Outer/mid walls — thicker stroke\n    + geom_segment(data=walls_outer_df, mapping=aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), color=INK, size=0.9)\n    # Inner walls — thinner stroke creates depth illusion drawing eye to center\n    + geom_segment(data=walls_inner_df, mapping=aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), color=INK, size=0.45)\n    # Entry gate marker: subtle arc at the threshold\n    + geom_segment(data=entry_gate_df, mapping=aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), color=INK_SOFT, size=1.4)\n    # GOAL bullseye: soft glow ring behind the solid dot\n    + geom_point(data=goal_df, mapping=aes(x=\"x\", y=\"y\"), color=GOAL_COLOR, size=11, alpha=0.18)\n    + geom_point(data=goal_df, mapping=aes(x=\"x\", y=\"y\"), color=GOAL_COLOR, size=5)\n    + geom_text(\n        data=goal_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=GOAL_COLOR, size=9, fontweight=\"bold\", nudge_y=0.6\n    )\n    + geom_text(data=entry_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=INK_SOFT, size=9, fontweight=\"bold\")\n    + geom_text(data=caption_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=INK_SOFT, size=7)\n    + coord_fixed(ratio=1)\n    + labs(title=\"maze-circular · python · plotnine · anyplot.ai\")\n    + theme_void()\n    + theme(\n        figure_size=(6, 6),\n        plot_title=element_text(size=12, ha=\"center\", weight=\"bold\", color=INK),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\")\n"}