#!/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.