// Generate a pac-man style maze. // Code is written in a style that should make it trivial to convert to Scratch, // i.e. no functions, no local variables, while loops instead of for loops, no <=, >=, or !=, // and all arrays are 1 dimensional, based at 1. (So the C implementation never accesses element 0) #include <stdio.h> #include <stdlib.h> // malloc, random, EXIT_SUCCESS #define repeat_until(x) while (!(x)) // Constants: int TARGET_HEIGHT, TARGET_WIDTH, TILE_DIMENSION, MIDDLE, WIDTH, HEIGHT, TETRIS_WIDTH, PACWIDTH, PACHEIGHT, MAZE_HEIGHT, LINE_WIDTH, LINEHEIGHT, TRUE, FALSE; // Scalars: int this_row, this_len, next_tile, first_tile, last_tile, dead_block, row, col, t, i, j, nibble, bit, k, iterate, paths, c, breakflag, left, right, returnval, f1; // Const arrays: #define TILEMAX 32 unsigned int tile[TILEMAX] = { 0, 142, 3208, 1100, 226, 46, 3140, 2188, 232, 79, 35976, 19524, 242, 47, 19524, 35016, 244, 1252, 1252, 78, 2248, 1220, 228, 140, 200, 196, 76, 14, 2184, 12, 136, 204, }; #ifdef DEBUG static char tc[]=" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+`-={}|[]\\:\";'<>,./"; #endif /* DEBUG */ // Arrays: long *tetris; int *maze; char *line; #define FLOOR(x) (x) // a hint for Scratch translation int pick_random_int_between(int low, int high) { // calls to this also map to a Scratch primitive return low + random()%(high-low+1); } void place_tile(int tileno, int r, int c) { t = tile[tileno+1]; next_tile += 1; row = 0;; repeat_until (!(row < 4)) { nibble = t % 16; t = FLOOR(t / 16); col = 0; repeat_until (!(col < 4)) { bit = (nibble > 7); nibble = (nibble+nibble) % 16; if (bit) tetris[((col+c)*TETRIS_WIDTH) + (row+r) + 1] = next_tile; col += 1; } row += 1; } } void can_place(int tileno, int r, int c) { t = tile[tileno+1]; returnval = TRUE; row = 0; repeat_until (!(row < 4 && returnval)) { nibble = t % 16; t = FLOOR(t / 16); col = 0; repeat_until (!(col < 4 && returnval)) { bit = (nibble > 7); nibble = (nibble+nibble) % 16; if (bit && ( (!(tetris[((col+c)*TETRIS_WIDTH) + (row+r) + 1] == 0)) || ((row+r) > (HEIGHT-1)) || ((c+col) > (WIDTH-1)) ) ) { returnval = FALSE; } col += 1; } row += 1; } } void debug(void) { #ifdef DEBUG i = HEIGHT-1; repeat_until (!(i+1 > 0)) { j = 0; repeat_until (!(j < WIDTH)) { putchar(tc[1+tetris[((j)*TETRIS_WIDTH) + (i) + 1]]); j += 1; } putchar('\n'); i -= 1; } putchar('\n'); #endif /* DEBUG */ } void drop(int tileno, int r, int c) { can_place(tileno, r, c); f1 = returnval; can_place(tileno, r-1, c); if (f1 && (r==0 || !returnval)) { place_tile(tileno, r, c); } else { if (r>0) drop(tileno, r-1, c); } } void play_tetris(void) { next_tile = 0; // used by 'place_tile' called from 'drop' i = 0; repeat_until (!(i < WIDTH)) { j = 0; repeat_until (!(j < HEIGHT+TILE_DIMENSION+1)) { tetris[((i)*TETRIS_WIDTH) + (j) + 1] = 0; j += 1; } i += 1; } drop(30, HEIGHT+1, FLOOR(WIDTH / 2)); // jail. Should also handle tunnels here. i = 0; repeat_until (!(i < 1000)) { drop(pick_random_int_between(first_tile, last_tile-1), HEIGHT, pick_random_int_between(0,WIDTH-1)); i += 1; } } void add(int this, int next) { line[((this_row)*LINE_WIDTH) + (this_len) + 1]= this; this_len += 1; line[((this_row)*LINE_WIDTH) + (this_len) + 1]= next; this_len += 1; line[((this_row)*LINE_WIDTH) + (this_len) + 1]= '\0'; } void init_row(void) { this_row = -1; this_len = 0; } void next_row(void) { this_row += 1; line[((this_row)*LINE_WIDTH) + (0) + 1] = ' '; line[((this_row)*LINE_WIDTH) + (1) + 1] = '\0'; this_len = 1; } void convert_to_maze(void) { row = 1; j = 0; repeat_until (!(j < WIDTH)) { col = 1; i = HEIGHT-1; repeat_until (!(i+1 > 0)) { maze[((col)*MAZE_HEIGHT ) + (row) + 1] = tetris[((j)*TETRIS_WIDTH) + (i) + 1]; if (maze[((col)*MAZE_HEIGHT ) + (row) + 1] == 0) maze[((col)*MAZE_HEIGHT ) + (row) + 1] = dead_block; col += 1; i -= 1; } i = 1; repeat_until (!(i < HEIGHT)) { maze[((col)*MAZE_HEIGHT ) + (row) + 1] = tetris[((j)*TETRIS_WIDTH) + (i) + 1]; if (maze[((col)*MAZE_HEIGHT ) + (row) + 1] == 0) maze[((col)*MAZE_HEIGHT ) + (row) + 1] = dead_block; col += 1; i += 1; } row += 1; j += 1; } } void debug_maze(void) { #ifdef DEBUG i = PACHEIGHT-2; repeat_until (!(i > 0)) { j = 1; repeat_until (!(j < PACWIDTH-1)) { putchar(tc[1+maze[((j)*MAZE_HEIGHT ) + (i) + 1]]); j += 1; } putchar('\n'); i -= 1; } putchar('\n'); #endif /* DEBUG */ } void cell(int above_below, int i, int j) { // This is messy but I don't have the time to work it out properly and // do a neater implementation. Sorry. There's a minor bug that is // worked around by 'trim_junk()' if (above_below < 0) { if ((i+1 == PACWIDTH) && (j+1 == PACHEIGHT)) { add('#', ' '); } else { if (!(maze[((i)*MAZE_HEIGHT ) + (j-1) + 1] == maze[((i)*MAZE_HEIGHT ) + (j) + 1])) { if ((maze[((i)*MAZE_HEIGHT ) + (j-1) + 1] == 0) && (maze[((i)*MAZE_HEIGHT ) + (j) + 1] == dead_block)) { if ((maze[((i)*MAZE_HEIGHT ) + (j) + 1] == dead_block) && (maze[((i-1)*MAZE_HEIGHT ) + (j) + 1] == 0)) { add(' ', ' '); } else { add('#', ' '); } } else { if ((maze[((i)*MAZE_HEIGHT ) + (j) + 1] == 0) && (maze[((i)*MAZE_HEIGHT ) + (j-1) + 1] == dead_block)) { if ((maze[((i-1)*MAZE_HEIGHT ) + (j-1) + 1] == dead_block) || (maze[((i)*MAZE_HEIGHT ) + (j-1) + 1] == 0)) { add(' ', ' '); } else { if ((maze[((i-1)*MAZE_HEIGHT ) + (j-1) + 1] == 0) && (maze[((i-1)*MAZE_HEIGHT ) + (j) + 1] == 0)) { add(' ', ' '); } else { add('#', ' '); } } } else { add('#', '#'); } } } else { if ((!(maze[((i-1)*MAZE_HEIGHT ) + (j) + 1] == maze[((i)*MAZE_HEIGHT ) + (j) + 1]))) { if ((maze[((i)*MAZE_HEIGHT ) + (j) + 1] == dead_block) && (maze[((i-1)*MAZE_HEIGHT ) + (j) + 1] == 0)) { add(' ', ' '); } else { add('#', ' '); } } else { if ( (maze[((i)*MAZE_HEIGHT ) + (j) + 1] == maze[((i)*MAZE_HEIGHT ) + (j-1) + 1]) && (!(maze[((i-1)*MAZE_HEIGHT ) + (j-1) + 1] == maze[((i)*MAZE_HEIGHT ) + (j) + 1]))) { add('#', ' '); } else { add(' ', ' '); } } } } } else if (above_below == 0) { if (!(maze[((i-1)*MAZE_HEIGHT ) + (j) + 1] == maze[((i)*MAZE_HEIGHT ) + (j) + 1])) { if ((maze[((i)*MAZE_HEIGHT ) + (j) + 1] == dead_block) && (maze[((i-1)*MAZE_HEIGHT ) + (j) + 1] == 0)) { add(' ', ' '); } else { add('#', ' '); } } else { add(' ', ' '); } } } void symmetry(int i) { right = MIDDLE; left = right; repeat_until (!(left+1 > 0)) { line[((i)*LINE_WIDTH) + (right) + 1] = line[((i)*LINE_WIDTH) + (left) + 1]; right += 1; left -= 1; } line[((i)*LINE_WIDTH) + (right) + 1] = '\0'; // end of line } void gen_text_maze(void) { init_row(); i = PACHEIGHT-1; repeat_until (!(i > 0)) { k = 0; repeat_until (!(k+1 > -1)) { next_row(); j = 1; repeat_until (!(j < PACWIDTH)) { cell(k,j,i); j += 1; } k -= 1; } i -= 1; } i = 0; repeat_until (!(i < TARGET_WIDTH+1)) { line[((TARGET_HEIGHT+1)*LINE_WIDTH) + (i) + 1] = ' '; i += 1; } line[((TARGET_HEIGHT+1)*LINE_WIDTH) + (TARGET_WIDTH+1) + 1] = '\0'; i = 0; repeat_until (!(i < PACHEIGHT*2 )) { symmetry(i); i += 1; } } void trim_central_column(void) { // Trim any central '#'s that don't connect above or below (OK if connecting to left and right) iterate = 0; repeat_until (!(iterate < PACHEIGHT*2-1)) { i = 1; repeat_until (!(i < PACHEIGHT*2-1)) { if ( line[((i)*LINE_WIDTH) + (MIDDLE) + 1] == '#' && line[((i)*LINE_WIDTH) + (MIDDLE-1) + 1] != '#' && ( (!(line[((i-1)*LINE_WIDTH) + (MIDDLE) + 1] == '#')) || (!(line[((i+1)*LINE_WIDTH) + (MIDDLE) + 1] == '#')) ) ) { line[((i)*LINE_WIDTH) + (MIDDLE) + 1] = ' '; } i += 1; } iterate += 1; } } void too_narrow(void) { // Reject any that don't have >= 3 #'s on any col. Should we check rows as well? i = 1; paths = 4; repeat_until (!(i < (PACHEIGHT-1)*2 && paths > 3)) { #ifdef DEBUG fprintf(stdout, "%03d: ", i); #endif /* DEBUG */ paths = 0; j = 1; repeat_until (!(j < 1+TARGET_WIDTH)) { #ifdef DEBUG putchar(line[((i)*LINE_WIDTH) + (j) + 1]); #endif /* DEBUG */ if (line[((i)*LINE_WIDTH) + (j) + 1] > ' ') paths += 1; // # or @ etc j += 1; } #ifdef DEBUG putchar('\n'); #endif /* DEBUG */ i += 1; } returnval = (paths < 4); } void wide_path(void) { // Reject if there is a path all the way across returnval = FALSE; i = 1; repeat_until (!(i < 1+TARGET_HEIGHT)) { paths = 0; j = 1; repeat_until (!(j < 1+TARGET_WIDTH)) { if (line[((i)*LINE_WIDTH) + (j) + 1] != ' ') paths += 1; // # or @ etc j += 1; } if (paths == TARGET_WIDTH) returnval = TRUE; i += 1; } if (!returnval) { // or all the way from top to bottom j = 1; repeat_until (!(j < 1+TARGET_WIDTH)) { paths = 0; i = 1; repeat_until (!(i < 1+TARGET_HEIGHT)) { if (line[((i)*LINE_WIDTH) + (j) + 1] != ' ') paths += 1; // # or @ etc i += 1; } if (paths == TARGET_HEIGHT) returnval = TRUE; j += 1; } if (!returnval) { // Reject if too many paths joining left to right paths = 0; i = 1; repeat_until (!(i < TARGET_HEIGHT)) { // check the logic of this, since I had got the 'too narrow' test wrong, and it's similar if (line[((i)*LINE_WIDTH) + (MIDDLE) + 1] != ' ' && line[((i)*LINE_WIDTH) + (MIDDLE-1) + 1] != ' ') paths += 1; // # or @ etc i += 1; } if (paths > 5) returnval = TRUE; } } } void small_loops(void) { // Reject squares (small loops): returnval = FALSE; i = 2; repeat_until (!(i < PACHEIGHT*2-2)) { j = 1; repeat_until (!(j < 1+TARGET_WIDTH)) { if (line[((i)*LINE_WIDTH) + (j) + 1] == ' ') { if (line[((i-1)*LINE_WIDTH) + (j-1) + 1] == '#') { if (line[((i-1)*LINE_WIDTH) + (j) + 1] == '#') { if (line[((i-1)*LINE_WIDTH) + (j+1) + 1] == '#') { if (line[((i)*LINE_WIDTH) + (j-1) + 1] == '#') { if (line[((i)*LINE_WIDTH) + (j+1) + 1] == '#') { if (line[((i+1)*LINE_WIDTH) + (j-1) + 1] == '#') { if (line[((i+1)*LINE_WIDTH) + (j) + 1] == '#') { if (line[((i+1)*LINE_WIDTH) + (j+1) + 1] == '#') { returnval = TRUE; #ifdef DEBUG //fprintf(stdout, "too small\n"); #endif } } } } } } } } } j += 1; } i += 1; } } void trim_junk(void) { i = 2; repeat_until (!(i < PACHEIGHT*2-2)) { j = 1; repeat_until (!(j < 1+TARGET_WIDTH)) { if (line[((i)*LINE_WIDTH) + (j) + 1] == '#') { if (line[((i-1)*LINE_WIDTH) + (j-1) + 1] == ' ') { if (line[((i-1)*LINE_WIDTH) + (j) + 1] == ' ') { if (line[((i-1)*LINE_WIDTH) + (j+1) + 1] == ' ') { if (line[((i)*LINE_WIDTH) + (j-1) + 1] == ' ') { if (line[((i)*LINE_WIDTH) + (j+1) + 1] == ' ') { if (line[((i+1)*LINE_WIDTH) + (j-1) + 1] == ' ') { if (line[((i+1)*LINE_WIDTH) + (j) + 1] == ' ') { if (line[((i+1)*LINE_WIDTH) + (j+1) + 1] == ' ') { line[((i)*LINE_WIDTH) + (j) + 1] = ' '; } } } } } } } } } j += 1; } i += 1; } } void bad_spawn_point(void) { // While we're at it, set up the jail exit breakflag = FALSE; i = FLOOR(TARGET_HEIGHT / 2); repeat_until (!((i > 0) && !breakflag)) { if (line[((i)*LINE_WIDTH) + (MIDDLE) + 1] == '#') { line[((i+1)*LINE_WIDTH) + (MIDDLE) + 1] = 'V'; // or maybe place this on the line above? if (line[((i-1)*LINE_WIDTH) + (MIDDLE) + 1] == '#') returnval = TRUE; breakflag = TRUE; } i -= 1; } if (!returnval) { // must spawn below jail breakflag = FALSE; returnval = FALSE; i = FLOOR(TARGET_HEIGHT / 2)+4; repeat_until (!((i < (PACHEIGHT-1)*2) && !breakflag)) { if (line[((i)*LINE_WIDTH) + (MIDDLE) + 1] == '#') { line[((i)*LINE_WIDTH) + (MIDDLE) + 1] = '@'; if (line[((i-1)*LINE_WIDTH) + (MIDDLE) + 1] == '#') returnval = TRUE; if (line[((i+1)*LINE_WIDTH) + (MIDDLE) + 1] == '#') returnval = TRUE; breakflag = TRUE; } i += 1; } if (!returnval) { // don't spawn on bottom line returnval = TRUE; i = i+1; repeat_until (!(i < (PACHEIGHT-1)*2)) { if (line[((i)*LINE_WIDTH) + (MIDDLE) + 1] == '#') returnval = FALSE; i += 1; } } } } void draw_maze(void) { printf(" 01234567890123456789012345\n"); i = 0; repeat_until (!(i < PACHEIGHT*2-1)) { printf("%03d: |%s| ", i, &line[((i)*LINE_WIDTH) + (0) + 1]); // has to be handled differently in Scratch anyway j = 0; repeat_until (!(j < 1+TARGET_WIDTH)) { c = line[((i)*LINE_WIDTH) + (j) + 1]; putchar(c); if (c == ' ') { putchar(c); } else { c = line[((i)*LINE_WIDTH) + (j+1) + 1]; if (c == ' ') { putchar(c); } else { putchar('#'); // hack for spawn point } } j += 1; } putchar('\n'); i += 1; } putchar('\n'); } void try_one(void) { play_tetris(); debug(); convert_to_maze(); debug_maze(); gen_text_maze(); trim_junk(); trim_central_column(); } int main(int argc, char **argv) { FALSE = (0==1); TRUE = (0==0); // Constants: TARGET_HEIGHT = 19; TARGET_WIDTH = 25; TILE_DIMENSION = 4; MIDDLE = FLOOR((TARGET_WIDTH+1) / 2); WIDTH = FLOOR(TARGET_HEIGHT / 2); HEIGHT = FLOOR(TARGET_WIDTH / 4); TETRIS_WIDTH = (HEIGHT+TILE_DIMENSION+1); PACWIDTH = (HEIGHT*2+1); PACHEIGHT = (WIDTH+2); MAZE_HEIGHT = (PACHEIGHT+TILE_DIMENSION); LINE_WIDTH = (PACWIDTH*6+1); LINEHEIGHT = (PACHEIGHT*2+1); // Arrays: tetris = (long *)malloc(sizeof(long) * (WIDTH * TETRIS_WIDTH + 1)); // 0,0 at bottom left, 8,19 at top right maze = (int *)malloc(sizeof(int) * ((PACWIDTH+TILE_DIMENSION)*MAZE_HEIGHT + 1)); line = (char *)malloc(sizeof(char) * ((LINEHEIGHT * LINE_WIDTH) + 1)); // slop is to allow for stretched image //#ifdef DEBUG srand((unsigned int)time(NULL)); // New maze every second! //#endif first_tile = 0; last_tile = TILEMAX; dead_block = last_tile+1; // Generating is cheap so we can afford to generate and reject any completed mazes that have problems returnval = TRUE; repeat_until (!(returnval)) { // while something is bad about this try try_one(); too_narrow(); if (!returnval) { wide_path(); if (!returnval) { small_loops(); if (!returnval) { bad_spawn_point(); } } } } draw_maze(); return (EXIT_SUCCESS); }