<!--

// JavaScript Document



/////////////////////////////////
// Movement Functions

function MoveObject(oObject)
{
	StayInMapArea(oObject);
	
	// updates the position of an object based on its velocity
	// x & y positions must be integers
	// but the positions are saved as floats so that changes of position < 1 will be logged
	// speed is pixels per seconds
	var distanceTravelled = oObject._speed * (gameTick / 1000);
	oObject._x += distanceTravelled * oObject._vx;
	oObject._y += distanceTravelled * oObject._vy;
	try
	{
		oObject.style.left = Math.round(oObject._x) +'px';
		oObject.style.top = Math.round(oObject._y) +'px';
	}
	catch(err)
	{
		WriteDebug1("failed to write left & top");
	}
	// Draw bigger people over the top of smaller ones
	// will probably want to change this when there are other types of objects than people	
	oObject.style.zIndex = 100 - oObject._rank;
	return;
}

function StayInMapArea(oPerson)
{
	// don't let objects go off the edge of the map
	if (oPerson._x <= leftEdge)
	{
		oPerson._vx *= -1;
		oPerson._x = leftEdge + 1;
		oPerson.style.left = oPerson._x+'px';
	}
	if (oPerson._x + oPerson._width >= rightEdge)
	{
		oPerson._vx *= -1;
		oPerson._x = rightEdge - 1 - oPerson._width;
		oPerson.style.left = oPerson._x+'px';
	}
	if (oPerson._y <= topEdge)
	{
		oPerson._vy *= -1;
		oPerson._y = topEdge + 1;
		oPerson.style.top = oPerson._y+'px';
	}
	if (oPerson._y + (oPerson._height)>= bottomEdge)
	{
		oPerson._vy *= -1;
		oPerson._y = bottomEdge - 1 - (oPerson._height);
		oPerson.style.top = oPerson._y+'px';
	}
	return true;
}

function ForceIntoMap(oObject)
{
  // if an object is outside the map area, force it into the map
  // it is effectively 'bounced' off the walls
	if (oObject._x <= leftEdge)
	{
		oObject._x = leftEdge - oObject._x;
	}
	if (oObject._x >= rightEdge)
	{
		oObject._x = (2 * rightEdge) - oObject._x;
	}
	if (oObject._y <= topEdge)
	{
		oObject._y = topEdge - oObject._y;
	}
	if (oObject._y >= bottomEdge)
	{
		oObject._y = (2 * bottomEdge) - oObject._y;
	}
	return true;
}

function Steer(dx, dy, object)
{
	// changes the object's direction by dx in the x direction, dy in the y direction
	// does not affect the object's speed
	// direction vector is always 1, 0 because it is a unit vector pointing along the x-axis
	var newVelocity = RotateVector(1 + dx, dy, (FindVectorAngle(object._vx, object._vy)));	
	var size = VectorMagnitude(newVelocity.x, newVelocity.y);
	return {x:newVelocity.x/size, y:newVelocity.y/size};
}

function Decelerate(oPerson, amount)
{
	if (oPerson._speed > amount)
	{
		oPerson._speed -= amount;
	}
}

function Accelerate(oPerson, amount)
{
	if (oPerson._speed < oPerson._maxSpeed - amount)
	{
		oPerson._speed += amount;
	}
}

function Follow(oPerson, oDestination)
{
	// follows a destination that is assumed to be moving
	// destination must have x, y, height, width
	// returns true if the person is at the destination
	
	if (IsOverlapping(oPerson, oDestination))
	{
		Decelerate(oPerson, 1);
		return true;
	}
	
	else
	{
		// find the destination's position in local space
		var oDestinationLocalPos =	GetLocalCoordinates(oPerson, oDestination);
		var local_x = oDestinationLocalPos.x;
		var local_y = oDestinationLocalPos.y;
		
		// if will collide with the destination in the next second, steer around it
		var avoidance_needed = AvoidObstacle(oPerson, oDestination, local_x, local_y);

		// if didn't steer around destination, steer towards it
		if (avoidance_needed ==  false)
		{
			PursueDestination(oPerson, local_y);
		}	
		return false;	
	}
}

function MoveTo(oPerson, oDestination)
{
	// steers a person towards a fixed destination
	// destination must have x, y, height, width
	// returns true if the person is at the destination
	
	if (IsOverlapping(oPerson, oDestination))
	{
		Decelerate(oPerson, 1);
		return true;
	}
	
	else
	{
		// find the destination's position in local space
		var oDestinationLocalPos =	GetLocalCoordinates(oPerson, oDestination);
		var local_x = oDestinationLocalPos.x;
		var local_y = oDestinationLocalPos.y;
		
// show where the destination is - temporary debug measure
	Destination0.style.left = oDestination._x;
	Destination0.style.top = oDestination._y;

		// steer towards the destination
		PursueDestination(oPerson, local_y);
		return false;	
	}
}

function PursueDestination(oPerson, localY)
{
	// steer towards a destination
	// localX, localY are the local co-ordinates of the destination
	var steer_amount = (localY < 0) ? -0.1 : 0.1;
	var newVelocity = Steer(0, steer_amount, oPerson);
	oPerson._vx = newVelocity.x;
	oPerson._vy = newVelocity.y;
	
	Accelerate(oPerson, 1);
	return;
}

function AvoidObstacle(oPerson, oDestination, localX, localY)
{
	// avoid running into an obstacle
	// localX, localY are the local co-ordinates of the destination
	if ((localX >= 0) && (localX <= (oPerson._speed * 3) - (oPerson._width)) 
	&& (localY >= (oDestination._height * -1)) && (localY < (oPerson._height)))
	{
		var steer_amount = (localY < 0) ? 0.1 : -0.1;
		var newVelocity = Steer(0, steer_amount, oPerson);
		oPerson._vx = newVelocity.x;
		oPerson._vy = newVelocity.y;

		Decelerate(oPerson, 1);
		return true;
	}
	else
	{
		return false;
	}
}

function IsOverlapping(obj1, obj2)
{
	// finds whether obj1 & obj2 are overlapping
	// compare distances between x midpoints of the two objects
	// and between y midpoints
	var overlapX = (Math.abs((obj1._x + (obj1._width / 2)) - (obj2._x + (obj2._width / 2))) <= ((obj1._width + obj2._width) / 2));
	var overlapY = (Math.abs((obj1._y + (obj1._height / 2)) - (obj2._y + (obj2._height / 2))) <= ((obj1._height + obj2._height) / 2));
	if (overlapX && overlapY)
	{
		return true;
	}
	else 
	{
		return false;
	}
}

/////////////////////////////////
// Vector Functions

// angles are measured in radians
// angles go anticlockwise from the x-axis
// it's a browser, so the y-axis points downward

function GetUnitVector(Angle)
{
	// returns x, y components for a unit-length vector pointing at the specified angle
	// note sine is negative because y-axis points downwards in browsers
	return {_x:Math.cos(Angle), _y:(Math.sin(Angle) * -1)};
}

function VectorMagnitude(x, y)
{
	// finds the magnitude of the vector
	return Math.sqrt((x * x) + (y * y)); 
}

function DistanceBetween(Obj1, Obj2)
{
	// finds the magnitude of the vector
	return Math.sqrt(((Obj1._x - Obj2._x) * (Obj1._x - Obj2._x)) + ((Obj1._y - Obj2._y) * (Obj1._y - Obj2._y))); 
}

function DotProduct(x_1, y_1, x_2, y_2)
{
	// finds the dot product of two vectors
	return ((x_1 * x_2) + (y_1 * y_2));
}

function AngleBetweenVectors(x_1, y_1, x_2, y_2)
{
	// finds the angle between two vectors
	// if magnitude of either vector = 0, return an angle of 0
	var magnitude1 = VectorMagnitude(x_1, y_1);
	var magnitude2 = VectorMagnitude(x_2, y_2);
	if ((magnitude1 == 0 ) || (magnitude2 == 0))
	{
		return 0;
	}
	else
	{
		// make sure that the cos is between 1 & -1
		var cosine = DotProduct(x_1, y_1, x_2, y_2)/(magnitude1 * magnitude2);
		var cosine = Math.min(1, cosine);
		var cosine = Math.max(-1, cosine);
		return Math.acos(cosine);
	}
}

function RelativePosition(obj1, obj2)
{
	// finds the vector pointing from obj1 to obj2
	return {_x: (obj2._x - obj1._x), _y: (obj2._y - obj1._y)};
}


function RotateVector(X, Y, angle)
{
	// rotates a vector by the angle
	var new_x = (X * Math.cos(angle)) + (Y * Math.sin(angle));
	var new_y = -1 * (X * Math.sin(angle)) + (Y * Math.cos(angle));
	return {x:new_x, y:new_y};
}

function GetLocalCoordinates(obj, target)
{
	// finds the target's position in the object's local space
	// works on object centres
	// translate the axes to the origin
	// then rotate so object is pointing along the x-axis
	var rotation_angle = -1 * FindVectorAngle(obj._vx, obj._vy);
	var local_pos = RotateVector(((target._x + (target._width / 2)) - (obj._x + (obj._width / 2))), 
	((target._y + (target._height / 2)) - (obj._y + (obj._height / 2))), rotation_angle);
	return {x: local_pos.x, y: local_pos.y};
}

function FindVectorAngle(x, y)
{
	// find the angle of the vector
	// if x = 0, vector is pointing straight down or straight up
	if (x == 0)
	{
		if (y >= 0) { vec_angle = (3 / 2) * Math.PI; }
		else { vec_angle = Math.PI/2; }
	}
	else
	{
		vec_angle = Math.atan(y / x);
		// quadrants with negative x needs to have pi added
		if (x > 0)
		{
			
			if (y > 0) // quadrant 4
			{
				vec_angle = (2 * Math.PI) - vec_angle;
			}
			else // quadrant 1
			{
				vec_angle = vec_angle * -1;
			}
		}
		else // x < 0
		{
			if (y > 0) // quadrant 3
			{
				vec_angle = Math.PI - vec_angle;
			}
			else // quadrant 2
			{
				vec_angle = Math.PI - vec_angle;
			}
		}
	}
	return vec_angle;
}

function GetDestination(oPerson, angle, distance)
{
  // find the x, y co-ordinates of a point at that angle and distance from the person
  // give it a 2-pixel height and width as a default
  // therefore the top left edge (which is what we store) is offset by -1, -1
  return {_x:(oPerson._x + (oPerson._width / 2) + (Math.cos(angle) * distance) - 1),
	 _y:(oPerson._y + (oPerson._height / 2) - (Math.sin(angle) * distance) - 1),
	_width:2, _height:2 }
}

/////////////////////////////////
// Mathematical Functions

function IsInt(num)
{
	var a = parseInt(num);
	var b = parseFloat(num);
	return (a == b);
}

function FindDistance(obj1, obj2)
{
	// finds the distance in pixels between two objects
	var side1 = obj1._x - obj2._x;
	var side2 = obj1._y - obj2._y;
	var hypotenuse = (side1 * side1) + (side2 * side2);
	return Math.sqrt(hypotenuse);
}

/////////////////////////////////
// Random Functions
function RandomInteger(lim) 
{
 	// generates a random integer between 1 and the limit
	 randomSeed = (randomSeed * 125) % 2796203;
  return (randomSeed % lim) + 1;
}

function RandomNumber(lim) 
{
 	// generates a random number between 0 and the limit
 	// it has 5 decimal places
	randomSeed = (randomSeed * 125) % 2796203;
  return ((randomSeed % (lim * 100000)) + 1)/100000;
}

function RandomAngle()
{
	// generates a random angle in radians
	return (RandomNumber(2) * Math.PI);
}

function RandomNormal(mean,standard_deviation)
{
	// returns a number distributed normally with the given mean and standard deviation
	var S = 1;
	var U1;
	var U2;
	var V1;
	var V2;
	var X;
   
	while (S >= 1 || S == 0)
	{
	   	U1 = RandomNumber(0);		// U1=[0,1]
	   	U2 = RandomNumber(0);		// U2=[0,1]
	   	
	   	V1 = 2*U1 - 1;				// V1=[-1,1]
	   	V2 = 2*U2 - 1;				// V2=[-1,1]

	   	S = V1 * V1 + V2 * V2;
	}
		
	X = Math.sqrt(-2 * Math.log(S) / S) * V1;
//	Y=Math.sqrt(-2 * Math.log(S) / S) * V2		// could calculate another value if desired

//	The above is called the Polar Method and is fully described in the Ross book [Ros88].
//	X and Y will be unit normal random variables (mean = 0 and variance = 1), and can be easilly modified for different mean and variance.
	X = mean + standard_deviation * X; //  convert to a normal variable with correct mean and variance

	return X;
}

/////////////////////////////////
// UI Functions

function PauseRun()
{
	// if there is a modal dialog open, ignore a button click
	if (modalDialogOpen==1)
	{
		return;
	}
	// pauses the game if it's running
	// or restarts if it's paused
	switch (isPaused)
	{case 0:
		document.getElementById("PauseButton").innerHTML="<img src=\"icons/ui/run.gif\" alt=\"Run the game\">&nbsp;Run";
		PauseGame();
		break;

	case 1:
		document.getElementById("PauseButton").innerHTML="<img src=\"icons/ui/pause.gif\" alt=\"Pause the game\">&nbsp;Pause";
		RunGame();
		break;
	}
}

function PauseGame()
{
	// stop the game timer
	clearTimeout(gameInterval);
	isPaused=1;
}

function RunGame()
{
	// start the game timer
	gameInterval = setInterval("GameUpdate();", gameTick);
	isPaused=0;
}

function SettingsDialogShow()
{
	// if there is a modal dialog open, ignore a button click
	if (modalDialogOpen==1)
	{
		return;
	}
	
	// if the game isn't paused, pause it
	if (isPaused == 0)
	{
		previousPauseStatus = 0;
		PauseRun();
	}
	else
	{
		previousPauseStatus = 1;
	}
	
	// set the UI to match current values
	document.getElementById("number_of_flatlanders").value=tribeSize;
	document.getElementById("seed").value=startSeed;
	document.getElementById("set_seed").checked=setSeed;
	SettingsDialogEnableSeed();
	
	document.getElementById("dialog_01").style.left = settingsDialogX;
	document.getElementById("dialog_01").style.top = settingsDialogY;
	// each div of the dialog must be shown
	document.getElementById("dialog_01").style.visibility="visible";
	document.getElementById("DialogTitle").style.visibility="visible";
	document.getElementById("dialog_01_pane_01").style.visibility="visible";
	modalDialogOpen = 1;
}

function SettingsDialogOK()
{
	
	var validSettings;
	validSettings = ValidateSettingsDialog();
	if (validSettings==1) {
	SettingsDialogHide();
	}
}

function SettingsDialogCancel()
{
	SettingsDialogHide();
}

function SettingsDialogHide()
{
	// each div of the dialog must be hidden
	document.getElementById("dialog_01").style.visibility="hidden";
	document.getElementById("DialogTitle").style.visibility="hidden";
	document.getElementById("dialog_01_pane_01").style.visibility="hidden";
		
	modalDialogOpen = 0;
	// unpause the game
	if (isPaused==1)
	{
		// but only if it was running before the dialog was opened
		if (previousPauseStatus == 0)
		{
			PauseRun();
		}
	}
}


function ValidateSettingsDialog()
{
	// get the UI components and their current values
	var object_flatlanders = document.getElementById("number_of_flatlanders");
	var num_flatlanders = object_flatlanders.value;
	var object_seed = document.getElementById("seed");
	var current_seed = object_seed.value;

	// check the number of flatlanders is valid
	// cancel if there is any error and let the player know what's wrong
	if(isNaN(num_flatlanders))
	{
		(num_flatlanders_string)
		object_flatlanders.focus();
		return 0;
	}
	else if (!IsInt(num_flatlanders))
	{
		alert(num_flatlanders_string)
		object_flatlanders.focus();
		return 0;		
	}
	else if (num_flatlanders<min_num_flatlanders)
	{
		alert(num_flatlanders_string)
		object_flatlanders.focus();
		return 0;		
	}
		else if (num_flatlanders>max_num_flatlanders)
	{
		alert(num_flatlanders_string)
		object_flatlanders.focus();
		return 0;		
	}
	
	// check the random seed is valid
	// cancel if there is any error and let the player know what's wrong
	if(isNaN(current_seed))
	{
		alert(seed_string)
		object_seed.focus();
		return 0;
	}
	else if (!IsInt(current_seed))
	{
		alert(seed_string)
		object_seed.focus();
		return 0;		
	}
	else if (current_seed<minSeed)
	{
		alert(seed_string)
		object_seed.focus();
		return 0;		
	}
	
	// if the numbers are ok, commit the UI values to the game
	else
	{
		tribeSize=document.getElementById("number_of_flatlanders").value;
		startSeed=document.getElementById("seed").value;
		setSeed=document.getElementById("set_seed").checked;
		return 1;
	}
}

function SettingsDialogEnableSeed()
{
	if (document.getElementById('set_seed').checked==true)
	{
		document.getElementById('seed').disabled=false;
		document.getElementById('seed_text').disabled=false;
	}
	else
	{
		document.getElementById('seed').disabled=true;
		document.getElementById('seed_text').disabled=true;
	}	
}


function NewGame()
{
	// if there is a modal dialog open, ignore a button click
	if (modalDialogOpen==1)
	{
		return;
	}
	// delete everybody except the mystery last/first person
	var num = people.length-2;
	var i;

	for (i = 0; i <= (num); i++)
	{
		DeletePerson(people[1]);
	}

	// and start a new game
	InitGame();
}

function SelectPerson(oPerson)
{
	// WriteDebug1("ID: " + oPerson._ID);
}

/////////////////////////////////
// Dragging Functions

// drag items are identified by their class
// class "dragdirectly" is for game objects like flatlanders that you just grab anywhere
// you can't drag game objects when the Settings dialog is open
// class "dialog_title_384" is for a dialog where you can only grab it by the title bar

var ie=document.all;
var nn6=document.getElementById&&!document.all;

var isdrag=false;
var MouseX,MouseY;
var dobj;

function movemouse(e)
{
  if (isdrag)
  {
    var newLeft = nn6 ? tx + e.clientX - MouseX : tx + event.clientX - MouseX;
    var newTop = nn6 ? ty + e.clientY - MouseY : ty + event.clientY - MouseY;

    dobj.style.left = newLeft;
    dobj.style.top  = newTop;
    
    if (dobj.className=="dragme")
    // if this is a game object with a strategy, update its position and interrupt the strategy
    {
    	dobj._x = newLeft;
    	dobj._y = newTop;

    	if (dobj._strategy != "drag")
    	{
    		dobj._strategy = "drag";		
			}		
		}
  return false;
  }
}

function selectmouse(e) 
{
  var fobj       = nn6 ? e.target : event.srcElement;
  var topelement = nn6 ? "HTML" : "BODY";

  while (fobj.tagName != topelement && fobj.className != "dragme" && fobj.className != "dialog_title_384")
  {
    fobj = nn6 ? fobj.parentNode : fobj.parentElement;
  }

  if ((fobj.className=="dragme" && modalDialogOpen==0) || (fobj.className=="dialog_title_384"))
  {
		isdrag = true;
    // when dragging a dialog by its title bar, move the whole dialog
    // when dragging a game object, move just the game object
    dobj = (fobj.className=="dialog_title_384")? fobj.parentNode : fobj;
    
    tx = parseInt(dobj.style.left+0);
    ty = parseInt(dobj.style.top+0);
    MouseX = nn6 ? e.clientX : event.clientX;
    MouseY = nn6 ? e.clientY : event.clientY;
    	
		document.onmousemove=movemouse;
    return false;
  }
}

document.onmousedown=selectmouse;
document.onmouseup=MouseUp;

function MouseUp()
{
	// if the mouse is released, stop any dragging behaviour and reset strategy of any game object that was being dragged
	isdrag=false;

	if (dobj != null)
	{
		if (dobj._strategy == "drag")
		{
			dobj._strategy = "evaluate";
			dobj._counter = 0;
		}
	}
}

/////////////////////////////////
// People Functions

function FindPersonByID(id)
{
	// returns the person object
	var i;
	for (i = 0; i <= (people.length-1); i++)
	{
		if (id == people[i]._ID)
		{
			return people[i];
		}
	}
	return -1;
}

function FindHighestRank(arr)
{
	// returns the highest ranked person in the array - returns the actual person NOT the id
	// this is because you have to find the actual person to get their rank
	// don't pass an array with 0 elements!
	var highestRank = 0;
	var highestRanker;
	var aPerson;
	
	var i;
	for (i = 0; i <= (arr.length-1); i++)
	{
		aPerson = FindPersonByID(arr[i]);
		
		if (aPerson._rank > highestRank)
		{
			highestRanker = aPerson;
			highestRank = aPerson._rank;
		}
	}
	return highestRanker;
}

function FindNeighbors(oPerson)
{
	var neighborsID=new Array();
	var neighborsDistance=new Array();
	var distance;
	
	// find the people this person can detect
	var i;
	for (i = 0; i <= (people.length-1); i++)
	{
		oNeighbor=people[i];
		if (oPerson._ID!=oNeighbor._ID)
		{
			// don't count yourself!
			// find how far away the neighbor is
			distance = FindDistance(oPerson, oNeighbor);
			if (distance < sightDistance)
			{
				// add the person to the list of neighbors
				neighborsID[neighborsID.length] = oNeighbor._ID;
				neighborsDistance[neighborsID.length] = distance;
			}
		}
	}
	return {_id:neighborsID, _distance:neighborsDistance};
}

function GetLeader(oPerson)
{
	// returns the id of a new leader for the person
	// find the people within sight
	var myNeighbors=new Array();
	var myNeighbors=FindNeighbors(oPerson)._id;
	var newLeader;
	var newLeader_id;
	var str = "";

	if (myNeighbors.length==0)
	{
		// if no neighbors, make the person their own leader
		newLeader_id = oPerson._ID;
	}
	else
	{
		newLeader = FindHighestRank(myNeighbors);

		if (newLeader._rank > oPerson._rank)
		{
			// follow somebody with higher rank than yourself
			newLeader_id = newLeader._ID;		
		}
		else
		{
			// make the person their own leader
			newLeader_id = oPerson._ID;	
		}
	}
		return newLeader_id;
}



function DeletePerson(oPerson)
{
	try
	{
		var index = oPerson._index;
		var elm = document.getElementById('TheTribe')
		elm.removeChild(people[oPerson._index]);

	// make sure all the remaining people have the right index
		var i;
		for (i=0; i<people.length; i++)
		{
			people[i]._index = i;
		}
	}
	catch(err)
	{
		// the last person doesn't seem to be deletable
		alert("can't delete this person")
	}
}

/////////////////////////////////
// Info Functions

function WriteDebug1(txt)
{
	document.getElementById("debug_01").innerHTML=txt;
}

function WriteInfo(object)
{
	var txt = "ID: " + object._ID;
	var txt = txt + ", Leader: " + object._leaderID;
//	var txt = txt + ", vx: " + object._vx;
//	var txt = txt + ", vy: " + object._vy;
//	var txt = txt + ", Rank: " + object._rank;
	var txt = txt + ", Strategy: " + object._strategy;
	document.getElementById("game_info").innerHTML=txt;
}

function ClearInfo()
{
	document.getElementById("game_info").innerHTML="&nbsp;";
}



//-->


