User prompt
oyunu genel bir optimize et cฬงok kasฤฑyor
User prompt
Please fix the bug: 'ReferenceError: Collider is not defined' in or related to this line: 'if (ball && block && Collider.intersect(ball, block)) {' Line Number: 1796
User prompt
Please fix the bug: 'Ball is not defined' in or related to this line: 'ball = new Ball();' Line Number: 502
User prompt
Please fix the bug: 'Paddle is not defined' in or related to this line: 'paddle = new Paddle();' Line Number: 458
User prompt
Please fix the bug: 'Paddle is not defined' in or related to this line: 'paddle = new Paddle();' Line Number: 458
User prompt
Please fix the bug: 'Paddle is not defined' in or related to this line: 'paddle = new Paddle();' Line Number: 458
User prompt
Please fix the bug: 'Paddle is not defined' in or related to this line: 'paddle = new Paddle();' Line Number: 458
User prompt
Please fix the bug: 'Paddle is not defined' in or related to this line: 'paddle = new Paddle();' Line Number: 458
User prompt
Please fix the bug: 'Paddle is not defined' in or related to this line: 'paddle = new Paddle();' Line Number: 458
User prompt
Please fix the bug: 'Block is not defined' in or related to this line: 'var block = new Block();' Line Number: 303
User prompt
2d collider ayarla buฬtuฬn oyunun profesyonel ol
User prompt
buฬtuฬn tugฬlalarฤฑn hitboxlarฤฑnฤฑ ayarla buฬtuฬn levellerde farklฤฑ sฬงekillerde oldugฬu icฬงin top algฤฑlamฤฑyor
User prompt
fire ball gelme sฤฑklฤฑgฬฤฑnฤฑ arttฤฑr
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'update')' in or related to this line: 'p.update();' Line Number: 1244
User prompt
poweruplar yazฤฑlanฤฑ yapmฤฑyor ne yazฤฑyorsa onu yapmalฤฑ mesala fireball yazฤฑyor atesฬง cฬงฤฑkmalฤฑ ve yakmalฤฑ tugฬlayฤฑ
User prompt
15 kere sagฬ ve sol duvara veya sol ve sagฬ duvara cฬงarpan buฬtuฬn toplar yukarฤฑ duvarฤฑ cฬงarpsฤฑn
User prompt
buฬtuฬn toplar egฬer 15 kere sagฬ ve sol duvara cฬงarptฤฑysa yukarฤฑdaki duvara dogฬru ivmelenme yasฬงasฤฑn
User prompt
havadan rastgele powerup duฬsฬงmesin sadece tugฬlalardan duฬsฬงsuฬn onuda arttฤฑr
User prompt
isฬงe yaramayan can sฤฑkฤฑcฤฑ poweruplarฤฑ kaldฤฑr daha egฬlenceli daha ilgi cฬงekici poweruplar ekle en az 50 tane ve buฬtuฬn hatalarฤฑnฤฑ fixle
User prompt
multiball powerup bug oluyor duฬzelt
User prompt
buฬtuฬn poweruplar paddle neresine degฬerse deysin hemen kullanฤฑlsฤฑn
User prompt
buฬtuฬn powerup duฬzenle bazฤฑlarฤฑ paddelฤฑn icฬงinden gecฬงiyor onlarฤฑ duฬzelt paddle icฬงinden gecฬงen powerup oฬzelligฬi olmasฤฑn
User prompt
her oyun basฬงladฤฑgฬฤฑnda score sฤฑfฤฑrlansฤฑn bu kalฤฑcฤฑ olsun
User prompt
buฬtuฬn objeler paddle neresine cฬงarparsa cฬงarpsฤฑn yukarฤฑ dogฬru cฬงฤฑksฤฑn
User prompt
daha fazla tugฬla ekle sadece yan olmasฤฑn dik de olsun cฬงarpraz da yuvarlak da buฬtuฬn sฬงekillerde olsun level arttฤฑkcฬงa dahada zorlasฬงtฤฑ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;
// 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;
}
}
};
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 (leave 40px margin)
var minX = self.width / 2 + 40;
var maxX = 2048 - self.width / 2 - 40;
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") {
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") {
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") {
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
var paddle,
ball,
blocks = [],
powerups = [],
powerupBalls = []; // holds PowerupBall1/2/3 instances
var lives = 3;
var level = 1;
var isBallLaunched = false;
var dragPaddle = false;
// 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);
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
var blockW = 200,
blockH = 80;
var marginX = 40,
marginY = 40;
var colors = ['red', 'green', 'blue', 'yellow'];
// --- New: More block shapes and orientations ---
function getShapePattern(level, rows, cols) {
// Returns a 2D array of objects: {type: 'normal'|'vertical'|'diagonal'|'round', present: true/false}
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
};
}
}
// Add vertical blocks (tall) for level 3+
if (level >= 3) {
for (var c = 0; c < cols; c += 3) {
for (var r = 0; r < rows; r++) {
if ((r + c) % 2 === 0) arr[r][c] = {
type: 'vertical',
present: true
};
}
}
}
// Add diagonal blocks for level 5+
if (level >= 5) {
for (var d = 0; d < Math.min(rows, cols); d++) {
arr[d][d] = {
type: 'diagonal',
present: true
};
arr[d][cols - d - 1] = {
type: 'diagonal',
present: true
};
}
}
// Add round blocks for level 7+
if (level >= 7) {
var centerR = Math.floor(rows / 2),
centerC = Math.floor(cols / 2);
for (var r = 0; r < rows; r++) {
for (var c = 0; c < cols; c++) {
var dist = Math.sqrt(Math.pow(r - centerR, 2) + Math.pow(c - centerC, 2));
if (dist < Math.min(rows, cols) / 3 && (r + c) % 2 === 0) {
arr[r][c] = {
type: 'round',
present: true
};
}
}
}
}
// Add more diagonal/vertical/round as level increases
if (level >= 12) {
for (var r = 0; r < rows; r++) {
for (var c = 0; c < cols; c++) {
if ((r + c + level) % 6 === 0) arr[r][c] = {
type: 'diagonal',
present: true
};
if ((r * c + level) % 8 === 0) arr[r][c] = {
type: 'vertical',
present: true
};
}
}
}
// Add more round blocks for level 15+
if (level >= 15) {
for (var r = 0; r < rows; r++) {
for (var c = 0; c < cols; c++) {
if ((r - c + level) % 7 === 0) arr[r][c] = {
type: 'round',
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
var rows = Math.min(3 + Math.floor(level / 2), 10);
var 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
}
var totalW = cols * blockW + (cols - 1) * marginX;
var startX = (2048 - totalW) / 2 + blockW / 2;
var startY = 300;
// --- New: get shape pattern for this level ---
var shapePattern = getShapePattern(level, rows, cols);
for (var r = 0; r < rows; r++) {
for (var c = 0; c < cols; c++) {
var placeBlock = true;
var blockType = 'normal';
// Animal pattern logic
if (animalPattern) {
placeBlock = animalPattern[r][c] === 1;
} else {
// Pattern logic
if (patternType === 1) {
// checker
if ((r + c) % 2 !== 0) placeBlock = false;
} else if (patternType === 2) {
// pyramid
var mid = Math.floor(cols / 2);
if (c < mid - r || c > mid + r) placeBlock = false;
} else if (patternType === 3) {
// random gaps
if (Math.abs((r * cols + c + level) % 7) === 0) placeBlock = false;
} else if (patternType === 4) {
// zigzag
if (r % 2 === 0 && c % 3 === 0 || r % 2 === 1 && c % 3 === 2) placeBlock = false;
}
// patternType 0: full grid
}
// --- New: shape pattern logic ---
if (shapePattern && shapePattern[r] && shapePattern[r][c]) {
placeBlock = shapePattern[r][c].present;
blockType = shapePattern[r][c].type;
}
if (placeBlock) {
var block = new Block();
var color = colors[(r + c + level) % colors.length];
block.setColor(color);
// --- New: block shape/orientation ---
block.blockType = blockType; // Set type for hitbox logic
if (blockType === 'vertical') {
block.blockSprite.height = blockH * 2.2;
block.blockSprite.width = blockW * 0.7;
block.blockSprite.rotation = 0;
} else if (blockType === 'diagonal') {
block.blockSprite.height = blockH * 1.2;
block.blockSprite.width = blockW * 1.2;
block.blockSprite.rotation = level % 2 === 0 ? Math.PI / 6 : -Math.PI / 6;
} else if (blockType === 'round') {
// Use ball asset for round block
var oldSprite = block.blockSprite;
block.removeChild(oldSprite);
var roundSprite = block.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
width: blockW * 1.1,
height: blockW * 1.1,
tint: oldSprite.tint
});
block.blockSprite = roundSprite;
block.blockType = 'round'; // Ensure type is set
// Remove HP bars for round blocks for clarity
if (block.hpBarBg) block.removeChild(block.hpBarBg);
if (block.hpBar) block.removeChild(block.hpBar);
block.hpBarBg = null;
block.hpBar = null;
} else {
// normal
block.blockSprite.height = blockH;
block.blockSprite.width = blockW;
block.blockSprite.rotation = 0;
block.blockType = 'normal';
}
block.x = startX + c * (blockW + marginX);
block.y = startY + r * (blockH + marginY);
// Increase block HP as level increases, and more for special shapes
var hpBonus = 0;
if (blockType === 'vertical' || blockType === 'diagonal') hpBonus = 1 + Math.floor(level / 6);
if (blockType === 'round') hpBonus = 2 + Math.floor(level / 5);
block.hp = 1 + Math.floor(level / 4) + Math.floor(r / 3) + hpBonus;
block.maxHp = block.hp;
block.setHp(block.hp, block.maxHp);
game.addChild(block);
blocks.push(block);
}
}
}
}
// Helper: spawn paddle
function spawnPaddle() {
if (paddle) paddle.destroy();
paddle = new Paddle();
paddle.x = 2048 / 2;
paddle.y = 2732 - 180;
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) {
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
var workingTypes = [
// Paddle/ball size & speed
'expand', 'shrink', 'bigball', 'smallball', 'paddleup', 'paddledown', 'paddlefast', 'paddleslow',
// Ball count
'multiball', 'ballsplit', 'ballclone',
// 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
'reverse', 'ghost', 'paddleghost', 'paddleleft', 'paddleright', 'paddleteleport', 'magnet',
// Special/fun
'sticky', 'life', 'laser',
// Increase fireball frequency by adding it multiple times
'fireball', 'fireball', 'fireball', 'fireball', 'fireball', '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
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);
powerups.push(power);
}
// Helper: next level
function nextLevel() {
level++;
lives = Math.min(5, lives + 1); // Add 1 life, max 5
// Remove all falling powerup balls
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 && 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();
spawnBlocks();
spawnPaddle();
spawnBall();
levelCleared = false;
}
// 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 && 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
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 && 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 = [];
spawnPaddle();
spawnBall();
}
// Helper: win
function winGame() {
LK.effects.flashScreen(0x00ff00, 1000);
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 = [];
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") {
paddle.moveTo(paddle.x * 0.7 + targetX * 0.3);
} 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();
// --- OPTIMIZATION: Only update extra balls if any exist ---
if (!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;
for (var eb = window.extraBalls.length - 1; eb >= 0; eb--) {
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 && powerupBalls.length) {
var cachedPaddleHitboxPB = typeof paddle !== "undefined" && paddle && typeof paddle.getHitbox === "function" ? paddle.getHitbox() : null;
for (var pbIdx = powerupBalls.length - 1; pbIdx >= 0; pbIdx--) {
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
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
// OPTIMIZATION: Skip destroyed/invalid powerups immediately
if (!p || typeof p.update !== "function" || p.destroyed) {
powerups.splice(i, 1);
continue;
}
p.update();
// 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) window.activePowerups = [];
if (!window.powerupTimers) window.powerupTimers = [];
if (!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,
paddleup: 6000,
paddledown: 6000,
paddlefast: 6000,
paddleslow: 6000,
ballheavy: 6000,
balllight: 6000,
blockshrink: 6000,
blockexpand: 6000,
ghost: 6000,
superball: 6000,
paddleghost: 6000,
reverse: 6000,
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 === '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;
// 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();
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();
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();
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);
} else if (p.type === 'paddleup') {
paddle.y -= 120;
LK.setTimeout(function () {
paddle.y += 120;
}, 6000);
} else if (p.type === 'paddledown') {
paddle.y += 120;
LK.setTimeout(function () {
paddle.y -= 120;
}, 6000);
// 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 === 'paddlefast') {
paddle.moveTo = function (orig) {
return function (x) {
orig.call(paddle, x * 1.2);
};
}(paddle.moveTo);
LK.setTimeout(function () {
if (!Paddle.prototype.moveTo) {
Paddle.prototype.moveTo = function (x) {
// Clamp paddle within game bounds (leave 40px margin)
var minX = this.width / 2 + 40;
var maxX = 2048 - this.width / 2 - 40;
this.x = Math.max(minX, Math.min(maxX, x));
};
}
paddle.moveTo = Paddle.prototype.moveTo;
}, 6000);
} else if (p.type === 'paddleslow') {
paddle.moveTo = function (orig) {
return function (x) {
orig.call(paddle, x * 0.8);
};
}(paddle.moveTo);
LK.setTimeout(function () {
if (!Paddle.prototype.moveTo) {
Paddle.prototype.moveTo = function (x) {
// Clamp paddle within game bounds (leave 40px margin)
var minX = this.width / 2 + 40;
var maxX = 2048 - this.width / 2 - 40;
this.x = Math.max(minX, Math.min(maxX, x));
};
}
paddle.moveTo = Paddle.prototype.moveTo;
}, 6000);
} 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 === 'paddleteleport') {
paddle.x = 400 + Math.random() * 1200;
} 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();
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 === 'reverse') {
// Reverse paddle movement
paddle.moveTo = function (orig) {
return function (x) {
orig.call(paddle, 2048 - x);
};
}(paddle.moveTo);
LK.setTimeout(function () {
if (!Paddle.prototype.moveTo) {
Paddle.prototype.moveTo = function (x) {
// Clamp paddle within game bounds (leave 40px margin)
var minX = this.width / 2 + 40;
var maxX = 2048 - this.width / 2 - 40;
this.x = Math.max(minX, Math.min(maxX, x));
};
}
paddle.moveTo = Paddle.prototype.moveTo;
}, 6000);
} else if (p.type === 'ghost') {
// Paddle becomes transparent
paddle.alpha = 0.3;
LK.setTimeout(function () {
paddle.alpha = 1;
}, 6000);
} 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 === 'paddleghost') {
paddle.alpha = 0.1;
LK.setTimeout(function () {
paddle.alpha = 1;
}, 6000);
} else if (p.type === 'paddleleft') {
paddle.x = Math.max(paddle.width / 2 + 40, paddle.x - 400);
} else if (p.type === 'paddleright') {
paddle.x = Math.min(2048 - paddle.width / 2 - 40, paddle.x + 400);
} 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]);
}
}
}
}
p.destroy();
powerups.splice(i, 1);
}
}
}
// Ball physics
if (ball && !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 more than 15 times without hitting any block, make ALL balls accelerate upward toward the top wall
if (ball.wallBouncesNoBlock >= 15) {
// Helper to accelerate a ball upward
var accelerateUpward = function accelerateUpward(b) {
// Only accelerate if not already moving strongly upward
if (b.vy > -Math.abs(b.speed ? b.speed * 2 : 44)) {
b.vx = 0;
b.vy = -Math.abs(b.speed ? b.speed * 2 : 44); // Use double speed or 44 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) accelerateUpward(ball);
// Extra balls
if (window.extraBalls && window.extraBalls.length) {
for (var eb = 0; eb < window.extraBalls.length; eb++) {
if (window.extraBalls[eb]) accelerateUpward(window.extraBalls[eb]);
}
}
// Powerup balls
if (powerupBalls && powerupBalls.length) {
for (var pb = 0; pb < powerupBalls.length; pb++) {
if (powerupBalls[pb]) accelerateUpward(powerupBalls[pb]);
}
}
}
// Paddle collision
if (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
for (var i = blocks.length - 1; i >= 0; i--) {
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
if (block && block.blockSprite) {
LK.effects.flashObject(block, 0xff6600, 200);
}
block.hp = 0;
if (block.updateHpBar) block.updateHpBar();
block.breakBlock();
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();
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;
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)
game.powerupSpawnInterval = 360 + Math.floor(Math.random() * 360);
}
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)
game.powerupBallInterval = 480 + Math.floor(Math.random() * 480);
}
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 && window.activePowerups.length && window.powerupIcons && 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
@@ -9,171 +9,594 @@
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
- // Attach ball asset
- self.ballSprite = self.attachAsset('ball', {
+ var ballSprite = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
- // Ball properties
- self.radius = self.ballSprite.width / 2;
+ self.radius = ballSprite.width / 2;
self.vx = 0;
self.vy = 0;
- self.speed = 22;
- self.sticky = false;
- self.superball = false;
- self.fireball = false;
- self.scaleX = 1;
- self.scaleY = 1;
- // Update method (called every frame)
- self.update = function () {
- // If sticky, don't move
- if (self.sticky) return;
- // Move ball
- self.x += self.vx;
- self.y += self.vy;
- // Clamp scale
- self.ballSprite.scale.x = self.scaleX;
- self.ballSprite.scale.y = self.scaleY;
- // Clamp alpha
- self.ballSprite.alpha = self.alpha !== undefined ? self.alpha : 1;
- };
- // Launch the ball at a given angle (in radians)
+ self.speed = 22; // Initial speed
+ self.sticky = false; // If true, ball sticks to paddle
self.launch = function (angle) {
- self.sticky = false;
+ // Launch ball at angle (in radians)
self.vx = Math.cos(angle) * self.speed;
self.vy = Math.sin(angle) * self.speed;
+ self.sticky = false;
};
- // Get hitbox for collision
- self.getHitbox = function () {
- var r = self.ballSprite.width / 2 * (self.scaleX || 1);
- return {
- x: self.x,
- y: self.y,
- width: r * 2,
- height: r * 2,
- hw: r,
- hh: r,
- r: r,
- radius: r
- };
+ self.update = function () {
+ if (!self.sticky) {
+ self.x += self.vx;
+ self.y += self.vy;
+ // 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;
+ }
+ }
};
return self;
});
// Block class
var Block = Container.expand(function () {
var self = Container.call(this);
- // Default color is blue
- self.color = 'blue';
- // Attach block sprite
- self.blockSprite = self.attachAsset('block_blue', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- // HP bar background and bar
+ // color: 'red', 'green', 'blue', 'yellow'
+ self.color = 'red';
+ self.hp = 1;
+ self.maxHp = 1;
self.hpBarBg = null;
self.hpBar = null;
- // Set color
+ self.blockType = 'normal'; // will be set by spawnBlocks
self.setColor = function (color) {
self.color = color;
- var assetId = 'block_' + color;
- // Remove old sprite if exists
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();
};
- // Set HP and update HP bar
self.setHp = function (hp, maxHp) {
self.hp = hp;
- self.maxHp = maxHp;
+ self.maxHp = maxHp || hp;
self.updateHpBar();
};
- // Update HP bar
self.updateHpBar = function () {
- // Remove old bars
- if (self.hpBarBg) self.removeChild(self.hpBarBg);
- if (self.hpBar) self.removeChild(self.hpBar);
- // Only show HP bar if maxHp > 1
- if (self.maxHp > 1) {
- var w = self.blockSprite.width * 0.8;
- var h = 12;
- // Background
- self.hpBarBg = self.attachAsset('block_blue', {
- anchorX: 0.5,
- anchorY: 0.5,
- width: w,
- height: h,
- tint: 0x222222
- });
- self.hpBarBg.y = self.blockSprite.height / 2 + 10;
- // Foreground
- self.hpBar = self.attachAsset('block_yellow', {
- anchorX: 0.5,
- anchorY: 0.5,
- width: w * Math.max(0, self.hp) / self.maxHp,
- height: h,
- tint: 0xFFE066
- });
- self.hpBar.y = self.blockSprite.height / 2 + 10;
+ 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;
};
- // Break block (destroy with effect)
self.breakBlock = function () {
- LK.effects.flashObject(self, 0xffffff, 200);
- self.destroy();
+ // Animate block breaking
+ tween(self, {
+ alpha: 0,
+ scaleX: 1.5,
+ scaleY: 1.5
+ }, {
+ duration: 200,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ self.destroy();
+ }
+ });
};
- // Get hitbox for Collider
+ // --- HITBOX LOGIC ---
+ // Returns the hitbox for this block, depending on its type and current transform
self.getHitbox = function () {
- return {
- x: self.x,
- y: self.y,
- width: self.blockSprite.width,
- height: self.blockSprite.height,
- hw: self.blockSprite.width / 2,
- hh: self.blockSprite.height / 2
- };
+ 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;
});
-// Music (optional, not required by MVP, so not included)
-/* End of gamecode.js */
// Paddle class
var Paddle = Container.expand(function () {
var self = Container.call(this);
- // Attach paddle asset
- self.paddleSprite = self.attachAsset('paddle', {
+ var paddleSprite = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
- // Set width/height for convenience
- self.width = self.paddleSprite.width;
- self.height = self.paddleSprite.height;
- // Default scale
- self.scaleX = 1;
- self.scaleY = 1;
- // Move paddle to x, clamped within bounds
+ // 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 (leave 40px margin)
var minX = self.width / 2 + 40;
var maxX = 2048 - self.width / 2 - 40;
self.x = Math.max(minX, Math.min(maxX, x));
};
- // Get hitbox for collision
- self.getHitbox = function () {
- return {
- x: self.x,
- y: self.y,
- width: self.width * (self.scaleX || 1),
- height: self.height * (self.scaleY || 1),
- hw: self.width * (self.scaleX || 1) / 2,
- hh: self.height * (self.scaleY || 1) / 2
- };
+ 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") {
+ 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") {
+ 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") {
+ 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
****/
@@ -183,14 +606,14 @@
/****
* Game Code
****/
-// Sounds
-// Power-up
-// Block colors
-// Paddle
-// Ball
// Game variables
+// Ball
+// Paddle
+// Block colors
+// Power-up
+// Sounds
var paddle,
ball,
blocks = [],
powerups = [],
@@ -662,11 +1085,13 @@
// Main update loop
game.update = function () {
// Ball update
if (ball) ball.update();
- // Update extra balls (if any)
+ // --- OPTIMIZATION: Only update extra balls if any exist ---
if (!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;
for (var eb = window.extraBalls.length - 1; eb >= 0; eb--) {
var ebBall = window.extraBalls[eb];
if (ebBall && ebBall.update) ebBall.update();
// --- Track left/right wall bounces for extra balls ---
@@ -707,18 +1132,15 @@
ebBall.hasHitBlockSinceReset = false;
}
// Remove if off screen
if (ebBall && ebBall.y - ebBall.radius > 2732) {
- // Just remove the extra ball, do not call loseLife()
ebBall.destroy();
window.extraBalls.splice(eb, 1);
- // Do NOT call loseLife() for extra balls
continue;
}
- // Paddle collision for extra balls
- if (ebBall && typeof paddle !== "undefined" && paddle && typeof paddle.getHitbox === "function") {
- var hitbox = paddle.getHitbox();
- if (Collider.intersect(ebBall, paddle) && ebBall.vy > 0) {
+ // 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);
@@ -733,10 +1155,11 @@
}
}
}
}
- // Update powerup balls to behave like normal balls but not cost a life when lost
+ // --- OPTIMIZATION: Only update powerup balls if any exist ---
if (powerupBalls && powerupBalls.length) {
+ var cachedPaddleHitboxPB = typeof paddle !== "undefined" && paddle && typeof paddle.getHitbox === "function" ? paddle.getHitbox() : null;
for (var pbIdx = powerupBalls.length - 1; pbIdx >= 0; pbIdx--) {
var pbBall = powerupBalls[pbIdx];
if (pbBall && pbBall.update) pbBall.update();
// --- Track left/right wall bounces for powerup balls ---
@@ -779,15 +1202,13 @@
// Remove if off screen
if (pbBall && pbBall.y - pbBall.radius > 2732) {
pbBall.destroy();
powerupBalls.splice(pbIdx, 1);
- // Do NOT call loseLife() for powerup balls
continue;
}
- // Paddle collision for powerup balls
- if (pbBall && typeof paddle !== "undefined" && paddle && typeof paddle.getHitbox === "function") {
- var hitbox = paddle.getHitbox();
- if (Collider.intersect(pbBall, paddle) && pbBall.vy > 0) {
+ // 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);
@@ -805,10 +1226,10 @@
}
// PowerUp update
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
- if (!p || typeof p.update !== "function") {
- // Remove invalid/undefined powerup objects to prevent future errors
+ // OPTIMIZATION: Skip destroyed/invalid powerups immediately
+ if (!p || typeof p.update !== "function" || p.destroyed) {
powerups.splice(i, 1);
continue;
}
p.update();
@@ -819,20 +1240,20 @@
continue;
}
// Paddle or ball collision
var collected = false;
- // Use Collider for paddle collision to ensure all powerups are collected by paddle
- if (paddle && Collider.intersect(p, paddle)) {
+ // 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 && Collider.intersect(p, 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 (Collider.intersect(p, window.extraBalls[eb])) {
+ if (p.intersects(window.extraBalls[eb])) {
collected = true;
break;
}
}
@@ -840,63 +1261,9 @@
// No level restriction: powerups are always collectible
if (collected) {
// --- Powerup balls update and collection ---
}
- for (var i = powerupBalls.length - 1; i >= 0; i--) {
- var pb = powerupBalls[i];
- pb.update();
- // Remove if off screen
- if (pb.y - pb.radius > 2732 + 100) {
- pb.destroy();
- powerupBalls.splice(i, 1);
- continue;
- }
- // Collect if hits paddle
- if (paddle && Collider.intersect(pb, paddle)) {
- // Show floating text for powerup ball
- var label = new Text2(pb.type.toUpperCase(), {
- size: 70,
- fill: 0xFFE066,
- font: "Arial Black"
- });
- label.anchor.set(0.5, 1);
- label.x = pb.x;
- label.y = pb.y - 60;
- game.addChild(label);
- tween(label, {
- y: label.y - 120,
- alpha: 0
- }, {
- duration: 1200,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- label.destroy();
- }
- });
- LK.getSound('powerup').play();
- // Give a reward: e.g. +1 life, +100 score, or multiball
- if (pb.type === 'powerupball1') {
- lives = Math.min(5, lives + 1);
- updateGUI();
- } else if (pb.type === 'powerupball2') {
- score += 100;
- updateGUI();
- } else if (pb.type === 'powerupball3') {
- // Add a multiball as a main ball, not just extra
- var newBall = new Ball();
- newBall.x = pb.x;
- newBall.y = pb.y;
- newBall.vx = pb.vx;
- newBall.vy = pb.vy;
- newBall.sticky = false;
- game.addChild(newBall);
- if (!window.extraBalls) window.extraBalls = [];
- window.extraBalls.push(newBall);
- }
- pb.destroy();
- powerupBalls.splice(i, 1);
- }
- }
+ // --- 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,
@@ -1568,18 +1935,18 @@
}
// Paddle collision
if (typeof paddle.getHitbox === "function") {
var hitbox = paddle.getHitbox();
- if (Collider.intersect(ball, paddle) && ball.vy > 0) {
+ 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 > paddle.y - paddle.height / 2) {
+ if (ball.y + ball.radius > hitbox.top) {
ball.y = paddle.y - paddle.height / 2 - ball.radius - 2;
}
LK.getSound('hit').play();
}
@@ -1625,11 +1992,13 @@
}
// Block collisions
for (var i = blocks.length - 1; i >= 0; i--) {
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 && Collider.intersect(ball, block)) {
+ if (ball && block && ball.intersects(block)) {
// Fireball: instantly destroy block and keep moving
if (ball.fireball) {
// Burn block: show a flash and destroy
if (block && block.blockSprite) {
@@ -1652,13 +2021,10 @@
hit = true;
// Do not reflect ball, let it keep moving
} else {
// Reflect ball
- // Use hitbox for overlap direction
- var a = typeof ball.getHitbox === "function" ? ball.getHitbox() : ball;
- var b = typeof block.getHitbox === "function" ? block.getHitbox() : block;
- var overlapX = Math.abs(a.x - b.x) - ((a.hw || a.width / 2 || a.r || a.radius) + (b.hw || b.width / 2 || b.r || b.radius));
- var overlapY = Math.abs(a.y - b.y) - ((a.hh || a.height / 2 || a.r || a.radius) + (b.hh || b.height / 2 || b.r || b.radius));
+ 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;
@@ -1669,14 +2035,12 @@
// 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 && Collider.intersect(ebBall, block)) {
+ if (ebBall && block && ebBall.intersects(block)) {
// Reflect extra ball
- var a = typeof ebBall.getHitbox === "function" ? ebBall.getHitbox() : ebBall;
- var b = typeof block.getHitbox === "function" ? block.getHitbox() : block;
- var overlapX = Math.abs(a.x - b.x) - ((a.hw || a.width / 2 || a.r || a.radius) + (b.hw || b.width / 2 || b.r || b.radius));
- var overlapY = Math.abs(a.y - b.y) - ((a.hh || a.height / 2 || a.r || a.radius) + (b.hh || b.height / 2 || b.r || b.radius));
+ 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;
@@ -1801,5 +2165,7 @@
icon.y = 170;
}
}
}
-};
\ No newline at end of file
+};
+// Music (optional, not required by MVP, so not included)
+/* End of gamecode.js */
\ No newline at end of file