/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // FallingShape class var FallingShape = Container.expand(function () { var self = Container.call(this); // Pick a random color/shape var colorList = ['shapeRed', 'shapeBlue', 'shapeGreen', 'shapeYellow', 'shapePurple']; var colorIdx = Math.floor(Math.random() * colorList.length); self.shapeId = colorList[colorIdx]; // Attach the shape asset var shape = self.attachAsset(self.shapeId, { anchorX: 0.5, anchorY: 0.5 }); // Set speed (randomize a bit for variety) self.speed = 12 + Math.floor(Math.random() * 6); // For collision detection self.radius = shape.width * 0.5; // For state tracking self.caught = false; self.missed = false; // Update method called every tick self.update = function () { self.y += self.speed; }; return self; }); // Player class var Player = Container.expand(function () { var self = Container.call(this); // Attach player asset var playerShape = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // For collision detection self.width = playerShape.width; self.height = playerShape.height; // For touch feedback self.flash = function () { tween(self, { alpha: 0.5 }, { duration: 80, onFinish: function onFinish() { tween(self, { alpha: 1 }, { duration: 120 }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xf0f8ff // Light blue background }); /**** * Game Code ****/ // Use a neutral (gray) color for the catching bar asset, not a color from the falling shapes // Game constants // Shapes for falling objects (different colors) // Player character // Sound effects // Music var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var PLAYER_Y = GAME_HEIGHT - 180; var SHAPE_SPAWN_INTERVAL = 48; // Ticks (0.8s at 60fps) var SHAPE_MIN_X = 120; var SHAPE_MAX_X = GAME_WIDTH - 120; var TARGET_SCORE = 20; var MAX_LIVES = 5; // Game state var player; var fallingShapes = []; var score = 0; var lives = MAX_LIVES; var dragNode = null; var lastTouchX = 0; // Track how many shapes to spawn per interval (difficulty) var shapesPerSpawn = 1; var lastShapeScore = 0; // Home screen overlay var homeOverlay = new Container(); // Add "confetti" shapes for extra playfulness for (var c = 0; c < 18; c++) { var confettiColors = [0xff4c4c, 0x4c7bff, 0x4cff7b, 0xfff94c, 0xb44cff]; var confettiId = ['shapeRed', 'shapeBlue', 'shapeGreen', 'shapeYellow', 'shapePurple'][Math.floor(Math.random() * 5)]; var confetti = LK.getAsset(confettiId, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7 + Math.random() * 0.5, scaleY: 0.7 + Math.random() * 0.5, x: GAME_WIDTH / 2 + Math.cos(c / 18 * Math.PI * 2) * (700 + Math.random() * 200), y: GAME_HEIGHT / 2 + Math.sin(c / 18 * Math.PI * 2) * (700 + Math.random() * 200) }); confetti.alpha = 0.7 + Math.random() * 0.2; homeOverlay.addChild(confetti); } // Add playful falling shapes as icons around the title var iconY = GAME_HEIGHT / 2 - 370; var iconSpacing = 260; var iconIds = ['shapeRed', 'shapeBlue', 'shapeGreen', 'shapeYellow', 'shapePurple']; for (var i = 0; i < iconIds.length; i++) { var icon = LK.getAsset(iconIds[i], { anchorX: 0.5, anchorY: 0.5, scaleX: 1.1, scaleY: 1.1, x: GAME_WIDTH / 2 - iconSpacing * 2 + iconSpacing * i, y: iconY }); icon.alpha = 0.93; homeOverlay.addChild(icon); } // Main title with a friendlier, rounded font and a subtle shadow var homeTitleShadow = new Text2('Catch the Color!', { size: 190, fill: 0x4c7bff, font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif" }); homeTitleShadow.anchor.set(0.5, 0.5); homeTitleShadow.x = GAME_WIDTH / 2 + 12; homeTitleShadow.y = GAME_HEIGHT / 2 - 160; homeTitleShadow.alpha = 0.22; homeOverlay.addChild(homeTitleShadow); var homeTitle = new Text2('Catch the Color!', { size: 190, fill: 0x333399, font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif" }); homeTitle.anchor.set(0.5, 0.5); homeTitle.x = GAME_WIDTH / 2; homeTitle.y = GAME_HEIGHT / 2 - 170; homeOverlay.addChild(homeTitle); // Playful subtitle var homeSubtitle = new Text2('A colorful game for everyone!', { size: 74, fill: 0x4c7bff, font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif" }); homeSubtitle.anchor.set(0.5, 0.5); homeSubtitle.x = GAME_WIDTH / 2 + 10; homeSubtitle.y = GAME_HEIGHT / 2 - 20; homeOverlay.addChild(homeSubtitle); // Description with a friendlier font and more spacing var homeDesc = new Text2('Catch only the target color!\nMiss or catch wrong color = lose a life.', { size: 66, fill: 0x333333, font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif" }); homeDesc.anchor.set(0.5, 0.5); homeDesc.x = GAME_WIDTH / 2; homeDesc.y = GAME_HEIGHT / 2 + 130; homeOverlay.addChild(homeDesc); // Play button with a soft colored shadow and rounded font var playBtnShadow = new Text2('PLAY', { size: 190, fill: 0x4c7bff, font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif" }); playBtnShadow.anchor.set(0.5, 0.5); playBtnShadow.x = GAME_WIDTH / 2 + 16; playBtnShadow.y = GAME_HEIGHT / 2 + 220 + 118; playBtnShadow.alpha = 0.22; homeOverlay.addChild(playBtnShadow); var playBtn = new Text2('PLAY', { size: 190, fill: 0xff4c4c, font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif" }); playBtn.anchor.set(0.5, 0.5); playBtn.x = GAME_WIDTH / 2; playBtn.y = GAME_HEIGHT / 2 + 220 + 128; playBtn.interactive = true; playBtn.buttonMode = true; homeOverlay.addChild(playBtn); homeOverlay.visible = true; game.addChild(homeOverlay); // Block game input until play var gameStarted = false; // Play button handler playBtn.down = function (x, y, obj) { homeOverlay.visible = false; gameStarted = true; // Show target color text when game starts targetColorTxt.visible = true; // Show player, score, lives, and high score when game starts player.visible = true; scoreTxt.visible = true; livesTxt.visible = true; highScoreTxt.visible = true; // Start music when game starts LK.playMusic('bgmusic'); }; // Target color logic var colorList = [{ id: 'shapeRed', name: 'Red' }, { id: 'shapeBlue', name: 'Blue' }, { id: 'shapeGreen', name: 'Green' }, { id: 'shapeYellow', name: 'Yellow' }, { id: 'shapePurple', name: 'Purple' }]; var targetColorIdx = Math.floor(Math.random() * colorList.length); var targetColor = colorList[targetColorIdx].id; var targetColorName = colorList[targetColorIdx].name; var targetColorTimer = null; // Target color display (centered) var targetColorTxt = new Text2('Catch: ' + targetColorName, { size: 140, fill: 0x333333 }); targetColorTxt.anchor.set(0.5, 0.5); targetColorTxt.x = LK.gui.center.width / 2; targetColorTxt.y = LK.gui.center.height / 2 - 100; LK.gui.center.addChild(targetColorTxt); // Hide target color text on home screen targetColorTxt.visible = false; // Hide player, score, lives, and high score on home screen // Only set player.visible if player is already defined if (typeof player !== "undefined" && player) { player.visible = false; } if (typeof scoreTxt !== "undefined" && scoreTxt) { scoreTxt.visible = false; } if (typeof livesTxt !== "undefined" && livesTxt) { livesTxt.visible = false; } if (typeof highScoreTxt !== "undefined" && highScoreTxt) { highScoreTxt.visible = false; } // Score display var scoreTxt = new Text2('Score: 0', { size: 120, fill: 0x222222 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // High score display var highScore = storage.highScore || 0; var highScoreTxt = new Text2('High Score: ' + highScore, { size: 50, fill: 0x888800 }); highScoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(highScoreTxt); // Lives display var livesTxt = new Text2('Lives: ' + MAX_LIVES, { size: 90, fill: 0x444444 }); livesTxt.anchor.set(0.5, 0); LK.gui.top.addChild(livesTxt); livesTxt.y = 130; // Center score, high score, and lives horizontally, but not in the top 100px scoreTxt.x = LK.gui.top.width / 2; scoreTxt.y = 20; highScoreTxt.x = LK.gui.top.width / 2; highScoreTxt.y = 220; livesTxt.x = LK.gui.top.width / 2; // Helper to update target color function updateTargetColor() { var prevIdx = targetColorIdx; // Pick a new color different from the last do { targetColorIdx = Math.floor(Math.random() * colorList.length); } while (targetColorIdx === prevIdx && colorList.length > 1); targetColor = colorList[targetColorIdx].id; targetColorName = colorList[targetColorIdx].name; targetColorTxt.setText('Catch: ' + targetColorName); } // Start/restart the 10s timer for target color function startTargetColorTimer() { if (targetColorTimer) { LK.clearInterval(targetColorTimer); } targetColorTimer = LK.setInterval(function () { updateTargetColor(); }, 10000); } startTargetColorTimer(); // Start music LK.playMusic('bgmusic'); // Create player player = new Player(); game.addChild(player); player.x = GAME_WIDTH / 2; player.y = PLAYER_Y; // Hide player, score, lives, and high score on home screen player.visible = false; scoreTxt.visible = false; livesTxt.visible = false; highScoreTxt.visible = false; // Helper: clamp player within screen function clampPlayerX(x) { var halfW = player.width * 0.5; if (x < halfW) { return halfW; } if (x > GAME_WIDTH - halfW) { return GAME_WIDTH - halfW; } return x; } // Touch/move handling function handleMove(x, y, obj) { if (!gameStarted || homeOverlay.visible) { return; } if (dragNode) { // Clamp to screen dragNode.x = clampPlayerX(x); lastTouchX = dragNode.x; } } game.move = handleMove; game.down = function (x, y, obj) { if (!gameStarted || homeOverlay.visible) { return; } // Only allow drag if touch is near player (within 200px vertically) if (y > player.y - 200) { dragNode = player; handleMove(x, y, obj); } }; game.up = function (x, y, obj) { if (!gameStarted || homeOverlay.visible) { return; } dragNode = null; }; // Spawn a new falling shape function spawnShape() { var shape = new FallingShape(); // Apply speed boost if any if (typeof game.shapeSpeedBoost !== "undefined") { shape.speed += game.shapeSpeedBoost; } // Random X within bounds shape.x = SHAPE_MIN_X + Math.random() * (SHAPE_MAX_X - SHAPE_MIN_X); shape.y = -100; fallingShapes.push(shape); game.addChild(shape); } // Animate positive feedback function animateCatch(shape) { tween(shape, { scaleX: 1.4, scaleY: 1.4, alpha: 0 }, { duration: 350, easing: tween.easeOut, onFinish: function onFinish() { shape.destroy(); } }); } // Animate negative feedback function animateMiss(shape) { tween(shape, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { shape.destroy(); } }); } // Update score/lives display function updateScoreLives() { scoreTxt.setText('Score: ' + score); livesTxt.setText('Lives: ' + lives); if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('High Score: ' + highScore); } } // Main game update loop game.update = function () { // Block all game logic if home screen is visible or game not started if (!gameStarted || homeOverlay.visible) { return; } // Increase speed every time score reaches a new multiple of 10 (but only once per threshold) if (typeof game.lastSpeedScore === "undefined") { game.lastSpeedScore = 0; } if (score >= 3 && score % 3 === 0 && game.lastSpeedScore !== score) { // Increase speed of new shapes if (typeof game.shapeSpeedBoost === "undefined") { game.shapeSpeedBoost = 0; } game.shapeSpeedBoost += 2; // Increase by 2 pixels per tick per 10 points game.lastSpeedScore = score; } // Increase number of falling shapes every time score increases by 3 if (score >= 3 && score % 3 === 0 && lastShapeScore !== score) { shapesPerSpawn += 1; lastShapeScore = score; } // Spawn shapes at interval if (LK.ticks % SHAPE_SPAWN_INTERVAL === 0) { for (var s = 0; s < shapesPerSpawn; s++) { spawnShape(); } } // Update all falling shapes for (var i = fallingShapes.length - 1; i >= 0; i--) { var shape = fallingShapes[i]; shape.update(); // Check for catch (simple AABB) if (!shape.caught && !shape.missed) { var dx = Math.abs(shape.x - player.x); var dy = Math.abs(shape.y - player.y); var catchW = player.width * 0.5 + shape.radius * 0.7; var catchH = player.height * 0.5 + shape.radius * 0.7; if (dx < catchW && dy < catchH) { // Caught! shape.caught = true; if (shape.shapeId === targetColor) { // Correct color score += 1; updateScoreLives(); LK.getSound('catch').play(); player.flash(); animateCatch(shape); fallingShapes.splice(i, 1); // Win condition if (score >= TARGET_SCORE) { LK.effects.flashScreen(0x00ff00, 800); LK.showYouWin(); return; } } else { // Wrong color // Only penalize if the shape's color is mentioned in the target color text var isMentioned = false; for (var c = 0; c < colorList.length; c++) { if (shape.shapeId === colorList[c].id) { isMentioned = true; break; } } if (isMentioned) { lives -= 1; updateScoreLives(); LK.getSound('miss').play(); player.flash(); animateMiss(shape); fallingShapes.splice(i, 1); // Lose condition if (lives <= 0) { LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } } else { // Not a mentioned color, just remove the shape with no penalty animateMiss(shape); fallingShapes.splice(i, 1); } } continue; } } // Check for miss (off bottom) if (!shape.caught && !shape.missed && shape.y > GAME_HEIGHT + 100) { shape.missed = true; // Only penalize if the shape's color is mentioned in the target color text AND is the current target color var isMentioned = false; for (var c = 0; c < colorList.length; c++) { if (shape.shapeId === colorList[c].id) { isMentioned = true; break; } } // Only decrease lives if the missed shape is the current target color if (isMentioned && shape.shapeId === targetColor) { lives -= 1; updateScoreLives(); LK.getSound('miss').play(); animateMiss(shape); fallingShapes.splice(i, 1); // Lose condition if (lives <= 0) { LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } } else { // Not a mentioned color or not the target color, just remove the shape with no penalty animateMiss(shape); fallingShapes.splice(i, 1); } } } }; // On game reset, clear state game.on('destroy', function () { fallingShapes = []; score = 0; lives = MAX_LIVES; dragNode = null; lastTouchX = 0; LK.stopMusic(); // Reset target color and timer if (targetColorTimer) { LK.clearInterval(targetColorTimer); } updateTargetColor(); startTargetColorTimer(); targetColorTxt.setText('Catch: ' + targetColorName); // Update high score display from storage in case it changed highScore = storage.highScore || 0; highScoreTxt.setText('High Score: ' + highScore); // Show home screen again homeOverlay.visible = true; gameStarted = false; // Hide target color text on home screen targetColorTxt.visible = false; });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// FallingShape class
var FallingShape = Container.expand(function () {
var self = Container.call(this);
// Pick a random color/shape
var colorList = ['shapeRed', 'shapeBlue', 'shapeGreen', 'shapeYellow', 'shapePurple'];
var colorIdx = Math.floor(Math.random() * colorList.length);
self.shapeId = colorList[colorIdx];
// Attach the shape asset
var shape = self.attachAsset(self.shapeId, {
anchorX: 0.5,
anchorY: 0.5
});
// Set speed (randomize a bit for variety)
self.speed = 12 + Math.floor(Math.random() * 6);
// For collision detection
self.radius = shape.width * 0.5;
// For state tracking
self.caught = false;
self.missed = false;
// Update method called every tick
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
// Attach player asset
var playerShape = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// For collision detection
self.width = playerShape.width;
self.height = playerShape.height;
// For touch feedback
self.flash = function () {
tween(self, {
alpha: 0.5
}, {
duration: 80,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 120
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xf0f8ff // Light blue background
});
/****
* Game Code
****/
// Use a neutral (gray) color for the catching bar asset, not a color from the falling shapes
// Game constants
// Shapes for falling objects (different colors)
// Player character
// Sound effects
// Music
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLAYER_Y = GAME_HEIGHT - 180;
var SHAPE_SPAWN_INTERVAL = 48; // Ticks (0.8s at 60fps)
var SHAPE_MIN_X = 120;
var SHAPE_MAX_X = GAME_WIDTH - 120;
var TARGET_SCORE = 20;
var MAX_LIVES = 5;
// Game state
var player;
var fallingShapes = [];
var score = 0;
var lives = MAX_LIVES;
var dragNode = null;
var lastTouchX = 0;
// Track how many shapes to spawn per interval (difficulty)
var shapesPerSpawn = 1;
var lastShapeScore = 0;
// Home screen overlay
var homeOverlay = new Container();
// Add "confetti" shapes for extra playfulness
for (var c = 0; c < 18; c++) {
var confettiColors = [0xff4c4c, 0x4c7bff, 0x4cff7b, 0xfff94c, 0xb44cff];
var confettiId = ['shapeRed', 'shapeBlue', 'shapeGreen', 'shapeYellow', 'shapePurple'][Math.floor(Math.random() * 5)];
var confetti = LK.getAsset(confettiId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7 + Math.random() * 0.5,
scaleY: 0.7 + Math.random() * 0.5,
x: GAME_WIDTH / 2 + Math.cos(c / 18 * Math.PI * 2) * (700 + Math.random() * 200),
y: GAME_HEIGHT / 2 + Math.sin(c / 18 * Math.PI * 2) * (700 + Math.random() * 200)
});
confetti.alpha = 0.7 + Math.random() * 0.2;
homeOverlay.addChild(confetti);
}
// Add playful falling shapes as icons around the title
var iconY = GAME_HEIGHT / 2 - 370;
var iconSpacing = 260;
var iconIds = ['shapeRed', 'shapeBlue', 'shapeGreen', 'shapeYellow', 'shapePurple'];
for (var i = 0; i < iconIds.length; i++) {
var icon = LK.getAsset(iconIds[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.1,
scaleY: 1.1,
x: GAME_WIDTH / 2 - iconSpacing * 2 + iconSpacing * i,
y: iconY
});
icon.alpha = 0.93;
homeOverlay.addChild(icon);
}
// Main title with a friendlier, rounded font and a subtle shadow
var homeTitleShadow = new Text2('Catch the Color!', {
size: 190,
fill: 0x4c7bff,
font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif"
});
homeTitleShadow.anchor.set(0.5, 0.5);
homeTitleShadow.x = GAME_WIDTH / 2 + 12;
homeTitleShadow.y = GAME_HEIGHT / 2 - 160;
homeTitleShadow.alpha = 0.22;
homeOverlay.addChild(homeTitleShadow);
var homeTitle = new Text2('Catch the Color!', {
size: 190,
fill: 0x333399,
font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif"
});
homeTitle.anchor.set(0.5, 0.5);
homeTitle.x = GAME_WIDTH / 2;
homeTitle.y = GAME_HEIGHT / 2 - 170;
homeOverlay.addChild(homeTitle);
// Playful subtitle
var homeSubtitle = new Text2('A colorful game for everyone!', {
size: 74,
fill: 0x4c7bff,
font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif"
});
homeSubtitle.anchor.set(0.5, 0.5);
homeSubtitle.x = GAME_WIDTH / 2 + 10;
homeSubtitle.y = GAME_HEIGHT / 2 - 20;
homeOverlay.addChild(homeSubtitle);
// Description with a friendlier font and more spacing
var homeDesc = new Text2('Catch only the target color!\nMiss or catch wrong color = lose a life.', {
size: 66,
fill: 0x333333,
font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif"
});
homeDesc.anchor.set(0.5, 0.5);
homeDesc.x = GAME_WIDTH / 2;
homeDesc.y = GAME_HEIGHT / 2 + 130;
homeOverlay.addChild(homeDesc);
// Play button with a soft colored shadow and rounded font
var playBtnShadow = new Text2('PLAY', {
size: 190,
fill: 0x4c7bff,
font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif"
});
playBtnShadow.anchor.set(0.5, 0.5);
playBtnShadow.x = GAME_WIDTH / 2 + 16;
playBtnShadow.y = GAME_HEIGHT / 2 + 220 + 118;
playBtnShadow.alpha = 0.22;
homeOverlay.addChild(playBtnShadow);
var playBtn = new Text2('PLAY', {
size: 190,
fill: 0xff4c4c,
font: "'Comic Sans MS', 'Comic Sans', 'Arial Rounded MT Bold', 'GillSans', 'Arial', sans-serif"
});
playBtn.anchor.set(0.5, 0.5);
playBtn.x = GAME_WIDTH / 2;
playBtn.y = GAME_HEIGHT / 2 + 220 + 128;
playBtn.interactive = true;
playBtn.buttonMode = true;
homeOverlay.addChild(playBtn);
homeOverlay.visible = true;
game.addChild(homeOverlay);
// Block game input until play
var gameStarted = false;
// Play button handler
playBtn.down = function (x, y, obj) {
homeOverlay.visible = false;
gameStarted = true;
// Show target color text when game starts
targetColorTxt.visible = true;
// Show player, score, lives, and high score when game starts
player.visible = true;
scoreTxt.visible = true;
livesTxt.visible = true;
highScoreTxt.visible = true;
// Start music when game starts
LK.playMusic('bgmusic');
};
// Target color logic
var colorList = [{
id: 'shapeRed',
name: 'Red'
}, {
id: 'shapeBlue',
name: 'Blue'
}, {
id: 'shapeGreen',
name: 'Green'
}, {
id: 'shapeYellow',
name: 'Yellow'
}, {
id: 'shapePurple',
name: 'Purple'
}];
var targetColorIdx = Math.floor(Math.random() * colorList.length);
var targetColor = colorList[targetColorIdx].id;
var targetColorName = colorList[targetColorIdx].name;
var targetColorTimer = null;
// Target color display (centered)
var targetColorTxt = new Text2('Catch: ' + targetColorName, {
size: 140,
fill: 0x333333
});
targetColorTxt.anchor.set(0.5, 0.5);
targetColorTxt.x = LK.gui.center.width / 2;
targetColorTxt.y = LK.gui.center.height / 2 - 100;
LK.gui.center.addChild(targetColorTxt);
// Hide target color text on home screen
targetColorTxt.visible = false;
// Hide player, score, lives, and high score on home screen
// Only set player.visible if player is already defined
if (typeof player !== "undefined" && player) {
player.visible = false;
}
if (typeof scoreTxt !== "undefined" && scoreTxt) {
scoreTxt.visible = false;
}
if (typeof livesTxt !== "undefined" && livesTxt) {
livesTxt.visible = false;
}
if (typeof highScoreTxt !== "undefined" && highScoreTxt) {
highScoreTxt.visible = false;
}
// Score display
var scoreTxt = new Text2('Score: 0', {
size: 120,
fill: 0x222222
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score display
var highScore = storage.highScore || 0;
var highScoreTxt = new Text2('High Score: ' + highScore, {
size: 50,
fill: 0x888800
});
highScoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(highScoreTxt);
// Lives display
var livesTxt = new Text2('Lives: ' + MAX_LIVES, {
size: 90,
fill: 0x444444
});
livesTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(livesTxt);
livesTxt.y = 130;
// Center score, high score, and lives horizontally, but not in the top 100px
scoreTxt.x = LK.gui.top.width / 2;
scoreTxt.y = 20;
highScoreTxt.x = LK.gui.top.width / 2;
highScoreTxt.y = 220;
livesTxt.x = LK.gui.top.width / 2;
// Helper to update target color
function updateTargetColor() {
var prevIdx = targetColorIdx;
// Pick a new color different from the last
do {
targetColorIdx = Math.floor(Math.random() * colorList.length);
} while (targetColorIdx === prevIdx && colorList.length > 1);
targetColor = colorList[targetColorIdx].id;
targetColorName = colorList[targetColorIdx].name;
targetColorTxt.setText('Catch: ' + targetColorName);
}
// Start/restart the 10s timer for target color
function startTargetColorTimer() {
if (targetColorTimer) {
LK.clearInterval(targetColorTimer);
}
targetColorTimer = LK.setInterval(function () {
updateTargetColor();
}, 10000);
}
startTargetColorTimer();
// Start music
LK.playMusic('bgmusic');
// Create player
player = new Player();
game.addChild(player);
player.x = GAME_WIDTH / 2;
player.y = PLAYER_Y;
// Hide player, score, lives, and high score on home screen
player.visible = false;
scoreTxt.visible = false;
livesTxt.visible = false;
highScoreTxt.visible = false;
// Helper: clamp player within screen
function clampPlayerX(x) {
var halfW = player.width * 0.5;
if (x < halfW) {
return halfW;
}
if (x > GAME_WIDTH - halfW) {
return GAME_WIDTH - halfW;
}
return x;
}
// Touch/move handling
function handleMove(x, y, obj) {
if (!gameStarted || homeOverlay.visible) {
return;
}
if (dragNode) {
// Clamp to screen
dragNode.x = clampPlayerX(x);
lastTouchX = dragNode.x;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (!gameStarted || homeOverlay.visible) {
return;
}
// Only allow drag if touch is near player (within 200px vertically)
if (y > player.y - 200) {
dragNode = player;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
if (!gameStarted || homeOverlay.visible) {
return;
}
dragNode = null;
};
// Spawn a new falling shape
function spawnShape() {
var shape = new FallingShape();
// Apply speed boost if any
if (typeof game.shapeSpeedBoost !== "undefined") {
shape.speed += game.shapeSpeedBoost;
}
// Random X within bounds
shape.x = SHAPE_MIN_X + Math.random() * (SHAPE_MAX_X - SHAPE_MIN_X);
shape.y = -100;
fallingShapes.push(shape);
game.addChild(shape);
}
// Animate positive feedback
function animateCatch(shape) {
tween(shape, {
scaleX: 1.4,
scaleY: 1.4,
alpha: 0
}, {
duration: 350,
easing: tween.easeOut,
onFinish: function onFinish() {
shape.destroy();
}
});
}
// Animate negative feedback
function animateMiss(shape) {
tween(shape, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
shape.destroy();
}
});
}
// Update score/lives display
function updateScoreLives() {
scoreTxt.setText('Score: ' + score);
livesTxt.setText('Lives: ' + lives);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('High Score: ' + highScore);
}
}
// Main game update loop
game.update = function () {
// Block all game logic if home screen is visible or game not started
if (!gameStarted || homeOverlay.visible) {
return;
}
// Increase speed every time score reaches a new multiple of 10 (but only once per threshold)
if (typeof game.lastSpeedScore === "undefined") {
game.lastSpeedScore = 0;
}
if (score >= 3 && score % 3 === 0 && game.lastSpeedScore !== score) {
// Increase speed of new shapes
if (typeof game.shapeSpeedBoost === "undefined") {
game.shapeSpeedBoost = 0;
}
game.shapeSpeedBoost += 2; // Increase by 2 pixels per tick per 10 points
game.lastSpeedScore = score;
}
// Increase number of falling shapes every time score increases by 3
if (score >= 3 && score % 3 === 0 && lastShapeScore !== score) {
shapesPerSpawn += 1;
lastShapeScore = score;
}
// Spawn shapes at interval
if (LK.ticks % SHAPE_SPAWN_INTERVAL === 0) {
for (var s = 0; s < shapesPerSpawn; s++) {
spawnShape();
}
}
// Update all falling shapes
for (var i = fallingShapes.length - 1; i >= 0; i--) {
var shape = fallingShapes[i];
shape.update();
// Check for catch (simple AABB)
if (!shape.caught && !shape.missed) {
var dx = Math.abs(shape.x - player.x);
var dy = Math.abs(shape.y - player.y);
var catchW = player.width * 0.5 + shape.radius * 0.7;
var catchH = player.height * 0.5 + shape.radius * 0.7;
if (dx < catchW && dy < catchH) {
// Caught!
shape.caught = true;
if (shape.shapeId === targetColor) {
// Correct color
score += 1;
updateScoreLives();
LK.getSound('catch').play();
player.flash();
animateCatch(shape);
fallingShapes.splice(i, 1);
// Win condition
if (score >= TARGET_SCORE) {
LK.effects.flashScreen(0x00ff00, 800);
LK.showYouWin();
return;
}
} else {
// Wrong color
// Only penalize if the shape's color is mentioned in the target color text
var isMentioned = false;
for (var c = 0; c < colorList.length; c++) {
if (shape.shapeId === colorList[c].id) {
isMentioned = true;
break;
}
}
if (isMentioned) {
lives -= 1;
updateScoreLives();
LK.getSound('miss').play();
player.flash();
animateMiss(shape);
fallingShapes.splice(i, 1);
// Lose condition
if (lives <= 0) {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
} else {
// Not a mentioned color, just remove the shape with no penalty
animateMiss(shape);
fallingShapes.splice(i, 1);
}
}
continue;
}
}
// Check for miss (off bottom)
if (!shape.caught && !shape.missed && shape.y > GAME_HEIGHT + 100) {
shape.missed = true;
// Only penalize if the shape's color is mentioned in the target color text AND is the current target color
var isMentioned = false;
for (var c = 0; c < colorList.length; c++) {
if (shape.shapeId === colorList[c].id) {
isMentioned = true;
break;
}
}
// Only decrease lives if the missed shape is the current target color
if (isMentioned && shape.shapeId === targetColor) {
lives -= 1;
updateScoreLives();
LK.getSound('miss').play();
animateMiss(shape);
fallingShapes.splice(i, 1);
// Lose condition
if (lives <= 0) {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
} else {
// Not a mentioned color or not the target color, just remove the shape with no penalty
animateMiss(shape);
fallingShapes.splice(i, 1);
}
}
}
};
// On game reset, clear state
game.on('destroy', function () {
fallingShapes = [];
score = 0;
lives = MAX_LIVES;
dragNode = null;
lastTouchX = 0;
LK.stopMusic();
// Reset target color and timer
if (targetColorTimer) {
LK.clearInterval(targetColorTimer);
}
updateTargetColor();
startTargetColorTimer();
targetColorTxt.setText('Catch: ' + targetColorName);
// Update high score display from storage in case it changed
highScore = storage.highScore || 0;
highScoreTxt.setText('High Score: ' + highScore);
// Show home screen again
homeOverlay.visible = true;
gameStarted = false;
// Hide target color text on home screen
targetColorTxt.visible = false;
});