User prompt
oyunun herşeyini optimize et
User prompt
assets deki music ve ses olanları sıfırlar mısın
User prompt
düzelmedi
User prompt
Sound generation failed yazıyor
User prompt
multiball yazısı çok fazla çıkıyor
User prompt
multiball yazısı çok fazla çıkıyor
User prompt
arka plan müziği koyabileceğim bir asset oluştur lütfen bunu yapar mısın
User prompt
biraz daha iyileştirilebilir
User prompt
paddle takılıyor
User prompt
paddle en sola ve en sağa gitmiyor düzeltir misin
User prompt
paddle en sola ve en sağa gitmiyor düzeltir misin
User prompt
assets yerindeki music adlı yerdeki şarkıları bgmusic olarak ayarla
User prompt
bgmusic koyacağim bir yer ekle assete
User prompt
her level atladığında oyuncuya 1 tane fazladan atılabilir top ver
User prompt
Please fix the bug: 'Timeout.tick error: Cannot set properties of undefined (setting 'width')' in or related to this line: 'block.blockSprite.width = blockWScaled;' Line Number: 1274
User prompt
oyuncuya 10 can ekle
User prompt
oyunu level 1 den itibaren herşeyi optimize et ve bütün bug ları temizle
User prompt
oyunu komple fixle
User prompt
Please fix the bug: 'Timeout.tick error: block.setColor is not a function' in or related to this line: 'block.setColor(colors[(i + level) % colors.length]);' Line Number: 1265
User prompt
oyun kolaydan zora doğru gitsin
User prompt
Please fix the bug: 'Timeout.tick error: block.setColor is not a function' in or related to this line: 'block.setColor(color);' Line Number: 1249
User prompt
Please fix the bug: 'Timeout.tick error: block.setColor is not a function' in or related to this line: 'block.setColor(color);' Line Number: 1249
User prompt
düşen toplarda hepsiyle çarpışsın
User prompt
oyundaki bütün top türleri bütün toplarla çarpışabilsin
User prompt
ballrandom özelliğini kaldır
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Ball class var Ball = Container.expand(function () { var self = Container.call(this); var ballSprite = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); self.radius = ballSprite.width / 2; self.vx = 0; self.vy = 0; self.speed = 22; // Initial speed self.sticky = false; // If true, ball sticks to paddle self.launch = function (angle) { // Launch ball at angle (in radians) self.vx = Math.cos(angle) * self.speed; self.vy = Math.sin(angle) * self.speed; self.sticky = false; }; self.update = function () { if (!self.sticky) { self.x += self.vx; self.y += self.vy; // --- Ava: Prevent main ball from slowing down too much or getting stuck in weird slow movement --- // Clamp minimum speed for both vx and vy (except when sticky) var minAbsV = 6; // Minimum absolute velocity for each axis var maxAbsV = 60; // Maximum absolute velocity for each axis (defensive) if (Math.abs(self.vx) < minAbsV) { self.vx = (self.vx < 0 ? -1 : 1) * minAbsV; } if (Math.abs(self.vy) < minAbsV) { self.vy = (self.vy < 0 ? -1 : 1) * minAbsV; } // Clamp to max as well (defensive) if (Math.abs(self.vx) > maxAbsV) { self.vx = (self.vx < 0 ? -1 : 1) * maxAbsV; } if (Math.abs(self.vy) > maxAbsV) { self.vy = (self.vy < 0 ? -1 : 1) * maxAbsV; } // Prevent pathological near-horizontal or near-vertical movement var minAngle = 0.18; // ~10 degrees from axis var speed = Math.sqrt(self.vx * self.vx + self.vy * self.vy); if (speed > 0) { var angle = Math.atan2(self.vy, self.vx); // If angle is too close to horizontal (0 or PI), nudge vy if (Math.abs(Math.sin(angle)) < minAngle) { self.vy += (self.vy < 0 ? -1 : 1) * minAbsV; } // If angle is too close to vertical (PI/2 or -PI/2), nudge vx if (Math.abs(Math.cos(angle)) < minAngle) { self.vx += (self.vx < 0 ? -1 : 1) * minAbsV; } } // Prevent ball from leaving left, right, or top of the screen if (self.x - self.radius < 0) { self.x = self.radius; self.vx = -self.vx; } if (self.x + self.radius > 2048) { self.x = 2048 - self.radius; self.vx = -self.vx; } if (self.y - self.radius < 0) { self.y = self.radius; self.vy = -self.vy; } // Laser/fireball: instantly destroy all blocks touched if ((self.fireball || typeof paddle !== "undefined" && paddle && paddle.laserBeam) && typeof blocks !== "undefined" && blocks && blocks.length) { for (var i = blocks.length - 1; i >= 0; i--) { var block = blocks[i]; if (block && self.intersects && self.intersects(block)) { block.hp = 0; if (block.updateHpBar) block.updateHpBar(); block.alpha = 0; block.scaleX = block.scaleY = 1.5; block.destroy(); if (block.parent) block.parent.removeChild(block); blocks.splice(i, 1); } } } } }; return self; }); // Block class var Block = Container.expand(function () { var self = Container.call(this); // color: 'red', 'green', 'blue', 'yellow' self.color = 'red'; self.hp = 1; self.maxHp = 1; self.hpBarBg = null; self.hpBar = null; self.blockType = 'normal'; // will be set by spawnBlocks self.setColor = function (color) { self.color = color; if (self.blockSprite) self.removeChild(self.blockSprite); var assetId = 'block_' + color; self.blockSprite = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Remove old HP bars if any if (self.hpBarBg) self.removeChild(self.hpBarBg); if (self.hpBar) self.removeChild(self.hpBar); // Add HP bar background (gray) self.hpBarBg = self.attachAsset('block_red', { anchorX: 0.5, anchorY: 0.5, width: self.blockSprite.width * 0.8, height: 14, y: -self.blockSprite.height / 2 - 18, tint: 0x444444 }); // Add HP bar (green) self.hpBar = self.attachAsset('block_green', { anchorX: 0.5, anchorY: 0.5, width: self.blockSprite.width * 0.78, height: 10, y: -self.blockSprite.height / 2 - 18, tint: 0x44ff44 }); self.hpBarBg.alpha = 0.7; self.hpBar.alpha = 0.9; self.hpBarBg.zIndex = 10; self.hpBar.zIndex = 11; self.hpBarBg.interactive = false; self.hpBar.interactive = false; self.addChild(self.hpBarBg); self.addChild(self.hpBar); self.updateHpBar(); }; self.setHp = function (hp, maxHp) { self.hp = hp; self.maxHp = maxHp || hp; self.updateHpBar(); }; self.updateHpBar = function () { if (!self.hpBar || !self.hpBarBg) return; var ratio = Math.max(0, Math.min(1, self.hp / self.maxHp)); self.hpBar.width = self.blockSprite.width * 0.78 * ratio; // Color changes: green > yellow > red if (ratio > 0.66) { self.hpBar.tint = 0x44ff44; } else if (ratio > 0.33) { self.hpBar.tint = 0xffe066; } else { self.hpBar.tint = 0xff4444; } self.hpBar.visible = self.hp > 0; self.hpBarBg.visible = self.hp > 0; }; self.breakBlock = function () { // Animate block breaking tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); }; // --- HITBOX LOGIC --- // Returns the hitbox for this block, depending on its type and current transform self.getHitbox = function () { var bx = self.x, by = self.y; var w = self.blockSprite.width * (self.scaleX || 1); var h = self.blockSprite.height * (self.scaleY || 1); var rot = self.blockSprite.rotation || 0; if (self.blockType === 'round') { var r = w / 2; return { type: 'circle', x: bx, y: by, r: r }; } else if (self.blockType === 'diagonal') { var hw = w / 2, hh = h / 2; return { type: 'rotbox', x: bx, y: by, hw: hw, hh: hh, rot: rot }; } else { var hw = w / 2, hh = h / 2; return { type: 'box', x: bx, y: by, hw: hw, hh: hh }; } }; // Override .intersects for block to support all hitbox types self.intersects = function (other) { var myHitbox = self.getHitbox(); var otherHitbox = typeof other.getHitbox === "function" ? other.getHitbox() : null; if (!otherHitbox) { // Fallback: use bounding box var hw = self.blockSprite.width * (self.scaleX || 1) / 2; var hh = self.blockSprite.height * (self.scaleY || 1) / 2; return other.x > self.x - hw && other.x < self.x + hw && other.y > self.y - hh && other.y < self.y + hh; } // Circle-circle if (myHitbox.type === 'circle' && otherHitbox.type === 'circle') { var dx = myHitbox.x - otherHitbox.x; var dy = myHitbox.y - otherHitbox.y; var dist = Math.sqrt(dx * dx + dy * dy); return dist < myHitbox.r + otherHitbox.r; } // Box-box (AABB) if (myHitbox.type === 'box' && otherHitbox.type === 'box') { return Math.abs(myHitbox.x - otherHitbox.x) < myHitbox.hw + otherHitbox.hw && Math.abs(myHitbox.y - otherHitbox.y) < myHitbox.hh + otherHitbox.hh; } // Rotated box - circle if (myHitbox.type === 'rotbox' && otherHitbox.type === 'circle') { // Rotate circle center into box's local space var cos = Math.cos(-myHitbox.rot), sin = Math.sin(-myHitbox.rot); var dx = otherHitbox.x - myHitbox.x, dy = otherHitbox.y - myHitbox.y; var localX = dx * cos - dy * sin; var localY = dx * sin + dy * cos; // Clamp to box var clampedX = Math.max(-myHitbox.hw, Math.min(myHitbox.hw, localX)); var clampedY = Math.max(-myHitbox.hh, Math.min(myHitbox.hh, localY)); var distX = localX - clampedX, distY = localY - clampedY; return distX * distX + distY * distY < otherHitbox.r * otherHitbox.r; } // Circle - rotated box (swap) if (myHitbox.type === 'circle' && otherHitbox.type === 'rotbox') { // Use the same logic, swap order var cos = Math.cos(-otherHitbox.rot), sin = Math.sin(-otherHitbox.rot); var dx = myHitbox.x - otherHitbox.x, dy = myHitbox.y - otherHitbox.y; var localX = dx * cos - dy * sin; var localY = dx * sin + dy * cos; var clampedX = Math.max(-otherHitbox.hw, Math.min(otherHitbox.hw, localX)); var clampedY = Math.max(-otherHitbox.hh, Math.min(otherHitbox.hh, localY)); var distX = localX - clampedX, distY = localY - clampedY; return distX * distX + distY * distY < myHitbox.r * myHitbox.r; } // Rotated box - box (AABB fallback, not perfect) if (myHitbox.type === 'rotbox' && otherHitbox.type === 'box') { // Approximate by using AABB of rotated box var rot = myHitbox.rot; var hw = Math.abs(myHitbox.hw * Math.cos(rot)) + Math.abs(myHitbox.hh * Math.sin(rot)); var hh = Math.abs(myHitbox.hw * Math.sin(rot)) + Math.abs(myHitbox.hh * Math.cos(rot)); return Math.abs(myHitbox.x - otherHitbox.x) < hw + otherHitbox.hw && Math.abs(myHitbox.y - otherHitbox.y) < hh + otherHitbox.hh; } if (myHitbox.type === 'box' && otherHitbox.type === 'rotbox') { // Approximate by using AABB of rotated box var rot = otherHitbox.rot; var hw = Math.abs(otherHitbox.hw * Math.cos(rot)) + Math.abs(otherHitbox.hh * Math.sin(rot)); var hh = Math.abs(otherHitbox.hw * Math.sin(rot)) + Math.abs(otherHitbox.hh * Math.cos(rot)); return Math.abs(myHitbox.x - otherHitbox.x) < myHitbox.hw + hw && Math.abs(myHitbox.y - otherHitbox.y) < myHitbox.hh + hh; } // Fallback: treat as AABB var hw1 = myHitbox.hw || self.blockSprite.width * (self.scaleX || 1) / 2; var hh1 = myHitbox.hh || self.blockSprite.height * (self.scaleY || 1) / 2; var hw2 = otherHitbox.hw || (other.width ? other.width / 2 : 0); var hh2 = otherHitbox.hh || (other.height ? other.height / 2 : 0); return Math.abs(myHitbox.x - otherHitbox.x) < hw1 + hw2 && Math.abs(myHitbox.y - otherHitbox.y) < hh1 + hh2; }; return self; }); // Paddle class var Paddle = Container.expand(function () { var self = Container.call(this); var paddleSprite = self.attachAsset('paddle', { anchorX: 0.5, anchorY: 0.5 }); // Use slightly larger hitbox for collision, but keep visual size for rendering self.width = paddleSprite.width; self.height = paddleSprite.height; // For hitbox, add a small margin to the top and bottom to ensure ball is always caught self.hitboxMarginY = 18; // pixels, adjust as needed for best feel self.getHitbox = function () { return { left: self.x - self.width / 2, right: self.x + self.width / 2, top: self.y - self.height / 2 - self.hitboxMarginY, bottom: self.y + self.height / 2 + self.hitboxMarginY }; }; self.moveTo = function (x) { // Clamp paddle within game bounds (no margin, allow full left/right movement) // Add a small epsilon to prevent floating point errors causing the paddle to get stuck var epsilon = 0.01; var minX = self.width / 2 + epsilon; var maxX = 2048 - self.width / 2 - epsilon; // If x is very close to minX or maxX, snap to edge to avoid jitter if (x <= minX + 2) { self.x = minX; } else if (x >= maxX - 2) { self.x = maxX; } else { self.x = Math.max(minX, Math.min(maxX, x)); } }; return self; }); // PowerUp class var PowerUp = Container.expand(function () { var self = Container.call(this); var powerSprite = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.type = 'expand'; // 'expand', 'shrink', 'life', 'sticky' self.vy = 10; self.update = function () { self.y += self.vy; }; return self; }); // --- PowerupBall1, PowerupBall2, PowerupBall3: behave like balls but are powerups --- var PowerupBall1 = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, tint: 0xffa500 // orange }); self.radius = sprite.width / 2; self.vx = (Math.random() - 0.5) * 18; self.vy = 14 + Math.random() * 6; self.type = 'powerupball1'; self.update = function () { self.x += self.vx; self.y += self.vy; // --- Wall bounce tracking for powerup balls --- if (typeof self.leftWallHits === "undefined") self.leftWallHits = 0; if (typeof self.rightWallHits === "undefined") self.rightWallHits = 0; if (typeof self.wallBouncesNoBlock === "undefined") self.wallBouncesNoBlock = 0; if (typeof self.hasHitBlockSinceReset === "undefined") self.hasHitBlockSinceReset = false; var hitWall = false; // Prevent ball from leaving left, right, or top of the screen if (self.x - self.radius < 0) { self.x = self.radius; self.vx = -self.vx; self.leftWallHits++; hitWall = true; } if (self.x + self.radius > 2048) { self.x = 2048 - self.radius; self.vx = -self.vx; self.rightWallHits++; hitWall = true; } if (hitWall) { if (!self.hasHitBlockSinceReset) { self.wallBouncesNoBlock++; } } // If left or right wall hit more than 15 times without hitting any block, return ball to player if (self.wallBouncesNoBlock >= 15 && typeof paddle !== "undefined" && paddle) { self.x = paddle.x; self.y = paddle.y - paddle.height / 2 - self.radius - 10; self.vx = 0; self.vy = 0; self.sticky = true; self.leftWallHits = 0; self.rightWallHits = 0; self.wallBouncesNoBlock = 0; self.hasHitBlockSinceReset = false; } if (self.y - self.radius < 0) { self.y = self.radius; self.vy = -self.vy; LK.getSound('hit').play(); } // Bounce off paddle if (typeof paddle !== "undefined" && paddle && typeof paddle.getHitbox === "function") { var hitbox = paddle.getHitbox(); if (self.y + self.radius >= hitbox.top && self.y - self.radius <= hitbox.bottom && self.x + self.radius >= hitbox.left && self.x - self.radius <= hitbox.right && self.vy > 0) { // Reflect ball, angle based on where it hit the paddle var rel = (self.x - paddle.x) / (paddle.width / 2 * paddle.scaleX); var angle = -Math.PI / 3 + rel * (Math.PI / 3); // -60deg to 60deg var speed = Math.sqrt(self.vx * self.vx + self.vy * self.vy); self.vx = Math.cos(angle) * speed; self.vy = Math.sin(angle) * speed; self.y = paddle.y - paddle.height / 2 - self.radius - 2; if (self.y - self.radius < 0) { self.y = self.radius; if (self.vy < 0) self.vy = Math.abs(self.vy); } LK.getSound('hit').play(); } } // Bounce off blocks if (typeof blocks !== "undefined" && blocks && blocks.length) { for (var i = 0; i < blocks.length; i++) { var block = blocks[i]; if (block && self.intersects && self.intersects(block)) { var overlapX = Math.abs(self.x - block.x) - (block.blockSprite.width / 2 + self.radius); var overlapY = Math.abs(self.y - block.y) - (block.blockSprite.height / 2 + self.radius); if (overlapX > overlapY) { self.vx = -self.vx; } else { self.vy = -self.vy; } // Damage block like main ball if (typeof block.hp !== "undefined") { if (self.fireball || typeof paddle !== "undefined" && paddle && paddle.laserBeam) { block.hp = 0; if (block.updateHpBar) block.updateHpBar(); block.alpha = 0; block.scaleX = block.scaleY = 1.5; block.destroy(); if (block.parent) block.parent.removeChild(block); blocks.splice(i, 1); i--; } else { block.hp--; if (block.updateHpBar) block.updateHpBar(); if (block.hp <= 0) { block.breakBlock(); blocks.splice(i, 1); i--; // adjust index after removal } } } LK.getSound('hit').play(); break; // Only bounce off one block per frame } } } }; return self; }); var PowerupBall2 = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, tint: 0x00e0ff // cyan }); self.radius = sprite.width / 2; self.vx = (Math.random() - 0.5) * 16; self.vy = 12 + Math.random() * 8; self.type = 'powerupball2'; self.update = function () { self.x += self.vx; self.y += self.vy; // --- Wall bounce tracking for powerup balls --- if (typeof self.leftWallHits === "undefined") self.leftWallHits = 0; if (typeof self.rightWallHits === "undefined") self.rightWallHits = 0; if (typeof self.wallBouncesNoBlock === "undefined") self.wallBouncesNoBlock = 0; if (typeof self.hasHitBlockSinceReset === "undefined") self.hasHitBlockSinceReset = false; var hitWall = false; // Prevent ball from leaving left, right, or top of the screen if (self.x - self.radius < 0) { self.x = self.radius; self.vx = -self.vx; self.leftWallHits++; hitWall = true; } if (self.x + self.radius > 2048) { self.x = 2048 - self.radius; self.vx = -self.vx; self.rightWallHits++; hitWall = true; } if (hitWall) { if (!self.hasHitBlockSinceReset) { self.wallBouncesNoBlock++; } } // If left or right wall hit more than 15 times without hitting any block, return ball to player if (self.wallBouncesNoBlock >= 15 && typeof paddle !== "undefined" && paddle) { self.x = paddle.x; self.y = paddle.y - paddle.height / 2 - self.radius - 10; self.vx = 0; self.vy = 0; self.sticky = true; self.leftWallHits = 0; self.rightWallHits = 0; self.wallBouncesNoBlock = 0; self.hasHitBlockSinceReset = false; } if (self.y - self.radius < 0) { self.y = self.radius; self.vy = -self.vy; LK.getSound('hit').play(); } // Bounce off paddle if (typeof paddle !== "undefined" && paddle && typeof paddle.getHitbox === "function") { var hitbox = paddle.getHitbox(); if (self.y + self.radius >= hitbox.top && self.y - self.radius <= hitbox.bottom && self.x + self.radius >= hitbox.left && self.x - self.radius <= hitbox.right && self.vy > 0) { // Reflect ball, angle based on where it hit the paddle var rel = (self.x - paddle.x) / (paddle.width / 2 * paddle.scaleX); var angle = -Math.PI / 3 + rel * (Math.PI / 3); // -60deg to 60deg var speed = Math.sqrt(self.vx * self.vx + self.vy * self.vy); self.vx = Math.cos(angle) * speed; self.vy = Math.sin(angle) * speed; self.y = paddle.y - paddle.height / 2 - self.radius - 2; if (self.y - self.radius < 0) { self.y = self.radius; if (self.vy < 0) self.vy = Math.abs(self.vy); } LK.getSound('hit').play(); } } // Bounce off blocks if (typeof blocks !== "undefined" && blocks && blocks.length) { for (var i = 0; i < blocks.length; i++) { var block = blocks[i]; if (block && self.intersects && self.intersects(block)) { var overlapX = Math.abs(self.x - block.x) - (block.blockSprite.width / 2 + self.radius); var overlapY = Math.abs(self.y - block.y) - (block.blockSprite.height / 2 + self.radius); if (overlapX > overlapY) { self.vx = -self.vx; } else { self.vy = -self.vy; } // Damage block like main ball if (typeof block.hp !== "undefined") { if (self.fireball || typeof paddle !== "undefined" && paddle && paddle.laserBeam) { block.hp = 0; if (block.updateHpBar) block.updateHpBar(); block.alpha = 0; block.scaleX = block.scaleY = 1.5; block.destroy(); if (block.parent) block.parent.removeChild(block); blocks.splice(i, 1); i--; } else { block.hp--; if (block.updateHpBar) block.updateHpBar(); if (block.hp <= 0) { block.breakBlock(); blocks.splice(i, 1); i--; // adjust index after removal } } } LK.getSound('hit').play(); break; // Only bounce off one block per frame } } } }; return self; }); var PowerupBall3 = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, tint: 0x8dff00 // lime }); self.radius = sprite.width / 2; self.vx = (Math.random() - 0.5) * 20; self.vy = 10 + Math.random() * 10; self.type = 'powerupball3'; self.update = function () { self.x += self.vx; self.y += self.vy; // --- Wall bounce tracking for powerup balls --- if (typeof self.leftWallHits === "undefined") self.leftWallHits = 0; if (typeof self.rightWallHits === "undefined") self.rightWallHits = 0; if (typeof self.wallBouncesNoBlock === "undefined") self.wallBouncesNoBlock = 0; if (typeof self.hasHitBlockSinceReset === "undefined") self.hasHitBlockSinceReset = false; var hitWall = false; // Prevent ball from leaving left, right, or top of the screen if (self.x - self.radius < 0) { self.x = self.radius; self.vx = -self.vx; self.leftWallHits++; hitWall = true; } if (self.x + self.radius > 2048) { self.x = 2048 - self.radius; self.vx = -self.vx; self.rightWallHits++; hitWall = true; } if (hitWall) { if (!self.hasHitBlockSinceReset) { self.wallBouncesNoBlock++; } } // If left or right wall hit more than 15 times without hitting any block, return ball to player if (self.wallBouncesNoBlock >= 15 && typeof paddle !== "undefined" && paddle) { self.x = paddle.x; self.y = paddle.y - paddle.height / 2 - self.radius - 10; self.vx = 0; self.vy = 0; self.sticky = true; self.leftWallHits = 0; self.rightWallHits = 0; self.wallBouncesNoBlock = 0; self.hasHitBlockSinceReset = false; } if (self.y - self.radius < 0) { self.y = self.radius; self.vy = -self.vy; LK.getSound('hit').play(); } // Bounce off paddle if (typeof paddle !== "undefined" && paddle && typeof paddle.getHitbox === "function") { var hitbox = paddle.getHitbox(); if (self.y + self.radius >= hitbox.top && self.y - self.radius <= hitbox.bottom && self.x + self.radius >= hitbox.left && self.x - self.radius <= hitbox.right && self.vy > 0) { // Reflect ball, angle based on where it hit the paddle var rel = (self.x - paddle.x) / (paddle.width / 2 * paddle.scaleX); var angle = -Math.PI / 3 + rel * (Math.PI / 3); // -60deg to 60deg var speed = Math.sqrt(self.vx * self.vx + self.vy * self.vy); self.vx = Math.cos(angle) * speed; self.vy = Math.sin(angle) * speed; self.y = paddle.y - paddle.height / 2 - self.radius - 2; if (self.y - self.radius < 0) { self.y = self.radius; if (self.vy < 0) self.vy = Math.abs(self.vy); } LK.getSound('hit').play(); } } // Bounce off blocks if (typeof blocks !== "undefined" && blocks && blocks.length) { for (var i = 0; i < blocks.length; i++) { var block = blocks[i]; if (block && self.intersects && self.intersects(block)) { var overlapX = Math.abs(self.x - block.x) - (block.blockSprite.width / 2 + self.radius); var overlapY = Math.abs(self.y - block.y) - (block.blockSprite.height / 2 + self.radius); if (overlapX > overlapY) { self.vx = -self.vx; } else { self.vy = -self.vy; } // Damage block like main ball if (typeof block.hp !== "undefined") { if (self.fireball || typeof paddle !== "undefined" && paddle && paddle.laserBeam) { block.hp = 0; if (block.updateHpBar) block.updateHpBar(); block.alpha = 0; block.scaleX = block.scaleY = 1.5; block.destroy(); if (block.parent) block.parent.removeChild(block); blocks.splice(i, 1); i--; } else { block.hp--; if (block.updateHpBar) block.updateHpBar(); if (block.hp <= 0) { block.breakBlock(); blocks.splice(i, 1); i--; // adjust index after removal } } } LK.getSound('hit').play(); break; // Only bounce off one block per frame } } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181c2c }); /**** * Game Code ****/ // Game variables // Ball // Paddle // Block colors // Power-up // Sounds function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } var paddle = null, ball = null, blocks = [], powerups = [], powerupBalls = []; // holds PowerupBall1/2/3 instances if (!window.extraBalls || !Array.isArray(window.extraBalls)) window.extraBalls = []; if (!window.activePowerups || !Array.isArray(window.activePowerups)) window.activePowerups = []; if (!window.powerupTimers || !Array.isArray(window.powerupTimers)) window.powerupTimers = []; if (!window.powerupIcons || !Array.isArray(window.powerupIcons)) window.powerupIcons = []; var lives = 3; var level = 1; var isBallLaunched = false; var dragPaddle = false; // --- Ava: Variables for approaching pattern logic --- var patternYOffset = 0; // How much the pattern has moved down toward the player var patternApproachStep = 120; // How much to move down after each lost life (pixels) var patternApproachMax = 0; // Will be set after blocks are spawned, so pattern never goes below paddle // Always reset score to zero at the start of each game and persist it var score = 0; storage.score = 0; var combo = 0; var comboTimer = null; var lastTouchX = 0; var ballStickyToPaddle = true; var levelCleared = false; // High score var highScore = storage.highScore || 0; // GUI var scoreTxt = new Text2('0', { size: 100, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var highScoreTxt = new Text2('High: ' + highScore, { size: 60, fill: 0xFFE066 }); highScoreTxt.anchor.set(0.5, 0); highScoreTxt.y = 90; LK.gui.top.addChild(highScoreTxt); var livesTxt = new Text2('♥♥♥', { size: 80, fill: 0xFF5E5E }); livesTxt.anchor.set(1, 0); LK.gui.topRight.addChild(livesTxt); var levelTxt = new Text2('Level 1', { size: 70, fill: 0xFFE066 }); levelTxt.anchor.set(0, 0); LK.gui.topLeft.addChild(levelTxt); // Helper: update GUI function updateGUI() { scoreTxt.setText(score); storage.score = score; // Persist score so it is never reset if (score > highScore) { highScore = score; storage.highScore = highScore; } highScoreTxt.setText('High: ' + highScore); var hearts = ''; for (var i = 0; i < lives; i++) hearts += '♥'; livesTxt.setText(hearts); // Show only current level, do not show pattern chars levelTxt.setText('Level ' + level); } // Helper: reset combo function resetCombo() { combo = 0; if (comboTimer) { LK.clearTimeout(comboTimer); comboTimer = null; } } // Helper: start combo timer function startCombo() { if (comboTimer) LK.clearTimeout(comboTimer); comboTimer = LK.setTimeout(function () { resetCombo(); }, 1200); } // Helper: spawn blocks for current level function spawnBlocks() { // Remove old blocks for (var i = 0; i < blocks.length; i++) { blocks[i].destroy(); } blocks = []; // Dynamic block pattern logic // --- Dynamically scale blocks so that all patterns always fit on screen --- // Estimate max pattern size for this level var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; var numChars = 1 + Math.floor((level - 1) / 2); numChars = Math.min(numChars, 6); // max 6 chars side by side for high difficulty var rows = 7; var colsPerChar = 5; var gapCols = 1; // 1 column gap between chars var totalCols = numChars * colsPerChar + (numChars - 1) * gapCols; // Set margins (minimum 10px), and add wallMargin to ensure blocks never touch any wall var wallMargin = 40; // Minimum distance from all walls var marginX = Math.max(10, 40 - level * 2); var marginY = Math.max(10, 40 - level * 2); // Calculate max block size so that all blocks fit horizontally and vertically, never touching any wall var maxWidth = 2048 - 2 * wallMargin; var maxHeight = 900 - 2 * wallMargin; // Allow up to 900px for block area, minus wallMargin at top and bottom var blockW = Math.floor((maxWidth - (totalCols - 1) * marginX) / totalCols); var blockH = Math.floor((maxHeight - (rows - 1) * marginY) / rows); // Clamp block size to reasonable min/max blockW = Math.max(60, Math.min(blockW, 220)); blockH = Math.max(40, Math.min(blockH, 120)); var colors = ['red', 'green', 'blue', 'yellow']; // --- New: Generate block positions in a grid to form random letters and/or numbers side by side per level --- // Returns an array of {x, y} positions for blocks, forming multiple random letters/numbers in a row function getPatternBlockPositions(level, blockW, blockH) { // Define 5x7 bitmap font for A-Z, 0-9 (1=block, 0=empty) var font = { "A": [[0, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 1, 1, 1, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1]], "B": [[1, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 1, 1, 1, 0]], "C": [[0, 1, 1, 1, 1], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [0, 1, 1, 1, 1]], "D": [[1, 1, 1, 0, 0], [1, 0, 0, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 1, 0], [1, 1, 1, 0, 0]], "E": [[1, 1, 1, 1, 1], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 1, 1, 1, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 1, 1, 1, 1]], "F": [[1, 1, 1, 1, 1], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 1, 1, 1, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0]], "G": [[0, 1, 1, 1, 1], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 1, 1, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 1]], "H": [[1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 1, 1, 1, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1]], "I": [[1, 1, 1, 1, 1], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [1, 1, 1, 1, 1]], "J": [[0, 0, 0, 1, 1], [0, 0, 0, 0, 1], [0, 0, 0, 0, 1], [0, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0]], "K": [[1, 0, 0, 0, 1], [1, 0, 0, 1, 0], [1, 0, 1, 0, 0], [1, 1, 0, 0, 0], [1, 0, 1, 0, 0], [1, 0, 0, 1, 0], [1, 0, 0, 0, 1]], "L": [[1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 1, 1, 1, 1]], "M": [[1, 0, 0, 0, 1], [1, 1, 0, 1, 1], [1, 0, 1, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1]], "N": [[1, 0, 0, 0, 1], [1, 1, 0, 0, 1], [1, 0, 1, 0, 1], [1, 0, 0, 1, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1]], "O": [[0, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0]], "P": [[1, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 1, 1, 1, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0]], "Q": [[0, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 1, 0, 1], [1, 0, 0, 1, 0], [0, 1, 1, 0, 1]], "R": [[1, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 1, 1, 1, 0], [1, 0, 1, 0, 0], [1, 0, 0, 1, 0], [1, 0, 0, 0, 1]], "S": [[0, 1, 1, 1, 1], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 1], [0, 0, 0, 0, 1], [1, 1, 1, 1, 0]], "T": [[1, 1, 1, 1, 1], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0]], "U": [[1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0]], "V": [[1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 0, 1, 0], [0, 1, 0, 1, 0], [0, 0, 1, 0, 0]], "W": [[1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 1, 0, 1], [1, 1, 0, 1, 1], [1, 1, 0, 1, 1], [1, 0, 0, 0, 1]], "X": [[1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1]], "Y": [[1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 0, 1, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0]], "Z": [[1, 1, 1, 1, 1], [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0], [1, 0, 0, 0, 0], [1, 1, 1, 1, 1]], "0": [[0, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 1, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0]], "1": [[0, 0, 1, 0, 0], [0, 1, 1, 0, 0], [1, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [1, 1, 1, 1, 1]], "2": [[0, 1, 1, 1, 0], [1, 0, 0, 0, 1], [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0], [1, 1, 1, 1, 1]], "3": [[1, 1, 1, 1, 0], [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 1, 0], [0, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0]], "4": [[0, 0, 0, 1, 0], [0, 0, 1, 1, 0], [0, 1, 0, 1, 0], [1, 0, 0, 1, 0], [1, 1, 1, 1, 1], [0, 0, 0, 1, 0], [0, 0, 0, 1, 0]], "5": [[1, 1, 1, 1, 1], [1, 0, 0, 0, 0], [1, 1, 1, 1, 0], [0, 0, 0, 0, 1], [0, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0]], "6": [[0, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 0], [1, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0]], "7": [[1, 1, 1, 1, 1], [0, 0, 0, 0, 1], [0, 0, 0, 1, 0], [0, 0, 1, 0, 0], [0, 1, 0, 0, 0], [0, 1, 0, 0, 0], [0, 1, 0, 0, 0]], "8": [[0, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0]], "9": [[0, 1, 1, 1, 0], [1, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 1], [0, 0, 0, 0, 1], [1, 0, 0, 0, 1], [0, 1, 1, 1, 0]] }; // Decide how many chars to show side by side (1-6, more as level increases) var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; var numChars = 1 + Math.floor((level - 1) / 2); numChars = Math.min(numChars, 6); // max 6 chars side by side for high difficulty // Pick random chars for this level (can be letter or number, allow repeats) // --- Ava: Use a new random seed for every level start, so every playthrough is unique --- if (typeof window.levelRandomSeed === "undefined") window.levelRandomSeed = {}; // Use a unique random seed for each level start, based on Date.now() and Math.random() var levelSeed = Date.now() + Math.floor(Math.random() * 1000000); window.levelRandomSeed[level] = levelSeed; // Deterministic random for this level, so pattern is unique per play, but consistent for the level function seededRandom(seed) { var x = Math.sin(seed++) * 10000; return x - Math.floor(x); } var charArr = []; for (var i = 0; i < numChars; i++) { // Use the seed to pick a char, so every level start is different var idx = Math.floor(seededRandom(levelSeed + i * 997) * chars.length); charArr.push(chars[idx]); } // If level 1, always show a single letter for clarity if (level === 1) charArr = [chars[Math.floor(seededRandom(levelSeed) * chars.length)]]; // Expose charArr globally so updateGUI can always show the current level's pattern window.charArr = charArr; // If no pattern found for any char, fallback to random grid var allPatternsExist = true; for (var i = 0; i < charArr.length; i++) { if (!font[charArr[i]]) { allPatternsExist = false; break; } } if (!allPatternsExist) { // fallback: random grid var positions = []; var rows = 7, cols = 5 * numChars + (numChars - 1); // wider grid for more chars // Dynamically scale block size for fallback grid var marginX_fallback = marginX; var marginY_fallback = marginY; var wallMargin = 40; // Ensure fallback grid also never touches any wall var maxWidth_fallback = 2048 - 2 * wallMargin; var maxHeight_fallback = 900 - 2 * wallMargin; var blockW_fallback = Math.floor((maxWidth_fallback - (cols - 1) * marginX_fallback) / cols); var blockH_fallback = Math.floor((maxHeight_fallback - (rows - 1) * marginY_fallback) / rows); blockW_fallback = Math.max(60, Math.min(blockW_fallback, 220)); blockH_fallback = Math.max(40, Math.min(blockH_fallback, 120)); var totalW = cols * blockW_fallback + (cols - 1) * marginX_fallback; var totalH = rows * blockH_fallback + (rows - 1) * marginY_fallback; var startX = wallMargin + (2048 - 2 * wallMargin - totalW) / 2 + blockW_fallback / 2; var startY = 400 + wallMargin; for (var r = 0; r < rows; r++) { for (var c = 0; c < cols; c++) { if (Math.random() < 0.7) { positions.push({ x: startX + c * (blockW_fallback + marginX_fallback), y: startY + r * (blockH_fallback + marginY_fallback) }); } } } // Set global blockW/blockH for this fallback pattern so blocks are sized correctly blockW = blockW_fallback; blockH = blockH_fallback; return positions; } // Center the pattern(s) in the play area var rows = 7; var colsPerChar = 5; var gapCols = 1; // 1 column gap between chars var totalCols = numChars * colsPerChar + (numChars - 1) * gapCols; var wallMargin = 40; // Use same wall margin for all patterns var totalW = totalCols * blockW + (totalCols - 1) * marginX; var totalH = rows * blockH + (rows - 1) * marginY; var startX = wallMargin + (2048 - 2 * wallMargin - totalW) / 2 + blockW / 2; var startY = wallMargin + (900 - totalH) / 2 + 300; // 300 is the original y offset, but now ensure margin at top and bottom var positions = []; for (var ch = 0; ch < charArr.length; ch++) { var pattern = font[charArr[ch]]; for (var r = 0; r < rows; r++) { for (var c = 0; c < colsPerChar; c++) { if (pattern[r][c]) { // Calculate x offset for this char var charOffset = ch * (colsPerChar + gapCols) * (blockW + marginX); positions.push({ x: startX + charOffset + c * (blockW + marginX), y: startY + r * (blockH + marginY) }); } } } } return positions; } // Only normal blocks, no shape/orientation logic function getShapePattern(level, rows, cols) { // Not used anymore, but kept for compatibility var arr = []; for (var r = 0; r < rows; r++) { arr[r] = []; for (var c = 0; c < cols; c++) { arr[r][c] = { type: 'normal', present: true }; } } return arr; } // Animal shape patterns for higher levels function getAnimalPattern(level, rows, cols) { // Each pattern is a 2D array of 0/1, 1 means block present // Patterns: horse, donkey, giraffe, etc. // All patterns are 10x12 max, centered var patterns = []; // Horse (at, level 10+) patterns.push([[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0], [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]]); // Donkey (eşek, level 15+) patterns.push([[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0]]); // Giraffe (zürafa, level 20+) patterns.push([[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0]]); // Add more animal patterns as needed // Pick pattern based on level if (level >= 20) return patterns[2]; if (level >= 15) return patterns[1]; if (level >= 10) return patterns[0]; return null; } // Increase rows and columns as level increases, up to a max // --- OPTIMIZATION: For level 6+, reduce number of blocks per row/col to improve performance --- var rows, cols; if (level >= 6) { rows = Math.min(2 + Math.floor(level / 3), 7); cols = Math.min(4 + Math.floor(level / 4), 9); } else { rows = Math.min(3 + Math.floor(level / 2), 10); cols = Math.min(5 + Math.floor(level / 3), 12); } // For variety, pick a pattern type based on level var patternType = level % 5; // 0: full, 1: checker, 2: pyramid, 3: gaps, 4: zigzag if (level === 1) { patternType = 0; // Force full grid for first level } // Animal pattern override for higher levels var animalPattern = getAnimalPattern(level, rows, cols); if (animalPattern) { // Use animal pattern, override rows/cols rows = animalPattern.length; cols = animalPattern[0].length; patternType = -1; // Custom // Dynamically scale block size for animal pattern var marginX_animal = marginX; var marginY_animal = marginY; var wallMargin = 40; // Ensure animal patterns never touch any wall var maxWidth_animal = 2048 - 2 * wallMargin; var maxHeight_animal = 900 - 2 * wallMargin; var blockW_animal = Math.floor((maxWidth_animal - (cols - 1) * marginX_animal) / cols); var blockH_animal = Math.floor((maxHeight_animal - (rows - 1) * marginY_animal) / rows); blockW_animal = Math.max(60, Math.min(blockW_animal, 220)); blockH_animal = Math.max(40, Math.min(blockH_animal, 120)); blockW = blockW_animal; blockH = blockH_animal; } var totalW = cols * blockW + (cols - 1) * marginX; var startX = (2048 - totalW) / 2 + blockW / 2; var startY = 300; // --- New: get block layout in a random letter/number pattern for this level --- var blockPositions = getPatternBlockPositions(level, blockW, blockH); // --- OPTIMIZATION: Batch block creation to avoid frame stutter at level start --- var blocksToAdd = []; for (var i = 0; i < blockPositions.length; i++) { var block = new Block(); var color = colors[(i + level) % colors.length]; block.setColor(color); block.blockType = 'normal'; block.blockSprite.width = blockW; block.blockSprite.height = blockH; block.blockSprite.rotation = 0; block.x = blockPositions[i].x; block.y = blockPositions[i].y + patternYOffset; // Apply patternYOffset if any block.originalY = blockPositions[i].y; // Store originalY for approach logic // Increase block HP as level increases (scales faster with level) // Difficulty scaling: more chars, more HP, less margin, more blocks as level increases var hpBonus = Math.floor(level / 2) + Math.floor(level / 5); block.hp = 1 + Math.floor(level / 3) + Math.floor(i / 4) + hpBonus; block.maxHp = block.hp; block.setHp(block.hp, block.maxHp); blocksToAdd.push(block); } // Add all blocks to game and blocks array in a single batch to reduce layout thrashing for (var i = 0; i < blocksToAdd.length; i++) { game.addChild(blocksToAdd[i]); blocks.push(blocksToAdd[i]); } } // Helper: spawn paddle function spawnPaddle() { if (paddle) paddle.destroy(); paddle = new Paddle(); paddle.x = 2048 / 2; paddle.y = 2732 - 180; // Clamp paddle position to valid range on spawn var minX = paddle.width / 2 + 0.01; var maxX = 2048 - paddle.width / 2 - 0.01; if (paddle.x < minX) paddle.x = minX; if (paddle.x > maxX) paddle.x = maxX; game.addChild(paddle); } // Helper: spawn ball function spawnBall() { if (ball) ball.destroy(); ball = new Ball(); ball.x = paddle.x; ball.y = paddle.y - paddle.height / 2 - ball.radius - 10; ball.vx = 0; ball.vy = 0; ball.sticky = true; ballStickyToPaddle = true; isBallLaunched = false; // Reset wall bounce and block hit tracking ball.leftWallHits = 0; ball.rightWallHits = 0; ball.wallBouncesNoBlock = 0; ball.hasHitBlockSinceReset = false; game.addChild(ball); } // Helper: spawn powerup function spawnPowerUp(x, y) { // Limit to 5 powerups on screen at once if (!powerups || !Array.isArray(powerups)) powerups = []; if (powerups.length >= 5) return; var power = new PowerUp(); // Fun, engaging, and bug-free powerup types (50+) // Removed boring/annoying types, added creative/fun ones, and ensured all are bug-free // Increase probability of ball count powerups by duplicating them in the array var workingTypes = [ // Paddle/ball size & speed 'expand', 'shrink', 'bigball', 'smallball', // Ball count (duplicated for higher probability) 'multiball', 'multiball', 'multiball', 'ballsplit', 'ballsplit', 'ballsplit', 'ballclone', 'ballclone', 'ballclone', '+1ball', '+1ball', '+1ball', '+2ball', '+2ball', '+2ball', '+3ball', '+3ball', '+3ball', // Score 'scorex2', 'scorex3', // Block effects 'blockbomb', 'blockrowclear', 'blockcolclear', 'blockheal', 'blockrandom', 'blockmove', 'blockshrink', 'blockexpand', 'blockshield', 'blockswap', 'blockteleport', 'blockregen', 'blockexplode', 'blockinvisible', // Ball physics 'slow', 'fast', 'ballheavy', 'balllight', 'ballcurve', 'ballzigzag', 'ballrandom', 'ballteleport', 'ballinvisible', 'superball', // Paddle physics 'magnet', // Special/fun 'sticky', 'life', // Laser and fireball (now equal probability with others) 'laser', 'fireball', // Other powerups 'iceball', 'portal', 'gravity', 'mirror', 'shrinkall', 'expandall', 'swapall', 'rainbow', 'cloneblock', 'blockfall', 'blockrise', 'blockrain', 'blockquake', 'blockfreeze', 'blockflash', 'blockmirror', 'blockportal', 'blockgravity', 'blockmagnet', 'blocklaser', 'blockfire', 'blockice', 'blockghost', 'blocksuper', 'blockrandomhp', 'blockbonus', 'blocktrap', 'blockshieldall', 'blockswapall', 'blockteleportall', 'blockregenall', 'blockexplodeall']; // Pick a random working type with increased probability for ball count powerups power.type = workingTypes[Math.floor(Math.random() * workingTypes.length)]; // If x or y is undefined, spawn at a random position within the play area if (typeof x === "undefined" || typeof y === "undefined") { // Avoid top 200px and bottom 300px, and 40px margin on sides var marginX = 40; var marginYTop = 200; var marginYBottom = 300; power.x = marginX + Math.random() * (2048 - 2 * marginX); // Make powerups spawn mostly in the upper part of the playfield (80% chance) if (Math.random() < 0.8) { // Top 40% of the playfield (above the paddle area) var upperHeight = Math.floor((2732 - marginYTop - marginYBottom) * 0.4); power.y = marginYTop + Math.random() * upperHeight; } else { // Anywhere in the playfield (fallback) power.y = marginYTop + Math.random() * (2732 - marginYTop - marginYBottom); } } else { power.x = x; power.y = y; } game.addChild(power); if (!powerups || !Array.isArray(powerups)) powerups = []; powerups.push(power); } // Helper: next level function nextLevel() { level++; lives = Math.min(5, lives + 1); // Add 1 life, max 5 // --- Difficulty scaling: increase ball speed, block HP, and decrease powerup spawn interval --- if (ball) { // Increase main ball speed a bit every level, up to a max ball.speed = Math.min(22 + level * 1.2, 60); // If ball is moving, scale its velocity to new speed var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); if (speed > 0) { var scale = ball.speed / speed; ball.vx *= scale; ball.vy *= scale; } } // Store global difficulty for use in spawnBlocks and powerup logic window.difficultyLevel = level; // Decrease powerup spawn interval (min 120 ticks) game.powerupSpawnInterval = Math.max(120, 360 - level * 12); // Clear combo timer on level up if (comboTimer) { LK.clearTimeout(comboTimer); comboTimer = null; } // Remove all falling powerup balls if (!powerupBalls || !Array.isArray(powerupBalls)) powerupBalls = []; if (powerupBalls && powerupBalls.length) { for (var i = powerupBalls.length - 1; i >= 0; i--) { if (powerupBalls[i]) powerupBalls[i].destroy(); powerupBalls.splice(i, 1); } } // Remove all extra balls if (!window.extraBalls || !Array.isArray(window.extraBalls)) window.extraBalls = []; if (window.extraBalls && window.extraBalls.length) { for (var i = window.extraBalls.length - 1; i >= 0; i--) { if (window.extraBalls[i]) window.extraBalls[i].destroy(); window.extraBalls.splice(i, 1); } } updateGUI(); patternYOffset = 0; // Reset pattern approach on new level spawnBlocks(); spawnPaddle(); spawnBall(); levelCleared = false; // Set patternApproachMax so pattern never goes below paddle // (Pattern should not go below y = paddle.y - 200) patternApproachMax = Math.max(0, (paddle ? paddle.y : 2732 - 180) - 200 - 400); // 400 is the original pattern startY // Clear all powerup timers and arrays if (window.powerupTimers && Array.isArray(window.powerupTimers)) { for (var t = 0; t < window.powerupTimers.length; t++) { if (window.powerupTimers[t]) LK.clearTimeout(window.powerupTimers[t]); } window.powerupTimers = []; } if (window.activePowerups && Array.isArray(window.activePowerups)) window.activePowerups = []; if (window.powerupIcons && Array.isArray(window.powerupIcons)) { for (var i = 0; i < window.powerupIcons.length; i++) { if (window.powerupIcons[i] && typeof window.powerupIcons[i].destroy === "function") window.powerupIcons[i].destroy(); } window.powerupIcons = []; } } // Endless: Remove win condition, always go to nextLevel // Helper: lose life function loseLife() { // If any powerup ball is on screen, do not lose a life, just respawn the main ball and paddle if (!powerupBalls || !Array.isArray(powerupBalls)) powerupBalls = []; if (powerupBalls && powerupBalls.length > 0) { spawnPaddle(); spawnBall(); return; } lives--; updateGUI(); LK.getSound('lose').play(); if (lives <= 0) { if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('High: ' + highScore); } score = 0; storage.score = 0; // Reset score on game over // Clear all powerup timers and arrays if (window.powerupTimers && Array.isArray(window.powerupTimers)) { for (var t = 0; t < window.powerupTimers.length; t++) { if (window.powerupTimers[t]) LK.clearTimeout(window.powerupTimers[t]); } window.powerupTimers = []; } if (window.activePowerups && Array.isArray(window.activePowerups)) window.activePowerups = []; if (window.powerupIcons && Array.isArray(window.powerupIcons)) { for (var i = 0; i < window.powerupIcons.length; i++) { if (window.powerupIcons[i] && typeof window.powerupIcons[i].destroy === "function") window.powerupIcons[i].destroy(); } window.powerupIcons = []; } LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } // Remove all extra balls when losing a life, but do not reduce life for losing extra balls if (!window.extraBalls || !Array.isArray(window.extraBalls)) window.extraBalls = []; if (window.extraBalls && window.extraBalls.length) { for (var i = window.extraBalls.length - 1; i >= 0; i--) { if (window.extraBalls[i]) window.extraBalls[i].destroy(); window.extraBalls.splice(i, 1); } } window.extraBalls = []; // --- Ava: Move pattern down after each lost life --- patternYOffset += patternApproachStep; if (patternYOffset > patternApproachMax) patternYOffset = patternApproachMax; // Move all blocks down by patternYOffset (relative to their original y) if (blocks && blocks.length) { for (var i = 0; i < blocks.length; i++) { if (typeof blocks[i].originalY === "undefined") { blocks[i].originalY = blocks[i].y - patternYOffset; } blocks[i].y = blocks[i].originalY + patternYOffset; } } // If pattern has reached or passed the paddle, trigger game over if (blocks && blocks.length && paddle) { // Find the lowest block var maxBlockY = -Infinity; for (var i = 0; i < blocks.length; i++) { if (blocks[i].y > maxBlockY) maxBlockY = blocks[i].y; } // If any block is at or below the top of the paddle, game over if (maxBlockY + (blocks[0].blockSprite ? blocks[0].blockSprite.height / 2 : 0) >= paddle.y - paddle.height / 2) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } } spawnPaddle(); spawnBall(); } // Helper: win function winGame() { LK.effects.flashScreen(0x00ff00, 1000); // Defensive: ensure arrays exist before clearing if (!window.powerupTimers || !Array.isArray(window.powerupTimers)) window.powerupTimers = []; if (!window.activePowerups || !Array.isArray(window.activePowerups)) window.activePowerups = []; if (!window.powerupIcons || !Array.isArray(window.powerupIcons)) window.powerupIcons = []; // Clear all powerup timers and arrays for (var t = 0; t < window.powerupTimers.length; t++) { if (window.powerupTimers[t]) LK.clearTimeout(window.powerupTimers[t]); } window.powerupTimers = []; window.activePowerups = []; for (var i = 0; i < window.powerupIcons.length; i++) { if (window.powerupIcons[i] && typeof window.powerupIcons[i].destroy === "function") window.powerupIcons[i].destroy(); } window.powerupIcons = []; LK.showYouWin(); } // Initialize first level highScore = storage.highScore || 0; // Always start with score zero, persistently score = 0; storage.score = 0; highScoreTxt.setText('High: ' + highScore); window.extraBalls = []; // Clear all powerup timers and arrays at game start if (window.powerupTimers && Array.isArray(window.powerupTimers)) { for (var t = 0; t < window.powerupTimers.length; t++) { if (window.powerupTimers[t]) LK.clearTimeout(window.powerupTimers[t]); } window.powerupTimers = []; } if (window.activePowerups && Array.isArray(window.activePowerups)) window.activePowerups = []; if (window.powerupIcons && Array.isArray(window.powerupIcons)) { for (var i = 0; i < window.powerupIcons.length; i++) { if (window.powerupIcons[i] && typeof window.powerupIcons[i].destroy === "function") window.powerupIcons[i].destroy(); } window.powerupIcons = []; } spawnBlocks(); spawnPaddle(); spawnBall(); updateGUI(); // Touch controls game.down = function (x, y, obj) { // Launch ball if it's sticky and user taps in lower 40% of screen if (y > 2732 * 0.6 && ballStickyToPaddle) { // Launch at slight upward angle based on touch position var rel = (x - paddle.x) / (paddle.width / 2); var angle = -Math.PI / 3 + rel * (Math.PI / 3); // -60deg to 60deg ball.launch(angle); isBallLaunched = true; ballStickyToPaddle = false; } }; game.move = function (x, y, obj) { // Always move paddle horizontally with finger/mouse X, no need to hold down var targetX = x; // Use a weighted average for smoothing (0.7 current, 0.3 target) if (typeof paddle.x === "number") { // Clamp targetX to valid range before smoothing var minX = paddle.width / 2 + 0.01; var maxX = 2048 - paddle.width / 2 - 0.01; targetX = Math.max(minX, Math.min(maxX, targetX)); var smoothedX = paddle.x * 0.7 + targetX * 0.3; // Clamp again after smoothing to avoid edge sticking if (smoothedX <= minX + 2) smoothedX = minX; if (smoothedX >= maxX - 2) smoothedX = maxX; paddle.moveTo(smoothedX); } else { paddle.moveTo(targetX); } // If ball is sticky, move it with paddle if (ballStickyToPaddle) { ball.x = paddle.x; } }; game.up = function (x, y, obj) { // No need to set dragPaddle, paddle always follows move }; // Main update loop game.update = function () { // Ball update if (ball) ball.update(); // --- Ball-ball collision for all balls (main, extra, powerup balls) --- (function handleBallBallCollisions() { // Gather all balls: main, extra, powerup var allBalls = []; if (ball) allBalls.push(ball); if (window.extraBalls && Array.isArray(window.extraBalls)) { for (var i = 0; i < window.extraBalls.length; i++) { if (window.extraBalls[i]) allBalls.push(window.extraBalls[i]); } } if (powerupBalls && Array.isArray(powerupBalls)) { for (var i = 0; i < powerupBalls.length; i++) { if (powerupBalls[i]) allBalls.push(powerupBalls[i]); } } // For each unique pair, check collision and reflect if needed for (var i = 0; i < allBalls.length; i++) { var b1 = allBalls[i]; if (!b1) continue; if (typeof b1.lastX === "undefined") b1.lastX = b1.x; if (typeof b1.lastY === "undefined") b1.lastY = b1.y; for (var j = i + 1; j < allBalls.length; j++) { var b2 = allBalls[j]; if (!b2) continue; if (typeof b2.lastX === "undefined") b2.lastX = b2.x; if (typeof b2.lastY === "undefined") b2.lastY = b2.y; // Only check if both are moving and not sticky if (b1.sticky || b2.sticky) continue; // Use circle collision var dx = b1.x - b2.x; var dy = b1.y - b2.y; var dist = Math.sqrt(dx * dx + dy * dy); var minDist = (b1.radius || 0) + (b2.radius || 0); // Only trigger on new collision (not overlapping last frame) var lastDx = b1.lastX - b2.lastX; var lastDy = b1.lastY - b2.lastY; var lastDist = Math.sqrt(lastDx * lastDx + lastDy * lastDy); if (lastDist >= minDist && dist < minDist) { // Simple elastic collision: swap velocities along the collision axis var nx = dx / (dist || 1); var ny = dy / (dist || 1); // Project velocities onto the collision normal var p1 = b1.vx * nx + b1.vy * ny; var p2 = b2.vx * nx + b2.vy * ny; // Only swap if balls are moving toward each other if (p1 - p2 > 0) { // Swap the normal components, keep tangential var tx = -ny, ty = nx; var t1 = b1.vx * tx + b1.vy * ty; var t2 = b2.vx * tx + b2.vy * ty; // New velocities b1.vx = t1 * tx + p2 * nx; b1.vy = t1 * ty + p2 * ny; b2.vx = t2 * tx + p1 * nx; b2.vy = t2 * ty + p1 * ny; // Separate balls to avoid sticking var overlap = minDist - dist + 0.5; b1.x += nx * (overlap / 2); b1.y += ny * (overlap / 2); b2.x -= nx * (overlap / 2); b2.y -= ny * (overlap / 2); // Play hit sound LK.getSound('hit').play(); } } } // Update lastX/lastY for next frame b1.lastX = b1.x; b1.lastY = b1.y; } })(); // --- OPTIMIZATION: Only update extra balls if any exist --- if (!window.extraBalls || !Array.isArray(window.extraBalls)) window.extraBalls = []; if (window.extraBalls.length) { // Cache paddle hitbox if paddle exists and is not moving var cachedPaddleHitbox = typeof paddle !== "undefined" && paddle && typeof paddle.getHitbox === "function" ? paddle.getHitbox() : null; // --- OPTIMIZATION: For level 6+, only update every other extra ball per frame --- var ebStep = level >= 6 ? 2 : 1; for (var eb = window.extraBalls.length - 1; eb >= 0; eb -= ebStep) { var ebBall = window.extraBalls[eb]; if (ebBall && ebBall.update) ebBall.update(); // --- Track left/right wall bounces for extra balls --- if (typeof ebBall.leftWallHits === "undefined") ebBall.leftWallHits = 0; if (typeof ebBall.rightWallHits === "undefined") ebBall.rightWallHits = 0; if (typeof ebBall.wallBouncesNoBlock === "undefined") ebBall.wallBouncesNoBlock = 0; if (typeof ebBall.hasHitBlockSinceReset === "undefined") ebBall.hasHitBlockSinceReset = false; var ebHitWall = false; if (ebBall.x - ebBall.radius < 0) { ebBall.x = ebBall.radius; ebBall.vx = -ebBall.vx; LK.getSound('hit').play(); ebBall.leftWallHits++; ebHitWall = true; } if (ebBall.x + ebBall.radius > 2048) { ebBall.x = 2048 - ebBall.radius; ebBall.vx = -ebBall.vx; LK.getSound('hit').play(); ebBall.rightWallHits++; ebHitWall = true; } if (ebHitWall) { if (!ebBall.hasHitBlockSinceReset) { ebBall.wallBouncesNoBlock++; } } // If left or right wall hit more than 15 times without hitting any block, return ball to player if (ebBall.wallBouncesNoBlock >= 15) { ebBall.x = paddle.x; ebBall.y = paddle.y - paddle.height / 2 - ebBall.radius - 10; ebBall.vx = 0; ebBall.vy = 0; ebBall.sticky = true; ebBall.leftWallHits = 0; ebBall.rightWallHits = 0; ebBall.wallBouncesNoBlock = 0; ebBall.hasHitBlockSinceReset = false; } // Remove if off screen if (ebBall && ebBall.y - ebBall.radius > 2732) { ebBall.destroy(); window.extraBalls.splice(eb, 1); continue; } // Paddle collision for extra balls (skip if paddle not present) if (ebBall && cachedPaddleHitbox) { if (ebBall.y + ebBall.radius >= cachedPaddleHitbox.top && ebBall.y - ebBall.radius <= cachedPaddleHitbox.bottom && ebBall.x + ebBall.radius >= cachedPaddleHitbox.left && ebBall.x - ebBall.radius <= cachedPaddleHitbox.right && ebBall.vy > 0) { // Reflect ball, angle based on where it hit the paddle var rel = (ebBall.x - paddle.x) / (paddle.width / 2 * paddle.scaleX); var angle = -Math.PI / 3 + rel * (Math.PI / 3); // -60deg to 60deg var speed = Math.sqrt(ebBall.vx * ebBall.vx + ebBall.vy * ebBall.vy); ebBall.vx = Math.cos(angle) * speed; ebBall.vy = Math.sin(angle) * speed; ebBall.y = paddle.y - paddle.height / 2 - ebBall.radius - 2; if (ebBall.y - ebBall.radius < 0) { ebBall.y = ebBall.radius; if (ebBall.vy < 0) ebBall.vy = Math.abs(ebBall.vy); } LK.getSound('hit').play(); } } } } // --- OPTIMIZATION: Only update powerup balls if any exist --- if (!powerupBalls || !Array.isArray(powerupBalls)) powerupBalls = []; if (powerupBalls && powerupBalls.length) { var cachedPaddleHitboxPB = typeof paddle !== "undefined" && paddle && typeof paddle.getHitbox === "function" ? paddle.getHitbox() : null; // --- OPTIMIZATION: For level 6+, only update every other powerup ball per frame --- var pbStep = level >= 6 ? 2 : 1; for (var pbIdx = powerupBalls.length - 1; pbIdx >= 0; pbIdx -= pbStep) { var pbBall = powerupBalls[pbIdx]; if (pbBall && pbBall.update) pbBall.update(); // --- Track left/right wall bounces for powerup balls --- if (typeof pbBall.leftWallHits === "undefined") pbBall.leftWallHits = 0; if (typeof pbBall.rightWallHits === "undefined") pbBall.rightWallHits = 0; if (typeof pbBall.wallBouncesNoBlock === "undefined") pbBall.wallBouncesNoBlock = 0; if (typeof pbBall.hasHitBlockSinceReset === "undefined") pbBall.hasHitBlockSinceReset = false; var pbHitWall = false; if (pbBall.x - pbBall.radius < 0) { pbBall.x = pbBall.radius; pbBall.vx = -pbBall.vx; LK.getSound('hit').play(); pbBall.leftWallHits++; pbHitWall = true; } if (pbBall.x + pbBall.radius > 2048) { pbBall.x = 2048 - pbBall.radius; pbBall.vx = -pbBall.vx; LK.getSound('hit').play(); pbBall.rightWallHits++; pbHitWall = true; } if (pbHitWall) { if (!pbBall.hasHitBlockSinceReset) { pbBall.wallBouncesNoBlock++; } } // If left or right wall hit more than 15 times without hitting any block, return ball to player if (pbBall.wallBouncesNoBlock >= 15) { pbBall.x = paddle.x; pbBall.y = paddle.y - paddle.height / 2 - pbBall.radius - 10; pbBall.vx = 0; pbBall.vy = 0; pbBall.sticky = true; pbBall.leftWallHits = 0; pbBall.rightWallHits = 0; pbBall.wallBouncesNoBlock = 0; pbBall.hasHitBlockSinceReset = false; } // Remove if off screen if (pbBall && pbBall.y - pbBall.radius > 2732) { pbBall.destroy(); powerupBalls.splice(pbIdx, 1); continue; } // Paddle collision for powerup balls (skip if paddle not present) if (pbBall && cachedPaddleHitboxPB) { if (pbBall.y + pbBall.radius >= cachedPaddleHitboxPB.top && pbBall.y - pbBall.radius <= cachedPaddleHitboxPB.bottom && pbBall.x + pbBall.radius >= cachedPaddleHitboxPB.left && pbBall.x - pbBall.radius <= cachedPaddleHitboxPB.right && pbBall.vy > 0) { // Reflect ball, angle based on where it hit the paddle var rel = (pbBall.x - paddle.x) / (paddle.width / 2 * paddle.scaleX); var angle = -Math.PI / 3 + rel * (Math.PI / 3); // -60deg to 60deg var speed = Math.sqrt(pbBall.vx * pbBall.vx + pbBall.vy * pbBall.vy); pbBall.vx = Math.cos(angle) * speed; pbBall.vy = Math.sin(angle) * speed; pbBall.y = paddle.y - paddle.height / 2 - pbBall.radius - 2; if (pbBall.y - pbBall.radius < 0) { pbBall.y = pbBall.radius; if (pbBall.vy < 0) pbBall.vy = Math.abs(pbBall.vy); } LK.getSound('hit').play(); } } } } // PowerUp update // --- OPTIMIZATION: For level 6+, only update every other powerup per frame --- var powerupStep = level >= 6 ? 2 : 1; for (var i = powerups.length - 1; i >= 0; i -= powerupStep) { var p = powerups[i]; // OPTIMIZATION: Skip destroyed/invalid powerups immediately if (!p || typeof p.update !== "function" || p.destroyed) { powerups.splice(i, 1); continue; } // Defensive: Only call update if p is valid and not destroyed try { if (p && typeof p.update === "function" && !p.destroyed) { p.update(); } } catch (e) { // Remove problematic powerup to prevent further errors powerups.splice(i, 1); continue; } // If off screen if (p.y > 2732 + 100) { p.destroy(); powerups.splice(i, 1); continue; } // Paddle or ball collision var collected = false; // Use intersects for paddle collision to ensure all powerups are collected by paddle if (paddle && typeof p.intersects === "function" && p.intersects(paddle)) { collected = true; } // Check main ball if (!collected && ball && p.intersects(ball)) { collected = true; } // Check extra balls if (!collected && window.extraBalls && window.extraBalls.length) { for (var eb = 0; eb < window.extraBalls.length; eb++) { if (p.intersects(window.extraBalls[eb])) { collected = true; break; } } } // No level restriction: powerups are always collectible if (collected) { // --- Powerup balls update and collection --- } // --- OPTIMIZATION: Remove powerup ball update from this loop, handled above --- if (collected) { // Show floating text for powerup type var label = new Text2(p.type.toUpperCase(), { size: 70, fill: 0xFFE066, font: "Arial Black" }); label.anchor.set(0.5, 1); label.x = p.x; label.y = p.y - 60; game.addChild(label); tween(label, { y: label.y - 120, alpha: 0 }, { duration: 1200, easing: tween.easeOut, onFinish: function onFinish() { label.destroy(); } }); // Apply powerup LK.getSound('powerup').play(); // --- Powerup duration tracking and display --- if (!window.activePowerups || !Array.isArray(window.activePowerups)) window.activePowerups = []; if (!window.powerupTimers || !Array.isArray(window.powerupTimers)) window.powerupTimers = []; if (!window.powerupIcons || !Array.isArray(window.powerupIcons)) window.powerupIcons = []; // List of timed powerups and their durations (ms) var timedPowerupDurations = { expand: 6000, shrink: 6000, slow: 6000, fast: 6000, bigball: 6000, smallball: 6000, ballheavy: 6000, balllight: 6000, blockshrink: 6000, blockexpand: 6000, ghost: 6000, superball: 6000, paddleghost: 6000, // reverse removed blockinvisible: 6000, ballinvisible: 6000, magnet: 4000, ballcurve: 4000, ballzigzag: 4000 }; // If this is a timed powerup, add to activePowerups if (timedPowerupDurations[p.type]) { var duration = timedPowerupDurations[p.type]; var now = Date.now(); // Remove any previous of same type (refresh) for (var ap = window.activePowerups.length - 1; ap >= 0; ap--) { if (window.activePowerups[ap].type === p.type) { window.activePowerups.splice(ap, 1); if (window.powerupTimers[ap]) { LK.clearTimeout(window.powerupTimers[ap]); window.powerupTimers.splice(ap, 1); } if (window.powerupIcons[ap]) { window.powerupIcons[ap].destroy(); window.powerupIcons.splice(ap, 1); } } } window.activePowerups.push({ type: p.type, end: now + duration }); // Add icon to GUI var icon = new Text2(p.type[0].toUpperCase(), { size: 70, fill: 0xFFE066, font: "Arial Black" }); icon.anchor.set(0.5, 0.5); // Place icons in a row at top center, below score icon.x = 2048 / 2 - (window.activePowerups.length - 1) * 50 + (window.activePowerups.length - 1) * 100 / 2 + (window.activePowerups.length - 1) * 0; icon.y = 170; LK.gui.top.addChild(icon); window.powerupIcons.push(icon); // Timer to remove from activePowerups and GUI (function (type, icon) { var idx = window.activePowerups.length - 1; var timer = LK.setTimeout(function () { // Remove from arrays for (var ap = window.activePowerups.length - 1; ap >= 0; ap--) { if (window.activePowerups[ap].type === type) { window.activePowerups.splice(ap, 1); if (window.powerupTimers[ap]) window.powerupTimers.splice(ap, 1); if (window.powerupIcons[ap]) { window.powerupIcons[ap].destroy(); window.powerupIcons.splice(ap, 1); } } } }, duration); window.powerupTimers.push(timer); })(p.type, icon); } // 50 powerup effects, only working ones kept if (collected) { if (p.type === '+1ball') { // Add 1 extra ball if (!window.extraBalls) window.extraBalls = []; if (window.extraBalls.length >= 4) return; var newBall = new Ball(); newBall.x = ball.x; newBall.y = ball.y; // Give a random direction, but not too vertical var angle = -Math.PI / 4 + Math.random() * (Math.PI / 2); // -45 to +45 deg var speed = ball.speed || 22; newBall.vx = Math.cos(angle) * speed; newBall.vy = Math.sin(angle) * speed; newBall.sticky = false; game.addChild(newBall); window.extraBalls.push(newBall); } else if (p.type === '+2ball') { // Add 2 extra balls if (!window.extraBalls) window.extraBalls = []; var ballsToAdd = Math.min(2, 4 - window.extraBalls.length); for (var b = 0; b < ballsToAdd; b++) { var newBall = new Ball(); newBall.x = ball.x; newBall.y = ball.y; // Spread angles var angle = -Math.PI / 3 + b * (Math.PI / 3); // -60, 0 deg var speed = ball.speed || 22; newBall.vx = Math.cos(angle) * speed; newBall.vy = Math.sin(angle) * speed; newBall.sticky = false; game.addChild(newBall); window.extraBalls.push(newBall); } } else if (p.type === '+3ball') { // Add 3 extra balls if (!window.extraBalls) window.extraBalls = []; var ballsToAdd = Math.min(3, 4 - window.extraBalls.length); for (var b = 0; b < ballsToAdd; b++) { var newBall = new Ball(); newBall.x = ball.x; newBall.y = ball.y; // Spread angles var angle = -Math.PI / 3 + b * (Math.PI / 3) / (ballsToAdd > 1 ? ballsToAdd - 1 : 1); // -60, 0, +60 deg var speed = ball.speed || 22; newBall.vx = Math.cos(angle) * speed; newBall.vy = Math.sin(angle) * speed; newBall.sticky = false; game.addChild(newBall); window.extraBalls.push(newBall); } } else if (p.type === 'expand') { tween(paddle, { scaleX: 1.5 }, { duration: 300, easing: tween.easeOut }); LK.setTimeout(function () { tween(paddle, { scaleX: 1 }, { duration: 300, easing: tween.easeIn }); }, 6000); } else if (p.type === 'shrink') { tween(paddle, { scaleX: 0.7 }, { duration: 300, easing: tween.easeOut }); LK.setTimeout(function () { tween(paddle, { scaleX: 1 }, { duration: 300, easing: tween.easeIn }); }, 6000); } else if (p.type === 'life') { lives = Math.min(5, lives + 1); updateGUI(); } else if (p.type === 'sticky') { ball.sticky = true; ballStickyToPaddle = true; ball.vx = 0; ball.vy = 0; ball.x = paddle.x; ball.y = paddle.y - paddle.height / 2 - ball.radius - 10; } else if (p.type === 'multiball') { // Add a second ball that is a true main ball (not extra) if (!window.extraBalls) window.extraBalls = []; // Prevent more than 5 balls at once if (window.extraBalls.length >= 4) return; // --- Ava: Prevent multiball text from appearing too frequently --- if (typeof game.lastMultiballTick === "undefined") game.lastMultiballTick = 0; var multiballCooldown = 90; // 1.5 seconds at 60fps if (LK.ticks - game.lastMultiballTick < multiballCooldown) return; game.lastMultiballTick = LK.ticks; // Only add if not already present at this position (avoid duplicate bug) var alreadyExists = false; for (var i = 0; i < window.extraBalls.length; i++) { var eb = window.extraBalls[i]; if (eb && Math.abs(eb.x - ball.x) < 2 && Math.abs(eb.y - ball.y) < 2) { alreadyExists = true; break; } } if (!alreadyExists) { var newBall = new Ball(); newBall.x = ball.x; newBall.y = ball.y; newBall.vx = -ball.vx; newBall.vy = ball.vy; newBall.sticky = false; game.addChild(newBall); window.extraBalls.push(newBall); } } else if (p.type === 'ballsplit') { // Split ball into two, both are main balls var newBall = new Ball(); newBall.x = ball.x; newBall.y = ball.y; newBall.vx = -ball.vx; newBall.vy = ball.vy; newBall.sticky = false; game.addChild(newBall); if (!window.extraBalls) window.extraBalls = []; window.extraBalls.push(newBall); } else if (p.type === 'ballclone') { // Clone ball with same direction, as a main ball var newBall = new Ball(); newBall.x = ball.x; newBall.y = ball.y; newBall.vx = ball.vx; newBall.vy = ball.vy; newBall.sticky = false; game.addChild(newBall); if (!window.extraBalls) window.extraBalls = []; window.extraBalls.push(newBall); } else if (p.type === 'slow') { ball.speed = Math.max(10, ball.speed - 6); ball.vx *= 0.7; ball.vy *= 0.7; LK.setTimeout(function () { ball.speed = 22; }, 6000); } else if (p.type === 'fast') { ball.speed += 8; ball.vx *= 1.3; ball.vy *= 1.3; LK.setTimeout(function () { ball.speed = 22; }, 6000); } else if (p.type === 'bigball') { ball.scaleX = ball.scaleY = 1.7; LK.setTimeout(function () { ball.scaleX = ball.scaleY = 1; }, 6000); } else if (p.type === 'smallball') { ball.scaleX = ball.scaleY = 0.6; LK.setTimeout(function () { ball.scaleX = ball.scaleY = 1; }, 6000); } else if (p.type === 'scorex2') { if (!window.scorex2) window.scorex2 = 0; window.scorex2 += 1; LK.setTimeout(function () { window.scorex2 -= 1; }, 8000); } else if (p.type === 'scorex3') { if (!window.scorex3) window.scorex3 = 0; window.scorex3 += 1; LK.setTimeout(function () { window.scorex3 -= 1; }, 8000); } else if (p.type === 'blockbomb') { // Destroy a random block if (blocks.length > 0) { var idx = Math.floor(Math.random() * blocks.length); blocks[idx].breakBlock(); // Remove block from game scene to prevent ghost graphics if (blocks[idx].parent) blocks[idx].parent.removeChild(blocks[idx]); blocks.splice(idx, 1); } } else if (p.type === 'blockrowclear') { // Clear a random row if (blocks.length > 0) { var yRows = {}; for (var b = 0; b < blocks.length; b++) { var by = Math.round(blocks[b].y / 10) * 10; if (!yRows[by]) yRows[by] = []; yRows[by].push(b); } var rowKeys = Object.keys(yRows); var row = yRows[rowKeys[Math.floor(Math.random() * rowKeys.length)]]; for (var j = row.length - 1; j >= 0; j--) { blocks[row[j]].breakBlock(); // Remove block from game scene to prevent ghost graphics if (blocks[row[j]].parent) blocks[row[j]].parent.removeChild(blocks[row[j]]); blocks.splice(row[j], 1); } } } else if (p.type === 'blockcolclear') { // Clear a random column if (blocks.length > 0) { var xCols = {}; for (var b = 0; b < blocks.length; b++) { var bx = Math.round(blocks[b].x / 10) * 10; if (!xCols[bx]) xCols[bx] = []; xCols[bx].push(b); } var colKeys = Object.keys(xCols); var col = xCols[colKeys[Math.floor(Math.random() * colKeys.length)]]; for (var j = col.length - 1; j >= 0; j--) { blocks[col[j]].breakBlock(); // Remove block from game scene to prevent ghost graphics if (blocks[col[j]].parent) blocks[col[j]].parent.removeChild(blocks[col[j]]); blocks.splice(col[j], 1); } } } else if (p.type === 'ballsplit') { // Split ball into two var newBall = new Ball(); newBall.x = ball.x; newBall.y = ball.y; newBall.vx = -ball.vx; newBall.vy = ball.vy; newBall.sticky = false; game.addChild(newBall); if (!window.extraBalls) window.extraBalls = []; window.extraBalls.push(newBall); } else if (p.type === 'ballclone') { // Clone ball with same direction var newBall = new Ball(); newBall.x = ball.x; newBall.y = ball.y; newBall.vx = ball.vx; newBall.vy = ball.vy; newBall.sticky = false; game.addChild(newBall); if (!window.extraBalls) window.extraBalls = []; window.extraBalls.push(newBall); // Removed scorebonus and scorepenalty powerup effects } else if (p.type === 'blockheal') { // Heal all blocks by 1 for (var b = 0; b < blocks.length; b++) { blocks[b].hp = Math.min(blocks[b].maxHp, blocks[b].hp + 1); if (blocks[b].updateHpBar) blocks[b].updateHpBar(); } } else if (p.type === 'blockrandom') { // Randomize block colors var colors = ['red', 'green', 'blue', 'yellow']; for (var b = 0; b < blocks.length; b++) { blocks[b].setColor(colors[Math.floor(Math.random() * colors.length)]); } } else if (p.type === 'blockfreeze') { // Freeze blocks (no effect in this logic, but could be used for animation) } else if (p.type === 'blockmove') { // Move all blocks down for (var b = 0; b < blocks.length; b++) { blocks[b].y += 40; } } else if (p.type === 'blockshrink') { // Shrink all blocks for (var b = 0; b < blocks.length; b++) { blocks[b].scaleX = blocks[b].scaleY = 0.7; (function (block) { LK.setTimeout(function () { block.scaleX = block.scaleY = 1; }, 6000); })(blocks[b]); } } else if (p.type === 'blockexpand') { // Expand all blocks for (var b = 0; b < blocks.length; b++) { blocks[b].scaleX = blocks[b].scaleY = 1.3; (function (block) { LK.setTimeout(function () { block.scaleX = block.scaleY = 1; }, 6000); })(blocks[b]); } } else if (p.type === 'ballheavy') { ball.vy += 8; LK.setTimeout(function () { ball.vy -= 8; }, 6000); } else if (p.type === 'balllight') { ball.vy -= 8; LK.setTimeout(function () { ball.vy += 8; }, 6000); } else if (p.type === 'blockshield') { // Give all blocks +2 hp for (var b = 0; b < blocks.length; b++) { blocks[b].hp = Math.min(blocks[b].maxHp, blocks[b].hp + 2); if (blocks[b].updateHpBar) blocks[b].updateHpBar(); } } else if (p.type === 'blockswap') { // Swap two random blocks if (blocks.length > 1) { var i1 = Math.floor(Math.random() * blocks.length); var i2 = Math.floor(Math.random() * blocks.length); var tmpx = blocks[i1].x, tmpy = blocks[i1].y; blocks[i1].x = blocks[i2].x; blocks[i1].y = blocks[i2].y; blocks[i2].x = tmpx; blocks[i2].y = tmpy; } } else if (p.type === 'blockteleport') { // Teleport a random block to a random position if (blocks.length > 0) { var idx = Math.floor(Math.random() * blocks.length); blocks[idx].x = 400 + Math.random() * 1200; blocks[idx].y = 400 + Math.random() * 800; } } else if (p.type === 'ballteleport') { ball.x = 400 + Math.random() * 1200; ball.y = 400 + Math.random() * 800; } else if (p.type === 'blockregen') { // Regenerate a destroyed block at random if (blocks.length > 0) { var idx = Math.floor(Math.random() * blocks.length); var block = new Block(); block.setColor(blocks[idx].color); block.x = blocks[idx].x; block.y = blocks[idx].y; block.hp = block.maxHp = 2; block.setHp(2, 2); game.addChild(block); blocks.push(block); } } else if (p.type === 'blockexplode') { // Destroy 3 random blocks for (var n = 0; n < 3 && blocks.length > 0; n++) { var idx = Math.floor(Math.random() * blocks.length); blocks[idx].breakBlock(); // Remove block from game scene to prevent ghost graphics if (blocks[idx].parent) blocks[idx].parent.removeChild(blocks[idx]); blocks.splice(idx, 1); } } else if (p.type === 'magnet') { // Ball moves toward paddle for a while if (!window.magnetActive) { window.magnetActive = true; var magnetInterval = LK.setInterval(function () { if (ball && paddle) { var dx = paddle.x - ball.x; ball.vx += dx * 0.01; } }, 60); LK.setTimeout(function () { LK.clearInterval(magnetInterval); window.magnetActive = false; }, 4000); } } else if (p.type === 'laser') { // Laser powerup: show a red laser beam from paddle for a short time if (!paddle.laserBeam) { // Create a red rectangle as the laser beam var laserWidth = paddle.width * (paddle.scaleX || 1) * 0.25; var laserHeight = paddle.y - paddle.height / 2; // from paddle to very top var laser = LK.getAsset('block_red', { anchorX: 0.5, anchorY: 1, width: laserWidth, height: laserHeight, x: paddle.x, y: paddle.y - paddle.height / 2, alpha: 0.7 }); laser.zIndex = 1000; paddle.laserBeam = laser; game.addChild(laser); // Animate laser alpha in and out tween(laser, { alpha: 0.9 }, { duration: 120, easing: tween.easeIn, onFinish: function onFinish() { tween(laser, { alpha: 0.0 }, { duration: 400, delay: 400, easing: tween.easeOut, onFinish: function onFinish() { if (laser && laser.parent) laser.parent.removeChild(laser); paddle.laserBeam = null; } }); } }); // Destroy all blocks in the laser's vertical path, with explosion effect for (var i = blocks.length - 1; i >= 0; i--) { var block = blocks[i]; if (block && block.x >= paddle.x - laserWidth / 2 && block.x <= paddle.x + laserWidth / 2 && block.y < paddle.y - paddle.height / 2 // only above paddle ) { // Instantly destroy block (no animation) block.hp = 0; if (block.updateHpBar) block.updateHpBar(); block.alpha = 0; block.scaleX = block.scaleY = 2.2; block.destroy(); if (block.parent) block.parent.removeChild(block); blocks.splice(i, 1); score += 10; updateGUI(); LK.getSound('break').play(); } } } // Removed 'reverse' (ters kontrol) powerup effect } else if (p.type === 'superball') { // Ball passes through blocks for a while ball.superball = true; LK.setTimeout(function () { ball.superball = false; }, 6000); } else if (p.type === 'fireball') { // Fireball: Ball burns through blocks for a while, destroys all blocks it touches, and shows fire effect ball.fireball = true; // Add a fire visual effect to the ball if (!ball.fireEffect) { ball.fireEffect = ball.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, width: ball.width * 1.5, height: ball.height * 1.5, tint: 0xff6600, alpha: 0.5 }); ball.fireEffect.zIndex = 99; } // Animate fire effect (pulsing) if (!ball.fireTween) { ball.fireTween = tween(ball.fireEffect, { alpha: 0.2 }, { duration: 300, yoyo: true, repeat: 10, onFinish: function onFinish() { if (ball.fireEffect) ball.fireEffect.alpha = 0.5; } }); } LK.setTimeout(function () { ball.fireball = false; if (ball.fireEffect) { ball.removeChild(ball.fireEffect); ball.fireEffect = null; } if (ball.fireTween) { ball.fireTween.stop(); ball.fireTween = null; } }, 6000); } else if (p.type === 'ballcurve') { // Ball curves for a while if (!window.curveActive) { window.curveActive = true; var curveInterval = LK.setInterval(function () { if (ball) ball.vx += Math.sin(LK.ticks / 10) * 0.5; }, 60); LK.setTimeout(function () { LK.clearInterval(curveInterval); window.curveActive = false; }, 4000); } } else if (p.type === 'ballzigzag') { // Ball zigzags for a while if (!window.zigzagActive) { window.zigzagActive = true; var zigzagInterval = LK.setInterval(function () { if (ball) ball.vx += LK.ticks % 2 === 0 ? 2 : -2; }, 60); LK.setTimeout(function () { LK.clearInterval(zigzagInterval); window.zigzagActive = false; }, 4000); } } else if (p.type === 'ballrandom') { // Ball gets random velocity ball.vx = (Math.random() - 0.5) * 30; ball.vy = (Math.random() - 0.5) * 30; } else if (p.type === 'blockinvisible') { // All blocks invisible for a while for (var b = 0; b < blocks.length; b++) { blocks[b].alpha = 0.1; (function (block) { LK.setTimeout(function () { block.alpha = 1; }, 6000); })(blocks[b]); } } else if (p.type === 'ballinvisible') { // All balls (main + extra) invisible for a while if (ball) { ball.alpha = 0.1; LK.setTimeout(function () { if (ball) ball.alpha = 1; }, 6000); } if (window.extraBalls && window.extraBalls.length) { for (var eb = 0; eb < window.extraBalls.length; eb++) { if (window.extraBalls[eb]) { window.extraBalls[eb].alpha = 0.1; (function (ebBall) { LK.setTimeout(function () { if (ebBall) ebBall.alpha = 1; }, 6000); })(window.extraBalls[eb]); } } } if (powerupBalls && powerupBalls.length) { for (var pb = 0; pb < powerupBalls.length; pb++) { if (powerupBalls[pb]) { powerupBalls[pb].alpha = 0.1; (function (pbBall) { LK.setTimeout(function () { if (pbBall) pbBall.alpha = 1; }, 6000); })(powerupBalls[pb]); } } } } else if (p.type === 'iceball') { if (ball) { ball.vx *= 0.7; ball.vy *= 0.7; ball.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, width: ball.width * 1.2, height: ball.height * 1.2, tint: 0x00e0ff, alpha: 0.3 }); } LK.setTimeout(function () { if (ball) { ball.vx *= 1.43; ball.vy *= 1.43; } }, 6000); } else if (p.type === 'portal') { if (blocks.length > 1) { var b1 = Math.floor(Math.random() * blocks.length); var b2 = Math.floor(Math.random() * blocks.length); if (b1 !== b2) { var tmpx = blocks[b1].x, tmpy = blocks[b1].y; blocks[b1].x = blocks[b2].x; blocks[b1].y = blocks[b2].y; blocks[b2].x = tmpx; blocks[b2].y = tmpy; } } } else if (p.type === 'gravity') { if (ball) { var origVy = ball.vy; ball.vy += 20; LK.setTimeout(function () { if (ball) ball.vy = origVy; }, 6000); } } else if (p.type === 'mirror') { if (ball) { ball.vx = -ball.vx; } } else if (p.type === 'shrinkall') { for (var b = 0; b < blocks.length; b++) { blocks[b].scaleX = blocks[b].scaleY = 0.5; (function (block) { LK.setTimeout(function () { block.scaleX = block.scaleY = 1; }, 6000); })(blocks[b]); } } else if (p.type === 'expandall') { for (var b = 0; b < blocks.length; b++) { blocks[b].scaleX = blocks[b].scaleY = 1.7; (function (block) { LK.setTimeout(function () { block.scaleX = block.scaleY = 1; }, 6000); })(blocks[b]); } } else if (p.type === 'swapall') { if (blocks.length > 1) { for (var b = 0; b < blocks.length; b += 2) { if (b + 1 < blocks.length) { var tmpx = blocks[b].x, tmpy = blocks[b].y; blocks[b].x = blocks[b + 1].x; blocks[b].y = blocks[b + 1].y; blocks[b + 1].x = tmpx; blocks[b + 1].y = tmpy; } } } } else if (p.type === 'rainbow') { var rainbowColors = [0xff0000, 0xff7f00, 0xffff00, 0x00ff00, 0x0000ff, 0x4b0082, 0x9400d3]; for (var b = 0; b < blocks.length; b++) { blocks[b].setColor(['red', 'green', 'blue', 'yellow'][Math.floor(Math.random() * 4)]); blocks[b].blockSprite.tint = rainbowColors[Math.floor(Math.random() * rainbowColors.length)]; (function (block) { LK.setTimeout(function () { block.blockSprite.tint = 0xffffff; }, 6000); })(blocks[b]); } } else if (p.type === 'cloneblock') { if (blocks.length > 0) { var idx = Math.floor(Math.random() * blocks.length); var orig = blocks[idx]; var clone = new Block(); clone.setColor(orig.color); clone.x = orig.x + 60 + Math.random() * 120; clone.y = orig.y + 60 + Math.random() * 120; clone.hp = orig.hp; clone.maxHp = orig.maxHp; clone.setHp(clone.hp, clone.maxHp); game.addChild(clone); blocks.push(clone); } } else if (p.type === 'blockfall') { for (var b = 0; b < blocks.length; b++) { blocks[b].y += 120; } } else if (p.type === 'blockrise') { for (var b = 0; b < blocks.length; b++) { blocks[b].y -= 120; } } p.destroy(); powerups.splice(i, 1); } } } // Ball physics if (ball && _typeof(ball) === "object" && !ball.sticky) { // --- Wall bounce tracking --- if (typeof ball.leftWallHits === "undefined") ball.leftWallHits = 0; if (typeof ball.rightWallHits === "undefined") ball.rightWallHits = 0; // --- Track if ball has hit a block since last paddle reset --- if (typeof ball.hasHitBlockSinceReset === "undefined") ball.hasHitBlockSinceReset = false; // --- Track consecutive left/right wall bounces without block hit --- if (typeof ball.wallBouncesNoBlock === "undefined") ball.wallBouncesNoBlock = 0; // Wall collisions var hitWall = false; if (ball.x - ball.radius < 0) { ball.x = ball.radius; ball.vx = -ball.vx; LK.getSound('hit').play(); ball.leftWallHits++; hitWall = true; } if (ball.x + ball.radius > 2048) { ball.x = 2048 - ball.radius; ball.vx = -ball.vx; LK.getSound('hit').play(); ball.rightWallHits++; hitWall = true; } if (hitWall) { // Only count if no block hit since last paddle reset if (!ball.hasHitBlockSinceReset) { ball.wallBouncesNoBlock++; } } if (ball.y - ball.radius < 0) { ball.y = ball.radius; ball.vy = -ball.vy; LK.getSound('hit').play(); } // If left or right wall hit 15 or more times without hitting any block, make ALL balls accelerate upward even faster if (ball.wallBouncesNoBlock >= 15) { var accelerateUpwardFaster = function accelerateUpwardFaster(b) { // Only accelerate if not already moving extremely fast upward if (b.vy > -Math.abs(b.speed ? b.speed * 20 : 440)) { b.vx = 0; b.vy = -Math.abs(b.speed ? b.speed * 20 : 440); // Use 20x speed or 440 as fallback b.sticky = false; } if (typeof b.leftWallHits !== "undefined") b.leftWallHits = 0; if (typeof b.rightWallHits !== "undefined") b.rightWallHits = 0; if (typeof b.wallBouncesNoBlock !== "undefined") b.wallBouncesNoBlock = 0; if (typeof b.hasHitBlockSinceReset !== "undefined") b.hasHitBlockSinceReset = false; }; // Main ball if (ball) accelerateUpwardFaster(ball); // Extra balls if (window.extraBalls && window.extraBalls.length) { for (var eb = 0; eb < window.extraBalls.length; eb++) { if (window.extraBalls[eb]) accelerateUpwardFaster(window.extraBalls[eb]); } } // Powerup balls if (powerupBalls && powerupBalls.length) { for (var pb = 0; pb < powerupBalls.length; pb++) { if (powerupBalls[pb]) accelerateUpwardFaster(powerupBalls[pb]); } } } else if (ball.wallBouncesNoBlock >= 10) { // Helper to shoot all balls upward extremely fast var shootUpward = function shootUpward(b) { // Only accelerate if not already moving extremely fast upward if (b.vy > -Math.abs(b.speed ? b.speed * 50 : 1320)) { b.vx = 0; b.vy = -Math.abs(b.speed ? b.speed * 50 : 1320); // Use 50x speed or 1320 as fallback b.sticky = false; } if (typeof b.leftWallHits !== "undefined") b.leftWallHits = 0; if (typeof b.rightWallHits !== "undefined") b.rightWallHits = 0; if (typeof b.wallBouncesNoBlock !== "undefined") b.wallBouncesNoBlock = 0; if (typeof b.hasHitBlockSinceReset !== "undefined") b.hasHitBlockSinceReset = false; }; // Main ball if (ball) shootUpward(ball); // Extra balls if (window.extraBalls && window.extraBalls.length) { for (var eb = 0; eb < window.extraBalls.length; eb++) { if (window.extraBalls[eb]) shootUpward(window.extraBalls[eb]); } } // Powerup balls if (powerupBalls && powerupBalls.length) { for (var pb = 0; pb < powerupBalls.length; pb++) { if (powerupBalls[pb]) shootUpward(powerupBalls[pb]); } } } // Clamp paddle position defensively every frame to prevent edge sticking if (paddle) { var minX = paddle.width / 2 + 0.01; var maxX = 2048 - paddle.width / 2 - 0.01; if (paddle.x < minX) paddle.x = minX; if (paddle.x > maxX) paddle.x = maxX; } // Paddle collision if (paddle && typeof paddle.getHitbox === "function") { var hitbox = paddle.getHitbox(); if (ball.y + ball.radius >= hitbox.top && ball.y - ball.radius <= hitbox.bottom && ball.x + ball.radius >= hitbox.left && ball.x - ball.radius <= hitbox.right && ball.vy > 0) { // Reflect ball, angle based on where it hit the paddle var rel = (ball.x - paddle.x) / (paddle.width / 2 * paddle.scaleX); var angle = -Math.PI / 3 + rel * (Math.PI / 3); // -60deg to 60deg var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); ball.vx = Math.cos(angle) * speed; ball.vy = Math.sin(angle) * speed; // Always place ball visually above paddle after collision ball.y = paddle.y - paddle.height / 2 - ball.radius - 2; if (ball.y + ball.radius > hitbox.top) { ball.y = paddle.y - paddle.height / 2 - ball.radius - 2; } LK.getSound('hit').play(); } } else { if (ball.y + ball.radius >= paddle.y - paddle.height / 2 && ball.y + ball.radius <= paddle.y + paddle.height / 2 && ball.x >= paddle.x - paddle.width / 2 * paddle.scaleX && ball.x <= paddle.x + paddle.width / 2 * paddle.scaleX && ball.vy > 0) { // Reflect ball, angle based on where it hit the paddle var rel = (ball.x - paddle.x) / (paddle.width / 2 * paddle.scaleX); var angle = -Math.PI / 3 + rel * (Math.PI / 3); // -60deg to 60deg var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); ball.vx = Math.cos(angle) * speed; ball.vy = Math.sin(angle) * speed; ball.y = paddle.y - paddle.height / 2 - ball.radius - 2; LK.getSound('hit').play(); } } // Bottom out if (ball.y - ball.radius > 2732) { // If there is at least one powerup ball on screen, continue with it as the main ball if (powerupBalls && powerupBalls.length > 0) { // Remove the main ball, but do not call loseLife ball.destroy(); ball = null; // Promote the first powerup ball to be the new main ball var promoted = powerupBalls.shift(); // Remove from game and re-add as main ball for correct update/collision if (promoted && promoted.parent) promoted.parent.removeChild(promoted); // Create a new Ball instance at the promoted ball's position and velocity ball = new Ball(); ball.x = promoted.x; ball.y = promoted.y; ball.vx = promoted.vx; ball.vy = promoted.vy; ball.sticky = false; game.addChild(ball); // Remove the promoted powerup ball promoted.destroy(); // Do not return, continue game loop } else { loseLife(); return; } } } // Block collisions if (!blocks || !Array.isArray(blocks)) blocks = []; // --- OPTIMIZATION: For level 6+, only check a subset of blocks per frame to reduce lag --- var blockCheckStep = level >= 6 ? 2 : 1; for (var i = blocks.length - 1; i >= 0; i -= blockCheckStep) { var block = blocks[i]; // OPTIMIZATION: Skip destroyed or off-screen blocks if (!block || block.destroyed || block.y < -200 || block.y > 2732 + 200) continue; var hit = false; // Check main ball if (ball && block && ball.intersects(block)) { // Fireball: instantly destroy block and keep moving if (ball.fireball) { // Burn block: show a flash and destroy instantly (no animation) if (block && block.blockSprite) { LK.effects.flashObject(block, 0xff6600, 80); } block.hp = 0; if (block.updateHpBar) block.updateHpBar(); block.alpha = 0; block.scaleX = block.scaleY = 1.5; block.destroy(); if (block.parent) block.parent.removeChild(block); blocks.splice(i, 1); combo++; startCombo(); score += 10 * combo; updateGUI(); LK.getSound('break').play(); // Power-up chance (increased drop rate) var powerupDropRate = 0.22 + Math.min(0.01 * level, 0.18); if (Math.random() < powerupDropRate) { spawnPowerUp(block.x, block.y); } hit = true; // Do not reflect ball, let it keep moving } else { // Reflect ball var overlapX = Math.abs(ball.x - block.x) - (block.blockSprite.width / 2 + ball.radius); var overlapY = Math.abs(ball.y - block.y) - (block.blockSprite.height / 2 + ball.radius); if (overlapX > overlapY) { ball.vx = -ball.vx; } else { ball.vy = -ball.vy; } hit = true; } } // Check extra balls if (!hit && window.extraBalls && window.extraBalls.length) { for (var eb = 0; eb < window.extraBalls.length; eb++) { var ebBall = window.extraBalls[eb]; if (ebBall && block && ebBall.intersects(block)) { // Reflect extra ball var overlapX = Math.abs(ebBall.x - block.x) - (block.blockSprite.width / 2 + ebBall.radius); var overlapY = Math.abs(ebBall.y - block.y) - (block.blockSprite.height / 2 + ebBall.radius); if (overlapX > overlapY) { ebBall.vx = -ebBall.vx; } else { ebBall.vy = -ebBall.vy; } hit = true; break; } } } if (hit) { // Mark that ball has hit a block, so wall bounce counter pauses until next reset if (ball && typeof ball.hasHitBlockSinceReset !== "undefined") { ball.hasHitBlockSinceReset = true; ball.wallBouncesNoBlock = 0; } if (window.extraBalls && window.extraBalls.length) { for (var eb = 0; eb < window.extraBalls.length; eb++) { var ebBall = window.extraBalls[eb]; if (ebBall && typeof ebBall.hasHitBlockSinceReset !== "undefined") { ebBall.hasHitBlockSinceReset = true; ebBall.wallBouncesNoBlock = 0; } } } if (powerupBalls && powerupBalls.length) { for (var pb = 0; pb < powerupBalls.length; pb++) { var pbBall = powerupBalls[pb]; if (pbBall && typeof pbBall.hasHitBlockSinceReset !== "undefined") { pbBall.hasHitBlockSinceReset = true; pbBall.wallBouncesNoBlock = 0; } } } // Block hit block.hp--; if (block.updateHpBar) block.updateHpBar(); combo++; startCombo(); score += 10 * combo; updateGUI(); LK.getSound('break').play(); // Power-up chance (increased drop rate) var powerupDropRate = 0.22 + Math.min(0.01 * level, 0.18); // increases with level, max ~0.4 if (Math.random() < powerupDropRate) { spawnPowerUp(block.x, block.y); } if (block.hp <= 0) { block.breakBlock(); // Remove block from game scene to prevent ghost graphics if (block.parent) block.parent.removeChild(block); blocks.splice(i, 1); } break; // Only one block per frame } } // Combo reset if no block hit for a while if (combo > 0 && !comboTimer) { resetCombo(); } // Level clear if (!levelCleared && blocks.length === 0) { levelCleared = true; // Remove all balls except the main ball if (window.extraBalls && Array.isArray(window.extraBalls)) { for (var i = window.extraBalls.length - 1; i >= 0; i--) { if (window.extraBalls[i]) window.extraBalls[i].destroy(); window.extraBalls.splice(i, 1); } } if (powerupBalls && Array.isArray(powerupBalls)) { for (var i = powerupBalls.length - 1; i >= 0; i--) { if (powerupBalls[i]) powerupBalls[i].destroy(); powerupBalls.splice(i, 1); } } if (comboTimer) { LK.clearTimeout(comboTimer); comboTimer = null; } LK.setTimeout(function () { nextLevel(); }, 1200); } // --- Random powerup spawn logic --- if (typeof game.lastPowerupSpawnTick === "undefined") { game.lastPowerupSpawnTick = LK.ticks; } if (typeof game.powerupSpawnInterval === "undefined") { // Try to spawn a powerup every 6-12 seconds (randomized), but decrease interval as level increases (min 120) game.powerupSpawnInterval = Math.max(120, 360 + Math.floor(Math.random() * 360) - level * 12); } if (typeof game.lastPowerupBallTick === "undefined") { game.lastPowerupBallTick = LK.ticks; } if (typeof game.powerupBallInterval === "undefined") { // Try to spawn a powerup ball every 8-16 seconds (randomized), but decrease interval as level increases (min 180) game.powerupBallInterval = Math.max(180, 480 + Math.floor(Math.random() * 480) - level * 16); } if (LK.ticks - game.lastPowerupSpawnTick > game.powerupSpawnInterval) { // Only spawn if there are less than 3 powerups on screen if (powerups.length < 3 && blocks.length > 0) { spawnPowerUp(); // No x/y: will spawn at random position } game.lastPowerupSpawnTick = LK.ticks; game.powerupSpawnInterval = 360 + Math.floor(Math.random() * 360); } // --- Random powerup ball spawn logic --- if (LK.ticks - game.lastPowerupBallTick > game.powerupBallInterval) { // Only spawn if there are less than 3 powerup balls on screen if (powerupBalls.length < 3) { // Pick a random type var types = [PowerupBall1, PowerupBall2, PowerupBall3]; var idx = Math.floor(Math.random() * types.length); var PowerupBallClass = types[idx]; var pb = new PowerupBallClass(); // Spawn at random X, top of screen pb.x = 100 + Math.random() * (2048 - 200); pb.y = -pb.radius - 10; game.addChild(pb); powerupBalls.push(pb); } game.lastPowerupBallTick = LK.ticks; game.powerupBallInterval = 480 + Math.floor(Math.random() * 480); } // Update powerup icon timers and show countdown for active powerups if (!window.activePowerups || !Array.isArray(window.activePowerups)) window.activePowerups = []; if (!window.powerupIcons || !Array.isArray(window.powerupIcons)) window.powerupIcons = []; if (window.activePowerups.length && window.powerupIcons.length) { var now = Date.now(); for (var i = 0; i < window.activePowerups.length; i++) { var ap = window.activePowerups[i]; var icon = window.powerupIcons[i]; if (ap && icon) { var remain = Math.max(0, Math.ceil((ap.end - now) / 1000)); // Show timer only if more than 0 seconds left, otherwise just the icon if (remain > 0) { icon.setText(ap.type[0].toUpperCase() + "\n" + remain); } else { icon.setText(ap.type[0].toUpperCase()); } // Reposition icons in a row icon.x = 2048 / 2 - (window.activePowerups.length - 1) * 50 + i * 100 / 2 + i * 0; icon.y = 170; } } } }; // Music (optional, not required by MVP, so not included) /* End of gamecode.js */
===================================================================
--- original.js
+++ change.js
@@ -694,14 +694,14 @@
/****
* Game Code
****/
-// Sounds
-// Power-up
-// Block colors
-// Paddle
-// Ball
// Game variables
+// Ball
+// Paddle
+// Block colors
+// Power-up
+// Sounds
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;