/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // BearObstacle class (spawns bear image) var BearObstacle = Container.expand(function () { var self = Container.call(this); var bear = self.attachAsset('bear', { anchorX: 0.5, anchorY: 0.5 }); self.radius = bear.width * 0.45; self.speed = 0; // Set by game // Walking animation: scaleX alternates to simulate walking function startBearWalkAnim() { // Reset scale bear.scaleX = 1; // Animate scaleX to 0.85 and back to 1.15, then repeat tween(bear, { scaleX: 0.85 }, { duration: 120, easing: tween.linear, onFinish: function onFinish() { tween(bear, { scaleX: 1.15 }, { duration: 120, easing: tween.linear, onFinish: startBearWalkAnim }); } }); } startBearWalkAnim(); return self; }); // Obstacle class (tree or rock) var Obstacle = Container.expand(function () { var self = Container.call(this); self.type = Math.random() < 0.6 ? 'tree' : 'rock'; var assetId = self.type === 'tree' ? 'tree' : 'rock'; var obs = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); self.radius = obs.width * 0.45; self.speed = 0; // Set by game return self; }); // Powerup class (shield or boost) var Powerup = Container.expand(function () { var self = Container.call(this); self.kind = Math.random() < 0.5 ? 'shield' : 'boost'; var assetId = self.kind === 'shield' ? 'shield' : 'boost'; var pu = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); self.radius = pu.width * 0.45; self.speed = 0; // Set by game return self; }); // Rock2Obstacle class (spawns rock2 image) var Rock2Obstacle = Container.expand(function () { var self = Container.call(this); var obs = self.attachAsset('rock2', { anchorX: 0.5, anchorY: 0.5 }); self.radius = obs.width * 0.45; self.speed = 0; // Set by game return self; }); // Snowboarder (player) class var Snowboarder = Container.expand(function () { var self = Container.call(this); var sb = self.attachAsset('snowboarder', { anchorX: 0.5, anchorY: 0.5 }); self.radius = sb.width * 0.45; // For collision self.hasShield = false; self.shieldNode = null; // Show shield visual if active self.setShield = function (active) { self.hasShield = active; if (active && !self.shieldNode) { self.shieldNode = self.attachAsset('shield', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3, alpha: 0.5 }); } else if (!active && self.shieldNode) { self.shieldNode.destroy(); self.shieldNode = null; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xffffff // White for snow }); /**** * Game Code ****/ // Speed boost powerup // Shield powerup // Rock obstacle // Tree obstacle // Snowboarder (player) // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var PLAY_AREA_MARGIN = 60; // px, left/right margin for obstacles (reduced for denser spread) var OBSTACLE_MIN_Y_DIST = 320; // Minimum vertical distance between obstacles (denser) var OBSTACLE_MAX_Y_DIST = 540; // Maximum vertical distance between obstacles (denser) var OBSTACLE_MIN_X = PLAY_AREA_MARGIN; var OBSTACLE_MAX_X = GAME_WIDTH - PLAY_AREA_MARGIN; var POWERUP_CHANCE = 0.12; // Chance to spawn a powerup instead of obstacle // Intensity increase after 1000 points var INTENSITY_SCORE_THRESHOLD = 1000; var INTENSE_OBSTACLE_MIN_Y_DIST = 160; // Denser in intense mode var INTENSE_OBSTACLE_MAX_Y_DIST = 320; var INTENSE_POWERUP_CHANCE = 0.16; // Game state var snowboarder; var obstacles = []; var powerups = []; var dragNode = null; var dragOffsetX = 0; var dragStartX = 0; var dragStartSnowboarderX = 0; var lastObstacleY = 0; var speed = 18; // Initial speed (pixels per frame) var maxSpeed = 48; var speedIncreaseEvery = 600; // Increase speed every N ticks var ticksSinceStart = 0; var distance = 0; // Distance traveled (score) var scoreTxt; var shieldTimer = 0; var boostTimer = 0; var isBoosting = false; // Add tree images to both sides of the screen as static background elements var leftTrees = []; var rightTrees = []; var treeSpacingY = 320 * 0.95; // Increase tree intensity by reducing spacing by 5% var treeY = 0; while (treeY < GAME_HEIGHT + 200) { // Left side var leftTree = LK.getAsset('tree', { anchorX: 0.5, anchorY: 0.5, x: PLAY_AREA_MARGIN / 2, y: treeY }); game.addChild(leftTree); leftTrees.push(leftTree); // Right side var rightTree = LK.getAsset('tree', { anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH - PLAY_AREA_MARGIN / 2, y: treeY }); game.addChild(rightTree); rightTrees.push(rightTree); treeY += treeSpacingY; } // GUI: Score scoreTxt = new Text2('0', { size: 120, fill: "#222" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // GUI: Lives var lives = 3; var livesTxt = new Text2('❤❤❤', { size: 100, fill: "#d22" }); // Anchor to right edge, top (anchorX: 1, anchorY: 0) livesTxt.anchor.set(1, 0); // Add to top right of GUI LK.gui.topRight.addChild(livesTxt); // Initialize player snowboarder = new Snowboarder(); game.addChild(snowboarder); snowboarder.x = GAME_WIDTH / 2; snowboarder.y = GAME_HEIGHT * 0.12; // Helper: Clamp value function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } // Helper: Distance between two objects function dist(a, b) { var dx = a.x - b.x; var dy = a.y - b.y; return Math.sqrt(dx * dx + dy * dy); } // Spawn an obstacle or powerup function spawnObstacleOrPowerup(y) { // Determine if intensity should be increased var scoreVal = Math.floor(distance / 10); var minYDist = OBSTACLE_MIN_Y_DIST; var maxYDist = OBSTACLE_MAX_Y_DIST; var powerupChance = POWERUP_CHANCE; if (scoreVal >= INTENSITY_SCORE_THRESHOLD) { minYDist = INTENSE_OBSTACLE_MIN_Y_DIST; maxYDist = INTENSE_OBSTACLE_MAX_Y_DIST; powerupChance = INTENSE_POWERUP_CHANCE; } if (Math.random() < powerupChance) { // Powerup var pu = new Powerup(); pu.x = Math.random() * (OBSTACLE_MAX_X - OBSTACLE_MIN_X) + OBSTACLE_MIN_X; pu.y = y; pu.speed = speed; powerups.push(pu); game.addChild(pu); } else { // Obstacle var obs; if (scoreVal >= 1000 && Math.random() < 0.10) { // After 1000 points, 10% chance to spawn bear obs = new BearObstacle(); } else if (scoreVal >= 500 && Math.random() < 0.33) { // After 500 points, 33% chance to spawn rock2 obs = new Rock2Obstacle(); } else { obs = new Obstacle(); } obs.x = Math.random() * (OBSTACLE_MAX_X - OBSTACLE_MIN_X) + OBSTACLE_MIN_X; obs.y = y; obs.speed = speed; obstacles.push(obs); game.addChild(obs); } } // Initial obstacles lastObstacleY = snowboarder.y - 600; for (var i = 0; i < 6; i++) { lastObstacleY -= Math.random() * (OBSTACLE_MAX_Y_DIST - OBSTACLE_MIN_Y_DIST) + OBSTACLE_MIN_Y_DIST; spawnObstacleOrPowerup(lastObstacleY); } // Touch/drag controls game.down = function (x, y, obj) { // Only allow drag if touch is on lower 2/3 of screen (to avoid top menu) if (y > GAME_HEIGHT * 0.2) { dragNode = snowboarder; dragStartX = x; dragStartSnowboarderX = snowboarder.x; } }; game.move = function (x, y, obj) { if (dragNode === snowboarder) { var dx = x - dragStartX; var newX = clamp(dragStartSnowboarderX + dx, OBSTACLE_MIN_X, OBSTACLE_MAX_X); snowboarder.x = newX; // Rotate snowboarder to face movement direction if (Math.abs(dx) > 8) { // Limit max angle to 30 degrees (PI/6) var maxAngle = Math.PI / 6; var angle = clamp(dx / 400, -1, 1) * maxAngle; snowboarder.rotation = angle; } else { snowboarder.rotation = 0; } } }; game.up = function (x, y, obj) { dragNode = null; if (snowboarder) snowboarder.rotation = 0; }; // Main game loop game.update = function () { ticksSinceStart++; // Increase speed over time if (ticksSinceStart % speedIncreaseEvery === 0 && speed < maxSpeed) { speed += 2; } // Speed scaling: increase by 20% for each 1000 points after the first 1000 points var scoreVal = Math.floor(distance / 10); if (scoreVal >= 1000) { // Number of 1000-point steps after the first 1000 var speedSteps = Math.floor((scoreVal - 1000) / 1000) + 1; var scaledSpeed = speed * Math.pow(1.2, speedSteps); // Clamp to maxSpeed if needed if (scaledSpeed > maxSpeed) { scaledSpeed = maxSpeed; } speed = scaledSpeed; } // If boosting, increase speed temporarily var currentSpeed = isBoosting ? speed * 1.7 : speed; // Move side trees up (reverse flow) for (var i = 0; i < leftTrees.length; i++) { leftTrees[i].y -= currentSpeed; // If tree goes off top, move it to bottom if (leftTrees[i].y < -200) { leftTrees[i].y += treeSpacingY * leftTrees.length; } } for (var i = 0; i < rightTrees.length; i++) { rightTrees[i].y -= currentSpeed; if (rightTrees[i].y < -200) { rightTrees[i].y += treeSpacingY * rightTrees.length; } } // Move obstacles and powerups up (reverse flow) for (var i = obstacles.length - 1; i >= 0; i--) { var obs = obstacles[i]; obs.y -= currentSpeed; obs.speed = currentSpeed; // If this is a BearObstacle, move it left to right if (obs instanceof BearObstacle) { // Initialize direction and lastX if not set if (typeof obs.bearDirection === "undefined") { obs.bearDirection = 1; // 1 = right, -1 = left } if (typeof obs.lastX === "undefined") { obs.lastX = obs.x; } // Move bear horizontally var bearSpeedX = 12; // px per frame, adjust for desired speed obs.x += obs.bearDirection * bearSpeedX; // Bounce at screen edges if (obs.x > OBSTACLE_MAX_X && obs.bearDirection > 0 || obs.x < OBSTACLE_MIN_X && obs.bearDirection < 0) { obs.bearDirection *= -1; // Clamp to edge obs.x = clamp(obs.x, OBSTACLE_MIN_X, OBSTACLE_MAX_X); } obs.lastX = obs.x; } // Remove if off screen (now above screen) if (obs.y + obs.height / 2 < -100) { obs.destroy(); obstacles.splice(i, 1); } } for (var i = powerups.length - 1; i >= 0; i--) { var pu = powerups[i]; pu.y -= currentSpeed; pu.speed = currentSpeed; // Remove if off screen (now above screen) if (pu.y + pu.height / 2 < -100) { pu.destroy(); powerups.splice(i, 1); } } // Continuously spawn obstacles and powerups as the player progresses (now below player) while (lastObstacleY < snowboarder.y + 3000) { // Use more intense spawn distances after threshold var scoreVal = Math.floor(distance / 10); var minYDist = OBSTACLE_MIN_Y_DIST; var maxYDist = OBSTACLE_MAX_Y_DIST; if (scoreVal >= INTENSITY_SCORE_THRESHOLD) { minYDist = INTENSE_OBSTACLE_MIN_Y_DIST; maxYDist = INTENSE_OBSTACLE_MAX_Y_DIST; } lastObstacleY += Math.random() * (maxYDist - minYDist) + minYDist; spawnObstacleOrPowerup(lastObstacleY); } // Obstacles and powerups keep coming endlessly as the player progresses // Always ensure obstacles continue to spawn all game, even if the player is not moving if (obstacles.length < 8) { // If for any reason there are too few obstacles, spawn more to keep the game challenging var scoreVal = Math.floor(distance / 10); var minYDist = OBSTACLE_MIN_Y_DIST; var maxYDist = OBSTACLE_MAX_Y_DIST; if (scoreVal >= INTENSITY_SCORE_THRESHOLD) { minYDist = INTENSE_OBSTACLE_MIN_Y_DIST; maxYDist = INTENSE_OBSTACLE_MAX_Y_DIST; } lastObstacleY += Math.random() * (maxYDist - minYDist) + minYDist; spawnObstacleOrPowerup(lastObstacleY); } // Collision: obstacles var scoreVal = Math.floor(distance / 10); // Track if snowboarder was colliding with any obstacle last frame if (typeof snowboarder.lastWasHit === "undefined") snowboarder.lastWasHit = false; var hitThisFrame = false; for (var i = 0; i < obstacles.length; i++) { var obs = obstacles[i]; if (dist(snowboarder, obs) < snowboarder.radius + obs.radius) { if (scoreVal < 200) { // Protect from collision in first 200 points: ignore collision // Optionally, flash snowboarder to indicate invulnerability LK.effects.flashObject(snowboarder, 0x66ffcc, 300); } else { if (snowboarder.hasShield) { snowboarder.setShield(false); // Flash snowboarder LK.effects.flashObject(snowboarder, 0xffe066, 600); } else { hitThisFrame = true; } } // Only check first collision for life loss break; } } // Only deduct a life if we were not hit last frame and are hit this frame if (!snowboarder.lastWasHit && hitThisFrame) { lives -= 1; // Clamp lives to minimum 0 if (lives < 0) lives = 0; // Update lives display var heartStr = ''; for (var i = 0; i < lives; i++) heartStr += '❤'; livesTxt.setText(heartStr); LK.effects.flashScreen(0xff0000, 900); if (lives <= 0) { LK.showGameOver(); return; } } snowboarder.lastWasHit = hitThisFrame; // Collision: powerups for (var i = powerups.length - 1; i >= 0; i--) { var pu = powerups[i]; if (dist(snowboarder, pu) < snowboarder.radius + pu.radius) { if (pu.kind === 'shield') { snowboarder.setShield(true); shieldTimer = 360; // 6 seconds at 60fps } else if (pu.kind === 'boost') { isBoosting = true; boostTimer = 180; // 3 seconds at 60fps // Flash snowboarder red for boost LK.effects.flashObject(snowboarder, 0xff3b3b, 400); } pu.destroy(); powerups.splice(i, 1); } } // Powerup timers if (snowboarder.hasShield) { shieldTimer--; if (shieldTimer <= 0) { snowboarder.setShield(false); } } if (isBoosting) { boostTimer--; if (boostTimer <= 0) { isBoosting = false; } } // Snowboarder stays at the top; do not move snowboarder down the screen (reverse flow) // snowboarder.y += currentSpeed; // Score: distance traveled distance += currentSpeed; var scoreVal = Math.floor(distance / 10); LK.setScore(scoreVal); scoreTxt.setText(scoreVal); // Endless game: no win condition, continues until hit }; /* End of game code */
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BearObstacle class (spawns bear image)
var BearObstacle = Container.expand(function () {
var self = Container.call(this);
var bear = self.attachAsset('bear', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bear.width * 0.45;
self.speed = 0; // Set by game
// Walking animation: scaleX alternates to simulate walking
function startBearWalkAnim() {
// Reset scale
bear.scaleX = 1;
// Animate scaleX to 0.85 and back to 1.15, then repeat
tween(bear, {
scaleX: 0.85
}, {
duration: 120,
easing: tween.linear,
onFinish: function onFinish() {
tween(bear, {
scaleX: 1.15
}, {
duration: 120,
easing: tween.linear,
onFinish: startBearWalkAnim
});
}
});
}
startBearWalkAnim();
return self;
});
// Obstacle class (tree or rock)
var Obstacle = Container.expand(function () {
var self = Container.call(this);
self.type = Math.random() < 0.6 ? 'tree' : 'rock';
var assetId = self.type === 'tree' ? 'tree' : 'rock';
var obs = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = obs.width * 0.45;
self.speed = 0; // Set by game
return self;
});
// Powerup class (shield or boost)
var Powerup = Container.expand(function () {
var self = Container.call(this);
self.kind = Math.random() < 0.5 ? 'shield' : 'boost';
var assetId = self.kind === 'shield' ? 'shield' : 'boost';
var pu = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = pu.width * 0.45;
self.speed = 0; // Set by game
return self;
});
// Rock2Obstacle class (spawns rock2 image)
var Rock2Obstacle = Container.expand(function () {
var self = Container.call(this);
var obs = self.attachAsset('rock2', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = obs.width * 0.45;
self.speed = 0; // Set by game
return self;
});
// Snowboarder (player) class
var Snowboarder = Container.expand(function () {
var self = Container.call(this);
var sb = self.attachAsset('snowboarder', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = sb.width * 0.45; // For collision
self.hasShield = false;
self.shieldNode = null;
// Show shield visual if active
self.setShield = function (active) {
self.hasShield = active;
if (active && !self.shieldNode) {
self.shieldNode = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.5
});
} else if (!active && self.shieldNode) {
self.shieldNode.destroy();
self.shieldNode = null;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xffffff // White for snow
});
/****
* Game Code
****/
// Speed boost powerup
// Shield powerup
// Rock obstacle
// Tree obstacle
// Snowboarder (player)
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLAY_AREA_MARGIN = 60; // px, left/right margin for obstacles (reduced for denser spread)
var OBSTACLE_MIN_Y_DIST = 320; // Minimum vertical distance between obstacles (denser)
var OBSTACLE_MAX_Y_DIST = 540; // Maximum vertical distance between obstacles (denser)
var OBSTACLE_MIN_X = PLAY_AREA_MARGIN;
var OBSTACLE_MAX_X = GAME_WIDTH - PLAY_AREA_MARGIN;
var POWERUP_CHANCE = 0.12; // Chance to spawn a powerup instead of obstacle
// Intensity increase after 1000 points
var INTENSITY_SCORE_THRESHOLD = 1000;
var INTENSE_OBSTACLE_MIN_Y_DIST = 160; // Denser in intense mode
var INTENSE_OBSTACLE_MAX_Y_DIST = 320;
var INTENSE_POWERUP_CHANCE = 0.16;
// Game state
var snowboarder;
var obstacles = [];
var powerups = [];
var dragNode = null;
var dragOffsetX = 0;
var dragStartX = 0;
var dragStartSnowboarderX = 0;
var lastObstacleY = 0;
var speed = 18; // Initial speed (pixels per frame)
var maxSpeed = 48;
var speedIncreaseEvery = 600; // Increase speed every N ticks
var ticksSinceStart = 0;
var distance = 0; // Distance traveled (score)
var scoreTxt;
var shieldTimer = 0;
var boostTimer = 0;
var isBoosting = false;
// Add tree images to both sides of the screen as static background elements
var leftTrees = [];
var rightTrees = [];
var treeSpacingY = 320 * 0.95; // Increase tree intensity by reducing spacing by 5%
var treeY = 0;
while (treeY < GAME_HEIGHT + 200) {
// Left side
var leftTree = LK.getAsset('tree', {
anchorX: 0.5,
anchorY: 0.5,
x: PLAY_AREA_MARGIN / 2,
y: treeY
});
game.addChild(leftTree);
leftTrees.push(leftTree);
// Right side
var rightTree = LK.getAsset('tree', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH - PLAY_AREA_MARGIN / 2,
y: treeY
});
game.addChild(rightTree);
rightTrees.push(rightTree);
treeY += treeSpacingY;
}
// GUI: Score
scoreTxt = new Text2('0', {
size: 120,
fill: "#222"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// GUI: Lives
var lives = 3;
var livesTxt = new Text2('❤❤❤', {
size: 100,
fill: "#d22"
});
// Anchor to right edge, top (anchorX: 1, anchorY: 0)
livesTxt.anchor.set(1, 0);
// Add to top right of GUI
LK.gui.topRight.addChild(livesTxt);
// Initialize player
snowboarder = new Snowboarder();
game.addChild(snowboarder);
snowboarder.x = GAME_WIDTH / 2;
snowboarder.y = GAME_HEIGHT * 0.12;
// Helper: Clamp value
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Helper: Distance between two objects
function dist(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
// Spawn an obstacle or powerup
function spawnObstacleOrPowerup(y) {
// Determine if intensity should be increased
var scoreVal = Math.floor(distance / 10);
var minYDist = OBSTACLE_MIN_Y_DIST;
var maxYDist = OBSTACLE_MAX_Y_DIST;
var powerupChance = POWERUP_CHANCE;
if (scoreVal >= INTENSITY_SCORE_THRESHOLD) {
minYDist = INTENSE_OBSTACLE_MIN_Y_DIST;
maxYDist = INTENSE_OBSTACLE_MAX_Y_DIST;
powerupChance = INTENSE_POWERUP_CHANCE;
}
if (Math.random() < powerupChance) {
// Powerup
var pu = new Powerup();
pu.x = Math.random() * (OBSTACLE_MAX_X - OBSTACLE_MIN_X) + OBSTACLE_MIN_X;
pu.y = y;
pu.speed = speed;
powerups.push(pu);
game.addChild(pu);
} else {
// Obstacle
var obs;
if (scoreVal >= 1000 && Math.random() < 0.10) {
// After 1000 points, 10% chance to spawn bear
obs = new BearObstacle();
} else if (scoreVal >= 500 && Math.random() < 0.33) {
// After 500 points, 33% chance to spawn rock2
obs = new Rock2Obstacle();
} else {
obs = new Obstacle();
}
obs.x = Math.random() * (OBSTACLE_MAX_X - OBSTACLE_MIN_X) + OBSTACLE_MIN_X;
obs.y = y;
obs.speed = speed;
obstacles.push(obs);
game.addChild(obs);
}
}
// Initial obstacles
lastObstacleY = snowboarder.y - 600;
for (var i = 0; i < 6; i++) {
lastObstacleY -= Math.random() * (OBSTACLE_MAX_Y_DIST - OBSTACLE_MIN_Y_DIST) + OBSTACLE_MIN_Y_DIST;
spawnObstacleOrPowerup(lastObstacleY);
}
// Touch/drag controls
game.down = function (x, y, obj) {
// Only allow drag if touch is on lower 2/3 of screen (to avoid top menu)
if (y > GAME_HEIGHT * 0.2) {
dragNode = snowboarder;
dragStartX = x;
dragStartSnowboarderX = snowboarder.x;
}
};
game.move = function (x, y, obj) {
if (dragNode === snowboarder) {
var dx = x - dragStartX;
var newX = clamp(dragStartSnowboarderX + dx, OBSTACLE_MIN_X, OBSTACLE_MAX_X);
snowboarder.x = newX;
// Rotate snowboarder to face movement direction
if (Math.abs(dx) > 8) {
// Limit max angle to 30 degrees (PI/6)
var maxAngle = Math.PI / 6;
var angle = clamp(dx / 400, -1, 1) * maxAngle;
snowboarder.rotation = angle;
} else {
snowboarder.rotation = 0;
}
}
};
game.up = function (x, y, obj) {
dragNode = null;
if (snowboarder) snowboarder.rotation = 0;
};
// Main game loop
game.update = function () {
ticksSinceStart++;
// Increase speed over time
if (ticksSinceStart % speedIncreaseEvery === 0 && speed < maxSpeed) {
speed += 2;
}
// Speed scaling: increase by 20% for each 1000 points after the first 1000 points
var scoreVal = Math.floor(distance / 10);
if (scoreVal >= 1000) {
// Number of 1000-point steps after the first 1000
var speedSteps = Math.floor((scoreVal - 1000) / 1000) + 1;
var scaledSpeed = speed * Math.pow(1.2, speedSteps);
// Clamp to maxSpeed if needed
if (scaledSpeed > maxSpeed) {
scaledSpeed = maxSpeed;
}
speed = scaledSpeed;
}
// If boosting, increase speed temporarily
var currentSpeed = isBoosting ? speed * 1.7 : speed;
// Move side trees up (reverse flow)
for (var i = 0; i < leftTrees.length; i++) {
leftTrees[i].y -= currentSpeed;
// If tree goes off top, move it to bottom
if (leftTrees[i].y < -200) {
leftTrees[i].y += treeSpacingY * leftTrees.length;
}
}
for (var i = 0; i < rightTrees.length; i++) {
rightTrees[i].y -= currentSpeed;
if (rightTrees[i].y < -200) {
rightTrees[i].y += treeSpacingY * rightTrees.length;
}
}
// Move obstacles and powerups up (reverse flow)
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.y -= currentSpeed;
obs.speed = currentSpeed;
// If this is a BearObstacle, move it left to right
if (obs instanceof BearObstacle) {
// Initialize direction and lastX if not set
if (typeof obs.bearDirection === "undefined") {
obs.bearDirection = 1; // 1 = right, -1 = left
}
if (typeof obs.lastX === "undefined") {
obs.lastX = obs.x;
}
// Move bear horizontally
var bearSpeedX = 12; // px per frame, adjust for desired speed
obs.x += obs.bearDirection * bearSpeedX;
// Bounce at screen edges
if (obs.x > OBSTACLE_MAX_X && obs.bearDirection > 0 || obs.x < OBSTACLE_MIN_X && obs.bearDirection < 0) {
obs.bearDirection *= -1;
// Clamp to edge
obs.x = clamp(obs.x, OBSTACLE_MIN_X, OBSTACLE_MAX_X);
}
obs.lastX = obs.x;
}
// Remove if off screen (now above screen)
if (obs.y + obs.height / 2 < -100) {
obs.destroy();
obstacles.splice(i, 1);
}
}
for (var i = powerups.length - 1; i >= 0; i--) {
var pu = powerups[i];
pu.y -= currentSpeed;
pu.speed = currentSpeed;
// Remove if off screen (now above screen)
if (pu.y + pu.height / 2 < -100) {
pu.destroy();
powerups.splice(i, 1);
}
}
// Continuously spawn obstacles and powerups as the player progresses (now below player)
while (lastObstacleY < snowboarder.y + 3000) {
// Use more intense spawn distances after threshold
var scoreVal = Math.floor(distance / 10);
var minYDist = OBSTACLE_MIN_Y_DIST;
var maxYDist = OBSTACLE_MAX_Y_DIST;
if (scoreVal >= INTENSITY_SCORE_THRESHOLD) {
minYDist = INTENSE_OBSTACLE_MIN_Y_DIST;
maxYDist = INTENSE_OBSTACLE_MAX_Y_DIST;
}
lastObstacleY += Math.random() * (maxYDist - minYDist) + minYDist;
spawnObstacleOrPowerup(lastObstacleY);
}
// Obstacles and powerups keep coming endlessly as the player progresses
// Always ensure obstacles continue to spawn all game, even if the player is not moving
if (obstacles.length < 8) {
// If for any reason there are too few obstacles, spawn more to keep the game challenging
var scoreVal = Math.floor(distance / 10);
var minYDist = OBSTACLE_MIN_Y_DIST;
var maxYDist = OBSTACLE_MAX_Y_DIST;
if (scoreVal >= INTENSITY_SCORE_THRESHOLD) {
minYDist = INTENSE_OBSTACLE_MIN_Y_DIST;
maxYDist = INTENSE_OBSTACLE_MAX_Y_DIST;
}
lastObstacleY += Math.random() * (maxYDist - minYDist) + minYDist;
spawnObstacleOrPowerup(lastObstacleY);
}
// Collision: obstacles
var scoreVal = Math.floor(distance / 10);
// Track if snowboarder was colliding with any obstacle last frame
if (typeof snowboarder.lastWasHit === "undefined") snowboarder.lastWasHit = false;
var hitThisFrame = false;
for (var i = 0; i < obstacles.length; i++) {
var obs = obstacles[i];
if (dist(snowboarder, obs) < snowboarder.radius + obs.radius) {
if (scoreVal < 200) {
// Protect from collision in first 200 points: ignore collision
// Optionally, flash snowboarder to indicate invulnerability
LK.effects.flashObject(snowboarder, 0x66ffcc, 300);
} else {
if (snowboarder.hasShield) {
snowboarder.setShield(false);
// Flash snowboarder
LK.effects.flashObject(snowboarder, 0xffe066, 600);
} else {
hitThisFrame = true;
}
}
// Only check first collision for life loss
break;
}
}
// Only deduct a life if we were not hit last frame and are hit this frame
if (!snowboarder.lastWasHit && hitThisFrame) {
lives -= 1;
// Clamp lives to minimum 0
if (lives < 0) lives = 0;
// Update lives display
var heartStr = '';
for (var i = 0; i < lives; i++) heartStr += '❤';
livesTxt.setText(heartStr);
LK.effects.flashScreen(0xff0000, 900);
if (lives <= 0) {
LK.showGameOver();
return;
}
}
snowboarder.lastWasHit = hitThisFrame;
// Collision: powerups
for (var i = powerups.length - 1; i >= 0; i--) {
var pu = powerups[i];
if (dist(snowboarder, pu) < snowboarder.radius + pu.radius) {
if (pu.kind === 'shield') {
snowboarder.setShield(true);
shieldTimer = 360; // 6 seconds at 60fps
} else if (pu.kind === 'boost') {
isBoosting = true;
boostTimer = 180; // 3 seconds at 60fps
// Flash snowboarder red for boost
LK.effects.flashObject(snowboarder, 0xff3b3b, 400);
}
pu.destroy();
powerups.splice(i, 1);
}
}
// Powerup timers
if (snowboarder.hasShield) {
shieldTimer--;
if (shieldTimer <= 0) {
snowboarder.setShield(false);
}
}
if (isBoosting) {
boostTimer--;
if (boostTimer <= 0) {
isBoosting = false;
}
}
// Snowboarder stays at the top; do not move snowboarder down the screen (reverse flow)
// snowboarder.y += currentSpeed;
// Score: distance traveled
distance += currentSpeed;
var scoreVal = Math.floor(distance / 10);
LK.setScore(scoreVal);
scoreTxt.setText(scoreVal);
// Endless game: no win condition, continues until hit
};
/* End of game code */
pine tree seen from above. In-Game asset. 2d. High contrast. No shadows
snowboarder seen from above. In-Game asset. 2d. High contrast. No shadows
rock. In-Game asset. 2d. High contrast. No shadows
grey rock. In-Game asset. 2d. High contrast. No shadows
boost arrow. In-Game asset. 2d. High contrast. No shadows
remove background