/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
level: 1
});
/****
* Classes
****/
var ClockNeedle = Container.expand(function () {
var self = Container.call(this);
var needleGraphics = self.attachAsset('needle', {
anchorX: 0.5,
anchorY: 1.0,
width: 10,
height: 500
});
self.angle = 0;
self.speed = 1;
self.direction = 1; // 1 clockwise, -1 counterclockwise
self.randomizeBehavior = false;
self.behaviorChangeTimer = 0;
self.update = function () {
// Rotate the needle
self.angle += self.speed * self.direction;
// Keep angle between 0-360
if (self.angle >= 360) {
self.angle -= 360;
} else if (self.angle < 0) {
self.angle += 360;
}
// Update the needle rotation
self.rotation = (self.angle + 90) * (Math.PI / 180);
// Random behavior changes if enabled
if (self.randomizeBehavior && self.behaviorChangeTimer <= 0) {
var randomChange = Math.random();
if (randomChange < 0.3) {
// Change direction
self.direction *= -1;
} else if (randomChange < 0.6) {
// Change speed (between 0.5 and 2.5)
self.speed = 0.5 + Math.random() * 2;
}
// Set new timer for next behavior change (2-6 seconds)
self.behaviorChangeTimer = 120 + Math.floor(Math.random() * 240);
}
if (self.behaviorChangeTimer > 0) {
self.behaviorChangeTimer--;
}
};
self.setSpeed = function (newSpeed) {
self.speed = newSpeed;
};
self.enableRandomBehavior = function () {
self.randomizeBehavior = true;
};
self.disableRandomBehavior = function () {
self.randomizeBehavior = false;
self.direction = 1;
self.speed = 1;
};
return self;
});
var Target = Container.expand(function (angle, distance) {
var self = Container.call(this);
var targetGraphics = self.attachAsset('target', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60
});
self.angle = angle || 0;
self.distance = distance || 400;
self.active = true;
self.hit = false;
// Calculate position based on angle and distance
self.updatePosition = function () {
var radian = self.angle * (Math.PI / 180);
self.x = Math.cos(radian) * self.distance;
self.y = Math.sin(radian) * self.distance;
};
self.markHit = function () {
if (!self.active) {
return;
}
self.hit = true;
self.active = false;
// Replace target with hit effect
targetGraphics.destroy();
var hitEffect = self.attachAsset('targetHit', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
// Animate hit effect
tween(hitEffect, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Target stays visible but faded
tween(hitEffect, {
alpha: 0.3,
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
});
};
self.markMiss = function () {
if (!self.active) {
return;
}
self.active = false;
// Replace target with miss effect
targetGraphics.destroy();
var missEffect = self.attachAsset('targetMiss', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
// Animate miss effect
tween(missEffect, {
alpha: 0
}, {
duration: 800,
easing: tween.easeOut
});
};
self.reset = function () {
self.active = true;
self.hit = false;
if (targetGraphics.parent) {
targetGraphics.destroy();
}
targetGraphics = self.attachAsset('target', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60
});
};
// Initialize position
self.updatePosition();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x121212
});
/****
* Game Code
****/
// Game state variables
var score = 0;
var currentStreak = 0;
var multiplier = 1;
var level = storage.level || 1;
var highScore = storage.highScore || 0;
var targets = [];
var needle = null;
var clockFace = null;
var clockCenter = null;
var isGameStarted = false;
var targetCount = 0;
// UI elements
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
var streakTxt = new Text2('Streak: 0', {
size: 60,
fill: 0xFFFFFF
});
var multiplierTxt = new Text2('x1', {
size: 80,
fill: 0xFFCC00
});
var levelTxt = new Text2('Level 1', {
size: 60,
fill: 0xFFFFFF
});
var highScoreTxt = new Text2('High Score: 0', {
size: 40,
fill: 0xAAAAAA
});
var instructionTxt = new Text2('Tap when the needle hits a target!', {
size: 60,
fill: 0xFFFFFF
});
// Position UI elements
scoreTxt.anchor.set(0.5, 0);
scoreTxt.y = 100;
LK.gui.top.addChild(scoreTxt);
streakTxt.anchor.set(0.5, 0);
streakTxt.y = 220;
LK.gui.top.addChild(streakTxt);
multiplierTxt.anchor.set(0.5, 0);
multiplierTxt.y = 280;
LK.gui.top.addChild(multiplierTxt);
levelTxt.anchor.set(0.5, 1);
levelTxt.y = -40;
LK.gui.bottom.addChild(levelTxt);
highScoreTxt.anchor.set(0.5, 1);
highScoreTxt.y = -100;
LK.gui.bottom.addChild(highScoreTxt);
instructionTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionTxt);
// Function to initialize the game
function initGame() {
// Create clock face
clockFace = LK.getAsset('clockFace', {
anchorX: 0.5,
anchorY: 0.5,
width: 1200,
height: 1200
});
game.addChild(clockFace);
// Position clock face at center of screen
clockFace.x = 2048 / 2;
clockFace.y = 2732 / 2;
// Create clock needle
needle = new ClockNeedle();
needle.x = 2048 / 2;
needle.y = 2732 / 2;
game.addChild(needle);
// Create clock center
clockCenter = LK.getAsset('clockCenter', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50
});
clockCenter.x = 2048 / 2;
clockCenter.y = 2732 / 2;
game.addChild(clockCenter);
// Create initial targets
createTargetsForLevel(level);
// Reset game variables
score = 0;
currentStreak = 0;
multiplier = 1;
// Update UI
updateUI();
// Play background music
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
});
// Mark game as started
isGameStarted = true;
// Hide instruction text after 3 seconds
LK.setTimeout(function () {
tween(instructionTxt, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut
});
}, 3000);
}
// Function to create targets based on current level
function createTargetsForLevel(level) {
// Clear existing targets
for (var i = 0; i < targets.length; i++) {
if (targets[i].parent) {
targets[i].destroy();
}
}
targets = [];
// Calculate number of targets based on level
targetCount = Math.min(3 + Math.floor(level / 2), 12);
// Create evenly spaced targets
var angleStep = 360 / targetCount;
var startAngle = Math.random() * 360; // Random starting angle
for (var i = 0; i < targetCount; i++) {
var angle = (startAngle + i * angleStep) % 360;
var target = new Target(angle, 450);
// Position relative to center of game
target.x += 2048 / 2;
target.y += 2732 / 2;
targets.push(target);
game.addChild(target);
}
// Set needle behavior based on level
if (level >= 3) {
needle.setSpeed(1 + level * 0.2); // Increase speed with level
}
if (level >= 5) {
needle.enableRandomBehavior();
} else {
needle.disableRandomBehavior();
}
}
// Function to update UI elements
function updateUI() {
scoreTxt.setText(score.toString());
streakTxt.setText("Streak: " + currentStreak);
multiplierTxt.setText("x" + multiplier);
levelTxt.setText("Level " + level);
highScoreTxt.setText("High Score: " + highScore);
// Update score in LK system
LK.setScore(score);
}
// Function to check if needle is hitting any target
function checkNeedleHit() {
var needleAngle = needle.angle;
var hitTarget = false;
var hitAngleTolerance = 10; // Degrees of tolerance for hitting
// Check each target
for (var i = 0; i < targets.length; i++) {
var target = targets[i];
if (!target.active) {
continue;
}
// Calculate angle difference
var angleDiff = Math.abs(needleAngle - target.angle);
if (angleDiff > 180) {
angleDiff = 360 - angleDiff;
}
// Check if needle is within tolerance range
if (angleDiff <= hitAngleTolerance) {
hitTarget = true;
handleTargetHit(target);
break;
}
}
if (!hitTarget) {
handleMiss();
}
}
// Function to handle successful target hit
function handleTargetHit(target) {
// Mark target as hit
target.markHit();
// Play hit sound
LK.getSound('hit').play();
// Update streak and multiplier
currentStreak++;
multiplier = Math.min(5, 1 + Math.floor(currentStreak / 3));
// Add score
var pointsGained = 100 * multiplier;
score += pointsGained;
// Show points animation
var pointsText = new Text2("+" + pointsGained, {
size: 80,
fill: 0x00FF00
});
pointsText.anchor.set(0.5, 0.5);
pointsText.x = target.x;
pointsText.y = target.y;
game.addChild(pointsText);
tween(pointsText, {
y: pointsText.y - 100,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
pointsText.destroy();
}
});
// Update UI
updateUI();
// Check if all targets are hit
var allTargetsHit = true;
for (var i = 0; i < targets.length; i++) {
if (targets[i].active) {
allTargetsHit = false;
break;
}
}
// If all targets hit, advance to next level
if (allTargetsHit) {
advanceLevel();
}
}
// Function to handle missed tap
function handleMiss() {
// Play miss sound
LK.getSound('miss').play();
// Check if the player is on a streak
if (currentStreak > 0) {
// Reset streak and multiplier
currentStreak = 0;
multiplier = 1;
// Show miss animation
LK.effects.flashScreen(0xff0000, 300);
// Maintain remaining targets when streak is lost
for (var i = 0; i < targets.length; i++) {
if (targets[i].active && !targets[i].hit) {
targets[i].reset();
}
}
// Update UI
updateUI();
} else {
// Show miss animation
LK.effects.flashScreen(0xff0000, 300);
// Mark all active targets as missed
for (var i = 0; i < targets.length; i++) {
if (targets[i].active) {
targets[i].markMiss();
}
}
// Update UI
updateUI();
// Check if score is higher than high score
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
// Show game over
LK.setTimeout(function () {
LK.showGameOver();
}, 1000);
}
}
// Function to advance to next level
function advanceLevel() {
level++;
storage.level = level;
// Play level up sound
LK.getSound('levelUp').play();
// Show level up animation
var levelUpText = new Text2("LEVEL UP!", {
size: 120,
fill: 0xFFCC00
});
levelUpText.anchor.set(0.5, 0.5);
levelUpText.x = 2048 / 2;
levelUpText.y = 2732 / 2;
game.addChild(levelUpText);
tween(levelUpText, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(levelUpText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
levelUpText.destroy();
// Create new targets for next level
createTargetsForLevel(level);
}
});
}
});
// Update UI
updateUI();
}
// Game tap/click handler
game.down = function (x, y, obj) {
if (!isGameStarted) {
// Start game on first tap
initGame();
return;
}
checkNeedleHit();
};
// Game update function
game.update = function () {
if (!isGameStarted) {
return;
}
// Nothing needs updating here as the needle has its own update method
};
// Initialize the game elements
highScore = storage.highScore || 0;
level = 1; // Always start at level 1 for a new game
highScoreTxt.setText("High Score: " + highScore);
// Show start instructions
instructionTxt.setText("Tap to start!"); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
level: 1
});
/****
* Classes
****/
var ClockNeedle = Container.expand(function () {
var self = Container.call(this);
var needleGraphics = self.attachAsset('needle', {
anchorX: 0.5,
anchorY: 1.0,
width: 10,
height: 500
});
self.angle = 0;
self.speed = 1;
self.direction = 1; // 1 clockwise, -1 counterclockwise
self.randomizeBehavior = false;
self.behaviorChangeTimer = 0;
self.update = function () {
// Rotate the needle
self.angle += self.speed * self.direction;
// Keep angle between 0-360
if (self.angle >= 360) {
self.angle -= 360;
} else if (self.angle < 0) {
self.angle += 360;
}
// Update the needle rotation
self.rotation = (self.angle + 90) * (Math.PI / 180);
// Random behavior changes if enabled
if (self.randomizeBehavior && self.behaviorChangeTimer <= 0) {
var randomChange = Math.random();
if (randomChange < 0.3) {
// Change direction
self.direction *= -1;
} else if (randomChange < 0.6) {
// Change speed (between 0.5 and 2.5)
self.speed = 0.5 + Math.random() * 2;
}
// Set new timer for next behavior change (2-6 seconds)
self.behaviorChangeTimer = 120 + Math.floor(Math.random() * 240);
}
if (self.behaviorChangeTimer > 0) {
self.behaviorChangeTimer--;
}
};
self.setSpeed = function (newSpeed) {
self.speed = newSpeed;
};
self.enableRandomBehavior = function () {
self.randomizeBehavior = true;
};
self.disableRandomBehavior = function () {
self.randomizeBehavior = false;
self.direction = 1;
self.speed = 1;
};
return self;
});
var Target = Container.expand(function (angle, distance) {
var self = Container.call(this);
var targetGraphics = self.attachAsset('target', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60
});
self.angle = angle || 0;
self.distance = distance || 400;
self.active = true;
self.hit = false;
// Calculate position based on angle and distance
self.updatePosition = function () {
var radian = self.angle * (Math.PI / 180);
self.x = Math.cos(radian) * self.distance;
self.y = Math.sin(radian) * self.distance;
};
self.markHit = function () {
if (!self.active) {
return;
}
self.hit = true;
self.active = false;
// Replace target with hit effect
targetGraphics.destroy();
var hitEffect = self.attachAsset('targetHit', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
// Animate hit effect
tween(hitEffect, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Target stays visible but faded
tween(hitEffect, {
alpha: 0.3,
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
});
};
self.markMiss = function () {
if (!self.active) {
return;
}
self.active = false;
// Replace target with miss effect
targetGraphics.destroy();
var missEffect = self.attachAsset('targetMiss', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
// Animate miss effect
tween(missEffect, {
alpha: 0
}, {
duration: 800,
easing: tween.easeOut
});
};
self.reset = function () {
self.active = true;
self.hit = false;
if (targetGraphics.parent) {
targetGraphics.destroy();
}
targetGraphics = self.attachAsset('target', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60
});
};
// Initialize position
self.updatePosition();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x121212
});
/****
* Game Code
****/
// Game state variables
var score = 0;
var currentStreak = 0;
var multiplier = 1;
var level = storage.level || 1;
var highScore = storage.highScore || 0;
var targets = [];
var needle = null;
var clockFace = null;
var clockCenter = null;
var isGameStarted = false;
var targetCount = 0;
// UI elements
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
var streakTxt = new Text2('Streak: 0', {
size: 60,
fill: 0xFFFFFF
});
var multiplierTxt = new Text2('x1', {
size: 80,
fill: 0xFFCC00
});
var levelTxt = new Text2('Level 1', {
size: 60,
fill: 0xFFFFFF
});
var highScoreTxt = new Text2('High Score: 0', {
size: 40,
fill: 0xAAAAAA
});
var instructionTxt = new Text2('Tap when the needle hits a target!', {
size: 60,
fill: 0xFFFFFF
});
// Position UI elements
scoreTxt.anchor.set(0.5, 0);
scoreTxt.y = 100;
LK.gui.top.addChild(scoreTxt);
streakTxt.anchor.set(0.5, 0);
streakTxt.y = 220;
LK.gui.top.addChild(streakTxt);
multiplierTxt.anchor.set(0.5, 0);
multiplierTxt.y = 280;
LK.gui.top.addChild(multiplierTxt);
levelTxt.anchor.set(0.5, 1);
levelTxt.y = -40;
LK.gui.bottom.addChild(levelTxt);
highScoreTxt.anchor.set(0.5, 1);
highScoreTxt.y = -100;
LK.gui.bottom.addChild(highScoreTxt);
instructionTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionTxt);
// Function to initialize the game
function initGame() {
// Create clock face
clockFace = LK.getAsset('clockFace', {
anchorX: 0.5,
anchorY: 0.5,
width: 1200,
height: 1200
});
game.addChild(clockFace);
// Position clock face at center of screen
clockFace.x = 2048 / 2;
clockFace.y = 2732 / 2;
// Create clock needle
needle = new ClockNeedle();
needle.x = 2048 / 2;
needle.y = 2732 / 2;
game.addChild(needle);
// Create clock center
clockCenter = LK.getAsset('clockCenter', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50
});
clockCenter.x = 2048 / 2;
clockCenter.y = 2732 / 2;
game.addChild(clockCenter);
// Create initial targets
createTargetsForLevel(level);
// Reset game variables
score = 0;
currentStreak = 0;
multiplier = 1;
// Update UI
updateUI();
// Play background music
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
});
// Mark game as started
isGameStarted = true;
// Hide instruction text after 3 seconds
LK.setTimeout(function () {
tween(instructionTxt, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut
});
}, 3000);
}
// Function to create targets based on current level
function createTargetsForLevel(level) {
// Clear existing targets
for (var i = 0; i < targets.length; i++) {
if (targets[i].parent) {
targets[i].destroy();
}
}
targets = [];
// Calculate number of targets based on level
targetCount = Math.min(3 + Math.floor(level / 2), 12);
// Create evenly spaced targets
var angleStep = 360 / targetCount;
var startAngle = Math.random() * 360; // Random starting angle
for (var i = 0; i < targetCount; i++) {
var angle = (startAngle + i * angleStep) % 360;
var target = new Target(angle, 450);
// Position relative to center of game
target.x += 2048 / 2;
target.y += 2732 / 2;
targets.push(target);
game.addChild(target);
}
// Set needle behavior based on level
if (level >= 3) {
needle.setSpeed(1 + level * 0.2); // Increase speed with level
}
if (level >= 5) {
needle.enableRandomBehavior();
} else {
needle.disableRandomBehavior();
}
}
// Function to update UI elements
function updateUI() {
scoreTxt.setText(score.toString());
streakTxt.setText("Streak: " + currentStreak);
multiplierTxt.setText("x" + multiplier);
levelTxt.setText("Level " + level);
highScoreTxt.setText("High Score: " + highScore);
// Update score in LK system
LK.setScore(score);
}
// Function to check if needle is hitting any target
function checkNeedleHit() {
var needleAngle = needle.angle;
var hitTarget = false;
var hitAngleTolerance = 10; // Degrees of tolerance for hitting
// Check each target
for (var i = 0; i < targets.length; i++) {
var target = targets[i];
if (!target.active) {
continue;
}
// Calculate angle difference
var angleDiff = Math.abs(needleAngle - target.angle);
if (angleDiff > 180) {
angleDiff = 360 - angleDiff;
}
// Check if needle is within tolerance range
if (angleDiff <= hitAngleTolerance) {
hitTarget = true;
handleTargetHit(target);
break;
}
}
if (!hitTarget) {
handleMiss();
}
}
// Function to handle successful target hit
function handleTargetHit(target) {
// Mark target as hit
target.markHit();
// Play hit sound
LK.getSound('hit').play();
// Update streak and multiplier
currentStreak++;
multiplier = Math.min(5, 1 + Math.floor(currentStreak / 3));
// Add score
var pointsGained = 100 * multiplier;
score += pointsGained;
// Show points animation
var pointsText = new Text2("+" + pointsGained, {
size: 80,
fill: 0x00FF00
});
pointsText.anchor.set(0.5, 0.5);
pointsText.x = target.x;
pointsText.y = target.y;
game.addChild(pointsText);
tween(pointsText, {
y: pointsText.y - 100,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
pointsText.destroy();
}
});
// Update UI
updateUI();
// Check if all targets are hit
var allTargetsHit = true;
for (var i = 0; i < targets.length; i++) {
if (targets[i].active) {
allTargetsHit = false;
break;
}
}
// If all targets hit, advance to next level
if (allTargetsHit) {
advanceLevel();
}
}
// Function to handle missed tap
function handleMiss() {
// Play miss sound
LK.getSound('miss').play();
// Check if the player is on a streak
if (currentStreak > 0) {
// Reset streak and multiplier
currentStreak = 0;
multiplier = 1;
// Show miss animation
LK.effects.flashScreen(0xff0000, 300);
// Maintain remaining targets when streak is lost
for (var i = 0; i < targets.length; i++) {
if (targets[i].active && !targets[i].hit) {
targets[i].reset();
}
}
// Update UI
updateUI();
} else {
// Show miss animation
LK.effects.flashScreen(0xff0000, 300);
// Mark all active targets as missed
for (var i = 0; i < targets.length; i++) {
if (targets[i].active) {
targets[i].markMiss();
}
}
// Update UI
updateUI();
// Check if score is higher than high score
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
// Show game over
LK.setTimeout(function () {
LK.showGameOver();
}, 1000);
}
}
// Function to advance to next level
function advanceLevel() {
level++;
storage.level = level;
// Play level up sound
LK.getSound('levelUp').play();
// Show level up animation
var levelUpText = new Text2("LEVEL UP!", {
size: 120,
fill: 0xFFCC00
});
levelUpText.anchor.set(0.5, 0.5);
levelUpText.x = 2048 / 2;
levelUpText.y = 2732 / 2;
game.addChild(levelUpText);
tween(levelUpText, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(levelUpText, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
levelUpText.destroy();
// Create new targets for next level
createTargetsForLevel(level);
}
});
}
});
// Update UI
updateUI();
}
// Game tap/click handler
game.down = function (x, y, obj) {
if (!isGameStarted) {
// Start game on first tap
initGame();
return;
}
checkNeedleHit();
};
// Game update function
game.update = function () {
if (!isGameStarted) {
return;
}
// Nothing needs updating here as the needle has its own update method
};
// Initialize the game elements
highScore = storage.highScore || 0;
level = 1; // Always start at level 1 for a new game
highScoreTxt.setText("High Score: " + highScore);
// Show start instructions
instructionTxt.setText("Tap to start!");