/**** * Classes ****/ // Bird class: flies from right to left, above ground, destroys itself off screen var Bird = Container.expand(function () { var self = Container.call(this); // Use 'gunes' asset for bird (placeholder, should be replaced with a bird asset if available) var birdSprite = self.attachAsset('gunes', { anchorX: 0.5, anchorY: 0.5 }); birdSprite.width = 120; birdSprite.height = 90; birdSprite.tint = 0x888888; birdSprite.alpha = 0.92; self.speed = 10 + Math.random() * 4; self.update = function () { self.x -= self.speed; // Destroy if off screen if (self.lastX >= -200 && self.x < -200) { self.destroy(); } self.lastX = self.x; }; return self; }); // Cactus class: moves left, destroys itself off screen var Cactus = Container.expand(function () { var self = Container.call(this); // Attach cactus asset, anchor bottom left var cactusSprite = self.attachAsset('cactus', { anchorX: 0.5, anchorY: 1 }); self.speed = 7 + Math.random() * 2; // even slower speed for easier gameplay self.update = function () { self.x -= self.speed; // Destroy if off screen if (self.lastX >= -200 && self.x < -200) { self.destroy(); } self.lastX = self.x; }; return self; }); // Cat class: handles jump, gravity, and collision var Cat = Container.expand(function () { var self = Container.call(this); // Attach cat asset, anchor bottom left for ground logic var catSprite = self.attachAsset('cat', { anchorX: 0.5, anchorY: 1 }); // Cat physics self.y = 0; self.x = 0; self.vy = 0; self.isJumping = false; self.groundY = 0; // will be set after creation // Double jump state self.jumpCount = 0; self.maxJumps = 2; // Jump parameters self.jumpVelocity = -65; self.gravity = 4; // Update method for physics self.update = function () { // Handle 3-second glide if long press is active, but only once per jump and only if stamina is full if (typeof isPressing !== "undefined" && isPressing && self.isJumping && !glideActive && !self.hasGlided && self.vy > 0 && typeof stamina !== "undefined" && stamina >= maxStamina) { // Only start glide if falling, not already gliding, haven't glided yet this jump, and stamina is full if (LK.ticks - pressStartTick >= 18) { // ~300ms threshold to avoid accidental short taps glideActive = true; self.isHovering = false; self.hasHovered = true; self.glideStartTick = LK.ticks; self.vy = 0; self.hasGlided = true; // Mark that we've glided this jump // Consume all stamina stamina = 0; staminaBar.width = Math.max(1, stamina / maxStamina * staminaBarFullWidth); // Activate bird damage immunity for 5 seconds birdImmunityActive = true; birdImmunityStartTick = LK.ticks; } } // If gliding, keep cat in air for up to 3 seconds (180 ticks) if (typeof glideActive !== "undefined" && glideActive) { if (LK.ticks - self.glideStartTick < 180) { // Stay in air, do not apply gravity, regardless of isPressing return; } else { // End glide glideActive = false; } } if (self.isJumping) { // If we are hovering, do not apply gravity or move if (self.isHovering) { // Determine hover duration: 12 ticks (195ms) for first jump, 6 ticks (100ms) for others var hoverDuration = self.jumpCount === 1 ? 12 : 6; if (LK.ticks - self.hoverStartTick >= hoverDuration) { self.isHovering = false; // Resume falling, keep vy as 0 so gravity starts pulling down self.vy = 0; } // While hovering, do nothing else return; } // Normal jump/fall self.vy += self.gravity; self.y += self.vy; // At apex: vy just turned positive (was going up, now going down) if (!self.isHovering && self.vy > 0 && !self.hasHovered) { self.isHovering = true; self.hasHovered = true; self.hoverStartTick = LK.ticks; self.vy = 0; // Stop vertical movement during hover return; } // Land on ground if (self.y >= self.groundY) { self.y = self.groundY; self.vy = 0; self.isJumping = false; self.isHovering = false; self.hasHovered = false; // If we just finished a glide (hasGlided true, glideActive false), start counting cacti for stamina refill if (typeof self.hasGlided !== "undefined" && self.hasGlided && typeof glideActive !== "undefined" && !glideActive) { // Start post-glide cactus counting if (typeof postGlideCactusCounter === "undefined") postGlideCactusCounter = 0; postGlideCactusCounter = 0; postGlideCountingActive = true; } self.hasGlided = false; // Reset glide on landing self.jumpCount = 0; // Reset jump count on landing } } }; // Jump method self.jump = function () { if (self.jumpCount < self.maxJumps) { self.isJumping = true; self.vy = self.jumpVelocity; self.isHovering = false; self.hasHovered = false; self.hasGlided = false; // Allow one glide per jump self.hoverStartTick = 0; self.jumpCount++; // Play cat sound on jump LK.getSound('kedi').play(); } }; return self; }); // Cloud class: moves left, destroys itself off screen var Cloud = Container.expand(function () { var self = Container.call(this); // Use a white ellipse for the cloud var cloudSprite = self.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5 }); // Randomize cloud size var scale = 1.2 + Math.random() * 1.2; cloudSprite.width = 320 * scale; cloudSprite.height = 120 * scale; cloudSprite.tint = 0xFFFFFF; cloudSprite.alpha = 0.7 + Math.random() * 0.2; self.speed = 1.5 + Math.random() * 1.2; self.update = function () { self.x -= self.speed; if (self.lastX >= -400 && self.x < -400) { self.destroy(); } self.lastX = self.x; }; return self; }); // Gold class: moves left, destroys itself off screen, collectible by cat var Gold = Container.expand(function () { var self = Container.call(this); // Attach gold asset, anchor center var goldSprite = self.attachAsset('ghost', { anchorX: 0.5, anchorY: 0.5 }); goldSprite.width = 80; goldSprite.height = 80; self.speed = 7 + Math.random() * 2; self.collected = false; self.update = function () { self.x -= self.speed; // Destroy if off screen if (self.lastX >= -200 && self.x < -200) { self.destroy(); } self.lastX = self.x; }; return self; }); // Sun class: static, top right var Sun = Container.expand(function () { var self = Container.call(this); // Use a yellow ellipse for the sun var sunSprite = self.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5 }); sunSprite.width = 260; sunSprite.height = 260; sunSprite.tint = 0xFFF700; sunSprite.alpha = 0.95; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xF7E9A0 // light sand yellow, desert-like }); /**** * Game Code ****/ ; // --- GAME CODE --- // --- Cactus background images for desert feel --- var cactusBgSprites = []; var cactusBgCount = 6; // Number of background cacti for (var i = 0; i < cactusBgCount; i++) { var cactusBg = LK.getAsset('cactus', { anchorX: 0.5, anchorY: 1 }); // Randomize X position, spread across the width cactusBg.x = 200 + i * (1700 / (cactusBgCount - 1)) + Math.random() * 80 - 40; // Randomize Y position, but always above ground cactusBg.y = GROUND_Y + 2; // Randomize scale for depth effect var scale = 0.7 + Math.random() * 0.5; cactusBg.width = 180 * scale; cactusBg.height = 180 * scale; cactusBg.alpha = 0.38 + Math.random() * 0.13; // faded for background // Optionally, tint to a lighter green for background cactusBg.tint = 0xB6E2A1; game.addChild(cactusBg); cactusBgSprites.push(cactusBg); } // Game constants var GROUND_Y = 2200; // y position of ground (bottom of screen minus margin) var CAT_START_X = 400; var CAT_START_Y = GROUND_Y; var CACTUS_SPAWN_MIN = 70; // min frames between cacti (slower, more time to jump) var CACTUS_SPAWN_MAX = 130; // max frames between cacti (slower, more time to jump) // --- Tile 'kum' (sand) image as background under the ground, filling as horizontal rows with no visible gaps --- var sandTiles = []; // Use the native asset width/height for perfect tiling, but scale up for larger sand tiles var sandAsset = LK.getAsset('kum', { anchorX: 0, anchorY: 0 }); // Scale factor for larger sand tiles (increased for bigger appearance) var sandScale = 2.3; var sandTileWidth = sandAsset.width * sandScale; var sandTileHeight = sandAsset.height * sandScale; // Remove the temporary asset from the stage if it was added if (typeof sandAsset.parent !== "undefined" && sandAsset.parent) sandAsset.parent.removeChild(sandAsset); // Calculate how many tiles are needed to cover the width and height, with +2 for safe coverage var sandColumnsCount = Math.ceil(2048 / sandTileWidth) + 2; var sandRowsCount = Math.ceil((2732 - (GROUND_Y + 2)) / sandTileHeight); // Place sand tiles exactly edge-to-edge horizontally for seamless infinite looping for (var row = 0; row < sandRowsCount; row++) { for (var col = 0; col < sandColumnsCount; col++) { var sandTile = LK.getAsset('kum', { anchorX: 0, anchorY: 0 }); // Place each tile exactly edge-to-edge horizontally, no gap, no overlap sandTile.x = Math.round(col * sandTileWidth); sandTile.y = Math.round(GROUND_Y + 2 + row * sandTileHeight); sandTile.width = sandTileWidth; sandTile.height = sandTileHeight; sandTile.alpha = 0.98; game.addChild(sandTile); sandTiles.push(sandTile); } } // No visible gap, no overlap, seamless horizontal tiling for infinite looping // Add two ground images ('zemin') for seamless horizontal scrolling var groundImg = LK.getAsset('zemin', { anchorX: 0, anchorY: 1 }); groundImg.x = 0; groundImg.y = GROUND_Y + 2; // +2 for slight overlap, ensures cat and cactus are on top groundImg.width = 2048; game.addChild(groundImg); // Add a second ground image for seamless looping var groundImg2 = LK.getAsset('zemin', { anchorX: 0, anchorY: 1 }); groundImg2.x = groundImg.x + groundImg.width; groundImg2.y = GROUND_Y + 2; groundImg2.width = 2048; game.addChild(groundImg2); // Add a ground image under each cactus as well, so cactus stands on ground // This is handled by drawing the ground first, so both cat and cactus visually stand on the same ground // Sun and clouds var sun; var clouds = []; var nextCloudTick = 0; // Birds var birds = []; var nextBirdTick = 0; // Game state var cat; var cacti = []; var golds = []; var score = 0; var goldScore = 0; var scoreTxt; var goldScoreTxt; var nextCactusTick = 0; var nextGoldTick = 0; var gameOver = false; // On game start, stamina is full stamina = maxStamina; if (typeof staminaBar !== "undefined") { staminaBar.width = Math.max(1, stamina / maxStamina * staminaBarFullWidth); } // Track how many cacti have been jumped over for stamina refill var cactiJumpedOver = 0; // Track post-glide cactus counting for stamina refill var postGlideCactusCounter = 0; var postGlideCountingActive = false; // Speed multiplier for game progression var speedMultiplier = 1; var SPEED_MULTIPLIER_INCREMENT = 0.08; // how much to increase per score var SPEED_MULTIPLIER_MAX = 3; // cap the speed increase // HP bar variables var maxHp = 100; var hp = maxHp; var hpBarFullWidth = 420; var hpBarHeight = 38; // Add HP bar background (grey) var hpBarBg = LK.getAsset('centerCircle', { anchorX: 0, anchorY: 0 }); hpBarBg.width = hpBarFullWidth; hpBarBg.height = hpBarHeight; hpBarBg.x = 110; hpBarBg.y = 30; hpBarBg.tint = 0x444444; hpBarBg.alpha = 0.45; LK.gui.top.addChild(hpBarBg); // Add HP bar (red) var hpBar = LK.getAsset('centerCircle', { anchorX: 0, anchorY: 0 }); hpBar.width = hpBarFullWidth; hpBar.height = hpBarHeight; hpBar.x = 110; hpBar.y = 30; hpBar.tint = 0xff4444; hpBar.alpha = 0.95; LK.gui.top.addChild(hpBar); // --- Stamina bar variables and GUI --- var maxStamina = 100; var stamina = maxStamina; var staminaBarFullWidth = 420; var staminaBarHeight = 28; // Stamina bar background (grey, below HP bar) var staminaBarBg = LK.getAsset('centerCircle', { anchorX: 0, anchorY: 0 }); staminaBarBg.width = staminaBarFullWidth; staminaBarBg.height = staminaBarHeight; staminaBarBg.x = 110; staminaBarBg.y = 80; staminaBarBg.tint = 0x444444; staminaBarBg.alpha = 0.35; LK.gui.top.addChild(staminaBarBg); // Stamina bar (blue) var staminaBar = LK.getAsset('centerCircle', { anchorX: 0, anchorY: 0 }); staminaBar.width = staminaBarFullWidth; staminaBar.height = staminaBarHeight; staminaBar.x = 110; staminaBar.y = 80; staminaBar.tint = 0x44aaff; staminaBar.alpha = 0.95; LK.gui.top.addChild(staminaBar); // Add score text to GUI scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Gold score text (smaller, right of score) goldScoreTxt = new Text2('0', { size: 90, fill: 0xFFD700 // gold color }); goldScoreTxt.anchor.set(0, 0); goldScoreTxt.x = 120; // right of main score goldScoreTxt.y = 20; LK.gui.top.addChild(goldScoreTxt); // Add sun (top right, margin from edge) sun = new Sun(); sun.x = 2048 - 220; sun.y = 220; game.addChild(sun); // Add 'gunes1' character image to the top right var gunes1Sprite = LK.getAsset('gunes1', { anchorX: 1, anchorY: 0 }); gunes1Sprite.x = 2048 - 40; // 40px margin from right edge gunes1Sprite.y = 40; // 40px margin from top edge gunes1Sprite.width = 200; gunes1Sprite.height = 200; game.addChild(gunes1Sprite); // Add 'evet' button to the top right corner of the GUI var evetBtn = new Text2('evet', { size: 90, fill: 0xFFFFFF, font: "Arial" }); evetBtn.anchor.set(1, 0); // right-top anchor evetBtn.x = LK.gui.top.width - 40; // 40px margin from right evetBtn.y = 30; // 30px margin from top LK.gui.topRight.addChild(evetBtn); // Cat2 enemy is not created until score >= 50 var cat2Sprite = null; // Add to update loop game.updateCat2 = function () { if (!cat2Sprite) return; // Horizontal movement cat2Sprite.x -= cat2Sprite.speed; // Jump logic if (!cat2Sprite.isJumping && cat2Sprite.jumpCooldown <= 0) { cat2Sprite.vy = cat2Sprite.jumpVelocity; cat2Sprite.isJumping = true; cat2Sprite.jumpCooldown = cat2Sprite.jumpInterval; } // Apply gravity if in air if (cat2Sprite.isJumping) { // Move more forward (left) while jumping cat2Sprite.x -= 8; // increased leftward movement during jump cat2Sprite.y += cat2Sprite.vy; cat2Sprite.vy += cat2Sprite.gravity; // Land on ground if (cat2Sprite.y >= cat2Sprite.groundY) { cat2Sprite.y = cat2Sprite.groundY; cat2Sprite.vy = 0; cat2Sprite.isJumping = false; // Randomize next jump interval for variety cat2Sprite.jumpInterval = 60 + Math.floor(Math.random() * 60); } } // Decrement jump cooldown if not jumping if (!cat2Sprite.isJumping && cat2Sprite.jumpCooldown > 0) { cat2Sprite.jumpCooldown--; } // Destroy if off screen to the left if (cat2Sprite.lastX >= -cat2Sprite.width && cat2Sprite.x < -cat2Sprite.width) { cat2Sprite.destroy(); cat2Sprite = null; } else if (cat2Sprite) { cat2Sprite.lastX = cat2Sprite.x; } }; // Spawn a few initial clouds for (var i = 0; i < 3; i++) { var cloud = new Cloud(); cloud.x = 400 + Math.random() * 1400; cloud.y = 200 + Math.random() * 400; cloud.lastX = cloud.x; game.addChild(cloud); clouds.push(cloud); } nextCloudTick = 60 + Math.floor(Math.random() * 120); // Create cat cat = new Cat(); cat.x = CAT_START_X; cat.y = CAT_START_Y; cat.groundY = CAT_START_Y; game.addChild(cat); // Track long press for gliding var isPressing = false; var pressStartTick = 0; var glideActive = false; // --- Bird damage immunity after stamina glide --- var birdImmunityActive = false; var birdImmunityStartTick = 0; var BIRD_IMMUNITY_DURATION = 300; // 5 seconds at 60fps // Touch/click to jump or start glide game.down = function (x, y, obj) { if (!gameOver) { cat.jump(); isPressing = true; pressStartTick = LK.ticks; glideActive = false; } // Update golds, check for collection for (var j = golds.length - 1; j >= 0; j--) { var gold = golds[j]; gold.update(); // Remove if off screen if (gold.x < -200) { gold.destroy(); golds.splice(j, 1); continue; } // Collect gold: only trigger on first intersect and not already collected if (!gold.collected && cat.intersects(gold)) { gold.collected = true; goldScore++; goldScoreTxt.setText(goldScore); gold.destroy(); golds.splice(j, 1); continue; } } }; // On release, stop pressing and gliding game.up = function (x, y, obj) { isPressing = false; // Do not forcibly end glide here; glide ends after 3s or on landing }; // Main update loop game.update = function () { if (gameOver) return; // --- Ground scrolling logic --- // Move both ground images left by cactus speed (use average cactus speed or a fixed value for smoothness) var groundScrollSpeed = 10 * speedMultiplier; // Match with cactus/bird speed for realism groundImg.x -= groundScrollSpeed; groundImg2.x -= groundScrollSpeed; // Move sand tiles to match ground movement (for vertical columns) for (var i = 0; i < sandTiles.length; i++) { sandTiles[i].x -= groundScrollSpeed; // If a sand tile is fully off screen to the left, move it to the right of the rightmost tile in its row if (sandTiles[i].x <= -sandTileWidth) { // Find the rightmost tile in the same row var thisRow = Math.floor((sandTiles[i].y - (GROUND_Y + 2)) / sandTileHeight); var maxX = -sandTileWidth; for (var j = 0; j < sandTiles.length; j++) { var otherRow = Math.floor((sandTiles[j].y - (GROUND_Y + 2)) / sandTileHeight); if (otherRow === thisRow && sandTiles[j].x > maxX) maxX = sandTiles[j].x; } // Place the tile exactly after the rightmost tile for perfect seamlessness sandTiles[i].x = maxX + sandTileWidth; // Snap to integer to avoid subpixel gaps sandTiles[i].x = Math.round(sandTiles[i].x); // Ensure no vertical offset is introduced when looping sandTiles[i].y = Math.round(GROUND_Y + 2 + thisRow * sandTileHeight); } } // This ensures sand tiles are always perfectly seamless and loop infinitely with the ground // If a ground image is fully off screen to the left, move it to the right of the other if (groundImg.x <= -groundImg.width) { groundImg.x = groundImg2.x + groundImg2.width; } if (groundImg2.x <= -groundImg2.width) { groundImg2.x = groundImg.x + groundImg.width; } // Update clouds and spawn new ones for (var i = clouds.length - 1; i >= 0; i--) { var cloud = clouds[i]; cloud.update(); if (cloud.x < -400) { cloud.destroy(); clouds.splice(i, 1); } } if (nextCloudTick <= 0) { var cloud = new Cloud(); cloud.x = 2048 + 200; cloud.y = 120 + Math.random() * 600; cloud.lastX = cloud.x; game.addChild(cloud); clouds.push(cloud); nextCloudTick = 60 + Math.floor(Math.random() * 120); } else { nextCloudTick--; } // Deactivate bird immunity after 5 seconds if (birdImmunityActive && LK.ticks - birdImmunityStartTick >= BIRD_IMMUNITY_DURATION) { birdImmunityActive = false; } // Update birds and spawn new ones for (var i = birds.length - 1; i >= 0; i--) { var bird = birds[i]; bird.update(); if (bird.x < -200) { bird.destroy(); birds.splice(i, 1); continue; } // Bird collision detection (only trigger on first intersect) if (!bird.lastWasIntersecting && cat.intersects(bird)) { // If immunity is active, skip damage if (!birdImmunityActive) { // Decrease HP by 10 hp -= 10; if (hp < 0) hp = 0; // Play cat sound on HP loss LK.getSound('kedi1').play(); // Update HP bar hpBar.width = Math.max(1, hp / maxHp * hpBarFullWidth); // Flash red for hit LK.effects.flashObject(cat, 0xff0000, 400); // If HP is 0, game over if (hp <= 0) { LK.effects.flashScreen(0xff0000, 800); // Play 'death' sound 3 times in quick succession when game over screen is shown LK.showGameOver(); LK.getSound('death').play(); LK.setTimeout(function () { LK.getSound('death').play(); }, 180); LK.setTimeout(function () { LK.getSound('death').play(); }, 360); gameOver = true; return; } } } bird.lastWasIntersecting = cat.intersects(bird); } if (nextBirdTick <= 0) { // Spawn 2-3 birds at once, with more scattered (random) vertical and horizontal spacing var birdCount = 2 + Math.floor(Math.random() * 2); // 2 or 3 birds var minBirdY = GROUND_Y - 950; // Lowered: birds fly closer to ground var maxBirdY = GROUND_Y - 650; // Lowered: birds fly closer to ground if (minBirdY < 0) minBirdY = 0; if (maxBirdY < 0) maxBirdY = 0; var yRange = maxBirdY - minBirdY; var usedYs = []; for (var b = 0; b < birdCount; b++) { var bird = new Bird(); // Scatter birds horizontally more bird.x = 2048 + 100 + Math.random() * 200 + b * 60 + Math.random() * 60; // Scatter birds vertically, allow overlap and more randomness var baseY = minBirdY + Math.random() * yRange; // Add more jitter, and avoid birds being too close vertically var jitterY = (Math.random() - 0.5) * 180; var finalY = baseY + jitterY; // Optionally, keep birds at least 120px apart vertically var minDist = 120; var attempts = 0; while (usedYs.some(function (y) { return Math.abs(y - finalY) < minDist; }) && attempts < 10) { finalY = minBirdY + Math.random() * yRange + (Math.random() - 0.5) * 180; attempts++; } usedYs.push(finalY); bird.y = finalY; bird.lastX = bird.x; // Apply speed multiplier to bird bird.speed = bird.speed * (0.8 + 0.4 * Math.random()) * speedMultiplier; game.addChild(bird); birds.push(bird); } // Bird group appears every 220-340 ticks (increased interval for more jump time) nextBirdTick = 220 + Math.floor(Math.random() * 120); } else { nextBirdTick--; } // Update cat cat.update(); // Spawn cactus if (nextCactusTick <= 0) { var cactus = new Cactus(); cactus.x = 2048 + 100; // spawn just off right edge cactus.y = GROUND_Y; cactus.lastX = cactus.x; // Apply speed multiplier to cactus cactus.speed = cactus.speed * speedMultiplier; game.addChild(cactus); cacti.push(cactus); // Use new CACTUS_SPAWN_MIN/MAX for more consistent jump windows var spawnInterval = CACTUS_SPAWN_MIN + Math.floor(Math.random() * (CACTUS_SPAWN_MAX - CACTUS_SPAWN_MIN + 1)); nextCactusTick = Math.floor(spawnInterval); } else { nextCactusTick--; } // Spawn gold if (nextGoldTick <= 0) { var gold = new Gold(); gold.x = 2048 + 100; // Random Y: between ground and a bit above cat's jump apex var jumpApex = GROUND_Y + cat.jumpVelocity * (Math.abs(cat.jumpVelocity) / (2 * cat.gravity)); var minY = GROUND_Y - 400; var maxY = GROUND_Y - 1000; if (maxY < 0) maxY = 0; gold.y = minY - Math.random() * (minY - maxY); gold.lastX = gold.x; gold.collected = false; gold.speed = gold.speed * speedMultiplier; game.addChild(gold); golds.push(gold); // Gold appears less frequently than cacti nextGoldTick = 80 + Math.floor(Math.random() * 120); } else { nextGoldTick--; } // Update golds, check for collection for (var j = golds.length - 1; j >= 0; j--) { var gold = golds[j]; gold.update(); // Remove if off screen if (gold.x < -200) { gold.destroy(); golds.splice(j, 1); continue; } // Collect gold: only trigger on first intersect and not already collected if (!gold.collected && cat.intersects(gold)) { gold.collected = true; goldScore++; goldScoreTxt.setText(goldScore); // Increase HP by 10, up to maxHp hp += 10; if (hp > maxHp) hp = maxHp; hpBar.width = Math.max(1, hp / maxHp * hpBarFullWidth); gold.destroy(); golds.splice(j, 1); continue; } } // Move cat2 enemy if present, but only after score >= 30 if (score >= 30) { // If cat2Sprite does not exist, create it if (!cat2Sprite && !gameOver) { if (typeof cat2RespawnTick === "undefined" || cat2RespawnTick <= 0) { cat2RespawnTick = 0; // spawn immediately on first time } if (cat2RespawnTick <= 0) { cat2Sprite = LK.getAsset('cat2', { anchorX: 1, anchorY: 1 }); cat2Sprite.x = 2048 - 40; cat2Sprite.y = GROUND_Y + 2; cat2Sprite.width = 420; cat2Sprite.height = 420; game.addChild(cat2Sprite); cat2Sprite.speed = 16; // even faster movement cat2Sprite.lastX = cat2Sprite.x; cat2Sprite.vy = 0; cat2Sprite.isJumping = false; cat2Sprite.jumpCooldown = 0; cat2Sprite.jumpInterval = 32 + Math.floor(Math.random() * 32); // jump even more often cat2Sprite.groundY = GROUND_Y + 2; cat2Sprite.jumpVelocity = -54; // even less jump height for cat2 cat2Sprite.gravity = 6.2; // slightly stronger gravity for a lower, snappier jump cat2Sprite.lastWasIntersecting = false; } else { cat2RespawnTick--; } } if (typeof game.updateCat2 === "function" && cat2Sprite) { game.updateCat2(); // Cat2 ile ana karakter çarpışma kontrolü (ilk çarpışma anı) if (cat2Sprite !== null && typeof cat2Sprite.lastWasIntersecting !== "undefined" && !cat2Sprite.lastWasIntersecting) { // Cat2'nin ana gövdesiyle (pelerin hariç) çarpışma kontrolü // Cat2'nin ana gövdesi için bir dikdörtgen belirle (pelerin hariç) // Cat2 sprite'ı sağdan sola hareket ettiği için, ana gövdeyi sprite'ın sol kısmında tut // Cat2'nin ana gövdesi: sprite'ın x, y, width, height'ı ile, width'in %40'ı kadar soldan (pelerin hariç) var cat2BodyWidth = cat2Sprite.width * 0.6; var cat2BodyHeight = cat2Sprite.height * 0.85; var cat2BodyX = cat2Sprite.x - cat2Sprite.width + cat2Sprite.width * 0.2; var cat2BodyY = cat2Sprite.y - cat2Sprite.height * 0.85; // Cat'in bounding box'ı var catBodyWidth = cat.width * 0.7; var catBodyHeight = cat.height * 0.85; var catBodyX = cat.x - cat.width * 0.5 + cat.width * 0.15; var catBodyY = cat.y - cat.height * 0.85; // Dikdörtgen çarpışma kontrolü var isBodyIntersect = cat2BodyX < catBodyX + catBodyWidth && cat2BodyX + cat2BodyWidth > catBodyX && cat2BodyY < catBodyY + catBodyHeight && cat2BodyY + cat2BodyHeight > catBodyY; if (isBodyIntersect) { // 15hp azalt hp -= 15; if (hp < 0) hp = 0; // HP barı güncelle hpBar.width = Math.max(1, hp / maxHp * hpBarFullWidth); // Kırmızı flash efekti LK.effects.flashObject(cat, 0xff0000, 400); // Ölüm kontrolü if (hp <= 0) { LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); LK.getSound('death').play(); LK.setTimeout(function () { LK.getSound('death').play(); }, 180); LK.setTimeout(function () { LK.getSound('death').play(); }, 360); gameOver = true; return; } } } // Son intersect durumunu güncelle if (cat2Sprite !== null) { cat2Sprite.lastWasIntersecting = cat.intersects(cat2Sprite); } // If cat2Sprite is destroyed, spawn a new one after a short delay if (!cat2Sprite || cat2Sprite.destroyed) { cat2Sprite = null; cat2RespawnTick = 30; } } } // Update cacti, check for collision and scoring for (var i = cacti.length - 1; i >= 0; i--) { var cactus = cacti[i]; cactus.update(); // Remove if off screen if (cactus.x < -200) { cactus.destroy(); cacti.splice(i, 1); continue; } // Collision detection (only trigger on first intersect) if (!cactus.lastWasIntersecting && cat.intersects(cactus)) { // Decrease HP by 20 hp -= 20; if (hp < 0) hp = 0; // Play cat sound on HP loss LK.getSound('kedi1').play(); // Update HP bar hpBar.width = Math.max(1, hp / maxHp * hpBarFullWidth); // Flash red for hit LK.effects.flashObject(cat, 0xff0000, 400); // If HP is 0, game over if (hp <= 0) { LK.effects.flashScreen(0xff0000, 800); // Play 'death' sound 3 times in quick succession when game over screen is shown LK.showGameOver(); LK.getSound('death').play(); LK.setTimeout(function () { LK.getSound('death').play(); }, 180); LK.setTimeout(function () { LK.getSound('death').play(); }, 360); gameOver = true; return; } } // Scoring: passed cactus (cat.x > cactus.x + cactus.width/2) and not already scored if (!cactus.scored && cat.x > cactus.x + cactus.width / 2) { score++; scoreTxt.setText(score); cactus.scored = true; // Increase speed multiplier, capped at max speedMultiplier += SPEED_MULTIPLIER_INCREMENT; if (speedMultiplier > SPEED_MULTIPLIER_MAX) speedMultiplier = SPEED_MULTIPLIER_MAX; // After level 30, allow 3 jumps if (score >= 30) { cat.maxJumps = 3; } // Track cacti jumped over for stamina refill after glide if (typeof cactiJumpedOver === "undefined") { cactiJumpedOver = 0; } cactiJumpedOver++; // Handle post-glide cactus counting for stamina refill if (typeof postGlideCountingActive === "undefined") postGlideCountingActive = false; if (typeof postGlideCactusCounter === "undefined") postGlideCactusCounter = 0; if (postGlideCountingActive) { postGlideCactusCounter++; if (postGlideCactusCounter >= 10) { stamina = maxStamina; staminaBar.width = Math.max(1, stamina / maxStamina * staminaBarFullWidth); postGlideCactusCounter = 0; // Continue counting for next refill } } // (Stamina refill now handled in Cat landing after glide.) } cactus.lastWasIntersecting = cat.intersects(cactus); } };
/****
* Classes
****/
// Bird class: flies from right to left, above ground, destroys itself off screen
var Bird = Container.expand(function () {
var self = Container.call(this);
// Use 'gunes' asset for bird (placeholder, should be replaced with a bird asset if available)
var birdSprite = self.attachAsset('gunes', {
anchorX: 0.5,
anchorY: 0.5
});
birdSprite.width = 120;
birdSprite.height = 90;
birdSprite.tint = 0x888888;
birdSprite.alpha = 0.92;
self.speed = 10 + Math.random() * 4;
self.update = function () {
self.x -= self.speed;
// Destroy if off screen
if (self.lastX >= -200 && self.x < -200) {
self.destroy();
}
self.lastX = self.x;
};
return self;
});
// Cactus class: moves left, destroys itself off screen
var Cactus = Container.expand(function () {
var self = Container.call(this);
// Attach cactus asset, anchor bottom left
var cactusSprite = self.attachAsset('cactus', {
anchorX: 0.5,
anchorY: 1
});
self.speed = 7 + Math.random() * 2; // even slower speed for easier gameplay
self.update = function () {
self.x -= self.speed;
// Destroy if off screen
if (self.lastX >= -200 && self.x < -200) {
self.destroy();
}
self.lastX = self.x;
};
return self;
});
// Cat class: handles jump, gravity, and collision
var Cat = Container.expand(function () {
var self = Container.call(this);
// Attach cat asset, anchor bottom left for ground logic
var catSprite = self.attachAsset('cat', {
anchorX: 0.5,
anchorY: 1
});
// Cat physics
self.y = 0;
self.x = 0;
self.vy = 0;
self.isJumping = false;
self.groundY = 0; // will be set after creation
// Double jump state
self.jumpCount = 0;
self.maxJumps = 2;
// Jump parameters
self.jumpVelocity = -65;
self.gravity = 4;
// Update method for physics
self.update = function () {
// Handle 3-second glide if long press is active, but only once per jump and only if stamina is full
if (typeof isPressing !== "undefined" && isPressing && self.isJumping && !glideActive && !self.hasGlided && self.vy > 0 && typeof stamina !== "undefined" && stamina >= maxStamina) {
// Only start glide if falling, not already gliding, haven't glided yet this jump, and stamina is full
if (LK.ticks - pressStartTick >= 18) {
// ~300ms threshold to avoid accidental short taps
glideActive = true;
self.isHovering = false;
self.hasHovered = true;
self.glideStartTick = LK.ticks;
self.vy = 0;
self.hasGlided = true; // Mark that we've glided this jump
// Consume all stamina
stamina = 0;
staminaBar.width = Math.max(1, stamina / maxStamina * staminaBarFullWidth);
// Activate bird damage immunity for 5 seconds
birdImmunityActive = true;
birdImmunityStartTick = LK.ticks;
}
}
// If gliding, keep cat in air for up to 3 seconds (180 ticks)
if (typeof glideActive !== "undefined" && glideActive) {
if (LK.ticks - self.glideStartTick < 180) {
// Stay in air, do not apply gravity, regardless of isPressing
return;
} else {
// End glide
glideActive = false;
}
}
if (self.isJumping) {
// If we are hovering, do not apply gravity or move
if (self.isHovering) {
// Determine hover duration: 12 ticks (195ms) for first jump, 6 ticks (100ms) for others
var hoverDuration = self.jumpCount === 1 ? 12 : 6;
if (LK.ticks - self.hoverStartTick >= hoverDuration) {
self.isHovering = false;
// Resume falling, keep vy as 0 so gravity starts pulling down
self.vy = 0;
}
// While hovering, do nothing else
return;
}
// Normal jump/fall
self.vy += self.gravity;
self.y += self.vy;
// At apex: vy just turned positive (was going up, now going down)
if (!self.isHovering && self.vy > 0 && !self.hasHovered) {
self.isHovering = true;
self.hasHovered = true;
self.hoverStartTick = LK.ticks;
self.vy = 0; // Stop vertical movement during hover
return;
}
// Land on ground
if (self.y >= self.groundY) {
self.y = self.groundY;
self.vy = 0;
self.isJumping = false;
self.isHovering = false;
self.hasHovered = false;
// If we just finished a glide (hasGlided true, glideActive false), start counting cacti for stamina refill
if (typeof self.hasGlided !== "undefined" && self.hasGlided && typeof glideActive !== "undefined" && !glideActive) {
// Start post-glide cactus counting
if (typeof postGlideCactusCounter === "undefined") postGlideCactusCounter = 0;
postGlideCactusCounter = 0;
postGlideCountingActive = true;
}
self.hasGlided = false; // Reset glide on landing
self.jumpCount = 0; // Reset jump count on landing
}
}
};
// Jump method
self.jump = function () {
if (self.jumpCount < self.maxJumps) {
self.isJumping = true;
self.vy = self.jumpVelocity;
self.isHovering = false;
self.hasHovered = false;
self.hasGlided = false; // Allow one glide per jump
self.hoverStartTick = 0;
self.jumpCount++;
// Play cat sound on jump
LK.getSound('kedi').play();
}
};
return self;
});
// Cloud class: moves left, destroys itself off screen
var Cloud = Container.expand(function () {
var self = Container.call(this);
// Use a white ellipse for the cloud
var cloudSprite = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5
});
// Randomize cloud size
var scale = 1.2 + Math.random() * 1.2;
cloudSprite.width = 320 * scale;
cloudSprite.height = 120 * scale;
cloudSprite.tint = 0xFFFFFF;
cloudSprite.alpha = 0.7 + Math.random() * 0.2;
self.speed = 1.5 + Math.random() * 1.2;
self.update = function () {
self.x -= self.speed;
if (self.lastX >= -400 && self.x < -400) {
self.destroy();
}
self.lastX = self.x;
};
return self;
});
// Gold class: moves left, destroys itself off screen, collectible by cat
var Gold = Container.expand(function () {
var self = Container.call(this);
// Attach gold asset, anchor center
var goldSprite = self.attachAsset('ghost', {
anchorX: 0.5,
anchorY: 0.5
});
goldSprite.width = 80;
goldSprite.height = 80;
self.speed = 7 + Math.random() * 2;
self.collected = false;
self.update = function () {
self.x -= self.speed;
// Destroy if off screen
if (self.lastX >= -200 && self.x < -200) {
self.destroy();
}
self.lastX = self.x;
};
return self;
});
// Sun class: static, top right
var Sun = Container.expand(function () {
var self = Container.call(this);
// Use a yellow ellipse for the sun
var sunSprite = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5
});
sunSprite.width = 260;
sunSprite.height = 260;
sunSprite.tint = 0xFFF700;
sunSprite.alpha = 0.95;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xF7E9A0 // light sand yellow, desert-like
});
/****
* Game Code
****/
;
// --- GAME CODE ---
// --- Cactus background images for desert feel ---
var cactusBgSprites = [];
var cactusBgCount = 6; // Number of background cacti
for (var i = 0; i < cactusBgCount; i++) {
var cactusBg = LK.getAsset('cactus', {
anchorX: 0.5,
anchorY: 1
});
// Randomize X position, spread across the width
cactusBg.x = 200 + i * (1700 / (cactusBgCount - 1)) + Math.random() * 80 - 40;
// Randomize Y position, but always above ground
cactusBg.y = GROUND_Y + 2;
// Randomize scale for depth effect
var scale = 0.7 + Math.random() * 0.5;
cactusBg.width = 180 * scale;
cactusBg.height = 180 * scale;
cactusBg.alpha = 0.38 + Math.random() * 0.13; // faded for background
// Optionally, tint to a lighter green for background
cactusBg.tint = 0xB6E2A1;
game.addChild(cactusBg);
cactusBgSprites.push(cactusBg);
}
// Game constants
var GROUND_Y = 2200; // y position of ground (bottom of screen minus margin)
var CAT_START_X = 400;
var CAT_START_Y = GROUND_Y;
var CACTUS_SPAWN_MIN = 70; // min frames between cacti (slower, more time to jump)
var CACTUS_SPAWN_MAX = 130; // max frames between cacti (slower, more time to jump)
// --- Tile 'kum' (sand) image as background under the ground, filling as horizontal rows with no visible gaps ---
var sandTiles = [];
// Use the native asset width/height for perfect tiling, but scale up for larger sand tiles
var sandAsset = LK.getAsset('kum', {
anchorX: 0,
anchorY: 0
});
// Scale factor for larger sand tiles (increased for bigger appearance)
var sandScale = 2.3;
var sandTileWidth = sandAsset.width * sandScale;
var sandTileHeight = sandAsset.height * sandScale;
// Remove the temporary asset from the stage if it was added
if (typeof sandAsset.parent !== "undefined" && sandAsset.parent) sandAsset.parent.removeChild(sandAsset);
// Calculate how many tiles are needed to cover the width and height, with +2 for safe coverage
var sandColumnsCount = Math.ceil(2048 / sandTileWidth) + 2;
var sandRowsCount = Math.ceil((2732 - (GROUND_Y + 2)) / sandTileHeight);
// Place sand tiles exactly edge-to-edge horizontally for seamless infinite looping
for (var row = 0; row < sandRowsCount; row++) {
for (var col = 0; col < sandColumnsCount; col++) {
var sandTile = LK.getAsset('kum', {
anchorX: 0,
anchorY: 0
});
// Place each tile exactly edge-to-edge horizontally, no gap, no overlap
sandTile.x = Math.round(col * sandTileWidth);
sandTile.y = Math.round(GROUND_Y + 2 + row * sandTileHeight);
sandTile.width = sandTileWidth;
sandTile.height = sandTileHeight;
sandTile.alpha = 0.98;
game.addChild(sandTile);
sandTiles.push(sandTile);
}
}
// No visible gap, no overlap, seamless horizontal tiling for infinite looping
// Add two ground images ('zemin') for seamless horizontal scrolling
var groundImg = LK.getAsset('zemin', {
anchorX: 0,
anchorY: 1
});
groundImg.x = 0;
groundImg.y = GROUND_Y + 2; // +2 for slight overlap, ensures cat and cactus are on top
groundImg.width = 2048;
game.addChild(groundImg);
// Add a second ground image for seamless looping
var groundImg2 = LK.getAsset('zemin', {
anchorX: 0,
anchorY: 1
});
groundImg2.x = groundImg.x + groundImg.width;
groundImg2.y = GROUND_Y + 2;
groundImg2.width = 2048;
game.addChild(groundImg2);
// Add a ground image under each cactus as well, so cactus stands on ground
// This is handled by drawing the ground first, so both cat and cactus visually stand on the same ground
// Sun and clouds
var sun;
var clouds = [];
var nextCloudTick = 0;
// Birds
var birds = [];
var nextBirdTick = 0;
// Game state
var cat;
var cacti = [];
var golds = [];
var score = 0;
var goldScore = 0;
var scoreTxt;
var goldScoreTxt;
var nextCactusTick = 0;
var nextGoldTick = 0;
var gameOver = false;
// On game start, stamina is full
stamina = maxStamina;
if (typeof staminaBar !== "undefined") {
staminaBar.width = Math.max(1, stamina / maxStamina * staminaBarFullWidth);
}
// Track how many cacti have been jumped over for stamina refill
var cactiJumpedOver = 0;
// Track post-glide cactus counting for stamina refill
var postGlideCactusCounter = 0;
var postGlideCountingActive = false;
// Speed multiplier for game progression
var speedMultiplier = 1;
var SPEED_MULTIPLIER_INCREMENT = 0.08; // how much to increase per score
var SPEED_MULTIPLIER_MAX = 3; // cap the speed increase
// HP bar variables
var maxHp = 100;
var hp = maxHp;
var hpBarFullWidth = 420;
var hpBarHeight = 38;
// Add HP bar background (grey)
var hpBarBg = LK.getAsset('centerCircle', {
anchorX: 0,
anchorY: 0
});
hpBarBg.width = hpBarFullWidth;
hpBarBg.height = hpBarHeight;
hpBarBg.x = 110;
hpBarBg.y = 30;
hpBarBg.tint = 0x444444;
hpBarBg.alpha = 0.45;
LK.gui.top.addChild(hpBarBg);
// Add HP bar (red)
var hpBar = LK.getAsset('centerCircle', {
anchorX: 0,
anchorY: 0
});
hpBar.width = hpBarFullWidth;
hpBar.height = hpBarHeight;
hpBar.x = 110;
hpBar.y = 30;
hpBar.tint = 0xff4444;
hpBar.alpha = 0.95;
LK.gui.top.addChild(hpBar);
// --- Stamina bar variables and GUI ---
var maxStamina = 100;
var stamina = maxStamina;
var staminaBarFullWidth = 420;
var staminaBarHeight = 28;
// Stamina bar background (grey, below HP bar)
var staminaBarBg = LK.getAsset('centerCircle', {
anchorX: 0,
anchorY: 0
});
staminaBarBg.width = staminaBarFullWidth;
staminaBarBg.height = staminaBarHeight;
staminaBarBg.x = 110;
staminaBarBg.y = 80;
staminaBarBg.tint = 0x444444;
staminaBarBg.alpha = 0.35;
LK.gui.top.addChild(staminaBarBg);
// Stamina bar (blue)
var staminaBar = LK.getAsset('centerCircle', {
anchorX: 0,
anchorY: 0
});
staminaBar.width = staminaBarFullWidth;
staminaBar.height = staminaBarHeight;
staminaBar.x = 110;
staminaBar.y = 80;
staminaBar.tint = 0x44aaff;
staminaBar.alpha = 0.95;
LK.gui.top.addChild(staminaBar);
// Add score text to GUI
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Gold score text (smaller, right of score)
goldScoreTxt = new Text2('0', {
size: 90,
fill: 0xFFD700 // gold color
});
goldScoreTxt.anchor.set(0, 0);
goldScoreTxt.x = 120; // right of main score
goldScoreTxt.y = 20;
LK.gui.top.addChild(goldScoreTxt);
// Add sun (top right, margin from edge)
sun = new Sun();
sun.x = 2048 - 220;
sun.y = 220;
game.addChild(sun);
// Add 'gunes1' character image to the top right
var gunes1Sprite = LK.getAsset('gunes1', {
anchorX: 1,
anchorY: 0
});
gunes1Sprite.x = 2048 - 40; // 40px margin from right edge
gunes1Sprite.y = 40; // 40px margin from top edge
gunes1Sprite.width = 200;
gunes1Sprite.height = 200;
game.addChild(gunes1Sprite);
// Add 'evet' button to the top right corner of the GUI
var evetBtn = new Text2('evet', {
size: 90,
fill: 0xFFFFFF,
font: "Arial"
});
evetBtn.anchor.set(1, 0); // right-top anchor
evetBtn.x = LK.gui.top.width - 40; // 40px margin from right
evetBtn.y = 30; // 30px margin from top
LK.gui.topRight.addChild(evetBtn);
// Cat2 enemy is not created until score >= 50
var cat2Sprite = null;
// Add to update loop
game.updateCat2 = function () {
if (!cat2Sprite) return;
// Horizontal movement
cat2Sprite.x -= cat2Sprite.speed;
// Jump logic
if (!cat2Sprite.isJumping && cat2Sprite.jumpCooldown <= 0) {
cat2Sprite.vy = cat2Sprite.jumpVelocity;
cat2Sprite.isJumping = true;
cat2Sprite.jumpCooldown = cat2Sprite.jumpInterval;
}
// Apply gravity if in air
if (cat2Sprite.isJumping) {
// Move more forward (left) while jumping
cat2Sprite.x -= 8; // increased leftward movement during jump
cat2Sprite.y += cat2Sprite.vy;
cat2Sprite.vy += cat2Sprite.gravity;
// Land on ground
if (cat2Sprite.y >= cat2Sprite.groundY) {
cat2Sprite.y = cat2Sprite.groundY;
cat2Sprite.vy = 0;
cat2Sprite.isJumping = false;
// Randomize next jump interval for variety
cat2Sprite.jumpInterval = 60 + Math.floor(Math.random() * 60);
}
}
// Decrement jump cooldown if not jumping
if (!cat2Sprite.isJumping && cat2Sprite.jumpCooldown > 0) {
cat2Sprite.jumpCooldown--;
}
// Destroy if off screen to the left
if (cat2Sprite.lastX >= -cat2Sprite.width && cat2Sprite.x < -cat2Sprite.width) {
cat2Sprite.destroy();
cat2Sprite = null;
} else if (cat2Sprite) {
cat2Sprite.lastX = cat2Sprite.x;
}
};
// Spawn a few initial clouds
for (var i = 0; i < 3; i++) {
var cloud = new Cloud();
cloud.x = 400 + Math.random() * 1400;
cloud.y = 200 + Math.random() * 400;
cloud.lastX = cloud.x;
game.addChild(cloud);
clouds.push(cloud);
}
nextCloudTick = 60 + Math.floor(Math.random() * 120);
// Create cat
cat = new Cat();
cat.x = CAT_START_X;
cat.y = CAT_START_Y;
cat.groundY = CAT_START_Y;
game.addChild(cat);
// Track long press for gliding
var isPressing = false;
var pressStartTick = 0;
var glideActive = false;
// --- Bird damage immunity after stamina glide ---
var birdImmunityActive = false;
var birdImmunityStartTick = 0;
var BIRD_IMMUNITY_DURATION = 300; // 5 seconds at 60fps
// Touch/click to jump or start glide
game.down = function (x, y, obj) {
if (!gameOver) {
cat.jump();
isPressing = true;
pressStartTick = LK.ticks;
glideActive = false;
}
// Update golds, check for collection
for (var j = golds.length - 1; j >= 0; j--) {
var gold = golds[j];
gold.update();
// Remove if off screen
if (gold.x < -200) {
gold.destroy();
golds.splice(j, 1);
continue;
}
// Collect gold: only trigger on first intersect and not already collected
if (!gold.collected && cat.intersects(gold)) {
gold.collected = true;
goldScore++;
goldScoreTxt.setText(goldScore);
gold.destroy();
golds.splice(j, 1);
continue;
}
}
};
// On release, stop pressing and gliding
game.up = function (x, y, obj) {
isPressing = false;
// Do not forcibly end glide here; glide ends after 3s or on landing
};
// Main update loop
game.update = function () {
if (gameOver) return;
// --- Ground scrolling logic ---
// Move both ground images left by cactus speed (use average cactus speed or a fixed value for smoothness)
var groundScrollSpeed = 10 * speedMultiplier; // Match with cactus/bird speed for realism
groundImg.x -= groundScrollSpeed;
groundImg2.x -= groundScrollSpeed;
// Move sand tiles to match ground movement (for vertical columns)
for (var i = 0; i < sandTiles.length; i++) {
sandTiles[i].x -= groundScrollSpeed;
// If a sand tile is fully off screen to the left, move it to the right of the rightmost tile in its row
if (sandTiles[i].x <= -sandTileWidth) {
// Find the rightmost tile in the same row
var thisRow = Math.floor((sandTiles[i].y - (GROUND_Y + 2)) / sandTileHeight);
var maxX = -sandTileWidth;
for (var j = 0; j < sandTiles.length; j++) {
var otherRow = Math.floor((sandTiles[j].y - (GROUND_Y + 2)) / sandTileHeight);
if (otherRow === thisRow && sandTiles[j].x > maxX) maxX = sandTiles[j].x;
}
// Place the tile exactly after the rightmost tile for perfect seamlessness
sandTiles[i].x = maxX + sandTileWidth;
// Snap to integer to avoid subpixel gaps
sandTiles[i].x = Math.round(sandTiles[i].x);
// Ensure no vertical offset is introduced when looping
sandTiles[i].y = Math.round(GROUND_Y + 2 + thisRow * sandTileHeight);
}
}
// This ensures sand tiles are always perfectly seamless and loop infinitely with the ground
// If a ground image is fully off screen to the left, move it to the right of the other
if (groundImg.x <= -groundImg.width) {
groundImg.x = groundImg2.x + groundImg2.width;
}
if (groundImg2.x <= -groundImg2.width) {
groundImg2.x = groundImg.x + groundImg.width;
}
// Update clouds and spawn new ones
for (var i = clouds.length - 1; i >= 0; i--) {
var cloud = clouds[i];
cloud.update();
if (cloud.x < -400) {
cloud.destroy();
clouds.splice(i, 1);
}
}
if (nextCloudTick <= 0) {
var cloud = new Cloud();
cloud.x = 2048 + 200;
cloud.y = 120 + Math.random() * 600;
cloud.lastX = cloud.x;
game.addChild(cloud);
clouds.push(cloud);
nextCloudTick = 60 + Math.floor(Math.random() * 120);
} else {
nextCloudTick--;
}
// Deactivate bird immunity after 5 seconds
if (birdImmunityActive && LK.ticks - birdImmunityStartTick >= BIRD_IMMUNITY_DURATION) {
birdImmunityActive = false;
}
// Update birds and spawn new ones
for (var i = birds.length - 1; i >= 0; i--) {
var bird = birds[i];
bird.update();
if (bird.x < -200) {
bird.destroy();
birds.splice(i, 1);
continue;
}
// Bird collision detection (only trigger on first intersect)
if (!bird.lastWasIntersecting && cat.intersects(bird)) {
// If immunity is active, skip damage
if (!birdImmunityActive) {
// Decrease HP by 10
hp -= 10;
if (hp < 0) hp = 0;
// Play cat sound on HP loss
LK.getSound('kedi1').play();
// Update HP bar
hpBar.width = Math.max(1, hp / maxHp * hpBarFullWidth);
// Flash red for hit
LK.effects.flashObject(cat, 0xff0000, 400);
// If HP is 0, game over
if (hp <= 0) {
LK.effects.flashScreen(0xff0000, 800);
// Play 'death' sound 3 times in quick succession when game over screen is shown
LK.showGameOver();
LK.getSound('death').play();
LK.setTimeout(function () {
LK.getSound('death').play();
}, 180);
LK.setTimeout(function () {
LK.getSound('death').play();
}, 360);
gameOver = true;
return;
}
}
}
bird.lastWasIntersecting = cat.intersects(bird);
}
if (nextBirdTick <= 0) {
// Spawn 2-3 birds at once, with more scattered (random) vertical and horizontal spacing
var birdCount = 2 + Math.floor(Math.random() * 2); // 2 or 3 birds
var minBirdY = GROUND_Y - 950; // Lowered: birds fly closer to ground
var maxBirdY = GROUND_Y - 650; // Lowered: birds fly closer to ground
if (minBirdY < 0) minBirdY = 0;
if (maxBirdY < 0) maxBirdY = 0;
var yRange = maxBirdY - minBirdY;
var usedYs = [];
for (var b = 0; b < birdCount; b++) {
var bird = new Bird();
// Scatter birds horizontally more
bird.x = 2048 + 100 + Math.random() * 200 + b * 60 + Math.random() * 60;
// Scatter birds vertically, allow overlap and more randomness
var baseY = minBirdY + Math.random() * yRange;
// Add more jitter, and avoid birds being too close vertically
var jitterY = (Math.random() - 0.5) * 180;
var finalY = baseY + jitterY;
// Optionally, keep birds at least 120px apart vertically
var minDist = 120;
var attempts = 0;
while (usedYs.some(function (y) {
return Math.abs(y - finalY) < minDist;
}) && attempts < 10) {
finalY = minBirdY + Math.random() * yRange + (Math.random() - 0.5) * 180;
attempts++;
}
usedYs.push(finalY);
bird.y = finalY;
bird.lastX = bird.x;
// Apply speed multiplier to bird
bird.speed = bird.speed * (0.8 + 0.4 * Math.random()) * speedMultiplier;
game.addChild(bird);
birds.push(bird);
}
// Bird group appears every 220-340 ticks (increased interval for more jump time)
nextBirdTick = 220 + Math.floor(Math.random() * 120);
} else {
nextBirdTick--;
}
// Update cat
cat.update();
// Spawn cactus
if (nextCactusTick <= 0) {
var cactus = new Cactus();
cactus.x = 2048 + 100; // spawn just off right edge
cactus.y = GROUND_Y;
cactus.lastX = cactus.x;
// Apply speed multiplier to cactus
cactus.speed = cactus.speed * speedMultiplier;
game.addChild(cactus);
cacti.push(cactus);
// Use new CACTUS_SPAWN_MIN/MAX for more consistent jump windows
var spawnInterval = CACTUS_SPAWN_MIN + Math.floor(Math.random() * (CACTUS_SPAWN_MAX - CACTUS_SPAWN_MIN + 1));
nextCactusTick = Math.floor(spawnInterval);
} else {
nextCactusTick--;
}
// Spawn gold
if (nextGoldTick <= 0) {
var gold = new Gold();
gold.x = 2048 + 100;
// Random Y: between ground and a bit above cat's jump apex
var jumpApex = GROUND_Y + cat.jumpVelocity * (Math.abs(cat.jumpVelocity) / (2 * cat.gravity));
var minY = GROUND_Y - 400;
var maxY = GROUND_Y - 1000;
if (maxY < 0) maxY = 0;
gold.y = minY - Math.random() * (minY - maxY);
gold.lastX = gold.x;
gold.collected = false;
gold.speed = gold.speed * speedMultiplier;
game.addChild(gold);
golds.push(gold);
// Gold appears less frequently than cacti
nextGoldTick = 80 + Math.floor(Math.random() * 120);
} else {
nextGoldTick--;
}
// Update golds, check for collection
for (var j = golds.length - 1; j >= 0; j--) {
var gold = golds[j];
gold.update();
// Remove if off screen
if (gold.x < -200) {
gold.destroy();
golds.splice(j, 1);
continue;
}
// Collect gold: only trigger on first intersect and not already collected
if (!gold.collected && cat.intersects(gold)) {
gold.collected = true;
goldScore++;
goldScoreTxt.setText(goldScore);
// Increase HP by 10, up to maxHp
hp += 10;
if (hp > maxHp) hp = maxHp;
hpBar.width = Math.max(1, hp / maxHp * hpBarFullWidth);
gold.destroy();
golds.splice(j, 1);
continue;
}
}
// Move cat2 enemy if present, but only after score >= 30
if (score >= 30) {
// If cat2Sprite does not exist, create it
if (!cat2Sprite && !gameOver) {
if (typeof cat2RespawnTick === "undefined" || cat2RespawnTick <= 0) {
cat2RespawnTick = 0; // spawn immediately on first time
}
if (cat2RespawnTick <= 0) {
cat2Sprite = LK.getAsset('cat2', {
anchorX: 1,
anchorY: 1
});
cat2Sprite.x = 2048 - 40;
cat2Sprite.y = GROUND_Y + 2;
cat2Sprite.width = 420;
cat2Sprite.height = 420;
game.addChild(cat2Sprite);
cat2Sprite.speed = 16; // even faster movement
cat2Sprite.lastX = cat2Sprite.x;
cat2Sprite.vy = 0;
cat2Sprite.isJumping = false;
cat2Sprite.jumpCooldown = 0;
cat2Sprite.jumpInterval = 32 + Math.floor(Math.random() * 32); // jump even more often
cat2Sprite.groundY = GROUND_Y + 2;
cat2Sprite.jumpVelocity = -54; // even less jump height for cat2
cat2Sprite.gravity = 6.2; // slightly stronger gravity for a lower, snappier jump
cat2Sprite.lastWasIntersecting = false;
} else {
cat2RespawnTick--;
}
}
if (typeof game.updateCat2 === "function" && cat2Sprite) {
game.updateCat2();
// Cat2 ile ana karakter çarpışma kontrolü (ilk çarpışma anı)
if (cat2Sprite !== null && typeof cat2Sprite.lastWasIntersecting !== "undefined" && !cat2Sprite.lastWasIntersecting) {
// Cat2'nin ana gövdesiyle (pelerin hariç) çarpışma kontrolü
// Cat2'nin ana gövdesi için bir dikdörtgen belirle (pelerin hariç)
// Cat2 sprite'ı sağdan sola hareket ettiği için, ana gövdeyi sprite'ın sol kısmında tut
// Cat2'nin ana gövdesi: sprite'ın x, y, width, height'ı ile, width'in %40'ı kadar soldan (pelerin hariç)
var cat2BodyWidth = cat2Sprite.width * 0.6;
var cat2BodyHeight = cat2Sprite.height * 0.85;
var cat2BodyX = cat2Sprite.x - cat2Sprite.width + cat2Sprite.width * 0.2;
var cat2BodyY = cat2Sprite.y - cat2Sprite.height * 0.85;
// Cat'in bounding box'ı
var catBodyWidth = cat.width * 0.7;
var catBodyHeight = cat.height * 0.85;
var catBodyX = cat.x - cat.width * 0.5 + cat.width * 0.15;
var catBodyY = cat.y - cat.height * 0.85;
// Dikdörtgen çarpışma kontrolü
var isBodyIntersect = cat2BodyX < catBodyX + catBodyWidth && cat2BodyX + cat2BodyWidth > catBodyX && cat2BodyY < catBodyY + catBodyHeight && cat2BodyY + cat2BodyHeight > catBodyY;
if (isBodyIntersect) {
// 15hp azalt
hp -= 15;
if (hp < 0) hp = 0;
// HP barı güncelle
hpBar.width = Math.max(1, hp / maxHp * hpBarFullWidth);
// Kırmızı flash efekti
LK.effects.flashObject(cat, 0xff0000, 400);
// Ölüm kontrolü
if (hp <= 0) {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
LK.getSound('death').play();
LK.setTimeout(function () {
LK.getSound('death').play();
}, 180);
LK.setTimeout(function () {
LK.getSound('death').play();
}, 360);
gameOver = true;
return;
}
}
}
// Son intersect durumunu güncelle
if (cat2Sprite !== null) {
cat2Sprite.lastWasIntersecting = cat.intersects(cat2Sprite);
}
// If cat2Sprite is destroyed, spawn a new one after a short delay
if (!cat2Sprite || cat2Sprite.destroyed) {
cat2Sprite = null;
cat2RespawnTick = 30;
}
}
}
// Update cacti, check for collision and scoring
for (var i = cacti.length - 1; i >= 0; i--) {
var cactus = cacti[i];
cactus.update();
// Remove if off screen
if (cactus.x < -200) {
cactus.destroy();
cacti.splice(i, 1);
continue;
}
// Collision detection (only trigger on first intersect)
if (!cactus.lastWasIntersecting && cat.intersects(cactus)) {
// Decrease HP by 20
hp -= 20;
if (hp < 0) hp = 0;
// Play cat sound on HP loss
LK.getSound('kedi1').play();
// Update HP bar
hpBar.width = Math.max(1, hp / maxHp * hpBarFullWidth);
// Flash red for hit
LK.effects.flashObject(cat, 0xff0000, 400);
// If HP is 0, game over
if (hp <= 0) {
LK.effects.flashScreen(0xff0000, 800);
// Play 'death' sound 3 times in quick succession when game over screen is shown
LK.showGameOver();
LK.getSound('death').play();
LK.setTimeout(function () {
LK.getSound('death').play();
}, 180);
LK.setTimeout(function () {
LK.getSound('death').play();
}, 360);
gameOver = true;
return;
}
}
// Scoring: passed cactus (cat.x > cactus.x + cactus.width/2) and not already scored
if (!cactus.scored && cat.x > cactus.x + cactus.width / 2) {
score++;
scoreTxt.setText(score);
cactus.scored = true;
// Increase speed multiplier, capped at max
speedMultiplier += SPEED_MULTIPLIER_INCREMENT;
if (speedMultiplier > SPEED_MULTIPLIER_MAX) speedMultiplier = SPEED_MULTIPLIER_MAX;
// After level 30, allow 3 jumps
if (score >= 30) {
cat.maxJumps = 3;
}
// Track cacti jumped over for stamina refill after glide
if (typeof cactiJumpedOver === "undefined") {
cactiJumpedOver = 0;
}
cactiJumpedOver++;
// Handle post-glide cactus counting for stamina refill
if (typeof postGlideCountingActive === "undefined") postGlideCountingActive = false;
if (typeof postGlideCactusCounter === "undefined") postGlideCactusCounter = 0;
if (postGlideCountingActive) {
postGlideCactusCounter++;
if (postGlideCactusCounter >= 10) {
stamina = maxStamina;
staminaBar.width = Math.max(1, stamina / maxStamina * staminaBarFullWidth);
postGlideCactusCounter = 0;
// Continue counting for next refill
}
}
// (Stamina refill now handled in Cat landing after glide.)
}
cactus.lastWasIntersecting = cat.intersects(cactus);
}
};