// This is the annotated version of my IOCCC submission. // // To differentiate between annotated comments and original filler comments, // the annotated comments use C99 single line comments ("//"), while the // original comments are all block comments ("/**/"). // I had two goals for this project: // 1. Generate a sixel image, since there seem to be a distinct lack of // sixel content in IOCCC. // 2. Have an ASCII art fallback for terminals without sixel support. // // As for the source code layout, I decided to use Marcille Donato from // Dungeon Meshi ("Delicious in Dungeon"). From there, it was obvious that // the sixel image would be that of the walking mushroom, and the ASCII art // fallback would be an ASCII art dungeon. The two functions are // implemented as follows: // 1. Generate a random dungeon. // 2. Replace all the wall characters from the dungeon with sixel data for // the walking mushroom. // The code starts with this "char*", which is used exactly twice: once for // data and variable declarations, and once more as an argument to main(). // But this "char*" only appears once in the code since it's duplicated via // preprocessor. char* // The first of 10 filler comments. /* (c) 2025 */ // Preprocessor trick to rearrange the code layout. The way ASCII art code // works is that each non-whitespace character in the layout template would // be replaced by code, and since #include lines would take up the whole // line, it would be better to place them near the end of the layout where // they don't waste so many non-whitespace characters. This #ifdef does // that shuffling so that the latter part of the code with all the #include // is parsed first, and then we do an "#include __FILE__" at the end to // resume parsing from the start. #ifdef r // This is the end of the argument list for main(). *J) { // Check command line arguments. // j = argc. // J = argv. // M = output dungeon width in the range of [36, 2047]. // A = output dungeon height in the range of [12, 2047]. // // The 2047 upperbound was chosen due to memory limits: we want to // allocate a bitmap at "width*height*144" bytes, and if we had gone for // one extra bit and set the limit at 4095, the requested size would // exceed signed 32bits. Since dungeon sizes are expected to be in the // range of terminal sizes, 2047 should be very generous. // // Also, at large enough dungeon sizes, the corresponding sixel image // will likely exceed the maximum image size supported by the terminal. // The average user will probably only want dungeons to be around 128x40. if( j < 3 || (M = atoi(J[1])) < 36 || (A = atoi(J[2])) < 12 || (M | A) > 2047 ) { // If any of the arguments failed to validate, output an error message // and exit with nonzero status. // // The error message is not the most descriptive text, I would have // preferred spelling out "width" and "height" instead of "w" and "h", // but there wasn't enough space. Rather than spelling out the // parameter names, I thought it was more useful to include the // parameter ranges. return +printf("%s\40" "{36<" "=w" "<2048} {12<=h<2048} [seed]\n", *J); } // I = width + 1 // // "Width+1" appears frequently during dungeon generation because we need // one extra byte in each row to account for the trailing newline. I = M + 1; // C = dungeon size with newlines included. C = I * A; // R = size of output sixel image. // // Output image is always square. R = 6; // Allocate buffer for dungeon, and determine the maximum output image // size. Dungeon will contain a series of non-whitespace characters to // mark all the walls, each of which will be replaced by a single sixel // byte. Normal sixel data is at 6 pixels per byte, and run-length // encoding using 3-byte sequences can get us up to 24x compression // ratio, so here we set image size from the optimistic estimate of // // pixel count >= dungeon area * 6 * 24 = M * A * 6 * 24 = M * A * 144 // // q = output buffer for dungeon data. The requested size here is 2x of // what's needed due to an aspect ratio correction step we will do // later. // R = size of output image, adjusted at 6 pixel increments until image // size is greater than the optimistic maximum pixel count. // // Note the use of {} empty loop body here, even though a single // semicolon would have been sufficient. When the code is reformatted as // ASCII art with multiple statements on one line, we would get compiler // warnings from "-Wempty-body" or "-Wmisleading-indentation" if we just // use a single semicolon, which is why we added more braces than needed // here and a lot of other places. for(q = malloc(C * 2); R * R < M * A * 144; R += 6) {} // b = buffer for image data. b = malloc(R * R); // o = buffer of offsets, used for floodfill and rasterization. // // Of the two operations that uses list of integers, floodfill memory use // is proportional to dungeon area (C), while rasterization use is // proportional to image height (R*2). Allocating for C*sizeof(int) // would have been sufficient for most dungeon sizes except for the // really small ones, where R*2>C. Here we are allocating for the worse // case in the small dungeons. We waste a bit of memory for the large // dungeons, but at least we are guaranteed not to write out of bounds. o = malloc(C * (LL = 2 * sizeof(int))); // Only proceed with the main logic if we managed to allocate all three // buffers we needed. // // On failure, we will skip over this conditional and return "!t". "t" // is a global variable that has the initial value of zero due to static // initialization, so "!t" will cause the program to exit with nonzero // status. // // We don't have a friendly error message when we run out of memory, // because this condition rarely happens, since we checked for the // expected sizes at the start of the program. if( q && b && o ) { // Seed random number generator using command line argument if it's // available, otherwise use ASLR to get a semi-random seed. // // Note the ASLR expression is a pointer difference type (ptrdiff_t). // We can't simply use "(int)&j" because pointers may be 64 bits, // while ints are often 32 bits. We can't use "(int*)&j" because // that's a pointer type and not an integer type. "(int*)0-&j" gets // us the right type and right bit width without the extra compiler // warnings. // // As to why we are using ASLR at all: traditionally the way to seed // random number generators would have been to use time(NULL), but // that requires #include, and we don't want to include // another header if we can avoid it. srand(j > 3 ? atoi(J[3]) : (int*)0 - &j); // Initialize a grid of random values: // - All edge cells are assigned 15. // - All middle cells gets a random value of 0 (space) or 1 (wall). // // The random threshold for the middle cells is not 0.5, but 0.47. We // are generating a random grid here in preparation for the smoothing // passes in the next loop, and 0.47 controls the density of the // walls. A threshold greater than 0.47 will cause the grid to start // with more space, and the smoothing passes tend to cause the result // to quickly converge to large empty areas. Similarly, a threshold // less than 0.47 will cause the grid to start with more walls, and // the end result tend to converge toward a completely filled map // without any spaces. 0.47 turned out to be a good threshold. // // q = dungeon buffer. // p = write pointer. // y = vertical position. Note that we are setting up a grid with 2x // the user requested height (A*2). p = q; for(y = 0; y < A * 2; y++) { // x = horizontal position. // I = width + 1. for(x = 0; x < I; x++) // Write edge walls (15), or random space/wall cells (0/1). // // Note that the right wall is two characters thick. The extra // column is needed to reserve space for the trailing newlines. *p++ = y && y < A * 2 - 1 && x && x < M - 1 ? r > .47 ? 0 : 1 : 15; } // Apply three smoothing passes over the randomized cell data. This // implements the algorithm described here: // https://www.roguebasin.com/index.php/Cellular_Automata_Method_for_Generating_Random_Cave-Like_Levels for(t = 0; t < 3; t++) { // Apply smoothing pass in the vertical range of [1,height*2-1], // skipping over the walls at top and bottom. for(y = 1; y < A * 2 - 1; y++) { // Apply smoothing pass in the horizontal range of [1,width-1], // skipping over the walls at left and right, and also skips // over the column reserved for newlines. // // The smoothing pass is implemented by summing bit 0 of all // nine cells in a 9x9 area, and setting bit 1 for the center // cell based on that sum. We want to set bit 1 if the cell // count is 5 or greater, or clear bit 1 if cell count is 4 or // less. Because the count is initialized to "-1", the // condition below checks for "z>3". // // Note that the counting is done by reading from bit 0, while // the result is written to bit 1. This means the output does // not interfere with the counts. We could have written the // results to the cells directly and the result wouldn't look // too awful, but it will be smoother than expected, and biased // toward one corner. // // z = 1-cell count, starting at -1. // v = vertical offset within the 3x3 area. // u = horizontal offset within the 3x3 area. for(x = 1; x < M - 1; q[y * I + x++] |= z > 3 ? 2 : 0) for(z = v = -1; v <= 1; v++) for(u = -1; u < 2; u++) z += q[(y + v) * I + x + u] & 1; } // Commit all wall bits from the loop above by shifting all cells // right by 1 bit. Here we are using a single loop to shift all // cells right, as opposed to a nested loop over rows and columns. // This is possible because the walls were initialized with 15, so // after three smoothing passes, the walls cells near the edges // with have the value of 1, just like the walls cells in the middle. p = q; for(x = 0; x < C * 2; x++) *p++ /= 2; } // Vertically shrink the dungeon by a factor of 2 to produce a dungeon // that matches the user requested height. // // The reason why we stretch vertically by 2 during generation is to // compensate for the non-square aspect ratio of terminal characters, // which tend to be closer to 1:2 rather than 1:1. By stretching // vertically first and then shrink it to size here, we get better // shaped caverns instead of mostly tall caverns. // // y = vertical position, advanced two rows at a time. // x = horizontal position. // p = write pointer. Note that the dungeon is shrunk in-place, so // for we overwrite one 1x1 cell for every 1x2 cells read. p = q; for(y = 0; y < A * 2; y += 2) { for(x = 0; x < I; x++) // Merge two vertically adjacent cells using bitwise-or. Note // that we use "-~y" instead of "(y+1)", saving 2 bytes due to // the higher operator precedence of unary operators. *p++ = q[y * I + x] | q[-~y * I + x]; } // Connect disjoint caverns in the map. This is done in four steps: // 0. Find a random space area. // 1. Mark all connected spaces using floodfill. // 2. Find another random space area that wasn't touched by the // floodfill step, and connect it to a previously floodfilled area. // 3. Repeat steps 1..3. // // All cells hold one of 3 values: // 0 = space, not yet filled (unconnected disjoint space). // 1 = wall. // 2 = space, already filled (space connected to other spaces). // // We start off by writing a 0 through "*p", which points at the end // of the dungeon text after the completion of the previous loop. // This makes "q" a NUL-terminated string, with some extra embedded // NULs to mark the space areas. for(*p = t = 0; t < 4; t++) { // Find a random space area. We start with a random offset inside // the dungeon area. // // x = random offset within dungeon area. x = r * C; // Find the next space area from the random starting point. Since // finding space area involves searching for a NUL character, // strlen does exactly what we wanted here. x += strlen(q + x); // If the offset we found is past the end of dungeon (x>=C), // search again from the start of the dungeon (x=strlen(q)). if( x >= C && (x = strlen(q)) >= C ) { // We failed to find a space area after two tries, which means // we have filled all space areas. // // x = 4, an offset guaranteed to point at a wall (top edge). // t = 4, which means we will exit the floodfill loop. x = t = +4; } // Check if this is not the first pass by checking the lower bits // of "t". It's not simply a nonzero check since we don't want to // enter this conditional when we have decided to exit the // floodfill loop via "t=4" above. if( t & 3 ) { // Since this is not the first pass, it means we have found a // previously unconnected region of space. We will connect this // region to the previous known space region by digging a tunnel // toward the previous starting point, and then do a floodfill // from the tunnel to connect both regions. // Dig the tunnel horizontally while the horizontal position // differs from the previous starting point ("x%I-v"), and while // we have not yet reached a filled space cell ("q[x]-2"). Note // that we can save a few bytes by avoiding the second // condition, but the end result would that we dig excessively // long tunnels that crosses many filled space areas, and it // doesn't look as nice. // // v = target horizontal position. // u = previous starting offset. // x = current starting offset. for(v = +u % I; x % I - v && q[x] - 2; x += x % I > v ? -1 : 1) { q[x] = 0; } // Dig the tunnel vertically while the position differs from the // previous starting point ("x-u"), and while we have not yet // reached a filled space cell ("q[x]-2"). // // u = previous starting offset. // x = current starting offset. for(; x - u && q[x] - 2; x += x > u ? -I : I) { q[x] = 0; } // Mark the end of the tunnel as a space. q[x] = 0; } // Record starting offset for this space region, and prepare to // floodfill. Floodfill is done with a manual stack instead of a // recursive function call, since we tend to run out of stack on // even moderately sized dungeons when using the runtime stack. // // o = floodfill stack. // u = starting offset, saved for next loop iteration. // x = current offset. // y = stack depth. *o = u = x; for(y = 1; y;) // Pop an offset off the stack, and expand if current cell is a // space cell. // // q = dungeon buffer. // v = offset from top of stack. if( !q[v = o[--y]] ) { // Mark current cell as filled space. q[v] = +2; // Push offsets to four neighboring cells onto the stack. o[y++] = v - M - 1; o[y++] = v - 1; o[y++] = v + I; o[y++] = v + 1; } } // At this point, we have a dungeon with a bunch of connected spaces // (filled with cell value 2). If there are still any spaces left // that are unconnected, we will fill those with walls so that there // are no disjoint spaces remaining. // // We will also take this time to insert newlines at the end of each // row. p = q; for(y = 0; y < A; y++, *p++ = 10) { for(x = 0; x < M; ++x, ++p) // If current cell is not a filled space (*p != 2), it becomes a // wall (1), otherwise it's converted to printable space // character (32). *p = *p - 2 ? 1 : 32; } // Here we are entering the sixel part of the program, having just // completed dungeon generation. "q" now contains three types of // characters: // - space (32). // - newline (10). // - walls (initially 1, but will get overwritten later). // // We will do a binary search to find the largest sixel image that can // be encoded using the wall characters. Binary search is done // because we can't predict the encoded size ahead of time, due to // run-length encoding. // // Alternatively, we could have written the program where we start // with the sixel image and try to generate a dungeon from there, but // that leaves us with unpredictable dungeon sizes and it's just less // convenient overall. // // j = lowerbound of image size, increased on success. // z = upperbound of image size, reduced on failure. // u = image size, the average of "j" and "z". // E = success status. A nonzero value means we were able to fit the // sixel image without exhausting all wall characters. j = 6; for(z = R; z - j > 1 || z == u; *(E ? &j : &z) = u) { // u = image size. u = (j + z) / 2; // S = image scaling factor. S = u / 90.; // Save a copy of sixel header format template. This is not used // until later, but for layout reasons, we save a pointer here. // // \x1bPq = enter sixel mode. // ";1;%d;%d = set image dimensions. We need to do this, otherwise // the right and bottom sides will not be sized // properly. Setting the image size ahead of time will // also help avoid transparency issues, especially on // inverted terminals where background color is white. // #1;2;100;100;100 = set color 1 to be RGB (100%,100%,100%). // #1 = set current color to color 1. *J = "\x1bPq\"1;1;%d;%d#1;2;100;100;100#1"; // Fill image buffer with black pixels. Note that we always fill // the full buffer (R*R), and not just the region we needed (u*u). // This is to avoid getting leftover pixels from previous // iterations, since we will encode the image 6 scanlines at a // time. // // We will also reset the variable for tracking the bottommost // scanline here. When converting pixels to sixels, we will drop // all trailing scanlines with black pixels to save space. // // b = image buffer. // v = bottommost scanline with pixels. // R = maximum image size. memset(b, v = 0, R * R); // Loop over all shapes until we run out of shapes. Shapes are // stored as follows: // - Values are store as 8bit values. // - A pair of values encodes coordinate for a single point. // - Four pairs of values encodes control points for a bezier // curve. Because the start of each curve overlaps with the end // of the previous curve, advancing to the next curve involves // advancing 6 values ahead (p+=6). // - A single zero marks the end of a shape. At the end of a shape, // p[0],p[1] is the coordinate of the last control point, and // p[2] is the trailing zero, thus advancing to the next shape // involves advancing 3 values ahead (p+=3). // - Two consecutive zeroes marks the end of all shapes. // // m = shape data. // p = read pointer. // i() = read a single component value, converting from 8bit ASCII // to scaled floating point. for(p = m; i(0) > 0; p += 3) { // Rasterize a single shape. This is done by tracking the // minimum and maximum X values for each scanline as we trace // out the bezier curve edges, and stopping when we see the // trailing zero ahead. // // o = list of offset pairs tracking scanline X ranges. // u = image height. for(memset(o, 0, u * LL); i(2) > 0; p += 6) { // Trace a single bezier curve. This is done by sampling at // 4x the vertical image size, which is sufficient to account // for all bezier curve points without skipping over any // scanlines. // // t = integer steps along the curve. // u = image height. for(t = 0; t < u * 4; t++) { // Z = floating point time along the curve, in the range // of [0, 1). Z = t / (u * 4.); // x = horizontal position of point along the curve. x = L(); // Increment read pointer to read the Y component values. p++; // y = vertical position of point along the curve. y = L(); // Decrement read pointer so that it's back to pointing at // X component values. p--; // g = pair of integer X range values. g = o + y * 2; // v = bottommost Y value with pixels. v = v > y ? v : y; // Update minimum X for this scanline. Note the special // case for *g==0, which gets replaced with the new value // unconditionally. *g = *g ? *g < x ? *g : x : x; // Update maximum X for this scanline. g++; *g = *g > x ? *g : x; } } // Fill each nonempty scanline. // // g = pointer to X range pairs for each scanline. // y = vertical scanline position. // u = image height. g = o; for(y = 0; y < u; y++, g += 2) // Fill this scanline if minimum X in this range pair is // nonzero. for(x = *g; *g && +x <= g[1]; ++x) // For the first three shapes (p<73), we use a dithering // scheme to give the shapes a different color. All // subsequent shapes gets assigned a solid white pixel. // // Instead of dithering, we could have encoded the output // using multiple colors. But multi-color support in // sixel is very cumbersome -- basically we need to draw // the same scanline in multiple passes with different // colors, skipping over the pixels that already have the // desired colors. Since we only have so few colors, the // extra complexity didn't seem worthwhile, so we just use // a single color and apply some dithering. b[y * u + x] = p < 73 + m ? (x + y) & 1 : 1; } // Now we have an image with one byte per pixel. We will convert // it in-place into sixel data. // // For layout reasons, this has been moved to a separate function // U(). w = b; y = 0; U(); // Write the NUL terminator to mark the end of sixel data. *w = 0; // Write sixel header to the start of dungeon buffer. We are // guaranteed to have enough room because the first row of dungeon // is all walls, and we have enforced a minimum width requirement // at the start of the program, so the wall characters will be // replaced with sixel header. w = q + sprintf(q, *J, u, u); // Since sprintf wrote a trailing NUL, we will replace that with a // "1" to restore the cell's wall status. // // We will also update "E" here to indicate that the process of // fitting an image to dungeon walls is successful so far. "E" // will become zero when we run out of wall cells, and we will // update the image dimension accordingly. E = *w = 1; for(p = b; E && *p; W()) { // Run-length encode the sixel data. // // For layout reasons, this has been moved to a separate // function V(). V(); } // We have either encoded all sixel data, or broke out of the loop // because we ran out of wall characters (E==0). If we were // successful, but there are still unused wall characters left // (w-qH:A?>D;LCRJWP" "WU`X`Y" "" "`[_]h`bj^mSuFt>j3ZDPDM':r;s" "t?" "" "q@oEoFnElDk>k. This is why I tried so // hard to drop header dependencies. That said, I could have implemented // all the string functions I needed in this file, but it would cost more // bytes than what's taken up by this filler comment, which is why I went // with . // // The comment here say "gakkou hajimette irai no saijo" or "the most // talented girl since the beginning of (magic) school". This refers to // Marcille. /* uUv3 ) Gakkou Hajimette' Irai No Saijo */ #include /* */ // t = redundant variable used for layout purposes, see the next "t" // below. None of the compilers complained about this because it's // global. int t, // M = output dungeon width (characters). M, // A = output dungeon height (characters). A, // R = theoretical maximum edge length of output sixel image (pixels). R, // C = output dungeon size including trailing newlines (characters). C, // I = width + 1 (M+1). I, // LL = 2 * sizeof(int). LL, // E = success status for replacing dungeon walls with sixel data. E, // o = floodfill offsets during dungeon generation. // = scanline ranges during rasterization. *o, // g = pointer inside "o" during rasterization, used to save a few // pointer operations. *g, // x, y = various positions and offsets. These are mostly consistently // used as horizontal and vertical positions. x, y, // z = wall count in a 3x3 area during dungeon generation. // = upperbound of image size during rasterization. z, // t = counter for tracking smoothing passes during dungeon generation. // = counter for tracking floodfill passes during dungeon generation. // = step index along bezier curve during rasterization. t, // u = horizontal offset in 3x3 area during dungeon generation. // = previous floodfill starting offset during dungeon generation. // = image height during rasterization. u, // v = vertical offset in 3x3 area during dungeon generation. // = previous floodfill starting column during dungeon generation. // = offset from top of floodfill stack. // = bottommost scanline position. // = repeat count for run-length encoding. v; // Copy sixel data with run-length encoding. void V() { // Count the number of repeated sixel bytes, up to 99. for(v = 0; *p - 45 && p[v] == *p && v < 99; v++) {} // Check if run-length encoding should be used. This is by verifying // that we have a long enough run, and there are enough consecutive wall // characters to hold the sequence. // // Sixel run-length sequences take the form of "!{count}{data}", for // example "!9@" or "!99@". This means it's not economical to use // run-length encoding if the run is 1-3 bytes long. The first condition // checks for the run length (v>3), and second condition (x>2) checks for // the available space. if( v > 3 && (x = strcspn(w, n)) > 2 ) { // We have a long enough run and enough space. We will begin the // run-length command with '!'. *w++ = 33; if( x > 3 && v > 9 ) { // There are at least 4 consecutive wall characters (x>3), and // repeat count is in the range of 10..99, so we will insert a // two-digit repeat count here. *w++ = v / 10 + 48; *w++ = v % 10 + 48; } else { // There are only 3 consecutive wall characters available, or // repeat count is in the range of 4..9, so we will insert a single // digit repeat count here. If repeat count was greater than 9, we // will clamp it to 9. v = v > 9 ? 9 : v; *w++ = 48 + v; } // Skip over the repeated characters. p += v - 1; } // Copy sixel character. *w++ = *p++; } // Convert pixel data to sixel data in-place. void U() { // Convert vertically, 6 scanlines at a time, stopping at the last // nonempty scanline (y<=v). for(; y <= v; y += 6) { // Convert a series of bytes horizontally while keeping track of // rightmost pixel. // // w = output pointer. // p = pointer to rightmost pixel. p = w; for(x = 0; x 63 ) p = w; } // Rewind write pointer to point at the rightmost pixel. This is so // that we don't waste space storing empty pixels at the end of each // row. w = p; // Write sixel newline command (45 = '-'). *w++ = 45; } } typedef double s; s (Z), // Z = time along bezier curve. S; // S = scaling factor for output image. // Convert shape data from ASCII byte to scaled floating point. s i(int d) { return (p[d] - 39) * S; } // Interpolate between two floating point values. s l(s a, s d) { return +a + (d - a) * Z; } // Interpolate along a bezier curve, using De Casteljau's algorithm. // // https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm s L() { return l(l(l(i(00), i(2)), l(i(2), i(4))), l(l(i(2), i(4)), l(i(4), i(6)))); } // Update "w" to point at next wall character. If this update would cause // "w" to move past the end of dungeon data, "E" is set to zero to indicate // that we have exhausted all wall characters. void W() { E = C - 03 > (w += strspn(w, n)) - q; {;} } // , needed for puts, printf, and sprintf. #include/**/ // , needed primarily for malloc(). If it weren't for malloc(), // we would have implented rand() ourselves and replace atoi with sscanf. #include // r = random floating point number between 0 and 1. #define r (s)rand() \ /*o*/ / RAND_MAX // Start of program. int main(int j, #include __FILE__ #/**/endif