/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Track number of times orbs have been generated
var Orb = Container.expand(function () {
var self = Container.call(this);
var orbGraphics = self.attachAsset('orb', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = (Math.random() - 0.5) * 16;
self.velocityY = (Math.random() - 0.5) * 16;
// Start random rotation animation
self.startRotation = function () {
var rotationDirection = Math.random() < 0.5 ? 1 : -1; // Random clockwise or counterclockwise
var rotationSpeed = (Math.random() * 2 + 1) * rotationDirection; // Random speed between 1-3, with direction
var currentRotation = orbGraphics.rotation;
var targetRotation = currentRotation + Math.PI * 2 * rotationSpeed; // Full rotation in chosen direction
tween(orbGraphics, {
rotation: targetRotation
}, {
duration: 2000 + Math.random() * 3000,
// Random duration between 2-5 seconds
easing: tween.linear,
onFinish: function onFinish() {
self.startRotation(); // Continue rotating with new random parameters
}
});
};
self.update = function () {
// Store previous position
var prevX = self.x;
var prevY = self.y;
self.x += self.velocityX;
self.y += self.velocityY;
// Check collision with terrain blocks
var orbRadius = 48; // Approximate orb radius
var collided = false;
for (var i = 0; i < terrainBlocks.length; i++) {
var block = terrainBlocks[i];
var blockHalfWidth = 92; // terrain block half width
var blockHalfHeight = 30.56; // terrain block half height
// Check if orb intersects with terrain block
var dx = Math.abs(self.x - block.x);
var dy = Math.abs(self.y - block.y);
if (dx < orbRadius + blockHalfWidth && dy < orbRadius + blockHalfHeight) {
// Collision detected - determine which side to bounce off
var overlapX = orbRadius + blockHalfWidth - dx;
var overlapY = orbRadius + blockHalfHeight - dy;
if (overlapX < overlapY) {
// Bounce horizontally
if (Math.random() < 0.1) {
// 10% chance to change direction randomly
self.velocityX = (Math.random() - 0.5) * 16;
} else {
// Normal bounce
self.velocityX *= -1;
}
// Push orb out of collision
if (self.x < block.x) {
self.x = block.x - blockHalfWidth - orbRadius;
} else {
self.x = block.x + blockHalfWidth + orbRadius;
}
} else {
// Bounce vertically
if (Math.random() < 0.1) {
// 10% chance to change direction randomly
self.velocityY = (Math.random() - 0.5) * 16;
} else {
// Normal bounce
self.velocityY *= -1;
}
// Push orb out of collision
if (self.y < block.y) {
self.y = block.y - blockHalfHeight - orbRadius;
} else {
self.y = block.y + blockHalfHeight + orbRadius;
}
}
collided = true;
break; // Only handle one collision per frame
}
}
// Bounce off screen boundaries as fallback (if no terrain collision)
if (!collided) {
if (self.x <= 40 || self.x >= 2008) {
if (Math.random() < 0.1) {
// 10% chance to change direction
self.velocityX *= -1;
} else {
// Always reverse velocity to prevent wall penetration
self.velocityX *= -1;
}
self.x = Math.max(40, Math.min(2008, self.x));
}
if (self.y <= 40 || self.y >= 2692) {
if (Math.random() < 0.1) {
// 10% chance to change direction
self.velocityY *= -1;
} else {
// Always reverse velocity to prevent wall penetration
self.velocityY *= -1;
}
self.y = Math.max(40, Math.min(2692, self.y));
}
}
};
// Start rotation animation when orb is created
self.startRotation();
return self;
});
var Stickman = Container.expand(function () {
var self = Container.call(this);
var stickmanGraphics = self.attachAsset('STICKMAN', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 12;
self.wall = 'bottom'; // current wall: 'top', 'right', 'bottom', 'left'
self.wallProgress = 0; // progress along current wall (0-1)
self.isJumping = false;
self.jumpStartWall = '';
self.jumpStartProgress = 0;
self.jumpProgress = 0;
self.jumpTargetWall = null; // Target wall for redirected jumps
self.update = function () {
if (self.isJumping) {
// Apply acceleration when approaching destination wall
var baseSpeed = 0.04;
var accelerationFactor = 1;
// Apply slow motion in the center of the jump (40%-60%)
if (self.jumpProgress >= 0.4 && self.jumpProgress <= 0.6) {
// Calculate how close we are to the center (0.4 to 0.6 maps to 1.0 to 0.0 to 1.0)
var centerDistance = Math.abs(0.5 - self.jumpProgress) / 0.1; // 0 at center, 1 at edges
var slowMotionFactor = 0.4875 + centerDistance * 0.5125; // 0.4875x speed at center (25% more slow motion), 1.0x at edges
accelerationFactor *= slowMotionFactor;
}
// Start accelerating when past 60% of the jump
if (self.jumpProgress > 0.6) {
// Calculate how close we are to the end (0.6 to 1.0 maps to 0.0 to 1.0)
var accelerationProgress = (self.jumpProgress - 0.6) / 0.4;
// Apply quadratic acceleration (starts slow, gets faster)
accelerationFactor = 1 + accelerationProgress * accelerationProgress * 1;
}
self.jumpProgress += baseSpeed * accelerationFactor;
var startPos;
if (self.jumpStartWall === 'current') {
// Use current position as start for redirected jumps
startPos = {
x: self.x,
y: self.y
};
} else {
startPos = self.getWallPosition(self.jumpStartWall, self.jumpStartProgress);
}
if (self.isParabolicJump) {
// Parabolic jump - elevate relative to current wall
var targetWall = self.jumpTargetWall || self.getOppositeWall(self.jumpStartWall);
var endPos = self.getWallPosition(targetWall, 0.5);
// Calculate base trajectory
var linearX = startPos.x + (endPos.x - startPos.x) * self.jumpProgress;
var linearY = startPos.y + (endPos.y - startPos.y) * self.jumpProgress;
// Add parabolic elevation with tween easing
var parabolaHeight = 400; // Maximum height of parabola
var elevationFactor = tween.easeInOut(self.jumpProgress);
var parabolicOffset = parabolaHeight * (1 - Math.pow(2 * self.jumpProgress - 1, 2));
// Apply elevation perpendicular to the wall direction
var wallDirection = self.getWallDirection(self.jumpStartWall);
self.x = linearX + wallDirection.perpX * parabolicOffset;
self.y = linearY + wallDirection.perpY * parabolicOffset;
} else {
// Regular jump - straight line
// Use jumpTargetWall if set, otherwise use opposite wall
var targetWall = self.jumpTargetWall || self.getOppositeWall(self.jumpStartWall);
// For direct jumps, target the perpendicular position on opposite wall
var targetProgress = self.jumpTargetWall ? self.jumpStartProgress : 0.5;
var endPos = self.getWallPosition(targetWall, targetProgress);
self.x = startPos.x + (endPos.x - startPos.x) * self.jumpProgress;
self.y = startPos.y + (endPos.y - startPos.y) * self.jumpProgress;
}
if (self.jumpProgress >= 1) {
self.isJumping = false;
var targetWall = self.jumpTargetWall || self.getOppositeWall(self.jumpStartWall);
self.wall = targetWall;
// For direct jumps, maintain perpendicular position; for others use center
self.wallProgress = self.jumpTargetWall ? self.jumpStartProgress : 0.5;
self.jumpProgress = 0;
self.jumpTargetWall = null; // Reset target wall
self.isParabolicJump = false; // Reset parabolic flag
// Create impact effect
createImpactEffect(self.x, self.y);
damageTerrain(self.x, self.y);
LK.getSound('impact').play();
}
} else {
self.wallProgress += self.speed / 1000;
if (self.wallProgress > 1) {
self.wallProgress = 0;
self.wall = self.getNextWall();
}
var pos = self.getWallPosition(self.wall, self.wallProgress);
self.x = pos.x;
self.y = pos.y;
// Check if stickman is on terrain (only when not jumping)
if (!self.isJumping) {
var onTerrain = false;
var stickmanRadius = 70; // Half of stickman width
// Check collision with all terrain blocks
for (var t = 0; t < terrainBlocks.length; t++) {
var block = terrainBlocks[t];
var blockHalfWidth = 92; // terrain block half width
var blockHalfHeight = 30.56; // terrain block half height
// Check if stickman overlaps with terrain block
var dx = Math.abs(self.x - block.x);
var dy = Math.abs(self.y - block.y);
if (dx < stickmanRadius + blockHalfWidth && dy < stickmanRadius + blockHalfHeight) {
onTerrain = true;
break;
}
}
// Track last terrain state
if (self.lastOnTerrain === undefined) self.lastOnTerrain = true;
// If we just stepped off terrain (transition from on terrain to off terrain)
if (self.lastOnTerrain && !onTerrain) {
// Deduct 1 point
var currentScore = LK.getScore();
var newScore = Math.max(0, currentScore - 1);
LK.setScore(newScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Check if score reached 0
if (newScore <= 0) {
LK.showGameOver();
}
}
// Update last terrain state
self.lastOnTerrain = onTerrain;
}
// Damage terrain as stickman runs
if (LK.ticks % 15 === 0) {
damageTerrain(self.x, self.y);
}
}
// Rotate STICKMAN so top points toward center
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var angleToCenter = Math.atan2(centerY - self.y, centerX - self.x);
// Add 90 degrees (Math.PI/2) so top of asset points toward center instead of side
stickmanGraphics.rotation = angleToCenter + Math.PI / 2;
// Randomly rotate sprite in any direction (but not in first third of wall and not while jumping)
if (Math.random() < 0.005 && self.wallProgress > 0.33 && !self.isJumping) {
// 0.5% chance per frame for less frequent but more varied rotations
var currentRotation = stickmanGraphics.rotation;
// Random rotation between -360 to 360 degrees (in radians)
var randomRotation = (Math.random() - 0.5) * 4 * Math.PI;
var targetRotation = currentRotation + randomRotation;
tween(stickmanGraphics, {
rotation: targetRotation
}, {
duration: 1000 + Math.random() * 1000,
// Random duration between 1-2 seconds
easing: tween.easeInOut
});
}
// Randomly flip sprite horizontally
if (Math.random() < 0.003) {
// 0.3% chance per frame for horizontal flipping
var currentScaleX = stickmanGraphics.scaleX;
var targetScaleX = currentScaleX * -1; // Flip horizontally
tween(stickmanGraphics, {
scaleX: targetScaleX
}, {
duration: 800 + Math.random() * 400,
// Random duration between 0.8-1.2 seconds
easing: tween.easeInOut,
onFinish: function onFinish() {
// After 2 seconds, return to normal horizontal orientation
LK.setTimeout(function () {
tween(stickmanGraphics, {
scaleX: 1
}, {
duration: 800,
easing: tween.easeInOut
});
}, 2000);
}
});
}
};
self.getWallPosition = function (wall, progress) {
// Stickman sprite half-width (140/2 = 70)
var stickmanHalfWidth = 70;
// Terrain block height is approximately 61.12, so half is about 30.56
var terrainHalfHeight = 30.56;
switch (wall) {
case 'top':
return {
x: progress * 2048,
y: stickmanHalfWidth + terrainHalfHeight // Position above terrain pixels
};
case 'right':
return {
x: 2048 - stickmanHalfWidth - terrainHalfHeight,
// Position above terrain pixels
y: 60 + progress * (2732 - 120)
};
case 'bottom':
return {
x: (1 - progress) * 2048,
y: 2732 - stickmanHalfWidth - terrainHalfHeight // Position above terrain pixels
};
case 'left':
return {
x: stickmanHalfWidth + terrainHalfHeight,
// Position above terrain pixels
y: 2672 - progress * (2732 - 120)
};
}
};
self.getNextWall = function () {
var walls = ['top', 'right', 'bottom', 'left'];
var currentIndex = walls.indexOf(self.wall);
return walls[(currentIndex - 1 + 4) % 4];
};
self.getOppositeWall = function (wall) {
switch (wall) {
case 'top':
return 'bottom';
case 'bottom':
return 'top';
case 'left':
return 'right';
case 'right':
return 'left';
}
};
self.jump = function () {
if (!self.isJumping) {
self.isJumping = true;
self.jumpStartWall = self.wall;
self.jumpStartProgress = self.wallProgress;
self.jumpProgress = 0;
self.jumpTargetWall = null; // Reset target wall for new jump
self.isParabolicJump = false; // Regular jump
// Decrease score by 1 point for each jump
var currentScore = LK.getScore();
var newScore = Math.max(0, currentScore - 1);
LK.setScore(newScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Check if score reached 0 after jump
if (newScore <= 0) {
LK.showGameOver();
}
LK.getSound('jump').play();
}
};
self.parabolicJump = function () {
if (!self.isJumping) {
self.isJumping = true;
self.jumpStartWall = self.wall;
self.jumpStartProgress = self.wallProgress;
self.jumpProgress = 0;
self.jumpTargetWall = null;
self.isParabolicJump = true; // Parabolic jump flag
// Decrease score by 1 point for parabolic jump
var currentScore = LK.getScore();
var newScore = Math.max(0, currentScore - 1);
LK.setScore(newScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Check if score reached 0 after jump
if (newScore <= 0) {
LK.showGameOver();
}
LK.getSound('jump').play();
}
};
self.directJump = function () {
if (!self.isJumping) {
self.isJumping = true;
self.jumpStartWall = self.wall;
self.jumpStartProgress = self.wallProgress;
self.jumpProgress = 0;
// Set target to opposite wall maintaining perpendicular position
self.jumpTargetWall = self.getOppositeWall(self.wall);
self.isParabolicJump = false; // Direct jump flag
// Decrease score by 1 point for direct jump
var currentScore = LK.getScore();
var newScore = Math.max(0, currentScore - 1);
LK.setScore(newScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Check if score reached 0 after jump
if (newScore <= 0) {
LK.showGameOver();
}
LK.getSound('jump').play();
}
};
self.getWallDirection = function (wall) {
// Returns perpendicular direction vectors for parabolic elevation
switch (wall) {
case 'top':
return {
perpX: 0,
perpY: 1
};
// Elevate downward from top wall
case 'bottom':
return {
perpX: 0,
perpY: -1
};
// Elevate upward from bottom wall
case 'left':
return {
perpX: 1,
perpY: 0
};
// Elevate rightward from left wall
case 'right':
return {
perpX: -1,
perpY: 0
};
// Elevate leftward from right wall
}
};
return self;
});
var TerrainBlock = Container.expand(function () {
var self = Container.call(this);
var terrainGraphics = self.attachAsset('terrain', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.maxHealth = 3;
self.damage = function () {
self.health--;
terrainGraphics.alpha = self.health / self.maxHealth;
if (self.health <= 0) {
self.destroy();
return true; // Block destroyed
}
return false;
};
self.repair = function () {
self.health = self.maxHealth;
terrainGraphics.alpha = 1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var orbGenerationCount = 0;
var stickman;
var orbs = [];
var terrainBlocks = [];
var orbsCollected = 0;
var gameTime = 0;
var destructionRate = 1;
var orbSpawnTimer = 10 * 60; // Start with 10 seconds
var orbSpawnActive = true; // Start active so timer is always running
var orbSpawnTxt = null;
// Create timer display
var timerTxt = new Text2('Time: 0', {
size: 60,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0.5);
timerTxt.y = -80; // Position above the score text
timerTxt.alpha = 0.4; // Reduce opacity to make it less prominent
LK.gui.center.addChild(timerTxt);
// Create score display
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0.5);
scoreTxt.alpha = 0.4; // Reduce opacity to make it less prominent
LK.gui.center.addChild(scoreTxt);
// Create orbs collected display (hidden)
var orbTxt = new Text2('Orbs: 0', {
size: 60,
fill: 0xFFD700
});
orbTxt.anchor.set(0.5, 0.5);
orbTxt.y = 100; // Position below the score text
orbTxt.alpha = 0; // Hide the orb counter completely
// Note: Not adding to LK.gui.center to keep it hidden
// Create orb spawn countdown display (always visible)
orbSpawnTxt = new Text2('Next Orb: 10', {
size: 70,
fill: 0xFF4500
});
orbSpawnTxt.anchor.set(0.5, 0.5);
orbSpawnTxt.y = 200; // Position below orb count
orbSpawnTxt.alpha = 0.4; // Always visible with reduced opacity
LK.gui.center.addChild(orbSpawnTxt);
// Initialize score to 1000 points
LK.setScore(1000);
scoreTxt.setText('Score: ' + LK.getScore());
// Initialize stickman
stickman = game.addChild(new Stickman());
var pos = stickman.getWallPosition('bottom', 0);
stickman.x = pos.x;
stickman.y = pos.y;
// Initialize orb
var firstOrb = game.addChild(new Orb());
firstOrb.x = 1024;
firstOrb.y = 1366;
orbs.push(firstOrb);
// Create initial terrain blocks around perimeter
function createInitialTerrain() {
// Top wall
for (var i = 0; i < 17; i++) {
var block = new TerrainBlock();
block.x = i * 120 + 60;
block.y = 20;
terrainBlocks.push(block);
game.addChild(block);
}
// Bottom wall
for (var i = 0; i < 17; i++) {
var block = new TerrainBlock();
block.x = i * 120 + 60;
block.y = 2712;
terrainBlocks.push(block);
game.addChild(block);
}
// Left wall
for (var i = 1; i < 22; i++) {
var block = new TerrainBlock();
block.x = 20;
block.y = i * 120 + 60;
block.rotation = Math.PI / 2; // Rotate 90 degrees toward center
terrainBlocks.push(block);
game.addChild(block);
}
// Right wall
for (var i = 1; i < 22; i++) {
var block = new TerrainBlock();
block.x = 2028;
block.y = i * 120 + 60;
block.rotation = -Math.PI / 2; // Rotate 90 degrees toward center
terrainBlocks.push(block);
game.addChild(block);
}
}
function createImpactEffect(x, y) {
var effect = LK.getAsset('impactEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
alpha: 0.8
});
game.addChild(effect);
tween(effect, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
effect.destroy();
}
});
}
function damageTerrain(x, y) {
var damageRadius = 150;
for (var i = terrainBlocks.length - 1; i >= 0; i--) {
var block = terrainBlocks[i];
var distance = Math.sqrt((block.x - x) * (block.x - x) + (block.y - y) * (block.y - y));
if (distance < damageRadius) {
if (block.damage()) {
terrainBlocks.splice(i, 1);
}
}
}
}
function repairTerrain() {
for (var i = 0; i < terrainBlocks.length; i++) {
terrainBlocks[i].repair();
}
}
function createNewOrb() {
if (orbs.length >= 3) return null; // Limit maximum orbs
var newOrb = game.addChild(new Orb());
newOrb.x = 200 + Math.random() * 1648;
newOrb.y = 200 + Math.random() * 2332;
newOrb.velocityX = (Math.random() - 0.5) * 16;
newOrb.velocityY = (Math.random() - 0.5) * 16;
orbs.push(newOrb);
return newOrb;
}
function createCenterOrb() {
var newOrb = game.addChild(new Orb());
newOrb.x = 1024; // Center X
newOrb.y = 1366; // Center Y
newOrb.velocityX = (Math.random() - 0.5) * 16;
newOrb.velocityY = (Math.random() - 0.5) * 16;
orbs.push(newOrb);
return newOrb;
}
function checkCollisions() {
// Check orb-to-orb collisions and repulsion
for (var i = 0; i < orbs.length; i++) {
var orb1 = orbs[i];
// Initialize collision tracking if not exists
if (!orb1.collidingWith) orb1.collidingWith = [];
if (!orb1.lastCollidingWith) orb1.lastCollidingWith = [];
for (var j = i + 1; j < orbs.length; j++) {
var orb2 = orbs[j];
// Initialize collision tracking if not exists
if (!orb2.collidingWith) orb2.collidingWith = [];
if (!orb2.lastCollidingWith) orb2.lastCollidingWith = [];
var dx = orb2.x - orb1.x;
var dy = orb2.y - orb1.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if orbs are colliding (assuming orb radius of about 48)
var isColliding = distance < 96;
var wasColliding = orb1.lastCollidingWith.indexOf(j) !== -1;
if (isColliding && !wasColliding) {
// Collision just started - apply repulsion only once
var repulsionForce = 8; // Strength of repulsion
var angle = Math.atan2(dy, dx);
// Apply repulsion to both orbs
var forceX = Math.cos(angle) * repulsionForce;
var forceY = Math.sin(angle) * repulsionForce;
// Push orbs apart
orb1.velocityX -= forceX;
orb1.velocityY -= forceY;
orb2.velocityX += forceX;
orb2.velocityY += forceY;
}
// Update collision tracking
if (isColliding) {
if (orb1.collidingWith.indexOf(j) === -1) orb1.collidingWith.push(j);
if (orb2.collidingWith.indexOf(i) === -1) orb2.collidingWith.push(i);
} else {
var index1 = orb1.collidingWith.indexOf(j);
if (index1 !== -1) orb1.collidingWith.splice(index1, 1);
var index2 = orb2.collidingWith.indexOf(i);
if (index2 !== -1) orb2.collidingWith.splice(index2, 1);
}
}
// Update last frame collision state for all orbs
orb1.lastCollidingWith = orb1.collidingWith.slice(); // Copy current state
}
// Check stickman-orb collisions
for (var i = orbs.length - 1; i >= 0; i--) {
var currentOrb = orbs[i];
var distance = Math.sqrt((stickman.x - currentOrb.x) * (stickman.x - currentOrb.x) + (stickman.y - currentOrb.y) * (stickman.y - currentOrb.y));
if (distance < 70) {
// Only allow collection and rewards if we haven't reached maximum collected orbs
if (orbsCollected < 3) {
orbsCollected++;
LK.setScore(LK.getScore() + 300);
scoreTxt.setText('Score: ' + LK.getScore());
// Repair terrain
repairTerrain();
// Regenerate 5 terrain blocks with wall center preference
regenerateTerrainBlocks(5);
// Remove collected orb
currentOrb.destroy();
orbs.splice(i, 1);
// Split into 2 new orbs (if under limit)
createNewOrb();
createNewOrb();
LK.getSound('collect').play();
// Create glow effect around stickman
tween(stickman, {
tint: 0xFFD700
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(stickman, {
tint: 0xFFFFFF
}, {
duration: 150,
easing: tween.easeIn
});
}
});
break; // Only collect one orb per frame
} else {
// At maximum orbs - make orb disappear and regenerate terrain
currentOrb.destroy();
orbs.splice(i, 1);
// Regenerate 5 terrain blocks with wall center preference
regenerateTerrainBlocks(5);
break; // Only process one orb per frame
}
}
}
}
function regenerateTerrainBlocks(count) {
// Define wall center positions with higher priority
var wallCenters = [{
x: 1024,
y: 20,
rotation: 0
},
// Top wall center
{
x: 1024,
y: 2712,
rotation: 0
},
// Bottom wall center
{
x: 20,
y: 1366,
rotation: Math.PI / 2
},
// Left wall center
{
x: 2028,
y: 1366,
rotation: -Math.PI / 2
} // Right wall center
];
// Define all possible terrain positions
var allPositions = [];
// Top wall positions
for (var i = 0; i < 17; i++) {
allPositions.push({
x: i * 120 + 60,
y: 20,
rotation: 0,
isCenter: i === 8
});
}
// Bottom wall positions
for (var i = 0; i < 17; i++) {
allPositions.push({
x: i * 120 + 60,
y: 2712,
rotation: 0,
isCenter: i === 8
});
}
// Left wall positions
for (var i = 1; i < 22; i++) {
allPositions.push({
x: 20,
y: i * 120 + 60,
rotation: Math.PI / 2,
isCenter: i === 11
});
}
// Right wall positions
for (var i = 1; i < 22; i++) {
allPositions.push({
x: 2028,
y: i * 120 + 60,
rotation: -Math.PI / 2,
isCenter: i === 11
});
}
// Filter out positions that already have terrain blocks
var availablePositions = [];
for (var p = 0; p < allPositions.length; p++) {
var pos = allPositions[p];
var hasBlock = false;
for (var t = 0; t < terrainBlocks.length; t++) {
var block = terrainBlocks[t];
var dx = Math.abs(block.x - pos.x);
var dy = Math.abs(block.y - pos.y);
if (dx < 30 && dy < 30) {
// Close enough to consider same position
hasBlock = true;
break;
}
}
if (!hasBlock) {
availablePositions.push(pos);
}
}
// Create blocks with preference for wall centers
var blocksCreated = 0;
while (blocksCreated < count && availablePositions.length > 0) {
var selectedIndex;
// 70% chance to prefer center positions if available
if (Math.random() < 0.7) {
var centerPositions = [];
for (var a = 0; a < availablePositions.length; a++) {
if (availablePositions[a].isCenter) {
centerPositions.push(a);
}
}
if (centerPositions.length > 0) {
selectedIndex = centerPositions[Math.floor(Math.random() * centerPositions.length)];
} else {
selectedIndex = Math.floor(Math.random() * availablePositions.length);
}
} else {
selectedIndex = Math.floor(Math.random() * availablePositions.length);
}
var selectedPos = availablePositions[selectedIndex];
var newBlock = new TerrainBlock();
newBlock.x = selectedPos.x;
newBlock.y = selectedPos.y;
newBlock.rotation = selectedPos.rotation;
terrainBlocks.push(newBlock);
game.addChild(newBlock);
// Remove used position
availablePositions.splice(selectedIndex, 1);
blocksCreated++;
}
}
function checkGameOver() {
if (terrainBlocks.length < 10) {
LK.showGameOver();
}
}
// Initialize terrain
createInitialTerrain();
// Play Flash music during the game
LK.playMusic('Flash');
// Context menu prevention is handled by LK engine automatically
// Game controls
game.down = function (x, y, obj) {
console.log('Game down event triggered', obj.event ? obj.event.button : 'no button info');
if (obj.event && obj.event.button !== undefined) {
// Handle mouse button clicks during jumping
if (stickman.isJumping) {
if (obj.event.button === 0) {
// Left click - redirect to bottom wall
stickman.jumpTargetWall = 'bottom';
// Update jump start position to current position for smooth redirection
stickman.jumpStartWall = 'current';
stickman.jumpStartProgress = 0;
stickman.jumpProgress = 0;
} else if (obj.event.button === 2) {
// Right click - redirect to top wall
stickman.jumpTargetWall = 'top';
// Update jump start position to current position for smooth redirection
stickman.jumpStartWall = 'current';
stickman.jumpStartProgress = 0;
stickman.jumpProgress = 0;
}
} else {
// Handle mouse button clicks when not jumping
// Check if no orbs are visible on screen
if (orbs.length === 0) {
// No orbs visible - trigger direct jump to opposite wall
console.log('No orbs visible - direct jump');
stickman.directJump();
} else {
// Orbs are visible - handle normal controls
if (obj.event.button === 0) {
// Left click - regular jump to opposite wall
console.log('Left click - regular jump');
stickman.jump();
} else if (obj.event.button === 2) {
// Right click - parabolic jump relative to current wall
console.log('Right click - parabolic jump');
stickman.parabolicJump();
}
}
}
} else {
// Fallback for touch devices
// Check if no orbs are visible on screen
if (orbs.length === 0) {
// No orbs visible - trigger direct jump to opposite wall
console.log('Touch fallback - no orbs visible - direct jump');
stickman.directJump();
} else {
// Orbs are visible - regular jump
console.log('Touch fallback - regular jump');
stickman.jump();
}
}
};
// Main game loop
game.update = function () {
gameTime++;
// Increase destruction rate over time
if (gameTime % 1800 === 0) {
// Every 30 seconds
destructionRate += 0.5;
}
// Random terrain destruction
if (Math.random() < 0.001 * destructionRate && terrainBlocks.length > 0) {
var randomIndex = Math.floor(Math.random() * terrainBlocks.length);
var block = terrainBlocks[randomIndex];
if (block.damage()) {
terrainBlocks.splice(randomIndex, 1);
}
}
// Update timer display only (no score increase over time)
if (gameTime % 60 === 0) {
// Every second
var seconds = Math.floor(gameTime / 60);
timerTxt.setText('Time: ' + seconds);
}
// Decrease score by 10 every 2 seconds (120 ticks)
if (gameTime % 120 === 0) {
var currentScore = LK.getScore();
var newScore = Math.max(0, currentScore - 10);
LK.setScore(newScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Check if score reached 0
if (newScore <= 0) {
LK.showGameOver();
}
}
// Only update orb spawn countdown if no orbs exist
if (orbs.length === 0) {
orbSpawnTimer--;
var secondsLeft = Math.ceil(orbSpawnTimer / 60);
orbSpawnTxt.setText('Next Orb: ' + secondsLeft);
// When timer reaches 0, spawn orb and reset
if (orbSpawnTimer <= 0) {
createCenterOrb();
orbSpawnTimer = 10 * 60; // Reset to 10 seconds
orbGenerationCount++; // Increment generation counter
// Change background to green on second generation
if (orbGenerationCount === 2) {
tween(game, {
backgroundColor: 0x00FF00
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
} else {
// Reset timer when orbs exist so it starts fresh when they're all gone
orbSpawnTimer = 10 * 60;
orbSpawnTxt.setText('Next Orb: 10');
}
checkCollisions();
checkGameOver();
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Track number of times orbs have been generated
var Orb = Container.expand(function () {
var self = Container.call(this);
var orbGraphics = self.attachAsset('orb', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = (Math.random() - 0.5) * 16;
self.velocityY = (Math.random() - 0.5) * 16;
// Start random rotation animation
self.startRotation = function () {
var rotationDirection = Math.random() < 0.5 ? 1 : -1; // Random clockwise or counterclockwise
var rotationSpeed = (Math.random() * 2 + 1) * rotationDirection; // Random speed between 1-3, with direction
var currentRotation = orbGraphics.rotation;
var targetRotation = currentRotation + Math.PI * 2 * rotationSpeed; // Full rotation in chosen direction
tween(orbGraphics, {
rotation: targetRotation
}, {
duration: 2000 + Math.random() * 3000,
// Random duration between 2-5 seconds
easing: tween.linear,
onFinish: function onFinish() {
self.startRotation(); // Continue rotating with new random parameters
}
});
};
self.update = function () {
// Store previous position
var prevX = self.x;
var prevY = self.y;
self.x += self.velocityX;
self.y += self.velocityY;
// Check collision with terrain blocks
var orbRadius = 48; // Approximate orb radius
var collided = false;
for (var i = 0; i < terrainBlocks.length; i++) {
var block = terrainBlocks[i];
var blockHalfWidth = 92; // terrain block half width
var blockHalfHeight = 30.56; // terrain block half height
// Check if orb intersects with terrain block
var dx = Math.abs(self.x - block.x);
var dy = Math.abs(self.y - block.y);
if (dx < orbRadius + blockHalfWidth && dy < orbRadius + blockHalfHeight) {
// Collision detected - determine which side to bounce off
var overlapX = orbRadius + blockHalfWidth - dx;
var overlapY = orbRadius + blockHalfHeight - dy;
if (overlapX < overlapY) {
// Bounce horizontally
if (Math.random() < 0.1) {
// 10% chance to change direction randomly
self.velocityX = (Math.random() - 0.5) * 16;
} else {
// Normal bounce
self.velocityX *= -1;
}
// Push orb out of collision
if (self.x < block.x) {
self.x = block.x - blockHalfWidth - orbRadius;
} else {
self.x = block.x + blockHalfWidth + orbRadius;
}
} else {
// Bounce vertically
if (Math.random() < 0.1) {
// 10% chance to change direction randomly
self.velocityY = (Math.random() - 0.5) * 16;
} else {
// Normal bounce
self.velocityY *= -1;
}
// Push orb out of collision
if (self.y < block.y) {
self.y = block.y - blockHalfHeight - orbRadius;
} else {
self.y = block.y + blockHalfHeight + orbRadius;
}
}
collided = true;
break; // Only handle one collision per frame
}
}
// Bounce off screen boundaries as fallback (if no terrain collision)
if (!collided) {
if (self.x <= 40 || self.x >= 2008) {
if (Math.random() < 0.1) {
// 10% chance to change direction
self.velocityX *= -1;
} else {
// Always reverse velocity to prevent wall penetration
self.velocityX *= -1;
}
self.x = Math.max(40, Math.min(2008, self.x));
}
if (self.y <= 40 || self.y >= 2692) {
if (Math.random() < 0.1) {
// 10% chance to change direction
self.velocityY *= -1;
} else {
// Always reverse velocity to prevent wall penetration
self.velocityY *= -1;
}
self.y = Math.max(40, Math.min(2692, self.y));
}
}
};
// Start rotation animation when orb is created
self.startRotation();
return self;
});
var Stickman = Container.expand(function () {
var self = Container.call(this);
var stickmanGraphics = self.attachAsset('STICKMAN', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 12;
self.wall = 'bottom'; // current wall: 'top', 'right', 'bottom', 'left'
self.wallProgress = 0; // progress along current wall (0-1)
self.isJumping = false;
self.jumpStartWall = '';
self.jumpStartProgress = 0;
self.jumpProgress = 0;
self.jumpTargetWall = null; // Target wall for redirected jumps
self.update = function () {
if (self.isJumping) {
// Apply acceleration when approaching destination wall
var baseSpeed = 0.04;
var accelerationFactor = 1;
// Apply slow motion in the center of the jump (40%-60%)
if (self.jumpProgress >= 0.4 && self.jumpProgress <= 0.6) {
// Calculate how close we are to the center (0.4 to 0.6 maps to 1.0 to 0.0 to 1.0)
var centerDistance = Math.abs(0.5 - self.jumpProgress) / 0.1; // 0 at center, 1 at edges
var slowMotionFactor = 0.4875 + centerDistance * 0.5125; // 0.4875x speed at center (25% more slow motion), 1.0x at edges
accelerationFactor *= slowMotionFactor;
}
// Start accelerating when past 60% of the jump
if (self.jumpProgress > 0.6) {
// Calculate how close we are to the end (0.6 to 1.0 maps to 0.0 to 1.0)
var accelerationProgress = (self.jumpProgress - 0.6) / 0.4;
// Apply quadratic acceleration (starts slow, gets faster)
accelerationFactor = 1 + accelerationProgress * accelerationProgress * 1;
}
self.jumpProgress += baseSpeed * accelerationFactor;
var startPos;
if (self.jumpStartWall === 'current') {
// Use current position as start for redirected jumps
startPos = {
x: self.x,
y: self.y
};
} else {
startPos = self.getWallPosition(self.jumpStartWall, self.jumpStartProgress);
}
if (self.isParabolicJump) {
// Parabolic jump - elevate relative to current wall
var targetWall = self.jumpTargetWall || self.getOppositeWall(self.jumpStartWall);
var endPos = self.getWallPosition(targetWall, 0.5);
// Calculate base trajectory
var linearX = startPos.x + (endPos.x - startPos.x) * self.jumpProgress;
var linearY = startPos.y + (endPos.y - startPos.y) * self.jumpProgress;
// Add parabolic elevation with tween easing
var parabolaHeight = 400; // Maximum height of parabola
var elevationFactor = tween.easeInOut(self.jumpProgress);
var parabolicOffset = parabolaHeight * (1 - Math.pow(2 * self.jumpProgress - 1, 2));
// Apply elevation perpendicular to the wall direction
var wallDirection = self.getWallDirection(self.jumpStartWall);
self.x = linearX + wallDirection.perpX * parabolicOffset;
self.y = linearY + wallDirection.perpY * parabolicOffset;
} else {
// Regular jump - straight line
// Use jumpTargetWall if set, otherwise use opposite wall
var targetWall = self.jumpTargetWall || self.getOppositeWall(self.jumpStartWall);
// For direct jumps, target the perpendicular position on opposite wall
var targetProgress = self.jumpTargetWall ? self.jumpStartProgress : 0.5;
var endPos = self.getWallPosition(targetWall, targetProgress);
self.x = startPos.x + (endPos.x - startPos.x) * self.jumpProgress;
self.y = startPos.y + (endPos.y - startPos.y) * self.jumpProgress;
}
if (self.jumpProgress >= 1) {
self.isJumping = false;
var targetWall = self.jumpTargetWall || self.getOppositeWall(self.jumpStartWall);
self.wall = targetWall;
// For direct jumps, maintain perpendicular position; for others use center
self.wallProgress = self.jumpTargetWall ? self.jumpStartProgress : 0.5;
self.jumpProgress = 0;
self.jumpTargetWall = null; // Reset target wall
self.isParabolicJump = false; // Reset parabolic flag
// Create impact effect
createImpactEffect(self.x, self.y);
damageTerrain(self.x, self.y);
LK.getSound('impact').play();
}
} else {
self.wallProgress += self.speed / 1000;
if (self.wallProgress > 1) {
self.wallProgress = 0;
self.wall = self.getNextWall();
}
var pos = self.getWallPosition(self.wall, self.wallProgress);
self.x = pos.x;
self.y = pos.y;
// Check if stickman is on terrain (only when not jumping)
if (!self.isJumping) {
var onTerrain = false;
var stickmanRadius = 70; // Half of stickman width
// Check collision with all terrain blocks
for (var t = 0; t < terrainBlocks.length; t++) {
var block = terrainBlocks[t];
var blockHalfWidth = 92; // terrain block half width
var blockHalfHeight = 30.56; // terrain block half height
// Check if stickman overlaps with terrain block
var dx = Math.abs(self.x - block.x);
var dy = Math.abs(self.y - block.y);
if (dx < stickmanRadius + blockHalfWidth && dy < stickmanRadius + blockHalfHeight) {
onTerrain = true;
break;
}
}
// Track last terrain state
if (self.lastOnTerrain === undefined) self.lastOnTerrain = true;
// If we just stepped off terrain (transition from on terrain to off terrain)
if (self.lastOnTerrain && !onTerrain) {
// Deduct 1 point
var currentScore = LK.getScore();
var newScore = Math.max(0, currentScore - 1);
LK.setScore(newScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Check if score reached 0
if (newScore <= 0) {
LK.showGameOver();
}
}
// Update last terrain state
self.lastOnTerrain = onTerrain;
}
// Damage terrain as stickman runs
if (LK.ticks % 15 === 0) {
damageTerrain(self.x, self.y);
}
}
// Rotate STICKMAN so top points toward center
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var angleToCenter = Math.atan2(centerY - self.y, centerX - self.x);
// Add 90 degrees (Math.PI/2) so top of asset points toward center instead of side
stickmanGraphics.rotation = angleToCenter + Math.PI / 2;
// Randomly rotate sprite in any direction (but not in first third of wall and not while jumping)
if (Math.random() < 0.005 && self.wallProgress > 0.33 && !self.isJumping) {
// 0.5% chance per frame for less frequent but more varied rotations
var currentRotation = stickmanGraphics.rotation;
// Random rotation between -360 to 360 degrees (in radians)
var randomRotation = (Math.random() - 0.5) * 4 * Math.PI;
var targetRotation = currentRotation + randomRotation;
tween(stickmanGraphics, {
rotation: targetRotation
}, {
duration: 1000 + Math.random() * 1000,
// Random duration between 1-2 seconds
easing: tween.easeInOut
});
}
// Randomly flip sprite horizontally
if (Math.random() < 0.003) {
// 0.3% chance per frame for horizontal flipping
var currentScaleX = stickmanGraphics.scaleX;
var targetScaleX = currentScaleX * -1; // Flip horizontally
tween(stickmanGraphics, {
scaleX: targetScaleX
}, {
duration: 800 + Math.random() * 400,
// Random duration between 0.8-1.2 seconds
easing: tween.easeInOut,
onFinish: function onFinish() {
// After 2 seconds, return to normal horizontal orientation
LK.setTimeout(function () {
tween(stickmanGraphics, {
scaleX: 1
}, {
duration: 800,
easing: tween.easeInOut
});
}, 2000);
}
});
}
};
self.getWallPosition = function (wall, progress) {
// Stickman sprite half-width (140/2 = 70)
var stickmanHalfWidth = 70;
// Terrain block height is approximately 61.12, so half is about 30.56
var terrainHalfHeight = 30.56;
switch (wall) {
case 'top':
return {
x: progress * 2048,
y: stickmanHalfWidth + terrainHalfHeight // Position above terrain pixels
};
case 'right':
return {
x: 2048 - stickmanHalfWidth - terrainHalfHeight,
// Position above terrain pixels
y: 60 + progress * (2732 - 120)
};
case 'bottom':
return {
x: (1 - progress) * 2048,
y: 2732 - stickmanHalfWidth - terrainHalfHeight // Position above terrain pixels
};
case 'left':
return {
x: stickmanHalfWidth + terrainHalfHeight,
// Position above terrain pixels
y: 2672 - progress * (2732 - 120)
};
}
};
self.getNextWall = function () {
var walls = ['top', 'right', 'bottom', 'left'];
var currentIndex = walls.indexOf(self.wall);
return walls[(currentIndex - 1 + 4) % 4];
};
self.getOppositeWall = function (wall) {
switch (wall) {
case 'top':
return 'bottom';
case 'bottom':
return 'top';
case 'left':
return 'right';
case 'right':
return 'left';
}
};
self.jump = function () {
if (!self.isJumping) {
self.isJumping = true;
self.jumpStartWall = self.wall;
self.jumpStartProgress = self.wallProgress;
self.jumpProgress = 0;
self.jumpTargetWall = null; // Reset target wall for new jump
self.isParabolicJump = false; // Regular jump
// Decrease score by 1 point for each jump
var currentScore = LK.getScore();
var newScore = Math.max(0, currentScore - 1);
LK.setScore(newScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Check if score reached 0 after jump
if (newScore <= 0) {
LK.showGameOver();
}
LK.getSound('jump').play();
}
};
self.parabolicJump = function () {
if (!self.isJumping) {
self.isJumping = true;
self.jumpStartWall = self.wall;
self.jumpStartProgress = self.wallProgress;
self.jumpProgress = 0;
self.jumpTargetWall = null;
self.isParabolicJump = true; // Parabolic jump flag
// Decrease score by 1 point for parabolic jump
var currentScore = LK.getScore();
var newScore = Math.max(0, currentScore - 1);
LK.setScore(newScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Check if score reached 0 after jump
if (newScore <= 0) {
LK.showGameOver();
}
LK.getSound('jump').play();
}
};
self.directJump = function () {
if (!self.isJumping) {
self.isJumping = true;
self.jumpStartWall = self.wall;
self.jumpStartProgress = self.wallProgress;
self.jumpProgress = 0;
// Set target to opposite wall maintaining perpendicular position
self.jumpTargetWall = self.getOppositeWall(self.wall);
self.isParabolicJump = false; // Direct jump flag
// Decrease score by 1 point for direct jump
var currentScore = LK.getScore();
var newScore = Math.max(0, currentScore - 1);
LK.setScore(newScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Check if score reached 0 after jump
if (newScore <= 0) {
LK.showGameOver();
}
LK.getSound('jump').play();
}
};
self.getWallDirection = function (wall) {
// Returns perpendicular direction vectors for parabolic elevation
switch (wall) {
case 'top':
return {
perpX: 0,
perpY: 1
};
// Elevate downward from top wall
case 'bottom':
return {
perpX: 0,
perpY: -1
};
// Elevate upward from bottom wall
case 'left':
return {
perpX: 1,
perpY: 0
};
// Elevate rightward from left wall
case 'right':
return {
perpX: -1,
perpY: 0
};
// Elevate leftward from right wall
}
};
return self;
});
var TerrainBlock = Container.expand(function () {
var self = Container.call(this);
var terrainGraphics = self.attachAsset('terrain', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.maxHealth = 3;
self.damage = function () {
self.health--;
terrainGraphics.alpha = self.health / self.maxHealth;
if (self.health <= 0) {
self.destroy();
return true; // Block destroyed
}
return false;
};
self.repair = function () {
self.health = self.maxHealth;
terrainGraphics.alpha = 1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var orbGenerationCount = 0;
var stickman;
var orbs = [];
var terrainBlocks = [];
var orbsCollected = 0;
var gameTime = 0;
var destructionRate = 1;
var orbSpawnTimer = 10 * 60; // Start with 10 seconds
var orbSpawnActive = true; // Start active so timer is always running
var orbSpawnTxt = null;
// Create timer display
var timerTxt = new Text2('Time: 0', {
size: 60,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0.5, 0.5);
timerTxt.y = -80; // Position above the score text
timerTxt.alpha = 0.4; // Reduce opacity to make it less prominent
LK.gui.center.addChild(timerTxt);
// Create score display
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0.5);
scoreTxt.alpha = 0.4; // Reduce opacity to make it less prominent
LK.gui.center.addChild(scoreTxt);
// Create orbs collected display (hidden)
var orbTxt = new Text2('Orbs: 0', {
size: 60,
fill: 0xFFD700
});
orbTxt.anchor.set(0.5, 0.5);
orbTxt.y = 100; // Position below the score text
orbTxt.alpha = 0; // Hide the orb counter completely
// Note: Not adding to LK.gui.center to keep it hidden
// Create orb spawn countdown display (always visible)
orbSpawnTxt = new Text2('Next Orb: 10', {
size: 70,
fill: 0xFF4500
});
orbSpawnTxt.anchor.set(0.5, 0.5);
orbSpawnTxt.y = 200; // Position below orb count
orbSpawnTxt.alpha = 0.4; // Always visible with reduced opacity
LK.gui.center.addChild(orbSpawnTxt);
// Initialize score to 1000 points
LK.setScore(1000);
scoreTxt.setText('Score: ' + LK.getScore());
// Initialize stickman
stickman = game.addChild(new Stickman());
var pos = stickman.getWallPosition('bottom', 0);
stickman.x = pos.x;
stickman.y = pos.y;
// Initialize orb
var firstOrb = game.addChild(new Orb());
firstOrb.x = 1024;
firstOrb.y = 1366;
orbs.push(firstOrb);
// Create initial terrain blocks around perimeter
function createInitialTerrain() {
// Top wall
for (var i = 0; i < 17; i++) {
var block = new TerrainBlock();
block.x = i * 120 + 60;
block.y = 20;
terrainBlocks.push(block);
game.addChild(block);
}
// Bottom wall
for (var i = 0; i < 17; i++) {
var block = new TerrainBlock();
block.x = i * 120 + 60;
block.y = 2712;
terrainBlocks.push(block);
game.addChild(block);
}
// Left wall
for (var i = 1; i < 22; i++) {
var block = new TerrainBlock();
block.x = 20;
block.y = i * 120 + 60;
block.rotation = Math.PI / 2; // Rotate 90 degrees toward center
terrainBlocks.push(block);
game.addChild(block);
}
// Right wall
for (var i = 1; i < 22; i++) {
var block = new TerrainBlock();
block.x = 2028;
block.y = i * 120 + 60;
block.rotation = -Math.PI / 2; // Rotate 90 degrees toward center
terrainBlocks.push(block);
game.addChild(block);
}
}
function createImpactEffect(x, y) {
var effect = LK.getAsset('impactEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
alpha: 0.8
});
game.addChild(effect);
tween(effect, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
effect.destroy();
}
});
}
function damageTerrain(x, y) {
var damageRadius = 150;
for (var i = terrainBlocks.length - 1; i >= 0; i--) {
var block = terrainBlocks[i];
var distance = Math.sqrt((block.x - x) * (block.x - x) + (block.y - y) * (block.y - y));
if (distance < damageRadius) {
if (block.damage()) {
terrainBlocks.splice(i, 1);
}
}
}
}
function repairTerrain() {
for (var i = 0; i < terrainBlocks.length; i++) {
terrainBlocks[i].repair();
}
}
function createNewOrb() {
if (orbs.length >= 3) return null; // Limit maximum orbs
var newOrb = game.addChild(new Orb());
newOrb.x = 200 + Math.random() * 1648;
newOrb.y = 200 + Math.random() * 2332;
newOrb.velocityX = (Math.random() - 0.5) * 16;
newOrb.velocityY = (Math.random() - 0.5) * 16;
orbs.push(newOrb);
return newOrb;
}
function createCenterOrb() {
var newOrb = game.addChild(new Orb());
newOrb.x = 1024; // Center X
newOrb.y = 1366; // Center Y
newOrb.velocityX = (Math.random() - 0.5) * 16;
newOrb.velocityY = (Math.random() - 0.5) * 16;
orbs.push(newOrb);
return newOrb;
}
function checkCollisions() {
// Check orb-to-orb collisions and repulsion
for (var i = 0; i < orbs.length; i++) {
var orb1 = orbs[i];
// Initialize collision tracking if not exists
if (!orb1.collidingWith) orb1.collidingWith = [];
if (!orb1.lastCollidingWith) orb1.lastCollidingWith = [];
for (var j = i + 1; j < orbs.length; j++) {
var orb2 = orbs[j];
// Initialize collision tracking if not exists
if (!orb2.collidingWith) orb2.collidingWith = [];
if (!orb2.lastCollidingWith) orb2.lastCollidingWith = [];
var dx = orb2.x - orb1.x;
var dy = orb2.y - orb1.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if orbs are colliding (assuming orb radius of about 48)
var isColliding = distance < 96;
var wasColliding = orb1.lastCollidingWith.indexOf(j) !== -1;
if (isColliding && !wasColliding) {
// Collision just started - apply repulsion only once
var repulsionForce = 8; // Strength of repulsion
var angle = Math.atan2(dy, dx);
// Apply repulsion to both orbs
var forceX = Math.cos(angle) * repulsionForce;
var forceY = Math.sin(angle) * repulsionForce;
// Push orbs apart
orb1.velocityX -= forceX;
orb1.velocityY -= forceY;
orb2.velocityX += forceX;
orb2.velocityY += forceY;
}
// Update collision tracking
if (isColliding) {
if (orb1.collidingWith.indexOf(j) === -1) orb1.collidingWith.push(j);
if (orb2.collidingWith.indexOf(i) === -1) orb2.collidingWith.push(i);
} else {
var index1 = orb1.collidingWith.indexOf(j);
if (index1 !== -1) orb1.collidingWith.splice(index1, 1);
var index2 = orb2.collidingWith.indexOf(i);
if (index2 !== -1) orb2.collidingWith.splice(index2, 1);
}
}
// Update last frame collision state for all orbs
orb1.lastCollidingWith = orb1.collidingWith.slice(); // Copy current state
}
// Check stickman-orb collisions
for (var i = orbs.length - 1; i >= 0; i--) {
var currentOrb = orbs[i];
var distance = Math.sqrt((stickman.x - currentOrb.x) * (stickman.x - currentOrb.x) + (stickman.y - currentOrb.y) * (stickman.y - currentOrb.y));
if (distance < 70) {
// Only allow collection and rewards if we haven't reached maximum collected orbs
if (orbsCollected < 3) {
orbsCollected++;
LK.setScore(LK.getScore() + 300);
scoreTxt.setText('Score: ' + LK.getScore());
// Repair terrain
repairTerrain();
// Regenerate 5 terrain blocks with wall center preference
regenerateTerrainBlocks(5);
// Remove collected orb
currentOrb.destroy();
orbs.splice(i, 1);
// Split into 2 new orbs (if under limit)
createNewOrb();
createNewOrb();
LK.getSound('collect').play();
// Create glow effect around stickman
tween(stickman, {
tint: 0xFFD700
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(stickman, {
tint: 0xFFFFFF
}, {
duration: 150,
easing: tween.easeIn
});
}
});
break; // Only collect one orb per frame
} else {
// At maximum orbs - make orb disappear and regenerate terrain
currentOrb.destroy();
orbs.splice(i, 1);
// Regenerate 5 terrain blocks with wall center preference
regenerateTerrainBlocks(5);
break; // Only process one orb per frame
}
}
}
}
function regenerateTerrainBlocks(count) {
// Define wall center positions with higher priority
var wallCenters = [{
x: 1024,
y: 20,
rotation: 0
},
// Top wall center
{
x: 1024,
y: 2712,
rotation: 0
},
// Bottom wall center
{
x: 20,
y: 1366,
rotation: Math.PI / 2
},
// Left wall center
{
x: 2028,
y: 1366,
rotation: -Math.PI / 2
} // Right wall center
];
// Define all possible terrain positions
var allPositions = [];
// Top wall positions
for (var i = 0; i < 17; i++) {
allPositions.push({
x: i * 120 + 60,
y: 20,
rotation: 0,
isCenter: i === 8
});
}
// Bottom wall positions
for (var i = 0; i < 17; i++) {
allPositions.push({
x: i * 120 + 60,
y: 2712,
rotation: 0,
isCenter: i === 8
});
}
// Left wall positions
for (var i = 1; i < 22; i++) {
allPositions.push({
x: 20,
y: i * 120 + 60,
rotation: Math.PI / 2,
isCenter: i === 11
});
}
// Right wall positions
for (var i = 1; i < 22; i++) {
allPositions.push({
x: 2028,
y: i * 120 + 60,
rotation: -Math.PI / 2,
isCenter: i === 11
});
}
// Filter out positions that already have terrain blocks
var availablePositions = [];
for (var p = 0; p < allPositions.length; p++) {
var pos = allPositions[p];
var hasBlock = false;
for (var t = 0; t < terrainBlocks.length; t++) {
var block = terrainBlocks[t];
var dx = Math.abs(block.x - pos.x);
var dy = Math.abs(block.y - pos.y);
if (dx < 30 && dy < 30) {
// Close enough to consider same position
hasBlock = true;
break;
}
}
if (!hasBlock) {
availablePositions.push(pos);
}
}
// Create blocks with preference for wall centers
var blocksCreated = 0;
while (blocksCreated < count && availablePositions.length > 0) {
var selectedIndex;
// 70% chance to prefer center positions if available
if (Math.random() < 0.7) {
var centerPositions = [];
for (var a = 0; a < availablePositions.length; a++) {
if (availablePositions[a].isCenter) {
centerPositions.push(a);
}
}
if (centerPositions.length > 0) {
selectedIndex = centerPositions[Math.floor(Math.random() * centerPositions.length)];
} else {
selectedIndex = Math.floor(Math.random() * availablePositions.length);
}
} else {
selectedIndex = Math.floor(Math.random() * availablePositions.length);
}
var selectedPos = availablePositions[selectedIndex];
var newBlock = new TerrainBlock();
newBlock.x = selectedPos.x;
newBlock.y = selectedPos.y;
newBlock.rotation = selectedPos.rotation;
terrainBlocks.push(newBlock);
game.addChild(newBlock);
// Remove used position
availablePositions.splice(selectedIndex, 1);
blocksCreated++;
}
}
function checkGameOver() {
if (terrainBlocks.length < 10) {
LK.showGameOver();
}
}
// Initialize terrain
createInitialTerrain();
// Play Flash music during the game
LK.playMusic('Flash');
// Context menu prevention is handled by LK engine automatically
// Game controls
game.down = function (x, y, obj) {
console.log('Game down event triggered', obj.event ? obj.event.button : 'no button info');
if (obj.event && obj.event.button !== undefined) {
// Handle mouse button clicks during jumping
if (stickman.isJumping) {
if (obj.event.button === 0) {
// Left click - redirect to bottom wall
stickman.jumpTargetWall = 'bottom';
// Update jump start position to current position for smooth redirection
stickman.jumpStartWall = 'current';
stickman.jumpStartProgress = 0;
stickman.jumpProgress = 0;
} else if (obj.event.button === 2) {
// Right click - redirect to top wall
stickman.jumpTargetWall = 'top';
// Update jump start position to current position for smooth redirection
stickman.jumpStartWall = 'current';
stickman.jumpStartProgress = 0;
stickman.jumpProgress = 0;
}
} else {
// Handle mouse button clicks when not jumping
// Check if no orbs are visible on screen
if (orbs.length === 0) {
// No orbs visible - trigger direct jump to opposite wall
console.log('No orbs visible - direct jump');
stickman.directJump();
} else {
// Orbs are visible - handle normal controls
if (obj.event.button === 0) {
// Left click - regular jump to opposite wall
console.log('Left click - regular jump');
stickman.jump();
} else if (obj.event.button === 2) {
// Right click - parabolic jump relative to current wall
console.log('Right click - parabolic jump');
stickman.parabolicJump();
}
}
}
} else {
// Fallback for touch devices
// Check if no orbs are visible on screen
if (orbs.length === 0) {
// No orbs visible - trigger direct jump to opposite wall
console.log('Touch fallback - no orbs visible - direct jump');
stickman.directJump();
} else {
// Orbs are visible - regular jump
console.log('Touch fallback - regular jump');
stickman.jump();
}
}
};
// Main game loop
game.update = function () {
gameTime++;
// Increase destruction rate over time
if (gameTime % 1800 === 0) {
// Every 30 seconds
destructionRate += 0.5;
}
// Random terrain destruction
if (Math.random() < 0.001 * destructionRate && terrainBlocks.length > 0) {
var randomIndex = Math.floor(Math.random() * terrainBlocks.length);
var block = terrainBlocks[randomIndex];
if (block.damage()) {
terrainBlocks.splice(randomIndex, 1);
}
}
// Update timer display only (no score increase over time)
if (gameTime % 60 === 0) {
// Every second
var seconds = Math.floor(gameTime / 60);
timerTxt.setText('Time: ' + seconds);
}
// Decrease score by 10 every 2 seconds (120 ticks)
if (gameTime % 120 === 0) {
var currentScore = LK.getScore();
var newScore = Math.max(0, currentScore - 10);
LK.setScore(newScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Check if score reached 0
if (newScore <= 0) {
LK.showGameOver();
}
}
// Only update orb spawn countdown if no orbs exist
if (orbs.length === 0) {
orbSpawnTimer--;
var secondsLeft = Math.ceil(orbSpawnTimer / 60);
orbSpawnTxt.setText('Next Orb: ' + secondsLeft);
// When timer reaches 0, spawn orb and reset
if (orbSpawnTimer <= 0) {
createCenterOrb();
orbSpawnTimer = 10 * 60; // Reset to 10 seconds
orbGenerationCount++; // Increment generation counter
// Change background to green on second generation
if (orbGenerationCount === 2) {
tween(game, {
backgroundColor: 0x00FF00
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
} else {
// Reset timer when orbs exist so it starts fresh when they're all gone
orbSpawnTimer = 10 * 60;
orbSpawnTxt.setText('Next Orb: 10');
}
checkCollisions();
checkGameOver();
};
Un stickman con una posición de corredor. In-Game asset. 2d. High contrast. No shadows
Cesped en forma de rectangulo como con el pasto arriba tierra y algunas rocas irregulares. In-Game asset. 2d. High contrast. No shadows
Stickman delgado en posición fetal y que este todo relleno de negro
Quitale el fondo
Bebidas energéticas verdes. In-Game asset. 2d. High contrast. No shadows
1 Lata verde con un rayo, sin fondo. In-Game asset. 2d. High contrast. No shadows
Change the green of the grass to dark green