/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Distraction Icon (obstacle)
var DistractionIcon = Container.expand(function () {
var self = Container.call(this);
var icon = self.attachAsset('distraction_icon', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'distraction';
self.lane = 1;
self.speed = 16; // Will be set by game, but default here
return self;
});
// Productivity Icon (collectible)
var ProdIcon = Container.expand(function () {
var self = Container.call(this);
var icon = self.attachAsset('prod_icon', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'prod';
self.lane = 1;
self.speed = 16; // Will be set by game, but default here
return self;
});
// Runner class
var Runner = Container.expand(function () {
var self = Container.call(this);
var runnerSprite = self.attachAsset('runner', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1; // Start in center lane (0: left, 1: center, 2: right)
self.y = 2200; // Near bottom of screen
// Move to lane with tween
self.moveToLane = function (targetLane) {
if (targetLane < 0 || targetLane >= laneCount) return;
self.lane = targetLane;
tween(self, {
x: laneX[targetLane]
}, {
duration: 120,
easing: tween.cubicOut
});
};
// Set initial position
self.x = laneX[self.lane];
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Lane positions (global, so all classes can access)
// Runner character
// Distraction icons (obstacles)
// Productivity icons (collectibles)
// Score display
var laneCount = 3;
var laneX = [2048 / 2 - 300,
// Left lane
2048 / 2,
// Center lane
2048 / 2 + 300 // Right lane
];
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Game variables
var runner;
var objects = []; // All collectibles and obstacles
var spawnTimer = 0;
var spawnInterval = 60; // Frames between spawns (will decrease)
var objectSpeed = 16; // Initial speed (will increase)
var minSpawnInterval = 24;
var maxObjectSpeed = 38;
var score = 0;
var lastMoveX = null;
var lastMoveY = null;
var dragStartX = null;
var dragStartY = null;
var dragActive = false;
// Initialize runner
runner = new Runner();
game.addChild(runner);
// Helper: spawn a new object (randomly prod or distraction)
function spawnObject() {
// Randomly choose lane (0, 1, or 2)
var lane = Math.floor(Math.random() * laneCount);
// Randomly choose type (60% prod, 40% distraction)
var isProd = Math.random() < 0.6;
var obj;
if (isProd) {
obj = new ProdIcon();
} else {
obj = new DistractionIcon();
}
// Ensure object spawns only in a lane, never between lanes
obj.lane = lane;
obj.x = laneX[lane];
obj.y = -100; // Start just above screen
// For distraction (obstacle), randomly assign a higher speed to some for reflex challenge
if (obj.type === 'distraction' && Math.random() < 0.4) {
// 40% of obstacles are fast (reflex test)
obj.speed = objectSpeed + 10 + Math.floor(Math.random() * 6); // +10~+15 px/frame
} else {
obj.speed = objectSpeed;
}
// Track lastY and lastWasIntersecting for event detection
obj.lastY = obj.y;
obj.lastWasIntersecting = false;
objects.push(obj);
game.addChild(obj);
}
// Touch/drag/swipe controls
game.down = function (x, y, obj) {
dragStartX = x;
dragStartY = y;
dragActive = true;
lastMoveX = x;
lastMoveY = y;
};
game.move = function (x, y, obj) {
if (!dragActive) return;
var dx = x - dragStartX;
var dy = y - dragStartY;
// Only consider horizontal swipes
if (Math.abs(dx) > 80 && Math.abs(dx) > Math.abs(dy)) {
if (dx > 0 && runner.lane < laneCount - 1) {
runner.moveToLane(runner.lane + 1);
dragActive = false;
} else if (dx < 0 && runner.lane > 0) {
runner.moveToLane(runner.lane - 1);
dragActive = false;
}
}
lastMoveX = x;
lastMoveY = y;
};
game.up = function (x, y, obj) {
// If tap (not swipe), move to lane based on tap position
if (dragActive && Math.abs(x - dragStartX) < 40 && Math.abs(y - dragStartY) < 40) {
// Tap: move to nearest lane
var tapLane = 0;
var minDist = Math.abs(x - laneX[0]);
for (var i = 1; i < laneCount; i++) {
var d = Math.abs(x - laneX[i]);
if (d < minDist) {
minDist = d;
tapLane = i;
}
}
runner.moveToLane(tapLane);
}
dragActive = false;
dragStartX = null;
dragStartY = null;
};
// Main game loop
game.update = function () {
// Increase difficulty over time
if (LK.ticks % 180 == 0) {
// Every 3 seconds
if (spawnInterval > minSpawnInterval) spawnInterval -= 2;
if (objectSpeed < maxObjectSpeed) objectSpeed += 1;
}
// Spawn objects
spawnTimer++;
if (spawnTimer >= spawnInterval) {
spawnObject();
spawnTimer = 0;
}
// Move objects and check collisions
for (var i = objects.length - 1; i >= 0; i--) {
var obj = objects[i];
obj.y += obj.speed;
// Remove if off screen (use lastY for transition detection)
if (obj.lastY <= 2800 && obj.y > 2800) {
obj.destroy();
objects.splice(i, 1);
continue;
}
// Collision with runner (use lastWasIntersecting for precise event)
// Only check for collision if in the same lane and vertical overlap
var isIntersecting = obj.lane === runner.lane && Math.abs(obj.y - runner.y + 40) < 120;
if (!obj.lastWasIntersecting && isIntersecting) {
if (obj.type === 'prod') {
// Collect
score += 1;
LK.setScore(score);
scoreTxt.setText(LK.getScore());
LK.effects.flashObject(runner, 0x4caf50, 200);
obj.destroy();
objects.splice(i, 1);
} else if (obj.type === 'distraction') {
// Hit distraction: game over
LK.effects.flashScreen(0xf44336, 800);
LK.showGameOver();
return;
}
}
// Update lastY and lastWasIntersecting for next frame
obj.lastY = obj.y;
obj.lastWasIntersecting = isIntersecting;
}
};
// Center runner vertically (bottom 1/5th of screen)
runner.y = 2732 - 350;
// Initial score
score = 0;
LK.setScore(score);
scoreTxt.setText(LK.getScore());
// Play background music at game start
LK.playMusic('Mozart'); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Distraction Icon (obstacle)
var DistractionIcon = Container.expand(function () {
var self = Container.call(this);
var icon = self.attachAsset('distraction_icon', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'distraction';
self.lane = 1;
self.speed = 16; // Will be set by game, but default here
return self;
});
// Productivity Icon (collectible)
var ProdIcon = Container.expand(function () {
var self = Container.call(this);
var icon = self.attachAsset('prod_icon', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'prod';
self.lane = 1;
self.speed = 16; // Will be set by game, but default here
return self;
});
// Runner class
var Runner = Container.expand(function () {
var self = Container.call(this);
var runnerSprite = self.attachAsset('runner', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1; // Start in center lane (0: left, 1: center, 2: right)
self.y = 2200; // Near bottom of screen
// Move to lane with tween
self.moveToLane = function (targetLane) {
if (targetLane < 0 || targetLane >= laneCount) return;
self.lane = targetLane;
tween(self, {
x: laneX[targetLane]
}, {
duration: 120,
easing: tween.cubicOut
});
};
// Set initial position
self.x = laneX[self.lane];
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Lane positions (global, so all classes can access)
// Runner character
// Distraction icons (obstacles)
// Productivity icons (collectibles)
// Score display
var laneCount = 3;
var laneX = [2048 / 2 - 300,
// Left lane
2048 / 2,
// Center lane
2048 / 2 + 300 // Right lane
];
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Game variables
var runner;
var objects = []; // All collectibles and obstacles
var spawnTimer = 0;
var spawnInterval = 60; // Frames between spawns (will decrease)
var objectSpeed = 16; // Initial speed (will increase)
var minSpawnInterval = 24;
var maxObjectSpeed = 38;
var score = 0;
var lastMoveX = null;
var lastMoveY = null;
var dragStartX = null;
var dragStartY = null;
var dragActive = false;
// Initialize runner
runner = new Runner();
game.addChild(runner);
// Helper: spawn a new object (randomly prod or distraction)
function spawnObject() {
// Randomly choose lane (0, 1, or 2)
var lane = Math.floor(Math.random() * laneCount);
// Randomly choose type (60% prod, 40% distraction)
var isProd = Math.random() < 0.6;
var obj;
if (isProd) {
obj = new ProdIcon();
} else {
obj = new DistractionIcon();
}
// Ensure object spawns only in a lane, never between lanes
obj.lane = lane;
obj.x = laneX[lane];
obj.y = -100; // Start just above screen
// For distraction (obstacle), randomly assign a higher speed to some for reflex challenge
if (obj.type === 'distraction' && Math.random() < 0.4) {
// 40% of obstacles are fast (reflex test)
obj.speed = objectSpeed + 10 + Math.floor(Math.random() * 6); // +10~+15 px/frame
} else {
obj.speed = objectSpeed;
}
// Track lastY and lastWasIntersecting for event detection
obj.lastY = obj.y;
obj.lastWasIntersecting = false;
objects.push(obj);
game.addChild(obj);
}
// Touch/drag/swipe controls
game.down = function (x, y, obj) {
dragStartX = x;
dragStartY = y;
dragActive = true;
lastMoveX = x;
lastMoveY = y;
};
game.move = function (x, y, obj) {
if (!dragActive) return;
var dx = x - dragStartX;
var dy = y - dragStartY;
// Only consider horizontal swipes
if (Math.abs(dx) > 80 && Math.abs(dx) > Math.abs(dy)) {
if (dx > 0 && runner.lane < laneCount - 1) {
runner.moveToLane(runner.lane + 1);
dragActive = false;
} else if (dx < 0 && runner.lane > 0) {
runner.moveToLane(runner.lane - 1);
dragActive = false;
}
}
lastMoveX = x;
lastMoveY = y;
};
game.up = function (x, y, obj) {
// If tap (not swipe), move to lane based on tap position
if (dragActive && Math.abs(x - dragStartX) < 40 && Math.abs(y - dragStartY) < 40) {
// Tap: move to nearest lane
var tapLane = 0;
var minDist = Math.abs(x - laneX[0]);
for (var i = 1; i < laneCount; i++) {
var d = Math.abs(x - laneX[i]);
if (d < minDist) {
minDist = d;
tapLane = i;
}
}
runner.moveToLane(tapLane);
}
dragActive = false;
dragStartX = null;
dragStartY = null;
};
// Main game loop
game.update = function () {
// Increase difficulty over time
if (LK.ticks % 180 == 0) {
// Every 3 seconds
if (spawnInterval > minSpawnInterval) spawnInterval -= 2;
if (objectSpeed < maxObjectSpeed) objectSpeed += 1;
}
// Spawn objects
spawnTimer++;
if (spawnTimer >= spawnInterval) {
spawnObject();
spawnTimer = 0;
}
// Move objects and check collisions
for (var i = objects.length - 1; i >= 0; i--) {
var obj = objects[i];
obj.y += obj.speed;
// Remove if off screen (use lastY for transition detection)
if (obj.lastY <= 2800 && obj.y > 2800) {
obj.destroy();
objects.splice(i, 1);
continue;
}
// Collision with runner (use lastWasIntersecting for precise event)
// Only check for collision if in the same lane and vertical overlap
var isIntersecting = obj.lane === runner.lane && Math.abs(obj.y - runner.y + 40) < 120;
if (!obj.lastWasIntersecting && isIntersecting) {
if (obj.type === 'prod') {
// Collect
score += 1;
LK.setScore(score);
scoreTxt.setText(LK.getScore());
LK.effects.flashObject(runner, 0x4caf50, 200);
obj.destroy();
objects.splice(i, 1);
} else if (obj.type === 'distraction') {
// Hit distraction: game over
LK.effects.flashScreen(0xf44336, 800);
LK.showGameOver();
return;
}
}
// Update lastY and lastWasIntersecting for next frame
obj.lastY = obj.y;
obj.lastWasIntersecting = isIntersecting;
}
};
// Center runner vertically (bottom 1/5th of screen)
runner.y = 2732 - 350;
// Initial score
score = 0;
LK.setScore(score);
scoreTxt.setText(LK.getScore());
// Play background music at game start
LK.playMusic('Mozart');