{"spec_id":"maze-circular","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nmaze-circular: Circular Maze Puzzle\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-20\n\"\"\"\n\nimport os\nimport sys\n\n\n# Drop script dir so `import pygal` resolves to the installed library, not this file\nsys.path[:] = [p for p in sys.path if p not in (\"\", \".\", os.path.dirname(os.path.abspath(__file__)))]\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\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_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nWALL_COLOR = \"#1A1A17\" if THEME == \"light\" else \"#E8E8E0\"\n\n# Data\nnp.random.seed(42)\nnum_rings = 7\nnum_sectors = 14\ninner_r = 0.10\nouter_r = 0.88\nradii = np.linspace(inner_r, outer_r, num_rings + 1)[1:]\nentry_sector = 0\n\n# Maze graph edges\nall_cells = [(r, s) for r in range(num_rings) for s in range(num_sectors)]\nedges = []\nfor ring in range(num_rings):\n    for sector in range(num_sectors):\n        edges.append(frozenset([(ring, sector), (ring, (sector + 1) % num_sectors)]))\n        if ring < num_rings - 1:\n            edges.append(frozenset([(ring, sector), (ring + 1, sector)]))\nedges = list(set(edges))\nnp.random.shuffle(edges)\n\n# Kruskal's maze generation (inline union-find with path halving)\nparent = {cell: cell for cell in all_cells}\nuf_rank = dict.fromkeys(all_cells, 0)\npassages = set()\nfor edge in edges:\n    cells = list(edge)\n    a = cells[0]\n    while parent[a] != a:\n        parent[a] = parent[parent[a]]\n        a = parent[a]\n    b = cells[1]\n    while parent[b] != b:\n        parent[b] = parent[parent[b]]\n        b = parent[b]\n    if a != b:\n        if uf_rank[a] < uf_rank[b]:\n            a, b = b, a\n        parent[b] = a\n        if uf_rank[a] == uf_rank[b]:\n            uf_rank[a] += 1\n        passages.add(edge)\n\n# Plot\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=(WALL_COLOR,),\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=2.5,\n)\n\nchart = pygal.XY(\n    width=2400,\n    height=2400,\n    style=custom_style,\n    title=\"maze-circular · python · pygal · anyplot.ai\",\n    show_legend=False,\n    show_dots=False,\n    stroke=True,\n    show_x_guides=False,\n    show_y_guides=False,\n    show_x_labels=False,\n    show_y_labels=False,\n    xrange=(-1.25, 1.25),\n    range=(-1.25, 1.25),\n    explicit_size=True,\n    fill=False,\n    margin=60,\n)\n\nPTS = 32  # arc sample points per sector — smooth curves at 2400 px canvas\nwall_stroke = {\"width\": 10, \"linecap\": \"round\", \"linejoin\": \"round\", \"color\": WALL_COLOR}\n\n# Outer boundary with entry gap at sector 0\nentry_a1 = 2 * np.pi * entry_sector / num_sectors\nentry_a2 = 2 * np.pi * (entry_sector + 1) / num_sectors\nangles_outer = np.linspace(entry_a2, entry_a1 + 2 * np.pi, PTS * (num_sectors - 1) + 1)\nouter_arc = [(outer_r * np.cos(a), outer_r * np.sin(a)) for a in angles_outer]\nchart.add(\"\", outer_arc, stroke_style=wall_stroke)\n\n# Inner circle (goal area boundary)\nangles_inner = np.linspace(0, 2 * np.pi, PTS * num_sectors + 1)\ninner_arc = [(inner_r * np.cos(a), inner_r * np.sin(a)) for a in angles_inner]\nchart.add(\"\", inner_arc, stroke_style=wall_stroke)\n\n# Ring walls (circular arc segments along ring boundaries)\nfor ring in range(num_rings - 1):\n    r = radii[ring]\n    for sector in range(num_sectors):\n        if frozenset([(ring, sector), (ring + 1, sector)]) not in passages:\n            a1 = 2 * np.pi * sector / num_sectors\n            a2 = 2 * np.pi * (sector + 1) / num_sectors\n            angles = np.linspace(a1, a2, PTS + 1)\n            pts = [(r * np.cos(a), r * np.sin(a)) for a in angles]\n            chart.add(\"\", pts, stroke_style=wall_stroke)\n\n# Radial walls (straight lines between sector boundaries)\nfor ring in range(num_rings):\n    r_in = radii[ring - 1] if ring > 0 else inner_r\n    r_out = radii[ring]\n    for sector in range(num_sectors):\n        next_s = (sector + 1) % num_sectors\n        if frozenset([(ring, sector), (ring, next_s)]) not in passages:\n            angle = 2 * np.pi * next_s / num_sectors\n            x1, y1 = r_in * np.cos(angle), r_in * np.sin(angle)\n            x2, y2 = r_out * np.cos(angle), r_out * np.sin(angle)\n            chart.add(\"\", [(x1, y1), (x2, y2)], stroke_style=wall_stroke)\n\n# Entry arrow (Okabe-Ito green #009E73) pointing inward — named for pygal interactive HTML tooltips\nmid_angle = (entry_a1 + entry_a2) / 2\ntip_x = (outer_r + 0.03) * np.cos(mid_angle)\ntip_y = (outer_r + 0.03) * np.sin(mid_angle)\nbase_x = (outer_r + 0.35) * np.cos(mid_angle)\nbase_y = (outer_r + 0.35) * np.sin(mid_angle)\nentry_stroke = {\"width\": 20, \"linecap\": \"round\", \"linejoin\": \"round\", \"color\": \"#009E73\"}\nchart.add(\"Entry →\", [(base_x, base_y), (tip_x, tip_y)], stroke_style=entry_stroke)\nwing1_x = tip_x + 0.13 * np.cos(mid_angle + np.pi - 0.45)\nwing1_y = tip_y + 0.13 * np.sin(mid_angle + np.pi - 0.45)\nwing2_x = tip_x + 0.13 * np.cos(mid_angle + np.pi + 0.45)\nwing2_y = tip_y + 0.13 * np.sin(mid_angle + np.pi + 0.45)\nchart.add(\"\", [(wing1_x, wing1_y), (tip_x, tip_y), (wing2_x, wing2_y)], stroke_style=entry_stroke)\n\n# Goal star (Okabe-Ito vermillion #C475FD) at center — named for pygal interactive HTML tooltips\nstar_pts = []\nfor i in range(10):\n    a_s = i * np.pi / 5 - np.pi / 2\n    r_s = 0.12 if i % 2 == 0 else 0.048\n    star_pts.append((r_s * np.cos(a_s), r_s * np.sin(a_s)))\nstar_pts.append(star_pts[0])\nchart.add(\"Goal ★\", star_pts, stroke_style={\"width\": 10, \"linecap\": \"round\", \"linejoin\": \"round\", \"color\": \"#C475FD\"})\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}