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