#!/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);
}