IOCCC、すごいよねえ。
公式サイトに行けば、昔からの入賞作品を見ることができる。
名作揃いだと思うのだが、個人的に特に好きなのが、1992年のBrian Westleyさん(aka Merlyn LeRoyさん)の作品。(ソースコード)
main(l ,a,n,d)char**a;{ for(d=atoi(a[1])/10*80- atoi(a[2])/5-596;n="@NKA\ CLCCGZAAQBEAADAFaISADJABBA^\ SNLGAQABDAXIMBAACTBATAHDBAN\ ZcEMMCCCCAAhEIJFAEAAABAfHJE\ TBdFLDAANEfDNBPHdBcBBBEA_AL\ H E L L O, W O R L D! " [l++-3];)for(;n-->64;) putchar(!d+++33^ l&1);}
古い仕様のCで書かれているが、GCC 4.9.3で正常にコンパイルできることは確認している。
cc westley.c
正常にコンパイルできたら動かしてみよう。例えばこんな感じで。
(動かす際には横幅80文字の端末を使う必要があるので注意)
a.out 35 135
出力はこんな感じ↓
しばらく出力を眺めて、何が書かれているのかを理解してから、もう一度ソースコードを見返した時に衝撃を受けた。
すごいよね~
このソースコードを見やすいものに書き換えて理解してみる。
まずは普通にインデントしてみる。
main(l,a,n,d) char**a; { for(d=atoi(a[1])/10*80-atoi(a[2])/5-596; n="@NKA" "CLCCGZAAQBEAADAFaISADJABBA^" "SNLGAQABDAXIMBAACTBATAHDBAN" "ZcEMMCCCCAAhEIJFAEAAABAfHJE" "TBdFLDAANEfDNBPHdBcBBBEA_AL" " H E L L O, W O R L D! "[l++-3];) for(;n-->64;) putchar(!d+++33^l&1); }
これだけでも見通しが良くなるんだ。
さらにもうちょっと見やすくしてみる。
main(int argc,char** argv) { int n,d; for(d=atoi(argv[1])/10*80-atoi(argv[2])/5-596; n="@NKA" "CLCCGZAAQBEAADAFaISADJABBA^" "SNLGAQABDAXIMBAACTBATAHDBAN" "ZcEMMCCCCAAhEIJFAEAAABAfHJE" "TBdFLDAANEfDNBPHdBcBBBEA_AL" " H E L L O, W O R L D! "[argc++-3];) for(;n-->64;) putchar(!d+++33^argc&1); }
一番トリッキーなのは
putchar(!d+++33^argc&1);
だと思う。括弧を使って見やすくしてみる。
putchar(((!(d++)) + 33) ^ (argc&1));
面白いコードだねえ。
!(d++)の!は論理式の否定。dが0の場合には!(d++)は1、dが0以外の場合には!(d++)は0。
argc&1はargcが偶数なら0、奇数なら1。
^は排他的論理和なので、((!(d++)) + 33)の最下位ビットを反転したりしなかったりすることになる。
へえ。
ASCIIコードで32はスペース、33は!、34は"、35は#。
つまりわかりやすく書けば、
if (argc % 2 == 0) { putchar(d++ == 0 ? '"' : '!'); } else { putchar(d++ == 0 ? '#' : ' '); }
となる。
更にわかりやすくなるように書き換えていって、結局はこんな感じに書いてみた。
#include <stdlib.h> #include <stdio.h> int main(int argc,char** argv) { int n,d,l = 0; char* str = "@NKA" "CLCCGZAAQBEAADAFaISADJABBA^" "SNLGAQABDAXIMBAACTBATAHDBAN" "ZcEMMCCCCAAhEIJFAEAAABAfHJE" "TBdFLDAANEfDNBPHdBcBBBEA_AL" " H E L L O, W O R L D! "; for (d=atoi(argv[1])/10*80 - atoi(argv[2])/5 - 596; (n=str[l]); l++) { for (;n>64;n--) { if (l % 2 == 0) { putchar(d == 0 ? '"' : '!'); } else { putchar(d == 0 ? '#' : ' '); } d++; } } return 0; }
@NKA…で始まるデータはもちろん地図情報。@(ASCIIで64)が0、A(ASCIIで65)が1、B(ASCIIで66)が2、…という意味がある。
それぞれの数字には、陸地あるいは海のデータが横に何個連続しているのか、という意味がある。
奇数番目は陸地のデータ、偶数番目は海のデータ。
つまり、1文字目(@)は最初に陸地のデータが何個連続しているのか(この場合はゼロ。つまり左上は海から始まることになる)、2文字目(N)はその次に海のデータが何個連続しているか、3文字目(K)がまた陸地で、…というデータになっている。
興味深いのは最後の" H E L L O, W O R L D! "の部分。スペース(ASCIIで32)は@(ASCIIで64)よりも小さいので、陸地はなし。次のH(ASCIIで72)は海が8個、次が陸地はなしで、また海があって、…ということで結局海が続いていることになる。
海を書いているのか、あるいは陸地を書いているのか、のフラグの変数がl(あるいはargc)。
また、1文字書くごとにdの値が増えていって、0になった時だけ普通の海(スペース)の代わりに「#」、陸地「!」の代わりに「"」が出力される。
マジックナンバーの596は北緯0度・東経0度の場所を意味している。つまり596番目の文字が基準点となっている。
ふーむ。ほんとによく書いてあるねえ。すごいねえ。