/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Background image container for easy background changes var Background = Container.expand(function () { var self = Container.call(this); // Use a full screen image, anchored top-left var bg = self.attachAsset('Grass', { anchorX: 0, anchorY: 0, width: GAME_W, height: GAME_H, x: 0, y: 0 }); return self; }); // Bullseye target var Bullseye = Container.expand(function () { var self = Container.call(this); // Outer ring var outer = self.attachAsset('bullseye_outer', { anchorX: 0.5, anchorY: 0.5 }); // Middle ring var mid = self.attachAsset('bullseye_mid', { anchorX: 0.5, anchorY: 0.5 }); // Inner ring (bull) var inner = self.attachAsset('bullseye_inner', { anchorX: 0.5, anchorY: 0.5 }); // For hit detection self.getCenter = function () { return { x: self.x, y: self.y }; }; self.getRadii = function () { return { outer: outer.width / 2, mid: mid.width / 2, inner: inner.width / 2 }; }; return self; }); // Crown reward visual var Crown = Container.expand(function () { var self = Container.call(this); var crown = self.attachAsset('crown', { anchorX: 0.5, anchorY: 0.5 }); self.visible = false; return self; }); // Dart (shows where the throw lands) var Dart = Container.expand(function () { var self = Container.call(this); var dart = self.attachAsset('dart', { anchorX: 0.5, anchorY: 0.5 }); return self; }); // Horizontal cursor (moves left/right) var HCursor = Container.expand(function () { var self = Container.call(this); var bar = self.attachAsset('h_cursor', { anchorX: 0.5, anchorY: 0.5 }); self.minX = 0; self.maxX = 0; self.speed = 1; self.direction = 1; // 1 = right, -1 = left self.active = false; self.update = function () { if (!self.active) return; self.x += self.speed * self.direction; if (self.x > self.maxX) { self.x = self.maxX; self.direction = -1; } if (self.x < self.minX) { self.x = self.minX; self.direction = 1; } }; self.lock = function () { self.active = false; }; self.reset = function () { self.active = false; self.direction = 1; }; return self; }); // Vertical cursor (moves up/down) var VCursor = Container.expand(function () { var self = Container.call(this); var bar = self.attachAsset('v_cursor', { anchorX: 0.5, anchorY: 0.5 }); self.minY = 0; self.maxY = 0; self.speed = 1; self.direction = 1; // 1 = down, -1 = up self.active = true; self.update = function () { if (!self.active) return; self.y += self.speed * self.direction; if (self.y > self.maxY) { self.y = self.maxY; self.direction = -1; } if (self.y < self.minY) { self.y = self.minY; self.direction = 1; } }; self.lock = function () { self.active = false; }; self.reset = function () { self.active = true; self.direction = 1; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x3cb043 // grassy green }); /**** * Game Code ****/ // Layout constants // Bullseye (center target) // Dart (shows where the throw lands) // Vertical cursor // Horizontal cursor // Sound for throw var GAME_W = 2048; var GAME_H = 2732; var BULLSEYE_X = GAME_W * 0.7; var BULLSEYE_Y = GAME_H * 0.5; var VCURSOR_X = GAME_W * 0.10; // closer to left edge var HCURSOR_Y = GAME_H * 0.88; // closer to bottom edge // State var state = 'v_cursor'; // 'v_cursor', 'h_cursor', 'throw_anim', 'result', 'wait' var level = 1; var vCursor, hCursor, bullseye, dart; var vLockY = 0, hLockX = 0; var score = 0; var lives = 3; // Number of lives var livesTxt; // Text2 for lives panel var levelTxt, infoTxt, scoreTxt; var throwAnimTimer = null; var resultTimer = null; // Track bullseye streak for crown reward var bullseyeStreak = 0; // Add crown visual (centered, hidden by default) - always on top by using LK.gui.center var crown = new Crown(); crown.x = LK.gui.center.width / 2; crown.y = LK.gui.center.height * 0.18; LK.gui.center.addChild(crown); // Add background (easy to change) var background = new Background(); game.addChild(background); // Add bullseye bullseye = new Bullseye(); function getRandomBullseyePos() { // Avoid edges and cursors: min/max margins // Loosen restrictions so bullseye can appear in more of the play area, but not too close to cursors var bullseyeRadius = 200; // half of outer ring var minX = VCURSOR_X + 220 + bullseyeRadius; // buffer from vCursor var maxX = GAME_W - 220 - bullseyeRadius; // buffer from right edge var minY = 220 + bullseyeRadius; var maxY = GAME_H - 220 - bullseyeRadius; // Random X, Y within safe area var x = minX + Math.random() * (maxX - minX); var y = minY + Math.random() * (maxY - minY); return { x: x, y: y }; } var bullseyePos = getRandomBullseyePos(); bullseye.x = bullseyePos.x; bullseye.y = bullseyePos.y; game.addChild(bullseye); // Add vertical cursor vCursor = new VCursor(); vCursor.x = VCURSOR_X; vCursor.minY = 400; vCursor.maxY = GAME_H - 400; vCursor.y = (vCursor.minY + vCursor.maxY) / 2; vCursor.speed = 10 + level * 2; game.addChild(vCursor); // Dynamic dashed guides will be handled below with containers and update logic. var vDashedGuide = new Container(); game.addChild(vDashedGuide); var hDashedGuide = new Container(); game.addChild(hDashedGuide); hCursor = new HCursor(); hCursor.y = HCURSOR_Y; hCursor.minX = 200; hCursor.maxX = GAME_W - 200; hCursor.x = (hCursor.minX + hCursor.maxX) / 2; hCursor.speed = 10 + level * 2; game.addChild(hCursor); // Add horizontal dashed guide at bullseye center for horizontal cursor // Add dart (hidden initially) dart = new Dart(); dart.visible = false; game.addChild(dart); // GUI: Stack Score, Level, and Lives vertically at top center var guiStackY = 0; scoreTxt = new Text2('Score: 0', { size: 90, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); // center top scoreTxt.x = LK.gui.top.width / 2; scoreTxt.y = guiStackY; LK.gui.top.addChild(scoreTxt); guiStackY += scoreTxt.height + 8; levelTxt = new Text2('Level 1', { size: 90, fill: 0xFFF000 }); levelTxt.anchor.set(0.5, 0); levelTxt.x = LK.gui.top.width / 2; levelTxt.y = guiStackY; LK.gui.top.addChild(levelTxt); guiStackY += levelTxt.height + 8; livesTxt = new Text2('Lives: ' + lives, { size: 90, fill: 0xFF4444 }); livesTxt.anchor.set(0.5, 0); livesTxt.x = LK.gui.top.width / 2; livesTxt.y = guiStackY; LK.gui.top.addChild(livesTxt); // GUI: Info infoTxt = new Text2('Tap to stop vertical', { size: 80, fill: 0x00FFCC }); infoTxt.anchor.set(0.5, 0.5); LK.gui.bottom.addChild(infoTxt); // Helper: Reset cursors for new level/throw function resetCursors() { vCursor.y = (vCursor.minY + vCursor.maxY) / 2; vCursor.speed = 10 + level * 0.5; vCursor.active = true; vCursor.direction = 1; hCursor.x = (hCursor.minX + hCursor.maxX) / 2; hCursor.speed = 10 + level * 0.5; hCursor.active = false; hCursor.direction = 1; } // Helper: Start new level function startLevel() { state = 'v_cursor'; resetCursors(); // Randomize bullseye position each level if (typeof getRandomBullseyePos === "function") { var bullseyePos = getRandomBullseyePos(); bullseye.x = bullseyePos.x; bullseye.y = bullseyePos.y; } dart.visible = false; infoTxt.setText('Tap to stop vertical'); levelTxt.setText('Level ' + level); // Clear guides if (typeof vDashedGuide !== "undefined") while (vDashedGuide.children.length > 0) vDashedGuide.removeChild(vDashedGuide.children[0]); if (typeof hDashedGuide !== "undefined") while (hDashedGuide.children.length > 0) hDashedGuide.removeChild(hDashedGuide.children[0]); if (typeof lives === "undefined" || lives > 3) { lives = 3; } livesTxt.setText('Lives: ' + lives); } // Helper: Show result (hit/miss) function showResult(hitType, points) { if (hitType === 'bull') { infoTxt.setText('🎯 Bullseye! +' + points); } else if (hitType === 'mid') { infoTxt.setText('Great! +' + points); } else if (hitType === 'outer') { infoTxt.setText('Good! +' + points); } else { infoTxt.setText('Miss!'); } } // Helper: Calculate hit function calcHit(x, y) { var center = bullseye.getCenter(); var dx = x - center.x; var dy = y - center.y; var dist = Math.sqrt(dx * dx + dy * dy); var radii = bullseye.getRadii(); if (dist <= radii.inner) return { type: 'bull', points: 5 }; if (dist <= radii.mid) return { type: 'mid', points: 3 }; if (dist <= radii.outer) return { type: 'outer', points: 1 }; return { type: 'miss', points: 0 }; } // Handle tap/clicks game.down = function (x, y, obj) { if (state === 'v_cursor') { // Lock vertical, enable horizontal vCursor.lock(); vLockY = vCursor.y; hCursor.active = true; state = 'h_cursor'; infoTxt.setText('Tap to stop horizontal'); } else if (state === 'h_cursor') { // Lock horizontal, throw dart hCursor.lock(); hLockX = hCursor.x; state = 'throw_anim'; infoTxt.setText('Throwing...'); // Clear guides if (typeof vDashedGuide !== "undefined") while (vDashedGuide.children.length > 0) vDashedGuide.removeChild(vDashedGuide.children[0]); if (typeof hDashedGuide !== "undefined") while (hDashedGuide.children.length > 0) hDashedGuide.removeChild(hDashedGuide.children[0]); // Animate dart from locked cursor position to bullseye dart.x = hLockX; dart.y = vLockY; dart.visible = true; LK.getSound('throw').play(); tween(dart, { x: bullseye.x, y: bullseye.y }, { duration: 400, easing: tween.cubicOut, onFinish: function onFinish() { // Calculate hit var hit = calcHit(hLockX, vLockY); if (hit.type === 'bull') { LK.getSound('hit').play(); bullseyeStreak += 1; // Show crown if 3 in a row if (bullseyeStreak === 3) { // Show crown visual crown.visible = true; crown.alpha = 0; // Show '+10 points' text at crown position (at the same time as crown appears) var bonusTxt = new Text2('+10 points', { size: 120, fill: 0xFFD700 }); bonusTxt.anchor.set(0.5, 0.5); bonusTxt.x = crown.x; bonusTxt.y = crown.y + 120; LK.gui.center.addChild(bonusTxt); // Animate bonus text up and fade out tween(bonusTxt, { y: bonusTxt.y - 120, alpha: 0 }, { duration: 900, easing: tween.cubicOut, onFinish: function onFinish() { if (bonusTxt && bonusTxt.parent) bonusTxt.parent.removeChild(bonusTxt); } }); // Give bonus points score += 10; LK.setScore(score); scoreTxt.setText('Score: ' + score); // Restore one life (if not max) if (lives < 3) { lives += 1; livesTxt.setText('Lives: ' + lives); } // Animate crown in tween(crown, { alpha: 1 }, { duration: 200, easing: tween.cubicOut, onFinish: function onFinish() { LK.getSound('applause').play(); LK.setTimeout(function () { tween(crown, { alpha: 0 }, { duration: 400, easing: tween.cubicIn, onFinish: function onFinish() { crown.visible = false; crown.alpha = 1; } }); }, 900); } }); // Reset streak after reward bullseyeStreak = 0; } } else { if (hit.type === 'miss') LK.getSound('miss').play(); bullseyeStreak = 0; if (hit.type !== 'bull') bullseyeStreak = 0; } score += hit.points; LK.setScore(score); scoreTxt.setText('Score: ' + score); showResult(hit.type, hit.points); // Next level or game over if (hit.type === 'miss') { // Lose a life lives -= 1; if (lives <= 0) { // Flash red, game over LK.effects.flashScreen(0xff0000, 800); livesTxt.setText('Lives: 0'); LK.setTimeout(function () { LK.showGameOver(); }, 900); state = 'wait'; } else { // Flash red, but continue to next level LK.effects.flashScreen(0xff0000, 800); livesTxt.setText('Lives: ' + lives); state = 'result'; resultTimer = LK.setTimeout(function () { level += 1; startLevel(); }, 900); } } else { // Flash green LK.effects.flashScreen(0x00ff00, 300); state = 'result'; resultTimer = LK.setTimeout(function () { level += 1; startLevel(); }, 900); } } }); } }; // Main update loop game.update = function () { if (state === 'v_cursor') { vCursor.update(); // Draw horizontal dashed line at vCursor.y // Clear previous while (vDashedGuide.children.length > 0) vDashedGuide.removeChild(vDashedGuide.children[0]); var dashLen = 48, dashGap = 32; var startX = vCursor.x + 90; // leave space for cursor asset var endX = GAME_W - 80; var y = vCursor.y; var x = startX; while (x + dashLen < endX) { var seg = LK.getAsset('bullseye_inner', { width: dashLen, height: 10, color: 0x888888, anchorX: 0, anchorY: 0.5 }); seg.x = x; seg.y = y; vDashedGuide.addChild(seg); x += dashLen + dashGap; } // Remove hDashedGuide if present while (hDashedGuide.children.length > 0) hDashedGuide.removeChild(hDashedGuide.children[0]); } else if (state === 'h_cursor') { hCursor.update(); // Draw vertical dashed line at hCursor.x // Clear previous while (hDashedGuide.children.length > 0) hDashedGuide.removeChild(hDashedGuide.children[0]); var dashLen = 48, dashGap = 32; var startY = hCursor.y - 90; // above cursor asset var endY = 200; // top margin var x = hCursor.x; var y = startY; // Draw upward from cursor to top while (y - dashLen > endY) { var seg = LK.getAsset('bullseye_inner', { width: 10, height: dashLen, color: 0x888888, anchorX: 0.5, anchorY: 1 }); seg.x = x; seg.y = y; hDashedGuide.addChild(seg); y -= dashLen + dashGap; } // Remove vDashedGuide if present while (vDashedGuide.children.length > 0) vDashedGuide.removeChild(vDashedGuide.children[0]); } else { // Remove both guides when not aiming while (vDashedGuide.children.length > 0) vDashedGuide.removeChild(vDashedGuide.children[0]); while (hDashedGuide.children.length > 0) hDashedGuide.removeChild(hDashedGuide.children[0]); } }; // On game reset, re-init state startLevel();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Background image container for easy background changes
var Background = Container.expand(function () {
var self = Container.call(this);
// Use a full screen image, anchored top-left
var bg = self.attachAsset('Grass', {
anchorX: 0,
anchorY: 0,
width: GAME_W,
height: GAME_H,
x: 0,
y: 0
});
return self;
});
// Bullseye target
var Bullseye = Container.expand(function () {
var self = Container.call(this);
// Outer ring
var outer = self.attachAsset('bullseye_outer', {
anchorX: 0.5,
anchorY: 0.5
});
// Middle ring
var mid = self.attachAsset('bullseye_mid', {
anchorX: 0.5,
anchorY: 0.5
});
// Inner ring (bull)
var inner = self.attachAsset('bullseye_inner', {
anchorX: 0.5,
anchorY: 0.5
});
// For hit detection
self.getCenter = function () {
return {
x: self.x,
y: self.y
};
};
self.getRadii = function () {
return {
outer: outer.width / 2,
mid: mid.width / 2,
inner: inner.width / 2
};
};
return self;
});
// Crown reward visual
var Crown = Container.expand(function () {
var self = Container.call(this);
var crown = self.attachAsset('crown', {
anchorX: 0.5,
anchorY: 0.5
});
self.visible = false;
return self;
});
// Dart (shows where the throw lands)
var Dart = Container.expand(function () {
var self = Container.call(this);
var dart = self.attachAsset('dart', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Horizontal cursor (moves left/right)
var HCursor = Container.expand(function () {
var self = Container.call(this);
var bar = self.attachAsset('h_cursor', {
anchorX: 0.5,
anchorY: 0.5
});
self.minX = 0;
self.maxX = 0;
self.speed = 1;
self.direction = 1; // 1 = right, -1 = left
self.active = false;
self.update = function () {
if (!self.active) return;
self.x += self.speed * self.direction;
if (self.x > self.maxX) {
self.x = self.maxX;
self.direction = -1;
}
if (self.x < self.minX) {
self.x = self.minX;
self.direction = 1;
}
};
self.lock = function () {
self.active = false;
};
self.reset = function () {
self.active = false;
self.direction = 1;
};
return self;
});
// Vertical cursor (moves up/down)
var VCursor = Container.expand(function () {
var self = Container.call(this);
var bar = self.attachAsset('v_cursor', {
anchorX: 0.5,
anchorY: 0.5
});
self.minY = 0;
self.maxY = 0;
self.speed = 1;
self.direction = 1; // 1 = down, -1 = up
self.active = true;
self.update = function () {
if (!self.active) return;
self.y += self.speed * self.direction;
if (self.y > self.maxY) {
self.y = self.maxY;
self.direction = -1;
}
if (self.y < self.minY) {
self.y = self.minY;
self.direction = 1;
}
};
self.lock = function () {
self.active = false;
};
self.reset = function () {
self.active = true;
self.direction = 1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x3cb043 // grassy green
});
/****
* Game Code
****/
// Layout constants
// Bullseye (center target)
// Dart (shows where the throw lands)
// Vertical cursor
// Horizontal cursor
// Sound for throw
var GAME_W = 2048;
var GAME_H = 2732;
var BULLSEYE_X = GAME_W * 0.7;
var BULLSEYE_Y = GAME_H * 0.5;
var VCURSOR_X = GAME_W * 0.10; // closer to left edge
var HCURSOR_Y = GAME_H * 0.88; // closer to bottom edge
// State
var state = 'v_cursor'; // 'v_cursor', 'h_cursor', 'throw_anim', 'result', 'wait'
var level = 1;
var vCursor, hCursor, bullseye, dart;
var vLockY = 0,
hLockX = 0;
var score = 0;
var lives = 3; // Number of lives
var livesTxt; // Text2 for lives panel
var levelTxt, infoTxt, scoreTxt;
var throwAnimTimer = null;
var resultTimer = null;
// Track bullseye streak for crown reward
var bullseyeStreak = 0;
// Add crown visual (centered, hidden by default) - always on top by using LK.gui.center
var crown = new Crown();
crown.x = LK.gui.center.width / 2;
crown.y = LK.gui.center.height * 0.18;
LK.gui.center.addChild(crown);
// Add background (easy to change)
var background = new Background();
game.addChild(background);
// Add bullseye
bullseye = new Bullseye();
function getRandomBullseyePos() {
// Avoid edges and cursors: min/max margins
// Loosen restrictions so bullseye can appear in more of the play area, but not too close to cursors
var bullseyeRadius = 200; // half of outer ring
var minX = VCURSOR_X + 220 + bullseyeRadius; // buffer from vCursor
var maxX = GAME_W - 220 - bullseyeRadius; // buffer from right edge
var minY = 220 + bullseyeRadius;
var maxY = GAME_H - 220 - bullseyeRadius;
// Random X, Y within safe area
var x = minX + Math.random() * (maxX - minX);
var y = minY + Math.random() * (maxY - minY);
return {
x: x,
y: y
};
}
var bullseyePos = getRandomBullseyePos();
bullseye.x = bullseyePos.x;
bullseye.y = bullseyePos.y;
game.addChild(bullseye);
// Add vertical cursor
vCursor = new VCursor();
vCursor.x = VCURSOR_X;
vCursor.minY = 400;
vCursor.maxY = GAME_H - 400;
vCursor.y = (vCursor.minY + vCursor.maxY) / 2;
vCursor.speed = 10 + level * 2;
game.addChild(vCursor);
// Dynamic dashed guides will be handled below with containers and update logic.
var vDashedGuide = new Container();
game.addChild(vDashedGuide);
var hDashedGuide = new Container();
game.addChild(hDashedGuide);
hCursor = new HCursor();
hCursor.y = HCURSOR_Y;
hCursor.minX = 200;
hCursor.maxX = GAME_W - 200;
hCursor.x = (hCursor.minX + hCursor.maxX) / 2;
hCursor.speed = 10 + level * 2;
game.addChild(hCursor);
// Add horizontal dashed guide at bullseye center for horizontal cursor
// Add dart (hidden initially)
dart = new Dart();
dart.visible = false;
game.addChild(dart);
// GUI: Stack Score, Level, and Lives vertically at top center
var guiStackY = 0;
scoreTxt = new Text2('Score: 0', {
size: 90,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0); // center top
scoreTxt.x = LK.gui.top.width / 2;
scoreTxt.y = guiStackY;
LK.gui.top.addChild(scoreTxt);
guiStackY += scoreTxt.height + 8;
levelTxt = new Text2('Level 1', {
size: 90,
fill: 0xFFF000
});
levelTxt.anchor.set(0.5, 0);
levelTxt.x = LK.gui.top.width / 2;
levelTxt.y = guiStackY;
LK.gui.top.addChild(levelTxt);
guiStackY += levelTxt.height + 8;
livesTxt = new Text2('Lives: ' + lives, {
size: 90,
fill: 0xFF4444
});
livesTxt.anchor.set(0.5, 0);
livesTxt.x = LK.gui.top.width / 2;
livesTxt.y = guiStackY;
LK.gui.top.addChild(livesTxt);
// GUI: Info
infoTxt = new Text2('Tap to stop vertical', {
size: 80,
fill: 0x00FFCC
});
infoTxt.anchor.set(0.5, 0.5);
LK.gui.bottom.addChild(infoTxt);
// Helper: Reset cursors for new level/throw
function resetCursors() {
vCursor.y = (vCursor.minY + vCursor.maxY) / 2;
vCursor.speed = 10 + level * 0.5;
vCursor.active = true;
vCursor.direction = 1;
hCursor.x = (hCursor.minX + hCursor.maxX) / 2;
hCursor.speed = 10 + level * 0.5;
hCursor.active = false;
hCursor.direction = 1;
}
// Helper: Start new level
function startLevel() {
state = 'v_cursor';
resetCursors();
// Randomize bullseye position each level
if (typeof getRandomBullseyePos === "function") {
var bullseyePos = getRandomBullseyePos();
bullseye.x = bullseyePos.x;
bullseye.y = bullseyePos.y;
}
dart.visible = false;
infoTxt.setText('Tap to stop vertical');
levelTxt.setText('Level ' + level);
// Clear guides
if (typeof vDashedGuide !== "undefined") while (vDashedGuide.children.length > 0) vDashedGuide.removeChild(vDashedGuide.children[0]);
if (typeof hDashedGuide !== "undefined") while (hDashedGuide.children.length > 0) hDashedGuide.removeChild(hDashedGuide.children[0]);
if (typeof lives === "undefined" || lives > 3) {
lives = 3;
}
livesTxt.setText('Lives: ' + lives);
}
// Helper: Show result (hit/miss)
function showResult(hitType, points) {
if (hitType === 'bull') {
infoTxt.setText('🎯 Bullseye! +' + points);
} else if (hitType === 'mid') {
infoTxt.setText('Great! +' + points);
} else if (hitType === 'outer') {
infoTxt.setText('Good! +' + points);
} else {
infoTxt.setText('Miss!');
}
}
// Helper: Calculate hit
function calcHit(x, y) {
var center = bullseye.getCenter();
var dx = x - center.x;
var dy = y - center.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var radii = bullseye.getRadii();
if (dist <= radii.inner) return {
type: 'bull',
points: 5
};
if (dist <= radii.mid) return {
type: 'mid',
points: 3
};
if (dist <= radii.outer) return {
type: 'outer',
points: 1
};
return {
type: 'miss',
points: 0
};
}
// Handle tap/clicks
game.down = function (x, y, obj) {
if (state === 'v_cursor') {
// Lock vertical, enable horizontal
vCursor.lock();
vLockY = vCursor.y;
hCursor.active = true;
state = 'h_cursor';
infoTxt.setText('Tap to stop horizontal');
} else if (state === 'h_cursor') {
// Lock horizontal, throw dart
hCursor.lock();
hLockX = hCursor.x;
state = 'throw_anim';
infoTxt.setText('Throwing...');
// Clear guides
if (typeof vDashedGuide !== "undefined") while (vDashedGuide.children.length > 0) vDashedGuide.removeChild(vDashedGuide.children[0]);
if (typeof hDashedGuide !== "undefined") while (hDashedGuide.children.length > 0) hDashedGuide.removeChild(hDashedGuide.children[0]);
// Animate dart from locked cursor position to bullseye
dart.x = hLockX;
dart.y = vLockY;
dart.visible = true;
LK.getSound('throw').play();
tween(dart, {
x: bullseye.x,
y: bullseye.y
}, {
duration: 400,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Calculate hit
var hit = calcHit(hLockX, vLockY);
if (hit.type === 'bull') {
LK.getSound('hit').play();
bullseyeStreak += 1;
// Show crown if 3 in a row
if (bullseyeStreak === 3) {
// Show crown visual
crown.visible = true;
crown.alpha = 0;
// Show '+10 points' text at crown position (at the same time as crown appears)
var bonusTxt = new Text2('+10 points', {
size: 120,
fill: 0xFFD700
});
bonusTxt.anchor.set(0.5, 0.5);
bonusTxt.x = crown.x;
bonusTxt.y = crown.y + 120;
LK.gui.center.addChild(bonusTxt);
// Animate bonus text up and fade out
tween(bonusTxt, {
y: bonusTxt.y - 120,
alpha: 0
}, {
duration: 900,
easing: tween.cubicOut,
onFinish: function onFinish() {
if (bonusTxt && bonusTxt.parent) bonusTxt.parent.removeChild(bonusTxt);
}
});
// Give bonus points
score += 10;
LK.setScore(score);
scoreTxt.setText('Score: ' + score);
// Restore one life (if not max)
if (lives < 3) {
lives += 1;
livesTxt.setText('Lives: ' + lives);
}
// Animate crown in
tween(crown, {
alpha: 1
}, {
duration: 200,
easing: tween.cubicOut,
onFinish: function onFinish() {
LK.getSound('applause').play();
LK.setTimeout(function () {
tween(crown, {
alpha: 0
}, {
duration: 400,
easing: tween.cubicIn,
onFinish: function onFinish() {
crown.visible = false;
crown.alpha = 1;
}
});
}, 900);
}
});
// Reset streak after reward
bullseyeStreak = 0;
}
} else {
if (hit.type === 'miss') LK.getSound('miss').play();
bullseyeStreak = 0;
if (hit.type !== 'bull') bullseyeStreak = 0;
}
score += hit.points;
LK.setScore(score);
scoreTxt.setText('Score: ' + score);
showResult(hit.type, hit.points);
// Next level or game over
if (hit.type === 'miss') {
// Lose a life
lives -= 1;
if (lives <= 0) {
// Flash red, game over
LK.effects.flashScreen(0xff0000, 800);
livesTxt.setText('Lives: 0');
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
state = 'wait';
} else {
// Flash red, but continue to next level
LK.effects.flashScreen(0xff0000, 800);
livesTxt.setText('Lives: ' + lives);
state = 'result';
resultTimer = LK.setTimeout(function () {
level += 1;
startLevel();
}, 900);
}
} else {
// Flash green
LK.effects.flashScreen(0x00ff00, 300);
state = 'result';
resultTimer = LK.setTimeout(function () {
level += 1;
startLevel();
}, 900);
}
}
});
}
};
// Main update loop
game.update = function () {
if (state === 'v_cursor') {
vCursor.update();
// Draw horizontal dashed line at vCursor.y
// Clear previous
while (vDashedGuide.children.length > 0) vDashedGuide.removeChild(vDashedGuide.children[0]);
var dashLen = 48,
dashGap = 32;
var startX = vCursor.x + 90; // leave space for cursor asset
var endX = GAME_W - 80;
var y = vCursor.y;
var x = startX;
while (x + dashLen < endX) {
var seg = LK.getAsset('bullseye_inner', {
width: dashLen,
height: 10,
color: 0x888888,
anchorX: 0,
anchorY: 0.5
});
seg.x = x;
seg.y = y;
vDashedGuide.addChild(seg);
x += dashLen + dashGap;
}
// Remove hDashedGuide if present
while (hDashedGuide.children.length > 0) hDashedGuide.removeChild(hDashedGuide.children[0]);
} else if (state === 'h_cursor') {
hCursor.update();
// Draw vertical dashed line at hCursor.x
// Clear previous
while (hDashedGuide.children.length > 0) hDashedGuide.removeChild(hDashedGuide.children[0]);
var dashLen = 48,
dashGap = 32;
var startY = hCursor.y - 90; // above cursor asset
var endY = 200; // top margin
var x = hCursor.x;
var y = startY;
// Draw upward from cursor to top
while (y - dashLen > endY) {
var seg = LK.getAsset('bullseye_inner', {
width: 10,
height: dashLen,
color: 0x888888,
anchorX: 0.5,
anchorY: 1
});
seg.x = x;
seg.y = y;
hDashedGuide.addChild(seg);
y -= dashLen + dashGap;
}
// Remove vDashedGuide if present
while (vDashedGuide.children.length > 0) vDashedGuide.removeChild(vDashedGuide.children[0]);
} else {
// Remove both guides when not aiming
while (vDashedGuide.children.length > 0) vDashedGuide.removeChild(vDashedGuide.children[0]);
while (hDashedGuide.children.length > 0) hDashedGuide.removeChild(hDashedGuide.children[0]);
}
};
// On game reset, re-init state
startLevel();