/* * 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 #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) 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 pause = { .tv_nsec = 5000000 }; 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 draw_walls(void) { XSetForeground(dpy, ctx, wall_col); XFillRectangle( dpy, window, ctx, PX(MARGIN), PX(MARGIN), PX(GRID_SIZE + 2), PX(WALL_THICKNESS)); XFillRectangle( dpy, window, ctx, PX(MARGIN), PX(MARGIN + WALL_THICKNESS + GRID_SIZE), PX(GRID_SIZE + 2), PX(WALL_THICKNESS)); XFillRectangle( dpy, window, ctx, PX(MARGIN), PX(MARGIN + WALL_THICKNESS), PX(WALL_THICKNESS), PX(GRID_SIZE)); XFillRectangle( dpy, window, ctx, PX(MARGIN + WALL_THICKNESS + GRID_SIZE), PX(MARGIN + WALL_THICKNESS), PX(WALL_THICKNESS), PX(GRID_SIZE - 1)); XFlush(dpy); } static void draw_maze(void) { const int margin_px = PX(MARGIN + WALL_THICKNESS); XClearArea( dpy, window, margin_px, margin_px, PX(GRID_SIZE), PX(GRID_SIZE), false); for (int x = 0; x < GRID_SIZE; ++x) { for (int y = 0; y < GRID_SIZE; ++y) { if (!maze[x][y].is_path) XSetForeground(dpy, ctx, wall_col); else if (maze[x][y].visited) XSetForeground(dpy, ctx, visited_col); else continue; const int left = margin_px + PX(x); const int top = margin_px + PX(y); XFillRectangle(dpy, window, ctx, left, top, PX(1), PX(1)); } } XFlush(dpy); } static bool random_walk(coord_pred_t should_visit, visit_fn_t visit_fn, vec2_t start) { draw_maze(); nanosleep(&pause, NULL); dir_t visit[] = { LEFT, RIGHT, UP, DOWN }; for (int i = 3; i > 0; --i) { const int r = rand() % (i + 1); const dir_t tmp = visit[r]; visit[r] = visit[i]; visit[i] = tmp; } vec2_t next, im; for (int i = 0; i < 4; ++i) { next.x = start.x + steps[visit[i]].x; next.y = start.y + steps[visit[i]].y; im.x = (start.x + next.x) / 2; im.y = (start.y + next.y) / 2; const bool x_in_bounds = next.x >= 0 && next.x < GRID_SIZE; const bool y_in_bounds = next.y >= 0 && next.y < GRID_SIZE; if (x_in_bounds && y_in_bounds && should_visit(next, im)) { if (visit_fn(next, im) || random_walk(should_visit, visit_fn, next)) return true; } } return false; } static bool is_wall(vec2_t c, vec2_t im) { (void)im; return !maze[c.x][c.y].is_path; } static bool generation_visit(vec2_t c, vec2_t im) { maze[c.x][c.y].is_path = true; maze[im.x][im.y].is_path = true; return false; } static bool accessible_and_unvisited(vec2_t c, vec2_t im) { return maze[im.x][im.y].is_path && !maze[c.x][c.y].visited; } static bool solve_visit(vec2_t c, vec2_t im) { maze[c.x][c.y].visited = true; maze[im.x][im.y].visited = true; return GOAL == c.x && GOAL == c.y; } 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); draw_walls(); // Generate memset(&maze, 0, sizeof(maze)); const vec2_t gen_start = { GOAL, GOAL }; maze[GOAL][GOAL].is_path = true; random_walk(is_wall, generation_visit, gen_start); // Solve const vec2_t solve_start = { 0, 0 }; maze[0][0].visited = true; random_walk(accessible_and_unvisited, solve_visit, solve_start); // Draw exit path draw_maze(); const int x = PX(MARGIN + GRID_SIZE + 1), y = PX(MARGIN + GRID_SIZE); XSetForeground(dpy, ctx, visited_col); XFillRectangle(dpy, window, ctx, x, y, PX(1), PX(1)); XFlush(dpy); // 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; }