/* * Copyright (c) Camden Dixie O'Brien * SPDX-License-Identifier: AGPL-3.0-only */ #define _POSIX_C_SOURCE 199309L #include #include #include #include #include #include #include #include #define MAZE_SIZE 24 #define GRID_SIZE (2 * MAZE_SIZE - 1) #define MARGIN 2 #define WALL_THICKNESS 1 #define WINDOW_SIZE (GRID_SIZE + 2 * (WALL_THICKNESS + MARGIN)) #define PX(x) ((x) << 3) #define GOAL (GRID_SIZE - 1) #define MAX_PATH_LENGTH (MAZE_SIZE * MAZE_SIZE) #define STACK_SIZE (MAZE_SIZE * MAZE_SIZE) typedef enum { LEFT, RIGHT, UP, DOWN } dir_t; typedef struct { int x, y; } vec2_t; typedef bool (*coord_pred_t)(vec2_t c, vec2_t im); typedef bool (*visit_fn_t)(vec2_t c, vec2_t im); typedef struct { bool is_path : 1; bool visited : 1; } cell_t; static const struct timespec gen_pause = { .tv_nsec = 5000000 }; static const struct timespec solve_pause = { .tv_nsec = 10000000 }; static const vec2_t steps[] = { [LEFT] = { .x = -2, .y = 0 }, [RIGHT] = { .x = 2, .y = 0 }, [UP] = { .x = 0, .y = -2 }, [DOWN] = { .x = 0, .y = 2 }, }; static Display *dpy; static Window window; static GC ctx; static cell_t maze[GRID_SIZE][GRID_SIZE]; static int bg_col, wall_col, visited_col; static void clear_path(vec2_t p) { const int margin_px = PX(MARGIN + WALL_THICKNESS); const int left = margin_px + PX(p.x); const int top = margin_px + PX(p.y); XFillRectangle(dpy, window, ctx, left, top, PX(1), PX(1)); XClearArea(dpy, window, left, top, PX(1), PX(1), false); XFlush(dpy); } static void draw_visited(vec2_t p) { const int margin_px = PX(MARGIN + WALL_THICKNESS); const int left = margin_px + PX(p.x); const int top = margin_px + PX(p.y); XFillRectangle(dpy, window, ctx, left, top, PX(1), PX(1)); XFlush(dpy); } static bool in_bounds(vec2_t p) { const bool valid_x = p.x >= 0 && p.x < GRID_SIZE; const bool valid_y = p.y >= 0 && p.y < GRID_SIZE; return valid_x && valid_y; } static bool finished_gen(vec2_t p) { vec2_t n; for (int i = 0; i < 4; ++i) { n.x = p.x + steps[i].x; n.y = p.y + steps[i].y; if (in_bounds(n) && !maze[n.x][n.y].is_path) return false; } return true; } static void generate(vec2_t p) { vec2_t stack[STACK_SIZE], *sp = stack; do { if (finished_gen(p)) { p = *(--sp); continue; } vec2_t n; do { dir_t d = rand() % 4; n.x = p.x + steps[d].x; n.y = p.y + steps[d].y; } while (!in_bounds(n) || maze[n.x][n.y].is_path); const vec2_t im = { .x = (p.x + n.x) / 2, .y = (p.y + n.y) / 2, }; maze[im.x][im.y].is_path = maze[n.x][n.y].is_path = true; clear_path(im); clear_path(n); *sp++ = p; p = n; nanosleep(&gen_pause, NULL); } while (sp != stack); } static void solve(vec2_t p, vec2_t *sp, vec2_t **end) { *sp++ = p; while (1) { if (GOAL == p.x && GOAL == p.y) { *sp++ = p; *end = sp; return; } vec2_t n, im; bool got_n = false; for (int i = 0; i < 4; ++i) { n.x = p.x + steps[i].x; n.y = p.y + steps[i].y; if (!in_bounds(n)) continue; im.x = (p.x + n.x) / 2; im.y = (p.y + n.y) / 2; if (maze[im.x][im.y].is_path && !maze[n.x][n.y].visited) { got_n = true; break; } } if (!got_n) { p = *(--sp); continue; } maze[im.x][im.y].visited = maze[n.x][n.y].visited = true; *sp++ = p; p = n; } } int main(void) { // Seed random number generation from time struct timeval tv; gettimeofday(&tv, NULL); srand(tv.tv_usec); XEvent evt; dpy = XOpenDisplay(NULL); assert(dpy); // Create window and configure graphics context wall_col = BlackPixel(dpy, DefaultScreen(dpy)); bg_col = WhitePixel(dpy, DefaultScreen(dpy)); window = XCreateSimpleWindow( dpy, DefaultRootWindow(dpy), 0, 0, PX(WINDOW_SIZE), PX(WINDOW_SIZE), 0, bg_col, bg_col); Atom del = XInternAtom(dpy, "WM_DELETE_WINDOW", false); XSetWMProtocols(dpy, window, &del, 1); ctx = DefaultGC(dpy, DefaultScreen(dpy)); // Create colormap and allocate colour for visited cells Colormap cm = XCreateColormap( dpy, window, DefaultVisual(dpy, DefaultScreen(dpy)), AllocNone); XColor xcol = { .red = 55555, .green = 10000, .blue = 10000 }; XAllocColor(dpy, cm, &xcol); visited_col = xcol.pixel; // Map window XSelectInput(dpy, window, StructureNotifyMask); XMapWindow(dpy, window); do XNextEvent(dpy, &evt); while (MapNotify != evt.type); while (1) { // Draw black box for walls XClearWindow(dpy, window); XSetForeground(dpy, ctx, wall_col); XFillRectangle( dpy, window, ctx, PX(MARGIN), PX(MARGIN), PX(GRID_SIZE + 2), PX(GRID_SIZE + 2)); const vec2_t exit = { GOAL + 1, GOAL }; clear_path(exit); XFlush(dpy); // Generate memset(&maze, 0, sizeof(maze)); const vec2_t gen_start = { GOAL, GOAL }; maze[GOAL][GOAL].is_path = true; clear_path(gen_start); generate(gen_start); // Solve const vec2_t solve_start = { 0, 0 }; vec2_t path[MAX_PATH_LENGTH], *path_end; maze[0][0].visited = true; solve(solve_start, path, &path_end); sleep(1); // Draw solution path XSetForeground(dpy, ctx, visited_col); const vec2_t *prev = &solve_start; for (const vec2_t *p = path; p < path_end; ++p) { const vec2_t im = { .x = (prev->x + p->x) / 2, .y = (prev->y + p->y) / 2, }; draw_visited(*p); draw_visited(im); nanosleep(&solve_pause, NULL); prev = p; } draw_visited(exit); sleep(1); } // Wait for window exit bool is_del = false; do { XNextEvent(dpy, &evt); if (ClientMessage == evt.type) is_del = (unsigned long)evt.xclient.data.l[0] == del; } while (!is_del); XCloseDisplay(dpy); return EXIT_SUCCESS; }