Skip to content

Commit 9a81dcd

Browse files
feat(pygal): implement maze-printable (#3272)
## Implementation: `maze-printable` - pygal Implements the **pygal** version of `maze-printable`. **File:** `plots/maze-printable/implementations/pygal.py` **Parent Issue:** #3232 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20795218976)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 196ea7a commit 9a81dcd

2 files changed

Lines changed: 398 additions & 0 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
""" pyplots.ai
2+
maze-printable: Printable Maze Puzzle
3+
Library: pygal 3.1.0 | Python 3.13.11
4+
Quality: 90/100 | Created: 2026-01-07
5+
"""
6+
7+
import os
8+
import re
9+
10+
import cairosvg
11+
import numpy as np
12+
import pygal
13+
from pygal.style import Style
14+
15+
16+
# Seed for reproducibility
17+
np.random.seed(42)
18+
19+
# Maze dimensions (25x25 as specified in spec)
20+
maze_width = 25
21+
maze_height = 25
22+
23+
# Generate maze using DFS algorithm
24+
# Each cell: 0 = wall, 1 = passage
25+
grid_h = maze_height * 2 + 1
26+
grid_w = maze_width * 2 + 1
27+
maze = np.zeros((grid_h, grid_w), dtype=int)
28+
29+
# Initialize cells (passages between walls)
30+
for y in range(maze_height):
31+
for x in range(maze_width):
32+
maze[y * 2 + 1, x * 2 + 1] = 1
33+
34+
# DFS maze generation
35+
stack = [(0, 0)]
36+
visited = set()
37+
visited.add((0, 0))
38+
39+
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
40+
41+
while stack:
42+
cx, cy = stack[-1]
43+
neighbors = []
44+
for dx, dy in directions:
45+
nx, ny = cx + dx, cy + dy
46+
if 0 <= nx < maze_width and 0 <= ny < maze_height and (nx, ny) not in visited:
47+
neighbors.append((nx, ny, dx, dy))
48+
49+
if neighbors:
50+
idx = np.random.randint(len(neighbors))
51+
nx, ny, dx, dy = neighbors[idx]
52+
# Remove wall between current and neighbor
53+
maze[cy * 2 + 1 + dy, cx * 2 + 1 + dx] = 1
54+
visited.add((nx, ny))
55+
stack.append((nx, ny))
56+
else:
57+
stack.pop()
58+
59+
# Create entrance (top-left) and exit (bottom-right)
60+
maze[0, 1] = 1 # Entrance at top
61+
maze[grid_h - 1, grid_w - 2] = 1 # Exit at bottom
62+
63+
# Mark start and goal positions in the maze data
64+
# Start: first cell after entrance (row 1, col 1)
65+
start_y, start_x = 1, 1
66+
# Goal: last cell before exit (row grid_h-2, col grid_w-2)
67+
goal_y, goal_x = grid_h - 2, grid_w - 2
68+
69+
# All walls are black - create enough black entries for each row
70+
colors = tuple(["#000000"] * grid_h)
71+
72+
# Custom style - clean black and white for printability
73+
custom_style = Style(
74+
background="white",
75+
plot_background="white",
76+
foreground="#000000",
77+
foreground_strong="#000000",
78+
foreground_subtle="#000000",
79+
colors=colors,
80+
title_font_size=96,
81+
label_font_size=1,
82+
major_label_font_size=1,
83+
legend_font_size=1,
84+
value_font_size=1,
85+
font_family="monospace",
86+
)
87+
88+
# Use Dot chart to create grid representation
89+
chart = pygal.Dot(
90+
width=3600,
91+
height=3600,
92+
style=custom_style,
93+
title="maze-printable · pygal · pyplots.ai",
94+
show_legend=False,
95+
show_x_labels=False,
96+
show_y_labels=False,
97+
dots_size=32,
98+
margin=100,
99+
)
100+
101+
# Add wall rows - walls show as black dots, passages are empty
102+
for y in range(grid_h):
103+
row_data = []
104+
for x in range(grid_w):
105+
if maze[y, x] == 0: # Wall
106+
row_data.append(1)
107+
else: # Passage
108+
row_data.append(None)
109+
chart.add("", row_data)
110+
111+
# First, create a helper chart with dots at start and goal positions to find exact coordinates
112+
helper_chart = pygal.Dot(
113+
width=3600,
114+
height=3600,
115+
style=custom_style,
116+
title="maze-printable · pygal · pyplots.ai",
117+
show_legend=False,
118+
show_x_labels=False,
119+
show_y_labels=False,
120+
dots_size=32,
121+
margin=100,
122+
)
123+
124+
# Add rows with dots only at start and goal positions
125+
for y in range(grid_h):
126+
row_data = []
127+
for x in range(grid_w):
128+
if (x == start_x and y == start_y) or (x == goal_x and y == goal_y):
129+
row_data.append(1)
130+
else:
131+
row_data.append(None)
132+
helper_chart.add("", row_data)
133+
134+
# Extract circle positions from helper chart
135+
helper_svg = helper_chart.render().decode("utf-8")
136+
circles = re.findall(r'<circle[^>]*cx="([^"]+)"[^>]*cy="([^"]+)"', helper_svg)
137+
138+
# Get start and goal positions (first circle is start, second is goal)
139+
# Note: pygal applies a transform to the plot group, so we need to add offsets
140+
# The plot group has transform="translate(margin, title_area_height + margin)"
141+
# margin = 100, title_area_height ~ 106 (based on title_font_size 96)
142+
plot_x_offset = 100
143+
plot_y_offset = 206 # Accounts for title area and margin
144+
145+
if len(circles) >= 2:
146+
s_x = float(circles[0][0]) + plot_x_offset
147+
s_y = float(circles[0][1]) + plot_y_offset
148+
g_x = float(circles[1][0]) + plot_x_offset
149+
g_y = float(circles[1][1]) + plot_y_offset
150+
else:
151+
# Fallback positions
152+
s_x, s_y = 300, 400
153+
g_x, g_y = 3350, 3350
154+
155+
# Render the main maze chart
156+
svg_string = chart.render().decode("utf-8")
157+
158+
# Create S and G text elements with bold styling - sized to fit within a cell
159+
# Cell spacing is approximately 64px, so font-size of 50 fits well
160+
s_marker = f'''<text x="{s_x}" y="{s_y}" font-family="Arial, sans-serif" font-size="50" font-weight="bold" fill="#000000" text-anchor="middle" dominant-baseline="central">S</text>'''
161+
g_marker = f'''<text x="{g_x}" y="{g_y}" font-family="Arial, sans-serif" font-size="50" font-weight="bold" fill="#000000" text-anchor="middle" dominant-baseline="central">G</text>'''
162+
163+
# Insert markers before the closing </svg> tag
164+
svg_with_markers = svg_string.replace("</svg>", f"{s_marker}\n{g_marker}\n</svg>")
165+
166+
# Write modified SVG and convert to PNG
167+
with open("plot_temp.svg", "w", encoding="utf-8") as f:
168+
f.write(svg_with_markers)
169+
170+
# Use cairosvg to convert SVG to PNG
171+
cairosvg.svg2png(bytestring=svg_with_markers.encode("utf-8"), write_to="plot.png")
172+
173+
# Also save HTML version with markers
174+
html_template = f"""<?xml version="1.0" encoding="utf-8"?>
175+
<!DOCTYPE html>
176+
<html>
177+
<head>
178+
<meta charset="utf-8">
179+
<title>maze-printable · pygal · pyplots.ai</title>
180+
</head>
181+
<body style="margin:0;padding:0;background:white;">
182+
{svg_with_markers}
183+
</body>
184+
</html>"""
185+
186+
with open("plot.html", "w", encoding="utf-8") as f:
187+
f.write(html_template)
188+
189+
# Clean up temp file
190+
if os.path.exists("plot_temp.svg"):
191+
os.remove("plot_temp.svg")
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
library: pygal
2+
specification_id: maze-printable
3+
created: '2026-01-07T20:23:18Z'
4+
updated: '2026-01-07T20:44:36Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20795218976
7+
issue: 3232
8+
python_version: 3.13.11
9+
library_version: 3.1.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/maze-printable/pygal/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/maze-printable/pygal/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/maze-printable/pygal/plot.html
13+
quality_score: 90
14+
review:
15+
strengths:
16+
- Creative adaptation of pygal Dot chart for maze rendering
17+
- Proper DFS maze generation algorithm ensuring single solution
18+
- Clean black-and-white design optimized for printing
19+
- Correct title format and square aspect ratio
20+
- SVG post-processing to add S/G markers at calculated positions
21+
- Proper entrance and exit gaps at maze borders
22+
weaknesses:
23+
- S and G markers could be slightly more prominent (larger font size would improve
24+
visibility)
25+
- Passage width is adequate but on the tighter side for comfortable pen/pencil marking
26+
image_description: The plot displays a 25x25 printable maze puzzle rendered using
27+
pygal's Dot chart. The maze consists of black circular dots forming the walls
28+
on a white background, creating clear passages between them. The title "maze-printable
29+
· pygal · pyplots.ai" appears at the top in a clean monospace font. An "S" marker
30+
indicates the start position near the top-left corner (with an entrance gap at
31+
the top border), and a "G" marker indicates the goal position near the bottom-right
32+
corner (with an exit gap at the bottom border). The maze uses the square 3600×3600
33+
format, with the maze grid filling the canvas well. The black-and-white design
34+
is print-friendly with good contrast.
35+
criteria_checklist:
36+
visual_quality:
37+
score: 36
38+
max: 40
39+
items:
40+
- id: VQ-01
41+
name: Text Legibility
42+
score: 8
43+
max: 10
44+
passed: true
45+
comment: Title is clearly readable. S and G markers are visible but could
46+
be slightly larger for better visibility.
47+
- id: VQ-02
48+
name: No Overlap
49+
score: 8
50+
max: 8
51+
passed: true
52+
comment: No overlapping elements; walls and passages are clearly distinguished
53+
- id: VQ-03
54+
name: Element Visibility
55+
score: 8
56+
max: 8
57+
passed: true
58+
comment: Dot size creates clear walls with appropriate passage widths
59+
- id: VQ-04
60+
name: Color Accessibility
61+
score: 5
62+
max: 5
63+
passed: true
64+
comment: Pure black on white provides maximum contrast and is colorblind-safe
65+
- id: VQ-05
66+
name: Layout Balance
67+
score: 5
68+
max: 5
69+
passed: true
70+
comment: Maze fills canvas well with balanced margins; good use of square
71+
format
72+
- id: VQ-06
73+
name: Axis Labels
74+
score: 0
75+
max: 2
76+
passed: true
77+
comment: N/A for maze (no axes needed)
78+
- id: VQ-07
79+
name: Grid & Legend
80+
score: 2
81+
max: 2
82+
passed: true
83+
comment: No grid or legend needed for maze; clean implementation
84+
spec_compliance:
85+
score: 25
86+
max: 25
87+
items:
88+
- id: SC-01
89+
name: Plot Type
90+
score: 8
91+
max: 8
92+
passed: true
93+
comment: Correct maze puzzle visualization using creative dot-based approach
94+
- id: SC-02
95+
name: Data Mapping
96+
score: 5
97+
max: 5
98+
passed: true
99+
comment: Walls correctly mapped as black dots, passages as empty space
100+
- id: SC-03
101+
name: Required Features
102+
score: 5
103+
max: 5
104+
passed: true
105+
comment: Has entrance, exit, S marker, G marker, guaranteed single solution
106+
via DFS
107+
- id: SC-04
108+
name: Data Range
109+
score: 3
110+
max: 3
111+
passed: true
112+
comment: 25x25 grid as specified, properly bounded
113+
- id: SC-05
114+
name: Legend Accuracy
115+
score: 2
116+
max: 2
117+
passed: true
118+
comment: N/A for maze; S and G markers serve as legend
119+
- id: SC-06
120+
name: Title Format
121+
score: 2
122+
max: 2
123+
passed: true
124+
comment: 'Correct format: maze-printable · pygal · pyplots.ai'
125+
data_quality:
126+
score: 17
127+
max: 20
128+
items:
129+
- id: DQ-01
130+
name: Feature Coverage
131+
score: 7
132+
max: 8
133+
passed: true
134+
comment: Shows maze with entrance, exit, start/goal markers, solvable paths;
135+
slight deduction for marker visibility
136+
- id: DQ-02
137+
name: Realistic Context
138+
score: 7
139+
max: 7
140+
passed: true
141+
comment: Printable maze puzzle is a real, neutral application per spec
142+
- id: DQ-03
143+
name: Appropriate Scale
144+
score: 3
145+
max: 5
146+
passed: true
147+
comment: 25x25 is within recommended range; passages are slightly tight for
148+
comfortable pen marking
149+
code_quality:
150+
score: 9
151+
max: 10
152+
items:
153+
- id: CQ-01
154+
name: KISS Structure
155+
score: 3
156+
max: 3
157+
passed: true
158+
comment: Linear script without functions/classes
159+
- id: CQ-02
160+
name: Reproducibility
161+
score: 3
162+
max: 3
163+
passed: true
164+
comment: Uses np.random.seed(42)
165+
- id: CQ-03
166+
name: Clean Imports
167+
score: 1
168+
max: 2
169+
passed: true
170+
comment: os import used only for cleanup; minor redundancy
171+
- id: CQ-04
172+
name: No Deprecated API
173+
score: 1
174+
max: 1
175+
passed: true
176+
comment: Uses current pygal API
177+
- id: CQ-05
178+
name: Output Correct
179+
score: 1
180+
max: 1
181+
passed: true
182+
comment: Saves as plot.png and plot.html
183+
library_features:
184+
score: 3
185+
max: 5
186+
items:
187+
- id: LF-01
188+
name: Distinctive Features
189+
score: 3
190+
max: 5
191+
passed: true
192+
comment: Creative use of Dot chart for maze visualization; SVG manipulation
193+
for markers shows advanced usage
194+
verdict: APPROVED
195+
impl_tags:
196+
dependencies:
197+
- cairosvg
198+
techniques:
199+
- annotations
200+
- html-export
201+
patterns:
202+
- data-generation
203+
- matrix-construction
204+
- iteration-over-groups
205+
dataprep: []
206+
styling:
207+
- minimal-chrome

0 commit comments

Comments
 (0)