/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ // Achievement popup class var AchievementPopup = Container.expand(function () { var self = Container.call(this); var txt = new Text2('', { size: 120, fill: 0xFFD700 }); txt.anchor.set(0.5, 0.5); self.addChild(txt); self.visible = false; self.show = function (message, color) { txt.setText(message); if (txt && txt.style) { txt.style.fill = color || 0xFFD700; } self.x = 2048 / 2; self.y = 2732 / 2 - 300; self.visible = true; self.alpha = 1; tween(self, { alpha: 0 }, { duration: 1200, onFinish: function onFinish() { self.visible = false; } }); }; return self; }); // Dog class var Dog = Container.expand(function () { var self = Container.call(this); self.dogAsset = null; self.laughAsset = null; self.currentDogId = null; // Helper to set dog asset based on selectedDogId self.setDogAssets = function () { // Remove old assets if any if (self.dogAsset && self.dogAsset.parent) self.removeChild(self.dogAsset); if (self.laughAsset && self.laughAsset.parent) self.removeChild(self.laughAsset); self.dogAsset = null; self.laughAsset = null; var dogOpt = null; for (var i = 0; i < DOG_OPTIONS.length; i++) { if (DOG_OPTIONS[i].id === selectedDogId) { dogOpt = DOG_OPTIONS[i]; break; } } if (!dogOpt) dogOpt = DOG_OPTIONS[0]; self.currentDogId = dogOpt.id; self.dogAsset = self.attachAsset(dogOpt.asset, { anchorX: 0.5, anchorY: 1 }); self.dogAsset.visible = false; // Laugh asset will be created on demand in laugh() }; self.setDogAssets(); self.visible = false; // Show dog at (x, y) self.show = function (x, y) { // If dog changed, update assets if (self.currentDogId !== selectedDogId) { self.setDogAssets(); } self.x = x; self.y = y; self.visible = true; if (self.dogAsset) self.dogAsset.visible = true; if (self.laughAsset) self.laughAsset.visible = false; // Optionally, you could add a little "pop up" animation here // For example, tween the dog from below the screen to y var startY = 2732 + (self.dogAsset ? self.dogAsset.height : 180); self.y = startY; tween(self, { y: y }, { duration: 350, easing: tween.cubicOut }); }; // Hide dog self.hide = function () { var endY = 2732 + (self.dogAsset ? self.dogAsset.height : 180); tween(self, { y: endY }, { duration: 350, easing: tween.cubicIn, onFinish: function onFinish() { self.visible = false; if (self.dogAsset) self.dogAsset.visible = false; } }); }; // Show laugh animation self.laugh = function (x, y) { // If dog changed, update assets if (self.currentDogId !== selectedDogId) { self.setDogAssets(); } self.x = x; self.y = y; self.visible = true; if (self.dogAsset) self.dogAsset.visible = false; // Find laugh asset id for this dog var dogOpt = null; for (var i = 0; i < DOG_OPTIONS.length; i++) { if (DOG_OPTIONS[i].id === selectedDogId) { dogOpt = DOG_OPTIONS[i]; break; } } if (!dogOpt) dogOpt = DOG_OPTIONS[0]; var laughId = dogOpt.laugh || "dog_laugh"; if (!self.laughAsset || self.laughAsset._assetId !== laughId) { if (self.laughAsset && self.laughAsset.parent) self.removeChild(self.laughAsset); self.laughAsset = self.attachAsset(laughId, { anchorX: 0.5, anchorY: 1 }); self.laughAsset._assetId = laughId; } self.laughAsset.visible = true; // Play laugh sound (use default for all) LK.getSound('dog_laugh').play(); // Hide after 1.2s LK.setTimeout(function () { self.laughAsset.visible = false; self.hide(); }, 1200); }; return self; }); // Duck class var Duck = Container.expand(function () { var self = Container.call(this); // Determine duck type based on level and DUCK_TYPE_CONFIG var duckType = 'normal'; var levelIdx = Math.max(0, Math.min(level - 1, DUCK_TYPE_CONFIG.length - 1)); var config = DUCK_TYPE_CONFIG[levelIdx]; var r = Math.random(); if (r < config.golden) { duckType = 'golden'; } else if (r < config.golden + config.armored) { duckType = 'armored'; } else if (r < config.golden + config.armored + config.mini) { duckType = 'mini'; } else if (Math.random() < 0.25) { duckType = 'fast'; } self.type = duckType; // Asset selection var duckAsset = null; if (duckType === 'golden') { duckAsset = self.attachAsset('duck_golden', { anchorX: 0.5, anchorY: 0.5 }); duckAsset.tint = 0xFFD700; } else if (duckType === 'armored') { duckAsset = self.attachAsset('duck_armored', { anchorX: 0.5, anchorY: 0.5 }); duckAsset.tint = 0x888888; } else if (duckType === 'mini') { duckAsset = self.attachAsset('duck_mini', { anchorX: 0.5, anchorY: 0.5 }); } else { // normal or fast var duckColors = ['duck_green', 'duck_blue', 'duck_red']; var colorIdx = Math.floor(Math.random() * duckColors.length); duckAsset = self.attachAsset(duckColors[colorIdx], { anchorX: 0.5, anchorY: 0.5 }); } // Set initial position and movement self.speed = 6 + Math.random() * 4; // Will be set by level self.angle = 0; // radians, will be set by level self.alive = true; self.hit = false; self.escaped = false; self.armor = duckType === 'armored' ? 2 : 1; // 2 hits for armored self.flashTween = null; // For animation self.flyTween = null; // --- Hitbox properties for all ducks (mini ducks are smaller, fast/golden slightly larger) --- self.getHitboxRadius = function () { var baseRadius = Math.max(duckAsset.width, duckAsset.height) * 0.5; if (self.type === 'mini') { return baseRadius * 0.55; } if (self.type === 'fast' || self.type === 'golden' || self.speed >= 6) { return baseRadius * 0.7; } return baseRadius * 0.55; }; // Head hitbox (top 1/3 of duck) self.isHeadshot = function (x, y) { var dx = self.x - x; var dy = self.y - y; var dist = Math.sqrt(dx * dx + dy * dy); var headRadius = duckAsset.height * 0.18; var headCenterY = self.y - duckAsset.height * 0.22; var headDist = Math.sqrt((self.x - x) * (self.x - x) + (headCenterY - y) * (headCenterY - y)); return headDist < headRadius; }; // Called every tick self.update = function () { if (!self.alive || self.hit) return; self.x += Math.cos(self.angle) * self.speed; self.y += Math.sin(self.angle) * self.speed; if (self.x < -100 || self.x > 2048 + 100 || self.y < -100 || self.y > 2732 + 100) { self.escaped = true; self.alive = false; } }; // Animate duck falling when hit self.fall = function (_onFinish) { self.alive = false; tween(self, { rotation: Math.PI * 1.5, y: self.y + 400 }, { duration: 700, easing: tween.cubicIn, onFinish: function onFinish() { if (_onFinish) _onFinish(); } }); }; // Armored duck: flash red on first hit self.flashRed = function () { if (self.flashTween) { self.flashTween.cancel(); } var origTint = duckAsset.tint; duckAsset.tint = 0xff2222; self.flashTween = tween(duckAsset, {}, { duration: 200, onFinish: function onFinish() { duckAsset.tint = 0x888888; self.flashTween = null; } }); }; return self; }); // Power-up collectible class var PowerupCollectible = Container.expand(function () { var self = Container.call(this); self.type = null; self.icon = null; self.radius = 60; self.speedY = -2 - Math.random() * 2; self.lifetime = 0; self.maxLifetime = 400; // ~6 seconds self.init = function (ptype, x, y) { self.type = ptype; var iconId = ptype === 'doubleBarrel' ? 'powerup_double' : ptype === 'slowTime' ? 'powerup_slow' : 'powerup_auto'; self.icon = self.attachAsset(iconId, { anchorX: 0.5, anchorY: 0.5 }); self.x = x; self.y = y; self.icon.scaleX = self.icon.scaleY = 1.1; self.visible = true; self.alpha = 0.92; }; self.update = function () { self.y += self.speedY; self.lifetime++; // Fade out near end of life if (self.lifetime > self.maxLifetime - 60) { self.alpha = Math.max(0, (self.maxLifetime - self.lifetime) / 60); } // Remove if out of bounds or expired if (self.y < 100 || self.lifetime > self.maxLifetime) { if (self.parent) self.parent.removeChild(self); if (typeof powerupCollectibles !== "undefined") { var idx = powerupCollectibles.indexOf(self); if (idx >= 0) powerupCollectibles.splice(idx, 1); } } }; // Check if tap is within radius self.isHit = function (x, y) { var dx = self.x - x; var dy = self.y - y; return dx * dx + dy * dy < self.radius * self.radius; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // sky blue }); /**** * Game Code ****/ // Use red as base, smaller // Use blue as base, gray tint // Use red as base, gold tint // --- Duck type probabilities and point values --- var DUCK_TYPE_CONFIG = [ // Level 1 { golden: 0.04, // 4% armored: 0.0, mini: 0.0 }, // Level 2 { golden: 0.06, armored: 0.08, mini: 0.04 }, // Level 3 { golden: 0.08, armored: 0.13, mini: 0.08 }, // Level 4 { golden: 0.10, armored: 0.18, mini: 0.13 }, // Level 5+ { golden: 0.13, armored: 0.22, mini: 0.18 }]; var DUCK_TYPE_POINTS = { normal: 10, golden: 50, armored: 20, mini: 25 }; // --- Power-up collectibles array --- // Ducks: 3 colors, 1 dog, 1 bullet, 1 background, 1 crosshair, 1 "laugh" dog // sky blue // Sounds // Music // Power-up icons and retro overlay var powerupCollectibles = []; // Spawn a power-up collectible at a random position function spawnPowerupCollectible(ptype) { var px = 200 + Math.random() * (2048 - 400); var py = 2732 - 200 - Math.random() * 800; var p = new PowerupCollectible(); p.init(ptype, px, py); powerupCollectibles.push(p); game.addChild(p); } // Activate a power-up function activatePowerup(ptype) { if (powerupActive[ptype]) return; powerupActive[ptype] = true; var duration = 0; if (ptype === 'doubleBarrel') duration = 7; if (ptype === 'slowTime') duration = 5; if (ptype === 'autoAim') duration = 3; powerupTimers[ptype] = duration; showPowerupIcon(ptype, duration); // Apply effect if (ptype === 'slowTime') { // Slow all ducks for (var i = 0; i < ducks.length; i++) { ducks[i].speed = ducks[i].speed * 0.35; } // Add retro overlay if (!slowTimeEffectOverlay) { slowTimeEffectOverlay = LK.getAsset('retro_overlay', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); slowTimeEffectOverlay.alpha = 0.22; game.addChild(slowTimeEffectOverlay); } } if (ptype === 'autoAim') { // No immediate effect, handled in shooting logic } if (ptype === 'doubleBarrel') { // No immediate effect, handled in shooting logic } // Set timeout to deactivate if (powerupTimeouts[ptype]) LK.clearTimeout(powerupTimeouts[ptype]); powerupTimeouts[ptype] = LK.setTimeout(function () { powerupActive[ptype] = false; powerupTimers[ptype] = 0; hidePowerupIcon(ptype); if (ptype === 'slowTime') { // Restore duck speed for (var i = 0; i < ducks.length; i++) { ducks[i].speed = ducks[i].speed / 0.35; } // Remove overlay if (slowTimeEffectOverlay && slowTimeEffectOverlay.parent) { slowTimeEffectOverlay.parent.removeChild(slowTimeEffectOverlay); slowTimeEffectOverlay = null; } } }, duration * 1000); } // Show power-up icon in GUI function showPowerupIcon(ptype, duration) { var iconId = ptype === 'doubleBarrel' ? 'powerup_double' : ptype === 'slowTime' ? 'powerup_slow' : 'powerup_auto'; var icon = LK.getAsset(iconId, { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); icon.scaleX = icon.scaleY = 1.1; var timerTxt = new Text2(duration + "s", { size: 60, fill: 0xffffff }); timerTxt.anchor.set(0.5, 0.5); icon.addChild(timerTxt); powerupIcons[ptype] = icon; // Place icons in top left, but not in 0-100px (menu area) var idx = ptype === 'doubleBarrel' ? 0 : ptype === 'slowTime' ? 1 : 2; icon.x = 120 + idx * 120; icon.y = 120; LK.gui.top.addChild(icon); icon._timerTxt = timerTxt; } // Hide power-up icon function hidePowerupIcon(ptype) { if (powerupIcons[ptype] && powerupIcons[ptype].parent) { powerupIcons[ptype].parent.removeChild(powerupIcons[ptype]); powerupIcons[ptype] = null; } } // Game state variables var ducks = []; var bullets = []; var level = 1; var ducksPerLevel = 5; var ducksToHit = 0; var ducksHit = 0; var ducksEscaped = 0; var maxBullets = 3; var bulletsLeft = 3; var timeLimit = 15; // seconds var timeLeft = 15; var gameActive = false; var waveActive = false; // Per-mode high score storage var highScoreClassic = storage.highScoreClassic || 0; var highScoreTimeAttack = storage.highScoreTimeAttack || 0; var highScoreEndless = storage.highScoreEndless || 0; var highScore = 0; // Will be set per mode in showMainMenu/startLevel var score = 0; var dog = null; var crosshair = null; var timerInterval = null; var levelText = null; var scoreText = null; var timerText = null; var bulletIcons = []; var waveResultTimeout = null; // Track missed shots for warning var missedShots = 0; var missedWarningShown = false; // --- Power-up State --- var powerupActive = { doubleBarrel: false, slowTime: false, autoAim: false }; var powerupTimers = { doubleBarrel: 0, slowTime: 0, autoAim: 0 }; var powerupIcons = { doubleBarrel: null, slowTime: null, autoAim: null }; var powerupTimeouts = { doubleBarrel: null, slowTime: null, autoAim: null }; // For slow time effect var slowTimeEffectOverlay = null; // --- Game Mode State --- var GAME_MODE_CLASSIC = "classic"; var GAME_MODE_TIMEATTACK = "timeattack"; var GAME_MODE_ENDLESS = "endless"; var gameMode = null; // Set by menu var mainMenuContainer = null; var endlessMissStreak = 0; var endlessGameOver = false; // --- Dog Selection State --- var DOG_OPTIONS = [{ id: "dodo", name: "Dodo", asset: "dog", laugh: "dog_laugh" }, { id: "kaiser", name: "Kaiser", asset: "dog_dalmatian", laugh: "laugh_d" }, { id: "maki", name: "Maki", asset: "dog_labrador", laugh: "laugh_l" }]; var selectedDogId = "dodo"; // default dog // Background var bg = LK.getAsset('bg', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(bg); // --- Main Menu UI --- function showMainMenu() { // Remove previous menu if present if (mainMenuContainer && mainMenuContainer.parent) { mainMenuContainer.parent.removeChild(mainMenuContainer); } mainMenuContainer = new Container(); // Dim background var menuBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 16, scaleY: 16, x: 2048 / 2, y: 2732 / 2 }); menuBg.alpha = 0.85; mainMenuContainer.addChild(menuBg); // Title var title = new Text2("What The Duck", { size: 220, fill: 0xFFD700, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); title.anchor.set(0.5, 0); title.x = 2048 / 2; title.y = 400; mainMenuContainer.addChild(title); // Classic Mode Button var btnClassic = new Text2("Classic Mode", { size: 120, fill: 0xFFFFFF, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); btnClassic.anchor.set(0.5, 0.5); btnClassic.x = 2048 / 2; btnClassic.y = 900; mainMenuContainer.addChild(btnClassic); // Time Attack Button var btnTime = new Text2("Time Attack", { size: 120, fill: 0xFFFFFF, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); btnTime.anchor.set(0.5, 0.5); btnTime.x = 2048 / 2; btnTime.y = 1100; mainMenuContainer.addChild(btnTime); // Endless Mode Button var btnEndless = new Text2("Endless Mode", { size: 120, fill: 0xFFFFFF, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); btnEndless.anchor.set(0.5, 0.5); btnEndless.x = 2048 / 2; btnEndless.y = 1300; mainMenuContainer.addChild(btnEndless); // Rules Button var btnRules = new Text2("Rules", { size: 100, fill: 0xFFFFFF, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); btnRules.anchor.set(0.5, 0.5); btnRules.x = 2048 / 2; btnRules.y = 1450; mainMenuContainer.addChild(btnRules); // Instructions var info = new Text2("Choose a mode to start!", { size: 80, fill: 0xFFFFFF, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); info.anchor.set(0.5, 0.5); info.x = 2048 / 2; info.y = 1550; mainMenuContainer.addChild(info); // --- Rules Popup --- var rulesPopup = null; function showRulesPopup() { if (rulesPopup && rulesPopup.parent) rulesPopup.parent.removeChild(rulesPopup); rulesPopup = new Container(); var popupBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 10, scaleY: 10, x: 2048 / 2, y: 2732 / 2 }); popupBg.alpha = 0.96; rulesPopup.addChild(popupBg); var rulesTitle = new Text2("Game Rules", { size: 120, fill: 0xFFD700, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); rulesTitle.anchor.set(0.5, 0); rulesTitle.x = 2048 / 2; rulesTitle.y = 400; rulesPopup.addChild(rulesTitle); var rulesText = new Text2("Classic Mode:\n" + "- Hit enough ducks with limited bullets to advance.\n" + "- Each level has more ducks and bullets.\n\n" + "Time Attack:\n" + "- 30 seconds to hit as many ducks as possible.\n" + "- Unlimited bullets.\n\n" + "Endless Mode:\n" + "- Ducks spawn forever, unlimited bullets.\n" + "- Game ends if you miss 3 times in a row.\n\n" + "Duck Types:\n" + "- Golden Duck: Fast, +50 points.\n" + "- Armored Duck: Needs 2 hits.\n" + "- Mini Fast Duck: Small, fast, +25 points.\n\n" + "Power-ups:\n" + "- Double Barrel: Hit 2 ducks at once.\n" + "- Slow Time: Ducks move slowly for 5s.\n" + "- Auto-Aim: Instantly hit nearest duck for 3s.\n\n" + "Tap power-up icons to collect. Good luck!", { size: 60, fill: 0xFF2222, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma", wordWrap: true, wordWrapWidth: 1600, align: "left" }); rulesText.anchor.set(0.5, 0); rulesText.x = 2048 / 2; rulesText.y = 550; rulesPopup.addChild(rulesText); var btnClose = new Text2("Close", { size: 100, fill: 0xFFD700, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); btnClose.anchor.set(0.5, 0.5); btnClose.x = 2048 / 2; btnClose.y = 2300; btnClose.interactive = true; btnClose.down = function () { if (rulesPopup.parent) rulesPopup.parent.removeChild(rulesPopup); }; rulesPopup.addChild(btnClose); game.addChild(rulesPopup); } // Button handlers // --- Dog Select Screen --- function showDogSelectScreen() { // Remove previous menu if present if (mainMenuContainer && mainMenuContainer.parent) { mainMenuContainer.parent.removeChild(mainMenuContainer); } mainMenuContainer = new Container(); // Dim background var menuBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 16, scaleY: 16, x: 2048 / 2, y: 2732 / 2 }); menuBg.alpha = 0.85; mainMenuContainer.addChild(menuBg); // Title var title = new Text2("Select Your Dog", { size: 180, fill: 0xFFD700, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); title.anchor.set(0.5, 0); title.x = 2048 / 2; title.y = 400; mainMenuContainer.addChild(title); // Dog options var spacing = 600; var startX = 2048 / 2 - spacing; var y = 1100; for (var i = 0; i < DOG_OPTIONS.length; i++) { (function (i) { var dogOpt = DOG_OPTIONS[i]; var asset = LK.getAsset(dogOpt.asset, { anchorX: 0.5, anchorY: 1, x: startX + i * spacing, y: y }); asset.scaleX = asset.scaleY = 1.1; mainMenuContainer.addChild(asset); var nameTxt = new Text2(dogOpt.name, { size: 100, fill: 0xFFFFFF, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); nameTxt.anchor.set(0.5, 0); nameTxt.x = asset.x; nameTxt.y = y + 30; mainMenuContainer.addChild(nameTxt); // Make asset interactive asset.interactive = true; asset.down = function () { selectedDogId = dogOpt.id; if (mainMenuContainer.parent) mainMenuContainer.parent.removeChild(mainMenuContainer); startLevel(); }; nameTxt.interactive = true; nameTxt.down = asset.down; })(i); } // Back button var btnBack = new Text2("Back", { size: 90, fill: 0xFFD700, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); btnBack.anchor.set(0.5, 0.5); btnBack.x = 2048 / 2; btnBack.y = 2100; btnBack.interactive = true; btnBack.down = function () { if (mainMenuContainer.parent) mainMenuContainer.parent.removeChild(mainMenuContainer); showMainMenu(); }; mainMenuContainer.addChild(btnBack); game.addChild(mainMenuContainer); } // Mode buttons now show dog select screen btnClassic.interactive = true; btnClassic.down = function () { gameMode = GAME_MODE_CLASSIC; showDogSelectScreen(); }; btnTime.interactive = true; btnTime.down = function () { gameMode = GAME_MODE_TIMEATTACK; showDogSelectScreen(); }; btnEndless.interactive = true; btnEndless.down = function () { gameMode = GAME_MODE_ENDLESS; showDogSelectScreen(); }; btnRules.interactive = true; btnRules.down = function () { showRulesPopup(); }; // Add to game game.addChild(mainMenuContainer); } // Show menu on load showMainMenu(); // Dog dog = new Dog(); game.addChild(dog); // Achievement popup (always on top) var achievementPopup = new AchievementPopup(); game.addChild(achievementPopup); // Crosshair crosshair = LK.getAsset('crosshair', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 }); crosshair.visible = false; game.addChild(crosshair); // GUI: Level, Score, Timer, Bullets levelText = new Text2('Level 1', { size: 90, fill: 0xFFF000 }); levelText.anchor.set(1, 0); // right align to match high score scoreText = new Text2('Score: 0', { size: 90, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); timerText = new Text2('Time: 15', { size: 90, fill: 0xFFFFFF }); timerText.anchor.set(0.5, 0); LK.gui.top.addChild(timerText); // High Score UI // Set highScore based on mode (default to classic) if (gameMode === GAME_MODE_TIMEATTACK) { highScore = highScoreTimeAttack; } else if (gameMode === GAME_MODE_ENDLESS) { highScore = highScoreEndless; } else { highScore = highScoreClassic; } var highScoreText = new Text2('High: ' + highScore, { size: 70, fill: 0xFFD700 }); highScoreText.anchor.set(1, 0); // Place high score in top right LK.gui.topRight.addChild(highScoreText); // Place level text directly under high score text in top right LK.gui.topRight.addChild(levelText); // Position GUI // Position GUI scoreText.x = 2048 / 2; timerText.x = 2048 * 3 / 4; highScoreText.x = 0; highScoreText.y = 0; levelText.x = 0; levelText.y = highScoreText.height + 10; // 10px gap below high score // Responsive UI: adjust font size and icon size for mobile function resizeUI() { var w = LK.width || 2048; var h = LK.height || 2732; var scale = Math.min(w / 2048, h / 2732); if (scoreText && scoreText.style) scoreText.style.size = 90 * scale; if (timerText && timerText.style) timerText.style.size = 90 * scale; if (highScoreText && highScoreText.style) highScoreText.style.size = 70 * scale; if (levelText && levelText.style) levelText.style.size = 90 * scale; for (var i = 0; i < bulletIcons.length; i++) { bulletIcons[i].scaleX = bulletIcons[i].scaleY = scale; } } LK.on('resize', resizeUI); resizeUI(); // Bullets GUI (now under the level text in the top right) // Show both bullet icons (bottom) and a text counter (top right under level) var bulletCountText = null; var bulletLabelText = null; function updateBulletIcons() { // Remove old icons for (var i = 0; i < bulletIcons.length; i++) { if (bulletIcons[i].parent) bulletIcons[i].parent.removeChild(bulletIcons[i]); } bulletIcons = []; // Draw new icons (up to 10 for visual clarity, then show text) var maxIcons = Math.min(10, bulletsLeft); // Center the icons under the screen var iconSpacing = 60; var totalWidth = (maxIcons - 1) * iconSpacing; var startX = 2048 / 2 - totalWidth / 2; for (var i = 0; i < maxIcons; i++) { var icon = LK.getAsset('bullet_icon', { anchorX: 0.5, anchorY: 0.5, x: startX + i * iconSpacing, y: 2732 - 120 }); LK.gui.bottom.addChild(icon); bulletIcons.push(icon); } // Remove old text if present if (bulletCountText && bulletCountText.parent) { bulletCountText.parent.removeChild(bulletCountText); } if (bulletLabelText && bulletLabelText.parent) { bulletLabelText.parent.removeChild(bulletLabelText); } // Add "Bullets" label above the count, now in top right under level bulletLabelText = new Text2("Bullets", { size: 60, fill: 0xFFF000, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); bulletLabelText.anchor.set(1, 0); // right align, top anchor bulletLabelText.x = 0; bulletLabelText.y = levelText.y + levelText.height + 10; LK.gui.topRight.addChild(bulletLabelText); // Show bullet count as text (always, for clarity) bulletCountText = new Text2("" + bulletsLeft, { size: 90, fill: 0xFFF000, font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma" }); bulletCountText.anchor.set(1, 0); // right align, top anchor bulletCountText.x = 0; bulletCountText.y = bulletLabelText.y + bulletLabelText.height + 2; LK.gui.topRight.addChild(bulletCountText); } // Start a new level function startLevel() { ducks = []; bullets = []; ducksHit = 0; ducksEscaped = 0; missedShots = 0; missedWarningShown = false; endlessMissStreak = 0; endlessGameOver = false; // --- Mode-specific setup --- if (gameMode === GAME_MODE_CLASSIC || !gameMode) { // Set ducksPerLevel and ducksToHit per level as per requirements if (level === 1) { ducksPerLevel = 6; ducksToHit = 3; } else if (level === 2) { ducksPerLevel = 12; ducksToHit = 6; } else if (level === 3) { ducksPerLevel = 20; ducksToHit = 12; } else if (level === 4) { ducksPerLevel = 30; ducksToHit = 18; } else if (level === 5) { ducksPerLevel = 40; ducksToHit = 20; } else { ducksPerLevel = Math.min(25, 15 + (level - 5) * 2); ducksToHit = Math.ceil(ducksPerLevel * 0.7); } // Set bulletsLeft per level: 10 for level 1, +10 bullets for every other level if (level === 1) { bulletsLeft = 10; } else if (level === 2) { bulletsLeft = 15 + 10; } else if (level === 3) { bulletsLeft = 20 + 10; } else if (level === 4) { bulletsLeft = 30 + 10; } else if (level === 5) { bulletsLeft = 32 + 10; } else { bulletsLeft = 32 + 2 * (level - 5) + 10; } maxBullets = bulletsLeft; timeLimit = Math.max(8, 15 - (level - 1)); timeLeft = timeLimit; levelText.setText('Level ' + level); } else if (gameMode === GAME_MODE_TIMEATTACK) { // 30 seconds, infinite ducks, infinite bullets, score = ducks hit ducksPerLevel = 99999; ducksToHit = 0; bulletsLeft = 99999; maxBullets = bulletsLeft; timeLimit = 30; timeLeft = 30; levelText.setText('Time Attack'); } else if (gameMode === GAME_MODE_ENDLESS) { // Infinite ducks, infinite bullets, game ends on 3 misses in a row ducksPerLevel = 99999; ducksToHit = 0; bulletsLeft = 99999; maxBullets = bulletsLeft; timeLimit = 99999; timeLeft = 99999; levelText.setText('Endless'); } // Set highScore for current mode if (gameMode === GAME_MODE_TIMEATTACK) { highScore = highScoreTimeAttack; } else if (gameMode === GAME_MODE_ENDLESS) { highScore = highScoreEndless; } else { highScore = highScoreClassic; } highScoreText.setText('High: ' + highScore); gameActive = true; waveActive = true; updateBulletIcons(); scoreText.setText('Score: ' + score); timerText.setText('Time: ' + timeLeft); // Duck spawn schedule and speed per level/mode var duckSpawnTimer = null; var ducksSpawned = 0; var ducksAtOnce = 1; var duckSpawnInterval = 3000; var duckSpeedMin = 2; var duckSpeedMax = 3.5; if (gameMode === GAME_MODE_CLASSIC || !gameMode) { if (level === 1) { ducksAtOnce = 1; duckSpawnInterval = 3000; duckSpeedMin = 2; duckSpeedMax = 3.5; } else if (level === 2) { ducksAtOnce = 1; duckSpawnInterval = 2000; duckSpeedMin = 3.5; duckSpeedMax = 5; } else if (level === 3) { ducksAtOnce = 2; duckSpawnInterval = 1800; duckSpeedMin = 4.2; duckSpeedMax = 5.8; } else if (level === 4) { ducksAtOnce = 3; duckSpawnInterval = 1500; duckSpeedMin = 5.5; duckSpeedMax = 7.2; } else if (level === 5) { ducksAtOnce = 4; duckSpawnInterval = 1200; duckSpeedMin = 6.5; duckSpeedMax = 8.5; } else { ducksAtOnce = 5; duckSpawnInterval = 1000; duckSpeedMin = 7.5 + (level - 5) * 0.5; duckSpeedMax = 9.5 + (level - 5) * 0.7; } } else if (gameMode === GAME_MODE_TIMEATTACK) { ducksAtOnce = 2; duckSpawnInterval = 900; duckSpeedMin = 4.5; duckSpeedMax = 7.5; } else if (gameMode === GAME_MODE_ENDLESS) { ducksAtOnce = 2; duckSpawnInterval = 900; duckSpeedMin = 4.5; duckSpeedMax = 7.5; } // Clear any previous duck spawn timer if (typeof duckSpawnTimer !== "undefined" && duckSpawnTimer) { LK.clearInterval(duckSpawnTimer); duckSpawnTimer = null; } ducksSpawned = 0; // Function to spawn ducks and power-ups function spawnDucks() { if (!gameActive || !waveActive) return; if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && ducksSpawned >= ducksPerLevel) { if (duckSpawnTimer) { LK.clearInterval(duckSpawnTimer); duckSpawnTimer = null; } return; } var ducksThisWave = gameMode === GAME_MODE_CLASSIC || !gameMode ? Math.min(ducksAtOnce, ducksPerLevel - ducksSpawned) : ducksAtOnce; for (var d = 0; d < ducksThisWave; d++) { var duck = new Duck(); // Randomize start edge: 0=left, 1=right, 2=bottom var edge = Math.floor(Math.random() * 3); var startX, startY, angle; if (edge === 0) { // left startX = -60; startY = 400 + Math.random() * (2732 - 800); angle = Math.random() * Math.PI / 3 - Math.PI / 6; // -30 to +30 deg } else if (edge === 1) { // right startX = 2048 + 60; startY = 400 + Math.random() * (2732 - 800); angle = Math.PI + (Math.random() * Math.PI / 3 - Math.PI / 6); // 150 to 210 deg } else { // bottom startX = 200 + Math.random() * (2048 - 400); startY = 2732 + 60; angle = -Math.PI / 2 + (Math.random() * Math.PI / 4 - Math.PI / 8); // -67 to -23 deg } duck.x = startX; duck.y = startY; duck.angle = angle; // Set duck speed and size based on type if (duck.type === 'golden') { duck.speed = duckSpeedMax + 2 + Math.random() * 1.5; } else if (duck.type === 'armored') { duck.speed = duckSpeedMin + Math.random() * (duckSpeedMax - duckSpeedMin) * 0.7; } else if (duck.type === 'mini') { duck.speed = duckSpeedMax + 1 + Math.random() * 1.2; duck.scaleX = duck.scaleY = 0.6 + Math.random() * 0.2; } else if (duck.type === 'fast') { duck.speed = duckSpeedMax + 0.7 + Math.random() * 0.7; } else { duck.speed = duckSpeedMin + Math.random() * (duckSpeedMax - duckSpeedMin); } ducks.push(duck); // Always add ducks above background and below crosshair/dog/UI game.addChild(duck); // Move dog and crosshair to top of display list if present if (dog && dog.parent) { dog.parent.removeChild(dog); game.addChild(dog); } if (crosshair && crosshair.parent) { crosshair.parent.removeChild(crosshair); game.addChild(crosshair); } LK.getSound('duck_fly').play(); ducksSpawned++; } // --- Power-up spawn logic --- // Power-ups can appear randomly (1 in 7 chance per spawn, but not if already active) if (Math.random() < 1 / 7) { var availablePowerups = []; if (!powerupActive.doubleBarrel) availablePowerups.push('doubleBarrel'); if (!powerupActive.slowTime) availablePowerups.push('slowTime'); if (!powerupActive.autoAim) availablePowerups.push('autoAim'); if (availablePowerups.length > 0) { var which = availablePowerups[Math.floor(Math.random() * availablePowerups.length)]; spawnPowerupCollectible(which); } } } spawnDucks(); // Spawn first batch immediately if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && ducksPerLevel > ducksAtOnce) { duckSpawnTimer = LK.setInterval(spawnDucks, duckSpawnInterval); } else if (gameMode === GAME_MODE_TIMEATTACK || gameMode === GAME_MODE_ENDLESS) { duckSpawnTimer = LK.setInterval(spawnDucks, duckSpawnInterval); } // Start timer if (timerInterval) LK.clearInterval(timerInterval); timerInterval = LK.setInterval(function () { if (!gameActive) return; if (gameMode === GAME_MODE_TIMEATTACK) { timeLeft--; timerText.setText('Time: ' + timeLeft); if (timeLeft <= 0) { endWave(); } } else if (gameMode === GAME_MODE_CLASSIC || !gameMode) { timeLeft--; timerText.setText('Time: ' + timeLeft); if (timeLeft <= 0) { endWave(); } } else if (gameMode === GAME_MODE_ENDLESS) { // No timer, but show "โ" or running time timerText.setText('Endless'); } }, 1000); } // End wave: check results, show dog if needed, advance or game over function endWave() { if (!waveActive) return; waveActive = false; gameActive = false; if (timerInterval) LK.clearInterval(timerInterval); // Remove remaining ducks for (var i = 0; i < ducks.length; i++) { if (ducks[i].parent) ducks[i].parent.removeChild(ducks[i]); } ducks = []; // --- Classic Mode --- if (gameMode === GAME_MODE_CLASSIC || !gameMode) { // Show dog if not enough ducks hit if (ducksHit < ducksToHit) { dog.laugh(2048 / 2, 2732 - 100); LK.effects.flashScreen(0xff0000, 800); // Show final score on Game Over LK.setTimeout(function () { var finalScoreText = new Text2('Final Score: ' + score, { size: 120, fill: 0xFFFF00 }); finalScoreText.anchor.set(0.5, 0.5); finalScoreText.x = 2048 / 2; finalScoreText.y = 2732 / 2; game.addChild(finalScoreText); LK.setTimeout(function () { if (finalScoreText.parent) finalScoreText.parent.removeChild(finalScoreText); LK.showGameOver(); }, 900); }, 400); // Game over after laugh waveResultTimeout = LK.setTimeout(function () { // handled above }, 1300); } else { // Advance to next level after short pause LK.effects.flashObject(levelText, 0x00ff00, 700); LK.setTimeout(function () { var finalScoreText = new Text2('Score: ' + score, { size: 120, fill: 0x00FF00 }); finalScoreText.anchor.set(0.5, 0.5); finalScoreText.x = 2048 / 2; finalScoreText.y = 2732 / 2; game.addChild(finalScoreText); LK.setTimeout(function () { if (finalScoreText.parent) finalScoreText.parent.removeChild(finalScoreText); level++; startLevel(); }, 700); }, 200); } } // --- Time Attack Mode --- else if (gameMode === GAME_MODE_TIMEATTACK) { // Show final score and return to menu LK.effects.flashScreen(0x00ffcc, 800); LK.setTimeout(function () { var finalScoreText = new Text2('Ducks Hit: ' + ducksHit, { size: 120, fill: 0x00FFCC }); finalScoreText.anchor.set(0.5, 0.5); finalScoreText.x = 2048 / 2; finalScoreText.y = 2732 / 2; game.addChild(finalScoreText); LK.setTimeout(function () { if (finalScoreText.parent) finalScoreText.parent.removeChild(finalScoreText); showMainMenu(); }, 1400); }, 400); } // --- Endless Mode --- else if (gameMode === GAME_MODE_ENDLESS) { endlessGameOver = true; LK.effects.flashScreen(0xff2222, 800); LK.setTimeout(function () { var finalScoreText = new Text2('Ducks Hit: ' + ducksHit, { size: 120, fill: 0xFF2222 }); finalScoreText.anchor.set(0.5, 0.5); finalScoreText.x = 2048 / 2; finalScoreText.y = 2732 / 2; game.addChild(finalScoreText); LK.setTimeout(function () { if (finalScoreText.parent) finalScoreText.parent.removeChild(finalScoreText); showMainMenu(); }, 1400); }, 400); } } // Handle tap/click to shoot game.down = function (x, y, obj) { if (!gameActive || !waveActive) return; // --- Power-up collectible check --- for (var i = powerupCollectibles.length - 1; i >= 0; i--) { var p = powerupCollectibles[i]; if (p.isHit(x, y)) { activatePowerup(p.type); if (p.parent) p.parent.removeChild(p); powerupCollectibles.splice(i, 1); LK.effects.flashObject(p, 0xffffff, 300); LK.getSound('hit').play(); return; } } // --- Bullets check --- if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && bulletsLeft <= 0) { LK.getSound('miss').play(); LK.effects.flashScreen(0x888888, 200); // Show reload animation (flash bullet icons red) for (var i = 0; i < bulletIcons.length; i++) { LK.effects.flashObject(bulletIcons[i], 0xff2222, 300); } // Optionally play reload sound if available // LK.getSound('reload').play(); return; } // Only decrement bullets and allow shooting if bulletsLeft > 0 (classic) if (gameMode === GAME_MODE_CLASSIC || !gameMode) { bulletsLeft--; updateBulletIcons(); if (bulletCountText && bulletCountText.setText) { bulletCountText.setText("Bullets: " + bulletsLeft); } // If bullets reach zero after this shot, trigger game over immediately if (bulletsLeft === 0) { LK.setTimeout(function () { LK.showGameOver(); }, 400); // short delay to allow last shot/crosshair to animate return; } } LK.getSound('shoot').play(); // Show crosshair at tap crosshair.x = x; crosshair.y = y; crosshair.visible = true; tween(crosshair, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { crosshair.visible = false; crosshair.alpha = 1; } }); // --- Power-up shooting logic --- // Auto-Aim: instantly hit nearest duck if (powerupActive.autoAim) { var nearestDuck = null; var minDist = 99999; for (var i = 0; i < ducks.length; i++) { var duck = ducks[i]; if (!duck.alive || duck.hit) continue; var dx = duck.x - x; var dy = duck.y - y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < minDist) { minDist = dist; nearestDuck = duck; } } if (nearestDuck) { // Hit the duck nearestDuck.hit = true; nearestDuck.fall(function () { if (nearestDuck.parent) nearestDuck.parent.removeChild(nearestDuck); if (dog) { dog.show(nearestDuck.x, 2732 - 100); LK.getSound('dog_laugh').play(); LK.setTimeout(function () { dog.hide(); }, 2000); } }); ducksHit++; var duckScore = 10; var achievementMsg = ''; var achievementColor = 0xFFD700; if (typeof nearestDuck.type !== "undefined" && nearestDuck.type === 'golden') { duckScore = 50; achievementMsg = 'Golden Duck! +50'; achievementColor = 0xFFD700; LK.effects.flashObject(nearestDuck, 0xffe066, 400); } else { LK.effects.flashObject(nearestDuck, 0xffff00, 300); } // Headshot bonus (auto-aim never headshots) score += duckScore; LK.getSound('hit').play(); if (typeof achievementPopup !== "undefined" && achievementMsg) { achievementPopup.show(achievementMsg, achievementColor); } if (ducksHit === 3 && typeof achievementPopup !== "undefined") { achievementPopup.show('Triple Hit!', 0x00FFCC); } if (ducksHit === ducksToHit && typeof achievementPopup !== "undefined") { achievementPopup.show('Sharp Shooter!', 0x00FF00); } if (score > highScore) { highScore = score; storage.highScore = highScore; if (typeof highScoreText !== "undefined") { highScoreText.setText('High: ' + highScore); } } scoreText.setText('Score: ' + score); if (score > highScore) { highScore = score; storage.highScore = highScore; if (typeof highScoreText !== "undefined") { highScoreText.setText('High: ' + highScore); } } if (gameMode === GAME_MODE_ENDLESS) { endlessMissStreak = 0; } // End wave check var aliveDucks = 0; for (var i = 0; i < ducks.length; i++) { if (ducks[i].alive) aliveDucks++; } if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && (aliveDucks === 0 && bulletsLeft === 0 || aliveDucks === 0 && typeof duckSpawnTimer !== "undefined" && (!duckSpawnTimer || ducks.length === 0))) { endWave(); } return; } } // Double Barrel: shoot a wide spread, hit up to 2 ducks if close if (powerupActive.doubleBarrel) { var hitDucks = []; for (var i = 0; i < ducks.length; i++) { var duck = ducks[i]; if (!duck.alive || duck.hit) continue; var dx = duck.x - x; var dy = duck.y - y; var dist = Math.sqrt(dx * dx + dy * dy); var hitRadius = typeof duck.getHitboxRadius === "function" ? duck.getHitboxRadius() * 1.5 : 120; if (dist < hitRadius) { hitDucks.push({ duck: duck, dist: dist }); } } // Sort by distance, hit up to 2 closest hitDucks.sort(function (a, b) { return a.dist - b.dist; }); hitDucks = hitDucks.slice(0, 2); if (hitDucks.length > 0) { for (var h = 0; h < hitDucks.length; h++) { var hitDuck = hitDucks[h].duck; hitDuck.hit = true; hitDuck.fall(function () { if (hitDuck.parent) hitDuck.parent.removeChild(hitDuck); if (dog) { dog.show(hitDuck.x, 2732 - 100); LK.getSound('dog_laugh').play(); LK.setTimeout(function () { dog.hide(); }, 2000); } }); ducksHit++; var duckScore = 10; var achievementMsg = ''; var achievementColor = 0xFFD700; if (typeof hitDuck.type !== "undefined" && hitDuck.type === 'golden') { duckScore = 50; achievementMsg = 'Golden Duck! +50'; achievementColor = 0xFFD700; LK.effects.flashObject(hitDuck, 0xffe066, 400); } else { LK.effects.flashObject(hitDuck, 0xffff00, 300); } // Headshot bonus var isHeadshot = typeof hitDuck.isHeadshot === "function" && hitDuck.isHeadshot(x, y); if (isHeadshot) { duckScore += 5; achievementMsg = 'Headshot! +' + duckScore; achievementColor = 0xFF2222; LK.effects.flashObject(hitDuck, 0xff2222, 400); } score += duckScore; LK.getSound('hit').play(); if (typeof achievementPopup !== "undefined" && achievementMsg) { achievementPopup.show(achievementMsg, achievementColor); } if (ducksHit === 3 && typeof achievementPopup !== "undefined") { achievementPopup.show('Triple Hit!', 0x00FFCC); } if (ducksHit === ducksToHit && typeof achievementPopup !== "undefined") { achievementPopup.show('Sharp Shooter!', 0x00FF00); } if (score > highScore) { highScore = score; storage.highScore = highScore; if (typeof highScoreText !== "undefined") { highScoreText.setText('High: ' + highScore); } } scoreText.setText('Score: ' + score); if (score > highScore) { highScore = score; storage.highScore = highScore; if (typeof highScoreText !== "undefined") { highScoreText.setText('High: ' + highScore); } } if (gameMode === GAME_MODE_ENDLESS) { endlessMissStreak = 0; } } // End wave check var aliveDucks = 0; for (var i = 0; i < ducks.length; i++) { if (ducks[i].alive) aliveDucks++; } if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && (aliveDucks === 0 && bulletsLeft === 0 || aliveDucks === 0 && typeof duckSpawnTimer !== "undefined" && (!duckSpawnTimer || ducks.length === 0))) { endWave(); } return; } } // --- Normal shooting logic --- // Check if a duck is hit (closest duck under tap, not already hit) var hitDuck = null; var minDist = 99999; for (var i = 0; i < ducks.length; i++) { var duck = ducks[i]; // Only allow hitting ducks that are alive and not hit yet if (!duck.alive || duck.hit) continue; var dx = duck.x - x; var dy = duck.y - y; var hitRadius = typeof duck.getHitboxRadius === "function" ? duck.getHitboxRadius() : 80; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < hitRadius && dist < minDist) { minDist = dist; hitDuck = duck; } } if (hitDuck) { // Armored duck: needs 2 hits if (hitDuck.type === 'armored' && hitDuck.armor > 1) { hitDuck.armor--; hitDuck.flashRed(); LK.effects.flashObject(hitDuck, 0xff2222, 200); LK.getSound('hit').play(); // No score yet, must hit again return; } // Mark duck as hit and start fall animation hitDuck.hit = true; hitDuck.fall(function () { if (hitDuck.parent) hitDuck.parent.removeChild(hitDuck); if (dog) { dog.show(hitDuck.x, 2732 - 100); LK.getSound('dog_laugh').play(); LK.setTimeout(function () { dog.hide(); }, 2000); } }); ducksHit++; // Score system: use DUCK_TYPE_POINTS if (typeof score === "undefined") score = 0; var duckScore = DUCK_TYPE_POINTS[hitDuck.type] || 10; var achievementMsg = ''; var achievementColor = 0xFFD700; if (hitDuck.type === 'golden') { achievementMsg = 'Golden Duck! +50'; achievementColor = 0xFFD700; LK.effects.flashObject(hitDuck, 0xffe066, 400); } else if (hitDuck.type === 'armored') { achievementMsg = 'Armored Duck! +20'; achievementColor = 0x888888; LK.effects.flashObject(hitDuck, 0xcccccc, 400); } else if (hitDuck.type === 'mini') { achievementMsg = 'Mini Fast Duck! +25'; achievementColor = 0x00e6ff; LK.effects.flashObject(hitDuck, 0x00e6ff, 350); } else { LK.effects.flashObject(hitDuck, 0xffff00, 300); } // Headshot bonus var isHeadshot = typeof hitDuck.isHeadshot === "function" && hitDuck.isHeadshot(x, y); if (isHeadshot) { duckScore += 5; achievementMsg = 'Headshot! +' + duckScore; achievementColor = 0xFF2222; LK.effects.flashObject(hitDuck, 0xff2222, 400); } score += duckScore; LK.getSound('hit').play(); // Show achievement popup if any if (typeof achievementPopup !== "undefined" && achievementMsg) { achievementPopup.show(achievementMsg, achievementColor); } // Triple hit achievement (3 ducks in 1 level) if (ducksHit === 3 && typeof achievementPopup !== "undefined") { achievementPopup.show('Triple Hit!', 0x00FFCC); } // Sharp shooter (all ducks hit) if (ducksHit === ducksToHit && typeof achievementPopup !== "undefined") { achievementPopup.show('Sharp Shooter!', 0x00FF00); } // Update high score if needed if (score > highScore) { highScore = score; if (gameMode === GAME_MODE_TIMEATTACK) { highScoreTimeAttack = highScore; storage.highScoreTimeAttack = highScore; } else if (gameMode === GAME_MODE_ENDLESS) { highScoreEndless = highScore; storage.highScoreEndless = highScore; } else { highScoreClassic = highScore; storage.highScoreClassic = highScore; } if (typeof highScoreText !== "undefined") { highScoreText.setText('High: ' + highScore); } } scoreText.setText('Score: ' + score); if (score > highScore) { highScore = score; storage.highScore = highScore; if (typeof highScoreText !== "undefined") { highScoreText.setText('High: ' + highScore); } } // Endless mode: reset miss streak if (gameMode === GAME_MODE_ENDLESS) { endlessMissStreak = 0; } } else { // Miss: do nothing to ducks, just play miss sound and flash LK.getSound('miss').play(); LK.effects.flashScreen(0x888888, 120); // Track missed shots and show warning after 20 misses missedShots++; if (missedShots >= 20 && !missedWarningShown) { missedWarningShown = true; if (typeof achievementPopup !== "undefined") { achievementPopup.show("Careful! 20 missed shots!", 0xFF2222); } } // Endless mode: increment miss streak, end game if 3 in a row if (gameMode === GAME_MODE_ENDLESS) { endlessMissStreak++; if (endlessMissStreak >= 3) { endWave(); return; } } } // If all ducks are gone or out of bullets, end wave (classic only) var aliveDucks = 0; for (var i = 0; i < ducks.length; i++) { if (ducks[i].alive) aliveDucks++; } // Only end wave if all ducks are gone AND no more ducks will spawn, or if out of bullets if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && (aliveDucks === 0 && bulletsLeft === 0 || aliveDucks === 0 && typeof duckSpawnTimer !== "undefined" && (!duckSpawnTimer || ducks.length === 0))) { endWave(); } }; // Move handler for crosshair (optional, for desktop) game.move = function (x, y, obj) { // Optionally show crosshair following finger/mouse // crosshair.x = x; // crosshair.y = y; }; // Main update loop game.update = function () { if (!gameActive || !waveActive) return; // Update ducks for (var i = ducks.length - 1; i >= 0; i--) { var duck = ducks[i]; duck.update(); if (duck.escaped && !duck.hit) { ducksEscaped++; if (duck.parent) duck.parent.removeChild(duck); ducks.splice(i, 1); // Endless mode: treat escaped duck as a miss if (gameMode === GAME_MODE_ENDLESS) { endlessMissStreak++; if (endlessMissStreak >= 3) { endWave(); return; } } } } // --- Power-up collectibles update --- for (var i = powerupCollectibles.length - 1; i >= 0; i--) { powerupCollectibles[i].update(); } // --- Power-up timers/icons update --- for (var ptype in powerupActive) { if (powerupActive[ptype] && powerupTimers[ptype] > 0) { // Decrement timer (per second, but update is 60fps) if (LK.ticks % 60 === 0) { powerupTimers[ptype]--; if (powerupIcons[ptype] && powerupIcons[ptype]._timerTxt) { powerupIcons[ptype]._timerTxt.setText(powerupTimers[ptype] + "s"); } } } } // If all ducks gone, end wave (classic only) var aliveDucks = 0; for (var i = 0; i < ducks.length; i++) { if (ducks[i].alive) aliveDucks++; } if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && aliveDucks === 0 && typeof duckSpawnTimer !== "undefined" && (!duckSpawnTimer || ducks.length === 0)) { endWave(); } }; // On game over, reset everything LK.on('gameover', function () { if (timerInterval) LK.clearInterval(timerInterval); if (waveResultTimeout) LK.clearTimeout(waveResultTimeout); if (typeof duckSpawnTimer !== "undefined" && duckSpawnTimer) { LK.clearInterval(duckSpawnTimer); duckSpawnTimer = null; } gameActive = false; waveActive = false; level = 1; ducks = []; bullets = []; ducksHit = 0; ducksEscaped = 0; // Reset bullets to 10 on gameover for level 1 bulletsLeft = 10; score = 0; updateBulletIcons(); levelText.setText('Level 1'); scoreText.setText('Score: ' + score); // Reset highScore to current mode's high score on gameover if (gameMode === GAME_MODE_TIMEATTACK) { highScore = highScoreTimeAttack; } else if (gameMode === GAME_MODE_ENDLESS) { highScore = highScoreEndless; } else { highScore = highScoreClassic; } if (typeof highScoreText !== "undefined") { highScoreText.setText('High: ' + highScore); } timerText.setText('Time: 15'); if (dog) dog.hide(); // Show main menu after short delay LK.setTimeout(function () { showMainMenu(); }, 1200); }); // On you win (if you want to add a win condition, e.g. after level 10) LK.on('youwin', function () { if (timerInterval) LK.clearInterval(timerInterval); if (waveResultTimeout) LK.clearTimeout(waveResultTimeout); if (typeof duckSpawnTimer !== "undefined" && duckSpawnTimer) { LK.clearInterval(duckSpawnTimer); duckSpawnTimer = null; } gameActive = false; waveActive = false; // Show win, then reset LK.setTimeout(function () { level = 1; ducks = []; bullets = []; ducksHit = 0; ducksEscaped = 0; bulletsLeft = 10; score = 0; updateBulletIcons(); levelText.setText('Level 1'); scoreText.setText('Score: ' + score); // Reset highScore to current mode's high score on youwin if (gameMode === GAME_MODE_TIMEATTACK) { highScore = highScoreTimeAttack; } else if (gameMode === GAME_MODE_ENDLESS) { highScore = highScoreEndless; } else { highScore = highScoreClassic; } timerText.setText('Time: 15'); if (dog) dog.hide(); showMainMenu(); }, 1800); }); // Start music LK.playMusic('bgm', { fade: { start: 0, end: 0.5, duration: 1200 } }); // Start first level // (Removed: now handled by showMainMenu)
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// Achievement popup class
var AchievementPopup = Container.expand(function () {
var self = Container.call(this);
var txt = new Text2('', {
size: 120,
fill: 0xFFD700
});
txt.anchor.set(0.5, 0.5);
self.addChild(txt);
self.visible = false;
self.show = function (message, color) {
txt.setText(message);
if (txt && txt.style) {
txt.style.fill = color || 0xFFD700;
}
self.x = 2048 / 2;
self.y = 2732 / 2 - 300;
self.visible = true;
self.alpha = 1;
tween(self, {
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
self.visible = false;
}
});
};
return self;
});
// Dog class
var Dog = Container.expand(function () {
var self = Container.call(this);
self.dogAsset = null;
self.laughAsset = null;
self.currentDogId = null;
// Helper to set dog asset based on selectedDogId
self.setDogAssets = function () {
// Remove old assets if any
if (self.dogAsset && self.dogAsset.parent) self.removeChild(self.dogAsset);
if (self.laughAsset && self.laughAsset.parent) self.removeChild(self.laughAsset);
self.dogAsset = null;
self.laughAsset = null;
var dogOpt = null;
for (var i = 0; i < DOG_OPTIONS.length; i++) {
if (DOG_OPTIONS[i].id === selectedDogId) {
dogOpt = DOG_OPTIONS[i];
break;
}
}
if (!dogOpt) dogOpt = DOG_OPTIONS[0];
self.currentDogId = dogOpt.id;
self.dogAsset = self.attachAsset(dogOpt.asset, {
anchorX: 0.5,
anchorY: 1
});
self.dogAsset.visible = false;
// Laugh asset will be created on demand in laugh()
};
self.setDogAssets();
self.visible = false;
// Show dog at (x, y)
self.show = function (x, y) {
// If dog changed, update assets
if (self.currentDogId !== selectedDogId) {
self.setDogAssets();
}
self.x = x;
self.y = y;
self.visible = true;
if (self.dogAsset) self.dogAsset.visible = true;
if (self.laughAsset) self.laughAsset.visible = false;
// Optionally, you could add a little "pop up" animation here
// For example, tween the dog from below the screen to y
var startY = 2732 + (self.dogAsset ? self.dogAsset.height : 180);
self.y = startY;
tween(self, {
y: y
}, {
duration: 350,
easing: tween.cubicOut
});
};
// Hide dog
self.hide = function () {
var endY = 2732 + (self.dogAsset ? self.dogAsset.height : 180);
tween(self, {
y: endY
}, {
duration: 350,
easing: tween.cubicIn,
onFinish: function onFinish() {
self.visible = false;
if (self.dogAsset) self.dogAsset.visible = false;
}
});
};
// Show laugh animation
self.laugh = function (x, y) {
// If dog changed, update assets
if (self.currentDogId !== selectedDogId) {
self.setDogAssets();
}
self.x = x;
self.y = y;
self.visible = true;
if (self.dogAsset) self.dogAsset.visible = false;
// Find laugh asset id for this dog
var dogOpt = null;
for (var i = 0; i < DOG_OPTIONS.length; i++) {
if (DOG_OPTIONS[i].id === selectedDogId) {
dogOpt = DOG_OPTIONS[i];
break;
}
}
if (!dogOpt) dogOpt = DOG_OPTIONS[0];
var laughId = dogOpt.laugh || "dog_laugh";
if (!self.laughAsset || self.laughAsset._assetId !== laughId) {
if (self.laughAsset && self.laughAsset.parent) self.removeChild(self.laughAsset);
self.laughAsset = self.attachAsset(laughId, {
anchorX: 0.5,
anchorY: 1
});
self.laughAsset._assetId = laughId;
}
self.laughAsset.visible = true;
// Play laugh sound (use default for all)
LK.getSound('dog_laugh').play();
// Hide after 1.2s
LK.setTimeout(function () {
self.laughAsset.visible = false;
self.hide();
}, 1200);
};
return self;
});
// Duck class
var Duck = Container.expand(function () {
var self = Container.call(this);
// Determine duck type based on level and DUCK_TYPE_CONFIG
var duckType = 'normal';
var levelIdx = Math.max(0, Math.min(level - 1, DUCK_TYPE_CONFIG.length - 1));
var config = DUCK_TYPE_CONFIG[levelIdx];
var r = Math.random();
if (r < config.golden) {
duckType = 'golden';
} else if (r < config.golden + config.armored) {
duckType = 'armored';
} else if (r < config.golden + config.armored + config.mini) {
duckType = 'mini';
} else if (Math.random() < 0.25) {
duckType = 'fast';
}
self.type = duckType;
// Asset selection
var duckAsset = null;
if (duckType === 'golden') {
duckAsset = self.attachAsset('duck_golden', {
anchorX: 0.5,
anchorY: 0.5
});
duckAsset.tint = 0xFFD700;
} else if (duckType === 'armored') {
duckAsset = self.attachAsset('duck_armored', {
anchorX: 0.5,
anchorY: 0.5
});
duckAsset.tint = 0x888888;
} else if (duckType === 'mini') {
duckAsset = self.attachAsset('duck_mini', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
// normal or fast
var duckColors = ['duck_green', 'duck_blue', 'duck_red'];
var colorIdx = Math.floor(Math.random() * duckColors.length);
duckAsset = self.attachAsset(duckColors[colorIdx], {
anchorX: 0.5,
anchorY: 0.5
});
}
// Set initial position and movement
self.speed = 6 + Math.random() * 4; // Will be set by level
self.angle = 0; // radians, will be set by level
self.alive = true;
self.hit = false;
self.escaped = false;
self.armor = duckType === 'armored' ? 2 : 1; // 2 hits for armored
self.flashTween = null;
// For animation
self.flyTween = null;
// --- Hitbox properties for all ducks (mini ducks are smaller, fast/golden slightly larger) ---
self.getHitboxRadius = function () {
var baseRadius = Math.max(duckAsset.width, duckAsset.height) * 0.5;
if (self.type === 'mini') {
return baseRadius * 0.55;
}
if (self.type === 'fast' || self.type === 'golden' || self.speed >= 6) {
return baseRadius * 0.7;
}
return baseRadius * 0.55;
};
// Head hitbox (top 1/3 of duck)
self.isHeadshot = function (x, y) {
var dx = self.x - x;
var dy = self.y - y;
var dist = Math.sqrt(dx * dx + dy * dy);
var headRadius = duckAsset.height * 0.18;
var headCenterY = self.y - duckAsset.height * 0.22;
var headDist = Math.sqrt((self.x - x) * (self.x - x) + (headCenterY - y) * (headCenterY - y));
return headDist < headRadius;
};
// Called every tick
self.update = function () {
if (!self.alive || self.hit) return;
self.x += Math.cos(self.angle) * self.speed;
self.y += Math.sin(self.angle) * self.speed;
if (self.x < -100 || self.x > 2048 + 100 || self.y < -100 || self.y > 2732 + 100) {
self.escaped = true;
self.alive = false;
}
};
// Animate duck falling when hit
self.fall = function (_onFinish) {
self.alive = false;
tween(self, {
rotation: Math.PI * 1.5,
y: self.y + 400
}, {
duration: 700,
easing: tween.cubicIn,
onFinish: function onFinish() {
if (_onFinish) _onFinish();
}
});
};
// Armored duck: flash red on first hit
self.flashRed = function () {
if (self.flashTween) {
self.flashTween.cancel();
}
var origTint = duckAsset.tint;
duckAsset.tint = 0xff2222;
self.flashTween = tween(duckAsset, {}, {
duration: 200,
onFinish: function onFinish() {
duckAsset.tint = 0x888888;
self.flashTween = null;
}
});
};
return self;
});
// Power-up collectible class
var PowerupCollectible = Container.expand(function () {
var self = Container.call(this);
self.type = null;
self.icon = null;
self.radius = 60;
self.speedY = -2 - Math.random() * 2;
self.lifetime = 0;
self.maxLifetime = 400; // ~6 seconds
self.init = function (ptype, x, y) {
self.type = ptype;
var iconId = ptype === 'doubleBarrel' ? 'powerup_double' : ptype === 'slowTime' ? 'powerup_slow' : 'powerup_auto';
self.icon = self.attachAsset(iconId, {
anchorX: 0.5,
anchorY: 0.5
});
self.x = x;
self.y = y;
self.icon.scaleX = self.icon.scaleY = 1.1;
self.visible = true;
self.alpha = 0.92;
};
self.update = function () {
self.y += self.speedY;
self.lifetime++;
// Fade out near end of life
if (self.lifetime > self.maxLifetime - 60) {
self.alpha = Math.max(0, (self.maxLifetime - self.lifetime) / 60);
}
// Remove if out of bounds or expired
if (self.y < 100 || self.lifetime > self.maxLifetime) {
if (self.parent) self.parent.removeChild(self);
if (typeof powerupCollectibles !== "undefined") {
var idx = powerupCollectibles.indexOf(self);
if (idx >= 0) powerupCollectibles.splice(idx, 1);
}
}
};
// Check if tap is within radius
self.isHit = function (x, y) {
var dx = self.x - x;
var dy = self.y - y;
return dx * dx + dy * dy < self.radius * self.radius;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // sky blue
});
/****
* Game Code
****/
// Use red as base, smaller
// Use blue as base, gray tint
// Use red as base, gold tint
// --- Duck type probabilities and point values ---
var DUCK_TYPE_CONFIG = [
// Level 1
{
golden: 0.04,
// 4%
armored: 0.0,
mini: 0.0
},
// Level 2
{
golden: 0.06,
armored: 0.08,
mini: 0.04
},
// Level 3
{
golden: 0.08,
armored: 0.13,
mini: 0.08
},
// Level 4
{
golden: 0.10,
armored: 0.18,
mini: 0.13
},
// Level 5+
{
golden: 0.13,
armored: 0.22,
mini: 0.18
}];
var DUCK_TYPE_POINTS = {
normal: 10,
golden: 50,
armored: 20,
mini: 25
};
// --- Power-up collectibles array ---
// Ducks: 3 colors, 1 dog, 1 bullet, 1 background, 1 crosshair, 1 "laugh" dog
// sky blue
// Sounds
// Music
// Power-up icons and retro overlay
var powerupCollectibles = [];
// Spawn a power-up collectible at a random position
function spawnPowerupCollectible(ptype) {
var px = 200 + Math.random() * (2048 - 400);
var py = 2732 - 200 - Math.random() * 800;
var p = new PowerupCollectible();
p.init(ptype, px, py);
powerupCollectibles.push(p);
game.addChild(p);
}
// Activate a power-up
function activatePowerup(ptype) {
if (powerupActive[ptype]) return;
powerupActive[ptype] = true;
var duration = 0;
if (ptype === 'doubleBarrel') duration = 7;
if (ptype === 'slowTime') duration = 5;
if (ptype === 'autoAim') duration = 3;
powerupTimers[ptype] = duration;
showPowerupIcon(ptype, duration);
// Apply effect
if (ptype === 'slowTime') {
// Slow all ducks
for (var i = 0; i < ducks.length; i++) {
ducks[i].speed = ducks[i].speed * 0.35;
}
// Add retro overlay
if (!slowTimeEffectOverlay) {
slowTimeEffectOverlay = LK.getAsset('retro_overlay', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
slowTimeEffectOverlay.alpha = 0.22;
game.addChild(slowTimeEffectOverlay);
}
}
if (ptype === 'autoAim') {
// No immediate effect, handled in shooting logic
}
if (ptype === 'doubleBarrel') {
// No immediate effect, handled in shooting logic
}
// Set timeout to deactivate
if (powerupTimeouts[ptype]) LK.clearTimeout(powerupTimeouts[ptype]);
powerupTimeouts[ptype] = LK.setTimeout(function () {
powerupActive[ptype] = false;
powerupTimers[ptype] = 0;
hidePowerupIcon(ptype);
if (ptype === 'slowTime') {
// Restore duck speed
for (var i = 0; i < ducks.length; i++) {
ducks[i].speed = ducks[i].speed / 0.35;
}
// Remove overlay
if (slowTimeEffectOverlay && slowTimeEffectOverlay.parent) {
slowTimeEffectOverlay.parent.removeChild(slowTimeEffectOverlay);
slowTimeEffectOverlay = null;
}
}
}, duration * 1000);
}
// Show power-up icon in GUI
function showPowerupIcon(ptype, duration) {
var iconId = ptype === 'doubleBarrel' ? 'powerup_double' : ptype === 'slowTime' ? 'powerup_slow' : 'powerup_auto';
var icon = LK.getAsset(iconId, {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
icon.scaleX = icon.scaleY = 1.1;
var timerTxt = new Text2(duration + "s", {
size: 60,
fill: 0xffffff
});
timerTxt.anchor.set(0.5, 0.5);
icon.addChild(timerTxt);
powerupIcons[ptype] = icon;
// Place icons in top left, but not in 0-100px (menu area)
var idx = ptype === 'doubleBarrel' ? 0 : ptype === 'slowTime' ? 1 : 2;
icon.x = 120 + idx * 120;
icon.y = 120;
LK.gui.top.addChild(icon);
icon._timerTxt = timerTxt;
}
// Hide power-up icon
function hidePowerupIcon(ptype) {
if (powerupIcons[ptype] && powerupIcons[ptype].parent) {
powerupIcons[ptype].parent.removeChild(powerupIcons[ptype]);
powerupIcons[ptype] = null;
}
}
// Game state variables
var ducks = [];
var bullets = [];
var level = 1;
var ducksPerLevel = 5;
var ducksToHit = 0;
var ducksHit = 0;
var ducksEscaped = 0;
var maxBullets = 3;
var bulletsLeft = 3;
var timeLimit = 15; // seconds
var timeLeft = 15;
var gameActive = false;
var waveActive = false;
// Per-mode high score storage
var highScoreClassic = storage.highScoreClassic || 0;
var highScoreTimeAttack = storage.highScoreTimeAttack || 0;
var highScoreEndless = storage.highScoreEndless || 0;
var highScore = 0; // Will be set per mode in showMainMenu/startLevel
var score = 0;
var dog = null;
var crosshair = null;
var timerInterval = null;
var levelText = null;
var scoreText = null;
var timerText = null;
var bulletIcons = [];
var waveResultTimeout = null;
// Track missed shots for warning
var missedShots = 0;
var missedWarningShown = false;
// --- Power-up State ---
var powerupActive = {
doubleBarrel: false,
slowTime: false,
autoAim: false
};
var powerupTimers = {
doubleBarrel: 0,
slowTime: 0,
autoAim: 0
};
var powerupIcons = {
doubleBarrel: null,
slowTime: null,
autoAim: null
};
var powerupTimeouts = {
doubleBarrel: null,
slowTime: null,
autoAim: null
};
// For slow time effect
var slowTimeEffectOverlay = null;
// --- Game Mode State ---
var GAME_MODE_CLASSIC = "classic";
var GAME_MODE_TIMEATTACK = "timeattack";
var GAME_MODE_ENDLESS = "endless";
var gameMode = null; // Set by menu
var mainMenuContainer = null;
var endlessMissStreak = 0;
var endlessGameOver = false;
// --- Dog Selection State ---
var DOG_OPTIONS = [{
id: "dodo",
name: "Dodo",
asset: "dog",
laugh: "dog_laugh"
}, {
id: "kaiser",
name: "Kaiser",
asset: "dog_dalmatian",
laugh: "laugh_d"
}, {
id: "maki",
name: "Maki",
asset: "dog_labrador",
laugh: "laugh_l"
}];
var selectedDogId = "dodo"; // default dog
// Background
var bg = LK.getAsset('bg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(bg);
// --- Main Menu UI ---
function showMainMenu() {
// Remove previous menu if present
if (mainMenuContainer && mainMenuContainer.parent) {
mainMenuContainer.parent.removeChild(mainMenuContainer);
}
mainMenuContainer = new Container();
// Dim background
var menuBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 16,
scaleY: 16,
x: 2048 / 2,
y: 2732 / 2
});
menuBg.alpha = 0.85;
mainMenuContainer.addChild(menuBg);
// Title
var title = new Text2("What The Duck", {
size: 220,
fill: 0xFFD700,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
title.anchor.set(0.5, 0);
title.x = 2048 / 2;
title.y = 400;
mainMenuContainer.addChild(title);
// Classic Mode Button
var btnClassic = new Text2("Classic Mode", {
size: 120,
fill: 0xFFFFFF,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
btnClassic.anchor.set(0.5, 0.5);
btnClassic.x = 2048 / 2;
btnClassic.y = 900;
mainMenuContainer.addChild(btnClassic);
// Time Attack Button
var btnTime = new Text2("Time Attack", {
size: 120,
fill: 0xFFFFFF,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
btnTime.anchor.set(0.5, 0.5);
btnTime.x = 2048 / 2;
btnTime.y = 1100;
mainMenuContainer.addChild(btnTime);
// Endless Mode Button
var btnEndless = new Text2("Endless Mode", {
size: 120,
fill: 0xFFFFFF,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
btnEndless.anchor.set(0.5, 0.5);
btnEndless.x = 2048 / 2;
btnEndless.y = 1300;
mainMenuContainer.addChild(btnEndless);
// Rules Button
var btnRules = new Text2("Rules", {
size: 100,
fill: 0xFFFFFF,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
btnRules.anchor.set(0.5, 0.5);
btnRules.x = 2048 / 2;
btnRules.y = 1450;
mainMenuContainer.addChild(btnRules);
// Instructions
var info = new Text2("Choose a mode to start!", {
size: 80,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
info.anchor.set(0.5, 0.5);
info.x = 2048 / 2;
info.y = 1550;
mainMenuContainer.addChild(info);
// --- Rules Popup ---
var rulesPopup = null;
function showRulesPopup() {
if (rulesPopup && rulesPopup.parent) rulesPopup.parent.removeChild(rulesPopup);
rulesPopup = new Container();
var popupBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 10,
scaleY: 10,
x: 2048 / 2,
y: 2732 / 2
});
popupBg.alpha = 0.96;
rulesPopup.addChild(popupBg);
var rulesTitle = new Text2("Game Rules", {
size: 120,
fill: 0xFFD700,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
rulesTitle.anchor.set(0.5, 0);
rulesTitle.x = 2048 / 2;
rulesTitle.y = 400;
rulesPopup.addChild(rulesTitle);
var rulesText = new Text2("Classic Mode:\n" + "- Hit enough ducks with limited bullets to advance.\n" + "- Each level has more ducks and bullets.\n\n" + "Time Attack:\n" + "- 30 seconds to hit as many ducks as possible.\n" + "- Unlimited bullets.\n\n" + "Endless Mode:\n" + "- Ducks spawn forever, unlimited bullets.\n" + "- Game ends if you miss 3 times in a row.\n\n" + "Duck Types:\n" + "- Golden Duck: Fast, +50 points.\n" + "- Armored Duck: Needs 2 hits.\n" + "- Mini Fast Duck: Small, fast, +25 points.\n\n" + "Power-ups:\n" + "- Double Barrel: Hit 2 ducks at once.\n" + "- Slow Time: Ducks move slowly for 5s.\n" + "- Auto-Aim: Instantly hit nearest duck for 3s.\n\n" + "Tap power-up icons to collect. Good luck!", {
size: 60,
fill: 0xFF2222,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma",
wordWrap: true,
wordWrapWidth: 1600,
align: "left"
});
rulesText.anchor.set(0.5, 0);
rulesText.x = 2048 / 2;
rulesText.y = 550;
rulesPopup.addChild(rulesText);
var btnClose = new Text2("Close", {
size: 100,
fill: 0xFFD700,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
btnClose.anchor.set(0.5, 0.5);
btnClose.x = 2048 / 2;
btnClose.y = 2300;
btnClose.interactive = true;
btnClose.down = function () {
if (rulesPopup.parent) rulesPopup.parent.removeChild(rulesPopup);
};
rulesPopup.addChild(btnClose);
game.addChild(rulesPopup);
}
// Button handlers
// --- Dog Select Screen ---
function showDogSelectScreen() {
// Remove previous menu if present
if (mainMenuContainer && mainMenuContainer.parent) {
mainMenuContainer.parent.removeChild(mainMenuContainer);
}
mainMenuContainer = new Container();
// Dim background
var menuBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 16,
scaleY: 16,
x: 2048 / 2,
y: 2732 / 2
});
menuBg.alpha = 0.85;
mainMenuContainer.addChild(menuBg);
// Title
var title = new Text2("Select Your Dog", {
size: 180,
fill: 0xFFD700,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
title.anchor.set(0.5, 0);
title.x = 2048 / 2;
title.y = 400;
mainMenuContainer.addChild(title);
// Dog options
var spacing = 600;
var startX = 2048 / 2 - spacing;
var y = 1100;
for (var i = 0; i < DOG_OPTIONS.length; i++) {
(function (i) {
var dogOpt = DOG_OPTIONS[i];
var asset = LK.getAsset(dogOpt.asset, {
anchorX: 0.5,
anchorY: 1,
x: startX + i * spacing,
y: y
});
asset.scaleX = asset.scaleY = 1.1;
mainMenuContainer.addChild(asset);
var nameTxt = new Text2(dogOpt.name, {
size: 100,
fill: 0xFFFFFF,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
nameTxt.anchor.set(0.5, 0);
nameTxt.x = asset.x;
nameTxt.y = y + 30;
mainMenuContainer.addChild(nameTxt);
// Make asset interactive
asset.interactive = true;
asset.down = function () {
selectedDogId = dogOpt.id;
if (mainMenuContainer.parent) mainMenuContainer.parent.removeChild(mainMenuContainer);
startLevel();
};
nameTxt.interactive = true;
nameTxt.down = asset.down;
})(i);
}
// Back button
var btnBack = new Text2("Back", {
size: 90,
fill: 0xFFD700,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
btnBack.anchor.set(0.5, 0.5);
btnBack.x = 2048 / 2;
btnBack.y = 2100;
btnBack.interactive = true;
btnBack.down = function () {
if (mainMenuContainer.parent) mainMenuContainer.parent.removeChild(mainMenuContainer);
showMainMenu();
};
mainMenuContainer.addChild(btnBack);
game.addChild(mainMenuContainer);
}
// Mode buttons now show dog select screen
btnClassic.interactive = true;
btnClassic.down = function () {
gameMode = GAME_MODE_CLASSIC;
showDogSelectScreen();
};
btnTime.interactive = true;
btnTime.down = function () {
gameMode = GAME_MODE_TIMEATTACK;
showDogSelectScreen();
};
btnEndless.interactive = true;
btnEndless.down = function () {
gameMode = GAME_MODE_ENDLESS;
showDogSelectScreen();
};
btnRules.interactive = true;
btnRules.down = function () {
showRulesPopup();
};
// Add to game
game.addChild(mainMenuContainer);
}
// Show menu on load
showMainMenu();
// Dog
dog = new Dog();
game.addChild(dog);
// Achievement popup (always on top)
var achievementPopup = new AchievementPopup();
game.addChild(achievementPopup);
// Crosshair
crosshair = LK.getAsset('crosshair', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
});
crosshair.visible = false;
game.addChild(crosshair);
// GUI: Level, Score, Timer, Bullets
levelText = new Text2('Level 1', {
size: 90,
fill: 0xFFF000
});
levelText.anchor.set(1, 0); // right align to match high score
scoreText = new Text2('Score: 0', {
size: 90,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
timerText = new Text2('Time: 15', {
size: 90,
fill: 0xFFFFFF
});
timerText.anchor.set(0.5, 0);
LK.gui.top.addChild(timerText);
// High Score UI
// Set highScore based on mode (default to classic)
if (gameMode === GAME_MODE_TIMEATTACK) {
highScore = highScoreTimeAttack;
} else if (gameMode === GAME_MODE_ENDLESS) {
highScore = highScoreEndless;
} else {
highScore = highScoreClassic;
}
var highScoreText = new Text2('High: ' + highScore, {
size: 70,
fill: 0xFFD700
});
highScoreText.anchor.set(1, 0);
// Place high score in top right
LK.gui.topRight.addChild(highScoreText);
// Place level text directly under high score text in top right
LK.gui.topRight.addChild(levelText);
// Position GUI
// Position GUI
scoreText.x = 2048 / 2;
timerText.x = 2048 * 3 / 4;
highScoreText.x = 0;
highScoreText.y = 0;
levelText.x = 0;
levelText.y = highScoreText.height + 10; // 10px gap below high score
// Responsive UI: adjust font size and icon size for mobile
function resizeUI() {
var w = LK.width || 2048;
var h = LK.height || 2732;
var scale = Math.min(w / 2048, h / 2732);
if (scoreText && scoreText.style) scoreText.style.size = 90 * scale;
if (timerText && timerText.style) timerText.style.size = 90 * scale;
if (highScoreText && highScoreText.style) highScoreText.style.size = 70 * scale;
if (levelText && levelText.style) levelText.style.size = 90 * scale;
for (var i = 0; i < bulletIcons.length; i++) {
bulletIcons[i].scaleX = bulletIcons[i].scaleY = scale;
}
}
LK.on('resize', resizeUI);
resizeUI();
// Bullets GUI (now under the level text in the top right)
// Show both bullet icons (bottom) and a text counter (top right under level)
var bulletCountText = null;
var bulletLabelText = null;
function updateBulletIcons() {
// Remove old icons
for (var i = 0; i < bulletIcons.length; i++) {
if (bulletIcons[i].parent) bulletIcons[i].parent.removeChild(bulletIcons[i]);
}
bulletIcons = [];
// Draw new icons (up to 10 for visual clarity, then show text)
var maxIcons = Math.min(10, bulletsLeft);
// Center the icons under the screen
var iconSpacing = 60;
var totalWidth = (maxIcons - 1) * iconSpacing;
var startX = 2048 / 2 - totalWidth / 2;
for (var i = 0; i < maxIcons; i++) {
var icon = LK.getAsset('bullet_icon', {
anchorX: 0.5,
anchorY: 0.5,
x: startX + i * iconSpacing,
y: 2732 - 120
});
LK.gui.bottom.addChild(icon);
bulletIcons.push(icon);
}
// Remove old text if present
if (bulletCountText && bulletCountText.parent) {
bulletCountText.parent.removeChild(bulletCountText);
}
if (bulletLabelText && bulletLabelText.parent) {
bulletLabelText.parent.removeChild(bulletLabelText);
}
// Add "Bullets" label above the count, now in top right under level
bulletLabelText = new Text2("Bullets", {
size: 60,
fill: 0xFFF000,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
bulletLabelText.anchor.set(1, 0); // right align, top anchor
bulletLabelText.x = 0;
bulletLabelText.y = levelText.y + levelText.height + 10;
LK.gui.topRight.addChild(bulletLabelText);
// Show bullet count as text (always, for clarity)
bulletCountText = new Text2("" + bulletsLeft, {
size: 90,
fill: 0xFFF000,
font: "'PressStart2P','GillSans-Bold',Impact,'Arial Black',Tahoma"
});
bulletCountText.anchor.set(1, 0); // right align, top anchor
bulletCountText.x = 0;
bulletCountText.y = bulletLabelText.y + bulletLabelText.height + 2;
LK.gui.topRight.addChild(bulletCountText);
}
// Start a new level
function startLevel() {
ducks = [];
bullets = [];
ducksHit = 0;
ducksEscaped = 0;
missedShots = 0;
missedWarningShown = false;
endlessMissStreak = 0;
endlessGameOver = false;
// --- Mode-specific setup ---
if (gameMode === GAME_MODE_CLASSIC || !gameMode) {
// Set ducksPerLevel and ducksToHit per level as per requirements
if (level === 1) {
ducksPerLevel = 6;
ducksToHit = 3;
} else if (level === 2) {
ducksPerLevel = 12;
ducksToHit = 6;
} else if (level === 3) {
ducksPerLevel = 20;
ducksToHit = 12;
} else if (level === 4) {
ducksPerLevel = 30;
ducksToHit = 18;
} else if (level === 5) {
ducksPerLevel = 40;
ducksToHit = 20;
} else {
ducksPerLevel = Math.min(25, 15 + (level - 5) * 2);
ducksToHit = Math.ceil(ducksPerLevel * 0.7);
}
// Set bulletsLeft per level: 10 for level 1, +10 bullets for every other level
if (level === 1) {
bulletsLeft = 10;
} else if (level === 2) {
bulletsLeft = 15 + 10;
} else if (level === 3) {
bulletsLeft = 20 + 10;
} else if (level === 4) {
bulletsLeft = 30 + 10;
} else if (level === 5) {
bulletsLeft = 32 + 10;
} else {
bulletsLeft = 32 + 2 * (level - 5) + 10;
}
maxBullets = bulletsLeft;
timeLimit = Math.max(8, 15 - (level - 1));
timeLeft = timeLimit;
levelText.setText('Level ' + level);
} else if (gameMode === GAME_MODE_TIMEATTACK) {
// 30 seconds, infinite ducks, infinite bullets, score = ducks hit
ducksPerLevel = 99999;
ducksToHit = 0;
bulletsLeft = 99999;
maxBullets = bulletsLeft;
timeLimit = 30;
timeLeft = 30;
levelText.setText('Time Attack');
} else if (gameMode === GAME_MODE_ENDLESS) {
// Infinite ducks, infinite bullets, game ends on 3 misses in a row
ducksPerLevel = 99999;
ducksToHit = 0;
bulletsLeft = 99999;
maxBullets = bulletsLeft;
timeLimit = 99999;
timeLeft = 99999;
levelText.setText('Endless');
}
// Set highScore for current mode
if (gameMode === GAME_MODE_TIMEATTACK) {
highScore = highScoreTimeAttack;
} else if (gameMode === GAME_MODE_ENDLESS) {
highScore = highScoreEndless;
} else {
highScore = highScoreClassic;
}
highScoreText.setText('High: ' + highScore);
gameActive = true;
waveActive = true;
updateBulletIcons();
scoreText.setText('Score: ' + score);
timerText.setText('Time: ' + timeLeft);
// Duck spawn schedule and speed per level/mode
var duckSpawnTimer = null;
var ducksSpawned = 0;
var ducksAtOnce = 1;
var duckSpawnInterval = 3000;
var duckSpeedMin = 2;
var duckSpeedMax = 3.5;
if (gameMode === GAME_MODE_CLASSIC || !gameMode) {
if (level === 1) {
ducksAtOnce = 1;
duckSpawnInterval = 3000;
duckSpeedMin = 2;
duckSpeedMax = 3.5;
} else if (level === 2) {
ducksAtOnce = 1;
duckSpawnInterval = 2000;
duckSpeedMin = 3.5;
duckSpeedMax = 5;
} else if (level === 3) {
ducksAtOnce = 2;
duckSpawnInterval = 1800;
duckSpeedMin = 4.2;
duckSpeedMax = 5.8;
} else if (level === 4) {
ducksAtOnce = 3;
duckSpawnInterval = 1500;
duckSpeedMin = 5.5;
duckSpeedMax = 7.2;
} else if (level === 5) {
ducksAtOnce = 4;
duckSpawnInterval = 1200;
duckSpeedMin = 6.5;
duckSpeedMax = 8.5;
} else {
ducksAtOnce = 5;
duckSpawnInterval = 1000;
duckSpeedMin = 7.5 + (level - 5) * 0.5;
duckSpeedMax = 9.5 + (level - 5) * 0.7;
}
} else if (gameMode === GAME_MODE_TIMEATTACK) {
ducksAtOnce = 2;
duckSpawnInterval = 900;
duckSpeedMin = 4.5;
duckSpeedMax = 7.5;
} else if (gameMode === GAME_MODE_ENDLESS) {
ducksAtOnce = 2;
duckSpawnInterval = 900;
duckSpeedMin = 4.5;
duckSpeedMax = 7.5;
}
// Clear any previous duck spawn timer
if (typeof duckSpawnTimer !== "undefined" && duckSpawnTimer) {
LK.clearInterval(duckSpawnTimer);
duckSpawnTimer = null;
}
ducksSpawned = 0;
// Function to spawn ducks and power-ups
function spawnDucks() {
if (!gameActive || !waveActive) return;
if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && ducksSpawned >= ducksPerLevel) {
if (duckSpawnTimer) {
LK.clearInterval(duckSpawnTimer);
duckSpawnTimer = null;
}
return;
}
var ducksThisWave = gameMode === GAME_MODE_CLASSIC || !gameMode ? Math.min(ducksAtOnce, ducksPerLevel - ducksSpawned) : ducksAtOnce;
for (var d = 0; d < ducksThisWave; d++) {
var duck = new Duck();
// Randomize start edge: 0=left, 1=right, 2=bottom
var edge = Math.floor(Math.random() * 3);
var startX, startY, angle;
if (edge === 0) {
// left
startX = -60;
startY = 400 + Math.random() * (2732 - 800);
angle = Math.random() * Math.PI / 3 - Math.PI / 6; // -30 to +30 deg
} else if (edge === 1) {
// right
startX = 2048 + 60;
startY = 400 + Math.random() * (2732 - 800);
angle = Math.PI + (Math.random() * Math.PI / 3 - Math.PI / 6); // 150 to 210 deg
} else {
// bottom
startX = 200 + Math.random() * (2048 - 400);
startY = 2732 + 60;
angle = -Math.PI / 2 + (Math.random() * Math.PI / 4 - Math.PI / 8); // -67 to -23 deg
}
duck.x = startX;
duck.y = startY;
duck.angle = angle;
// Set duck speed and size based on type
if (duck.type === 'golden') {
duck.speed = duckSpeedMax + 2 + Math.random() * 1.5;
} else if (duck.type === 'armored') {
duck.speed = duckSpeedMin + Math.random() * (duckSpeedMax - duckSpeedMin) * 0.7;
} else if (duck.type === 'mini') {
duck.speed = duckSpeedMax + 1 + Math.random() * 1.2;
duck.scaleX = duck.scaleY = 0.6 + Math.random() * 0.2;
} else if (duck.type === 'fast') {
duck.speed = duckSpeedMax + 0.7 + Math.random() * 0.7;
} else {
duck.speed = duckSpeedMin + Math.random() * (duckSpeedMax - duckSpeedMin);
}
ducks.push(duck);
// Always add ducks above background and below crosshair/dog/UI
game.addChild(duck);
// Move dog and crosshair to top of display list if present
if (dog && dog.parent) {
dog.parent.removeChild(dog);
game.addChild(dog);
}
if (crosshair && crosshair.parent) {
crosshair.parent.removeChild(crosshair);
game.addChild(crosshair);
}
LK.getSound('duck_fly').play();
ducksSpawned++;
}
// --- Power-up spawn logic ---
// Power-ups can appear randomly (1 in 7 chance per spawn, but not if already active)
if (Math.random() < 1 / 7) {
var availablePowerups = [];
if (!powerupActive.doubleBarrel) availablePowerups.push('doubleBarrel');
if (!powerupActive.slowTime) availablePowerups.push('slowTime');
if (!powerupActive.autoAim) availablePowerups.push('autoAim');
if (availablePowerups.length > 0) {
var which = availablePowerups[Math.floor(Math.random() * availablePowerups.length)];
spawnPowerupCollectible(which);
}
}
}
spawnDucks(); // Spawn first batch immediately
if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && ducksPerLevel > ducksAtOnce) {
duckSpawnTimer = LK.setInterval(spawnDucks, duckSpawnInterval);
} else if (gameMode === GAME_MODE_TIMEATTACK || gameMode === GAME_MODE_ENDLESS) {
duckSpawnTimer = LK.setInterval(spawnDucks, duckSpawnInterval);
}
// Start timer
if (timerInterval) LK.clearInterval(timerInterval);
timerInterval = LK.setInterval(function () {
if (!gameActive) return;
if (gameMode === GAME_MODE_TIMEATTACK) {
timeLeft--;
timerText.setText('Time: ' + timeLeft);
if (timeLeft <= 0) {
endWave();
}
} else if (gameMode === GAME_MODE_CLASSIC || !gameMode) {
timeLeft--;
timerText.setText('Time: ' + timeLeft);
if (timeLeft <= 0) {
endWave();
}
} else if (gameMode === GAME_MODE_ENDLESS) {
// No timer, but show "โ" or running time
timerText.setText('Endless');
}
}, 1000);
}
// End wave: check results, show dog if needed, advance or game over
function endWave() {
if (!waveActive) return;
waveActive = false;
gameActive = false;
if (timerInterval) LK.clearInterval(timerInterval);
// Remove remaining ducks
for (var i = 0; i < ducks.length; i++) {
if (ducks[i].parent) ducks[i].parent.removeChild(ducks[i]);
}
ducks = [];
// --- Classic Mode ---
if (gameMode === GAME_MODE_CLASSIC || !gameMode) {
// Show dog if not enough ducks hit
if (ducksHit < ducksToHit) {
dog.laugh(2048 / 2, 2732 - 100);
LK.effects.flashScreen(0xff0000, 800);
// Show final score on Game Over
LK.setTimeout(function () {
var finalScoreText = new Text2('Final Score: ' + score, {
size: 120,
fill: 0xFFFF00
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 2732 / 2;
game.addChild(finalScoreText);
LK.setTimeout(function () {
if (finalScoreText.parent) finalScoreText.parent.removeChild(finalScoreText);
LK.showGameOver();
}, 900);
}, 400);
// Game over after laugh
waveResultTimeout = LK.setTimeout(function () {
// handled above
}, 1300);
} else {
// Advance to next level after short pause
LK.effects.flashObject(levelText, 0x00ff00, 700);
LK.setTimeout(function () {
var finalScoreText = new Text2('Score: ' + score, {
size: 120,
fill: 0x00FF00
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 2732 / 2;
game.addChild(finalScoreText);
LK.setTimeout(function () {
if (finalScoreText.parent) finalScoreText.parent.removeChild(finalScoreText);
level++;
startLevel();
}, 700);
}, 200);
}
}
// --- Time Attack Mode ---
else if (gameMode === GAME_MODE_TIMEATTACK) {
// Show final score and return to menu
LK.effects.flashScreen(0x00ffcc, 800);
LK.setTimeout(function () {
var finalScoreText = new Text2('Ducks Hit: ' + ducksHit, {
size: 120,
fill: 0x00FFCC
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 2732 / 2;
game.addChild(finalScoreText);
LK.setTimeout(function () {
if (finalScoreText.parent) finalScoreText.parent.removeChild(finalScoreText);
showMainMenu();
}, 1400);
}, 400);
}
// --- Endless Mode ---
else if (gameMode === GAME_MODE_ENDLESS) {
endlessGameOver = true;
LK.effects.flashScreen(0xff2222, 800);
LK.setTimeout(function () {
var finalScoreText = new Text2('Ducks Hit: ' + ducksHit, {
size: 120,
fill: 0xFF2222
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 2732 / 2;
game.addChild(finalScoreText);
LK.setTimeout(function () {
if (finalScoreText.parent) finalScoreText.parent.removeChild(finalScoreText);
showMainMenu();
}, 1400);
}, 400);
}
}
// Handle tap/click to shoot
game.down = function (x, y, obj) {
if (!gameActive || !waveActive) return;
// --- Power-up collectible check ---
for (var i = powerupCollectibles.length - 1; i >= 0; i--) {
var p = powerupCollectibles[i];
if (p.isHit(x, y)) {
activatePowerup(p.type);
if (p.parent) p.parent.removeChild(p);
powerupCollectibles.splice(i, 1);
LK.effects.flashObject(p, 0xffffff, 300);
LK.getSound('hit').play();
return;
}
}
// --- Bullets check ---
if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && bulletsLeft <= 0) {
LK.getSound('miss').play();
LK.effects.flashScreen(0x888888, 200);
// Show reload animation (flash bullet icons red)
for (var i = 0; i < bulletIcons.length; i++) {
LK.effects.flashObject(bulletIcons[i], 0xff2222, 300);
}
// Optionally play reload sound if available
// LK.getSound('reload').play();
return;
}
// Only decrement bullets and allow shooting if bulletsLeft > 0 (classic)
if (gameMode === GAME_MODE_CLASSIC || !gameMode) {
bulletsLeft--;
updateBulletIcons();
if (bulletCountText && bulletCountText.setText) {
bulletCountText.setText("Bullets: " + bulletsLeft);
}
// If bullets reach zero after this shot, trigger game over immediately
if (bulletsLeft === 0) {
LK.setTimeout(function () {
LK.showGameOver();
}, 400); // short delay to allow last shot/crosshair to animate
return;
}
}
LK.getSound('shoot').play();
// Show crosshair at tap
crosshair.x = x;
crosshair.y = y;
crosshair.visible = true;
tween(crosshair, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
crosshair.visible = false;
crosshair.alpha = 1;
}
});
// --- Power-up shooting logic ---
// Auto-Aim: instantly hit nearest duck
if (powerupActive.autoAim) {
var nearestDuck = null;
var minDist = 99999;
for (var i = 0; i < ducks.length; i++) {
var duck = ducks[i];
if (!duck.alive || duck.hit) continue;
var dx = duck.x - x;
var dy = duck.y - y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
nearestDuck = duck;
}
}
if (nearestDuck) {
// Hit the duck
nearestDuck.hit = true;
nearestDuck.fall(function () {
if (nearestDuck.parent) nearestDuck.parent.removeChild(nearestDuck);
if (dog) {
dog.show(nearestDuck.x, 2732 - 100);
LK.getSound('dog_laugh').play();
LK.setTimeout(function () {
dog.hide();
}, 2000);
}
});
ducksHit++;
var duckScore = 10;
var achievementMsg = '';
var achievementColor = 0xFFD700;
if (typeof nearestDuck.type !== "undefined" && nearestDuck.type === 'golden') {
duckScore = 50;
achievementMsg = 'Golden Duck! +50';
achievementColor = 0xFFD700;
LK.effects.flashObject(nearestDuck, 0xffe066, 400);
} else {
LK.effects.flashObject(nearestDuck, 0xffff00, 300);
}
// Headshot bonus (auto-aim never headshots)
score += duckScore;
LK.getSound('hit').play();
if (typeof achievementPopup !== "undefined" && achievementMsg) {
achievementPopup.show(achievementMsg, achievementColor);
}
if (ducksHit === 3 && typeof achievementPopup !== "undefined") {
achievementPopup.show('Triple Hit!', 0x00FFCC);
}
if (ducksHit === ducksToHit && typeof achievementPopup !== "undefined") {
achievementPopup.show('Sharp Shooter!', 0x00FF00);
}
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
if (typeof highScoreText !== "undefined") {
highScoreText.setText('High: ' + highScore);
}
}
scoreText.setText('Score: ' + score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
if (typeof highScoreText !== "undefined") {
highScoreText.setText('High: ' + highScore);
}
}
if (gameMode === GAME_MODE_ENDLESS) {
endlessMissStreak = 0;
}
// End wave check
var aliveDucks = 0;
for (var i = 0; i < ducks.length; i++) {
if (ducks[i].alive) aliveDucks++;
}
if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && (aliveDucks === 0 && bulletsLeft === 0 || aliveDucks === 0 && typeof duckSpawnTimer !== "undefined" && (!duckSpawnTimer || ducks.length === 0))) {
endWave();
}
return;
}
}
// Double Barrel: shoot a wide spread, hit up to 2 ducks if close
if (powerupActive.doubleBarrel) {
var hitDucks = [];
for (var i = 0; i < ducks.length; i++) {
var duck = ducks[i];
if (!duck.alive || duck.hit) continue;
var dx = duck.x - x;
var dy = duck.y - y;
var dist = Math.sqrt(dx * dx + dy * dy);
var hitRadius = typeof duck.getHitboxRadius === "function" ? duck.getHitboxRadius() * 1.5 : 120;
if (dist < hitRadius) {
hitDucks.push({
duck: duck,
dist: dist
});
}
}
// Sort by distance, hit up to 2 closest
hitDucks.sort(function (a, b) {
return a.dist - b.dist;
});
hitDucks = hitDucks.slice(0, 2);
if (hitDucks.length > 0) {
for (var h = 0; h < hitDucks.length; h++) {
var hitDuck = hitDucks[h].duck;
hitDuck.hit = true;
hitDuck.fall(function () {
if (hitDuck.parent) hitDuck.parent.removeChild(hitDuck);
if (dog) {
dog.show(hitDuck.x, 2732 - 100);
LK.getSound('dog_laugh').play();
LK.setTimeout(function () {
dog.hide();
}, 2000);
}
});
ducksHit++;
var duckScore = 10;
var achievementMsg = '';
var achievementColor = 0xFFD700;
if (typeof hitDuck.type !== "undefined" && hitDuck.type === 'golden') {
duckScore = 50;
achievementMsg = 'Golden Duck! +50';
achievementColor = 0xFFD700;
LK.effects.flashObject(hitDuck, 0xffe066, 400);
} else {
LK.effects.flashObject(hitDuck, 0xffff00, 300);
}
// Headshot bonus
var isHeadshot = typeof hitDuck.isHeadshot === "function" && hitDuck.isHeadshot(x, y);
if (isHeadshot) {
duckScore += 5;
achievementMsg = 'Headshot! +' + duckScore;
achievementColor = 0xFF2222;
LK.effects.flashObject(hitDuck, 0xff2222, 400);
}
score += duckScore;
LK.getSound('hit').play();
if (typeof achievementPopup !== "undefined" && achievementMsg) {
achievementPopup.show(achievementMsg, achievementColor);
}
if (ducksHit === 3 && typeof achievementPopup !== "undefined") {
achievementPopup.show('Triple Hit!', 0x00FFCC);
}
if (ducksHit === ducksToHit && typeof achievementPopup !== "undefined") {
achievementPopup.show('Sharp Shooter!', 0x00FF00);
}
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
if (typeof highScoreText !== "undefined") {
highScoreText.setText('High: ' + highScore);
}
}
scoreText.setText('Score: ' + score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
if (typeof highScoreText !== "undefined") {
highScoreText.setText('High: ' + highScore);
}
}
if (gameMode === GAME_MODE_ENDLESS) {
endlessMissStreak = 0;
}
}
// End wave check
var aliveDucks = 0;
for (var i = 0; i < ducks.length; i++) {
if (ducks[i].alive) aliveDucks++;
}
if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && (aliveDucks === 0 && bulletsLeft === 0 || aliveDucks === 0 && typeof duckSpawnTimer !== "undefined" && (!duckSpawnTimer || ducks.length === 0))) {
endWave();
}
return;
}
}
// --- Normal shooting logic ---
// Check if a duck is hit (closest duck under tap, not already hit)
var hitDuck = null;
var minDist = 99999;
for (var i = 0; i < ducks.length; i++) {
var duck = ducks[i];
// Only allow hitting ducks that are alive and not hit yet
if (!duck.alive || duck.hit) continue;
var dx = duck.x - x;
var dy = duck.y - y;
var hitRadius = typeof duck.getHitboxRadius === "function" ? duck.getHitboxRadius() : 80;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < hitRadius && dist < minDist) {
minDist = dist;
hitDuck = duck;
}
}
if (hitDuck) {
// Armored duck: needs 2 hits
if (hitDuck.type === 'armored' && hitDuck.armor > 1) {
hitDuck.armor--;
hitDuck.flashRed();
LK.effects.flashObject(hitDuck, 0xff2222, 200);
LK.getSound('hit').play();
// No score yet, must hit again
return;
}
// Mark duck as hit and start fall animation
hitDuck.hit = true;
hitDuck.fall(function () {
if (hitDuck.parent) hitDuck.parent.removeChild(hitDuck);
if (dog) {
dog.show(hitDuck.x, 2732 - 100);
LK.getSound('dog_laugh').play();
LK.setTimeout(function () {
dog.hide();
}, 2000);
}
});
ducksHit++;
// Score system: use DUCK_TYPE_POINTS
if (typeof score === "undefined") score = 0;
var duckScore = DUCK_TYPE_POINTS[hitDuck.type] || 10;
var achievementMsg = '';
var achievementColor = 0xFFD700;
if (hitDuck.type === 'golden') {
achievementMsg = 'Golden Duck! +50';
achievementColor = 0xFFD700;
LK.effects.flashObject(hitDuck, 0xffe066, 400);
} else if (hitDuck.type === 'armored') {
achievementMsg = 'Armored Duck! +20';
achievementColor = 0x888888;
LK.effects.flashObject(hitDuck, 0xcccccc, 400);
} else if (hitDuck.type === 'mini') {
achievementMsg = 'Mini Fast Duck! +25';
achievementColor = 0x00e6ff;
LK.effects.flashObject(hitDuck, 0x00e6ff, 350);
} else {
LK.effects.flashObject(hitDuck, 0xffff00, 300);
}
// Headshot bonus
var isHeadshot = typeof hitDuck.isHeadshot === "function" && hitDuck.isHeadshot(x, y);
if (isHeadshot) {
duckScore += 5;
achievementMsg = 'Headshot! +' + duckScore;
achievementColor = 0xFF2222;
LK.effects.flashObject(hitDuck, 0xff2222, 400);
}
score += duckScore;
LK.getSound('hit').play();
// Show achievement popup if any
if (typeof achievementPopup !== "undefined" && achievementMsg) {
achievementPopup.show(achievementMsg, achievementColor);
}
// Triple hit achievement (3 ducks in 1 level)
if (ducksHit === 3 && typeof achievementPopup !== "undefined") {
achievementPopup.show('Triple Hit!', 0x00FFCC);
}
// Sharp shooter (all ducks hit)
if (ducksHit === ducksToHit && typeof achievementPopup !== "undefined") {
achievementPopup.show('Sharp Shooter!', 0x00FF00);
}
// Update high score if needed
if (score > highScore) {
highScore = score;
if (gameMode === GAME_MODE_TIMEATTACK) {
highScoreTimeAttack = highScore;
storage.highScoreTimeAttack = highScore;
} else if (gameMode === GAME_MODE_ENDLESS) {
highScoreEndless = highScore;
storage.highScoreEndless = highScore;
} else {
highScoreClassic = highScore;
storage.highScoreClassic = highScore;
}
if (typeof highScoreText !== "undefined") {
highScoreText.setText('High: ' + highScore);
}
}
scoreText.setText('Score: ' + score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
if (typeof highScoreText !== "undefined") {
highScoreText.setText('High: ' + highScore);
}
}
// Endless mode: reset miss streak
if (gameMode === GAME_MODE_ENDLESS) {
endlessMissStreak = 0;
}
} else {
// Miss: do nothing to ducks, just play miss sound and flash
LK.getSound('miss').play();
LK.effects.flashScreen(0x888888, 120);
// Track missed shots and show warning after 20 misses
missedShots++;
if (missedShots >= 20 && !missedWarningShown) {
missedWarningShown = true;
if (typeof achievementPopup !== "undefined") {
achievementPopup.show("Careful! 20 missed shots!", 0xFF2222);
}
}
// Endless mode: increment miss streak, end game if 3 in a row
if (gameMode === GAME_MODE_ENDLESS) {
endlessMissStreak++;
if (endlessMissStreak >= 3) {
endWave();
return;
}
}
}
// If all ducks are gone or out of bullets, end wave (classic only)
var aliveDucks = 0;
for (var i = 0; i < ducks.length; i++) {
if (ducks[i].alive) aliveDucks++;
}
// Only end wave if all ducks are gone AND no more ducks will spawn, or if out of bullets
if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && (aliveDucks === 0 && bulletsLeft === 0 || aliveDucks === 0 && typeof duckSpawnTimer !== "undefined" && (!duckSpawnTimer || ducks.length === 0))) {
endWave();
}
};
// Move handler for crosshair (optional, for desktop)
game.move = function (x, y, obj) {
// Optionally show crosshair following finger/mouse
// crosshair.x = x;
// crosshair.y = y;
};
// Main update loop
game.update = function () {
if (!gameActive || !waveActive) return;
// Update ducks
for (var i = ducks.length - 1; i >= 0; i--) {
var duck = ducks[i];
duck.update();
if (duck.escaped && !duck.hit) {
ducksEscaped++;
if (duck.parent) duck.parent.removeChild(duck);
ducks.splice(i, 1);
// Endless mode: treat escaped duck as a miss
if (gameMode === GAME_MODE_ENDLESS) {
endlessMissStreak++;
if (endlessMissStreak >= 3) {
endWave();
return;
}
}
}
}
// --- Power-up collectibles update ---
for (var i = powerupCollectibles.length - 1; i >= 0; i--) {
powerupCollectibles[i].update();
}
// --- Power-up timers/icons update ---
for (var ptype in powerupActive) {
if (powerupActive[ptype] && powerupTimers[ptype] > 0) {
// Decrement timer (per second, but update is 60fps)
if (LK.ticks % 60 === 0) {
powerupTimers[ptype]--;
if (powerupIcons[ptype] && powerupIcons[ptype]._timerTxt) {
powerupIcons[ptype]._timerTxt.setText(powerupTimers[ptype] + "s");
}
}
}
}
// If all ducks gone, end wave (classic only)
var aliveDucks = 0;
for (var i = 0; i < ducks.length; i++) {
if (ducks[i].alive) aliveDucks++;
}
if ((gameMode === GAME_MODE_CLASSIC || !gameMode) && aliveDucks === 0 && typeof duckSpawnTimer !== "undefined" && (!duckSpawnTimer || ducks.length === 0)) {
endWave();
}
};
// On game over, reset everything
LK.on('gameover', function () {
if (timerInterval) LK.clearInterval(timerInterval);
if (waveResultTimeout) LK.clearTimeout(waveResultTimeout);
if (typeof duckSpawnTimer !== "undefined" && duckSpawnTimer) {
LK.clearInterval(duckSpawnTimer);
duckSpawnTimer = null;
}
gameActive = false;
waveActive = false;
level = 1;
ducks = [];
bullets = [];
ducksHit = 0;
ducksEscaped = 0;
// Reset bullets to 10 on gameover for level 1
bulletsLeft = 10;
score = 0;
updateBulletIcons();
levelText.setText('Level 1');
scoreText.setText('Score: ' + score);
// Reset highScore to current mode's high score on gameover
if (gameMode === GAME_MODE_TIMEATTACK) {
highScore = highScoreTimeAttack;
} else if (gameMode === GAME_MODE_ENDLESS) {
highScore = highScoreEndless;
} else {
highScore = highScoreClassic;
}
if (typeof highScoreText !== "undefined") {
highScoreText.setText('High: ' + highScore);
}
timerText.setText('Time: 15');
if (dog) dog.hide();
// Show main menu after short delay
LK.setTimeout(function () {
showMainMenu();
}, 1200);
});
// On you win (if you want to add a win condition, e.g. after level 10)
LK.on('youwin', function () {
if (timerInterval) LK.clearInterval(timerInterval);
if (waveResultTimeout) LK.clearTimeout(waveResultTimeout);
if (typeof duckSpawnTimer !== "undefined" && duckSpawnTimer) {
LK.clearInterval(duckSpawnTimer);
duckSpawnTimer = null;
}
gameActive = false;
waveActive = false;
// Show win, then reset
LK.setTimeout(function () {
level = 1;
ducks = [];
bullets = [];
ducksHit = 0;
ducksEscaped = 0;
bulletsLeft = 10;
score = 0;
updateBulletIcons();
levelText.setText('Level 1');
scoreText.setText('Score: ' + score);
// Reset highScore to current mode's high score on youwin
if (gameMode === GAME_MODE_TIMEATTACK) {
highScore = highScoreTimeAttack;
} else if (gameMode === GAME_MODE_ENDLESS) {
highScore = highScoreEndless;
} else {
highScore = highScoreClassic;
}
timerText.setText('Time: 15');
if (dog) dog.hide();
showMainMenu();
}, 1800);
});
// Start music
LK.playMusic('bgm', {
fade: {
start: 0,
end: 0.5,
duration: 1200
}
});
// Start first level
// (Removed: now handled by showMainMenu)
bullet. In-Game asset. 2d. High contrast. No shadows
crosshair. In-Game asset. 2d. High contrast. No shadows
pixart jungle. In-Game asset. 2d. High contrast. No shadows
pixart hunting brown dog with black ears and white mouth holding a gun. In-Game asset. 2d. High contrast. No shadows
pixart brown hunting dog with black ears and white mouth laughing. In-Game asset. 2d. High contrast. No shadows
pixart blue duck flying. In-Game asset. 2d. High contrast. No shadows
pixart green duck flying. In-Game asset. 2d. High contrast. No shadows
pixart red duck flying. In-Game asset. 2d. High contrast. No shadows
pigeon flying. In-Game asset. 2d. High contrast. No shadows
black wall. In-Game asset. 2d. High contrast. No shadows
empty beer glass. In-Game asset. 2d. High contrast. No shadows
pixart sand watch. In-Game asset. 2d. High contrast. No shadows
dalmation dog holding a gun. In-Game asset. 2d. High contrast. No shadows. hunting
labrador dog hold a hunt gun. In-Game asset. 2d. High contrast. No shadows
dalmatian dog laugh. In-Game asset. 2d. High contrast. No shadows
labrador laugh. In-Game asset. 2d. High contrast. No shadows