User prompt
Please fix the bug: 'Uncaught TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'txt.style.fill = color || 0xFFD700;' Line Number: 42
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'size')' in or related to this line: 'scoreText.style.size = 90 * scale;' Line Number: 311
User prompt
🕹️ Gameplay Improvements: Fix Duck Collision Issue in Level 3: Ducks in Level 3 must be hittable just like in earlier levels. Make sure hitboxes are accurate and responsive. Progressive Levels: Add at least 5 levels with increasing difficulty. Ducks move faster and in bigger groups as levels progress. Add a Score System: Show a score counter at the top. +10 points per duck hit. Show final score on "Level Complete" or "Game Over" screen. Add Limited Ammo: Each level has a set number of bullets (e.g., 5–10). Display remaining bullets on screen. Add a reload animation or sound if all bullets are used. Add Timer: Each level should have a countdown timer. If the timer runs out, the level ends. Encourage fast and accurate shooting. 🐶 Retro Dog Animation: Dog Celebrates After Each Duck Hit: Show a retro pixel dog pop up and celebrate when a duck is hit. Dog lifts the duck in its mouth or laughs happily. Add retro-style bark sound effect. 📱 Mobile Optimization: Touch Controls: Make sure the game works smoothly on phones and tablets. Tap anywhere to shoot; make the tap area responsive. Responsive UI: Score, bullets, and timer should resize properly on mobile screens. 🎨 Visual & Audio Polish: Retro Background Music: Add looping 8-bit background music that matches the arcade style. Use sound effects for duck flaps, shots, dog barks, and duck falls. Variety of Ducks: Add different duck types (normal, fast, golden). Golden duck = rare, faster, gives +50 points. 🧠 Bonus Features: Headshot Bonus: If the player hits the head of the duck, award extra points (+5 bonus). Achievements / Milestones: Show simple pop-ups like "Nice Shot!", "Triple Hit!", or "Sharp Shooter!" to reward good performance. Start Menu & Restart Button: Add a start screen with "Play", "How to Play", and "High Scores". Include a restart option after game over. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
level text must be under the high score text
User prompt
level text should be middle in screen
User prompt
separate level text and high score text
User prompt
can you update my game link so users can see my games last version
User prompt
🔧 Check and Fix the Following: Collision Detection / Hitbox: Make sure the ducks in Level 3 have the same hitbox size and position as ducks in Levels 1 and 2. Adjust the hitbox to cover the full duck sprite (not just a tiny part). Click/Tap Registration: Ensure that player taps/clicks are properly registered on fast-moving ducks. Use a slightly larger hit area for fast ducks to make them easier to hit. Speed Adjustment (if needed): If ducks in Level 3 are too fast, reduce their speed slightly so players have a fair chance to react. Z-Index / Layering: Make sure ducks are not behind other UI elements or invisible layers that block interaction. Ducks must be on the topmost clickable layer. Consistency: Apply the same shooting logic from Level 1 and 2 to all ducks in Level 3. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
when ı shot the ducks game end fix this
User prompt
1. Game Ends After One Duck Is Shot – FIX THIS: Do not end the game when a duck is hit. Instead, when a duck is shot: Add +10 points to the score. Play a short “duck hit” animation and sound. Let the duck fall to the ground and disappear. The game should only end: When the player runs out of time. Or after missing too many ducks. Or after completing all levels. 🐶 2. Add Duck Retriever Dog Animation: After a duck is shot and falls to the ground: Show a retro-style dog popping up from the bottom of the screen. The dog should hold the duck in its mouth or raise it proudly (like in Duck Hunt). Play a short celebration animation (2–3 seconds). Optional: Add a bark or celebration sound. Dog Animation Details: Pixel art (8-bit or 16-bit style). Only appears after a duck is shot. Stays on screen for a short time, then disappears.
User prompt
🎯 1. Duck Speed and Spawn Rate (Too Fast at Start): Ducks should appear one at a time in Level 1. In Level 1, ducks should move slowly (slow flight speed). Starting from Level 2, ducks can start appearing more frequently and faster, with speed increasing slightly each level. Duck spawn schedule per level: Level 1: One duck every 3 seconds, max 5 ducks, slow speed. Level 2: One duck every 2 seconds, max 8 ducks, medium speed. Level 3: Two ducks at a time every 2 seconds, max 12 ducks, faster. Level 4+: Three ducks at a time every 1.5 seconds, max 15 ducks, fast speed. 🧮 2. Add a Scoring System: Add a visible Score counter at the top of the screen. Each duck hit = +10 points. Missing a duck = no points, but does not subtract. Show final score on “Game Over” and “Level Complete” screens. 🪙 3. Optional: Bonus Points Hitting 3 ducks in a row without missing = Bonus +20 points. Hitting a rare "golden duck" (optional) = +50 points. 📱 4. Make sure the game works smoothly on mobile: Touch controls must be responsive. Game elements should be scaled and centered correctly on small screens. Shooting should work by tapping the screen anywhere. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Retro Duck Shooter
Initial prompt
Game Title: Retro Duck Shooter Gameplay Overview: The player is a hunter who must shoot flying ducks on the screen using a tap (mobile) or mouse click (desktop). Ducks appear from the sides or bottom of the screen and fly in random directions. The player must shoot a certain number of ducks within a time limit to proceed to the next level. If the player misses too many ducks, the game ends. The game includes multiple levels with increasing difficulty (speed of ducks, number of ducks, etc.). Controls: Mobile: Tap anywhere on the screen to shoot. Desktop: Click anywhere with the mouse to shoot. Only one shot per tap/click. Limited number of bullets per round (e.g., 3 shots per wave). Game Elements: 1. Player Gun: Static crosshair or moving scope that follows tap/click. Retro pixel-art shotgun sound when fired. 2. Ducks: Different colored ducks (yellow, red, blue). Ducks fly with flapping animation in random directions and speeds. Some ducks fly faster (harder to hit). Ducks make a flapping sound and fall down with a funny animation when shot. 3. Dog (optional, like in original Duck Hunt): Appears at the bottom of the screen to laugh if player misses. Pops up with a laugh animation and sound. Optional: dog picks up ducks that fall. 4. UI Elements: Score counter (ducks hit). Bullet counter (number of shots remaining). Timer for each level (e.g., 30 seconds). "Level Complete" and "Game Over" screens. Retro 8-bit style fonts and buttons. 5. Levels: Level 1: 5 ducks, slow speed, 3 bullets per wave. Level 2: 7 ducks, medium speed, 3 bullets per wave. Level 3: 10 ducks, fast speed, 3 bullets per wave. Add up to 5 levels or more, with difficulty increasing each time. After final level, show “You Win!” screen. 6. Sound Effects: Shooting sound (retro pixel shotgun). Duck flapping and falling. Dog laughing. Level start and complete sound jingles. 7. Graphics Style: All sprites should be retro-style pixel art (8-bit or 16-bit). Use an old-school Atari color palette. Background can be a blue sky with pixelated trees or grass. 8. Game Logic: Player must hit at least 70% of ducks to pass each level. If player runs out of bullets or misses too many ducks, show “Game Over”. Add a restart option. Save the highest score (local storage). Additional Notes: Ensure the game scales properly on mobile screen sizes (responsive layout). Use touch-friendly controls and optimize for performance on mobile. Include a simple main menu with “Start Game” button.
/**** * 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