Implementation notes This file describes the various bits of nagisa.pl. Not so much the clever or black magic bits, most of it is trivial! Well, some parts are more interesting than others. ---------------------------------------------------------------------- 0. Concept A program that: - Outputs 100 other valid programs. - The 100 programs are all linked to each other. Inspired by "dango daikazoku". Everyone combined for 100 person family. Really inspired, actually. Clannad completely exceeded the scope and depth of the games before it, and gave me a new perspective on life. The timing also worked out just right this year... all things in alignment, I just had to write this program now. That, and toilet seat cover. ---------------------------------------------------------------------- 1. 100 person family Nagisa prints ASCII art dango based on current time. 100 variations total for 100 person family. Each dango is filled with the same Perl code. Output fits in 80x25 text, so it's compatible with most terminals. Due to size constraints, the 100 dango are printed separately rather than together. By "size" I mean screen size -- 80x25 is bit too small to draw more than two things at once, and then it will be difficult to differentiate them with only black and white pixels, *and* fit all the code in that. So I settled with just one dango, but 100 variations of it. Actually, even before that -- the dango could have been filled with something other than code, regular ASCII art characters like .o8P' for example. But there would be no fun in that, anyone can write programs that output text, but not everyone can write code that output code. ---------------------------------------------------------------------- 2. Code that outputs code Perl does the trick, one of the best languages for this purpose. Something like: $c = q{ print '$c = q{', $c, '};eval $c;'; }; eval $c; Arbitrary code can be inserted before "print", and things would generally work. For formatting purposes, there is also this bit outside the q{} $c =~ s/\s//g; Which removes all whitespaces inside the quoted string. This allows the code to be formatted arbitrarily, as long as we are careful to write things that tokenize well (for example, "'x' eq $x" works, but "$x eq 'x'" doesn't). ---------------------------------------------------------------------- 3. Code that fits arbitrary shape Because of the s/\s//g bit, just about anything would work as long as we reserve a few consecutive pixels (characters to be replaced by code) near the top and bottom. That is, we need to make room for: $c = q{ and $c =~ s/\s//g; $eval $c; So it isn't so much a problem of code that fits arbitrary shapes, but finding shapes that can contain slightly constrained code. Also, the shapes should resemble the dango we are trying to draw in the first place. The top edge problem is solved trivially due to the dango shape, which contains no sharp edges, so we always have 2-3 pixels to spare. 20 pixels for the bottom edge is slightly more difficult... in the end, the image was generated by: - Draw all scanlines for top half of dango normally. - Draw all scanlines except the last one for the bottom half. - For the final scanline, ensure that the run length is at least 20 pixels, and keep it centered with the rest of the dango shape. - Skip over the eyes while we draw the scanlines. For a bit of variation, the aspect ratio is adjusted over time. This makes the code to shift around the pixels instead of remaining static, to keep things a bit more interesting. ---------------------------------------------------------------------- 4. Fitting code to shape First render the shape to string, then output one character of code for each pixel. The reason why we have exactly the same number of code characters as pixels is no accident: - Count all pixels in shape. - Generate padding that is (pixel count - code size). This is inserted just before the s/\s//g bit, so that the last part always end up on the last scanline. - Output one code character per pixel. ---------------------------------------------------------------------- 5. Encoding / obfuscating source The steps above describe how the dango code is generated. However, the code that generates the first frame need not look like any of the generated code, and need not be written in Perl either (although it makes sense to use the same language). For a bit of variety, this was formatted differently, and xor-encoded. This has the benefit of adding more pixels so that the initial ASCII art looks nicer, and also allows some semi-subliminal message to be encoded. The side effect is that the final encode source very much doesn't resemble Perl code anymore. All the better ^_^; ---------------------------------------------------------------------- 6. Testing To be sure that there are indeed 100 different programs and they all work, I have another script that generates the 100 different Perl scripts, run the generated Perl scripts and see that they all match the original. This works by replacing "time" with a hardcoded constant, which was okay until I decided to xor-encode the source. In later versions, the 100 variations were generated by replacing "time" function with one that returns fixed timestamp. The output is then joined together to generate an animated GIF, and the source function is then verified visually. The animated GIF is also not too shabby to look at, although some browsers will not render it at the right speed for one reason or another. So it's implemented and tested, and declared complete after adding this final bit of documentation. No black magic after all, right? ^_^; Actually it wasn't tested quite enough. On 2011-12-16, I got an email saying the output doesn't always parse. Perhaps Perl has changed during these 4 years? Updated the code and tests a bit, now Nagisa should work as intended. Thanks to Dominique Martinet for the bug report! ---------------------------------------------------------------------- 7. Finally... December 22 - happy birthday to Tohno Minagi. December 23 - happy birthday to Minasa Nayuki. December 24 - happy birthday to Furukawa Nagisa. Dango, dango. -- omoikane@uguu.org - http://uguu.org/