/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var PlayBall = Container.expand(function (lane, size) {
var self = Container.call(this);
self.lane = lane || 'middle';
self.size = size || 'small';
self.speed = 8;
self.isLarge = size === 'large';
// Array of random colors for playballs
var colors = [0xff5722,
// Orange
0x2196f3,
// Blue
0x4caf50,
// Green
0xffeb3b,
// Yellow
0x9c27b0,
// Purple
0xe91e63,
// Pink
0x00bcd4,
// Cyan
0xff9800,
// Deep Orange
0x673ab7,
// Deep Purple
0x3f51b5 // Indigo
];
// Select random color
var randomColor = colors[Math.floor(Math.random() * colors.length)];
// Create playball
var ballAsset = size === 'large' ? 'playballLarge' : 'playball';
// Create playball
var ball = self.attachAsset(ballAsset, {
anchorX: 0.5,
anchorY: 0.5,
tint: randomColor
});
// Add rotation animation
self.rotation = 0;
self.update = function () {
self.x -= self.speed;
// Rotate ball as it rolls
self.rotation -= 0.1;
ball.rotation = self.rotation;
};
return self;
});
// New Player class with boat design
var Player = Container.expand(function () {
var self = Container.call(this);
// Create boat hull (main body) - scaled to 1x (50% smaller than previous 2x)
var boatHull = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.6,
tint: 0x8B4513 // Brown color for wooden boat
});
// Create boat front (bow) - scaled to 1x (50% smaller) with adjusted position
var boatBow = self.attachAsset('spikePoint', {
anchorX: 0.5,
anchorY: 0.5,
x: 70,
y: 0,
scaleX: 0.8,
scaleY: 0.6,
rotation: -Math.PI / 2,
tint: 0x8B4513 // Brown color
});
// Create boat cabin - scaled to 1x (50% smaller) with adjusted position
var boatCabin = self.attachAsset('platformFine', {
anchorX: 0.5,
anchorY: 0.5,
x: -10,
y: 0,
scaleX: 4,
scaleY: 6,
tint: 0xFFFFFF // White cabin
});
// Create boat mast - scaled to 1x (50% smaller) with adjusted position
var boatMast = self.attachAsset('stickBody', {
anchorX: 0.5,
anchorY: 1,
x: -10,
y: -20,
scaleX: 0.5,
scaleY: 1.5,
tint: 0x654321 // Dark brown
});
// Create boat sail - scaled to 1x (50% smaller) with adjusted position
var boatSail = self.attachAsset('spikePoint', {
anchorX: 0.5,
anchorY: 0.8,
x: -10,
y: -30,
scaleX: 1.2,
scaleY: 0.8,
tint: 0xFFFFFF // White sail
});
// Create boat flag - scaled to 1x (50% smaller) with adjusted position
var boatFlag = self.attachAsset('spawnArrow', {
anchorX: 0,
anchorY: 0.5,
x: -10,
y: -65,
scaleX: 0.6,
scaleY: 0.4,
tint: 0xFF0000 // Red flag
});
// Animation variables for boat movement
self.animationTime = 0;
self.animationSpeed = 0.1;
// Player properties
self.currentLane = 0; // 0=top, 1=bottom (only 2 lanes now)
self.isMoving = false;
self.targetY = 0;
self.moveSpeed = 18; // Faster movement
// Initialize lane positions array for cleaner code
self.lanePositions = [0, 0]; // Will be set when lanes are defined
// Move up to previous lane
self.moveUp = function () {
if (!self.isMoving && self.currentLane > 0) {
self.currentLane--;
self.isMoving = true;
self.updateTargetPosition();
LK.getSound('platformSwitch').play();
}
};
// Move down to next lane
self.moveDown = function () {
if (!self.isMoving && self.currentLane < 1) {
self.currentLane++;
self.isMoving = true;
self.updateTargetPosition();
LK.getSound('platformSwitch').play();
}
};
// Update target position based on current lane
self.updateTargetPosition = function () {
// Use global lane positions if available, otherwise use defaults - adjusted for 1x player (50% smaller)
if (typeof topLaneY !== 'undefined') {
self.lanePositions[0] = topLaneY - 80;
self.lanePositions[1] = bottomLaneY - 80;
}
self.targetY = self.lanePositions[self.currentLane];
};
// Update function called every frame
self.update = function () {
// Handle lane switching movement
if (self.isMoving) {
var diff = self.targetY - self.y;
// Vertical movement
if (Math.abs(diff) < self.moveSpeed) {
self.y = self.targetY;
self.isMoving = false;
} else {
self.y += diff > 0 ? self.moveSpeed : -self.moveSpeed;
}
}
// Animate boat with bobbing motion
self.animationTime += self.animationSpeed;
var bobOffset = Math.sin(self.animationTime) * 3;
var tiltOffset = Math.sin(self.animationTime * 0.5) * 0.05;
// Bob the entire boat up and down slightly
self.y += bobOffset * 0.1;
// Tilt the boat slightly
self.rotation = tiltOffset;
// Animate the sail swaying
boatSail.rotation = Math.sin(self.animationTime * 0.7) * 0.1;
// Animate the flag waving
boatFlag.rotation = Math.sin(self.animationTime * 2) * 0.3;
};
return self;
});
var SpawnArrow = Container.expand(function (lane) {
var self = Container.call(this);
self.lane = lane || 'middle';
var arrowGraphics = self.attachAsset('spawnArrow', {
anchorX: 0.5,
anchorY: 0.5
});
arrowGraphics.rotation = Math.PI; // Point left toward incoming obstacles
self.fadeTimer = 0;
self.maxFadeTime = 60; // 1 second at 60fps
self.update = function () {
self.fadeTimer++;
var alpha = 1 - self.fadeTimer / self.maxFadeTime;
if (alpha <= 0) {
self.destroy();
return;
}
arrowGraphics.alpha = alpha;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x00a8e6
});
/****
* Game Code
****/
// Game dimensions: 2048x2732
var Obstacle = PlayBall;
var gameWidth = 2048;
var gameHeight = 2732;
// Lane positions - now only 2 lanes
var topLaneY = gameHeight * 0.4;
var bottomLaneY = gameHeight * 0.6;
// Game variables
var player;
var obstacles = [];
var spawnArrows = [];
var pendingObstacles = [];
var gameSpeed = 1;
var spawnTimer = 0;
var spawnInterval = 120; // frames between spawns
var gameTime = 0; // tracks game time in frames (60fps = 1 second)
var speedIncreaseInterval = 600; // increase speed every 10 seconds at 60fps
var largeObstaclesSpawned = 0;
var lastScoreCheck = 0;
var gameStarted = false;
var startButton;
// Initialize score display
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create player
player = game.addChild(new Player());
player.x = gameWidth * 0.35; // Move player closer to center for better camera view
player.y = topLaneY - 80; // Position on top of platform with 1x player height (50% smaller)
player.updateTargetPosition(); // Initialize target positions
player.visible = false; // Hide player until game starts
// Create start menu
var startMenu = new Container();
game.addChild(startMenu);
// Create background boat animation
var backgroundBoat = new Player();
backgroundBoat.x = -200; // Start off-screen to the left
backgroundBoat.y = gameHeight * 0.5;
backgroundBoat.alpha = 0.3; // Make it semi-transparent
backgroundBoat.scaleX = 1.2; // 50% larger than player boat
backgroundBoat.scaleY = 1.2;
startMenu.addChild(backgroundBoat);
// Animate background boat moving across screen
var backgroundBoatTween = tween(backgroundBoat, {
x: gameWidth + 200
}, {
duration: 8000,
easing: tween.linear,
onComplete: function onComplete() {
// Reset position and restart animation
backgroundBoat.x = -200;
backgroundBoatTween.restart();
}
});
// Create second background boat at different position
var backgroundBoat2 = new Player();
backgroundBoat2.x = gameWidth * 0.5; // Start in middle
backgroundBoat2.y = gameHeight * 0.35;
backgroundBoat2.alpha = 0.2; // Even more transparent
backgroundBoat2.scaleX = 0.9; // 50% larger scale
backgroundBoat2.scaleY = 0.9;
startMenu.addChild(backgroundBoat2);
// Animate second boat with different speed
var backgroundBoatTween2 = tween(backgroundBoat2, {
x: gameWidth + 200
}, {
duration: 6000,
easing: tween.linear,
onComplete: function onComplete() {
// Reset position and restart animation
backgroundBoat2.x = -200;
backgroundBoatTween2.restart();
}
});
// Add game title
var titleText = new Text2('ESCAPE FROM THE MINES BY BOAT', {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = gameWidth / 2;
titleText.y = gameHeight * 0.3;
startMenu.addChild(titleText);
// Create start button
startButton = new Container();
startMenu.addChild(startButton);
var buttonBg = startButton.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 2,
tint: 0x4CAF50
});
var buttonText = new Text2('START', {
size: 80,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
startButton.addChild(buttonText);
startButton.x = gameWidth / 2;
startButton.y = gameHeight / 2;
// Add instructions
var instructionsText = new Text2('Swipe up/down or use arrows to swim', {
size: 50,
fill: 0xFFFFFF
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = gameWidth / 2;
instructionsText.y = gameHeight * 0.65;
startMenu.addChild(instructionsText);
// Add additional instruction
var dodgeText = new Text2('Dodge the playballs!', {
size: 50,
fill: 0xFFFFFF
});
dodgeText.anchor.set(0.5, 0.5);
dodgeText.x = gameWidth / 2;
dodgeText.y = gameHeight * 0.72;
startMenu.addChild(dodgeText);
// Add decorative playballs to menu
var menuBall1 = startMenu.attachAsset('playball', {
anchorX: 0.5,
anchorY: 0.5,
x: gameWidth * 0.2,
y: gameHeight * 0.3,
tint: 0xff5722
});
var menuBall2 = startMenu.attachAsset('playball', {
anchorX: 0.5,
anchorY: 0.5,
x: gameWidth * 0.8,
y: gameHeight * 0.3,
tint: 0x2196f3
});
// Animate menu balls
tween(menuBall1, {
rotation: Math.PI * 2
}, {
duration: 3000,
loop: true,
easing: tween.linear
});
tween(menuBall2, {
rotation: -Math.PI * 2
}, {
duration: 3000,
loop: true,
easing: tween.linear
});
// Start button click handler
startButton.down = function (x, y, obj) {
gameStarted = true;
player.visible = true;
if (backgroundBoatTween) {
backgroundBoatTween.stop();
}
if (backgroundBoatTween2) {
backgroundBoatTween2.stop();
}
startMenu.destroy();
startMenu = null;
startButton = null;
};
// Create arrow buttons
var arrowUp = LK.getAsset('arrowUp', {
anchorX: 0.5,
anchorY: 0.5
});
arrowUp.x = gameWidth - 150;
arrowUp.y = gameHeight - 300;
LK.gui.addChild(arrowUp);
var arrowDown = LK.getAsset('arrowDown', {
anchorX: 0.5,
anchorY: 0.5
});
arrowDown.x = gameWidth - 150;
arrowDown.y = gameHeight - 150;
LK.gui.addChild(arrowDown);
// Arrow button event handlers
arrowUp.down = function (x, y, obj) {
if (gameStarted) {
player.moveUp();
}
};
arrowDown.down = function (x, y, obj) {
if (gameStarted) {
player.moveDown();
}
};
// Draw stone platform tracks with finer texture matching hitbox
// Create top platform using fine texture tiles
for (var i = 0; i < 41; i++) {
var topTile = LK.getAsset('platformFine', {
anchorX: 0,
anchorY: 0.5,
scaleX: 3,
scaleY: 7.2
});
topTile.x = i * 50;
topTile.y = topLaneY;
game.addChild(topTile);
}
// Create bottom platform using fine texture tiles
for (var i = 0; i < 41; i++) {
var bottomTile = LK.getAsset('platformFine', {
anchorX: 0,
anchorY: 0.5,
scaleX: 3,
scaleY: 7.2
});
bottomTile.x = i * 50;
bottomTile.y = bottomLaneY;
game.addChild(bottomTile);
}
function spawnObstacle() {
var lanes = ['top', 'bottom'];
var sizes = ['small', 'medium', 'large'];
var currentScore = LK.getScore();
var scoreSegment = Math.floor(currentScore / 150);
var requiredLargeObstacles = (scoreSegment + 1) * 3;
var forceLarge = false;
// Check if we need to force a large obstacle
if (scoreSegment > Math.floor(lastScoreCheck / 150)) {
// We've entered a new 150-point segment, reset counter
largeObstaclesSpawned = 0;
lastScoreCheck = currentScore;
}
// Force large obstacle if we haven't met the requirement and we're near the end of the segment
var progressInSegment = currentScore % 150;
if (progressInSegment > 100 && largeObstaclesSpawned < 3) {
forceLarge = true;
}
var randomSize;
if (forceLarge) {
randomSize = 'large';
} else {
randomSize = sizes[Math.floor(Math.random() * sizes.length)];
}
// Track large obstacles
if (randomSize === 'large') {
largeObstaclesSpawned++;
}
var randomLane;
// 46% more chance to spawn obstacle on player's current lane
// Base probability for 2 lanes is 50%, so 46% more = 73% for player's lane
if (Math.random() < 0.73) {
randomLane = lanes[player.currentLane];
} else {
// 27% chance for the other lane
randomLane = lanes[1 - player.currentLane];
}
// Create spawn arrow indicator
var spawnArrow = new SpawnArrow(randomLane);
spawnArrow.x = gameWidth - 250; // Position spawn arrow closer to match tighter view
if (randomLane === 'top') {
spawnArrow.y = topLaneY;
} else {
spawnArrow.y = bottomLaneY;
}
// Add pulsing animation to spawn arrow
tween(spawnArrow, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 250,
easing: tween.easeInOut
});
tween(spawnArrow, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 250,
easing: tween.easeInOut
});
spawnArrows.push(spawnArrow);
game.addChild(spawnArrow);
// Schedule obstacle to spawn after 0.5 seconds (30 frames at 60fps)
var pendingObstacle = {
lane: randomLane,
size: randomSize,
spawnTime: LK.ticks + 30
};
pendingObstacles.push(pendingObstacle);
}
function createObstacle(lane, size) {
var obstacle = new PlayBall(lane, size);
obstacle.x = gameWidth - 200; // Spawn obstacles closer to screen for tighter view
if (lane === 'top') {
obstacle.y = topLaneY; // Position at center of platform
} else {
obstacle.y = bottomLaneY; // Position at center of platform
}
var baseSpeed = 8;
var speedBonus = Math.floor(LK.getScore() / 100);
obstacle.speed = (baseSpeed + speedBonus) * gameSpeed;
obstacles.push(obstacle);
game.addChild(obstacle);
}
function checkCollisions() {
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
var collision = false;
// Check collision based on lane
if (obstacle.lane === 'top' && player.currentLane === 0 || obstacle.lane === 'bottom' && player.currentLane === 1) {
collision = player.intersects(obstacle);
}
if (collision) {
LK.getSound('collision').play();
LK.getSound('explosion').play();
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
}
}
// Touch controls for platform switching
var touchStartY = 0;
game.down = function (x, y, obj) {
touchStartY = y;
};
game.up = function (x, y, obj) {
if (gameStarted) {
var swipeDistance = y - touchStartY;
if (Math.abs(swipeDistance) > 100) {
if (swipeDistance < 0) {
// Swipe up
player.moveUp();
} else {
// Swipe down
player.moveDown();
}
}
}
};
// Main game loop
game.update = function () {
// Only update game logic if game has started
if (!gameStarted) {
return;
}
// Update game time (1 second = 60 frames at 60fps)
gameTime++;
// Update score: 1 point every 0.5 seconds (30 frames)
var timeScore = Math.floor(gameTime / 30);
LK.setScore(timeScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Increase obstacle speed by 1 for every 100 points
var baseSpeed = 8;
var speedBonus = Math.floor(LK.getScore() / 100);
var currentObstacleSpeed = baseSpeed + speedBonus;
// Increase game speed over time
if (LK.ticks % speedIncreaseInterval === 0) {
gameSpeed += 0.1;
if (spawnInterval > 60) {
spawnInterval -= 5;
}
}
// Spawn obstacles
spawnTimer++;
// Reduce spawn interval by 40% to increase frequency (multiply by 0.6)
var adjustedSpawnInterval = Math.floor(spawnInterval * 0.6);
if (spawnTimer >= adjustedSpawnInterval) {
spawnObstacle();
spawnTimer = 0;
}
// Check for pending obstacles to spawn
for (var i = pendingObstacles.length - 1; i >= 0; i--) {
var pending = pendingObstacles[i];
if (LK.ticks >= pending.spawnTime) {
createObstacle(pending.lane, pending.size);
pendingObstacles.splice(i, 1);
}
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
obstacle.speed = currentObstacleSpeed * gameSpeed;
// Remove obstacles that are off screen
if (obstacle.x < -100) {
obstacle.destroy();
obstacles.splice(i, 1);
}
}
// Update and clean up spawn arrows
for (var i = spawnArrows.length - 1; i >= 0; i--) {
var arrow = spawnArrows[i];
if (arrow.fadeTimer >= arrow.maxFadeTime) {
spawnArrows.splice(i, 1);
}
}
// Check for collisions
checkCollisions();
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var PlayBall = Container.expand(function (lane, size) {
var self = Container.call(this);
self.lane = lane || 'middle';
self.size = size || 'small';
self.speed = 8;
self.isLarge = size === 'large';
// Array of random colors for playballs
var colors = [0xff5722,
// Orange
0x2196f3,
// Blue
0x4caf50,
// Green
0xffeb3b,
// Yellow
0x9c27b0,
// Purple
0xe91e63,
// Pink
0x00bcd4,
// Cyan
0xff9800,
// Deep Orange
0x673ab7,
// Deep Purple
0x3f51b5 // Indigo
];
// Select random color
var randomColor = colors[Math.floor(Math.random() * colors.length)];
// Create playball
var ballAsset = size === 'large' ? 'playballLarge' : 'playball';
// Create playball
var ball = self.attachAsset(ballAsset, {
anchorX: 0.5,
anchorY: 0.5,
tint: randomColor
});
// Add rotation animation
self.rotation = 0;
self.update = function () {
self.x -= self.speed;
// Rotate ball as it rolls
self.rotation -= 0.1;
ball.rotation = self.rotation;
};
return self;
});
// New Player class with boat design
var Player = Container.expand(function () {
var self = Container.call(this);
// Create boat hull (main body) - scaled to 1x (50% smaller than previous 2x)
var boatHull = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.6,
tint: 0x8B4513 // Brown color for wooden boat
});
// Create boat front (bow) - scaled to 1x (50% smaller) with adjusted position
var boatBow = self.attachAsset('spikePoint', {
anchorX: 0.5,
anchorY: 0.5,
x: 70,
y: 0,
scaleX: 0.8,
scaleY: 0.6,
rotation: -Math.PI / 2,
tint: 0x8B4513 // Brown color
});
// Create boat cabin - scaled to 1x (50% smaller) with adjusted position
var boatCabin = self.attachAsset('platformFine', {
anchorX: 0.5,
anchorY: 0.5,
x: -10,
y: 0,
scaleX: 4,
scaleY: 6,
tint: 0xFFFFFF // White cabin
});
// Create boat mast - scaled to 1x (50% smaller) with adjusted position
var boatMast = self.attachAsset('stickBody', {
anchorX: 0.5,
anchorY: 1,
x: -10,
y: -20,
scaleX: 0.5,
scaleY: 1.5,
tint: 0x654321 // Dark brown
});
// Create boat sail - scaled to 1x (50% smaller) with adjusted position
var boatSail = self.attachAsset('spikePoint', {
anchorX: 0.5,
anchorY: 0.8,
x: -10,
y: -30,
scaleX: 1.2,
scaleY: 0.8,
tint: 0xFFFFFF // White sail
});
// Create boat flag - scaled to 1x (50% smaller) with adjusted position
var boatFlag = self.attachAsset('spawnArrow', {
anchorX: 0,
anchorY: 0.5,
x: -10,
y: -65,
scaleX: 0.6,
scaleY: 0.4,
tint: 0xFF0000 // Red flag
});
// Animation variables for boat movement
self.animationTime = 0;
self.animationSpeed = 0.1;
// Player properties
self.currentLane = 0; // 0=top, 1=bottom (only 2 lanes now)
self.isMoving = false;
self.targetY = 0;
self.moveSpeed = 18; // Faster movement
// Initialize lane positions array for cleaner code
self.lanePositions = [0, 0]; // Will be set when lanes are defined
// Move up to previous lane
self.moveUp = function () {
if (!self.isMoving && self.currentLane > 0) {
self.currentLane--;
self.isMoving = true;
self.updateTargetPosition();
LK.getSound('platformSwitch').play();
}
};
// Move down to next lane
self.moveDown = function () {
if (!self.isMoving && self.currentLane < 1) {
self.currentLane++;
self.isMoving = true;
self.updateTargetPosition();
LK.getSound('platformSwitch').play();
}
};
// Update target position based on current lane
self.updateTargetPosition = function () {
// Use global lane positions if available, otherwise use defaults - adjusted for 1x player (50% smaller)
if (typeof topLaneY !== 'undefined') {
self.lanePositions[0] = topLaneY - 80;
self.lanePositions[1] = bottomLaneY - 80;
}
self.targetY = self.lanePositions[self.currentLane];
};
// Update function called every frame
self.update = function () {
// Handle lane switching movement
if (self.isMoving) {
var diff = self.targetY - self.y;
// Vertical movement
if (Math.abs(diff) < self.moveSpeed) {
self.y = self.targetY;
self.isMoving = false;
} else {
self.y += diff > 0 ? self.moveSpeed : -self.moveSpeed;
}
}
// Animate boat with bobbing motion
self.animationTime += self.animationSpeed;
var bobOffset = Math.sin(self.animationTime) * 3;
var tiltOffset = Math.sin(self.animationTime * 0.5) * 0.05;
// Bob the entire boat up and down slightly
self.y += bobOffset * 0.1;
// Tilt the boat slightly
self.rotation = tiltOffset;
// Animate the sail swaying
boatSail.rotation = Math.sin(self.animationTime * 0.7) * 0.1;
// Animate the flag waving
boatFlag.rotation = Math.sin(self.animationTime * 2) * 0.3;
};
return self;
});
var SpawnArrow = Container.expand(function (lane) {
var self = Container.call(this);
self.lane = lane || 'middle';
var arrowGraphics = self.attachAsset('spawnArrow', {
anchorX: 0.5,
anchorY: 0.5
});
arrowGraphics.rotation = Math.PI; // Point left toward incoming obstacles
self.fadeTimer = 0;
self.maxFadeTime = 60; // 1 second at 60fps
self.update = function () {
self.fadeTimer++;
var alpha = 1 - self.fadeTimer / self.maxFadeTime;
if (alpha <= 0) {
self.destroy();
return;
}
arrowGraphics.alpha = alpha;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x00a8e6
});
/****
* Game Code
****/
// Game dimensions: 2048x2732
var Obstacle = PlayBall;
var gameWidth = 2048;
var gameHeight = 2732;
// Lane positions - now only 2 lanes
var topLaneY = gameHeight * 0.4;
var bottomLaneY = gameHeight * 0.6;
// Game variables
var player;
var obstacles = [];
var spawnArrows = [];
var pendingObstacles = [];
var gameSpeed = 1;
var spawnTimer = 0;
var spawnInterval = 120; // frames between spawns
var gameTime = 0; // tracks game time in frames (60fps = 1 second)
var speedIncreaseInterval = 600; // increase speed every 10 seconds at 60fps
var largeObstaclesSpawned = 0;
var lastScoreCheck = 0;
var gameStarted = false;
var startButton;
// Initialize score display
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create player
player = game.addChild(new Player());
player.x = gameWidth * 0.35; // Move player closer to center for better camera view
player.y = topLaneY - 80; // Position on top of platform with 1x player height (50% smaller)
player.updateTargetPosition(); // Initialize target positions
player.visible = false; // Hide player until game starts
// Create start menu
var startMenu = new Container();
game.addChild(startMenu);
// Create background boat animation
var backgroundBoat = new Player();
backgroundBoat.x = -200; // Start off-screen to the left
backgroundBoat.y = gameHeight * 0.5;
backgroundBoat.alpha = 0.3; // Make it semi-transparent
backgroundBoat.scaleX = 1.2; // 50% larger than player boat
backgroundBoat.scaleY = 1.2;
startMenu.addChild(backgroundBoat);
// Animate background boat moving across screen
var backgroundBoatTween = tween(backgroundBoat, {
x: gameWidth + 200
}, {
duration: 8000,
easing: tween.linear,
onComplete: function onComplete() {
// Reset position and restart animation
backgroundBoat.x = -200;
backgroundBoatTween.restart();
}
});
// Create second background boat at different position
var backgroundBoat2 = new Player();
backgroundBoat2.x = gameWidth * 0.5; // Start in middle
backgroundBoat2.y = gameHeight * 0.35;
backgroundBoat2.alpha = 0.2; // Even more transparent
backgroundBoat2.scaleX = 0.9; // 50% larger scale
backgroundBoat2.scaleY = 0.9;
startMenu.addChild(backgroundBoat2);
// Animate second boat with different speed
var backgroundBoatTween2 = tween(backgroundBoat2, {
x: gameWidth + 200
}, {
duration: 6000,
easing: tween.linear,
onComplete: function onComplete() {
// Reset position and restart animation
backgroundBoat2.x = -200;
backgroundBoatTween2.restart();
}
});
// Add game title
var titleText = new Text2('ESCAPE FROM THE MINES BY BOAT', {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = gameWidth / 2;
titleText.y = gameHeight * 0.3;
startMenu.addChild(titleText);
// Create start button
startButton = new Container();
startMenu.addChild(startButton);
var buttonBg = startButton.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 2,
tint: 0x4CAF50
});
var buttonText = new Text2('START', {
size: 80,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
startButton.addChild(buttonText);
startButton.x = gameWidth / 2;
startButton.y = gameHeight / 2;
// Add instructions
var instructionsText = new Text2('Swipe up/down or use arrows to swim', {
size: 50,
fill: 0xFFFFFF
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = gameWidth / 2;
instructionsText.y = gameHeight * 0.65;
startMenu.addChild(instructionsText);
// Add additional instruction
var dodgeText = new Text2('Dodge the playballs!', {
size: 50,
fill: 0xFFFFFF
});
dodgeText.anchor.set(0.5, 0.5);
dodgeText.x = gameWidth / 2;
dodgeText.y = gameHeight * 0.72;
startMenu.addChild(dodgeText);
// Add decorative playballs to menu
var menuBall1 = startMenu.attachAsset('playball', {
anchorX: 0.5,
anchorY: 0.5,
x: gameWidth * 0.2,
y: gameHeight * 0.3,
tint: 0xff5722
});
var menuBall2 = startMenu.attachAsset('playball', {
anchorX: 0.5,
anchorY: 0.5,
x: gameWidth * 0.8,
y: gameHeight * 0.3,
tint: 0x2196f3
});
// Animate menu balls
tween(menuBall1, {
rotation: Math.PI * 2
}, {
duration: 3000,
loop: true,
easing: tween.linear
});
tween(menuBall2, {
rotation: -Math.PI * 2
}, {
duration: 3000,
loop: true,
easing: tween.linear
});
// Start button click handler
startButton.down = function (x, y, obj) {
gameStarted = true;
player.visible = true;
if (backgroundBoatTween) {
backgroundBoatTween.stop();
}
if (backgroundBoatTween2) {
backgroundBoatTween2.stop();
}
startMenu.destroy();
startMenu = null;
startButton = null;
};
// Create arrow buttons
var arrowUp = LK.getAsset('arrowUp', {
anchorX: 0.5,
anchorY: 0.5
});
arrowUp.x = gameWidth - 150;
arrowUp.y = gameHeight - 300;
LK.gui.addChild(arrowUp);
var arrowDown = LK.getAsset('arrowDown', {
anchorX: 0.5,
anchorY: 0.5
});
arrowDown.x = gameWidth - 150;
arrowDown.y = gameHeight - 150;
LK.gui.addChild(arrowDown);
// Arrow button event handlers
arrowUp.down = function (x, y, obj) {
if (gameStarted) {
player.moveUp();
}
};
arrowDown.down = function (x, y, obj) {
if (gameStarted) {
player.moveDown();
}
};
// Draw stone platform tracks with finer texture matching hitbox
// Create top platform using fine texture tiles
for (var i = 0; i < 41; i++) {
var topTile = LK.getAsset('platformFine', {
anchorX: 0,
anchorY: 0.5,
scaleX: 3,
scaleY: 7.2
});
topTile.x = i * 50;
topTile.y = topLaneY;
game.addChild(topTile);
}
// Create bottom platform using fine texture tiles
for (var i = 0; i < 41; i++) {
var bottomTile = LK.getAsset('platformFine', {
anchorX: 0,
anchorY: 0.5,
scaleX: 3,
scaleY: 7.2
});
bottomTile.x = i * 50;
bottomTile.y = bottomLaneY;
game.addChild(bottomTile);
}
function spawnObstacle() {
var lanes = ['top', 'bottom'];
var sizes = ['small', 'medium', 'large'];
var currentScore = LK.getScore();
var scoreSegment = Math.floor(currentScore / 150);
var requiredLargeObstacles = (scoreSegment + 1) * 3;
var forceLarge = false;
// Check if we need to force a large obstacle
if (scoreSegment > Math.floor(lastScoreCheck / 150)) {
// We've entered a new 150-point segment, reset counter
largeObstaclesSpawned = 0;
lastScoreCheck = currentScore;
}
// Force large obstacle if we haven't met the requirement and we're near the end of the segment
var progressInSegment = currentScore % 150;
if (progressInSegment > 100 && largeObstaclesSpawned < 3) {
forceLarge = true;
}
var randomSize;
if (forceLarge) {
randomSize = 'large';
} else {
randomSize = sizes[Math.floor(Math.random() * sizes.length)];
}
// Track large obstacles
if (randomSize === 'large') {
largeObstaclesSpawned++;
}
var randomLane;
// 46% more chance to spawn obstacle on player's current lane
// Base probability for 2 lanes is 50%, so 46% more = 73% for player's lane
if (Math.random() < 0.73) {
randomLane = lanes[player.currentLane];
} else {
// 27% chance for the other lane
randomLane = lanes[1 - player.currentLane];
}
// Create spawn arrow indicator
var spawnArrow = new SpawnArrow(randomLane);
spawnArrow.x = gameWidth - 250; // Position spawn arrow closer to match tighter view
if (randomLane === 'top') {
spawnArrow.y = topLaneY;
} else {
spawnArrow.y = bottomLaneY;
}
// Add pulsing animation to spawn arrow
tween(spawnArrow, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 250,
easing: tween.easeInOut
});
tween(spawnArrow, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 250,
easing: tween.easeInOut
});
spawnArrows.push(spawnArrow);
game.addChild(spawnArrow);
// Schedule obstacle to spawn after 0.5 seconds (30 frames at 60fps)
var pendingObstacle = {
lane: randomLane,
size: randomSize,
spawnTime: LK.ticks + 30
};
pendingObstacles.push(pendingObstacle);
}
function createObstacle(lane, size) {
var obstacle = new PlayBall(lane, size);
obstacle.x = gameWidth - 200; // Spawn obstacles closer to screen for tighter view
if (lane === 'top') {
obstacle.y = topLaneY; // Position at center of platform
} else {
obstacle.y = bottomLaneY; // Position at center of platform
}
var baseSpeed = 8;
var speedBonus = Math.floor(LK.getScore() / 100);
obstacle.speed = (baseSpeed + speedBonus) * gameSpeed;
obstacles.push(obstacle);
game.addChild(obstacle);
}
function checkCollisions() {
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
var collision = false;
// Check collision based on lane
if (obstacle.lane === 'top' && player.currentLane === 0 || obstacle.lane === 'bottom' && player.currentLane === 1) {
collision = player.intersects(obstacle);
}
if (collision) {
LK.getSound('collision').play();
LK.getSound('explosion').play();
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
}
}
// Touch controls for platform switching
var touchStartY = 0;
game.down = function (x, y, obj) {
touchStartY = y;
};
game.up = function (x, y, obj) {
if (gameStarted) {
var swipeDistance = y - touchStartY;
if (Math.abs(swipeDistance) > 100) {
if (swipeDistance < 0) {
// Swipe up
player.moveUp();
} else {
// Swipe down
player.moveDown();
}
}
}
};
// Main game loop
game.update = function () {
// Only update game logic if game has started
if (!gameStarted) {
return;
}
// Update game time (1 second = 60 frames at 60fps)
gameTime++;
// Update score: 1 point every 0.5 seconds (30 frames)
var timeScore = Math.floor(gameTime / 30);
LK.setScore(timeScore);
scoreTxt.setText('Score: ' + LK.getScore());
// Increase obstacle speed by 1 for every 100 points
var baseSpeed = 8;
var speedBonus = Math.floor(LK.getScore() / 100);
var currentObstacleSpeed = baseSpeed + speedBonus;
// Increase game speed over time
if (LK.ticks % speedIncreaseInterval === 0) {
gameSpeed += 0.1;
if (spawnInterval > 60) {
spawnInterval -= 5;
}
}
// Spawn obstacles
spawnTimer++;
// Reduce spawn interval by 40% to increase frequency (multiply by 0.6)
var adjustedSpawnInterval = Math.floor(spawnInterval * 0.6);
if (spawnTimer >= adjustedSpawnInterval) {
spawnObstacle();
spawnTimer = 0;
}
// Check for pending obstacles to spawn
for (var i = pendingObstacles.length - 1; i >= 0; i--) {
var pending = pendingObstacles[i];
if (LK.ticks >= pending.spawnTime) {
createObstacle(pending.lane, pending.size);
pendingObstacles.splice(i, 1);
}
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
obstacle.speed = currentObstacleSpeed * gameSpeed;
// Remove obstacles that are off screen
if (obstacle.x < -100) {
obstacle.destroy();
obstacles.splice(i, 1);
}
}
// Update and clean up spawn arrows
for (var i = spawnArrows.length - 1; i >= 0; i--) {
var arrow = spawnArrows[i];
if (arrow.fadeTimer >= arrow.maxFadeTime) {
spawnArrows.splice(i, 1);
}
}
// Check for collisions
checkCollisions();
};