/****
* 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;
});