/**** 
* Classes
****/ 
/** 
* config {
* 	x        : Number || 0,
* 	y        : Number || 0,
* 	rotation : Number || 0,
* }
**/ 
var ConfigContainer = Container.expand(function (config) {
	var self = Container.call(this);
	config = config || {};
	;
	self.x = config.x || 0;
	self.y = config.y || 0;
	self.rotation = config.rotation || 0;
	if (config.scale !== undefined || config.scaleX !== undefined || config.scaleY !== undefined) {
		var scaleX = config.scaleX !== undefined ? config.scaleX : config.scale !== undefined ? config.scale : 1;
		var scaleY = config.scaleY !== undefined ? config.scaleY : config.scale !== undefined ? config.scale : 1;
		self.scale.set(scaleX, scaleY);
	}
	;
	return self;
});
var Sun = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	for (var i = 0; i < 10; i++) {
		self.attachAsset('circle', {
			width: 500 + 200 * i,
			height: 500 + 200 * i,
			color: 0xFFFF00,
			alpha: 0.1,
			anchorX: 0.5,
			anchorY: 0.5
		});
	}
	var sunAsset = self.attachAsset('sun', {
		anchorX: 0.5075,
		anchorY: 0.5,
		alpha: 0.75
	});
	;
	self.update = update;
	;
	function update() {
		if (LK.ticks % 30 == 0) {
			sunAsset.scale.x *= -1;
		}
	}
});
var RoadSegment = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var background = self.addChild(new Container());
	self.attachAsset('roadSegment', {
		anchorX: 1,
		anchorY: 1
	});
	self.attachAsset('roadSegment', {
		anchorX: 1,
		anchorY: 1,
		scaleX: 0.95,
		scaleY: 0.95,
		tint: 0x555555
	});
	if (config.index % 3 === 0) {
		attachAssetRadial(self, 'square', {
			offset: 200,
			width: 100,
			height: 10,
			anchorR: 0.5,
			anchorX: 0.5,
			anchorY: 0.5
		});
	}
	;
	self.update = update;
	self.regenerate = regenerate;
	self.fadeout = false;
	self.distance = config.distance;
	self.step = config.step;
	self.stepChance = config.stepChance;
	self.previous = config.previous;
	;
	function attachAssetRadial(parent, asset, obj) {
		var offsetTotal = ROAD_SEGMENT_HEIGHT - (obj.offset || 10);
		var rotation = -(1 - obj.anchorR) * ROAD_SEGMENT_ANGLE;
		parent.attachAsset(asset, {
			x: Math.sin(rotation) * offsetTotal,
			y: Math.cos(rotation) * -offsetTotal,
			width: obj.width,
			height: obj.height,
			anchorX: obj.anchorX,
			anchorY: obj.anchorY,
			scaleX: obj.scaleX || 1,
			scaleY: obj.scaleY || 1,
			rotation: (obj.rotation || 0) + rotation
		});
	}
	function generateBackground() {
		console.log(self.distance);
		background.removeChildren();
		var plantCount = Math.floor(Math.random() * (ROAD_GEN_PLANT_MAX + 1));
		if (self.distance === SCORE_TOTAL_DISTANCE) {
			attachAssetRadial(background, 'flag', {
				anchorR: 0.5,
				anchorX: 0.1,
				anchorY: 1
			});
		} else {
			if (Math.random() < ROAD_GEN_TREE_CHANCE) {
				var treeIndex = Math.floor(Math.random() * ROAD_GEN_TREE_ASSETS);
				var scale = 0.9 + 0.2 * Math.random();
				attachAssetRadial(background, 'tree' + treeIndex, {
					anchorR: 0.4 + 0.2 * Math.random(),
					anchorX: 0.5,
					anchorY: 1,
					scaleX: scale,
					scaleY: scale
				});
			}
			for (var i = 0; i < plantCount; i++) {
				var plantIndex = Math.floor(Math.random() * ROAD_GEN_PLANT_ASSETS);
				var scale = 0.9 + 0.2 * Math.random();
				attachAssetRadial(background, 'plant' + plantIndex, {
					anchorR: 0.1 + 0.8 * Math.random(),
					anchorX: 0.5,
					anchorY: 1,
					scaleX: Math.random() < 0.5 ? -scale : scale,
					scaleY: scale
				});
			}
		}
		if (Math.random() < ROAD_GEN_CLOUD_CHANCE) {
			var cloudIndex = Math.floor(Math.random() * ROAD_GEN_CLOUD_ASSETS);
			var scale = 1.0 + 1.5 * Math.random();
			attachAssetRadial(background, 'cloud' + cloudIndex, {
				offset: -800,
				anchorR: -1.0 + 2.0 * Math.random(),
				anchorX: 0.5,
				anchorY: 0.5,
				scaleX: scale,
				scaleY: scale
			});
		}
	}
	function regenerate(incrementDistance) {
		if (incrementDistance) {
			self.distance += SCORE_SEGMENT_DISTANCE * ROAD_SEGMENT_COUNT;
		}
		var stepping = self.distance <= SCORE_TOTAL_DISTANCE;
		var stepChance = self.previous.stepChance;
		var stepUp = stepping && (self.previous.step < ROAD_STEP_COUNT ? Math.random() < self.stepChance : false);
		var stepDown = stepping && (self.previous.step > 0 ? Math.random() < self.stepChance : false);
		if (stepUp || stepDown) {
			if (stepUp && stepDown) {
				if (Math.random() < 0.5) {
					stepDown = false;
				} else {
					stepUp = false;
				}
			}
			self.step = self.previous.step + (stepUp ? 1 : -1);
			self.stepChance = ROAD_STEP_CHANCE_BASE;
		} else {
			self.step = self.previous.step;
			self.stepChance = self.previous.stepChance + ROAD_STEP_CHANCE_INCREMENT;
		}
		generateBackground();
		self.fadeout = false;
		rescale();
	}
	function update() {
		if (self.fadeout && self.alpha > 0) {
			if ((self.alpha -= ROAD_SEGMENT_FADEOUT) < 0) {
				self.alpha = 0;
			}
		} else if (!self.fadeout && self.alpha < 1) {
			if ((self.alpha += ROAD_SEGMENT_FADEOUT) > 1) {
				self.alpha = 1;
			}
		}
	}
	function rescale() {
		var newScale = (ROAD_STEP_HEIGHT_BASE + self.step * ROAD_STEP_HEIGHT_INCREMENT) / ROAD_SEGMENT_HEIGHT; // TEMP
		self.scale.set(newScale);
	}
	;
	rescale();
});
var Road = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var segments = [];
	for (var i = 0; i < ROAD_SEGMENT_COUNT; i++) {
		var segment = segments[i] = self.addChild(new RoadSegment({
			index: i,
			previous: i > 0 ? segments[i - 1] : undefined,
			rotation: i * ROAD_SEGMENT_ANGLE,
			distance: (i - 1) * SCORE_SEGMENT_DISTANCE,
			stepChance: ROAD_STEP_CHANCE_BASE,
			step: ROAD_STEP_DEFAULT
		}));
		if (i === ROAD_SEGMENT_COUNT - 1) {
			segments[0].previous = segment;
		}
		if (i >= ROAD_FREE_RUN_COUNT) {
			segment.regenerate();
		}
		self.attachAsset('roadSegment', {
			anchorX: 1,
			anchorY: 1,
			scaleX: 0.27,
			scaleY: 0.27,
			rotation: i * ROAD_SEGMENT_ANGLE
		});
	}
	self.attachAsset('circle', {
		width: 500,
		height: 500,
		anchorX: 0.5,
		anchorY: 0.5,
		tint: 0x222222
	});
	self.attachAsset('circle', {
		width: 490,
		height: 490,
		anchorX: 0.5,
		anchorY: 0.5,
		tint: 0x87CEEB
	});
	var destroyIndex = -1;
	var regenerateIndex = -1;
	;
	self.rotate = rotate;
	self.getIndex = getIndex;
	self.getNode = getNode;
	self.getNodeDist = getNodeDist;
	;
	function rotate(speed) {
		self.rotation -= speed;
		var newDestroyIndex = getIndex(ROAD_SEGMENT_DESTROY);
		var newRegenerateIndex = getIndex(20); // TEMP
		if (newDestroyIndex >= 0 && newDestroyIndex !== destroyIndex && !segments[newDestroyIndex].fadeout) {
			destroyIndex = newDestroyIndex;
			segments[destroyIndex].fadeout = true;
		}
		if (newRegenerateIndex >= 0 && newRegenerateIndex !== regenerateIndex && segments[newRegenerateIndex].fadeout) {
			regenerateIndex = newRegenerateIndex;
			segments[regenerateIndex].regenerate(true);
		}
	}
	function getIndex(shift) {
		return Math.floor(-self.rotation / MATH_2_PI * ROAD_SEGMENT_COUNT + (shift || 0) + 1) % ROAD_SEGMENT_COUNT;
	}
	function getNode(shift) {
		return segments[getIndex(shift)];
	}
	function getNodeDist() {
		return ROAD_SEGMENT_ANGLE - -self.rotation % ROAD_SEGMENT_ANGLE;
	}
});
var PlayerBody = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	// Animation settings
	var animation = animationStand;
	var alpha = 0;
	var blendAnimation = undefined;
	var blendDecrement = 0;
	var blendAlpha = 0;
	var afterTint = 0xAAAAAA;
	// Body settings
	var bodyOffsetY = config.y || 0;
	var pelvisOffsetX = -10;
	var armUpperAngle = -3 * MATH_EIGHTH_PI;
	var armUpperOffsetX = 15;
	var armUpperOffsetY = 30;
	var armLowerAngle = -MATH_QUARTER_PI;
	var armLowerOffsetY = 90;
	var legUpperAngle = 3 * MATH_EIGHTH_PI;
	var legUpperOffsetX = 10;
	var legLowerOffsetY = 135;
	var footOffsetY = 100;
	// Create and attach body parts
	var armUpperRight = self.addChild(new ConfigContainer({
		y: armUpperOffsetY
	}));
	var legUpperRight = self.addChild(new ConfigContainer({
		x: pelvisOffsetX + legUpperOffsetX
	}));
	var torso = self.attachAsset('torso', {
		anchorX: 0.5
	});
	var head = self.attachAsset('head', {
		anchorX: 0.15,
		anchorY: 0.95
	});
	var pelvis = self.attachAsset('pelvis', {
		x: pelvisOffsetX,
		y: torso.height,
		anchorX: 0.5,
		anchorY: 0.2,
		tint: afterTint
	});
	var legUpperLeft = self.addChild(new ConfigContainer({
		x: pelvisOffsetX - legUpperOffsetX
	}));
	var armUpperLeft = self.addChild(new ConfigContainer({
		y: armUpperOffsetY
	}));
	// Arm extensions
	armUpperRight.attachAsset('upperArm', {
		rotation: armUpperAngle,
		anchorX: 0.85,
		anchorY: 0.25,
		tint: afterTint
	});
	armUpperLeft.attachAsset('upperArm', {
		rotation: armUpperAngle,
		anchorX: 0.85,
		anchorY: 0.25
	});
	var armLowerRight = armUpperRight.attachAsset('lowerArm', {
		y: armLowerOffsetY,
		rotation: armLowerAngle,
		anchorX: 0.95,
		anchorY: 0.05,
		tint: afterTint
	});
	var armLowerLeft = armUpperLeft.attachAsset('lowerArm', {
		y: armLowerOffsetY,
		rotation: armLowerAngle,
		anchorX: 0.95,
		anchorY: 0.05
	});
	// Leg extensions
	legUpperRight.attachAsset('upperLeg', {
		rotation: legUpperAngle,
		anchorY: 0.1,
		tint: afterTint
	});
	legUpperLeft.attachAsset('upperLeg', {
		rotation: legUpperAngle,
		anchorY: 0.1
	});
	var legLowerRight = legUpperRight.attachAsset('lowerLeg', {
		y: legLowerOffsetY,
		anchorX: 0.65,
		tint: afterTint
	});
	var legLowerLeft = legUpperLeft.attachAsset('lowerLeg', {
		y: legLowerOffsetY,
		anchorX: 0.65
	});
	var footRight = legLowerRight.attachAsset('foot', {
		y: footOffsetY,
		anchorX: 0.2,
		anchorY: 0.05,
		tint: afterTint
	});
	var footLeft = legLowerLeft.attachAsset('foot', {
		y: footOffsetY,
		anchorX: 0.2,
		anchorY: 0.05
	});
	// Additional positioning
	armUpperLeft.x = -torso.width / 2 + armUpperOffsetX;
	armUpperRight.x = torso.width / 2 - armUpperOffsetX;
	legUpperLeft.y = torso.height + pelvis.height / 3;
	legUpperRight.y = torso.height + pelvis.height / 3;
	;
	self.animate = animate;
	self.pushAnimation = pushAnimation;
	;
	function animate(increment) {
		alpha = (alpha + increment) % 1;
		if (blendAlpha > 0) {
			if ((blendAlpha -= blendDecrement) <= 0) {
				blendAlpha = 0;
				blendAnimation = undefined;
			}
		}
		var pose = animation(alpha);
		var blendPose = blendAnimation && blendAnimation(alpha);
		self.y = animateProperty(pose, blendPose, blendAlpha, 'BODY_OFFSET', bodyOffsetY);
		head.rotation = animateProperty(pose, blendPose, blendAlpha, 'HEAD_ROTATION');
		armUpperRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_UPPER_RIGHT_ROTATION');
		armUpperLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_UPPER_LEFT_ROTATION');
		armLowerRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_LOWER_RIGHT_ROTATION', armLowerAngle);
		armLowerLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_LOWER_LEFT_ROTATION', armLowerAngle);
		legUpperRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_UPPER_RIGHT_ROTATION');
		legUpperLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_UPPER_LEFT_ROTATION');
		legLowerRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_LOWER_RIGHT_ROTATION');
		legLowerLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_LOWER_LEFT_ROTATION');
		footRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'FOOT_RIGHT_ROTATION');
		footLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'FOOT_LEFT_ROTATION');
	}
	function pushAnimation(newAnimation, newBlendDecrement) {
		blendDecrement = newBlendDecrement;
		if (newAnimation !== animation) {
			blendAnimation = animation;
			animation = newAnimation;
			blendAlpha = 1;
		}
	}
	;
	animate(0);
});
var Player = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var speedFactor = PLAYER_SPEED_FACTOR_BASE;
	var stopped = false;
	var falling = false;
	var jumping = false; // If the player is currently jumping (and immune to gravity)
	var jumpAction = false; // Tracks tap releases
	var jumpStep = 0; // Tracks min/max jump heights
	var aerialSpeed = 0; // Actual vertical/air speed
	var baseY = config.y;
	var step = ROAD_STEP_DEFAULT;
	var nodeDistance = 0;
	var nodeTicks = 0;
	var nodeMulti = 0;
	var body = self.addChild(new PlayerBody({
		y: -370
	}));
	;
	self.update = update;
	self.jumpStart = jumpStart;
	self.jumpEnd = jumpEnd;
	self.jumping = false;
	;
	function update() {
		var nextNode = road.getNode(1);
		var currentNode = nextNode.previous;
		// Update horizontal movement
		var outputSpeed = PLAYER_SPEED_BASE * speedFactor;
		var distance = road.getNodeDist();
		// Check for collision
		if (stopped && nextNode.step <= step) {
			stopped = false;
		} else if (nextNode.step > step && outputSpeed > distance) {
			road.rotate(distance - PLAYER_SPACING);
			speedFactor = PLAYER_SPEED_FACTOR_BASE;
			stopped = true;
			if (currentNode.step <= step) {
				body.pushAnimation(animationStand, PLAYER_POSE_TRANSITION);
			}
		}
		if (!stopped) {
			road.rotate(outputSpeed);
			if (!gameOver) {
				if (speedFactor < PLAYER_SPEED_FACTOR_MAX) {
					if ((speedFactor += PLAYER_SPEED_FACTOR_INCREMENT) >= PLAYER_SPEED_FACTOR_MAX) {
						speedFactor = PLAYER_SPEED_FACTOR_MAX;
					}
				}
			} else {
				if (speedFactor > 0) {
					if ((speedFactor -= PLAYER_SPEED_DECREMENT) <= 0) {
						speedFactor = 0;
					}
				}
			}
		}
		// TOOD: Get new node if applicable
		// Update vertical movement
		if (jumping) {
			jumpStep += aerialSpeed;
			if (!jumpAction && jumpStep >= PLAYER_JUMP_STEP_MIN || jumpStep >= PLAYER_JUMP_STEP_MAX) {
				falling = true;
				jumping = false;
				jumpAction = false;
				jumpStep = 0;
			}
		} else if (falling) {
			aerialSpeed -= PLAYER_GRAVITY;
		} else if (step > currentNode.step) {
			falling = true;
			body.pushAnimation(animationJump, PLAYER_POSE_TRANSITION);
		}
		step += aerialSpeed;
		if (falling && step <= currentNode.step) {
			step = currentNode.step;
			falling = false;
			aerialSpeed = 0;
			body.pushAnimation(stopped ? animationStand : animationRun, PLAYER_POSE_TRANSITION);
		}
		// Score calculations
		if (!gameOver && currentNode.distance > nodeDistance) {
			adjustScore(nodeMulti / nodeTicks);
			currentNode.distance = nodeDistance;
			nodeMulti = 0;
			nodeTicks = 0;
		}
		nodeMulti += stopped ? SCORE_STOPPED_FACTOR : speedFactor;
		nodeTicks++;
		// Adjustments
		var stepHeight = ROAD_STEP_HEIGHT_BASE + step * ROAD_STEP_HEIGHT_INCREMENT; // TODO: Better scaling
		self.y = baseY - stepHeight; // TODO: Better scaling
		self.scale.set(PLAYER_SCALE_BASE * stepHeight / ROAD_SEGMENT_HEIGHT); // TODO: Better scaling
		body.animate(PLAYER_ANIMATION_SPEED_BASE * speedFactor);
		multiplierText.setText((stopped ? SCORE_STOPPED_FACTOR : speedFactor).toFixed(1) + 'x');
	}
	function jumpStart() {
		if (!gameOver && !jumping && !falling) {
			jumping = true;
			jumpAction = true;
			aerialSpeed = PLAYER_JUMP_STEP_INCREMENT;
			body.pushAnimation(animationJump, PLAYER_POSE_TRANSITION);
		}
	}
	function jumpEnd() {
		jumpAction = false;
	}
	function adjustScore(averageMulti) {
		var points = Math.floor(averageMulti * SCORE_SEGMENT_POINTS);
		score = Math.max(0, score + points);
		scoreText.setText(String(score).padStart(6, '0'));
		distanceText.setText((distanceRemaining -= SCORE_SEGMENT_DISTANCE) + 'm');
		incrementText.setText((points === SCORE_MAX_POINTS ? '[MAX!] +' : '+') + points);
		if (distanceRemaining <= 0) {
			body.pushAnimation(animationStand, PLAYER_SPEED_DECREMENT / speedFactor);
			callGameOver();
		}
	}
	;
	body.pushAnimation(animationRun, PLAYER_POSE_TRANSITION);
});
/** 
* config {
* 	x        : Number || 0, // See: ConfigContainer
* 	y        : Number || 0, // See: ConfigContainer
* 	rotation : Number || 0, // See: ConfigContainer
* 	anchorX  : Number || 0,
* 	anchorY  : Number || 1,
* 	size     : Number || TEXT_DEFAULT_SIZE,
* 	weight   : Number || TEXT_DEFAULT_WEIGHT,
* 	font     : String || TEXT_DEFAULT_FONT,
* 	fill     : String || TEXT_DEFAULT_FILL,
* 	border   : String || TEXT_DEFAULT_BORDER,
* }
**/ 
var BorderedText = ConfigContainer.expand(function (text, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	;
	var anchorX = config.anchorX !== undefined ? config.anchorX : 0;
	var anchorY = config.anchorY !== undefined ? config.anchorY : 1;
	var size = config.size !== undefined ? config.size : TEXT_DEFAULT_SIZE;
	var weight = config.weight !== undefined ? config.weight : TEXT_DEFAULT_WEIGHT;
	var font = config.font !== undefined ? config.font : TEXT_DEFAULT_FONT;
	var textFill = config.fill !== undefined ? config.fill : TEXT_DEFAULT_FILL;
	var borderFill = config.border !== undefined ? config.border : TEXT_DEFAULT_BORDER;
	var textAssets = [];
	var mainAsset;
	;
	self.setText = setText;
	self.setFill = setFill;
	;
	function setText(newText) {
		for (var i = 0; i < textAssets.length; i++) {
			textAssets[i].setText(newText);
		}
	}
	function setFill(newFill) {
		textFill = newFill;
		mainAsset.fill = newFill;
	}
	function buildTextAssets(newText) {
		for (var i = 0; i < TEXT_OFFSETS.length; i++) {
			var main = i === TEXT_OFFSETS.length - 1;
			var fill = main ? textFill : borderFill;
			var textAsset = textAssets[i];
			if (textAsset) {
				textAsset.destroy();
			}
			textAsset = self.addChild(new Text2(newText, {
				fill: fill,
				font: font,
				size: size
			}));
			textAsset.anchor = {
				x: anchorX,
				y: anchorY
			}; // NOTE: Cannot be set in config
			textAsset.x = TEXT_OFFSETS[i][0] * weight; // NOTE: Cannot be set in config
			textAsset.y = TEXT_OFFSETS[i][1] * weight; // NOTE: Cannot be set in config
			textAssets[i] = textAsset;
		}
		mainAsset = textAssets[TEXT_OFFSETS.length - 1];
	}
	;
	buildTextAssets(text);
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x87CEEB // Sky blue color
});
/**** 
* Game Code
****/ 
;
//==============================================================================
// Global Constants & Settings
//==============================================================================
;
// Math Constants / Pre-calculations
var MATH_4_PI = Math.PI * 4;
var MATH_2_PI = Math.PI * 2;
var MATH_HALF_PI = Math.PI / 2;
var MATH_THIRD_PI = Math.PI / 3;
var MATH_QUARTER_PI = Math.PI / 4;
var MATH_FIFTH_PI = Math.PI / 5;
var MATH_SIXTH_PI = Math.PI / 5;
var MATH_EIGHTH_PI = Math.PI / 8;
var MATH_HALF_ROOT_3 = Math.sqrt(3) / 2; // Required by: TEXT_OFFSETS, BorderedText, BorderedSymbol, BorderedShape, SymbolText
;
// Text Settings
var TEXT_OFFSETS = [[0, 1], [MATH_HALF_ROOT_3, 0.5], [MATH_HALF_ROOT_3, -0.5], [0, -1], [-MATH_HALF_ROOT_3, -0.5], [-MATH_HALF_ROOT_3, 0.5], [0, 0]]; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_WEIGHT = 4; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_BORDER = '#000000'; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_FILL = '#FFFFFF'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_FONT = 'Courier'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_SIZE = 80; // Required by: BorderedText, SymbolText
;
// Game Constants
var GAME_TICKS = 60;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
;
// Road Constants
var ROAD_SEGMENT_DISTANCE = 1.0;
var ROAD_SEGMENT_POINTS = 100;
var ROAD_SEGMENT_COUNT = 30;
var ROAD_SEGMENT_ANGLE = MATH_2_PI / ROAD_SEGMENT_COUNT;
var ROAD_SEGMENT_HEIGHT = 1000;
var ROAD_SEGMENT_DESTROY = -2;
var ROAD_SEGMENT_FADEOUT = 0.015;
var ROAD_FREE_RUN_COUNT = 5;
var ROAD_STEP_COUNT = 5;
var ROAD_STEP_DEFAULT = 2;
var ROAD_STEP_HEIGHT_BASE = 400;
var ROAD_STEP_HEIGHT_INCREMENT = 80;
var ROAD_STEP_CHANCE_BASE = 0.1;
var ROAD_STEP_CHANCE_INCREMENT = 0.05;
var ROAD_GEN_PLANT_ASSETS = 3;
var ROAD_GEN_PLANT_MAX = 3;
var ROAD_GEN_TREE_ASSETS = 4;
var ROAD_GEN_TREE_CHANCE = 0.1;
var ROAD_GEN_CLOUD_ASSETS = 4;
var ROAD_GEN_CLOUD_CHANCE = 0.2;
;
// Player Constants
var PLAYER_SPACING = ROAD_SEGMENT_ANGLE / 8; // 1/8 of a segment
var PLAYER_POSE_TRANSITION = 0.1;
var PLAYER_GRAVITY = 0.02;
var PLAYER_JUMP_STEP_MIN = 1.0;
var PLAYER_JUMP_STEP_MAX = 2.0;
var PLAYER_JUMP_STEP_INCREMENT = 0.15;
var PLAYER_ANIMATION_SPEED_BASE = 0.02;
var PLAYER_SCALE_BASE = 0.5;
var PLAYER_SPEED_BASE = MATH_2_PI / (GAME_TICKS * 10); // 10s to complete a revolution
var PLAYER_SPEED_FACTOR_BASE = 1.0;
var PLAYER_SPEED_FACTOR_MAX = 1.5;
var PLAYER_SPEED_FACTOR_INCREMENT = 0.001;
var PLAYER_SPEED_DECREMENT = 0.01;
;
// Scoring Constants
var SCORE_TOTAL_DISTANCE = 500;
var SCORE_SEGMENT_DISTANCE = 1;
var SCORE_SEGMENT_POINTS = 100;
var SCORE_STOPPED_FACTOR = -0.2;
var SCORE_MAX_POINTS = SCORE_SEGMENT_POINTS * PLAYER_SPEED_FACTOR_MAX;
//==============================================================================
// Game Instances & Variables
//==============================================================================
;
// Variables
var score = 0;
var winningTime = 0;
var gameOver = false;
var distanceRemaining = SCORE_TOTAL_DISTANCE;
;
// Instances
var sun = game.addChild(new Sun({
	x: GAME_WIDTH / 2,
	y: 50
}));
var road = game.addChild(new Road({
	x: GAME_WIDTH / 2,
	y: GAME_HEIGHT - GAME_WIDTH / 2
}));
var player = game.addChild(new Player({
	x: road.x,
	y: road.y,
	scale: 0.5
}));
var distanceText = game.addChild(new BorderedText(distanceRemaining + 'm', {
	x: road.x,
	y: road.y - TEXT_DEFAULT_SIZE,
	anchorX: 0.5,
	anchorY: 1
}));
var scoreText = game.addChild(new BorderedText('000000', {
	x: road.x,
	y: road.y,
	size: TEXT_DEFAULT_SIZE * 1.5,
	anchorX: 0.5,
	anchorY: 0.5
}));
var multiplierText = game.addChild(new BorderedText('1.0x', {
	x: road.x,
	y: road.y + TEXT_DEFAULT_SIZE,
	anchorX: 0.5,
	anchorY: 0
}));
var incrementText = game.addChild(new BorderedText('', {
	x: road.x - 300,
	y: road.y,
	anchorX: 1,
	anchorY: 0.5
}));
var winningText = game.addChild(new BorderedText('', {
	x: GAME_WIDTH / 2,
	y: 500,
	anchorX: 0.5,
	anchorY: 0.5,
	size: 2 * TEXT_DEFAULT_SIZE
}));
;
//==============================================================================
// Global events
//==============================================================================
;
game.down = player.jumpStart;
game.up = player.jumpEnd;
;
//==============================================================================
// Global functions
//==============================================================================
;
// Helpers
function callGameOver() {
	gameOver = true;
	winningTime = LK.ticks;
	LK.setScore(score);
	winningText.setText('Congratulations!');
	// Check if winningTime is set and show game over screen after 60 ticks
	game.update = function () {
		if (winningTime && LK.ticks >= winningTime + 120) {
			LK.showGameOver();
		}
	};
}
;
// Animations & handlers
function animateProperty(animationPose, blendPose, blendAlpha, key, baseValue) {
	var animationPoseValue = (animationPose || {})[key] || 0;
	var blendPoseValue = (blendPose || {})[key] || 0;
	return (baseValue || 0) + animationPoseValue * (1 - blendAlpha) + blendPoseValue * blendAlpha;
}
function animationRun(alpha) {
	// Animation constants
	var armUpperAngle = MATH_FIFTH_PI;
	var armLowerAngle = -5 * MATH_EIGHTH_PI;
	var legLowerAngle = MATH_EIGHTH_PI;
	// Sinusoidals
	var rightSinusoidal = Math.cos(MATH_2_PI * (alpha + 0.5));
	var leftSinusoidal = Math.cos(MATH_2_PI * alpha);
	var doubleSinusoidal = Math.cos(MATH_4_PI * alpha);
	// Animation calculations
	var armUpperRightSwing = -rightSinusoidal * MATH_QUARTER_PI;
	var armUpperLeftSwing = -leftSinusoidal * MATH_QUARTER_PI;
	var legUpperRightSwing = rightSinusoidal * MATH_THIRD_PI;
	var legUpperLeftSwing = leftSinusoidal * MATH_THIRD_PI;
	return {
		BODY_OFFSET: doubleSinusoidal * 10,
		HEAD_ROTATION: (1 + doubleSinusoidal) * Math.PI / 32,
		ARM_UPPER_RIGHT_ROTATION: armUpperAngle + armUpperRightSwing,
		ARM_UPPER_LEFT_ROTATION: armUpperAngle + armUpperLeftSwing,
		ARM_LOWER_RIGHT_ROTATION: armLowerAngle + armUpperRightSwing / 2,
		ARM_LOWER_LEFT_ROTATION: armLowerAngle + armUpperLeftSwing / 2,
		LEG_UPPER_RIGHT_ROTATION: legUpperRightSwing,
		LEG_UPPER_LEFT_ROTATION: legUpperLeftSwing,
		LEG_LOWER_RIGHT_ROTATION: legLowerAngle + (alpha > 0.5 ? MATH_THIRD_PI + (1 - Math.abs(rightSinusoidal)) * MATH_SIXTH_PI : Math.abs(-legUpperRightSwing)),
		LEG_LOWER_LEFT_ROTATION: legLowerAngle + (alpha <= 0.5 ? MATH_THIRD_PI + (1 - Math.abs(leftSinusoidal)) * MATH_SIXTH_PI : Math.abs(-legUpperLeftSwing)),
		FOOT_RIGHT_ROTATION: Math.max(0, legUpperRightSwing * 2 / 3) - legLowerAngle,
		FOOT_LEFT_ROTATION: Math.max(0, legUpperLeftSwing * 2 / 3) - legLowerAngle
	};
}
function animationJump(alpha) {
	var sinusoidal = Math.cos(MATH_2_PI * alpha);
	return animationRun(0.35 + sinusoidal * 0.02);
}
function animationStand(alpha) {
	var sinusoidal = Math.cos(MATH_2_PI * alpha);
	var offsetAngle = Math.PI / 16;
	var sharedAngle = (2 + sinusoidal) * Math.PI / 64;
	return {
		BODY_OFFSET: sinusoidal * 5,
		ARM_UPPER_RIGHT_ROTATION: 0.5 * offsetAngle,
		ARM_UPPER_LEFT_ROTATION: 3 * offsetAngle,
		ARM_LOWER_RIGHT_ROTATION: -4 * offsetAngle,
		ARM_LOWER_LEFT_ROTATION: -4 * offsetAngle,
		LEG_UPPER_RIGHT_ROTATION: 1.5 * offsetAngle - sharedAngle,
		LEG_UPPER_LEFT_ROTATION: -1.5 * offsetAngle - sharedAngle,
		LEG_LOWER_RIGHT_ROTATION: 2 * sharedAngle,
		LEG_LOWER_LEFT_ROTATION: 2 * sharedAngle,
		FOOT_RIGHT_ROTATION: -sharedAngle - 1.5 * offsetAngle,
		FOOT_LEFT_ROTATION: -sharedAngle + 1.5 * offsetAngle
	};
} /**** 
* Classes
****/ 
/** 
* config {
* 	x        : Number || 0,
* 	y        : Number || 0,
* 	rotation : Number || 0,
* }
**/ 
var ConfigContainer = Container.expand(function (config) {
	var self = Container.call(this);
	config = config || {};
	;
	self.x = config.x || 0;
	self.y = config.y || 0;
	self.rotation = config.rotation || 0;
	if (config.scale !== undefined || config.scaleX !== undefined || config.scaleY !== undefined) {
		var scaleX = config.scaleX !== undefined ? config.scaleX : config.scale !== undefined ? config.scale : 1;
		var scaleY = config.scaleY !== undefined ? config.scaleY : config.scale !== undefined ? config.scale : 1;
		self.scale.set(scaleX, scaleY);
	}
	;
	return self;
});
var Sun = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	for (var i = 0; i < 10; i++) {
		self.attachAsset('circle', {
			width: 500 + 200 * i,
			height: 500 + 200 * i,
			color: 0xFFFF00,
			alpha: 0.1,
			anchorX: 0.5,
			anchorY: 0.5
		});
	}
	var sunAsset = self.attachAsset('sun', {
		anchorX: 0.5075,
		anchorY: 0.5,
		alpha: 0.75
	});
	;
	self.update = update;
	;
	function update() {
		if (LK.ticks % 30 == 0) {
			sunAsset.scale.x *= -1;
		}
	}
});
var RoadSegment = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var background = self.addChild(new Container());
	self.attachAsset('roadSegment', {
		anchorX: 1,
		anchorY: 1
	});
	self.attachAsset('roadSegment', {
		anchorX: 1,
		anchorY: 1,
		scaleX: 0.95,
		scaleY: 0.95,
		tint: 0x555555
	});
	if (config.index % 3 === 0) {
		attachAssetRadial(self, 'square', {
			offset: 200,
			width: 100,
			height: 10,
			anchorR: 0.5,
			anchorX: 0.5,
			anchorY: 0.5
		});
	}
	;
	self.update = update;
	self.regenerate = regenerate;
	self.fadeout = false;
	self.distance = config.distance;
	self.step = config.step;
	self.stepChance = config.stepChance;
	self.previous = config.previous;
	;
	function attachAssetRadial(parent, asset, obj) {
		var offsetTotal = ROAD_SEGMENT_HEIGHT - (obj.offset || 10);
		var rotation = -(1 - obj.anchorR) * ROAD_SEGMENT_ANGLE;
		parent.attachAsset(asset, {
			x: Math.sin(rotation) * offsetTotal,
			y: Math.cos(rotation) * -offsetTotal,
			width: obj.width,
			height: obj.height,
			anchorX: obj.anchorX,
			anchorY: obj.anchorY,
			scaleX: obj.scaleX || 1,
			scaleY: obj.scaleY || 1,
			rotation: (obj.rotation || 0) + rotation
		});
	}
	function generateBackground() {
		console.log(self.distance);
		background.removeChildren();
		var plantCount = Math.floor(Math.random() * (ROAD_GEN_PLANT_MAX + 1));
		if (self.distance === SCORE_TOTAL_DISTANCE) {
			attachAssetRadial(background, 'flag', {
				anchorR: 0.5,
				anchorX: 0.1,
				anchorY: 1
			});
		} else {
			if (Math.random() < ROAD_GEN_TREE_CHANCE) {
				var treeIndex = Math.floor(Math.random() * ROAD_GEN_TREE_ASSETS);
				var scale = 0.9 + 0.2 * Math.random();
				attachAssetRadial(background, 'tree' + treeIndex, {
					anchorR: 0.4 + 0.2 * Math.random(),
					anchorX: 0.5,
					anchorY: 1,
					scaleX: scale,
					scaleY: scale
				});
			}
			for (var i = 0; i < plantCount; i++) {
				var plantIndex = Math.floor(Math.random() * ROAD_GEN_PLANT_ASSETS);
				var scale = 0.9 + 0.2 * Math.random();
				attachAssetRadial(background, 'plant' + plantIndex, {
					anchorR: 0.1 + 0.8 * Math.random(),
					anchorX: 0.5,
					anchorY: 1,
					scaleX: Math.random() < 0.5 ? -scale : scale,
					scaleY: scale
				});
			}
		}
		if (Math.random() < ROAD_GEN_CLOUD_CHANCE) {
			var cloudIndex = Math.floor(Math.random() * ROAD_GEN_CLOUD_ASSETS);
			var scale = 1.0 + 1.5 * Math.random();
			attachAssetRadial(background, 'cloud' + cloudIndex, {
				offset: -800,
				anchorR: -1.0 + 2.0 * Math.random(),
				anchorX: 0.5,
				anchorY: 0.5,
				scaleX: scale,
				scaleY: scale
			});
		}
	}
	function regenerate(incrementDistance) {
		if (incrementDistance) {
			self.distance += SCORE_SEGMENT_DISTANCE * ROAD_SEGMENT_COUNT;
		}
		var stepping = self.distance <= SCORE_TOTAL_DISTANCE;
		var stepChance = self.previous.stepChance;
		var stepUp = stepping && (self.previous.step < ROAD_STEP_COUNT ? Math.random() < self.stepChance : false);
		var stepDown = stepping && (self.previous.step > 0 ? Math.random() < self.stepChance : false);
		if (stepUp || stepDown) {
			if (stepUp && stepDown) {
				if (Math.random() < 0.5) {
					stepDown = false;
				} else {
					stepUp = false;
				}
			}
			self.step = self.previous.step + (stepUp ? 1 : -1);
			self.stepChance = ROAD_STEP_CHANCE_BASE;
		} else {
			self.step = self.previous.step;
			self.stepChance = self.previous.stepChance + ROAD_STEP_CHANCE_INCREMENT;
		}
		generateBackground();
		self.fadeout = false;
		rescale();
	}
	function update() {
		if (self.fadeout && self.alpha > 0) {
			if ((self.alpha -= ROAD_SEGMENT_FADEOUT) < 0) {
				self.alpha = 0;
			}
		} else if (!self.fadeout && self.alpha < 1) {
			if ((self.alpha += ROAD_SEGMENT_FADEOUT) > 1) {
				self.alpha = 1;
			}
		}
	}
	function rescale() {
		var newScale = (ROAD_STEP_HEIGHT_BASE + self.step * ROAD_STEP_HEIGHT_INCREMENT) / ROAD_SEGMENT_HEIGHT; // TEMP
		self.scale.set(newScale);
	}
	;
	rescale();
});
var Road = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var segments = [];
	for (var i = 0; i < ROAD_SEGMENT_COUNT; i++) {
		var segment = segments[i] = self.addChild(new RoadSegment({
			index: i,
			previous: i > 0 ? segments[i - 1] : undefined,
			rotation: i * ROAD_SEGMENT_ANGLE,
			distance: (i - 1) * SCORE_SEGMENT_DISTANCE,
			stepChance: ROAD_STEP_CHANCE_BASE,
			step: ROAD_STEP_DEFAULT
		}));
		if (i === ROAD_SEGMENT_COUNT - 1) {
			segments[0].previous = segment;
		}
		if (i >= ROAD_FREE_RUN_COUNT) {
			segment.regenerate();
		}
		self.attachAsset('roadSegment', {
			anchorX: 1,
			anchorY: 1,
			scaleX: 0.27,
			scaleY: 0.27,
			rotation: i * ROAD_SEGMENT_ANGLE
		});
	}
	self.attachAsset('circle', {
		width: 500,
		height: 500,
		anchorX: 0.5,
		anchorY: 0.5,
		tint: 0x222222
	});
	self.attachAsset('circle', {
		width: 490,
		height: 490,
		anchorX: 0.5,
		anchorY: 0.5,
		tint: 0x87CEEB
	});
	var destroyIndex = -1;
	var regenerateIndex = -1;
	;
	self.rotate = rotate;
	self.getIndex = getIndex;
	self.getNode = getNode;
	self.getNodeDist = getNodeDist;
	;
	function rotate(speed) {
		self.rotation -= speed;
		var newDestroyIndex = getIndex(ROAD_SEGMENT_DESTROY);
		var newRegenerateIndex = getIndex(20); // TEMP
		if (newDestroyIndex >= 0 && newDestroyIndex !== destroyIndex && !segments[newDestroyIndex].fadeout) {
			destroyIndex = newDestroyIndex;
			segments[destroyIndex].fadeout = true;
		}
		if (newRegenerateIndex >= 0 && newRegenerateIndex !== regenerateIndex && segments[newRegenerateIndex].fadeout) {
			regenerateIndex = newRegenerateIndex;
			segments[regenerateIndex].regenerate(true);
		}
	}
	function getIndex(shift) {
		return Math.floor(-self.rotation / MATH_2_PI * ROAD_SEGMENT_COUNT + (shift || 0) + 1) % ROAD_SEGMENT_COUNT;
	}
	function getNode(shift) {
		return segments[getIndex(shift)];
	}
	function getNodeDist() {
		return ROAD_SEGMENT_ANGLE - -self.rotation % ROAD_SEGMENT_ANGLE;
	}
});
var PlayerBody = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	// Animation settings
	var animation = animationStand;
	var alpha = 0;
	var blendAnimation = undefined;
	var blendDecrement = 0;
	var blendAlpha = 0;
	var afterTint = 0xAAAAAA;
	// Body settings
	var bodyOffsetY = config.y || 0;
	var pelvisOffsetX = -10;
	var armUpperAngle = -3 * MATH_EIGHTH_PI;
	var armUpperOffsetX = 15;
	var armUpperOffsetY = 30;
	var armLowerAngle = -MATH_QUARTER_PI;
	var armLowerOffsetY = 90;
	var legUpperAngle = 3 * MATH_EIGHTH_PI;
	var legUpperOffsetX = 10;
	var legLowerOffsetY = 135;
	var footOffsetY = 100;
	// Create and attach body parts
	var armUpperRight = self.addChild(new ConfigContainer({
		y: armUpperOffsetY
	}));
	var legUpperRight = self.addChild(new ConfigContainer({
		x: pelvisOffsetX + legUpperOffsetX
	}));
	var torso = self.attachAsset('torso', {
		anchorX: 0.5
	});
	var head = self.attachAsset('head', {
		anchorX: 0.15,
		anchorY: 0.95
	});
	var pelvis = self.attachAsset('pelvis', {
		x: pelvisOffsetX,
		y: torso.height,
		anchorX: 0.5,
		anchorY: 0.2,
		tint: afterTint
	});
	var legUpperLeft = self.addChild(new ConfigContainer({
		x: pelvisOffsetX - legUpperOffsetX
	}));
	var armUpperLeft = self.addChild(new ConfigContainer({
		y: armUpperOffsetY
	}));
	// Arm extensions
	armUpperRight.attachAsset('upperArm', {
		rotation: armUpperAngle,
		anchorX: 0.85,
		anchorY: 0.25,
		tint: afterTint
	});
	armUpperLeft.attachAsset('upperArm', {
		rotation: armUpperAngle,
		anchorX: 0.85,
		anchorY: 0.25
	});
	var armLowerRight = armUpperRight.attachAsset('lowerArm', {
		y: armLowerOffsetY,
		rotation: armLowerAngle,
		anchorX: 0.95,
		anchorY: 0.05,
		tint: afterTint
	});
	var armLowerLeft = armUpperLeft.attachAsset('lowerArm', {
		y: armLowerOffsetY,
		rotation: armLowerAngle,
		anchorX: 0.95,
		anchorY: 0.05
	});
	// Leg extensions
	legUpperRight.attachAsset('upperLeg', {
		rotation: legUpperAngle,
		anchorY: 0.1,
		tint: afterTint
	});
	legUpperLeft.attachAsset('upperLeg', {
		rotation: legUpperAngle,
		anchorY: 0.1
	});
	var legLowerRight = legUpperRight.attachAsset('lowerLeg', {
		y: legLowerOffsetY,
		anchorX: 0.65,
		tint: afterTint
	});
	var legLowerLeft = legUpperLeft.attachAsset('lowerLeg', {
		y: legLowerOffsetY,
		anchorX: 0.65
	});
	var footRight = legLowerRight.attachAsset('foot', {
		y: footOffsetY,
		anchorX: 0.2,
		anchorY: 0.05,
		tint: afterTint
	});
	var footLeft = legLowerLeft.attachAsset('foot', {
		y: footOffsetY,
		anchorX: 0.2,
		anchorY: 0.05
	});
	// Additional positioning
	armUpperLeft.x = -torso.width / 2 + armUpperOffsetX;
	armUpperRight.x = torso.width / 2 - armUpperOffsetX;
	legUpperLeft.y = torso.height + pelvis.height / 3;
	legUpperRight.y = torso.height + pelvis.height / 3;
	;
	self.animate = animate;
	self.pushAnimation = pushAnimation;
	;
	function animate(increment) {
		alpha = (alpha + increment) % 1;
		if (blendAlpha > 0) {
			if ((blendAlpha -= blendDecrement) <= 0) {
				blendAlpha = 0;
				blendAnimation = undefined;
			}
		}
		var pose = animation(alpha);
		var blendPose = blendAnimation && blendAnimation(alpha);
		self.y = animateProperty(pose, blendPose, blendAlpha, 'BODY_OFFSET', bodyOffsetY);
		head.rotation = animateProperty(pose, blendPose, blendAlpha, 'HEAD_ROTATION');
		armUpperRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_UPPER_RIGHT_ROTATION');
		armUpperLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_UPPER_LEFT_ROTATION');
		armLowerRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_LOWER_RIGHT_ROTATION', armLowerAngle);
		armLowerLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_LOWER_LEFT_ROTATION', armLowerAngle);
		legUpperRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_UPPER_RIGHT_ROTATION');
		legUpperLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_UPPER_LEFT_ROTATION');
		legLowerRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_LOWER_RIGHT_ROTATION');
		legLowerLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_LOWER_LEFT_ROTATION');
		footRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'FOOT_RIGHT_ROTATION');
		footLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'FOOT_LEFT_ROTATION');
	}
	function pushAnimation(newAnimation, newBlendDecrement) {
		blendDecrement = newBlendDecrement;
		if (newAnimation !== animation) {
			blendAnimation = animation;
			animation = newAnimation;
			blendAlpha = 1;
		}
	}
	;
	animate(0);
});
var Player = ConfigContainer.expand(function (config) {
	var self = ConfigContainer.call(this, config);
	var speedFactor = PLAYER_SPEED_FACTOR_BASE;
	var stopped = false;
	var falling = false;
	var jumping = false; // If the player is currently jumping (and immune to gravity)
	var jumpAction = false; // Tracks tap releases
	var jumpStep = 0; // Tracks min/max jump heights
	var aerialSpeed = 0; // Actual vertical/air speed
	var baseY = config.y;
	var step = ROAD_STEP_DEFAULT;
	var nodeDistance = 0;
	var nodeTicks = 0;
	var nodeMulti = 0;
	var body = self.addChild(new PlayerBody({
		y: -370
	}));
	;
	self.update = update;
	self.jumpStart = jumpStart;
	self.jumpEnd = jumpEnd;
	self.jumping = false;
	;
	function update() {
		var nextNode = road.getNode(1);
		var currentNode = nextNode.previous;
		// Update horizontal movement
		var outputSpeed = PLAYER_SPEED_BASE * speedFactor;
		var distance = road.getNodeDist();
		// Check for collision
		if (stopped && nextNode.step <= step) {
			stopped = false;
		} else if (nextNode.step > step && outputSpeed > distance) {
			road.rotate(distance - PLAYER_SPACING);
			speedFactor = PLAYER_SPEED_FACTOR_BASE;
			stopped = true;
			if (currentNode.step <= step) {
				body.pushAnimation(animationStand, PLAYER_POSE_TRANSITION);
			}
		}
		if (!stopped) {
			road.rotate(outputSpeed);
			if (!gameOver) {
				if (speedFactor < PLAYER_SPEED_FACTOR_MAX) {
					if ((speedFactor += PLAYER_SPEED_FACTOR_INCREMENT) >= PLAYER_SPEED_FACTOR_MAX) {
						speedFactor = PLAYER_SPEED_FACTOR_MAX;
					}
				}
			} else {
				if (speedFactor > 0) {
					if ((speedFactor -= PLAYER_SPEED_DECREMENT) <= 0) {
						speedFactor = 0;
					}
				}
			}
		}
		// TOOD: Get new node if applicable
		// Update vertical movement
		if (jumping) {
			jumpStep += aerialSpeed;
			if (!jumpAction && jumpStep >= PLAYER_JUMP_STEP_MIN || jumpStep >= PLAYER_JUMP_STEP_MAX) {
				falling = true;
				jumping = false;
				jumpAction = false;
				jumpStep = 0;
			}
		} else if (falling) {
			aerialSpeed -= PLAYER_GRAVITY;
		} else if (step > currentNode.step) {
			falling = true;
			body.pushAnimation(animationJump, PLAYER_POSE_TRANSITION);
		}
		step += aerialSpeed;
		if (falling && step <= currentNode.step) {
			step = currentNode.step;
			falling = false;
			aerialSpeed = 0;
			body.pushAnimation(stopped ? animationStand : animationRun, PLAYER_POSE_TRANSITION);
		}
		// Score calculations
		if (!gameOver && currentNode.distance > nodeDistance) {
			adjustScore(nodeMulti / nodeTicks);
			currentNode.distance = nodeDistance;
			nodeMulti = 0;
			nodeTicks = 0;
		}
		nodeMulti += stopped ? SCORE_STOPPED_FACTOR : speedFactor;
		nodeTicks++;
		// Adjustments
		var stepHeight = ROAD_STEP_HEIGHT_BASE + step * ROAD_STEP_HEIGHT_INCREMENT; // TODO: Better scaling
		self.y = baseY - stepHeight; // TODO: Better scaling
		self.scale.set(PLAYER_SCALE_BASE * stepHeight / ROAD_SEGMENT_HEIGHT); // TODO: Better scaling
		body.animate(PLAYER_ANIMATION_SPEED_BASE * speedFactor);
		multiplierText.setText((stopped ? SCORE_STOPPED_FACTOR : speedFactor).toFixed(1) + 'x');
	}
	function jumpStart() {
		if (!gameOver && !jumping && !falling) {
			jumping = true;
			jumpAction = true;
			aerialSpeed = PLAYER_JUMP_STEP_INCREMENT;
			body.pushAnimation(animationJump, PLAYER_POSE_TRANSITION);
		}
	}
	function jumpEnd() {
		jumpAction = false;
	}
	function adjustScore(averageMulti) {
		var points = Math.floor(averageMulti * SCORE_SEGMENT_POINTS);
		score = Math.max(0, score + points);
		scoreText.setText(String(score).padStart(6, '0'));
		distanceText.setText((distanceRemaining -= SCORE_SEGMENT_DISTANCE) + 'm');
		incrementText.setText((points === SCORE_MAX_POINTS ? '[MAX!] +' : '+') + points);
		if (distanceRemaining <= 0) {
			body.pushAnimation(animationStand, PLAYER_SPEED_DECREMENT / speedFactor);
			callGameOver();
		}
	}
	;
	body.pushAnimation(animationRun, PLAYER_POSE_TRANSITION);
});
/** 
* config {
* 	x        : Number || 0, // See: ConfigContainer
* 	y        : Number || 0, // See: ConfigContainer
* 	rotation : Number || 0, // See: ConfigContainer
* 	anchorX  : Number || 0,
* 	anchorY  : Number || 1,
* 	size     : Number || TEXT_DEFAULT_SIZE,
* 	weight   : Number || TEXT_DEFAULT_WEIGHT,
* 	font     : String || TEXT_DEFAULT_FONT,
* 	fill     : String || TEXT_DEFAULT_FILL,
* 	border   : String || TEXT_DEFAULT_BORDER,
* }
**/ 
var BorderedText = ConfigContainer.expand(function (text, config) {
	var self = ConfigContainer.call(this, config);
	config = config || {};
	;
	var anchorX = config.anchorX !== undefined ? config.anchorX : 0;
	var anchorY = config.anchorY !== undefined ? config.anchorY : 1;
	var size = config.size !== undefined ? config.size : TEXT_DEFAULT_SIZE;
	var weight = config.weight !== undefined ? config.weight : TEXT_DEFAULT_WEIGHT;
	var font = config.font !== undefined ? config.font : TEXT_DEFAULT_FONT;
	var textFill = config.fill !== undefined ? config.fill : TEXT_DEFAULT_FILL;
	var borderFill = config.border !== undefined ? config.border : TEXT_DEFAULT_BORDER;
	var textAssets = [];
	var mainAsset;
	;
	self.setText = setText;
	self.setFill = setFill;
	;
	function setText(newText) {
		for (var i = 0; i < textAssets.length; i++) {
			textAssets[i].setText(newText);
		}
	}
	function setFill(newFill) {
		textFill = newFill;
		mainAsset.fill = newFill;
	}
	function buildTextAssets(newText) {
		for (var i = 0; i < TEXT_OFFSETS.length; i++) {
			var main = i === TEXT_OFFSETS.length - 1;
			var fill = main ? textFill : borderFill;
			var textAsset = textAssets[i];
			if (textAsset) {
				textAsset.destroy();
			}
			textAsset = self.addChild(new Text2(newText, {
				fill: fill,
				font: font,
				size: size
			}));
			textAsset.anchor = {
				x: anchorX,
				y: anchorY
			}; // NOTE: Cannot be set in config
			textAsset.x = TEXT_OFFSETS[i][0] * weight; // NOTE: Cannot be set in config
			textAsset.y = TEXT_OFFSETS[i][1] * weight; // NOTE: Cannot be set in config
			textAssets[i] = textAsset;
		}
		mainAsset = textAssets[TEXT_OFFSETS.length - 1];
	}
	;
	buildTextAssets(text);
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x87CEEB // Sky blue color
});
/**** 
* Game Code
****/ 
;
//==============================================================================
// Global Constants & Settings
//==============================================================================
;
// Math Constants / Pre-calculations
var MATH_4_PI = Math.PI * 4;
var MATH_2_PI = Math.PI * 2;
var MATH_HALF_PI = Math.PI / 2;
var MATH_THIRD_PI = Math.PI / 3;
var MATH_QUARTER_PI = Math.PI / 4;
var MATH_FIFTH_PI = Math.PI / 5;
var MATH_SIXTH_PI = Math.PI / 5;
var MATH_EIGHTH_PI = Math.PI / 8;
var MATH_HALF_ROOT_3 = Math.sqrt(3) / 2; // Required by: TEXT_OFFSETS, BorderedText, BorderedSymbol, BorderedShape, SymbolText
;
// Text Settings
var TEXT_OFFSETS = [[0, 1], [MATH_HALF_ROOT_3, 0.5], [MATH_HALF_ROOT_3, -0.5], [0, -1], [-MATH_HALF_ROOT_3, -0.5], [-MATH_HALF_ROOT_3, 0.5], [0, 0]]; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_WEIGHT = 4; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_BORDER = '#000000'; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_FILL = '#FFFFFF'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_FONT = 'Courier'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_SIZE = 80; // Required by: BorderedText, SymbolText
;
// Game Constants
var GAME_TICKS = 60;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
;
// Road Constants
var ROAD_SEGMENT_DISTANCE = 1.0;
var ROAD_SEGMENT_POINTS = 100;
var ROAD_SEGMENT_COUNT = 30;
var ROAD_SEGMENT_ANGLE = MATH_2_PI / ROAD_SEGMENT_COUNT;
var ROAD_SEGMENT_HEIGHT = 1000;
var ROAD_SEGMENT_DESTROY = -2;
var ROAD_SEGMENT_FADEOUT = 0.015;
var ROAD_FREE_RUN_COUNT = 5;
var ROAD_STEP_COUNT = 5;
var ROAD_STEP_DEFAULT = 2;
var ROAD_STEP_HEIGHT_BASE = 400;
var ROAD_STEP_HEIGHT_INCREMENT = 80;
var ROAD_STEP_CHANCE_BASE = 0.1;
var ROAD_STEP_CHANCE_INCREMENT = 0.05;
var ROAD_GEN_PLANT_ASSETS = 3;
var ROAD_GEN_PLANT_MAX = 3;
var ROAD_GEN_TREE_ASSETS = 4;
var ROAD_GEN_TREE_CHANCE = 0.1;
var ROAD_GEN_CLOUD_ASSETS = 4;
var ROAD_GEN_CLOUD_CHANCE = 0.2;
;
// Player Constants
var PLAYER_SPACING = ROAD_SEGMENT_ANGLE / 8; // 1/8 of a segment
var PLAYER_POSE_TRANSITION = 0.1;
var PLAYER_GRAVITY = 0.02;
var PLAYER_JUMP_STEP_MIN = 1.0;
var PLAYER_JUMP_STEP_MAX = 2.0;
var PLAYER_JUMP_STEP_INCREMENT = 0.15;
var PLAYER_ANIMATION_SPEED_BASE = 0.02;
var PLAYER_SCALE_BASE = 0.5;
var PLAYER_SPEED_BASE = MATH_2_PI / (GAME_TICKS * 10); // 10s to complete a revolution
var PLAYER_SPEED_FACTOR_BASE = 1.0;
var PLAYER_SPEED_FACTOR_MAX = 1.5;
var PLAYER_SPEED_FACTOR_INCREMENT = 0.001;
var PLAYER_SPEED_DECREMENT = 0.01;
;
// Scoring Constants
var SCORE_TOTAL_DISTANCE = 500;
var SCORE_SEGMENT_DISTANCE = 1;
var SCORE_SEGMENT_POINTS = 100;
var SCORE_STOPPED_FACTOR = -0.2;
var SCORE_MAX_POINTS = SCORE_SEGMENT_POINTS * PLAYER_SPEED_FACTOR_MAX;
//==============================================================================
// Game Instances & Variables
//==============================================================================
;
// Variables
var score = 0;
var winningTime = 0;
var gameOver = false;
var distanceRemaining = SCORE_TOTAL_DISTANCE;
;
// Instances
var sun = game.addChild(new Sun({
	x: GAME_WIDTH / 2,
	y: 50
}));
var road = game.addChild(new Road({
	x: GAME_WIDTH / 2,
	y: GAME_HEIGHT - GAME_WIDTH / 2
}));
var player = game.addChild(new Player({
	x: road.x,
	y: road.y,
	scale: 0.5
}));
var distanceText = game.addChild(new BorderedText(distanceRemaining + 'm', {
	x: road.x,
	y: road.y - TEXT_DEFAULT_SIZE,
	anchorX: 0.5,
	anchorY: 1
}));
var scoreText = game.addChild(new BorderedText('000000', {
	x: road.x,
	y: road.y,
	size: TEXT_DEFAULT_SIZE * 1.5,
	anchorX: 0.5,
	anchorY: 0.5
}));
var multiplierText = game.addChild(new BorderedText('1.0x', {
	x: road.x,
	y: road.y + TEXT_DEFAULT_SIZE,
	anchorX: 0.5,
	anchorY: 0
}));
var incrementText = game.addChild(new BorderedText('', {
	x: road.x - 300,
	y: road.y,
	anchorX: 1,
	anchorY: 0.5
}));
var winningText = game.addChild(new BorderedText('', {
	x: GAME_WIDTH / 2,
	y: 500,
	anchorX: 0.5,
	anchorY: 0.5,
	size: 2 * TEXT_DEFAULT_SIZE
}));
;
//==============================================================================
// Global events
//==============================================================================
;
game.down = player.jumpStart;
game.up = player.jumpEnd;
;
//==============================================================================
// Global functions
//==============================================================================
;
// Helpers
function callGameOver() {
	gameOver = true;
	winningTime = LK.ticks;
	LK.setScore(score);
	winningText.setText('Congratulations!');
	// Check if winningTime is set and show game over screen after 60 ticks
	game.update = function () {
		if (winningTime && LK.ticks >= winningTime + 120) {
			LK.showGameOver();
		}
	};
}
;
// Animations & handlers
function animateProperty(animationPose, blendPose, blendAlpha, key, baseValue) {
	var animationPoseValue = (animationPose || {})[key] || 0;
	var blendPoseValue = (blendPose || {})[key] || 0;
	return (baseValue || 0) + animationPoseValue * (1 - blendAlpha) + blendPoseValue * blendAlpha;
}
function animationRun(alpha) {
	// Animation constants
	var armUpperAngle = MATH_FIFTH_PI;
	var armLowerAngle = -5 * MATH_EIGHTH_PI;
	var legLowerAngle = MATH_EIGHTH_PI;
	// Sinusoidals
	var rightSinusoidal = Math.cos(MATH_2_PI * (alpha + 0.5));
	var leftSinusoidal = Math.cos(MATH_2_PI * alpha);
	var doubleSinusoidal = Math.cos(MATH_4_PI * alpha);
	// Animation calculations
	var armUpperRightSwing = -rightSinusoidal * MATH_QUARTER_PI;
	var armUpperLeftSwing = -leftSinusoidal * MATH_QUARTER_PI;
	var legUpperRightSwing = rightSinusoidal * MATH_THIRD_PI;
	var legUpperLeftSwing = leftSinusoidal * MATH_THIRD_PI;
	return {
		BODY_OFFSET: doubleSinusoidal * 10,
		HEAD_ROTATION: (1 + doubleSinusoidal) * Math.PI / 32,
		ARM_UPPER_RIGHT_ROTATION: armUpperAngle + armUpperRightSwing,
		ARM_UPPER_LEFT_ROTATION: armUpperAngle + armUpperLeftSwing,
		ARM_LOWER_RIGHT_ROTATION: armLowerAngle + armUpperRightSwing / 2,
		ARM_LOWER_LEFT_ROTATION: armLowerAngle + armUpperLeftSwing / 2,
		LEG_UPPER_RIGHT_ROTATION: legUpperRightSwing,
		LEG_UPPER_LEFT_ROTATION: legUpperLeftSwing,
		LEG_LOWER_RIGHT_ROTATION: legLowerAngle + (alpha > 0.5 ? MATH_THIRD_PI + (1 - Math.abs(rightSinusoidal)) * MATH_SIXTH_PI : Math.abs(-legUpperRightSwing)),
		LEG_LOWER_LEFT_ROTATION: legLowerAngle + (alpha <= 0.5 ? MATH_THIRD_PI + (1 - Math.abs(leftSinusoidal)) * MATH_SIXTH_PI : Math.abs(-legUpperLeftSwing)),
		FOOT_RIGHT_ROTATION: Math.max(0, legUpperRightSwing * 2 / 3) - legLowerAngle,
		FOOT_LEFT_ROTATION: Math.max(0, legUpperLeftSwing * 2 / 3) - legLowerAngle
	};
}
function animationJump(alpha) {
	var sinusoidal = Math.cos(MATH_2_PI * alpha);
	return animationRun(0.35 + sinusoidal * 0.02);
}
function animationStand(alpha) {
	var sinusoidal = Math.cos(MATH_2_PI * alpha);
	var offsetAngle = Math.PI / 16;
	var sharedAngle = (2 + sinusoidal) * Math.PI / 64;
	return {
		BODY_OFFSET: sinusoidal * 5,
		ARM_UPPER_RIGHT_ROTATION: 0.5 * offsetAngle,
		ARM_UPPER_LEFT_ROTATION: 3 * offsetAngle,
		ARM_LOWER_RIGHT_ROTATION: -4 * offsetAngle,
		ARM_LOWER_LEFT_ROTATION: -4 * offsetAngle,
		LEG_UPPER_RIGHT_ROTATION: 1.5 * offsetAngle - sharedAngle,
		LEG_UPPER_LEFT_ROTATION: -1.5 * offsetAngle - sharedAngle,
		LEG_LOWER_RIGHT_ROTATION: 2 * sharedAngle,
		LEG_LOWER_LEFT_ROTATION: 2 * sharedAngle,
		FOOT_RIGHT_ROTATION: -sharedAngle - 1.5 * offsetAngle,
		FOOT_LEFT_ROTATION: -sharedAngle + 1.5 * offsetAngle
	};
}
 
 
 white
 
 
 
 
 white
 circle sliced into many pieces, flat image. 2d, white background, shadowless.
 
 
 
 pixel art of a tall, tree. game asset, 2d, white background, shadowless.
 
 Pixel art street lamp. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
 
 pixel art cloud. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
 pixel art sun. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.