In Windows, the Win32::Console::ANSI module is required.
Most will recognize that the picture is based on [Erudil]'s amazing Camel Code, to which this is a tribute.
#!/usr/bin/perl -w
sub j(\$){(
$P,$V)= @_;while($$P=~s:^
([()])::x){ $V+=('('eq$1)?-32:31
}$V+=ord( substr( $$P,0,1,""))-74} sub a{
my($I,$K,$ J,$L)=@_ ;$I=int($I*$M/$Z);$K=int(
$K*$M/$Z);$J=int($J*$M /$Z);$L=int($L*$M/$Z); $G=$
J-$I;$F=$L-$K;$E=(abs($ G)>=abs($F))?$G:$F;($E<0) and($
I,$K)=($J,$L);$E||=.01 ;for($i=0;$i<=abs$E;$i++ ){ $D->{$K
+int($i*$F/$E) }->{$I+int($i*$G/$E)}=1}}sub p{$D={};$
Z=$z||.01;map{ $H=$_;$I=$N=j$H;$K=$O=j$H;while($H){$q=ord
substr($H,0,1,"" );if(42==$q){$J=j$H;$L=j$H}else{$q-=43;$L =$q
%9;$J=($q-$L)/9;$L=$q-9*$J-4;$J-=4}$J+=$I;$L+=$K;a($I,$K,$J,$ L);
($I,$K)=($J,$L)}a($I,$K,$N,$O)}@_;my$T;map{$y=$_;map{ $T.=$D->{$y}
->{$_}?$\:' '}(-59..59);$T.="\n"}(-23..23);print"\e[H$T"}$w= eval{
require Win32::Console::ANSI};$b=$w?'1;7;':'';($j,$u,$s,$t,$a,$n,$o
,$h,$c,$k,$p,$e,$r,$l,$C)=split/}/,'Tw*JSK8IAg*PJ[*J@wR}*JR]*QJ[*J'.
'BA*JQK8I*JC}KUz]BAIJT]*QJ[R?-R[e]\RI'.'}Tn*JQ]wRAI*JDnR8QAU}wT8KT'.
']n*JEI*EJR*QJ]*JR*DJ@IQ[}*JSe*JD[n]*JPe*'.'JBI/KI}T8@?PcdnfgVCBRcP'.
'?ABKV]]}*JWe*JD[n]*JPe*JC?8B*JE};Vq*OJQ/IP['.'wQ}*JWeOe{n*EERk8;'.
'J*JC}/U*OJd[OI@*BJ*JXn*J>w]U}CWq*OJc8KJ?O[e]U/T*QJP?}*JSe*JCnTe'.
'QIAKJR}*JV]wRAI*J?}T]*RJcJI[\]3;U]Uq*PM[wV]W]WCT*DM*SJ'. 'ZP[Z'.
'PZa[\]UKVgogK9K*QJ[\]n[RI@*EH@IddR[Q[]T]T]T3o[dk*JE'. '[Z\U'.
'{T]*JPKTKK]*OJ[QIO[PIQIO[[gUKU\k*JE+J+J5R5AI*EJ00'. 'BCB*'.
'DMKKJIR[Q+*EJ0*EK';sub h{$\ = qw(% & @ x)[int rand
4];map{printf "\e[$b;%dm",int(rand 6)+101- 60*
$w;system( "cls")if$w ;($A,$S)= ($_[1], $
_[0]);($M, @,)= split '}';for( $z=256
;$z>0; $z -=$S){$S*= $A;p @,} sleep$_
[2];while ($_[3]&&($ z+=$ S) <=256){
p@,}}("". "32}7D$j" ."}AG". "$u}OG"
."$s}WG" ."$t","" ."24}(" ."IJ$a"
."}1G$n" ."}CO$o" ."}GG$t" ."}QC"
."$h}" ."^G$e" ."})IG" ."$r",
"32}?" ."H$p}FG$e}QG$r". "}ZC"
."$l", "28}(LC" ."" ."".
"$h}:" ."J$a}EG". "$c"
."}M" ."C$k}ZG". "$e"
."}" ."dG$r","18" ."}("
."D;" ."$C" )}{h(16 ,1,1,0
);h(8, .98,0,0 );h(16 ,1,1,1)
;h(8.0 ,0.98,0, 1); redo}###
#written 060204 by
#liverpole @@@@@@@
#@@@@@@@@@@@
you totally broke my putty window. it's light blue. i was hoping i'd get a clue as to what you're doing but just running it in a putty window to a BSD box. (no, no activestate on this windows box, rarely use it). but no. you gotta go breakin' stuff.
$/ = q#(\w)# ; sub sig { print scalar reverse join ' ', @_ } sig
map { s$\$/\$/$\$2\$1$g && $_ } split( ' ', ",erckha rlPe erthnoa stJu" );
Did you read the Important Note, in red, at the top, which says to run it in an xterm (Linux) or 'cmd' shell (Windows)? Those are the two OS's that I tested the script on.
absolutely awesome++
Apart from the visual effect of the japh while running, I'm also impressed with the way the code renders with my (vim) syntax highlighting - was that deliberate?
For the record, I ran it in a PuTTY window, through which I'm ssh'd to my local (debian) linux box - and it works perfectly :)
I haven't tried it in an xterm because I don't run X :)
I'm glad to see you're a fellow vim user. I did notice the highlighting, but no, I didn't do it on purpose, it was just a coincidence. I was more concerned with getting the text format right. I was actually surprised to find that I could get the whole thing into a "camel-sized" image ... the original program was almost 200 lines long.
If nobody else publishes a "spoiler" in the near future, I may go ahead and submit the original program, along with an explanation.
#!/usr/bin/perl -w
#
#
# Notes for Perlmonks:
#
# 1. Runs in both Windows and Linux
# 2. Terminal must be at least 120 wide by 50 tall
#
# Steps to making a 3D moving image:
#
# 1. Create a description of the polygons (defined by line segments)
# 2. Project those lines into one screen "frame"
# 3. Display the frame
# 4. Zoom the polygons through the different planes on the Z axis
# 5. Obfuscate the program
#
# Notes on the data encryption algorithm:
#
# 1. The first point is an absolute offset from the center of the screen.
# The subroutine decode_point() strips off the next character from the
# current set of polygons, turning it into an x or y point as needed.
# If the next character starts with one or more of '(' or ')', the
# value -32 or 31 is respectively added, to increase the range for
# starting points with are too far from the center. Otherwise, each
# x or y value can be a value between -32 (ord('*') - 74) and 48
# (ord('z') - 74).
#
# 2. The second point (and successive ones) for each polygon is calculated
# relative to the previous point. If the difference between the x and
# y pairs is each within the range (-4 ... 4), a single character is
# used to encode both x and y of the new point. Otherwise, a '*' is
# used as an 'escape' to denote that the next 2 characters will be used
# (as in step 1) to signify a larger delta distance.
#
# Strict
use warnings; # Warnings can remain in the final version, but 'strict'
use strict; # will unfortunately have to be dropped, since most of the
# 'my' variables will be replaced by globals (to save space).
#
# Globals
#
my ($minx, $maxx) = (-59, 59);
my ($miny, $maxy) = (-23, 23);
my $pscreen = {};
my $pixel;
my $lens;
my $zoom;
my $zoom_factor;
#
# Set $is_windows to 0 or 1 based on existence of the Win32::Console::ANSI
# module, which is only needed for (and available under) Windows.
#
my $is_windows = eval { require Win32::Console::ANSI };
my $escape = $is_windows ? '1;7;' : '0';
#
# Definitions of the polygons for each letter (and the final camel). Each
# animated letter is defined by a polygon. That polygon is a starting point
# relative to the center of the screen, followed by a set of points relative
# to the last.
#
my ($j, $u, $s, $t, $a, $n, $o, $h, $c, $k, $p, $e, $r, $l, $camel) = split(/}/, 'Tw*JSK8IAg*PJ[*J@wR}*JR]*QJ[*JBA*JQK8I*JC}KUz]BAIJT]*QJ[R?-R[e]\\RI}Tn*JQ]wRAI*JDnR8QAU}wT8KT]n*JEI*EJR*QJ]*JR*DJ@IQ[}*JSe*JD[n]*JPe*JBI/KI}T8@?PcdnfgVCBRcP?ABKV]]}*JWe*JD[n]*JPe*JC?8B*JE};Vq*OJQ/IP[wQ}*JWeOe{n*EERk8;J*JC}/U*OJd[OI@*BJ*JXn*J>w]U}CWq*OJc8KJ?O[e]U/T*QJP?}*JSe*JCnTeQIAKJR}*JV]wRAI*J?}T]*RJcJI[\\]3;U]Uq*PM[wV]W]WCT*DM*SJZP[ZPZa[\\]UKVgogK9K*QJ[\\]n[RI@*EH@IddR[Q[]T]T]T3o[dk*JE[Z\\U{T]*JPKTKK]*OJ[QIO[PIQIO[[gUKU\\k*JE+J+J5R5AI*EJ00BCB*DMKKJIR[Q+*EJ0*EK');
#
# Given a reference to the current shape (polyogon), extracts and returns the
# next (x,y) coordinate point.
#
sub decode_point (\$) {
my ($P) = @_;
my $V = 0;
while ($$P =~ s/^([()])//x) {
$V += '(' eq $1 ? -32 : 31;
}
$V += ord(substr $$P, 0, 1, '') - 74;
}
#
# Given two points (x0,y0) and (x1,y1), uses a very simple algorithm
# to construct a line between those points.
#
sub create_line {
my ($x0, $y0, $x1, $y1) = @_;
$x0 = int $x0 * $lens / $zoom_factor;
$y0 = int $y0 * $lens / $zoom_factor;
$x1 = int $x1 * $lens / $zoom_factor;
$y1 = int $y1 * $lens / $zoom_factor;
my $G = $x1 - $x0;
my $F = $y1 - $y0;
my $E = abs $G >= abs $F ? $G : $F;
($x0, $y0) = ($x1, $y1) if $E < 0;
$E ||= 0.01;
for (my $i = 0; $i <= abs $E; ++$i) {
$$pscreen{$y0 + int($i * $F / $E)}{$x0 + int($i * $G / $E)} = 1;
}
}
#
# Given a set of points representing a single polygon, calculates and draws
# all of its lines "into" the current frame, and displays the frame.
#
sub create_polygons {
my (@points) = @_;
$pscreen = {};
$zoom_factor = $zoom || 0.01;
my ($nextx, $nexty);
map {
my $polygon = $_;
my $x0 = my $x1 = decode_point($polygon);
my $y0 = my $y1 = decode_point($polygon);
while ($polygon) {
my $q = substr($polygon, 0, 1, '');
if ($q eq '*') {
# Special case -- next (x,y) point is encoded
# into 2 distinct characters
#
$nextx = decode_point($polygon);
$nexty = decode_point($polygon);
}
else {
# Usual case -- convert a single character into an (x,y) point
# relative to the previous point, where the absolute value of
# the delta x or y values is a maximum of 4.
#
$q = ord($q) - 43;
$nexty = $q % 9;
$nextx = ($q - $nexty) / 9;
$nexty = $q - 9 * $nextx - 4;
$nextx -= 4;
}
$nextx += $x0;
$nexty += $y0;
create_line($x0, $y0, $nextx, $nexty);
($x0, $y0) = ($nextx, $nexty);
}
# Reconnect the final point with the first point
create_line($x0, $y0, $x1, $y1);
} @points;
# Render the frame
my $frame;
map {
my $y = $_;
map {
$frame .= $$pscreen{$y}{$_} ? $pixel : ' '
} ($minx..$maxx);
$frame .= "\n"
} ($miny..$maxy);
# Display the next frame
print "\e[H$frame";
}
#
# Animate each of the "words" (plus a final camel's image). A random
# "pixel" is chosen for all of the lines, as well as a random color.
#
sub animate {
# Choose one of 4 random pixels
$pixel = ('%', '&', '@', 'x')[int rand 4];
map {
my ($speed, $acceleration, $nseconds, $reverse_zoom) = @_;
my @points;
($lens, @points) = split(/}/, $_);
printf "\e[$escape;%dm", int(rand 6) + 101 - 60 * ($is_windows || 0);
$is_windows and system("cls");
#
# Set the Z coordinate to values from 256 to 0, varying by $speed.
# This causes the object to 'zoom' towards us, as the 'lens' which
# is our terminal screen is at (z=0).
#
for ($zoom = 256; $zoom > 0; $zoom -= $speed) {
$speed *= $acceleration;
create_polygons(@points);
}
sleep $nseconds;
while ($reverse_zoom and ($zoom += $speed) <= 256) {
create_polygons(@points);
}
} (
"32}7D$j}AG$u}OG$s}WG$t",
"24}(IJ$a}1G$n}CO$o}GG$t}QC$h}^G$e})IG$r",
"32}?H$p}FG$e}QG$r}ZC$l",
"28}(LC$h}:J$a}EG$c}MC$k}ZG$e}dG$r",
"18}(D;$camel"
);
}
while (1) {
animate(16, 1, 1, 0);
animate(8, 0.98, 0, 0);
animate(16, 1, 1, 1);
animate(8, 0.98, 0, 1);
}
Each word ("Just", "another", "Perl", "hacker") is made up of polygons, where the final point is automatically reconnected to the starting point. The final "camel" is a single, huge polygon. Each polygon is encoded into a string of ascii characters. The first (x,y) point is saved as an offset relative to the center of the screen, and successive points are calculated as the relative (x,y) distance from the previous point.
When the animate function is called, it cycles through each set of polygons, decoding the ascii characters into the individual points which represent each line of each polygon. For each plane of the Z-axis (representing distance away from the viewer), the (x,y) points are all scaled to conform to that distance. Once all the lines have been packed into the frame, the frame is displayed, and causes the word to appear as though it is zooming closer or farther away from the viewer. I found that I had to "slow down" the characters if they were zooming "past" the viewer, otherwise they were too hard to read (hence the "acceleration" variable).
Originally, the data for the points was almost twice as large as the final version, and I wanted to compress it further, so I used a mechanism whereby each ascii character represented a value from 0 to 80, for a total of 81 combinations, which, for any point of a polygon which was not the first, could represent both the x and y value, by taking the delta-x and delta-y with the previous (x,y) point, as long as both deltas were within the range (-4 ... 4). (Since the full range of 9 values, squared, equals 81). If this were not the case for any given point, the ascii character '*' was used as an 'escape' character to revert to using the next 2 ascii characters to encode the point.
It was a lot of fun learning how to create a simple 3D animation. I hope you enjoyed watching it!
If your terminal/putty/shell window stays colored after running this (as mine does, which is a little annoying :) and you're not familiar with shell escapes for colorizing foregrounds/backgrounds of terminal/putty/shell windows (as I wasn't), be sure to have on of these handy for resetting your colors to the way they were:
perl -e 'printf "\e[0%dm", 0'
:)
$/ = q#(\w)# ; sub sig { print scalar reverse join ' ', @_ } sig
map { s$\$/\$/$\$2\$1$g && $_ } split( ' ', ",erckha rlPe erthnoa stJu" );
You can make it even easier to remember by golfing it to: perl -e 'print "\e[m"'
perlmonks.org content © perlmonks.org and acid06, chargrill, liverpole, McDarren, Ray Smith, wazoox, wulvrine
prlmnks.org © 2006 edmund von der burg (eccles & toad)
v 0.03