/****
* 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