function getFullProgram(sched){ 		var sMPW = prParams["mpwe"]; 	var diffCode = getDiffCode(prParams["diff"]);	var isMara = isMarathon(); 		// For people in the 6-10 mile per week category, we need to provide 	// a special beginning program. Then we append the program from 11-15.	if(sMPW <= 10)	{ 		var aw = new Array(); 		for(var i = 0; i < prParams["slen"] && i < 4; i++)			{ aw[i] = new lprepWeek(i, sched, diffCode); }				if(prParams["slen"] > 4)		{ 			prParams["mpwe"] = 11; 			prParams["slen"] = prParams["slen"] - 4;			var nSched = new defaultScheduler(); 			var newProg = getFullProgram(nSched);  						for(var i = 0; i < nSched.getProgLength(); i++)				{ aw[aw.length] = newProg[i]; } 		} 		return aw; 	}		var aw = new Array();	// Currently, we seem to only have the codes for the marathon progression. 	var weekCodes = isMara ? mWeekCodes(sched.getProgLength()) : bWeekCodes(sched.getProgLength());    		var targDist = sMPW; 	var distMod = true;	var prevWeek = null; 			for(var i = 0; i < sched.getProgLength(); i++)	{ 		// calculate the mileage increment. 		aw[i] = isMara ? maraWeekObj(targDist, sched, weekCodes[i]) : weekObj(targDist, sched, weekCodes[i], prevWeek);		aw[i].setFromPrevWeeks(aw, sched); 		aw[i].allocateDist(); 						var a = 0; 		if(aw[i].addDist())		{ 			if(diffCode <= 1) { a = sMPW > 30 ? 2 : 1; } 			else			{ 				// Hard/Very Hard mileage increases - see 				// "Mileage Increases" section of spec				a = sMPW > 30 ? 2 : 1;				a = distMod ? a + 1 : a; 				distMod = !distMod; 			}			targDist += a; 		}		prevWeek = aw[i]; 	} 		return aw; } /** * Factory method which returns a week object of the appropriate type.  * For standard training program, this can be a Basic Week, Recovery Week,  * or Taper Week */ function weekObj(targDist, sched, wkCde, prevWeek){	var wInd = wkCde.match(/\d+/); 	wInd = wInd*1.0; 	if(wkCde.indexOf("r") != -1)		{ return new rcovrWeek(Math.floor(targDist*.75), sched); } 	if(wkCde.indexOf("t") != -1)		{ return new btaprWeek(targDist*.75, sched); } 	else		{ return new basicWeek(targDist, sched, wInd); }  }function genWeek(tDist){ genWeekApply(this, tDist); } /**  * This is a hack to circumvent the fact that Mac IE does not support  * genWeek.apply(this, [...]) */ function genWeekApply(gw, tDist){ 	gw.targDist = tDist; 	gw.days = new Array(); 	gw.dispData = "GENERIC";  		for(var i = 0; i < 7; i++)		{ gw.days[i] = new dayObj(); } } genWeek.prototype.getSpecRun = function(){  	for(var i = 0; i < 7; i++) 	{ 		if(isSpecRun(this.days[i])) 		{ return this.days[i]; } 	} 	return null; } genWeek.prototype.getLongRun = function(){ 	for(var i = 0; i < 7; i++) 	{ if(this.days[i].cde == "lgrn") { return this.days[i]; } } 	return null; } genWeek.prototype.addDist = function(){ return false; } genWeek.prototype.realDist = function(){	 	var x = 0; 	for(var i = 0; i < 7; i++) 	{		if(this.days[i] == null) { logErr("Warning>> null days object."); }		else { x = x + this.days[i].dst; }  	}         	return x; } /** * Distributes weekly distance to the days that can "accept" more distance. */ genWeek.prototype.allocateDist = function(){ 	var x = this.realDist(); 	var curRegDist = 2;  		// In principle, this should be an infinite loop, but that screws me 	// up all the time. 	for(var j = 0; j < 100; j++)	{ 		for(var i = 0; x < this.targDist && i < 7; i++)		{ 			if(this.days[i].addDist(curRegDist)) { x++; } 		} 				curRegDist += 1; 				if(x >= this.targDist) { break; } 	}} /** * Sets the training days from sched to be regular (EZ) runs. */ genWeek.prototype.setRegRuns = function(sched){ 	// Training days per week depends on CURRENT mileage for standard training, 	// but on STARTING mileage for marathon training. See "Workout Days" section 	// in spec.	var tds = isMarathon() ? 	sched.getTrainDays(prParams["mpwe"]) : 	sched.getTrainDays(this.targDist);		for(var i = 0; i < tds.length; i++)		{ this.days[tds[i]] = new regRun(); } } genWeek.prototype.getDisplayInfo = function(){ return this.dispData;  } genWeek.prototype.setFromPrevWeeks = function(prevWeeks){ } function dayObj(){ dayObjApply(this); } function dayObjApply(dObj){	/** 	 * This is the total distance for the run. 	 */ 	dObj.dst = 0; 	dObj.cde = "rest";	dObj.paceWeek = -1; } dayObj.prototype.getDescLine = function(lNum){ 	var dd = this.getDispData(); 	if(lNum >= dd.length) 		{ return ""; } 	return dd[lNum]; } dayObj.prototype.getDispData = function(){         return new Array("Rest", "/XT"); } dayObj.prototype.getDist = function(){ return this.dst; } dayObj.prototype.getPaceCode = function(){ return "xpacex"; }/*** Called to add distance to a day. BUT only has an effect for regular and special run * days! Returns true if the distance was successfully added. */ dayObj.prototype.addDist = function(curRegDist){ return false; } regRun.prototype = new dayObj(); function regRun(){ 	dayObjApply(this); 	this.cde = "easy"; 		// Regular runs must be at least two miles. 	this.dst = 2; } regRun.prototype.addDist = function(curRegDist){ this.dst = this.dst + 1; return true; } regRun.prototype.getDispData = function(){ 	var dd = new Array(); 	dd[0] = "Easy&nbsp\;Run"; 	dd[1] = "Dist:&nbsp\;" + getDistDispStr(this.dst, isMetric()) + "<br>@" + this.getPaceCode(); 	return dd; } longRun.prototype = new dayObj(); function longRun(wkDst){ 	dayObjApply(this); 	this.dst = 4; 	var dFlags = new Array(8, 12, 16, 20, 24, 28, 32, 36, 40, 47, 53, 60, 68, 77, 84, 93, 100); 		for(var i = 0; i < dFlags.length; i++) 		{ if(wkDst > dFlags[i]) this.dst++; }         	this.str = "Long Run"; 	this.cde = "lgrn"; 	} function longRunProg(sMPW, wInd){ 	var lr = new longRun(); 		var bDst = Math.round((sMPW - 11)/5) + 5; 	var addTo = Math.ceil(wInd/2-.005); 	lr.dst = bDst + addTo;	return lr; } longRun.prototype.getDispData = function(){ 	var dd = new Array(); 	dd[0] = "Long&nbsp\;Run"; 	dd[1] = "Dist:&nbsp\;" + getDistDispStr(this.dst, isMetric()) + "<br>@" + this.getPaceCode(); 	return dd; } function maraDispData(){ 	var dd = new Array(); 	dd[0] = "Marathon Race Day"; 	dd[1] = "26 miles"; 	dd[2] = "Good Luck!!!"; 	return dd; } /*** Returns the appropriate paced run object corresponding to the given week. This information can * now depend on the distinction between 5K/10K/half-marathon. */ function pacedRunObj(cMPW, wInd){ 	var mn = Math.floor((wInd-1)/3); 	var wi = wInd % 3; 	// This code implements the Marker system, by incrementing the starting 	// month number based on the starting MPW.		var reInd = mn; var rsInd = mn; var trInd = mn; 	if(prParams["mpwe"] > 45) {		reInd += 1; 		rsInd += 1; 		trInd += 2; 	} 	else if(prParams["mpwe"] > 30) { 		reInd += 1; 		rsInd += 1; 		trInd += 1; 	} 		var trAr = new Array(3, 4, 5, 6, 7, 8, 9); 		if(prParams.rdst.indexOf("fivk") != -1) 	{ 		if(wi == 1)			{ return new repeatEight(cMPW, reInd); } 		if(wi == 0)			{ return new repeatSixteen(cMPW, rsInd); } 		if(wi == 2) 			{ return new tempoRun(trAr[trInd]); } 	} 	else if(prParams.rdst.indexOf("tenk") != -1) 	{ 		if(wi == 2)			{ return new repeatEight(cMPW, reInd); } 		else			{ return new tempoRun(trAr[trInd]);   } 	} 	else if(prParams.rdst.indexOf("half") != -1) 	{ 		if(wi == 2)			{ return new repeatSixteen(cMPW, rsInd); } 		else			{ return new tempoRun(trAr[trInd]); } 	} 	return null; } tempoRun.prototype = new dayObj(); /**  * Creates a tempo Run from a distance which represents the actual length of  * the core run component. 2 miles of additional distance is added, and the  * total distance is rounded up.  */ function tempoRun(trDst){ 	dayObjApply(this); 	this.cde = "temp";	this.num = trDst; 	this.dst = Math.ceil(trDst) + 2; } function getTempoRunFromCMPW(cMPW){ 			logErr("Tempo run from current MPW: " + cMPW); 	var trAr = new Array(3, 3.5, 4, 5);  	var tFlags = new Array(15, 30, 45); 	var x = 0; 		for(var i = 0; i < tFlags.length; i++)		{ if(cMPW > tFlags[i]) x++; } 		logErr("Tempo run from current MPW: " + cMPW); 		logErr("TTempo run distance: " + trAr[x]); 			return new tempoRun(trAr[x]); } tempoRun.prototype.getDispData = function(){       	var dd = new Array(); 	dd[0] = "Tempo Run";    dd[1] = "Dist: " + getDistDispStr(this.dst, isMetric()) + ", inc<br>";     dd[1] = dd[1] + "Warm\; " + getDistDispStr(this.num, isMetric()) + "@" + this.getPaceCode() + "\; Cool"; 	return dd; } repeatEight.prototype = new dayObj(); /**  * Constructs a x800 special run from the current weekly mileage and the  * month number of the program. The cMPW is currently IGNORED, however, this is * a somewhat controversial point, so it remains in the function. See special  * run section in specs document for the origins of these distance progressions.  */ function repeatEight(cMPW, mn){ 	dayObjApply(this); 	this.cde = "y800"; 		var trAr = new Array(3, 4, 5, 6, 7, 8); 	var dsAr = new Array(5, 5, 6, 7, 8, 8); 	/* 	var mpwFlags = new Array(20, 30, 40); 	var x = 0; 		for(var i = 0; i < mpwFlags.length; i++)		{ if(cMPW > mpwFlags[i]) x++; }	*/ 		this.num = trAr[mn]; 	this.dst = dsAr[mn]; 	} repeatEight.prototype.getDispData = function(){ 	var dd = new Array(); 	dd[0] = "Speedwork";    dd[1] = "Dist: " + getDistDispStr(this.dst, isMetric()) + ", inc<br>";     dd[1] = dd[1] + "Warm\; " + this.num + "x800@" + this.getPaceCode() + "<br>";     dd[1] = dd[1] + "w/400 jogs\; Cool"; 	return dd; } repeatSixteen.prototype = new dayObj(); /**  * Constructs a x1600 object from the current weekly mileage and the month  * number. Currently the cMPW is IGNORED.  */ function repeatSixteen(cMPW, mn){ 	dayObjApply(this);	var trAr = new Array(2, 3, 4, 5, 6); 	var dsAr = new Array(5, 7, 8, 10, 11); 		/* 	var mpwFlags = getRSFlags(); 	var x = 0; 		for(var i = 0; i < mpwFlags.length; i++)		{ if(cMPW > mpwFlags[i]) x++; }	*/ 			this.num = trAr[mn]; 	this.dst = dsAr[mn]; 	this.cde = "y160"; } repeatSixteen.prototype.getDispData = function(){ 	var dd = new Array(); 	dd[0] = "Speedwork";    dd[1] = "Dist: " + getDistDispStr(this.dst, isMetric()) + ", inc<br>";     dd[1] = dd[1] + "Warm\; " + this.num + "x1600@" + this.getPaceCode() + "<br>";     dd[1] = dd[1] + "w/800 jogs\; Cool"; 	return dd; } repeatSixteen.prototype.addDist = function(curRegDist){ 	// If curRegDist above srDst, increment; otherwise no. 	// basically this should stay flat until reg run dist start to rise above it. 	if(curRegDist > this.dst)	{ 		this.dst += 1; 		return true; 	} 	return false; } repeatEight.prototype.addDist = repeatSixteen.prototype.addDist; tempoRun.prototype.addDist = repeatSixteen.prototype.addDist; function raceDay(){ 	dayObjApply(this);	this.dst = raceLengthFromCode(prParams["rdst"]); 	this.cde = "race"; 	} raceDay.prototype = new dayObj(); raceDay.prototype.getDispData = function(){ 	var tg = new trainGoal(); 	var dd = new Array(); 	dd[0] = "Race Day";	dd[1] = "Dist:&nbsp;" + (new trainGoal()).opts[dataObj["rdst"]] + "<br>Good Luck!"; 	return dd; } function raceLengthFromCode(cde){ 	switch(cde) { 		case "fivk" 	: return 3; 		case "tenk1" 	: return 5; 		case "tenk2"	: return 5; 		case "tenk3"	: return 6; 		case "tenk4" 	: return 8; 		case "half1"	: return 9; 		case "half2"	: return 10; 		case "half4"	: return 13; 		case "mara"		: return 26;		default			: return 10; // shouldn't happen...	} } /** * This is a hack to get around the fact that Mac IE does not support "instanceof" operation.  */ function isSpecRun(rObj){ 	var c = rObj.cde; 	return (c == "y800" || c == "temp" || c == "y160");}btaprWeek.prototype = new genWeek();/**  * Taper week for standard training. Need to provide the special run from the  * second week of the program, which is copied and inserted into this week. * Specifically DO NOT call genWeekApply, because that work is done in  * setFromPrevWeeks() code */ function btaprWeek(tDist, sched){ 		this.dispData = "STANDARD TAPER";  	this.cde = "btapr"; } /**  * Most of the data for this week is taken from the previous weeks.  * The easy runs and long run are 50% of the immediately previous week,  * while the special run comes from the first week of the program.  */ btaprWeek.prototype.setFromPrevWeeks = function(prevWeeks, sched){	var mpw = prParams["mpwe"]; 	var dAr; 	var sDist = 2; 		if(mpw <= 15) 	{ 		dAr = new Array(); 		dAr[0] = 2;  	} 	else if(mpw <= 25)	{		dAr = new Array(2, 3); 		sDist = 3; 	} 	else if(mpw <= 40)	{ 		dAr = new Array(2, 3, 5);		sDist = 4; 	} 	else	{ 		dAr = new Array(2, 3, 5); 		sDist = 5; 	} 			this.days = new Array(7); 	for(var i = 0; i < 7; i++)	{ this.days[i] = new dayObj(); } 		for(var i = 0; i < dAr.length; i++)	{ 		this.days[dAr[i]] = new regRun(); 		this.days[dAr[i]].dst = sDist; 	} 		// Special Run from first week that actually has a special run. 	for(var i = 0; i < prevWeeks.length; i++)	{ 		if(prevWeeks[i].getSpecRun() != null)		{ 			this.days[4] = mClone(prevWeeks[i].getSpecRun()); 			break; 		} 	} 		// Race day is assumed to be on Sunday	this.days[0] = new raceDay(); 		this.targDist = this.realDist(); } btaprWeek.prototype.allocateDist = function() { } function Scheduler(){ 	// Should never be called} Scheduler.prototype.longRunDay = function(){ return this.lrday; } /** * Returns the paced run day for this scheduler. The paced run day is always the  * training day that is "farthest" from the Long Run day.  */Scheduler.prototype.pacedRunDay = function(mpw){ 	var trDays = this.getTrainDays(mpw); 	var prtarg = (this.lrday + 3.5) % 7; 		var prd = 10000; 		for(var i = 0; i < trDays.length; i++)	{ 		prd = Math.abs(prd - prtarg) < Math.abs(trDays[i] - prtarg) ? prd : trDays[i]; 	} 	return prd; } /**  * This function returns the length of the program  */ Scheduler.prototype.getProgLength = function(){ 	// Dont *think* I need to check for errors here...?	return Math.round(prParams["slen"]*1.0);  } /*** Returns an array of days the user trains on. * If this has not been set explicitly, returns the default workout days. * See "Workout Days" section of specs doc. */ Scheduler.prototype.getTrainDays = function(mpw) { 	var nDays = 3;  			if(prParams.rdst == "mara")	{ 		if(mpw > 10) { nDays = 2; } 		if(mpw > 15) { nDays = 3; } 		if(mpw > 25) { nDays = 4; } 		if(mpw > 40) { nDays = 5; } 		if(mpw > 46) { nDays = 6; } 	} 	else	{ 		nDays = 3; 		if(mpw > 15) { nDays = 4; } 		if(mpw > 25) { nDays = 5; } 		if(mpw > 40) { nDays = 6; } 	} 		var btds = baseTrainDays(nDays); 		for(var i = 0; i < btds.length; i++)		{ btds[i] = (btds[i] + this.lrday) % 7; }		return btds; } function baseTrainDays(nDays){ 	if(nDays == 2) 		{ return new Array(0, 4); } 	if(nDays == 3) 		{ return new Array(0, 2, 4); } 	if(nDays == 4)		{ return new Array(0, 2, 4, 5); } 	if(nDays == 5)		{ return new Array(0, 2, 3, 4, 5); } 	if(nDays == 6)		{ return new Array(0, 2, 3, 4, 5, 6); } 	} defaultScheduler.prototype = new Scheduler(); function defaultScheduler(){ 	//Scheduler.apply(this, [])	this.lrday = prParams["lrdy"] * 1.0; } /*** Constructs a Pacer object from the given time information and race length.  * Calculates a modified base speed from the race length and time provided. * See Pace Fraction section in the specs document. */ function Pacer(hour, mins, secs, rlen) { 	{ 		var time = 0; 		time += hour * 60; 		time += mins * 1; 		time += secs / 60; 		this.time = time; 	} 		var dist = 0; 	if(rlen.indexOf("onem") != -1) { dist = 1609 / 1.12 ; } 	if(rlen.indexOf("fivk") != -1) { dist = 5000; } 	if(rlen.indexOf("tenk") != -1) { dist = 10000 / .96 ; } 	if(rlen.indexOf("half") != -1) { dist = 21097 / .91 ; } 	if(rlen.indexOf("mara") != -1) { dist = 42194 / .87 ; } 		this.baseSpeed = dist / this.time; } /**  * Returns a Pacer object calculated from the prParams.  */ function getPacerFromPR(){ return new Pacer(prParams["hour"], prParams["mins"], prParams["secs"], prParams["rlen"]); } /**  * Returns a Pacer object calculated from information in the form (document.forms.trainform).  */ function getPacerFromForm(){ return new Pacer(document.forms.trainform.hour.value, 	document.forms.trainform.mins.value, 	document.forms.trainform.secs.value, 	document.forms.trainform.rlen.value); } /** * Returns a time string of the form min:sec per mile/km.  * Have to *divide* by coef because of wacky runners inverse speed system.  * This code implements pace increases based on progression in the schedule.  */Pacer.prototype.getTimeString = function(dayO, met, wkNum){ 	var coef = this.getPaceCoef(dayO); 	var dMul = 1.0; 	{ 		var mNum = Math.floor(wkNum/4); 		if(prParams.diff == "mod" || prParams.diff == "hard")			{ dMul = dMul - mNum * .01; } 		if(prParams.diff == "vhard")			{ dMul = dMul - mNum * .02; } 	} 		return timeConvert(this.baseSpeed/(coef*dMul), met);     } /**  * Returns a coefficient used to modify the base 5K pace for different  * workout types. The coefficient is fixed for the special runs, while  * for the easy and long runs the coefficient depends on the baseSpeed.  * Specifically, for fast runners the easy/long pace is a larger fractional  * increase over the 5K pace than for slower runners. See the pace fraction  * section of the spec.  */ Pacer.prototype.getPaceCoef = function(dObj){ 	var pc = 1.19 	if(dObj.cde == "easy" || dObj.cde == "lgrn")	{ 		// First convert to min/mile		var mpm = 1/this.baseSpeed * 1609; 		if(mpm < 4) 			{ pc = 1.32; } 		else if(mpm < 5)			{ pc = (mpm - 4) * (-.02) + 1.32; } 		else if(mpm < 7) 			{ pc = (mpm - 5) * (-.01) + 1.30; } 		else if(mpm < 9)			{ pc = (mpm - 7) * (-.03/2) + 1.28; } 		else if(mpm < 11)			{ pc = (mpm - 9) * (-.03/2) + 1.25; }    		else if(mpm < 13)			{ pc = (mpm - 11) * (-.03/2) + 1.22; }		else			{ pc = 1.19; }	} 		// Tempo Run	if(dObj.cde == "temp")		{ pc = 1.07 + (dObj.num - 3) * .01; } 		if(dObj.cde == "y800")		{ pc = .48; } 		if(dObj.cde == "y160")		{ pc = 1.01; } 		return pc;   } basicWeek.prototype = new genWeek(); /** * Basic training week for non-marathon programs. */ function basicWeek(tDist, sched, wInd){ 	genWeekApply(this, tDist); 		this.dispData = "STANDARD TRAINING"; 		// First set the EZ runs. 	this.setRegRuns(sched); 		var lrd = sched.longRunDay(tDist); 	var prd = sched.pacedRunDay(tDist); 		// Sets the lgrn run. 	  	// this.days[lrd] = new longRun(tDist);	this.days[lrd] = new longRunProg(prParams["mpwe"], wInd); 		// All basic weeks have special runs.  	this.days[prd] = pacedRunObj(tDist, wInd); 		 }basicWeek.prototype.addDist = function(){ return true; } /*** Returns an array of strings representing week codes such as "n4" which is * the fourth normal training week. Argument is the number of training weeks * in the program. See "Date Limited" section in spec. */ function bWeekCodes(tw){ 	var wcs = weekCodeSub(tw);	var bwa = new Array(); 	var j = 1; 		for(var i = 0; i < wcs.length; i++)	{ 		for(var x = 0; x < wcs[i]; x++)		{ 			bwa[bwa.length] = ("n" + j);  			j += 1; 		} 		if(i < wcs.length - 1)			{ bwa[bwa.length] = ("r" + (i+1)); }  	} 	bwa[bwa.length] = "t1"; 	return bwa;  } function weekCodeSub(tw){ 	switch (tw){		case 16: return [3, 3, 3, 3]; 		case 15: return [3, 3, 3, 2];		case 14: return [4, 4, 3]; 		case 13: return [4, 3, 3]; 		case 12: return [3, 3, 3]; 		case 11: return [5, 4]; 		case 10: return [4, 4]; 		case  9: return [4, 3]; 		case  8: return [3, 3]; 		case  7: return [3, 2]; 		case  6: return [5]; 		case  5: return [4]; 		case  4: return [3]; 		case  3: return [2]; 		case  2: return [1]; 		default: 			logErr("Invalid argument to weekCodeSub:" + tw); 			return [1]; 	}} 