/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// Ball class: handles color, rotation, and animation
var ColorBall = Container.expand(function () {
var self = Container.call(this);
// Color order: 0=Red, 1=Blue, 2=Green
self.colors = ['red', 'blue', 'green'];
self.colorIdx = 0;
// --- Add shadow assets for each color, matching the ball shape and style ---
self.shadowAssets = {
red: self.attachAsset('ballRed', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: self.ballAssets && self.ballAssets.red ? self.ballAssets.red.height * 0.045 : 22,
scaleX: 1.04,
scaleY: 1.04,
alpha: 0.32,
tint: 0x000000
}),
blue: self.attachAsset('ballBlue', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: self.ballAssets && self.ballAssets.blue ? self.ballAssets.blue.height * 0.045 : 22,
scaleX: 1.04,
scaleY: 1.04,
alpha: 0.32,
tint: 0x000000
}),
green: self.attachAsset('ballGreen', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: self.ballAssets && self.ballAssets.green ? self.ballAssets.green.height * 0.045 : 22,
scaleX: 1.04,
scaleY: 1.04,
alpha: 0.32,
tint: 0x000000
})
};
// Attach all color assets, only one visible at a time
self.ballAssets = {
red: self.attachAsset('ballRed', {
anchorX: 0.5,
anchorY: 0.5
}),
blue: self.attachAsset('ballBlue', {
anchorX: 0.5,
anchorY: 0.5
}),
green: self.attachAsset('ballGreen', {
anchorX: 0.5,
anchorY: 0.5
})
};
// Place shadow behind the ball
self.addChildAt(self.shadowAssets.red, 0);
self.addChildAt(self.shadowAssets.blue, 0);
self.addChildAt(self.shadowAssets.green, 0);
self.setColor = function (color) {
self.color = color;
self.ballAssets.red.visible = color === 'red';
self.ballAssets.blue.visible = color === 'blue';
self.ballAssets.green.visible = color === 'green';
// Show only the shadow for the current color
self.shadowAssets.red.visible = color === 'red';
self.shadowAssets.blue.visible = color === 'blue';
self.shadowAssets.green.visible = color === 'green';
};
self.setColor('red');
self.rotationSpeed = 0.02; // radians per tick
self.update = function () {
// Defensive: ensure rotationSpeed is always defined
if (typeof self.rotationSpeed !== "number") self.rotationSpeed = 0.02;
self.rotation += self.rotationSpeed;
};
// Animate color change (optional: flash or scale)
self.animateColorChange = function () {
tween(self, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.cubicIn
});
}
});
};
return self;
});
// Button class: handles color, label, and press feedback
var ColorButton = Container.expand(function () {
var self = Container.call(this);
// color: 'red', 'blue', 'green'
self.setColor = function (color) {
self.color = color;
if (self.asset) {
self.removeChild(self.asset);
}
if (self.label) {
self.removeChild(self.label);
}
if (self.hitbox) {
self.removeChild(self.hitbox);
}
var assetId = color === 'red' ? 'btnRed' : color === 'blue' ? 'btnBlue' : 'btnGreen';
self.asset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add realistic shadow by duplicating the button asset, tinting and offsetting it
if (self.shadow) {
self.removeChild(self.shadow);
}
var shadowAssetId = assetId;
self.shadow = self.attachAsset(shadowAssetId, {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: self.asset.height * 0.06,
// offset shadow downward (closer)
scaleX: 1.03,
scaleY: 1.03,
alpha: 0.32,
tint: 0x000000
});
self.shadow.zIndex = -2;
self.addChild(self.shadow);
self.addChild(self.asset);
};
// Animate press feedback
self.animatePress = function () {
tween(self.asset, {
scaleX: 0.93,
scaleY: 0.93
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.asset, {
scaleX: 1,
scaleY: 1
}, {
duration: 80,
easing: tween.cubicIn
});
}
});
};
// For hit detection
self.containsPoint = function (x, y) {
var local = self.toLocal({
x: x,
y: y
});
return local.x > -self.hitbox.width / 2 && local.x < self.hitbox.width / 2 && local.y > -self.hitbox.height / 2 && local.y < self.hitbox.height / 2;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xffb638
});
/****
* Game Code
****/
// btnBlue image
// btnGreen image
// btnRed image
// Health bar assets
// --- Game State ---
// Ball colors
// Button colors
// Sound
// Music (optional, but initialized for future use)
var ball;
var buttons = [];
var colorOrder = ['red', 'blue', 'green'];
var currentColor = 'red';
var score = 0;
var highScore = storage.highScore || 0;
var timeLimit = 5000; // ms, initial time to respond (5 seconds)
var minTimeLimit = 5000; // ms, minimum allowed (5 seconds)
var timeDecrease = 80; // ms, decrease per correct
var rotationSpeed = 0.02; // initial
var maxRotationSpeed = 0.09;
var rotationIncrease = 0.008;
var waitingForInput = true;
var responseTimer = null;
var lastTapTime = 0;
// --- UI Elements ---
// Add shadow for score text (centered, slightly offset, behind main score)
var scoreShadowTxt = new Text2('0', {
size: 120,
fill: "#000",
alpha: 0.32
});
scoreShadowTxt.anchor.set(0.5, 0);
scoreShadowTxt.y = 60 + 3; // offset shadow downward (closer)
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFF4500
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.y = 60;
// Add scoreTxt after timer and hearts to ensure it is always on top
LK.setTimeout(function () {
LK.gui.top.addChild(scoreShadowTxt);
LK.gui.top.addChild(scoreTxt);
}, 0);
var highScoreTxt = new Text2('BEST: ' + highScore, {
size: 60,
fill: 0xFF4500,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.y = 190;
LK.gui.top.addChild(highScoreTxt);
// --- Timer Bar (under score) ---
// --- Timer Bar (under score, in GUI) ---
var timerBar = LK.getAsset('timerBar', {
width: 900,
height: 60,
color: 0xffffff,
anchorX: 0.5,
anchorY: 0.0
});
timerBar.x = 0.5; // relative to gui width
timerBar.y = scoreTxt.y + scoreTxt.height + 70; // 70px below score (moved further down)
timerBar.alpha = 0.18;
timerBar.isGUI = true;
LK.gui.top.addChild(timerBar);
var timerBarFill = LK.getAsset('timerBarFill', {
width: 900,
height: 60,
color: 0x2ecc40,
anchorX: 0.5,
anchorY: 0.0
});
timerBarFill.x = 0.5; // relative to gui width
timerBarFill.y = scoreTxt.y + scoreTxt.height + 70; // 70px below score (moved further down)
timerBarFill.isGUI = true;
LK.gui.top.addChild(timerBarFill);
// --- Health Bars (under timer, in GUI) ---
var health = 3;
var maxHealth = 3;
var healthBars = [];
var healthBarSpacing = 130;
var healthBarWidth = 120;
var healthBarHeight = 120;
var healthBarAssets = [{
id: 'healthBarRed',
color: 0xd83318
},
// Red
{
id: 'healthBarBlue',
color: 0x187ad8
},
// Blue
{
id: 'healthBarGreen',
color: 0x2ecc40
} // Green
];
var healthBarY = timerBar.y + timerBar.height + 30; // 30px below timer bar
// Calculate total width of all health bars and starting x for centering
var totalHealthWidth = (maxHealth - 1) * healthBarSpacing;
var healthStartX = 0.5 + -(totalHealthWidth / 2); // 0.5 is center in LK.gui coordinates
for (var i = 0; i < maxHealth; i++) {
var assetInfo = healthBarAssets[i];
var bar = LK.getAsset(assetInfo.id, {
width: healthBarWidth,
height: healthBarHeight,
color: assetInfo.color,
anchorX: 0.5,
anchorY: 0.0
});
// Center health bars under the timer
bar.x = healthStartX + i * healthBarSpacing;
bar.y = healthBarY;
bar.isGUI = true;
LK.gui.top.addChild(bar);
healthBars.push(bar);
}
// Helper to update health bar visuals
function updateHealthBars() {
for (var i = 0; i < maxHealth; i++) {
// Each bar is independent: show full alpha if "alive", faded if "lost"
healthBars[i].alpha = i < health ? 1 : 0.18;
}
}
updateHealthBars();
// --- Setup Ball ---
ball = new ColorBall();
ball.x = 2048 / 2;
ball.y = 1100;
game.addChild(ball);
// --- Setup Buttons ---
var btnSpacing = 480;
var btnY = 2732 - 320;
// Create btnRed
var btnRed = new ColorButton();
btnRed.setColor('red');
btnRed.x = 2048 / 2 - btnSpacing;
btnRed.y = btnY;
game.addChild(btnRed);
// Create btnBlue
var btnBlue = new ColorButton();
btnBlue.setColor('blue');
btnBlue.x = 2048 / 2;
btnBlue.y = btnY;
game.addChild(btnBlue);
// Create btnGreen
var btnGreen = new ColorButton();
btnGreen.setColor('green');
btnGreen.x = 2048 / 2 + btnSpacing;
btnGreen.y = btnY;
game.addChild(btnGreen);
// Place all buttons in the same positions as btnRed, btnBlue, btnGreen
buttons = [btnRed, btnBlue, btnGreen];
// Ensure all color buttons are positioned exactly as btnRed, btnBlue, btnGreen
buttons[0].x = btnRed.x;
buttons[0].y = btnRed.y;
buttons[1].x = btnBlue.x;
buttons[1].y = btnBlue.y;
buttons[2].x = btnGreen.x;
buttons[2].y = btnGreen.y;
// --- Helper Functions ---
function setScore(val) {
score = val;
scoreTxt.setText(score);
if (typeof scoreShadowTxt !== "undefined") {
scoreShadowTxt.setText(score);
}
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('BEST: ' + highScore);
}
}
function randomColor() {
// Never repeat the same color twice in a row
var idx = colorOrder.indexOf(currentColor);
var nextIdx = idx;
while (nextIdx === idx) {
nextIdx = Math.floor(Math.random() * 3);
}
return colorOrder[nextIdx];
}
function startNewRound() {
waitingForInput = true;
// Pick new color
currentColor = randomColor();
ball.setColor(currentColor);
ball.animateColorChange();
// Timer always starts from 5 seconds (5000ms) for each round, and decreases by 0.1s for each correct answer
timeLimit = 5000 - score * 100;
if (timeLimit < 500) {
timeLimit = 500; // minimum 0.5s to avoid negative/zero
}
// --- Smoothly increase rotation speed at every multiple of 10 points ---
var baseSpeed = 0.02;
var speedStep = 0.008;
var maxSpeed = 0.09;
var targetSpeed = baseSpeed + Math.floor(score / 10) * speedStep;
if (targetSpeed > maxSpeed) targetSpeed = maxSpeed;
// Only tween if the speed is changing
if (Math.abs(ball.rotationSpeed - targetSpeed) > 0.0001) {
tween.stop(ball, {
rotationSpeed: true
});
tween(ball, {
rotationSpeed: targetSpeed
}, {
duration: 700,
easing: tween.cubicInOut
});
}
rotationSpeed = targetSpeed;
ball.targetRotationSpeed = targetSpeed;
// Start timer
timerBarFill.width = 900;
if (responseTimer) {
LK.clearTimeout(responseTimer);
responseTimer = null;
}
responseTimer = LK.setTimeout(function () {
// Time's up
waitingForInput = false;
LK.effects.flashScreen(0xff0000, 600);
LK.getSound('wrong').play();
health--;
updateHealthBars();
if (health <= 0) {
endGame();
} else {
// Give player another chance, start new round after short delay
responseTimer = LK.setTimeout(function () {
startNewRound();
}, 700);
}
}, timeLimit);
lastTapTime = Date.now();
}
function endGame() {
if (responseTimer) {
LK.clearTimeout(responseTimer);
responseTimer = null;
}
waitingForInput = false;
// Flash ball red
LK.effects.flashObject(ball, 0xff0000, 600);
// Show game over (handled by LK)
LK.showGameOver();
}
// --- Input Handling ---
game.down = function (x, y, obj) {
if (!waitingForInput) {
return;
}
// Divide the screen into three vertical regions: left, middle, right
var region;
if (x < 2048 / 3) {
region = 'left';
} else if (x > 2048 * 2 / 3) {
region = 'right';
} else {
region = 'middle';
}
// Map region to color
var color;
if (region === 'left') {
color = 'red';
} else if (region === 'right') {
color = 'green';
} else {
color = 'blue';
}
// Find the button with this color (for animation/feedback)
var btn = null;
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].color === color) {
btn = buttons[i];
break;
}
}
if (btn) {
handleButtonPress(btn);
}
};
function handleButtonPress(btn) {
if (!waitingForInput) {
return;
}
btn.animatePress();
LK.effects.flashObject(btn.asset, 0xffffff, 120);
waitingForInput = false;
if (btn.color === currentColor) {
// Correct!
setScore(score + 1);
LK.getSound('correct').play();
// Animate ball
tween(ball, {
scaleX: 1.18,
scaleY: 1.18
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(ball, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.cubicIn
});
}
});
// Decrease timer by 0.1 seconds (100ms) for each correct answer
if (responseTimer) {
LK.clearTimeout(responseTimer);
responseTimer = null;
}
responseTimer = LK.setTimeout(function () {
startNewRound();
}, 220);
} else {
// Wrong!
LK.effects.flashScreen(0xff0000, 600);
LK.getSound('wrong').play();
if (responseTimer) {
LK.clearTimeout(responseTimer);
responseTimer = null;
}
health--;
updateHealthBars();
if (health <= 0) {
endGame();
} else {
// Decrease timer by 0.2 seconds (200ms) for each incorrect answer and reset to 3 seconds
timeLimit = 3000 - score * 100 - (maxHealth - health) * 200;
if (timeLimit < 500) {
timeLimit = 500; // minimum 0.5s to avoid negative/zero
}
// Give player another chance, start new round after short delay
responseTimer = LK.setTimeout(function () {
startNewRound();
}, 700);
}
}
}
// --- Game Update Loop ---
game.update = function () {
// Animate timer bar
if (waitingForInput) {
var elapsed = Date.now() - lastTapTime;
var frac = 1 - elapsed / timeLimit;
if (frac < 0) {
frac = 0;
}
timerBarFill.width = 900 * frac;
// Do not change timerBarFill color or tint here to prevent fading
} else {
timerBarFill.width = 0;
}
};
// --- Start Game ---
function startGame() {
LK.playMusic('g');
setScore(0);
timeLimit = 5000;
rotationSpeed = 0.02;
ball.rotationSpeed = rotationSpeed;
ball.targetRotationSpeed = rotationSpeed;
health = maxHealth;
updateHealthBars();
startNewRound();
}
startGame();
// --- Responsive UI (optional, handled by LK) ---
// --- Music (optional, not started by default) ---
// LK.playMusic('bgmusic', {fade: {start: 0, end: 1, duration: 1000}}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// Ball class: handles color, rotation, and animation
var ColorBall = Container.expand(function () {
var self = Container.call(this);
// Color order: 0=Red, 1=Blue, 2=Green
self.colors = ['red', 'blue', 'green'];
self.colorIdx = 0;
// --- Add shadow assets for each color, matching the ball shape and style ---
self.shadowAssets = {
red: self.attachAsset('ballRed', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: self.ballAssets && self.ballAssets.red ? self.ballAssets.red.height * 0.045 : 22,
scaleX: 1.04,
scaleY: 1.04,
alpha: 0.32,
tint: 0x000000
}),
blue: self.attachAsset('ballBlue', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: self.ballAssets && self.ballAssets.blue ? self.ballAssets.blue.height * 0.045 : 22,
scaleX: 1.04,
scaleY: 1.04,
alpha: 0.32,
tint: 0x000000
}),
green: self.attachAsset('ballGreen', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: self.ballAssets && self.ballAssets.green ? self.ballAssets.green.height * 0.045 : 22,
scaleX: 1.04,
scaleY: 1.04,
alpha: 0.32,
tint: 0x000000
})
};
// Attach all color assets, only one visible at a time
self.ballAssets = {
red: self.attachAsset('ballRed', {
anchorX: 0.5,
anchorY: 0.5
}),
blue: self.attachAsset('ballBlue', {
anchorX: 0.5,
anchorY: 0.5
}),
green: self.attachAsset('ballGreen', {
anchorX: 0.5,
anchorY: 0.5
})
};
// Place shadow behind the ball
self.addChildAt(self.shadowAssets.red, 0);
self.addChildAt(self.shadowAssets.blue, 0);
self.addChildAt(self.shadowAssets.green, 0);
self.setColor = function (color) {
self.color = color;
self.ballAssets.red.visible = color === 'red';
self.ballAssets.blue.visible = color === 'blue';
self.ballAssets.green.visible = color === 'green';
// Show only the shadow for the current color
self.shadowAssets.red.visible = color === 'red';
self.shadowAssets.blue.visible = color === 'blue';
self.shadowAssets.green.visible = color === 'green';
};
self.setColor('red');
self.rotationSpeed = 0.02; // radians per tick
self.update = function () {
// Defensive: ensure rotationSpeed is always defined
if (typeof self.rotationSpeed !== "number") self.rotationSpeed = 0.02;
self.rotation += self.rotationSpeed;
};
// Animate color change (optional: flash or scale)
self.animateColorChange = function () {
tween(self, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.cubicIn
});
}
});
};
return self;
});
// Button class: handles color, label, and press feedback
var ColorButton = Container.expand(function () {
var self = Container.call(this);
// color: 'red', 'blue', 'green'
self.setColor = function (color) {
self.color = color;
if (self.asset) {
self.removeChild(self.asset);
}
if (self.label) {
self.removeChild(self.label);
}
if (self.hitbox) {
self.removeChild(self.hitbox);
}
var assetId = color === 'red' ? 'btnRed' : color === 'blue' ? 'btnBlue' : 'btnGreen';
self.asset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add realistic shadow by duplicating the button asset, tinting and offsetting it
if (self.shadow) {
self.removeChild(self.shadow);
}
var shadowAssetId = assetId;
self.shadow = self.attachAsset(shadowAssetId, {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: self.asset.height * 0.06,
// offset shadow downward (closer)
scaleX: 1.03,
scaleY: 1.03,
alpha: 0.32,
tint: 0x000000
});
self.shadow.zIndex = -2;
self.addChild(self.shadow);
self.addChild(self.asset);
};
// Animate press feedback
self.animatePress = function () {
tween(self.asset, {
scaleX: 0.93,
scaleY: 0.93
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.asset, {
scaleX: 1,
scaleY: 1
}, {
duration: 80,
easing: tween.cubicIn
});
}
});
};
// For hit detection
self.containsPoint = function (x, y) {
var local = self.toLocal({
x: x,
y: y
});
return local.x > -self.hitbox.width / 2 && local.x < self.hitbox.width / 2 && local.y > -self.hitbox.height / 2 && local.y < self.hitbox.height / 2;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xffb638
});
/****
* Game Code
****/
// btnBlue image
// btnGreen image
// btnRed image
// Health bar assets
// --- Game State ---
// Ball colors
// Button colors
// Sound
// Music (optional, but initialized for future use)
var ball;
var buttons = [];
var colorOrder = ['red', 'blue', 'green'];
var currentColor = 'red';
var score = 0;
var highScore = storage.highScore || 0;
var timeLimit = 5000; // ms, initial time to respond (5 seconds)
var minTimeLimit = 5000; // ms, minimum allowed (5 seconds)
var timeDecrease = 80; // ms, decrease per correct
var rotationSpeed = 0.02; // initial
var maxRotationSpeed = 0.09;
var rotationIncrease = 0.008;
var waitingForInput = true;
var responseTimer = null;
var lastTapTime = 0;
// --- UI Elements ---
// Add shadow for score text (centered, slightly offset, behind main score)
var scoreShadowTxt = new Text2('0', {
size: 120,
fill: "#000",
alpha: 0.32
});
scoreShadowTxt.anchor.set(0.5, 0);
scoreShadowTxt.y = 60 + 3; // offset shadow downward (closer)
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFF4500
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.y = 60;
// Add scoreTxt after timer and hearts to ensure it is always on top
LK.setTimeout(function () {
LK.gui.top.addChild(scoreShadowTxt);
LK.gui.top.addChild(scoreTxt);
}, 0);
var highScoreTxt = new Text2('BEST: ' + highScore, {
size: 60,
fill: 0xFF4500,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.y = 190;
LK.gui.top.addChild(highScoreTxt);
// --- Timer Bar (under score) ---
// --- Timer Bar (under score, in GUI) ---
var timerBar = LK.getAsset('timerBar', {
width: 900,
height: 60,
color: 0xffffff,
anchorX: 0.5,
anchorY: 0.0
});
timerBar.x = 0.5; // relative to gui width
timerBar.y = scoreTxt.y + scoreTxt.height + 70; // 70px below score (moved further down)
timerBar.alpha = 0.18;
timerBar.isGUI = true;
LK.gui.top.addChild(timerBar);
var timerBarFill = LK.getAsset('timerBarFill', {
width: 900,
height: 60,
color: 0x2ecc40,
anchorX: 0.5,
anchorY: 0.0
});
timerBarFill.x = 0.5; // relative to gui width
timerBarFill.y = scoreTxt.y + scoreTxt.height + 70; // 70px below score (moved further down)
timerBarFill.isGUI = true;
LK.gui.top.addChild(timerBarFill);
// --- Health Bars (under timer, in GUI) ---
var health = 3;
var maxHealth = 3;
var healthBars = [];
var healthBarSpacing = 130;
var healthBarWidth = 120;
var healthBarHeight = 120;
var healthBarAssets = [{
id: 'healthBarRed',
color: 0xd83318
},
// Red
{
id: 'healthBarBlue',
color: 0x187ad8
},
// Blue
{
id: 'healthBarGreen',
color: 0x2ecc40
} // Green
];
var healthBarY = timerBar.y + timerBar.height + 30; // 30px below timer bar
// Calculate total width of all health bars and starting x for centering
var totalHealthWidth = (maxHealth - 1) * healthBarSpacing;
var healthStartX = 0.5 + -(totalHealthWidth / 2); // 0.5 is center in LK.gui coordinates
for (var i = 0; i < maxHealth; i++) {
var assetInfo = healthBarAssets[i];
var bar = LK.getAsset(assetInfo.id, {
width: healthBarWidth,
height: healthBarHeight,
color: assetInfo.color,
anchorX: 0.5,
anchorY: 0.0
});
// Center health bars under the timer
bar.x = healthStartX + i * healthBarSpacing;
bar.y = healthBarY;
bar.isGUI = true;
LK.gui.top.addChild(bar);
healthBars.push(bar);
}
// Helper to update health bar visuals
function updateHealthBars() {
for (var i = 0; i < maxHealth; i++) {
// Each bar is independent: show full alpha if "alive", faded if "lost"
healthBars[i].alpha = i < health ? 1 : 0.18;
}
}
updateHealthBars();
// --- Setup Ball ---
ball = new ColorBall();
ball.x = 2048 / 2;
ball.y = 1100;
game.addChild(ball);
// --- Setup Buttons ---
var btnSpacing = 480;
var btnY = 2732 - 320;
// Create btnRed
var btnRed = new ColorButton();
btnRed.setColor('red');
btnRed.x = 2048 / 2 - btnSpacing;
btnRed.y = btnY;
game.addChild(btnRed);
// Create btnBlue
var btnBlue = new ColorButton();
btnBlue.setColor('blue');
btnBlue.x = 2048 / 2;
btnBlue.y = btnY;
game.addChild(btnBlue);
// Create btnGreen
var btnGreen = new ColorButton();
btnGreen.setColor('green');
btnGreen.x = 2048 / 2 + btnSpacing;
btnGreen.y = btnY;
game.addChild(btnGreen);
// Place all buttons in the same positions as btnRed, btnBlue, btnGreen
buttons = [btnRed, btnBlue, btnGreen];
// Ensure all color buttons are positioned exactly as btnRed, btnBlue, btnGreen
buttons[0].x = btnRed.x;
buttons[0].y = btnRed.y;
buttons[1].x = btnBlue.x;
buttons[1].y = btnBlue.y;
buttons[2].x = btnGreen.x;
buttons[2].y = btnGreen.y;
// --- Helper Functions ---
function setScore(val) {
score = val;
scoreTxt.setText(score);
if (typeof scoreShadowTxt !== "undefined") {
scoreShadowTxt.setText(score);
}
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('BEST: ' + highScore);
}
}
function randomColor() {
// Never repeat the same color twice in a row
var idx = colorOrder.indexOf(currentColor);
var nextIdx = idx;
while (nextIdx === idx) {
nextIdx = Math.floor(Math.random() * 3);
}
return colorOrder[nextIdx];
}
function startNewRound() {
waitingForInput = true;
// Pick new color
currentColor = randomColor();
ball.setColor(currentColor);
ball.animateColorChange();
// Timer always starts from 5 seconds (5000ms) for each round, and decreases by 0.1s for each correct answer
timeLimit = 5000 - score * 100;
if (timeLimit < 500) {
timeLimit = 500; // minimum 0.5s to avoid negative/zero
}
// --- Smoothly increase rotation speed at every multiple of 10 points ---
var baseSpeed = 0.02;
var speedStep = 0.008;
var maxSpeed = 0.09;
var targetSpeed = baseSpeed + Math.floor(score / 10) * speedStep;
if (targetSpeed > maxSpeed) targetSpeed = maxSpeed;
// Only tween if the speed is changing
if (Math.abs(ball.rotationSpeed - targetSpeed) > 0.0001) {
tween.stop(ball, {
rotationSpeed: true
});
tween(ball, {
rotationSpeed: targetSpeed
}, {
duration: 700,
easing: tween.cubicInOut
});
}
rotationSpeed = targetSpeed;
ball.targetRotationSpeed = targetSpeed;
// Start timer
timerBarFill.width = 900;
if (responseTimer) {
LK.clearTimeout(responseTimer);
responseTimer = null;
}
responseTimer = LK.setTimeout(function () {
// Time's up
waitingForInput = false;
LK.effects.flashScreen(0xff0000, 600);
LK.getSound('wrong').play();
health--;
updateHealthBars();
if (health <= 0) {
endGame();
} else {
// Give player another chance, start new round after short delay
responseTimer = LK.setTimeout(function () {
startNewRound();
}, 700);
}
}, timeLimit);
lastTapTime = Date.now();
}
function endGame() {
if (responseTimer) {
LK.clearTimeout(responseTimer);
responseTimer = null;
}
waitingForInput = false;
// Flash ball red
LK.effects.flashObject(ball, 0xff0000, 600);
// Show game over (handled by LK)
LK.showGameOver();
}
// --- Input Handling ---
game.down = function (x, y, obj) {
if (!waitingForInput) {
return;
}
// Divide the screen into three vertical regions: left, middle, right
var region;
if (x < 2048 / 3) {
region = 'left';
} else if (x > 2048 * 2 / 3) {
region = 'right';
} else {
region = 'middle';
}
// Map region to color
var color;
if (region === 'left') {
color = 'red';
} else if (region === 'right') {
color = 'green';
} else {
color = 'blue';
}
// Find the button with this color (for animation/feedback)
var btn = null;
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].color === color) {
btn = buttons[i];
break;
}
}
if (btn) {
handleButtonPress(btn);
}
};
function handleButtonPress(btn) {
if (!waitingForInput) {
return;
}
btn.animatePress();
LK.effects.flashObject(btn.asset, 0xffffff, 120);
waitingForInput = false;
if (btn.color === currentColor) {
// Correct!
setScore(score + 1);
LK.getSound('correct').play();
// Animate ball
tween(ball, {
scaleX: 1.18,
scaleY: 1.18
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(ball, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.cubicIn
});
}
});
// Decrease timer by 0.1 seconds (100ms) for each correct answer
if (responseTimer) {
LK.clearTimeout(responseTimer);
responseTimer = null;
}
responseTimer = LK.setTimeout(function () {
startNewRound();
}, 220);
} else {
// Wrong!
LK.effects.flashScreen(0xff0000, 600);
LK.getSound('wrong').play();
if (responseTimer) {
LK.clearTimeout(responseTimer);
responseTimer = null;
}
health--;
updateHealthBars();
if (health <= 0) {
endGame();
} else {
// Decrease timer by 0.2 seconds (200ms) for each incorrect answer and reset to 3 seconds
timeLimit = 3000 - score * 100 - (maxHealth - health) * 200;
if (timeLimit < 500) {
timeLimit = 500; // minimum 0.5s to avoid negative/zero
}
// Give player another chance, start new round after short delay
responseTimer = LK.setTimeout(function () {
startNewRound();
}, 700);
}
}
}
// --- Game Update Loop ---
game.update = function () {
// Animate timer bar
if (waitingForInput) {
var elapsed = Date.now() - lastTapTime;
var frac = 1 - elapsed / timeLimit;
if (frac < 0) {
frac = 0;
}
timerBarFill.width = 900 * frac;
// Do not change timerBarFill color or tint here to prevent fading
} else {
timerBarFill.width = 0;
}
};
// --- Start Game ---
function startGame() {
LK.playMusic('g');
setScore(0);
timeLimit = 5000;
rotationSpeed = 0.02;
ball.rotationSpeed = rotationSpeed;
ball.targetRotationSpeed = rotationSpeed;
health = maxHealth;
updateHealthBars();
startNewRound();
}
startGame();
// --- Responsive UI (optional, handled by LK) ---
// --- Music (optional, not started by default) ---
// LK.playMusic('bgmusic', {fade: {start: 0, end: 1, duration: 1000}});