In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Now you too can understand how Arena Shooting in 100 Lines works

UPDATE: Now the code is fully commented, 
I have finished the remaining parts.
by the way it is nice to fit so much functionality into 100 lines
Hats off keim_at_Si!
What I have done to the code:
- renaming variables/functions
- explaining constructs
- moving functions outside the main constructor
What I haven't done:
- modifying the internal logic
- refactoring. (I am trying to perserve the original forms) 
Arena Shooting in 100 Lines.
Movement;[Arrow key], Shot;[Ctrl], Slow & Head fix;[Shift]
/**
 * Copyright kalevionni ( http://wonderfl.net/user/kalevionni )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/xMWC
 */

// UPDATE: Now the code is fully commented, 
// I have finished the remaining parts.

// forked from Arena Shooting in 100 Lines
// by the way it is nice to fit so much functionality into 100 lines
// Hats off keim_at_Si!

// What I have done to the code:
// - renaming variables/functions
// - explaining constructs
// - moving functions outside the main constructor

// What I haven't done:
// - modifying the internal logic
// - refactoring. (I am trying to perserve the original forms) 


// Arena Shooting in 100 Lines.
//  Movement;[Arrow key], Shot;[Ctrl], Slow & Head fix;[Shift]
package {
import flash.display.*;
import flash.events.*;
import flash.filters.*;
import flash.geom.*;
import flash.text.*;
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#ffffff")]

public class HundredLinesArena extends Sprite {
	
	// title text
    private var title_text:TextField = new TextField();
    
    // used in enterframeEnemy
    private var level:int=0;    
	//player movieclip
    private var player:MovieClip; 
	//field?
    private var field:Sprite = stage.addChild(new Sprite()) as Sprite;
    //array for bullet movieclips
    private var bullets:Array=[];
    //array for contatining shot movieclips
    private var shots:Array=[];
//    private var ii:*;

	//glows are placed into an array because they will
	//be assigned as the filter property of movieclips 
    private var blueglow:Array = [new GlowFilter(0x0000ff)]; // player and shot
    private var redglow:Array = [new GlowFilter(0xff0000)]; // enemy bullet


    private var score:int=0;
    
	// ever increasing frame count
    private var cnt:int = 0;
	// a bit field for keys
    private var keys:uint = 0;
	//keymap
    private var kmap:Array=[32,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,4,8];
    
	//this vector server as the direction for the player
    private var unit_vector:Point = new Point(1,0);
    

	
	// general movieclip setter
	// mc: the movieclip object
	// props: object that will be used to set the properites of the mc
	// color: line color
	// commands: array of 1s and 2s. 1 == moveto, 2 == lineto
	// vertices: for every command there is a number pair in this array (a coordinate)

	// example:                         moveto   lineto  lineto  lineto  lineto
	// commands: [1,2,2,2,2], vertices: [-1,-1,   1,-1,   1,1,   -1,1,   -1,-1]
	// this draws a rectangle
	private function $(mc:*, props:*,color:uint = 0, 
	                  commands:Array = null, vertices:Array = null) : void {
			
		//adding the props' object properties and values to the mc movieclip
		for (var p:String in props) mc[p] = props[p];
		
		// setting the linestyle color
		if (color) mc.graphics.lineStyle(1, color, 1, false, "normal", null, null, 3);
		
		// uses the commands and the vertices arrays to pass them to drawPath
		if (commands) mc.graphics.drawPath(Vector.<int>(commands), Vector.<Number>(vertices), "nonZero");
		// http://livedocs.adobe.com/flex/3/langref/flash/display/Graphics.html#drawPath()
		
		// The enterframe function of the movieclip
		if (props.fn) mc.addEventListener(Event.ENTER_FRAME, props.fn);
		
		// if props has an 'ar' propery (which is an array)
		// mc will be added to the end of this
		if (props.ar) 
			mc.ar.push(mc);
	}		

        //enter frame event for the player object
    private function enterframePlayer(e:Event) : void {   // player

        // 00100000b = 32 dec is the bit for the shift key
        // when shift is not pressed r = 9,
        // when shift pressed r = 6
        // r will be the amount of movement 
        var r:Number = (keys & 32)?6:9;
		
		// man... I hope your collagues don't have to read your code. so...
		// in the keys     00000100b == 4dec is the bit for the right key 
		//                 00000001b == 1dec is the bit for the left key
		//                 00001000b == 8dec is the bit for the down key
		//                 00000010b == 2dec is the bit for the up key
		//
		//   example: (keys & 4) is the bitwise AND operation.
		//            also called masking. In this case 00000100b 
		//            binary field is used. This is a mask which selects
		//            the bit for the right key.

		//   the >> is the bit shift operator.
		//   here you can see why shifting by two is used together with the (keys & 4)
		//   bitmask. the (... >> 2) shifts the position 2 bit to the position 0.
		//   after the shifting the value of ((keys&4) >> 2) is either 0 or 1 depending on the bit.
		
		var keydir:Point = new Point((( (keys & 4) >> 2) - (keys & 1)) * r, 
		                             (((keys & 8) >> 3) - ((keys & 2) >> 1)) * r);
		// summary: keydir.x = 9  when right is pressed
		//          keydir.x = -9 when left is pressed
		//          keydir.y = 9 when down is pressed
		//          keydir.y = -9 when up is pressed
		//  we have 6 instead of 9 when the shift is hold.  
		
		
		
		
		// if the future position player.x+keydir.x
		// is both bigger than -390 and less then 390 then
		// add the player position the difference
		// essentially this part is responsible for
		// ensuring that the player doesn't wander outside the map
        player.x += (player.x+keydir.x>-390 && player.x+keydir.x<390) ? keydir.x : 0;
        player.y += (player.y+keydir.y>-390 && player.y+keydir.y<390) ? keydir.y : 0;


		// to make sure the player stays in the center we have to move the field too
        //this would place the player to the top left corner:
        //field.x = (-player.x);         //field.y = (-player.y);
         
		//we need the 232 offset, (approx half of the 465 witdth and height)
		//to place the player to the center :
		//field.x = (232-player.x); field.y = (232-player.y);
		
		//this is the same as:
		//field.x += (232-player.x)-field.x;
		//think of that by adding the amount on the right we 
		//move the map closer to its ideal position
		
		// when we enclose this into a paren ()*0.1, we say that 
		// by each frame we move 10% closer to the ideal position
		// that helps easing the movement.
		
        field.x += ((232-player.x) - field.x)*0.1;
        field.y += ((232-player.y) - field.y)*0.1;        

        
        
        // if the shift is not pressed
        if (!(keys&32)) {
        	
        	//unit_vector stores the direction of the player
        	// offset actually adds to the x and y coordinates
        	//       dir.x  ==      -9, 0, 9
        	// dir.x * 0.05 ==   -0.45, 0, 0.45
        	
        	
        	// this is how the smooth turning works.
        	// by always adding(the offset) the keydir*0.05 (-0.45,+0.45) and normalizing it,
        	// the unit_vector will get closer to the keydir direction with each frame.
        	// http://en.wikipedia.org/wiki/Euclidean_vector#Addition_and_subtraction
        	// here keydir is the vector defined by the arrow keys
        	// and the unit_vector is the actual direction of the player
            unit_vector.offset(keydir.x*0.05, keydir.y*0.05);
            unit_vector.normalize(1); // make vector length 1
            
            // it is an interesting question why the player can do a 180 degree
            // turn after advancing in one direction.
            // the answer is simple: after the first turn the unit vector perserves
            // a little bit of the previous direction. This is because the fact, that by
            // adding to one (I) vector to another (J) many times won't make (J) point to
            // the exact same direction as (I). So, when you see your player advancing to
            // the right, the unit_vector.x is usually not exactly 1.
            
            // Ofcourse if you don't add that another direction into your unit-vector
            // the 180 degree turn won't be possible. This is the reason that right after
            // start when you press the LEFT key, the player won't do the 180 deg turn.
            
            // to investigate the issue more uncomment the following line (I have added
            // my wonderfl_trace to the code) then move left and right, notice that x 
            // remains 1, now move up and down a bit and see how it works
            // trace("unit_vector.x: " + unit_vector.x)
             
			
			//rotates the player into direction
            player.rotation = Math.atan2(unit_vector.y, unit_vector.x)*(180/Math.PI);
        }
		
		// kf&16 is true when CTRL is down
		// cnt is increased by one on every frame
		// cnt&1 is true if the first bit of cnt is 1 and that happens every other frame

        if (!title_text.visible && (keys&16) && (cnt&1)) {    // create shot
            var shotprops:Object=
		      {x:player.x,y:player.y,    //same pos as player
			  v:new Point(unit_vector.x*24,unit_vector.y*24), 
			  						     // v for commonMoveObject, the perframe move
			                             // go same direction as player, 24 unit/frame
			                             
			  rotation:player.rotation,  // face the same direction as player
			  r:0,                       // rotation per frame is none (needed by commonMoveObj)
			  filters:blueglow,          // ... yeah. right.
			  fn:commonMoveObject,       // use the commonMoveObject
			  ar:shots};                 // add every shot to the shots array
            					  
            //now let's draw our rectangle-buster, blue beams, this should be obvious
            $(field.addChild(new MovieClip()), shotprops, 0x80ffff, [1,2,1,2], [6,6,-6,6,6,-6,-6,-6]);
        }
    }
        
	private function enterframeExplosion(e:Event) : void {   // explosion
	    if ((e.target.alpha = (--e.target.cnt)*0.03) == 0) kill(e.target);
	    else {
	        e.target.v.x *= 0.95; // v is the velocity vector for the partice. 
	        e.target.v.y *= 0.95; // this is code for slowing the particles to 95% every frame
	        commonMoveObject(e);
	    }
	}

	
    private function commonMoveObject(e:Event) : void {   // common motion
		// used for movieclips with both an r (rotation per frame)
		// and a v.x & v.y property (movement per frame)
		
		// here event.target is the movieclip which the event belongs to
		// the r propery is set by the $ function and it is the per frame rotation
		// for a particular object
        e.target.rotation += e.target.r;
		
		// v is a vector which has an X and an Y propery
		// and it defines the per frame movement for the object
        e.target.x += e.target.v.x;
        e.target.y += e.target.v.y;
		
		// objects wandering outside the x[-400;+400] y[-400;+400] territory will be killed.
        if (e.target.x<-400 || e.target.y<-400 || e.target.x>400 || e.target.y>400) kill(e.target);
    }

	// The distance between t and every object in the list array
	// will be calculated. If that distance is lower then
	// the sqare root of r2 (r2 == distance^2) then
	// t.life will be decreased
	// if t.life falls below 1, hit returns true
    private function hit(t:*, list:Array, r2:Number) : Boolean {   // hit evaluation
    
    
    // t.l seems to be the life of the t object
    // 
    //                                                             /|
    // http://en.wikipedia.org/wiki/Pythagorean_theorem         /   |
    // a^2 = (list[i].x-t.x)*(list[i].x-t.x)               c /      | b
    // b^2 = (list[i].y-t.y)*(list[i].y-t.y)              /         |
    // c^2 = r2                                        /____________|
    //                                                        a
    // condition (a^2 + b^2 < r2)
    // is the same as (square_root(a^2 + b^2) < sqaure_root(r2)) where
    // square_root(a^2 + b^2) is the distance between the 2 points
    //
    // The condition in the if is essetially responsible for calculating 
    // the distance between the t object and each element in the list array
    // if the distance is less then square_root(r2)
    // then kill the colliding object in the list
    // and decrease the life of the t object
    // if life falls below 1, return ture 
    
    
        for (var i:int = 0; i<list.length; i++) // iterating over the list.
            if ((list[i].x-t.x)*(list[i].x-t.x)+(list[i].y-t.y)*(list[i].y-t.y) < r2) {
                kill(list[i]);
                if (--t.life <= 0) return true;
            }
        return false;
    }

    private function kill(mc:*) : void {   // kill object
		
		// a very ugly way to detach the movieclip from its parent and remove its
		// enterframe event in one step
        mc.parent.removeChild(mc).removeEventListener("enterFrame", mc.fn);
		
		// if the mc has an array element (which also means that it is part of that array)
		// then remove
        if (mc.ar) mc.ar = mc.ar.splice(mc.ar.indexOf(mc), 1);
    }

    private function enterframeEnemy(e:Event) : void {   // enemy
    var i:int;
        e.target.cnt++; // increase the cnt propery of the enemy. cnt stores the number
                        // of frames an enemy is staying on the scrren
                        
        // (!title_text.visible): if the game is on 
        // !(e.target.cnt % (80-level)): 
        // and if the enemy's frame counter (e.target.cnt) is divisible by (80-level)
        // % is the division-remainder operator, the remainder is 0 when the stuff is divisible.
        // "!" inverts truthvalue
        // so, if level is 0 then this will be true every 80th frame
        // by each level the bullets are coming more and more rapidly            
        if (!title_text.visible && !(e.target.cnt % (80-level))) {  // create bullet
            var v:Point = new Point(player.x - e.target.x, player.y - e.target.y); 
            // v is pointing to the player
            
            //http://livedocs.adobe.com/flex/3/langref/flash/geom/Point.html#normalize()
            v.normalize((Math.random()*0.06+0.03)*(50+level)); 
						   // random and level defines the length of the vector
                           // pointing to the player
                           // and this lenth will be used to set the per frame
                           // advancing
            
            var bulletprops:Object =  
            {x:e.target.x, y:e.target.y, // parent pos 
            v:v,                         // previous v
            r:-5,                        // slow counterclockwise rotation
			fn:commonMoveObject,         // give it the commonMoveObject as enterframe event 
			ar:bullets,                  // place it to the bullets array 
			filters:redglow};            //glow glow
            
            // I have detailed quite a few of these calls, check the $ function and other $ calls.						
            $(field.addChild(new MovieClip()), bulletprops, 0xffffff, [1, 2, 2, 2, 2], [3,3,-3,3,-3,-3,3,-3,3,3]);
        }
        
        // check the enemy and the shots for collision.
        // if shot distance <= 20 then destroy, check the description of hit.
        if (hit(e.target, shots, 400)) {  // destruction
            kill(e.target);
            
            // in the following line increase the score
            level = (++score<30) ?                //if score < 30  then 
            				(score*2) :           //level = score * 2
            				   (score<60) ?       //else if score < 60 then
            				     (score*0.5+45)   // level = score*0.5+45
            				          : 75;       // else level = 75
            				          

			// now an enemy dies. here is the part for creating the nice explosion effect            				          
            for (i=0; i<8; i++) {   // create particles (8)
                v = new Point(Math.random()*16-8, Math.random()*16-8); 
                //each around the enemy x:+-8 y:+-8
                
                var particleprops:Object = 
					{x:e.target.x, y:e.target.y, //same position as enemy 
					v:v,  // same velocity vector
					r:20, // 20 rotation (have you noticed? they are all rotating clockwise)
					fn:enterframeExplosion, // give them their enterFrame function 
					cnt:30, // this is the count used to control the fadeout effect
					filters:blueglow}; //.. yeah yeah. Nice glow!
                							
                //if this is not trivial, check out other descriptions of the $ call.  							
                $(field.addChild(new MovieClip()), particleprops, 0x80ffff, [1, 2, 2, 2, 2], [5,5,5,-5,-5,-5,-5,5,5,5]);
            }
        }
        // the enemies user the commonMoveObj to rotate (r per frame) and 
        // move towards their (v.x, v.y) direction
        
        // every frame
        commonMoveObject(e);
    }

    function HundredLinesArena() {
    	WTrace.initTrace(stage);
    	
        var i:int;

		// score board
        var scoreboard:TextField = new TextField();


        
        $(title_text,{
            x:180,
            y:230,
            htmlText:"<font color='#406080' size='40' face='_sans'>100Lines Arena</font>",
            width:282
            });
        
        
        addChild(scoreboard);
        addChild(title_text);
        
        // check out bitwise operators to understand how this works.
        // in short: each bit is responsible for storing a key status
        // later you find some more detail, but this is fairly easy to
        // understand if you know the operators and binary arithmetic
        stage.addEventListener(KeyboardEvent.KEY_DOWN,
                     function(e:KeyboardEvent) : void { keys |=  kmap[e.keyCode-16]; }); //set bit
                               
        stage.addEventListener(KeyboardEvent.KEY_UP,  
                     function(e:KeyboardEvent) : void { keys &= ~kmap[e.keyCode-16]; }); //clear bit
        
		// Enter Frame Function
		addEventListener(Event.ENTER_FRAME, 
            function(e:Event) : void {
               scoreboard.htmlText="<font color='#80c0ff' size='20' face='_sans'>Score:" + String(score) + "</font>";
            if (title_text.visible && cnt>30 && keys!=0) title_text.visible = Boolean(level = score = 0);
            
            // yuck!
            // first, when the title_text is not visible it means that the game 
            // is on (!title_text.visible)
            
            // then we have (title_text.visible = !(player.visible = !hit(player, bullets, 36)))
            // lets start from the inside
            // player.visible = !hit(player, bullets, 36)
            // hit compares the distance^2 of each bullet to the player 
            // so, if a bullet is 6 unit or closer to the player, 
            // then the hit returns true (otherwise hit returns false)
            
            // when hit returns true, the player.visible gets false
            // and then title_text.visible gets true.
            // and this is our second condition.
            // to roll it  back: 1. when the game is on (title_text invisible)
            //                   2. and the player gets a hit (hit returns ture)
            //                   3. then hide player (!true)
            //					 4. then show the title_text (game off)
            //                   5. reset cnt (the global frame count)
            if (!title_text.visible && (title_text.visible = !(player.visible = !hit(player, bullets, 36)))) cnt = 0;
			

			// cnt is ever growing
			// cnt&31 masks all the bits above the 5th bit, since & is the logical AND operator
			// 31 dec == 11111 bin
			// this means that cnt&31 selects the value of the first 5 bit of cnt
			// !(++cnt)&31 evaulates to true when (++cnt)&31 == 0, that is all the 5 bits are zero
			// this happens every 32th call of (++cnt)&31, approx every sec with framerate 32
			
            if (!((++cnt) & 31) && !title_text.visible) {

				// r is a random number between 0-32
                var r:Number = Math.random() * 32;
				
				//The x position of the enemy depends on r
				//summary:
				// this expression
				// has 4 possible outcomes
				// for the x depending on the value of r
				// r[24..32[ : x = 0
				// r[16..24[ : x = 8
				// r[8..16[  : x = r - 8  //means x gets a random value between [0..8[
				// r[0..8[   : x = r      //means the same thing
                var x:Number = (r >= 24) ? 							// if r >= 24 then
											0: 						//  x = 0
											(r >= 16) ? 			// else if r >= 16
													8:				//  x = 8 
													(r >= 8) ?      // else if r >=8
														(r - 8) :   //  x = r-8
														r;          // else x = r
				
				// another one of these ugly bastards
                var y:Number = (r<8) ? 								// if r < 8 then
            						0:								//  y = 0
            						(r<16)?							// else if r < 16 then
            							8:							//  y =8
            							(r<24)?						// else if r < 24 then
            								(r-16):					//  y = r - 16
            								(r-24);					// else y = r - 24
            								
            	// if you look closely you will notice that the above stuff makes
            	// sure that one position will be on the side of the map
                
                var enemyprops:Object = 
						{x:x*100-400, y:y*100-400, 
						v:new Point(4-x,4-y), //direction of the enemy, 
											// ensures it proceeds towards the inner side of the map  
						fn:enterframeEnemy, // enemy enterframe
						cnt:0, 				// enemy cnt: number of frames he has been on the screen
						r:5,				// enemy rotation per frame 
						life:3,	 			// enemy life, decrease with every hit
						filters:redglow};	// enemy glow
                $(field.addChild(new MovieClip()), enemyprops, 0xffff80, [1, 2, 2, 2, 2], [20,20,  20,-20,  -20,-20,  -20,20,  20,20]);
            }
        });
        
        // creating the 8x8 field
        for (i=-400; i<=400; i+=100) 
               //$(field, {rotationX:-30}, 0x80c0ff, [1,2,1,2], [-400,i,400,i,i,-400,i,400]);
               $(field, 
			     {rotationX: -30 }, // -30: the tilt of the map
				                    // you can add z: 500 to increse the view distance
									// remove or decrese the rotation by changing rotationX
									
				 0x80c0ff,          // color
				 [1,2,1,2] // 1 == moveto, 2 == lineto
				  , 
				 [ -400, i, //moveto   //
				   400, i,  //lineto
				   i, -400, //moveto
				   i, 400]  //lineto
				 );
        
		
			   
		//This is the creation and the setting up of the player object
		//me is the variable which holds it
		
		// field is a movie clip (field)
		// field.addChild return the object
		// which gets passed to $
        $(  field.addChild(player = new MovieClip()), 
		    {fn:enterframePlayer,	// enter frame function 
		    life:0, 				// life. you can use 1 too. its the same. hit checks if life <= 0
		    						// so, the player dies after the hit anyways
		    						// I guess he used l:0 to make it harder to understand what it is ;)
		    filters:blueglow }, 	// filter
			0x80ffff, //color
			[1,2,2,2], //commands: 1 == moveto, 2 == lineto
			
			 [ -9, 6,   //moveto
			   -9, -6,  //lineto
			    9, 0,   //lineto
			   -9, 6]   //lineto
			);
        
  
    } // function HundredLinesArena (constructor)
} // class
} // package

///////  WONDERFL TRACE //////
import flash.display.Sprite;
import flash.display.Stage;
import flash.text.TextField;
import flash.text.TextFormat;


//global trace function
var trace:Function;

//wtreace class
class WTrace
{
        private static var FONT:String = "Fixedsys";
        private static var SIZE:Number = 12;
        private static var TextFields:Array = [];
        private static var trace_stage:Stage;
        
        public static function initTrace(stg:Stage):void
        {
            trace_stage = stg;
            trace = wtrace;
        }
        
        private static function scrollup():void
        {
            // maximum number of lines: 100
            if (TextFields.length > 100) 
            {
                var removeme:TextField = TextFields.shift();
                trace_stage.removeChild(removeme);
                removeme = null;
            }
            for(var x:Number=0;x<TextFields.length;x++)
            {
                (TextFields[x] as TextField).y -= SIZE*1.2;
            }
        }
    
        public static function wtrace(... args):void
        {
        
            var s:String="";
            var tracefield:TextField;
            
            for (var i:int;i < args.length;i++)
            {
                // imitating flash:
                // putting a space between the parameters
                if (i != 0) s+=" ";
                s+=args[i].toString();
            }
            

            tracefield= new TextField();
            tracefield.autoSize = "left";
            tracefield.text = s;
            tracefield.y = trace_stage.stageHeight - 20;

            var tf:TextFormat = new TextFormat(FONT, SIZE);
            tracefield.setTextFormat(tf);
            trace_stage.addChild(tracefield);
            scrollup();                      
            TextFields.push(tracefield);
            
        }
}