perlでテトリス!
偶然おもしろいモノを発見しました。コンソールで遊べるperlテトリスです。
スクリーンショットとってみました。
なんと、macbookのターミナル上でカラフルなテトリスが元気よく動いてます!
それにしても、俺テトリス下手だな。。。
ってのはおいといて、ソースを見てみましょう。難読化されてます。
#!/usr/bin/perl $_='A=15; B=30; select(stdin); $|=1; select(stdout);$|=1; system "stty -echo -icanon eol \001"; for C(split(/\s/,"010.010.010.010 77.77 022.020.020 330.030.030 440.044.000 055.550.000 666.060.". "000")){D=0;for E(split(/\./,C)){F=0;for G(split("",E)){C[P][F++ ][D]=G} D++}J[P]=F; I[P++] =D}%L=split(/ /,"m _".chr(72)." c 2". chr(74)." a _m");sub a{for K(split(/ /,shift)){(K,L)=split(/=/,K );K=L{K};K=~s/_/L/; printf "%c[K",27}}sub u{a("a=40");for D(0..B -1){for F(0..A-1){M=G[F][D];if(R[F][D]!=M) {R[F][D]=M;a("m"."=". (5+D).";".(F*2+5)); a("a=".(40+M).";" .(30+M));print " "x2}}}a( "m=0;0 a=37;40")}sub r{(N)=@_;while(N--) {Q=W;W=O=H;H=Q;for F( 0 ..Q-1){for D(0..O-1) {Q[F][D]=K[F][D]}}for F(0..O-1){for D(0..Q- 1){K[F][D]= Q[Q-D-1][F]}}}}sub l{for F(0..W-1){for D(0..H-1){(K[ F][D]&& ((G[X+F][Y+D])|| (X+F<0)||(X+F>=A)|| (Y+D>=B)))&& return 0}}1}sub p{for F(0..W-1){for D(0..H-1){(K[F][D]>0)&&(G[X+F][Y+D] =K[F][D]) }}1}sub o{for F(0..W-1){for D(0..H-1){(K[F][D]>0)&&(G[ X+F][ Y+D]=0)}}}sub n{C=int(rand(P)) ;W=J[C];H=I[C];X=int(A/2)-1 ;Y=0;for F(0..W-1){for D(0..H-1){K[F][D]= C[C][F][D]}}r(int(rand (4)));l&&p}sub c{d:for(D=B;D>=0;D--){for F(0..A-1){G[F][D]||next d}for(D2=D;D2>=0; D2--){for F(0..A-1){G[F][D2]= (D2>1)?G[F][D2-1 ]:0; }}u;}}a ("m=0;0 a=0;37;40 c");print "\n\n".4x" "." "x(A-4). "perltris\n".(" "x4)."--"xA."\n".((" "x3)."|"." "x(A*2)."|\n")xB .(" "x4). "--"xA."\n";n;for(;;) {u;R=chr(1); (S,T)=select(R,U,V, 0.01);if(S) {Z=getc;}else {if($e++>20){Z=" ";$e=0;}else{next;} } if(Z eq "k"){o;r(1);l||r(3);p}; if(Z eq "j"){o;X--;l||X++;p}; if (Z eq "l"){o;X++;l||X--;p};if(Z eq " "){o;Y++;(E=l)||Y--;p;E|| c |c|c|c|c|n||goto g;};if(Z eq "q"){last;}}g: a("a=0 m=".(B+8).";0 " ); system "stty sane"; '; s/([A-Z])/\$$1/g; s/\%\$/\%/g; eval;
きゃー 変態!
と叫びたくなりますね。
でもぐっとこらえて、こいつをコピペしてlinuxの上で走らせてみてください。
どうですか?テトリスできましたか!?
ちなみに操作はj,kで左右移動、lでアイテムを回転。それだけです。
ちょっと整形してみる
さて、この難読コード、ちょっと面白そうだから、分解&整形してみます。
最後のevalの部分をprint $_に変更して実行結果をファイルに出力。それでもってperltidyで整形。
・・・なんと、整形してもなお十分に意味不明なコードでした。読み下すにはだいぶ根性が必要です。
$A = 15; $B = 30; select(stdin); $| = 1; select(stdout); $| = 1; system "stty -echo -icanon eol \001"; for $C ( split( /\s/, "010.010.010.010 77.77 022.020.020 330.030.030 440.044.000 055.550.000 666.060." . "000" ) ) { $D = 0; for $E ( split( /\./, $C ) ) { $F = 0; for $G ( split( "", $E ) ) { $C[$P][ $F++ ][$D] = $G; } $D++; } $J[$P] = $F; $I[ $P++ ] = $D; } %L = split( / /, "m _" . chr(72) . " c 2" . chr(74) . " a _m" ); sub a { for $K ( split( / /, shift ) ) { ( $K, $L ) = split( /=/, $K ); $K = $L{$K}; $K =~ s/_/$L/; printf "%c[$K", 27; } } sub u { a("a=40"); for $D ( 0 .. $B - 1 ) { for $F ( 0 .. $A - 1 ) { $M = $G[$F][$D]; if ( $R[$F][$D] != $M ) { $R[$F][$D] = $M; a( "m" . "=" . ( 5 + $D ) . ";" . ( $F * 2 + 5 ) ); a( "a=" . ( 40 + $M ) . ";" . ( 30 + $M ) ); print " " x 2; } } } a("m=0;0 a=37;40"); } sub r { ($N) = @_; while ( $N-- ) { $Q = $W; $W = $O = $H; $H = $Q; for $F ( 0 .. $Q - 1 ) { for $D ( 0 .. $O - 1 ) { $Q[$F][$D] = $K[$F][$D] } } for $F ( 0 .. $O - 1 ) { for $D ( 0 .. $Q - 1 ) { $K[$F][$D] = $Q[ $Q - $D - 1 ][$F] } } } } sub l { for $F ( 0 .. $W - 1 ) { for $D ( 0 .. $H - 1 ) { ( $K[$F][$D] && ( ( $G[ $X + $F ][ $Y + $D ] ) || ( $X + $F < 0 ) || ( $X + $F >= $A ) || ( $Y + $D >= $B ) ) ) && return 0; } } 1; } sub p { for $F ( 0 .. $W - 1 ) { for $D ( 0 .. $H - 1 ) { ( $K[$F][$D] > 0 ) && ( $G[ $X + $F ][ $Y + $D ] = $K[$F][$D] ); } } 1; } sub o { for $F ( 0 .. $W - 1 ) { for $D ( 0 .. $H - 1 ) { ( $K[$F][$D] > 0 ) && ( $G[ $X + $F ][ $Y + $D ] = 0 ); } } } sub n { $C = int( rand($P) ); $W = $J[$C]; $H = $I[$C]; $X = int( $A / 2 ) - 1; $Y = 0; for $F ( 0 .. $W - 1 ) { for $D ( 0 .. $H - 1 ) { $K[$F][$D] = $C[$C][$F][$D] } } r( int( rand(4) ) ); l && p; } sub c { d: for ( $D = $B; $D >= 0; $D-- ) { for $F ( 0 .. $A - 1 ) { $G[$F][$D] || next d; } for ( $D2 = $D; $D2 >= 0; $D2-- ) { for $F ( 0 .. $A - 1 ) { $G[$F][$D2] = ( $D2 > 1 ) ? $G[$F][ $D2 - 1 ] : 0; } } u; } } a("m=0;0 a=0;37;40 c"); print "\n\n" . 4 x " " . " " x ( $A - 4 ) . "perltris\n" . ( " " x 4 ) . "--" x $A . "\n" . ( ( " " x 3 ) . "|" . " " x ( $A * 2 ) . "|\n" ) x $B . ( " " x 4 ) . "--" x $A . "\n"; n; for ( ;; ) { u; $R = chr(1); ( $S, $T ) = select( $R, $U, $V, 0.01 ); if ($S) { $Z = getc; } else { if ( $e++ > 20 ) { $Z = " "; $e = 0; } else { next; } } if ( $Z eq "k" ) { o; r(1); l || r(3); p } if ( $Z eq "j" ) { o; $X--; l || $X++; p } if ( $Z eq "l" ) { o; $X++; l || $X--; p } if ( $Z eq " " ) { o; $Y++; ( $E = l ) || $Y--; p; $E || c | c | c | c | c | n || goto g; } if ( $Z eq "q" ) { last; } } g: a( "a=0 m=" . ( $B + 8 ) . ";0 " ); system "stty sane";
やばいです。血圧があがりそう。
全面的にグローバル変数まみれ。変数名も故意にわかりづらくしてあります。
また時折まったく意味のない関数や変数がトラップのようにちりばめれていて、まさに難読コードここにありって感じです。
しかし頑張って読んでみると、なんとも味わい深いテクニックが何カ所か発見できます。
注目すべきテクニック
なんともレガシー感あふれるコードですが、以下のポイントが気になりました。
- sttyでコンソールの挙動変更
- printf("\e[0;0H");やprintf("\e[0;37;40m"); でカーソル操作や色設定
- 座標の回転
- selectを使ったループ内でのスピード制御
いいですねぇ。古き良き日の「スクリプト」って感じがします。
時間がある時にでもじっくり追ってみたいコードです。
ちなみに元ソースはココ↓
http://www.colinfahey.com/tetris/tetris_ja.html
テトリスのなにか。なんだかよくわからん。日本語が激しくでたらめ。たぶんまともな日本人には読めません。
なにはともあれ、コンソールの上でテトリスで遊べるようになってヨカッタです。
さぁ、仕事してるフリして Let'sテトリス!