//=============================================================================
// AI reference book: "玩 Lode Runner 學 C 語言"
// http://www.kingstone.com.tw/book/book_page.asp?kmcode=2014710650538
//=============================================================================

var movePolicy = [ [0, 0, 0, 0, 0, 0], //* move_map is used to find *//
                   [0, 1, 1, 0, 1, 1], //* wheather to move a enm   *//
                   [1, 1, 1, 1, 1, 1], //* by indexing into it with *//
                   [1, 2, 1, 1, 2, 1], //* enm_byte + num_enm +     *//
                   [1, 2, 2, 1, 2, 2], //* set_num to get a byte.   *//
                   [2, 2, 2, 2, 2, 2], //* then that byte is checked*//
                   [2, 2, 3, 2, 2, 3], //* for !=0 and then decrmnt *//
                   [2, 3, 3, 2, 3, 3], //* for next test until = 0  *// 
                   [3, 3, 3, 3, 3, 3], 
                   [3, 3, 4, 3, 3, 4],
                   [3, 4, 4, 3, 4, 4],
                   [4, 4, 4, 4, 4, 4]
				 ];	

var moveOffset = 0;
var moveId = 0;    //current guard id

var numOfMoveItems = movePolicy[0].length;

//********************************
//initial guard start move value 
//********************************
function initGuardVariable()
{
	moveOffset = moveId = 0;
}

function moveGuard()
{
	var moves;
	var curGuard;
	var x, y, yOffset;
	
	if(!guardCount) return; //no guard
	
	if( ++moveOffset >= numOfMoveItems ) moveOffset = 0;
	moves = movePolicy[guardCount][moveOffset];  // get next moves 
 
	while ( moves-- > 0) {                       // slows guard relative to runner
		if(++moveId >= guardCount) moveId = 0; 
		curGuard = guard[moveId];
		
		if(curGuard.action == ACT_IN_HOLE || curGuard.action == ACT_REBORN) {
			continue;
		}
		
		guardMoveStep(moveId, bestMove(moveId));
	}
}	

function guardMoveStep( id, action)
{
	var curGuard = guard[id];
	var x = curGuard.pos.x;
	var xOffset = curGuard.pos.xOffset;
	var y = curGuard.pos.y;
	var yOffset = curGuard.pos.yOffset;

	var curToken, nextToken;
	var centerX, centerY;
	var curShape, newShape;
	var stayCurrPos;
	
	centerX = centerY = ACT_STOP;
	curShape = newShape = curGuard.shape;

	if(curGuard.action == ACT_CLIMB_OUT && action == ACT_STOP) 
		curGuard.action = ACT_STOP; //for level 16, 30, guard will stock in hole
	
	switch(action) {
	case ACT_UP:
	case ACT_DOWN:
	case ACT_FALL:
		if ( action == ACT_UP ) {	
			stayCurrPos = ( y <= 0 ||
			                (nextToken = map[x][y-1].act) == BLOCK_T ||
			                nextToken == SOLID_T || nextToken == TRAP_T || 
			    	        nextToken == GUARD_T);
			
			if( yOffset <= 0 && stayCurrPos)
				action = ACT_STOP;
		} else { //ACT_DOWN || ACT_FALL
			stayCurrPos = ( y >= maxTileY ||
			                (nextToken = map[x][y+1].act) == BLOCK_T ||
			                nextToken == SOLID_T || nextToken == GUARD_T);	
			
			if( action == ACT_FALL && yOffset < 0 && map[x][y].base == BLOCK_T) {
				action = ACT_IN_HOLE;
				stayCurrPos = 1;
			} else {
				if ( yOffset >= 0 && stayCurrPos) 
					action = ACT_STOP;
			}
		}
		
		if ( action != ACT_STOP ) {
			if ( xOffset > 0) 
				centerX = ACT_LEFT;
			else if ( xOffset < 0)
				centerX = ACT_RIGHT;
		}
		break;
	case ACT_LEFT:
	case ACT_RIGHT:		
		if ( action == ACT_LEFT ) {
			/* original source code from book
			stayCurrPos = ( x <= 0 ||
			                (nextToken = map[x-1][y].act) == BLOCK_T ||
			                nextToken == SOLID_T || nextToken == TRAP_T || 
		                    nextToken == GUARD_T); 
			*/					
			// change check TRAP_T from base, 
			// for support level 41==> runner in trap will cause guard move
			stayCurrPos = ( x <= 0 ||
			                (nextToken = map[x-1][y].act) == BLOCK_T ||
			                nextToken == SOLID_T || nextToken == GUARD_T ||
						    map[x-1][y].base == TRAP_T); 
		
			if (xOffset <= 0 && stayCurrPos)
				action = ACT_STOP;
		} else { //ACT_RIGHT
			/* original source code from book
			stayCurrPos = ( x >= maxTileX ||
			                (nextToken = map[x+1][y].act) == BLOCK_T ||
			                nextToken == SOLID_T || nextToken == TRAP_T || 
		                    nextToken == GUARD_T); 
			*/
			// change check TRAP_T from base, 
			// for support level 41==> runner in trap will cause guard move
			stayCurrPos = ( x >= maxTileX ||
			                (nextToken = map[x+1][y].act) == BLOCK_T ||
			                nextToken == SOLID_T || nextToken == GUARD_T || 
		                    map[x+1][y].base == TRAP_T); 
			
			if (xOffset >= 0 && stayCurrPos)
				action = ACT_STOP;
		}
			
		if ( action != ACT_STOP ) {
			if ( yOffset > 0 ) 
				centerY = ACT_UP;
			else if ( yOffset < 0) 
				centerY = ACT_DOWN;
		}
		break;
	}
	
	curToken = map[x][y].base;

	if ( action == ACT_UP ) {
		yOffset -= yMove;

		if(stayCurrPos && yOffset < 0) yOffset = 0; //stay on current position
		else if(yOffset < -H2) { //move to y-1 position 
			if ( curToken == BLOCK_T || curToken == HLADR_T ) curToken = EMPTY_T; //in hole or hide laddr
			map[x][y].act = curToken; //runner move to [x][y-1], so set [x][y].act to previous state
			y--;
			yOffset = tileH + yOffset;
			if(map[x][y].act == RUNNER_T) setRunnerDead(); //collision
			//map[x][y].act = GUARD_T;
		}
		
		if( yOffset <= 0 && yOffset > -yMove) {
			dropGold(id); //decrease count
		}
		newShape = "runUpDn";
	}
	
	if ( centerY == ACT_UP ) {
		yOffset -= yMove;
		if( yOffset < 0) yOffset = 0; //move to center Y	
	}
	
	if ( action == ACT_DOWN || action == ACT_FALL || action == ACT_IN_HOLE) {
		var holdOnBar = 0;
		if(curToken == BAR_T) {
			if( yOffset < 0) holdOnBar = 1;
			else if(action == ACT_DOWN && y < maxTileY && map[x][y+1].act != LADDR_T) {
				action = ACT_FALL; //shape fixed: 2014/03/27
			}
		}
		
		yOffset += yMove;
		
		if(holdOnBar == 1 && yOffset >= 0) {
			yOffset = 0; //fall and hold on bar
			action = ACT_FALL_BAR;
		}
		if(stayCurrPos && yOffset > 0 ) yOffset = 0; //stay on current position
		else if(yOffset > H2) { //move to y+1 position
			if(curToken == BLOCK_T || curToken == HLADR_T) curToken = EMPTY_T; //in hole or hide laddr
			map[x][y].act = curToken; //runner move to [x][y+1], so set [x][y].act to previous state
			y++;
			yOffset = yOffset - tileH;
			if(map[x][y].act == RUNNER_T) setRunnerDead(); //collision
			//map[x][y].act = GUARD_T;
		}
		
		//add condition: AI version >= 3 will decrease drop count while guard fall
		if( ((curAiVersion >= 3 && action == ACT_FALL) || action == ACT_DOWN) && 
		     yOffset >= 0 && yOffset < yMove) 
		{ 	//try drop gold
			dropGold(id); //decrease count
		}
		
		if(action == ACT_IN_HOLE) { //check in hole or still falling
			if (yOffset < 0) {
				action = ACT_FALL; //still falling
				
				//----------------------------------------------------------------------
				//For AI version >= 4, drop gold before guard failing into hole totally
				if(curAiVersion >= 4 && curGuard.hasGold > 0) {
					if(map[x][y-1].base == EMPTY_T) {
						//drop gold above
						addGold(x, y-1);
					} else 
						decGold(); //gold disappear 
					curGuard.hasGold = 0;
					guardRemoveRedhat(curGuard); //9/4/2016
				}
				//----------------------------------------------------------------------
				
			} else { //fall into hole (yOffset MUST = 0)

				//----------------------------------------------------------------------
				//For AI version < 4, drop gold after guard failing into hole totally
				if( curGuard.hasGold > 0 ) {
					if(map[x][y-1].base == EMPTY_T) {
						//drop gold above
						addGold(x, y-1);
					} else 
						decGold(); //gold disappear 
				}
				curGuard.hasGold = 0;
				guardRemoveRedhat(curGuard); //9/4/2016
				//----------------------------------------------------------------------
				
				if( curShape == "fallRight") newShape = "shakeRight";
				else newShape = "shakeLeft";
				themeSoundPlay("trap");
				shakeTimeStart = recordCount; //for debug
				if(curAiVersion < 3) {
					curGuard.sprite.on("animationend", function() { climbOut(id); });
				} else {
					add2GuardShakeQueue(id, newShape);
				}
				
				if(playMode == PLAY_CLASSIC || playMode == PLAY_AUTO || playMode == PLAY_DEMO) {
					drawScore(SCORE_IN_HOLE);
				} else {
					//for modem mode & edit mode
					//drawGuard(1); //only guard dead need add count
				}
			}
		}
		
		if(action == ACT_DOWN) {
			newShape = "runUpDn";
		} else { //ACT_FALL or ACT_FALL_BAR
			if (action == ACT_FALL_BAR) {
				if(curGuard.lastLeftRight == ACT_LEFT) newShape = "barLeft";
				else newShape = "barRight";
			} else {
				if(action == ACT_FALL && curShape != "fallLeft" && curShape != "fallRight") {
					if (curGuard.lastLeftRight == ACT_LEFT) newShape = "fallLeft";
					else newShape = "fallRight";
				}
			}
		}
	}	

	if ( centerY == ACT_DOWN ) {
		yOffset += yMove;
		if ( yOffset > 0 ) yOffset = 0; //move to center Y
	}
	
	if ( action == ACT_LEFT) {
		xOffset -= xMove;

		if(stayCurrPos && xOffset < 0) xOffset = 0; //stay on current position
		else if ( xOffset < -W2) { //move to x-1 position 
			if(curToken == BLOCK_T || curToken == HLADR_T) curToken = EMPTY_T; //in hole or hide laddr
			map[x][y].act = curToken; //runner move to [x-1][y], so set [x][y].act to previous state
			x--;
			xOffset = tileW + xOffset;
			if(map[x][y].act == RUNNER_T) setRunnerDead(); //collision
			//map[x][y].act = GUARD_T;
		}
		if( xOffset <= 0 && xOffset > -xMove) {
			dropGold(id); //try to drop gold
		}
		if(curToken == BAR_T) newShape = "barLeft";
		else newShape = "runLeft";
	}
	
	if ( centerX == ACT_LEFT ) {
		xOffset -= xMove;
		if ( xOffset < 0) xOffset = 0; //move to center X
	}
	
	if ( action == ACT_RIGHT ) {
		xOffset += xMove;

		if(stayCurrPos && xOffset > 0) xOffset = 0; //stay on current position
		else if ( xOffset > W2) { //move to x+1 position 
			if(curToken == BLOCK_T || curToken == HLADR_T) curToken = EMPTY_T; //in hole or hide laddr
			map[x][y].act = curToken; //runner move to [x+1][y], so set [x][y].act to previous state
			x++;
			xOffset = xOffset - tileW;
			if(map[x][y].act == RUNNER_T) setRunnerDead(); //collision
			//map[x][y].act = GUARD_T;
		}
		if( xOffset >= 0 && xOffset < xMove) {
			dropGold(id);
		}
		if(curToken == BAR_T) newShape = "barRight";
		else newShape = "runRight";
	}
	
	if ( centerX == ACT_RIGHT ) {
		xOffset += xMove;
		if ( xOffset > 0) xOffset = 0; //move to center X
	}
 
	//if(curGuard == ACT_CLIMB_OUT) action == ACT_CLIMB_OUT;
	
 	if(action == ACT_STOP) {
		if(curGuard.action != ACT_STOP){
			curGuard.sprite.stop();
			if(curGuard.action != ACT_CLIMB_OUT) curGuard.action = ACT_STOP;
		}
	} else {
		if(curGuard.action == ACT_CLIMB_OUT) action = ACT_CLIMB_OUT;
		curGuard.sprite.x = (x * tileW + xOffset) * tileScale | 0;
		curGuard.sprite.y = (y * tileH + yOffset) * tileScale | 0;
		curGuard.pos = { x:x, y:y, xOffset:xOffset, yOffset:yOffset};	
		if(curShape != newShape) {
			curGuard.sprite.gotoAndPlay(newShape);
			curGuard.shape = newShape;
		}
		if(action != curGuard.action){
			curGuard.sprite.play();
		}
		curGuard.action = action;
		if(action == ACT_LEFT || action == ACT_RIGHT) curGuard.lastLeftRight = action;
	}
	map[x][y].act = GUARD_T;

	// Check to get gold and carry 
	if(map[x][y].base == GOLD_T && curGuard.hasGold == 0 &&
		((!xOffset && yOffset >= 0 && yOffset < H4) || 
		 (!yOffset && xOffset >= 0 && xOffset < W4) || 
		 (y < maxTileY && map[x][y+1].base == LADDR_T && yOffset < H4) // gold above laddr
		)
	  )  
	{
		//curGuard.hasGold = ((Math.random()*26)+14)|0; //14 - 39 
		curGuard.hasGold = ((Math.random()*26)+12)|0; //12 - 37 change gold drop steps
		guardWearRedhat(curGuard); //9/4/2016
		if(playMode == PLAY_AUTO || playMode == PLAY_DEMO || playMode == PLAY_DEMO_ONCE) 	getDemoGold(curGuard);
		if(recordMode) processRecordGold(curGuard);
		removeGold(x, y);
		//debug ("get, (x,y) = " + x + "," + y + ", offset = " + xOffset); 
	}

	//check collision again 
	//checkCollision(runner.pos.x, runner.pos.y);
}

//meanings: guard with gold
function guardWearRedhat(guard)
{
	if(redhatMode) guard.sprite.spriteSheet = redhatData;
}

//meanings: guard without gold
function guardRemoveRedhat(guard) 
{
	if(redhatMode) guard.sprite.spriteSheet = guardData;
}

function dropGold(id) 
{
	var curGuard = guard[id];
	var nextToken;
	var drop = 0;
	
	switch (true) {
	case (curGuard.hasGold > 1):
		curGuard.hasGold--; // count > 1,  don't drop it only decrease count 
		//loadingTxt.text = "(" + id + ") = " + curGuard.hasGold;
		break;	
	case (curGuard.hasGold == 1): //drop gold
		var x = curGuard.pos.x, y = curGuard.pos.y;
		
		if(map[x][y].base == EMPTY_T && ( y >= maxTileY ||
		   ((nextToken = map[x][y+1].base) == BLOCK_T || 
			nextToken == SOLID_T || nextToken == LADDR_T) )
		) {
			addGold(x,y);
			curGuard.hasGold = -1; //for record play action always use value = -1
			//curGuard.hasGold =  -(((Math.random()*10)+1)|0); //-1 ~ -10; //waiting time for get gold
			guardRemoveRedhat(curGuard); //9/4/2016	
			drop = 1;
		}
		break;	
	case (curGuard.hasGold < 0):
		curGuard.hasGold++; //wait, don't get gold till count = 0
		//loadingTxt.text = "(" + id + ") = " + curGuard.hasGold;
		break;	
	}
	return drop;
}


//=============================================
// BEGIN NEW SHAKE METHOD for AI version >= 3
//=============================================
var DEBUG_TIME = 0;
var shakeRight  = [  8,  9, 10,  9, 10,  8 ];
var shakeLeft   = [ 30, 31, 32, 31, 32, 30 ];
var shakeTime   = [ 36,  3,  3,  3,  3,  3 ];

var shakingGuardList = [];

function initStillFrameVariable()
{
	initGuardShakeVariable();
	initFillHoleVariable();
	initRebornVariable();
}

function initGuardShakeVariable()
{
	shakingGuardList = [];
	
	//-------------------------------------------------------------------------
	// Shake time extension when guard in hole,
	// so the runner can dig three hold for three guards and pass through them. 
	// The behavior almost same as original lode runner (APPLE-II version).
	// 2016/06/04
	//-------------------------------------------------------------------------
	if(curAiVersion <= 3) shakeTime = [ 36,  3,  3,  3,  3,  3 ]; //for AI VERSION = 3
	else                  shakeTime = [ 51,  3,  3,  3,  3,  3 ]; //for AI VERSION > 3
}

function add2GuardShakeQueue(id, shape)
{
	var curGuard = guard[id];
	
	if(shape == "shakeRight") {
		curGuard.shapeFrame = shakeRight;	
	} else { 
		curGuard.shapeFrame = shakeLeft;	
	}
	
	curGuard.curFrameIdx  =  0;
	curGuard.curFrameTime = -1; //for init
		
	shakingGuardList.push(id);
	//error("push id =" + id + "(" + shakingGuardList + ")" );
	
}

function processGuardShake()
{
	var curGuard, curIdx;
	for(var i = 0; i < shakingGuardList.length;) {
		curGuard = guard[shakingGuardList[i]];
		curIdx = curGuard.curFrameIdx;
		
		if( curGuard.curFrameTime < 0) { //start shake => set still frame
			curGuard.curFrameTime = 0;
			curGuard.sprite.gotoAndStop(curGuard.shapeFrame[curIdx]);
		} else {
			if(++curGuard.curFrameTime >= shakeTime[curIdx]) {
				if(++curGuard.curFrameIdx < curGuard.shapeFrame.length) {
					//change frame
					curGuard.curFrameTime = 0;
					curGuard.sprite.gotoAndStop(curGuard.shapeFrame[curGuard.curFrameIdx]);
				} else {
					//shake time out 
				
					var id = shakingGuardList[i];
					shakingGuardList.splice(i, 1); //remove from list
					//error("remove id =" + id + "(" + shakingGuardList + ")" );
					climbOut(id); //climb out
					continue;
				}
				
			}
		}
		i++;
	}
}

function removeFromShake(id)
{
	for(var i = 0; i < shakingGuardList.length;i++) {
		if(shakingGuardList[i] == id) {
			shakingGuardList.splice(i, 1); //remove from list
			//error("remove id =" + id + "(" + shakingGuardList + ")" );
			return;
		}
	}
	error("design error id =" + id + "(" + shakingGuardList + ")" );
}

//======================
// END NEW SHAKE METHOD 
//======================

function climbOut(id)
{
	var curGuard = guard[id]
	
	curGuard.action = ACT_CLIMB_OUT;
	curGuard.sprite.removeAllEventListeners ("animationend");
	curGuard.sprite.gotoAndPlay("runUpDn");
	curGuard.shape = "runUpDn";
	curGuard.holePos = {x: curGuard.pos.x, y: curGuard.pos.y };
	
	if(DEBUG_TIME) loadingTxt.text = "ShakeTime = " + (recordCount - shakeTimeStart); //for debug
}

function bestMove(id)
{
	var guarder = guard[id];
	var x = guarder.pos.x;
	var xOffset = guarder.pos.xOffset;
	var y = guarder.pos.y;
	var yOffset = guarder.pos.yOffset;
	
	var curToken, nextBelow, nextMove;
	var checkSameLevelOnly = 0;
	
	curToken = map[x][y].base;
	
	if(guarder.action == ACT_CLIMB_OUT)  { //clib from hole
		if(guarder.pos.y == guarder.holePos.y) {
			return (ACT_UP);
		}else {
			checkSameLevelOnly = 1;
			if(guarder.pos.x != guarder.holePos.x) { //out of hole
				guarder.action = ACT_LEFT;
			}
		}
	}
	
	if( !checkSameLevelOnly) {
	
		/****** next check to see if enm must fall. if so ***********/
		/****** return e_fall and skip the rest.          ***********/
	
		if ( curToken == LADDR_T || (curToken == BAR_T && yOffset === 0) ) { //ladder & bar
			/* no guard fall */ 
		} else if ( yOffset < 0) //no laddr & yOffset < 0 ==> falling
			return (ACT_FALL);
		else if ( y < maxTileY )
		{
			nextBelow = map[x][y+1].act;
			
			if ( (nextBelow == EMPTY_T || nextBelow == RUNNER_T )) {
				return (ACT_FALL);
			} else if ( nextBelow == BLOCK_T || nextBelow == SOLID_T || 
					  nextBelow == GUARD_T || nextBelow == LADDR_T ) {
				/* no guard fall */
			} else {
				return ( ACT_FALL );		
			}
		}	
	}

	/******* next check to see if palyer on same floor *********/
	/******* and whether enm can get him. Ignore walls *********/
	var runnerX = runner.pos.x;
	var runnerY = runner.pos.y;	
	
//	if ( y == runnerY ) { // same floor with runner
	if ( y == runnerY && runner.action != ACT_FALL) { //case : guard on laddr and falling => don't catch it 
		while ( x != runnerX ) {
			if ( y < maxTileY )
				nextBelow = map[x][y+1].base;
			else nextBelow = SOLID_T;
			
			curToken = map[x][y].base;
			
			if ( curToken == LADDR_T || curToken == BAR_T ||  // go through	
				 nextBelow == SOLID_T || nextBelow == LADDR_T ||
				 nextBelow == BLOCK_T || map[x][y+1].act == GUARD_T || //fixed: must check map[].act with guard_t (for support champLevel:43)
			     nextBelow == BAR_T || nextBelow == GOLD_T) //add BAR_T & GOLD_T for support level 92 
			{
				if ( x < runnerX)  // guard left to runner
					++x;	
				else if ( x > runnerX )
					--x;      // guard right to runner
			} else break;     // exit loop with closest x if no path to runner
		}
		
		if ( x == runnerX )  // scan for a path ignoring walls is a success
		{
			if (guarder.pos.x < runnerX ) {  //if left of man go right else left 
				nextMove = ACT_RIGHT;
			} else if ( guarder.pos.x > runnerX ) {
				nextMove = ACT_LEFT;
			} else { // guard X = runner X
				if ( guarder.pos.xOffset < runner.pos.xOffset )
					nextMove = ACT_RIGHT;
				else
					nextMove = ACT_LEFT;
			}
			return (nextMove); 
		}
	} // if ( y == runnerY ) { ... 
	
	/********** If can't reach man on current level, then scan floor *********/
	/********** (ignoring walls) and look up and down for best move  *********/

	return scanFloor(id);
}

var bestPath, bestRating, curRating;		
var leftEnd, rightEnd;	
var startX, startY;

function scanFloor(id)
{
	var x, y;
	var curToken, nextBelow;
	var curPath;
	
	x = startX = guard[id].pos.x;
	y = startY = guard[id].pos.y;
	
	bestRating = 255;   // start with worst rating
	curRating = 255;
	bestPath = ACT_STOP;
	
	/****** get ends for search along floor ******/
	
	while ( x > 0 ) {                                    //get left end first
		curToken = map[x-1][y].act;
		if ( curToken == BLOCK_T || curToken == SOLID_T )
			break;
		if ( curToken == LADDR_T || curToken == BAR_T || y >= maxTileY ||
		     y < maxTileY && ( ( nextBelow = map[x-1][y+1].base) == BLOCK_T ||
		     nextBelow == SOLID_T || nextBelow == LADDR_T ) )
			--x;
		else {
			--x;                                        // go on left anyway 
			break;
		}		
	}
	
	leftEnd = x;
	x = startX;
	while ( x < maxTileX ) {                           // get right end next
		curToken = map[x+1][y].act;
		if ( curToken  == BLOCK_T || curToken == SOLID_T )
			break;
		
		if ( curToken == LADDR_T || curToken == BAR_T || y >= maxTileY ||
		     y < maxTileY && ( ( nextBelow = map[x+1][y+1].base) == BLOCK_T ||
		     nextBelow == SOLID_T || nextBelow == LADDR_T ) )
			++x;
		else {                                         // go on right anyway
			++x;                                        
			break;
		}		
	}
	
	rightEnd = x;

	/******* Do middle scan first for best rating and direction *******/
	
	x = startX;
	if ( y < maxTileY && 
		 (nextBelow = map[x][y+1].base) != BLOCK_T && nextBelow != SOLID_T )
		scanDown( x, ACT_DOWN ); 

	if( map[x][y].base == LADDR_T )
		scanUp( x, ACT_UP );

	/******* next scan both sides of floor for best rating *******/

	curPath = ACT_LEFT;
	x = leftEnd; 
	
	while ( true ) {
		if ( x == startX ) {
			if ( curPath == ACT_LEFT && rightEnd != startX ) {
				curPath = ACT_RIGHT;
				x = rightEnd;
			}
			else break;
		}
	
		if( y < maxTileY && 
			(nextBelow = map [x][y+1].base) != BLOCK_T && nextBelow != SOLID_T )
			scanDown (x, curPath );

		if( map[x][y].base == LADDR_T )
			scanUp ( x, curPath );
		
		if ( curPath == ACT_LEFT )
			x++;
		else x--;	
	}	

	
	return ( bestPath );	
}                           // end scan floor for best direction to go  
	
function scanDown(x, curPath ) 
{
	var y;
	var nextBelow; //curRating;
	var runnerX = runner.pos.x;
	var runnerY = runner.pos.y;
	
	y = startY;
	
	while( y < maxTileY && ( nextBelow = map [x][y+1].base) != BLOCK_T &&
		    nextBelow != SOLID_T )                  // while no floor below ==> can move down
	{
		if ( map[x][y].base != EMPTY_T && map[x][y].base != HLADR_T) { // if not falling ==> try move left or right 
			//************************************************************************************
			// 2014/04/14 Add check  "map[x][y].base != HLADR_T" for support 
			// champLevel 19: a guard in hole with h-ladder can run left after dig the left hole
			//************************************************************************************
			if ( x > 0 ) {                          // if not at left edge check left side
				if ( (nextBelow = map[x-1][y+1].base) == BLOCK_T ||
					nextBelow == LADDR_T || nextBelow == SOLID_T ||
					map[x-1][y].base == BAR_T )     // can move left       
				{
					if ( y >= runnerY )             // no need to go on
						break;                      // already below runner
				}
			}	
			
			if ( x < maxTileX )                     // if not at right edge check right side
			{
				if ( (nextBelow = map[x+1][y+1].base) == BLOCK_T ||
					nextBelow == LADDR_T || nextBelow == SOLID_T ||
					map[x+1][y].base == BAR_T )     // can move right
				{
					if ( y >= runnerY )
						break;
				}
			}
		}                                           // end if not falling
		++y;                                        // go to next level down
	}                                               // end while ( y < maxTileY ... ) scan down
	
	if( y == runnerY ) {                            // update best rating and direct.
		curRating = Math.abs(startX - x); 
//		if ( (curRating = runnerX - x) < 0) //BUG from original book ? (changed by Simon)
//			curRating = -curRating; //ABS
	} else if ( y > runnerY )
		curRating = y - runnerY + 200;               // position below runner
	else curRating = runnerY - y + 100;              // position above runner
	
	if( curRating < bestRating )
	{
		bestRating = curRating;
		bestPath = curPath;
	}
	
}                                                   // end Scan Down

function scanUp( x, curPath )
{
	var y;
	var nextBelow; //curRating;
	var runnerX = runner.pos.x;
	var runnerY = runner.pos.y;
	
	y = startY;
	
	while ( y > 0 && map[x][y].base == LADDR_T ) {  // while can go up
		--y;
		if ( x > 0 ) {                              // if not at left edge check left side
			if ( (nextBelow = map[x-1][y+1].base) == BLOCK_T ||
				nextBelow == SOLID_T || nextBelow == LADDR_T ||
				map[x-1][y].base == BAR_T )         // can move left
			{
				if ( y <= runnerY )                 // no need to go on 
					break;                          // already above runner
			}
		}

		if ( x < maxTileX ) {                       // if not at right edge check right side
			if ( (nextBelow = map[x+1][y+1].base) == BLOCK_T ||
				nextBelow == SOLID_T || nextBelow == LADDR_T ||
				map[x+1][y].base == BAR_T )         // can move right
			{
				if ( y <= runnerY )
					break;
			}
		}  
		//--y;
	}                                               // end while ( y > 0 && laddr up) scan up 
	
	if ( y == runnerY ) {                           // update best rating and direct.
		curRating = Math.abs(startX - x);
		//if ( (curRating = runnerX - x) < 0) // BUG from original book ? (changed by Simon)
		//	curRating = -curRating; //ABS
	} else if ( y > runnerY )
		curRating = y - runnerY + 200;              // position below runner   
	else curRating = runnerY - y + 100;             // position above runner    
	
	if ( curRating < bestRating )
	{
		bestRating = curRating;
		bestPath = curPath;
	}
	
}                                                   // end scan Up

var bornRndX; //range random 0..maxTileX

function initRnd()
{
	//bornRndX = new rangeRandom(0, maxTileX, curLevel); //set fixed seed for demo mode
	bornRndX = new rangeRandom(0, maxTileX, 0); //random range 0 .. maxTileX
}

function getGuardId(x, y)
{
	var id;
	
	for(id = 0; id < guardCount; id++) {
		if ( guard[id].pos.x == x && guard[id].pos.y == y) break;
	}
	assert(id < guardCount, "Error: can not get guard position!");
	
	return id;
}

function guardReborn(x, y)
{
	var id;
	
	//get guard id  by current in hole position
	id = getGuardId(x, y);

	var bornY = 1; //start on line 2
	var bornX = bornRndX.get();
	var rndStart = bornX;
	
	
	while(map[bornX][bornY].act != EMPTY_T || map[bornX][bornY].base == GOLD_T || map[bornX][bornY].base == BLOCK_T ) { 
		//BUG FIXED for level 115 (can not reborn at bornX=27)
		//don't born at gold position & diged position, 2/24/2015
		if( (bornX = bornRndX.get()) == rndStart) {                               
			bornY++;
		}
		assert(bornY <= maxTileY, "Error: Born Y too large !");
	}
	//debug("bornX = " + bornX);
	if(playMode == PLAY_AUTO || playMode == PLAY_DEMO || playMode == PLAY_DEMO_ONCE) {
		var bornPos = getDemoBornPos();
		bornX = bornPos.x;
		bornY = bornPos.y;
	}
	
	if(recordMode == RECORD_KEY) saveRecordBornPos(bornX, bornY);
	else if(recordMode == RECORD_PLAY) {
		var bornPos = getRecordBornPos();
		bornX = bornPos.x;
		bornY = bornPos.y;
	}
	
	map[bornX][bornY].act = GUARD_T;
	//debug("born (x,y) = (" + bornX + "," + bornY + ")");
	
	var curGuard = guard[id];
	
	curGuard.pos = { x:bornX, y:bornY, xOffset:0, yOffset: 0 };
	curGuard.sprite.x = bornX * tileWScale | 0;
	curGuard.sprite.y = bornY * tileHScale | 0;
	
	rebornTimeStart = recordCount;
	if(curAiVersion < 3) {
		curGuard.sprite.on("animationend", function() { rebornComplete(id); });
		curGuard.sprite.gotoAndPlay("reborn");
	} else {
		add2RebornQueue(id);
	}

	curGuard.shape = "reborn";
	curGuard.action = ACT_REBORN;
	
}

function rebornComplete(id)
{
	var x = guard[id].pos.x;
	var y = guard[id].pos.y;

	if( map[x][y].act == RUNNER_T) setRunnerDead(); //collision
	map[x][y].act  = GUARD_T; 
	guard[id].sprite.removeAllEventListeners ("animationend");
	guard[id].action = ACT_FALL;
	guard[id].shape = "fallRight";
	//guard[id].hasGold = 0;
	guard[id].sprite.gotoAndPlay("fallRight");
	themeSoundPlay("reborn");
	
	if(DEBUG_TIME) loadingTxt.text = "rebornTime = " + (recordCount - rebornTimeStart); //for debug
}

function setRunnerDead()
{
	if(!godMode) gameState = GAME_RUNNER_DEAD; 
}

//===============================================
// BEGIN NEW FOR REBORN (ai version >= 3)
//===============================================
var rebornFrame = [ 28, 29 ];
var rebornTime  = [  6,  2 ];

var rebornGuardList = [];

function initRebornVariable()
{
	rebornGuardList = [];
}

function add2RebornQueue(id)
{
	var curGuard = guard[id];
	
	curGuard.sprite.gotoAndStop("reborn");
	curGuard.curFrameIdx  =   0;
	curGuard.curFrameTime =  -1;
		
	rebornGuardList.push(id);
}

function processReborn()
{
	var curGuard, curIdx;
	
	for(var i = 0; i < rebornGuardList.length;) {
		curGuard = guard[rebornGuardList[i]];
		curIdx = curGuard.curFrameIdx;
		
		if(++curGuard.curFrameTime >= rebornTime[curIdx]) {
			if(++curGuard.curFrameIdx < rebornFrame.length) {
				//change frame
				curGuard.curFrameTime = 0;
				curGuard.sprite.gotoAndStop(rebornFrame[curGuard.curFrameIdx]);
			} else {
				//reborn 
				var id = rebornGuardList[i];
				rebornGuardList.splice(i, 1); //remove from list
				rebornComplete(id);
				continue;
			}
		}
		i++;
	}
}
//====================
// END NEW FOR REBORN 
//====================
