User prompt
protect from collision in first 200 points
User prompt
Please fix the bug: 'GAME_WIDTH is not defined' in or related to this line: 'var OBSTACLE_MAX_X = GAME_WIDTH - PLAY_AREA_MARGIN;' Line Number: 139
User prompt
Please fix the bug: 'GAME_WIDTH is not defined' in or related to this line: 'var OBSTACLE_MAX_X = GAME_WIDTH - PLAY_AREA_MARGIN;' Line Number: 139
User prompt
press mouse to start
User prompt
no obstacles in first 200 points
User prompt
give three lives
User prompt
turn boarder in the movement direction
User prompt
spread obstacles on screen more densely
User prompt
increase tree intensity by 5%
User prompt
spawn rock2 after 500 points
User prompt
put tree images on bothsides of the screen
User prompt
speed increases 20% by each 1000 points after first 1000 points
User prompt
object intensity increases after 1000 points
User prompt
obstacles continue to show all game
User prompt
put objects continuously
User prompt
rocks and trees keep coming
User prompt
games continues till hit
User prompt
snow boarder does not come down
User prompt
place the snowboarder on top
User prompt
reverse the flow
Code edit (1 edits merged)
Please save this source code
User prompt
Downhill Dash: Snowboard Escape
Initial prompt
down hill snowboard game
/**** * 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, boost, or coin) var Powerup = Container.expand(function () { var self = Container.call(this); // Set powerup type with reduced shield probability var rand = Math.random(); if (rand < 0.02) { // 2% chance for shield (further reduced) self.kind = 'shield'; } else if (rand < 0.15) { // 10% chance for boost (reduced) self.kind = 'boost'; } else { self.kind = 'coin'; // Default to coin instead of shield } // Handle coin type if set externally if (self.kind === 'coin') { self.kind = 'coin'; } var assetId = self.kind === 'coin' ? 'coin' : 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 ****/ // Game constants // Snowboarder (player) // Tree obstacle // Rock obstacle // Shield powerup // Speed boost powerup 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); // GUI: Shield Timer var shieldTimerTxt = new Text2('', { size: 80, fill: 0x00AAFF }); // Anchor to right edge, bottom (anchorX: 1, anchorY: 1) shieldTimerTxt.anchor.set(1, 1); // Add to bottom right of GUI LK.gui.bottomRight.addChild(shieldTimerTxt); // Initialize player snowboarder = new Snowboarder(); game.addChild(snowboarder); snowboarder.x = GAME_WIDTH / 2; snowboarder.y = GAME_HEIGHT * 0.12; // Start playing action music LK.playMusic('actionmusic'); // 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() < 0.45) { // Coin (collectible for score) var coin = new Powerup(); coin.kind = 'coin'; // Override to be a coin coin.x = Math.random() * (OBSTACLE_MAX_X - OBSTACLE_MIN_X) + OBSTACLE_MIN_X; coin.y = y; coin.speed = speed; powerups.push(coin); game.addChild(coin); } else 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; // Play boarding sound when moving if (Math.abs(dx) > 5) { LK.getSound('Boarding').play(); } // 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% every 100 points var currentScore = LK.getScore(); if (currentScore >= 100) { // Number of 100-point steps var speedSteps = Math.floor(currentScore / 100); var scaledSpeed = speed * Math.pow(1.2, speedSteps); // Apply the scaled speed but don't modify the base speed variable // Use currentSpeed calculation below instead var gameSpeed = scaledSpeed; } else { var gameSpeed = speed; } // If boosting, increase speed temporarily var currentSpeed = isBoosting ? gameSpeed * 1.7 : gameSpeed; // 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) { // Shield protects against ALL obstacles - no damage taken, just flash LK.effects.flashObject(snowboarder, 0xffe066, 600); // Play appropriate sound even when shielded to give feedback for (var j = 0; j < obstacles.length; j++) { var shieldObs = obstacles[j]; if (dist(snowboarder, shieldObs) < snowboarder.radius + shieldObs.radius) { if (shieldObs instanceof BearObstacle) { LK.getSound('hitbear').play(); // Use bear sound for bear when shielded } else if (shieldObs.type === 'tree') { LK.getSound('hittree').play(); } else { LK.getSound('hitrock').play(); } break; } } } 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) { // Play appropriate sound based on obstacle type for (var j = 0; j < obstacles.length; j++) { var obs = obstacles[j]; if (dist(snowboarder, obs) < snowboarder.radius + obs.radius) { if (obs instanceof BearObstacle) { LK.getSound('hitbear').play(); // Use bear sound for bear collision } else if (obs.type === 'tree') { LK.getSound('hittree').play(); } else { LK.getSound('hitrock').play(); } break; } } 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 === 'coin') { // Increase score by 10 points for coin collection LK.setScore(LK.getScore() + 10); scoreTxt.setText(LK.getScore()); LK.effects.flashObject(snowboarder, 0xffd700, 300); LK.getSound('collect').play(); } else if (pu.kind === 'shield') { snowboarder.setShield(true); shieldTimer = 600; // 10 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); // Play speedup sound when boost is used LK.getSound('speedup').play(); } pu.destroy(); powerups.splice(i, 1); } } // Powerup timers if (snowboarder.hasShield) { shieldTimer--; // Update shield timer display var secondsLeft = Math.ceil(shieldTimer / 60); shieldTimerTxt.setText('🛡️ ' + secondsLeft); // Add pulsing animation to shield timer text if (shieldTimer % 30 === 0) { tween(shieldTimerTxt, { scaleX: 1.2, scaleY: 1.2 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(shieldTimerTxt, { scaleX: 1, scaleY: 1 }, { duration: 150, easing: tween.easeIn }); } }); } if (shieldTimer <= 0) { snowboarder.setShield(false); shieldTimerTxt.setText(''); // Clear shield timer text } } else { shieldTimerTxt.setText(''); // Clear shield timer text when no shield } 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: only from coin collection (no distance scoring) distance += currentSpeed; var scoreVal = LK.getScore(); // 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, boost, or coin)
var Powerup = Container.expand(function () {
var self = Container.call(this);
// Set powerup type with reduced shield probability
var rand = Math.random();
if (rand < 0.02) {
// 2% chance for shield (further reduced)
self.kind = 'shield';
} else if (rand < 0.15) {
// 10% chance for boost (reduced)
self.kind = 'boost';
} else {
self.kind = 'coin'; // Default to coin instead of shield
}
// Handle coin type if set externally
if (self.kind === 'coin') {
self.kind = 'coin';
}
var assetId = self.kind === 'coin' ? 'coin' : 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
****/
// Game constants
// Snowboarder (player)
// Tree obstacle
// Rock obstacle
// Shield powerup
// Speed boost powerup
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);
// GUI: Shield Timer
var shieldTimerTxt = new Text2('', {
size: 80,
fill: 0x00AAFF
});
// Anchor to right edge, bottom (anchorX: 1, anchorY: 1)
shieldTimerTxt.anchor.set(1, 1);
// Add to bottom right of GUI
LK.gui.bottomRight.addChild(shieldTimerTxt);
// Initialize player
snowboarder = new Snowboarder();
game.addChild(snowboarder);
snowboarder.x = GAME_WIDTH / 2;
snowboarder.y = GAME_HEIGHT * 0.12;
// Start playing action music
LK.playMusic('actionmusic');
// 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() < 0.45) {
// Coin (collectible for score)
var coin = new Powerup();
coin.kind = 'coin'; // Override to be a coin
coin.x = Math.random() * (OBSTACLE_MAX_X - OBSTACLE_MIN_X) + OBSTACLE_MIN_X;
coin.y = y;
coin.speed = speed;
powerups.push(coin);
game.addChild(coin);
} else 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;
// Play boarding sound when moving
if (Math.abs(dx) > 5) {
LK.getSound('Boarding').play();
}
// 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% every 100 points
var currentScore = LK.getScore();
if (currentScore >= 100) {
// Number of 100-point steps
var speedSteps = Math.floor(currentScore / 100);
var scaledSpeed = speed * Math.pow(1.2, speedSteps);
// Apply the scaled speed but don't modify the base speed variable
// Use currentSpeed calculation below instead
var gameSpeed = scaledSpeed;
} else {
var gameSpeed = speed;
}
// If boosting, increase speed temporarily
var currentSpeed = isBoosting ? gameSpeed * 1.7 : gameSpeed;
// 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) {
// Shield protects against ALL obstacles - no damage taken, just flash
LK.effects.flashObject(snowboarder, 0xffe066, 600);
// Play appropriate sound even when shielded to give feedback
for (var j = 0; j < obstacles.length; j++) {
var shieldObs = obstacles[j];
if (dist(snowboarder, shieldObs) < snowboarder.radius + shieldObs.radius) {
if (shieldObs instanceof BearObstacle) {
LK.getSound('hitbear').play(); // Use bear sound for bear when shielded
} else if (shieldObs.type === 'tree') {
LK.getSound('hittree').play();
} else {
LK.getSound('hitrock').play();
}
break;
}
}
} 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) {
// Play appropriate sound based on obstacle type
for (var j = 0; j < obstacles.length; j++) {
var obs = obstacles[j];
if (dist(snowboarder, obs) < snowboarder.radius + obs.radius) {
if (obs instanceof BearObstacle) {
LK.getSound('hitbear').play(); // Use bear sound for bear collision
} else if (obs.type === 'tree') {
LK.getSound('hittree').play();
} else {
LK.getSound('hitrock').play();
}
break;
}
}
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 === 'coin') {
// Increase score by 10 points for coin collection
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
LK.effects.flashObject(snowboarder, 0xffd700, 300);
LK.getSound('collect').play();
} else if (pu.kind === 'shield') {
snowboarder.setShield(true);
shieldTimer = 600; // 10 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);
// Play speedup sound when boost is used
LK.getSound('speedup').play();
}
pu.destroy();
powerups.splice(i, 1);
}
}
// Powerup timers
if (snowboarder.hasShield) {
shieldTimer--;
// Update shield timer display
var secondsLeft = Math.ceil(shieldTimer / 60);
shieldTimerTxt.setText('🛡️ ' + secondsLeft);
// Add pulsing animation to shield timer text
if (shieldTimer % 30 === 0) {
tween(shieldTimerTxt, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(shieldTimerTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
if (shieldTimer <= 0) {
snowboarder.setShield(false);
shieldTimerTxt.setText(''); // Clear shield timer text
}
} else {
shieldTimerTxt.setText(''); // Clear shield timer text when no shield
}
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: only from coin collection (no distance scoring)
distance += currentSpeed;
var scoreVal = LK.getScore();
// 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
yellow gold coin. In-Game asset. 2d. High contrast. No shadows
make blue