#!/usr/bin/perl
sub r{int rand pop} # Random integer generator, used quite a bit...
$|++; # Causes output autoflush, but also $| is used below for its new value, 1
@g=map{[map{' '}0..$=]}0..25; # sets $g[z][x] to a space for all x and all z, so we're starting with a blank grid.
my$j=pop||2+r(3); # Number of stickmen: user-specified or else at least two but not more than five.
@f=map{ # Each element of @f holds one stickman.
# Each stickman has some private variables:
my$k=my$n=$b++; # $n is the number of this stickman. $k is the number of his current "opponent".
my$z=1; # $z is his height off the ground.
my$x=r$=; # $x is the stickman's horizontal position.
# $= is used for its default value (60) and is the width of the field.
my($q,$v,$e)=my@l=(0,0,0,0);
# @l holds numbers corresponding to the positions of the stickman's four limbs.
# (leftarm, rightarm, leftleg, rightleg).
# 0 means the limb is down, 1 means out, 2 means up.
# $q and $v are velocities (horizontal and vertical respectively).
# $e is this stickman's "state": 0 = resting, 1 = attacking, 2 = fleeing.
my$h=r(9); # $h is "health", a.k.a. "breath", i.e., how long before the dude needs a breather.
$w= {# The variable $w is a decoy; what matters is that this assignment is the
# last thing in the sub and therefore this has reference is the value that
# is returned from the map. i.e., @f is an array of hashrefs, each one
# containing the closures that make the stickman work.
i=>sub {
# i stands for "iteration". This stuff happens once per "turn", for each stickman.
$k=r($j) while($k==$n or!$f[$k]); # Make sure stickman has a valid "opponent".
my$o=$f[$k]; # Prefetch the opponent hashref, for easy access.
$h+=($e?-1:1); # Resting regains breath; attacking and fleeing exhaust it.
if($h<1){$e=0;$k=$n} # If exhausted, rest, and choose a new opponent next iteration.
if($h>6){$e=1} # If we have enough breath, attack.
if(!$z){ # If we're at ground level, we can make decisions about movement...
my$u=($x<$$o{x}->()?1:-1); # Sets $u to the direction toward our opponent.
$u*=(($e>1)?-1:1); # Reverse that direction if we're fleeing.
$q=$e?($u*(r(7))):0; # Set horizontal velocity (0 if resting).
if($e>1){$q||=4} # If we're fleeing, we can't have a 0 horiz. velocity.
if($e and(r(7)>3)){$v=r(6)} # If not resting, there's a chance we might leap.
}
my$t=($$o{z}->()cmp$z)+1; # Target limb position. Up if opponent is up, down if down, out if neither.
$t=0 if($x==$$o{x}->()); # But if we're at the same horizontal position as opponent, put limbs down.
my$s=(($x<$$o{x}->())?1:0); # Selects left or right limbs, depending on opponent's relative position.
for(0,2){ # 0 is the base index into @l for arms; 2 is the base index for legs.
$l[$_+$s]+=($t cmp$l[$_+$s])} # Sets limb positions based on target position ($t) and side ($s).
for(0,2){
$l[$_+($s?0:1)]=0} # Put down the limbs on the other side.
if($z){$v-=2} # Gravity.
$x+=$q;$z+=$v; # Moves horiz. and vert. position based on the velocities.
$z=0 if$z<0; # Nobody falls below the ground.
if($x<2){$x=2;$q*=-1 if $q<0} # Nobody goes past the left edge...
if($x>$=-2){$x=$=-2;$q*=-1 if $q>0} # ... or the right edge.
if ((abs($x-($$o{x}->()))+1)*
(1+abs($z+1-($$o{z}->())))<9) # If we're "pretty close" to our opponent...
{
r(2)?($$o{c}->($n) or $e=1):${$f[$n]}{c}->($k);
# This triggers a conflict. Who "wins" is randomized.
# In the first case, we win and the opponent's collision
# routine is called with our number, and our state is set
# to attack. In the other case, our own collision
# routine is called with our opponent's number.
}
},
d=>sub {
# Draw routine. Places chars on the grid representing this stickman.
$g[20-$z][$x]="O"; # Head
$g[21-$z][$x]="+"; # Torso
$g[22-$z][$x]="|"; # Abdomen
for(0..1){ # 0 and 1 represent the two sides (left and right).
my@c=($_?('/','-',"\\"):("\\",'-','/'));
# On the left, / is down, - out, \ up. On the right it's the reverse.
$g[21-$z][$x+($_?1:-1)] # Upper arm:
=$l[$_]?'-':$c[2]; # - if arm is not at rest; down position otherwise.
$g[22-$z-$l[$_]][$x+($_?2:-2)] # Lower arm:
=$c[2-$l[$_]]; # up, out, or down if arm is up, out, or down.
$g[23-$z][$x+($_?1:-1)] # Upper leg:
=$l[2+$_]>1?'-':$c[2]; # - if limb is up, down view otherwise.
$g[24-$z-($l[2+$_]>1?1:0)][$x+($_?2:-2)] # Lower leg:
=$c[$l[2+$_]>1?1:2]; # - if up, down view otherwise.
}
},
z=>sub{$z}, # the z and x accessors are so that
x=>sub{$x}, # stickmen can get their opponents' positions.
n=>sub{$n=pop}, # Renumbers this stickman; used in cases of collision.
c=>sub { # Collision-recovery routine.
$h=0;$e=2; # Exhausted and fleeing...
$k=pop; # ... from the stickman who just collided with us.
$q=0; # No horizontal motion (initially).
if(!r(7)){ # But there's a small chance the stickman will no longer fight...
splice@f,$n,1; # In which case he's removed from the list,
for(@f){$$_{n}->($h++)} # and the list is renumbered.
}
}
}
}1..$j; # We put one of these stickmen in @f for each number from 1 to $j
while(1){ # Then we loop...
++$iter;# This was a holdover from debugging that I forgot to remove.
if(@f==1){exit 0} # If there's only one stickman left, he wins and we're done.
for$m(@f){ # Each stickman must
$$m{i}->();# do his once-per-loop stuff (an iteration)
$$m{d}->() # and then draw himself onto the grid.
}
print$/x$=; # This is a crude way of clearing the screen.
for $a(1..25){ # We're going to print 25 lines of the grid, starting at the top.
print$/,"|",@{$g[$a]},"|"; # The vertical bars are the left and right edges.
# The array in $g[$a] holds whatever characters the d routines of the stickmen have drawn there.
@{$g[$a]}=map{' '}0..$=; # This resets the line to blank for next
# time around, so the stickmen don't
# leave trails.
}
sleep 1 # I like [diotalevi]'s [select] much better, as the 1-second delay is really too long.
}#Optional command-line argument selects how many ninja stickmen.