/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ // Runner class: represents a colored runner moving along a lane, with animation states var Runner = Container.expand(function () { var self = Container.call(this); // Properties self.colorId = null; // 0: Red, 1: Blue, 2: Yellow, 3: Green self.lane = null; // 0-3 self.isDragging = false; self.speed = 8; // Will be set by game, increases over time // Animation state self.state = 0; // 0 or 1 self.stateTick = 0; // For animation timing // Attach runner asset (image, colorId and state) self.setColor = function (colorId) { self.colorId = colorId; // Remove previous asset if any if (self.runnerAsset) { self.removeChild(self.runnerAsset); } self.state = 0; self.stateTick = 0; self.runnerAsset = self.attachAsset('runner_' + colorId + '_0', { anchorX: 0.5, anchorY: 0.5, width: runnerSize, height: runnerSize }); }; // Swap runner asset to new state self.setState = function (state) { if (self.state === state) return; self.state = state; if (self.runnerAsset) { self.removeChild(self.runnerAsset); } self.runnerAsset = self.attachAsset('runner_' + self.colorId + '_' + state, { anchorX: 0.5, anchorY: 0.5, width: runnerSize, height: runnerSize }); }; // Called every tick self.update = function () { if (!self.isDragging) { self.x += self.speed; // Play running sound if not already playing if (!self._runningSound || self._runningSound.paused) { self._runningSound = LK.getSound('running'); self._runningSound.play(); } } else { // Pause running sound if dragging if (self._runningSound) { self._runningSound.stop(); } } // Animate runner: swap state every 8 ticks to mimic running self.stateTick++; if (self.stateTick >= 8) { var nextState = self.state === 0 ? 1 : 0; self.setState(nextState); self.stateTick = 0; } }; // For drag start self.down = function (x, y, obj) { if (!self.isDragging) { self.isDragging = true; dragRunner = self; dragOffsetY = y - self.y; // Bring to front if (self.parent) { self.parent.removeChild(self); game.addChild(self); } } }; // For drag end self.up = function (x, y, obj) { if (self.isDragging) { self.isDragging = false; dragRunner = null; // Snap to nearest lane var laneIdx = getLaneFromY(self.y); self.lane = laneIdx; self.y = getLaneY(laneIdx); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // --- Constants --- // Red, state 0 // Red, state 1 // Blue, state 0 // Blue, state 1 // Yellow, state 0 // Yellow, state 1 // Green, state 0 // Green, state 1 var NUM_LANES = 4; var laneColors = [0xd83318, 0x1e6be3, 0xf7e12c, 0x2ecc40]; // Red, Blue, Yellow, Green var laneNames = ['Red', 'Blue', 'Yellow', 'Green']; var laneWidth = 420; var laneGap = 170; // Further increased gap for more vertical spacing var laneStartX = 0; // Extend lanes to the left border var laneEndX = 2048 - 60; // Move destinations a bit more to the right var runnerSize = 170; var runnerSpawnX = 100; // Center lanes vertically with padding at top and bottom var totalLaneHeight = 0; var laneHeight = 140; // Increased lane height for better visuals var NUM_LANES = 4; totalLaneHeight = NUM_LANES * laneHeight + (NUM_LANES - 1) * laneGap; var verticalPadding = Math.floor((2732 - totalLaneHeight) / 2); var runnerMinY = verticalPadding; var runnerMaxY = 2732 - verticalPadding - laneHeight; var runnerSpawnInterval = 90; // ticks between spawns, will decrease var minSpawnInterval = 30; var speedIncreaseInterval = 480; // ticks between speed increases (decreased for faster ramp) var runnerBaseSpeed = 6; // decreased initial speed var runnerMaxSpeed = 24; var runnerSpeedStep = 1.1; // decreased speed increase value // --- State --- var runners = []; var dragRunner = null; var dragOffsetY = 0; var score = 0; var combo = 0; var bestCombo = 0; var failedMatches = 0; // Track total failed matches var spawnTimer = 0; game._endgameSoundPlayed = false; var spawnInterval = runnerSpawnInterval; var runnerSpeed = runnerBaseSpeed; var ticksSinceStart = 0; var lastLaneY = []; // cache lane Y positions // --- GUI Elements --- var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // High score variable and text var highScore = storage.highScore || 0; var highScoreTxt = new Text2('Best: ' + highScore, { size: 70, fill: 0xFFD700 }); highScoreTxt.anchor.set(1, 0); // Top right highScoreTxt.x = 0; highScoreTxt.y = 0; LK.gui.topRight.addChild(highScoreTxt); var comboTxt = new Text2('', { size: 70, fill: 0xFFE066 }); comboTxt.anchor.set(0.5, 0); comboTxt.y = 130; LK.gui.top.addChild(comboTxt); // --- Background Image --- var backgroundImg = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChild(backgroundImg); // --- Lane & Destination Graphics --- var laneRects = []; var destRects = []; for (var i = 0; i < NUM_LANES; i++) { // Lane rectangle var laneY = getLaneY(i); lastLaneY[i] = laneY; var laneRect = LK.getAsset('lane_' + i, { anchorX: 0, anchorY: 0.5, x: laneStartX, y: laneY + laneHeight / 2, width: laneEndX - laneStartX, // Extend lane to left border height: laneHeight, color: 0x333333, shape: 'box' }); game.addChild(laneRect); laneRects.push(laneRect); // Destination circle var destCircle = LK.getAsset('dest_' + i, { anchorX: 0.5, anchorY: 0.5, x: laneEndX - 60, // Move a bit more to the left y: laneY + laneHeight / 2 - 36, // Place slightly higher width: runnerSize * 1.35, height: runnerSize * 1.35, color: laneColors[i], shape: 'ellipse' }); game.addChild(destCircle); destRects.push(destCircle); // Destination label var destLabel = new Text2(laneNames[i], { size: 68, fill: 0xFFFFFF }); destLabel.anchor.set(0.5, 0.5); destLabel.x = laneEndX - 60; // Move label slightly to the left to match destination destLabel.y = laneY + laneHeight / 2 + runnerSize * 0.7; // Slightly higher game.addChild(destLabel); } // --- Helper Functions --- function getLaneY(laneIdx) { // Lanes are now centered with verticalPadding, laneHeight, and laneGap return runnerMinY + laneIdx * (laneHeight + laneGap); } function getLaneFromY(y) { for (var i = 0; i < NUM_LANES; i++) { var laneY = lastLaneY[i]; if (y >= laneY && y < laneY + laneHeight) { return i; } } // Clamp to nearest if (y < lastLaneY[0]) return 0; if (y > lastLaneY[NUM_LANES - 1] + laneHeight) return NUM_LANES - 1; // Fallback return 0; } // Spawns a new runner at a random lane and color function spawnRunner() { var colorId = Math.floor(Math.random() * NUM_LANES); var laneIdx = Math.floor(Math.random() * NUM_LANES); var runner = new Runner(); runner.setColor(colorId); runner.lane = laneIdx; runner.x = runnerSpawnX; runner.y = getLaneY(laneIdx) + laneHeight / 2; runner.speed = runnerSpeed; runners.push(runner); game.addChild(runner); } // --- Game Event Handlers --- // Dragging logic: handled globally for all runners game.move = function (x, y, obj) { if (dragRunner) { // Clamp y to game area var newY = y - dragOffsetY; if (newY < runnerMinY) newY = runnerMinY; if (newY > runnerMaxY) newY = runnerMaxY; dragRunner.y = newY + laneHeight / 2; // Snap to nearest lane visually var laneIdx = getLaneFromY(dragRunner.y - laneHeight / 2); dragRunner.lane = laneIdx; } }; game.down = function (x, y, obj) { // Check if a runner is under the touch for (var i = runners.length - 1; i >= 0; i--) { var r = runners[i]; var dx = x - r.x; var dy = y - r.y; if (Math.abs(dx) < runnerSize / 1.2 && Math.abs(dy) < runnerSize / 1.2) { // Start drag r.down(x, y, obj); break; } } }; game.up = function (x, y, obj) { if (dragRunner) { dragRunner.up(x, y, obj); } }; // --- Main Game Loop --- game.update = function () { ticksSinceStart++; // Spawn runners spawnTimer++; if (spawnTimer >= spawnInterval) { spawnRunner(); spawnTimer = 0; } // Increase speed and difficulty over time if (ticksSinceStart % speedIncreaseInterval === 0 && spawnInterval > minSpawnInterval) { spawnInterval -= 8; if (spawnInterval < minSpawnInterval) spawnInterval = minSpawnInterval; runnerSpeed += runnerSpeedStep; if (runnerSpeed > runnerMaxSpeed) runnerSpeed = runnerMaxSpeed; } // Update runners for (var i = runners.length - 1; i >= 0; i--) { var r = runners[i]; r.speed = runnerSpeed; r.update(); // If runner reaches destination area if (!r.isDragging && r.x + runnerSize / 2 >= laneEndX - runnerSize * 0.6) { var laneIdx = r.lane; var destColor = laneColors[laneIdx]; if (r.colorId === laneIdx) { // Correct delivery score += 1; combo += 1; if (combo > bestCombo) bestCombo = combo; scoreTxt.setText(score); LK.getSound('correct').play(); // Update high score if needed if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('Best: ' + highScore); } if (combo > 1) { comboTxt.setText('Combo x' + combo + '!'); LK.getSound('combo').play(); } else { comboTxt.setText(''); } // Flash destination LK.effects.flashObject(destRects[laneIdx], 0xffffff, 300); } else { // Wrong delivery combo = 0; comboTxt.setText(''); // Flash screen red LK.effects.flashScreen(0xff0000, 500); LK.getSound('wrong').play(); // Penalty: remove score if > 0 if (score > 0) score--; scoreTxt.setText(score); // Increment failed matches and check for game over failedMatches++; if (failedMatches >= 3 && !game._endgameSoundPlayed) { game._endgameSoundPlayed = true; LK.getSound('endgame').play(); LK.setTimeout(function () { LK.showGameOver(); }, 600); // Delay to allow endgame sound to play return; } } // Remove runner r.destroy(); runners.splice(i, 1); continue; } // If runner goes off screen (missed) if (r.x > 2048 + runnerSize) { // Missed runner: treat as mistake combo = 0; comboTxt.setText(''); LK.effects.flashScreen(0xff0000, 500); LK.getSound('wrong').play(); if (score > 0) score--; scoreTxt.setText(score); // Increment failed matches and check for game over failedMatches++; if (failedMatches >= 3 && !game._endgameSoundPlayed) { game._endgameSoundPlayed = true; LK.getSound('endgame').play(); LK.setTimeout(function () { LK.showGameOver(); }, 600); // Delay to allow endgame sound to play return; } r.destroy(); runners.splice(i, 1); continue; } } }; // --- Asset Initialization (images) --- // Runner image assets (one per color) // Lane image assets (one per color) // Destination image assets (one per color)
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// Runner class: represents a colored runner moving along a lane, with animation states
var Runner = Container.expand(function () {
var self = Container.call(this);
// Properties
self.colorId = null; // 0: Red, 1: Blue, 2: Yellow, 3: Green
self.lane = null; // 0-3
self.isDragging = false;
self.speed = 8; // Will be set by game, increases over time
// Animation state
self.state = 0; // 0 or 1
self.stateTick = 0; // For animation timing
// Attach runner asset (image, colorId and state)
self.setColor = function (colorId) {
self.colorId = colorId;
// Remove previous asset if any
if (self.runnerAsset) {
self.removeChild(self.runnerAsset);
}
self.state = 0;
self.stateTick = 0;
self.runnerAsset = self.attachAsset('runner_' + colorId + '_0', {
anchorX: 0.5,
anchorY: 0.5,
width: runnerSize,
height: runnerSize
});
};
// Swap runner asset to new state
self.setState = function (state) {
if (self.state === state) return;
self.state = state;
if (self.runnerAsset) {
self.removeChild(self.runnerAsset);
}
self.runnerAsset = self.attachAsset('runner_' + self.colorId + '_' + state, {
anchorX: 0.5,
anchorY: 0.5,
width: runnerSize,
height: runnerSize
});
};
// Called every tick
self.update = function () {
if (!self.isDragging) {
self.x += self.speed;
// Play running sound if not already playing
if (!self._runningSound || self._runningSound.paused) {
self._runningSound = LK.getSound('running');
self._runningSound.play();
}
} else {
// Pause running sound if dragging
if (self._runningSound) {
self._runningSound.stop();
}
}
// Animate runner: swap state every 8 ticks to mimic running
self.stateTick++;
if (self.stateTick >= 8) {
var nextState = self.state === 0 ? 1 : 0;
self.setState(nextState);
self.stateTick = 0;
}
};
// For drag start
self.down = function (x, y, obj) {
if (!self.isDragging) {
self.isDragging = true;
dragRunner = self;
dragOffsetY = y - self.y;
// Bring to front
if (self.parent) {
self.parent.removeChild(self);
game.addChild(self);
}
}
};
// For drag end
self.up = function (x, y, obj) {
if (self.isDragging) {
self.isDragging = false;
dragRunner = null;
// Snap to nearest lane
var laneIdx = getLaneFromY(self.y);
self.lane = laneIdx;
self.y = getLaneY(laneIdx);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// --- Constants ---
// Red, state 0
// Red, state 1
// Blue, state 0
// Blue, state 1
// Yellow, state 0
// Yellow, state 1
// Green, state 0
// Green, state 1
var NUM_LANES = 4;
var laneColors = [0xd83318, 0x1e6be3, 0xf7e12c, 0x2ecc40]; // Red, Blue, Yellow, Green
var laneNames = ['Red', 'Blue', 'Yellow', 'Green'];
var laneWidth = 420;
var laneGap = 170; // Further increased gap for more vertical spacing
var laneStartX = 0; // Extend lanes to the left border
var laneEndX = 2048 - 60; // Move destinations a bit more to the right
var runnerSize = 170;
var runnerSpawnX = 100;
// Center lanes vertically with padding at top and bottom
var totalLaneHeight = 0;
var laneHeight = 140; // Increased lane height for better visuals
var NUM_LANES = 4;
totalLaneHeight = NUM_LANES * laneHeight + (NUM_LANES - 1) * laneGap;
var verticalPadding = Math.floor((2732 - totalLaneHeight) / 2);
var runnerMinY = verticalPadding;
var runnerMaxY = 2732 - verticalPadding - laneHeight;
var runnerSpawnInterval = 90; // ticks between spawns, will decrease
var minSpawnInterval = 30;
var speedIncreaseInterval = 480; // ticks between speed increases (decreased for faster ramp)
var runnerBaseSpeed = 6; // decreased initial speed
var runnerMaxSpeed = 24;
var runnerSpeedStep = 1.1; // decreased speed increase value
// --- State ---
var runners = [];
var dragRunner = null;
var dragOffsetY = 0;
var score = 0;
var combo = 0;
var bestCombo = 0;
var failedMatches = 0; // Track total failed matches
var spawnTimer = 0;
game._endgameSoundPlayed = false;
var spawnInterval = runnerSpawnInterval;
var runnerSpeed = runnerBaseSpeed;
var ticksSinceStart = 0;
var lastLaneY = []; // cache lane Y positions
// --- GUI Elements ---
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score variable and text
var highScore = storage.highScore || 0;
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 70,
fill: 0xFFD700
});
highScoreTxt.anchor.set(1, 0); // Top right
highScoreTxt.x = 0;
highScoreTxt.y = 0;
LK.gui.topRight.addChild(highScoreTxt);
var comboTxt = new Text2('', {
size: 70,
fill: 0xFFE066
});
comboTxt.anchor.set(0.5, 0);
comboTxt.y = 130;
LK.gui.top.addChild(comboTxt);
// --- Background Image ---
var backgroundImg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(backgroundImg);
// --- Lane & Destination Graphics ---
var laneRects = [];
var destRects = [];
for (var i = 0; i < NUM_LANES; i++) {
// Lane rectangle
var laneY = getLaneY(i);
lastLaneY[i] = laneY;
var laneRect = LK.getAsset('lane_' + i, {
anchorX: 0,
anchorY: 0.5,
x: laneStartX,
y: laneY + laneHeight / 2,
width: laneEndX - laneStartX,
// Extend lane to left border
height: laneHeight,
color: 0x333333,
shape: 'box'
});
game.addChild(laneRect);
laneRects.push(laneRect);
// Destination circle
var destCircle = LK.getAsset('dest_' + i, {
anchorX: 0.5,
anchorY: 0.5,
x: laneEndX - 60,
// Move a bit more to the left
y: laneY + laneHeight / 2 - 36,
// Place slightly higher
width: runnerSize * 1.35,
height: runnerSize * 1.35,
color: laneColors[i],
shape: 'ellipse'
});
game.addChild(destCircle);
destRects.push(destCircle);
// Destination label
var destLabel = new Text2(laneNames[i], {
size: 68,
fill: 0xFFFFFF
});
destLabel.anchor.set(0.5, 0.5);
destLabel.x = laneEndX - 60; // Move label slightly to the left to match destination
destLabel.y = laneY + laneHeight / 2 + runnerSize * 0.7; // Slightly higher
game.addChild(destLabel);
}
// --- Helper Functions ---
function getLaneY(laneIdx) {
// Lanes are now centered with verticalPadding, laneHeight, and laneGap
return runnerMinY + laneIdx * (laneHeight + laneGap);
}
function getLaneFromY(y) {
for (var i = 0; i < NUM_LANES; i++) {
var laneY = lastLaneY[i];
if (y >= laneY && y < laneY + laneHeight) {
return i;
}
}
// Clamp to nearest
if (y < lastLaneY[0]) return 0;
if (y > lastLaneY[NUM_LANES - 1] + laneHeight) return NUM_LANES - 1;
// Fallback
return 0;
}
// Spawns a new runner at a random lane and color
function spawnRunner() {
var colorId = Math.floor(Math.random() * NUM_LANES);
var laneIdx = Math.floor(Math.random() * NUM_LANES);
var runner = new Runner();
runner.setColor(colorId);
runner.lane = laneIdx;
runner.x = runnerSpawnX;
runner.y = getLaneY(laneIdx) + laneHeight / 2;
runner.speed = runnerSpeed;
runners.push(runner);
game.addChild(runner);
}
// --- Game Event Handlers ---
// Dragging logic: handled globally for all runners
game.move = function (x, y, obj) {
if (dragRunner) {
// Clamp y to game area
var newY = y - dragOffsetY;
if (newY < runnerMinY) newY = runnerMinY;
if (newY > runnerMaxY) newY = runnerMaxY;
dragRunner.y = newY + laneHeight / 2;
// Snap to nearest lane visually
var laneIdx = getLaneFromY(dragRunner.y - laneHeight / 2);
dragRunner.lane = laneIdx;
}
};
game.down = function (x, y, obj) {
// Check if a runner is under the touch
for (var i = runners.length - 1; i >= 0; i--) {
var r = runners[i];
var dx = x - r.x;
var dy = y - r.y;
if (Math.abs(dx) < runnerSize / 1.2 && Math.abs(dy) < runnerSize / 1.2) {
// Start drag
r.down(x, y, obj);
break;
}
}
};
game.up = function (x, y, obj) {
if (dragRunner) {
dragRunner.up(x, y, obj);
}
};
// --- Main Game Loop ---
game.update = function () {
ticksSinceStart++;
// Spawn runners
spawnTimer++;
if (spawnTimer >= spawnInterval) {
spawnRunner();
spawnTimer = 0;
}
// Increase speed and difficulty over time
if (ticksSinceStart % speedIncreaseInterval === 0 && spawnInterval > minSpawnInterval) {
spawnInterval -= 8;
if (spawnInterval < minSpawnInterval) spawnInterval = minSpawnInterval;
runnerSpeed += runnerSpeedStep;
if (runnerSpeed > runnerMaxSpeed) runnerSpeed = runnerMaxSpeed;
}
// Update runners
for (var i = runners.length - 1; i >= 0; i--) {
var r = runners[i];
r.speed = runnerSpeed;
r.update();
// If runner reaches destination area
if (!r.isDragging && r.x + runnerSize / 2 >= laneEndX - runnerSize * 0.6) {
var laneIdx = r.lane;
var destColor = laneColors[laneIdx];
if (r.colorId === laneIdx) {
// Correct delivery
score += 1;
combo += 1;
if (combo > bestCombo) bestCombo = combo;
scoreTxt.setText(score);
LK.getSound('correct').play();
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
if (combo > 1) {
comboTxt.setText('Combo x' + combo + '!');
LK.getSound('combo').play();
} else {
comboTxt.setText('');
}
// Flash destination
LK.effects.flashObject(destRects[laneIdx], 0xffffff, 300);
} else {
// Wrong delivery
combo = 0;
comboTxt.setText('');
// Flash screen red
LK.effects.flashScreen(0xff0000, 500);
LK.getSound('wrong').play();
// Penalty: remove score if > 0
if (score > 0) score--;
scoreTxt.setText(score);
// Increment failed matches and check for game over
failedMatches++;
if (failedMatches >= 3 && !game._endgameSoundPlayed) {
game._endgameSoundPlayed = true;
LK.getSound('endgame').play();
LK.setTimeout(function () {
LK.showGameOver();
}, 600); // Delay to allow endgame sound to play
return;
}
}
// Remove runner
r.destroy();
runners.splice(i, 1);
continue;
}
// If runner goes off screen (missed)
if (r.x > 2048 + runnerSize) {
// Missed runner: treat as mistake
combo = 0;
comboTxt.setText('');
LK.effects.flashScreen(0xff0000, 500);
LK.getSound('wrong').play();
if (score > 0) score--;
scoreTxt.setText(score);
// Increment failed matches and check for game over
failedMatches++;
if (failedMatches >= 3 && !game._endgameSoundPlayed) {
game._endgameSoundPlayed = true;
LK.getSound('endgame').play();
LK.setTimeout(function () {
LK.showGameOver();
}, 600); // Delay to allow endgame sound to play
return;
}
r.destroy();
runners.splice(i, 1);
continue;
}
}
};
// --- Asset Initialization (images) ---
// Runner image assets (one per color)
// Lane image assets (one per color)
// Destination image assets (one per color)
Modern App Store icon, high definition, square with rounded corners, for a game titled "Color Runner Dispatcher" and with the description "Drag and drop colored runners onto matching lanes, rerouting them in real time to deliver them to the correct colored destination. Score points for perfect matches and build combos, but avoid mistakes as the game speeds up!". No text on icon!
flat but natural looking asphalt road. In-Game asset. 2d. High contrast. No shadows
red house, open door looking left. In-Game asset. 2d. High contrast. No shadows
blue house, open door looking left. In-Game asset. 2d. High contrast. No shadows
yellow house, open door looking left. In-Game asset. 2d. High contrast. No shadows
green house, open door looking left. In-Game asset. 2d. High contrast. No shadows
red runner running. In-Game asset. 2d. High contrast. No shadows
blue runner running. In-Game asset. 2d. High contrast. No shadows
green runner running. In-Game asset. 2d. High contrast. No shadows
yellow runner running. In-Game asset. 2d. High contrast. No shadows