#include #include #include #include static char *points = "6m2o1v7{;~E|DvCq:k6m'bhbkcqhsmurnnhkdbdbh'L/U/T)a+n-q7sFtM{NtVga;M8A55@/L/'DM>H:A?>D;LCRJWPWU`X`Y`[_]h`bj^mSuFt>j3ZDPDM':r;st?q@oEoFnElDk>k= (width + 1) * height - 3; } /* Generate mushroom to a particular size, returns 0 on success. */ static int GenerateMushroom() { /* Allocate buffers on first call. Subsequent calls will work with smaller output sizes, so the buffers allocated on the first call will be large enough. */ if( image == NULL ) { image = malloc(dx * dx); range = malloc(dx * 2 * sizeof(int)); if( image == NULL || range == NULL ) { puts("Out of memory"); exit(EXIT_FAILURE); } } /* Fill background with black pixels. */ memset(image, 0, dx * dx); scale = dx / 90.0; p = points; while( P(0) > 0 ) { /* Collect horizontal ranges for each scanline. This allows us to draw filled shapes one scanline at a time, and the shapes can be both convex and concave as long as each scanline is continuous. We can enforce the continuous requirement by tweaking our shapes, saving us the complexity of having to write a more featureful rasterizer. */ memset(range, 0, dx * 2 * sizeof(int)); for(; P(2) > 0; p += 6) { for(t = 0; t < dx * 4; t++) { ft = (double)t / (dx * 4.0); x = InterpolateComponent(); p++; y = InterpolateComponent(); p--; if( x > 0 && x < dx && y >= 0 && y < dx ) { if( range[y * 2] == 0 ) { range[y * 2] = range[y * 2 + 1] = x; } else { range[y * 2] = range[y * 2] < x ? range[y * 2] : x; range[y * 2 + 1] = range[y * 2 + 1] > x ? range[y * 2 + 1] : x; } } } } /* Draw scanlines. */ for(y = 0; y < dx; y++) { if( range[y * 2] != 0 ) { for(x = range[y * 2]; x <= range[y * 2 + 1]; x++) image[y * dx + x] = p < points + 73 ? (x + y) & 1 : 1; } } /* p[0],p[1] is the last pair of coordinates. p[2] is a trailing zero. Thus, to get to the start of the next shape, we need to skip 3. */ p += 3; } /* Convert to bitmap data to sixel. */ w = image; for(y = 0; y < dx; y += 6) { for(x = 0; x < dx; x++) { *w++ = 0x3f + image[y * dx + x] + (image[(y + 1) * dx + x] << 1) + (image[(y + 2) * dx + x] << 2) + (image[(y + 3) * dx + x] << 3) + (image[(y + 4) * dx + x] << 4) + (image[(y + 5) * dx + x] << 5); } *w++ = '-'; } *w = '\0'; /* Replace wall characters with sixel data. We will start with the header, which is fixed size. We are guaranteed to have enough contiguous bytes for the header since we enforced a minimum output width, and generated borders on all four sides. */ memcpy(cave, "\x1bPq#0;2;0;0;0#1;2;100;100;100#1", 31); w = cave + 31; for(p = image; *p;) { if( SkipToNextWall() ) return 1; /* Copy byte. */ *w++ = *p++; } /* Add padding. */ while( w - cave < (width + 1) * height - 3 ) { if( SkipToNextWall() ) return 1; *w++ = '$'; } /* Add footer. */ strcpy(w, "\x1b\\"); /* Success. */ return 0; } /* Read a single cell with offset. */ static int ReadCell() { return cave[(y + dy) * (width + 1) + x + dx] & 1; } /* Floodfill an open region. Cave cells are encoded in three states: 0 = open, unfilled. 1 = wall. 2 = open, filled. */ static void FloodFill(int offset) { if( cave[offset] != '\0' ) return; cave[offset] = '\2'; FloodFill(offset + 1); FloodFill(offset + width + 1); FloodFill(offset - 1); FloodFill(offset - width - 1); } int main(int argc, char **argv) { if( argc != 3 || (width = atoi(argv[1])) < 32 || (height = atoi(argv[2])) < 16 ) { return printf("%s {width} {height}\n", *argv); } /* Allocate output buffer, with each row padded 1 to account for newline characters. Here we allocate a buffer that's twice the requested height. We will generate a cave with this double height, and then shrink it by half later. This is to account for the taller character cells in terminals. If we don't do this, we end up with caves that are more elongated vertically. With this change, we will end up with more horizontally orientated caves (because terminal character aspect ratios tend to be just short of double height), but it looks generally nicer that way. */ cave = malloc((width + 1) * height * 2); if( cave == NULL ) { puts("Out of memory"); return 1; } srand(time(NULL)); /* Fill cave with random bits. */ p = cave; for(y = 0; y < height * 2; y++) { for(x = 0; x < width + 1; x++) { /* We use 1 to indicate walls and 0 to indicate spaces. Only the lowest bit is used in the final output, but we fill 4 bits for the edge cells since the smoothing function will shift all cells right 3 bits. Note that the right wall is 2 cells thick. The extra cell is to account for the newline characters. */ *p++ = (y == 0 || y == height * 2 - 1 || x == 0 || x >= width - 1) ? 15 : R() <= 0.47 ? 1 : 0; } } /* Apply smoothing function. */ for(t = 0; t < 3; t++) { for(y = 1; y < height * 2 - 1; y++) { for(x = 1; x < width - 1; x++) { /* Sum the cave cell values from bit 0, then apply the sum to bit 1. We could also apply the sum directly to bit 0, which will result in an overly smoothed cave that is biased toward upper left. */ count = 0; for(dy = -1; dy <= 1; dy++) { for(dx = -1; dx <= 1; dx++) count += ReadCell(); } cave[y * (width + 1) + x] |= count >= 5 ? 2 : 0; } } /* Shift all cells right. */ p = cave; for(x = 0; x < (width + 1) * height * 2; x++) *p++ >>= 1; } /* Shrink cave to the requested size. */ p = cave; for(y = 0; y < height * 2; y += 2) { for(x = 0; x <= width; x++) *p++ = cave[y * (width + 1) + x] | cave[(y + 1) * (width + 1) + x]; } /* Add terminating NUL character. This is needed to make strlen work. */ *p = '\0'; /* Connect disjoint regions. */ for(t = 0; t < 4; t++) { /* Find a random starting point. */ x = R() * (height * (width + 1)); x += strlen(cave + x); if( x >= height * (width + 1) ) x = strlen(cave); if( x >= height * (width + 1) ) { /* Couldn't find a random starting point, which means all disjoint regions have been filled. */ break; } if( t > 0 ) { /* If we managed to find a new starting point after the first pass, it means we found a new disjoint region. Connect it to the previously filled region by digging a path to the previous starting point. We will stop as soon as we reach a previously filled region. We could remove that check and save a few bytes, and the end result will be some excessively long tunnels that cross multiple regions. */ dy = dx % (width + 1); for(; x % (width + 1) != dy && cave[x] != '\2'; x += x % (width + 1) > dy ? -1 : 1) cave[x] = '\0'; for(; x != dx && cave[x] != '\2'; x += x > dx ? -(width + 1) : width + 1) cave[x] = '\0'; cave[x] = '\0'; } /* Floodfill this open region. */ FloodFill(x); /* Record previous starting point. */ dx = x; } /* Convert cave data to printable characters, and count number of wall characters. Any space not covered by a floodfill pass will be converted to walls, since they are disconnected from other spaces. */ count = 0; p = cave; for(y = 0; y < height; y++) { for(x = 0; x < width; x++) { if( *p != '\2' ) { *p++ = '#'; count++; } else { *p++ = ' '; } } *p++ = '\n'; } /* Each wall character encodes 6 pixels without compression. If we use only 3 byte repeat sequences ("!9{pixel}"), we can get a max compression ration of 1:3, so maximum number of output pixels is count*6*3. We will set the output dimension to be a square that exceeds this pixel count, and gradually reduce it until compression succeeds. Alternatively, we can give up compression, which would allow us to encode the mushroom in one go, but it will be smaller. */ for(dx = 6; dx * dx < count * 6 * 3; dx += 6) {} /* Generate mushroom to fit cave walls, reducing image size in 6 pixel steps until we succeed. This loop is guaranteed to terminate because we enforced a minimum size at the start of the program, so we will always have enough walls for at least a small mushroom. */ while( GenerateMushroom() ) dx -= 6; puts(cave); return 0; }