{"spec_id":"crossword-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' crossword-basic: Crossword Puzzle Grid\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 89/100 | Created: 2026-05-20\n\nlibrary(ggplot2)\nlibrary(ragg)\n\n# Theme tokens\nTHEME    <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG  <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nINK      <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\n\n# 13x13 crossword grid (1 = black cell, 0 = entry cell)\n# Traditional 180-degree rotational symmetry\nn <- 13\ngrid_data <- matrix(c(\n  0,0,0,1,1,0,0,0,1,1,0,0,0,\n  0,0,0,0,0,0,1,0,0,0,0,0,0,\n  1,0,0,0,0,0,0,0,0,0,0,0,1,\n  0,0,0,0,1,0,0,0,1,0,0,0,0,\n  0,1,0,0,0,0,0,0,0,0,0,1,0,\n  0,0,0,1,0,0,0,0,0,1,0,0,0,\n  0,0,1,0,1,0,0,0,1,0,1,0,0,\n  0,0,0,1,0,0,0,0,0,1,0,0,0,\n  0,1,0,0,0,0,0,0,0,0,0,1,0,\n  0,0,0,0,1,0,0,0,1,0,0,0,0,\n  1,0,0,0,0,0,0,0,0,0,0,0,1,\n  0,0,0,0,0,0,1,0,0,0,0,0,0,\n  0,0,0,1,1,0,0,0,1,1,0,0,0\n), nrow = n, ncol = n, byrow = TRUE)\n\n# Cell data frame — row 1 at top, so plot y = n + 1 - row\nrows_idx <- rep(1:n, each = n)\ncols_idx <- rep(1:n, times = n)\n\ndf <- data.frame(\n  col        = cols_idx,\n  y          = n + 1 - rows_idx,\n  fill_color = ifelse(\n    as.logical(grid_data[cbind(rows_idx, cols_idx)]),\n    \"black\", \"white\"\n  )\n)\n\n# Assign clue numbers in reading order (top-left to bottom-right)\nnum_list <- list()\nclue_n   <- 1\nfor (r in 1:n) {\n  for (c in 1:n) {\n    is_across <- grid_data[r, c] == 0 &&\n      (c == 1 || grid_data[r, c - 1] == 1) &&\n      c < n && grid_data[r, c + 1] == 0\n    is_down <- grid_data[r, c] == 0 &&\n      (r == 1 || grid_data[r - 1, c] == 1) &&\n      r < n && grid_data[r + 1, c] == 0\n    if (is_across || is_down) {\n      num_list[[length(num_list) + 1]] <- data.frame(\n        col   = c,\n        y     = n + 1 - r,\n        label = as.character(clue_n)\n      )\n      clue_n <- clue_n + 1\n    }\n  }\n}\nnum_df <- do.call(rbind, num_list)\n\n# Plot\np <- ggplot(df, aes(x = col, y = y)) +\n  geom_tile(aes(fill = fill_color), color = \"#333333\", linewidth = 0.3) +\n  scale_fill_identity() +\n  geom_text(\n    data  = num_df,\n    aes(x = col - 0.40, y = y + 0.38, label = label),\n    size  = 2.0,\n    color = \"black\",\n    hjust = 0,\n    vjust = 1\n  ) +\n  annotate(\n    \"rect\",\n    xmin = 0.5, xmax = n + 0.5,\n    ymin = 0.5, ymax = n + 0.5,\n    fill      = NA,\n    color     = INK,\n    linewidth = 0.7\n  ) +\n  coord_fixed(ratio = 1, clip = \"off\") +\n  scale_x_continuous(expand = expansion(add = 0.25)) +\n  scale_y_continuous(expand = expansion(add = 0.25)) +\n  labs(\n    title    = \"crossword-basic · r · ggplot2 · anyplot.ai\",\n    subtitle = \"13×13 American-style crossword · 53 entries\"\n  ) +\n  theme_void() +\n  theme(\n    plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG),\n    plot.title = element_text(\n      color  = INK,\n      size   = 12,\n      hjust  = 0.5,\n      margin = margin(t = 8, b = 4)\n    ),\n    plot.subtitle = element_text(\n      color  = INK_SOFT,\n      size   = 8,\n      hjust  = 0.5,\n      margin = margin(b = 12)\n    ),\n    plot.margin = margin(12, 15, 12, 15)\n  )\n\n# Save\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 6,\n  height   = 6,\n  units    = \"in\",\n  dpi      = 400\n)\n"}