/* * Copyright (c) Camden Dixie O'Brien * SPDX-License-Identifier: AGPL-3.0-only */ #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 { bool cells[GRID_SIZE][GRID_SIZE]; } maze_t; typedef struct { int x, y; } vec2_t; 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 void generate_step(maze_t *m, int x, int y) { m->cells[x][y] = true; if (GOAL == x && GOAL == y) return; 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; } for (int i = 0; i < 4; ++i) { const int xp = x + steps[visit[i]].x; const int yp = y + steps[visit[i]].y; const bool x_in_bounds = xp >= 0 && xp < GRID_SIZE; const bool y_in_bounds = yp >= 0 && yp < GRID_SIZE; if (x_in_bounds && y_in_bounds && !m->cells[xp][yp]) { const int xi = (x + xp) / 2; const int yi = (y + yp) / 2; m->cells[xi][yi] = true; generate_step(m, xp, yp); } } } static void generate_maze(maze_t *m) { memset(m, 0, sizeof(maze_t)); generate_step(m, 0, 0); } static void draw_maze(Display *dpy, Window w, GC gc, const maze_t *m) { // Draw walls XFillRectangle( dpy, w, gc, PX(MARGIN), PX(MARGIN), PX(GRID_SIZE + 2), PX(WALL_THICKNESS)); XFillRectangle( dpy, w, gc, PX(MARGIN), PX(MARGIN + WALL_THICKNESS + GRID_SIZE), PX(GRID_SIZE + 2), PX(WALL_THICKNESS)); XFillRectangle( dpy, w, gc, PX(MARGIN), PX(MARGIN + WALL_THICKNESS + 1), PX(WALL_THICKNESS), PX(GRID_SIZE - 1)); XFillRectangle( dpy, w, gc, PX(MARGIN + WALL_THICKNESS + GRID_SIZE), PX(MARGIN + WALL_THICKNESS), PX(WALL_THICKNESS), PX(GRID_SIZE)); // Draw cells for (int x = 0; x < GRID_SIZE; ++x) { for (int y = 0; y < GRID_SIZE; ++y) { if (m->cells[x][y]) continue; const int left = PX(MARGIN + WALL_THICKNESS + x); const int top = PX(MARGIN + WALL_THICKNESS + y); XFillRectangle(dpy, w, gc, left, top, PX(1), PX(1)); } } XFlush(dpy); } int main(void) { // Seed random number generation from time struct timeval tv; gettimeofday(&tv, NULL); srand(tv.tv_usec); XEvent evt; Display *dpy = XOpenDisplay(NULL); assert(dpy); // Create window and configure graphics context const int black = BlackPixel(dpy, DefaultScreen(dpy)); const int white = WhitePixel(dpy, DefaultScreen(dpy)); Window w = XCreateSimpleWindow( dpy, DefaultRootWindow(dpy), 0, 0, PX(WINDOW_SIZE), PX(WINDOW_SIZE), 0, white, white); Atom del = XInternAtom(dpy, "WM_DELETE_WINDOW", false); XSetWMProtocols(dpy, w, &del, 1); GC gc = DefaultGC(dpy, DefaultScreen(dpy)); XSetForeground(dpy, gc, black); // Map window XSelectInput(dpy, w, StructureNotifyMask); XMapWindow(dpy, w); do XNextEvent(dpy, &evt); while (MapNotify != evt.type); // Generate and draw maze maze_t m; generate_maze(&m); draw_maze(dpy, w, gc, &m); // 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; }