{"spec_id":"maze-printable","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' maze-printable: Printable Maze Puzzle\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 90/100 | Created: 2026-09-05\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(ragg)\n\nset.seed(42)\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_MUTED <- if (THEME == \"light\") \"#6B6A63\" else \"#A8A79F\"\nIMPRINT_PALETTE <- c(\n  \"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\",\n  \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"\n)\n\n# --- Maze generation (randomized depth-first backtracker) ---------------\n# A spanning tree over the cell grid guarantees exactly one path between\n# any two cells, so start and goal have a single unique solution route.\nmaze_width  <- 22\nmaze_height <- 22\n\neast_open  <- matrix(FALSE, nrow = maze_width, ncol = maze_height)\nsouth_open <- matrix(FALSE, nrow = maze_width, ncol = maze_height)\nvisited    <- matrix(FALSE, nrow = maze_width, ncol = maze_height)\n\nvisited[1, 1] <- TRUE\nstack <- list(list(x = 0, y = 0))\n\nwhile (length(stack) > 0) {\n  cur <- stack[[length(stack)]]\n  cx  <- cur$x\n  cy  <- cur$y\n\n  neighbors <- list()\n  if (cx > 0 && !visited[cx, cy + 1]) {\n    neighbors[[length(neighbors) + 1]] <- list(x = cx - 1, y = cy, dir = \"W\")\n  }\n  if (cx < maze_width - 1 && !visited[cx + 2, cy + 1]) {\n    neighbors[[length(neighbors) + 1]] <- list(x = cx + 1, y = cy, dir = \"E\")\n  }\n  if (cy > 0 && !visited[cx + 1, cy]) {\n    neighbors[[length(neighbors) + 1]] <- list(x = cx, y = cy - 1, dir = \"N\")\n  }\n  if (cy < maze_height - 1 && !visited[cx + 1, cy + 2]) {\n    neighbors[[length(neighbors) + 1]] <- list(x = cx, y = cy + 1, dir = \"S\")\n  }\n\n  if (length(neighbors) > 0) {\n    pick <- neighbors[[sample.int(length(neighbors), 1)]]\n    nx <- pick$x\n    ny <- pick$y\n\n    if (pick$dir == \"E\") east_open[cx + 1, cy + 1] <- TRUE\n    if (pick$dir == \"W\") east_open[nx + 1, ny + 1] <- TRUE\n    if (pick$dir == \"S\") south_open[cx + 1, cy + 1] <- TRUE\n    if (pick$dir == \"N\") south_open[nx + 1, ny + 1] <- TRUE\n\n    visited[nx + 1, ny + 1] <- TRUE\n    stack[[length(stack) + 1]] <- list(x = nx, y = ny)\n  } else {\n    stack[[length(stack)]] <- NULL\n  }\n}\n\n# --- Wall segments --------------------------------------------------------\n# Row 0 sits at the top of the print, so its y-coordinate is the tallest.\nvertical_walls <- expand.grid(col = 0:(maze_width - 2), row = 0:(maze_height - 1)) |>\n  filter(!east_open[cbind(col + 1, row + 1)]) |>\n  transmute(x = col + 1, xend = col + 1, y = maze_height - row - 1, yend = maze_height - row)\n\nhorizontal_walls <- expand.grid(col = 0:(maze_width - 1), row = 0:(maze_height - 2)) |>\n  filter(!south_open[cbind(col + 1, row + 1)]) |>\n  transmute(x = col, xend = col + 1, y = maze_height - row - 1, yend = maze_height - row - 1)\n\n# Outer border, with a one-cell gap for the entrance (top-left) and exit\n# (bottom-right) so the puzzle can be entered and exited with a pen.\nborder_walls <- tibble::tibble(\n  x    = c(1,           0,               0,           maze_width),\n  xend = c(maze_width,  maze_width - 1,  0,           maze_width),\n  y    = c(maze_height, 0,               0,           0),\n  yend = c(maze_height, 0,               maze_height, maze_height)\n)\n\nwalls <- bind_rows(vertical_walls, horizontal_walls, border_walls)\n\n# --- Start / goal markers --------------------------------------------------\nmarkers <- tibble::tibble(\n  label = c(\"S\", \"G\"),\n  x     = c(0.5, maze_width - 0.5),\n  y     = c(maze_height - 0.5, 0.5),\n  color = c(IMPRINT_PALETTE[1], IMPRINT_PALETTE[2])\n)\n\n# --- Plot --------------------------------------------------------------\ntitle_text <- \"maze-printable · r · ggplot2 · anyplot.ai\"\nsubtitle_text <- sprintf(\n  \"%d × %d grid · exactly one path from start to goal\",\n  maze_width, maze_height\n)\n\np <- ggplot() +\n  geom_segment(\n    data = walls, aes(x = x, xend = xend, y = y, yend = yend),\n    color = INK, linewidth = 1.3, lineend = \"square\", linejoin = \"mitre\"\n  ) +\n  geom_point(\n    data = markers, aes(x = x, y = y, color = color),\n    size = 9, show.legend = FALSE\n  ) +\n  geom_text(\n    data = markers, aes(x = x, y = y, label = label),\n    color = PAGE_BG, size = 4.2, fontface = \"bold\"\n  ) +\n  scale_color_identity() +\n  coord_fixed(\n    xlim = c(-0.5, maze_width + 0.5),\n    ylim = c(-0.5, maze_height + 0.5),\n    expand = FALSE\n  ) +\n  labs(title = title_text, subtitle = subtitle_text) +\n  theme_void(base_size = 8) +\n  theme(\n    plot.background  = element_rect(fill = PAGE_BG, color = PAGE_BG),\n    panel.background = element_rect(fill = PAGE_BG, color = NA),\n    plot.title       = element_text(color = INK, size = 12, hjust = 0.5, margin = margin(b = 6)),\n    plot.subtitle    = element_text(color = INK_MUTED, size = 8, hjust = 0.5, margin = margin(b = 10)),\n    plot.margin      = margin(t = 20, r = 20, b = 20, l = 20)\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"}