{"spec_id":"crossword-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\ncrossword-basic: Crossword Puzzle Grid\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 84/100 | Updated: 2026-05-20\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\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\"\n\n# Crossword cell colors — fixed regardless of theme (contrast IS the data)\nCELL_BLOCK = \"#1A1A17\"\nCELL_NUMBERED = \"#FFFFFF\"  # word-start cells: pure white for visual emphasis\nCELL_ENTRY = \"#FAFAFA\"  # plain entry cells: slightly warm off-white\n\n# Data - 15x15 crossword grid with 180-degree rotational symmetry\ngrid_size = 15\n\ngrid = np.zeros((grid_size, grid_size), dtype=int)\n\nblack_cells = [\n    (0, 4),\n    (0, 10),\n    (1, 4),\n    (1, 10),\n    (2, 7),\n    (3, 0),\n    (3, 1),\n    (3, 8),\n    (3, 9),\n    (4, 5),\n    (4, 11),\n    (4, 12),\n    (4, 13),\n    (4, 14),\n    (5, 3),\n    (5, 6),\n    (6, 2),\n    (6, 9),\n    (6, 10),\n    (7, 0),\n    (7, 7),\n    (7, 14),\n]\n\nfor r, c in black_cells:\n    grid[r, c] = 1\n    grid[grid_size - 1 - r, grid_size - 1 - c] = 1\n\n# Generate clue numbers (cells that start across or down words)\nnumbers = {}\nclue_num = 1\n\nfor r in range(grid_size):\n    for c in range(grid_size):\n        if grid[r, c] == 1:\n            continue\n\n        starts_across = (c == 0 or grid[r, c - 1] == 1) and (c < grid_size - 1 and grid[r, c + 1] == 0)\n        starts_down = (r == 0 or grid[r - 1, c] == 1) and (r < grid_size - 1 and grid[r + 1, c] == 0)\n\n        if starts_across or starts_down:\n            numbers[(r, c)] = clue_num\n            clue_num += 1\n\n# Plot\nfig = go.Figure()\n\ncell_size = 1\nnumbered_set = set(numbers.keys())\n\nfor r in range(grid_size):\n    for c in range(grid_size):\n        x0, y0 = c * cell_size, (grid_size - 1 - r) * cell_size\n        x1, y1 = x0 + cell_size, y0 + cell_size\n\n        if grid[r, c] == 1:\n            fill_color = CELL_BLOCK\n        elif (r, c) in numbered_set:\n            fill_color = CELL_NUMBERED  # word-start: pure white\n        else:\n            fill_color = CELL_ENTRY  # plain entry: warm off-white\n\n        fig.add_shape(\n            type=\"rect\", x0=x0, y0=y0, x1=x1, y1=y1, fillcolor=fill_color, line={\"color\": INK_SOFT, \"width\": 1.5}\n        )\n\n# Thick outer border — traditional crosswords use a heavier frame to contain the grid\nfig.add_shape(\n    type=\"rect\",\n    x0=-0.05,\n    y0=-0.05,\n    x1=grid_size + 0.05,\n    y1=grid_size + 0.05,\n    fillcolor=\"rgba(0,0,0,0)\",\n    line={\"color\": INK_SOFT, \"width\": 3},\n)\n\n# Clue numbers in word-start cells\nfor (r, c), num in numbers.items():\n    x = c * cell_size + 0.07\n    y = (grid_size - 1 - r) * cell_size + cell_size - 0.07\n\n    fig.add_annotation(\n        x=x,\n        y=y,\n        text=str(num),\n        showarrow=False,\n        font={\"size\": 11, \"color\": INK, \"family\": \"Arial\"},\n        xanchor=\"left\",\n        yanchor=\"top\",\n    )\n\n# Style\nfig.update_layout(\n    title={\n        \"text\": \"crossword-basic · python · plotly · anyplot.ai\", \"font\": {\"size\": 16, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"\n    },\n    xaxis={\n        \"showgrid\": False,\n        \"zeroline\": False,\n        \"showticklabels\": False,\n        \"scaleanchor\": \"y\",\n        \"scaleratio\": 1,\n        \"range\": [-0.3, grid_size + 0.3],\n    },\n    yaxis={\"showgrid\": False, \"zeroline\": False, \"showticklabels\": False, \"range\": [-0.3, grid_size + 0.3]},\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    margin={\"l\": 50, \"r\": 50, \"t\": 90, \"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"}