#include <stdio.h>
extern long int random(void);
#define EXIT_SUCCESS 0
#define TARGET_HEIGHT 19
#define TARGET_WIDTH 25
#define TILE_DIMENSION 4
#define MIDDLE ((TARGET_WIDTH+1)/2)
#define WIDTH (TARGET_HEIGHT/2)
#define HEIGHT (TARGET_WIDTH/4)
#define JAIL 1
#ifndef FALSE
#define FALSE (0!=0)
#define TRUE (!FALSE)
#endif
static int next_tile = 0, first_tile, last_tile, dead_block;
unsigned int tile[] = {
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,
};
long tetris[WIDTH][HEIGHT+TILE_DIMENSION+1]; // 0,0 at bottom left, 8,19 at top right
void place_tile(int tileno, int r, int c)
{
int row, col, t = tile[tileno], nibble, bit;
next_tile++;
for (row = 0; row < 4; row++) {
nibble = t&15; t >>= 4;
for (col = 0; col < 4; col++) {
bit = nibble&8; nibble <<= 1;
if (bit) tetris[col+c][row+r] = next_tile;
}
}
}
int can_place(int tileno, int r, int c)
{
int row, col, t = tile[tileno], nibble, bit;
for (row = 0; row < 4; row++) {
nibble = t&15; t >>= 4;
for (col = 0; col < 4; col++) {
bit = nibble&8; nibble <<= 1;
if ((bit && tetris[col+c][row+r] != 0) || (bit && ((row+r) > (HEIGHT-1)))) {
return FALSE;
}
if (bit && ((c+col) > WIDTH-1)) {
return FALSE;
}
}
}
return TRUE;
}
#ifdef DEBUG
static char t[]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!@#$%^&*()_+`-={}|[]\\:\";'<>,./";
#endif
void debug(void) {
#ifdef DEBUG
int i, j;
for (i = HEIGHT-1; i >= 0; i--) {
for (j = 0; j < WIDTH; j++) {
putchar(t[tetris[j][i]]);
}
putchar('\n');
}
putchar('\n');
#endif
}
void drop(int tileno, int r, int c)
{
if (can_place(tileno, r, c) && (r==0 || !can_place(tileno, r-1, c))) {
place_tile(tileno, r, c);
} else {
if (r>0) drop(tileno, r-1, c);
}
}
int ran(int low, int high) {
return low + random()%(high-low+1);
}
void play_tetris(void) {
int i, j;
next_tile = 0; // used by 'place_tile' called from 'drop'
for (i = 0; i < WIDTH; i++) {
for (j = 0; j < HEIGHT+TILE_DIMENSION+1; j++) {
tetris[i][j] = 0;
}
}
drop(30, HEIGHT+1, WIDTH/2); // jail. Should also handle tunnels here.
for (i = 0; i < 1000; i++) {
drop(ran(first_tile, last_tile-1), HEIGHT, ran(0,WIDTH-1));
}
}
#define PACWIDTH (HEIGHT*2+1)
#define PACHEIGHT (WIDTH+2)
static int maze[PACWIDTH+TILE_DIMENSION][PACHEIGHT+TILE_DIMENSION];
static int this_row, this_len; // remember to ensure a blank line at top and bottom for tombstones
char line[PACHEIGHT*2+1][PACWIDTH*6+1]; // slop is to allow for stretched image
void add(int this, int next) {
line[this_row][this_len++]= this;
line[this_row][this_len++]= next;
line[this_row][this_len]= '\0';
}
void init_row(void) {
this_row = -1;
this_len = 0;
}
void next_row(void) {
this_row++;
line[this_row][0] = ' ';
line[this_row][1] = '\0';
this_len = 1;
}
void convert_to_maze(void) {
int i, j, row, col;
row = 1;
for (j = 0; j < WIDTH; j++) {
col = 1;
for (i = HEIGHT-1; i >= 0; i--) {
maze[col][row] = tetris[j][i]; if (maze[col][row] == 0) maze[col][row] = dead_block;
col++;
}
for (i = 1; i < HEIGHT; i++) {
maze[col][row] = tetris[j][i]; if (maze[col][row] == 0) maze[col][row] = dead_block;
col++;
}
row++;
}
}
void debug_maze(void) {
#ifdef DEBUG
int i, j;
for (i = PACHEIGHT-2; i > 0; i--) {
for (j = 1; j < PACWIDTH-1; j++) {
putchar(t[maze[j][i]]);
}
putchar('\n');
}
putchar('\n');
#endif
}
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][j-1] != maze[i][j]) {
if ((maze[i][j-1] == 0) && (maze[i][j] == dead_block)) {
if ((maze[i][j] == dead_block) && (maze[i-1][j] == 0)) {
add(' ', ' ');
} else {
add('#', ' ');
}
} else {
if ((maze[i][j] == 0) && (maze[i][j-1] == dead_block)) {
if ((maze[i-1][j-1] == dead_block) || (maze[i][j-1] == 0)) {
add(' ', ' ');
} else {
if ((maze[i-1][j-1] == 0) && (maze[i-1][j] == 0)) {
add(' ', ' ');
} else {
add('#', ' ');
}
}
} else {
add('#', '#');
}
}
} else {
if ((maze[i-1][j] != maze[i][j])) {
if ((maze[i][j] == dead_block) && (maze[i-1][j] == 0)) {
add(' ', ' ');
} else {
add('#', ' ');
}
} else {
if ((maze[i][j] == maze[i][j-1]) && (maze[i-1][j-1] != maze[i][j])) {
add('#', ' ');
} else {
add(' ', ' ');
}
}
}
}
} else if (above_below == 0) {
if ((maze[i-1][j] != maze[i][j])) {
if ((maze[i][j] == dead_block) && (maze[i-1][j] == 0)) {
add(' ', ' ');
} else {
add('#', ' ');
}
} else add(' ', ' ');
}
}
void symmetry(int i, char *s) {
char *left, *right;
int len = MIDDLE;
left = right = s+len;
while (len-- >= 0) *right++ = *left--;
*right = '\0';
}
void gen_text_maze(void) {
int i, j, k;
init_row();
for (i = PACHEIGHT-1; i > 0; i--) {
for (k = 0; k >= -1; k--) {
next_row();
for (j = 1; j < PACWIDTH; j++) {
cell(k,j,i);
}
}
}
for (i = 0; i < TARGET_WIDTH+1; i++) line[TARGET_HEIGHT+1][i] = ' ';
line[TARGET_HEIGHT+1][TARGET_WIDTH+1] = '\0';
for (i = 0; i < PACHEIGHT*2/*-1*/; i++) {
symmetry(i, line[i]);
}
}
void trim_central_column(void) {
int iterate, i;
// Trim any central '#'s that don't connect above or below (OK if connecting to left and right)
for (iterate = 0; iterate < PACHEIGHT*2-1; iterate++) {
for (i = 1; i < PACHEIGHT*2-1; i++) {
if (line[i][MIDDLE] == '#' && line[i][MIDDLE-1] != '#' && (line[i-1][MIDDLE] != '#' || line[i+1][MIDDLE] != '#')) {
line[i][MIDDLE] = ' ';
}
}
}
}
int too_narrow(void) {
// Reject any that don't have >= 3 #'s on any row or col
int i, j, paths = 0;
for (i = 1; i < PACHEIGHT*2-1; i++) {
for (j = 1; j <= TARGET_WIDTH; j++) {
if (line[i][j] > ' ') paths += 1; // # or @ etc
}
}
return(paths < 4);
}
int wide_path(void) {
// Reject if there is a path all the way across
int i, j, paths;
for (i = 1; i <= TARGET_HEIGHT; i++) {
paths = 0;
for (j = 1; j <= TARGET_WIDTH; j++) {
if (line[i][j] != ' ') paths += 1; // # or @ etc
}
if (paths == TARGET_WIDTH) return(TRUE);
}
for (j = 1; j <= TARGET_WIDTH; j++) {
paths = 0;
for (i = 1; i <= TARGET_HEIGHT; i++) {
if (line[i][j] != ' ') paths += 1; // # or @ etc
}
if (paths == TARGET_HEIGHT) return(TRUE);
}
// Reject if too many paths joining left to right
paths = 0;
for (i = 1; i < TARGET_HEIGHT; i++) {
if (line[i][MIDDLE] != ' ' && line[i][MIDDLE-1] != ' ') paths += 1; // # or @ etc
}
if (paths > 5) return(TRUE);
return(FALSE);
}
int small_loops(void) {
// Reject squares (small loops):
int i, j;
for (i = 2; i < PACHEIGHT*2-2; i++) {
for (j = 1; j <= TARGET_WIDTH; j++) {
if (line[i][j] == ' ' &&
line[i-1][j-1] == '#' &&
line[i-1][j] == '#' &&
line[i-1][j+1] == '#' &&
line[i][j-1] == '#' &&
line[i][j+1] == '#' &&
line[i+1][j-1] == '#' &&
line[i+1][j] == '#' &&
line[i+1][j+1] == '#'
) return(TRUE);
}
}
return(FALSE);
}
void trim_junk(void) {
int i, j;
for (i = 2; i < PACHEIGHT*2-2; i++) {
for (j = 1; j <= TARGET_WIDTH; j++) {
if (line[i][j] == '#' &&
line[i-1][j-1] == ' ' &&
line[i-1][j] == ' ' &&
line[i-1][j+1] == ' ' &&
line[i][j-1] == ' ' &&
line[i][j+1] == ' ' &&
line[i+1][j-1] == ' ' &&
line[i+1][j] == ' ' &&
line[i+1][j+1] == ' '
) line[i][j] = ' ';
}
}
}
int bad_spawn_point(void) {
int i;
// While we're at it, set up the jail exit
for (i = (TARGET_HEIGHT/2); i > 0; i--) {
if (line[i][MIDDLE] == '#') {
line[i+1][MIDDLE] = 'V';
if (line[i-1][MIDDLE] == '#') return(TRUE);
break;
}
}
// must spawn below jail
for (i = (TARGET_HEIGHT/2)+4; i < (PACHEIGHT-1)*2; i++) {
if (line[i][MIDDLE] == '#') {
line[i][MIDDLE] = '@';
if (line[i-1][MIDDLE] == '#') return TRUE;
if (line[i+1][MIDDLE] == '#') return TRUE;
break;
}
}
// don't spawn on bottom line
for (i = i+1; i < (PACHEIGHT-1)*2; i++) {
if (line[i][MIDDLE] == '#') return(FALSE);
}
return(TRUE);
}
void draw_maze(void) {
int i, j, c;
printf(" 01234567890123456789012345\n");
for (i = 0; i < PACHEIGHT*2-1; i++) {
printf("%03d: |%s| ", i, line[i]);
for (j = 0; j <= TARGET_WIDTH; j++) {
if ((j == TARGET_WIDTH/2) || (j == (TARGET_WIDTH/2)+2)) {
//putchar('.'); putchar('.'); -- unfortunately fixing the wide middle sometimes
// leaves a vertical path down the centerline... - needs tweaking...
} else {
c = line[i][j];
putchar(c);
if (c == ' ') putchar(c); else putchar((line[i][j+1] == ' ' ? ' ' : '#')); // hack for spawn point
}
}
putchar('\n');
}
putchar('\n');
}
int main(int argc, char **argv)
{
#ifdef DEBUG
srand((unsigned int)time(NULL));
#endif
first_tile = 0; last_tile = sizeof(tile)/sizeof(tile[0])-1; dead_block = last_tile+1;
// Generating is cheap so we can afford to generate and reject
for (;;) {
play_tetris();
debug();
convert_to_maze();
debug_maze();
gen_text_maze();
trim_junk();
trim_central_column();
// Reject any completed mazes that have problems
if (too_narrow()) continue;
if (wide_path()) continue;
if (small_loops()) continue;
if (bad_spawn_point()) continue;
draw_maze();
break;
}
return(EXIT_SUCCESS);
}