Code edit (1 edits merged)
Please save this source code
User prompt
Berzerk Ball: Homerun Smash
Initial prompt
copy Berzerk Ball game, this game The sequel to Homerun in Berzerk Land will probably show up sometime in 2011, but since we really appreciate your support, we've decided to give you something to sink your teeth into in the meantime. Behold Berzerk Ball, the expansion pack of the original Homerun game. A brand new look for the game, new weapons, new items and a sexy new character are all bundled in for you so you get the chance to beat your friends' high scores all over again! Oh yeah, and make sure to check out the iPhone and iPod version of it at: We also would like to take the occasion to wish you a merry Christmas and a happy, super-sharp New Year 2011! Thanks for all your support and we hope to see you guys back as top-shape as ever next year (aka in a couple of days from now)!
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { bestDistance: 0, coins: 0, upgrades: { power: 1, bounce: 1 } }); /**** * Classes ****/ // Bat class: the weapon to hit the geek var Bat = Container.expand(function () { var self = Container.call(this); var batSprite = self.attachAsset('bat', { anchorX: 0.1, anchorY: 0.5 }); self.swinging = false; self.angle = -Math.PI / 4; // resting angle self.power = 0; self.maxPower = 1; self.swingTween = null; self.setAngle = function (a) { self.rotation = a; self.angle = a; }; self.swing = function (onHit) { if (self.swinging) return; self.swinging = true; // Animate swing tween(self, { rotation: Math.PI / 2 }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { self.swinging = false; self.setAngle(-Math.PI / 4); if (onHit) onHit(); } }); }; return self; }); // Geek class: the character to launch var Geek = Container.expand(function () { var self = Container.call(this); var geekSprite = self.attachAsset('geek', { anchorX: 0.5, anchorY: 0.5 }); self.radius = geekSprite.width / 2; self.vx = 0; // velocity x self.vy = 0; // velocity y self.rotationSpeed = 0; self.isFlying = false; self.isStopped = false; self.distance = 0; self.bounceCount = 0; // For collision detection self.getBounds = function () { return { x: self.x - self.radius, y: self.y - self.radius, width: self.radius * 2, height: self.radius * 2 }; }; // Called every tick self.update = function () { if (!self.isFlying || self.isStopped) return; // Physics self.x += self.vx; self.y += self.vy; self.vy += 1.2; // gravity // Air friction self.vx *= 0.995; self.vy *= 0.995; // Rotation self.rotation += self.rotationSpeed; // Distance tracking self.distance += self.vx; // Ground collision if (self.y + self.radius >= groundY) { self.y = groundY - self.radius; if (Math.abs(self.vy) > 8) { // Bounce self.vy = -Math.abs(self.vy) * (0.45 + 0.05 * upgrades.bounce); self.vx *= 0.92; self.rotationSpeed *= 0.7; self.bounceCount++; LK.getSound('bounce').play(); // Small upward nudge to avoid sticking self.y -= 2; } else { // Stop self.vy = 0; self.vx = 0; self.isStopped = true; self.isFlying = false; onGeekStopped(); } } }; return self; }); // Obstacle (rock) var Rock = Container.expand(function () { var self = Container.call(this); var rockSprite = self.attachAsset('rock', { anchorX: 0.5, anchorY: 0.5 }); self.radius = rockSprite.width / 2; self.getBounds = function () { return { x: self.x - self.radius, y: self.y - self.radius, width: self.radius * 2, height: self.radius * 2 }; }; return self; }); // Power-up (spring) var Spring = Container.expand(function () { var self = Container.call(this); var springSprite = self.attachAsset('spring', { anchorX: 0.5, anchorY: 0.5 }); self.radius = springSprite.width / 2; self.getBounds = function () { return { x: self.x - self.radius, y: self.y - self.radius, width: self.radius * 2, height: self.radius * 2 }; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // Sky blue }); /**** * Game Code ****/ // Music // Sound effects // Power-up (spring) // Obstacle (rock) // Ground // Bat (the weapon) // Geek (the character to launch) // --- Global variables --- var groundY = 2732 - 80; // ground top var geek, bat; var rocks = []; var springs = []; var upgrades = storage.upgrades || { power: 1, bounce: 1 }; var coins = storage.coins || 0; var bestDistance = storage.bestDistance || 0; var runDistance = 0; var runCoins = 0; var isAiming = false; var aimPower = 0; var aimAngle = -Math.PI / 4; var aimMaxPower = 1.5 + 0.2 * upgrades.power; var aimMinAngle = -Math.PI / 3; var aimMaxAngle = -Math.PI / 8; var aimDir = 1; var aimTimer = 0; var gameState = 'aim'; // 'aim', 'flying', 'stopped' // --- GUI --- var scoreTxt = new Text2('0 m', { size: 100, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var coinTxt = new Text2('0', { size: 70, fill: 0xFFD700 }); coinTxt.anchor.set(1, 0); LK.gui.topRight.addChild(coinTxt); var bestTxt = new Text2('Best: 0 m', { size: 60, fill: "#fff" }); bestTxt.anchor.set(0, 0); LK.gui.topLeft.addChild(bestTxt); var upgradeTxt = new Text2('', { size: 60, fill: "#fff" }); upgradeTxt.anchor.set(0.5, 1); LK.gui.bottom.addChild(upgradeTxt); // --- Ground --- var ground = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: groundY }); game.addChild(ground); // --- Functions --- function resetRun() { // Remove old objects if (geek) geek.destroy(); if (bat) bat.destroy(); for (var i = 0; i < rocks.length; i++) rocks[i].destroy(); for (var i = 0; i < springs.length; i++) springs[i].destroy(); rocks = []; springs = []; runDistance = 0; runCoins = 0; aimPower = 0; aimAngle = -Math.PI / 4; aimDir = 1; aimTimer = 0; gameState = 'aim'; // Geek geek = new Geek(); geek.x = 350; geek.y = groundY - 120; geek.rotation = 0; game.addChild(geek); // Bat bat = new Bat(); bat.x = geek.x - 80; bat.y = groundY - 120; bat.setAngle(-Math.PI / 4); game.addChild(bat); // Obstacles for (var i = 0; i < 3; i++) { var rock = new Rock(); rock.x = 900 + 700 * i + Math.random() * 200; rock.y = groundY - 30; rocks.push(rock); game.addChild(rock); } // Power-ups for (var i = 0; i < 2; i++) { var spring = new Spring(); spring.x = 1200 + 1000 * i + Math.random() * 300; spring.y = groundY - 20; springs.push(spring); game.addChild(spring); } // GUI scoreTxt.setText('0 m'); coinTxt.setText('' + coins); bestTxt.setText('Best: ' + bestDistance + ' m'); upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')'); } function onGeekStopped() { // End of run runDistance = Math.floor(geek.distance / 10); if (runDistance > bestDistance) { bestDistance = runDistance; storage.bestDistance = bestDistance; } coins += Math.floor(runDistance / 10) + runCoins; storage.coins = coins; // Show game over LK.effects.flashScreen(0x000000, 600); LK.showGameOver(); } function launchGeek(power, angle) { // Bat swing animation bat.swing(function () { // Launch var p = 38 * power * (1 + 0.15 * upgrades.power); geek.vx = p * Math.cos(angle); geek.vy = p * Math.sin(angle) - 10; geek.rotationSpeed = 0.2 + 0.1 * power; geek.isFlying = true; gameState = 'flying'; LK.getSound('hit').play(); // Remove bat after swing LK.setTimeout(function () { bat.destroy(); }, 300); }); } // --- Input handling --- game.down = function (x, y, obj) { if (gameState === 'aim') { isAiming = true; aimTimer = 0; } else if (gameState === 'stopped') { // Check if bottom area tapped for upgrades if (y > 2732 - 300) { // Alternate upgrades: tap left half for power, right half for bounce if (x < 2048 / 2) { // Power upgrade var cost = 10 + upgrades.power * 10; if (coins >= cost) { upgrades.power++; coins -= cost; storage.upgrades = upgrades; storage.coins = coins; LK.effects.flashObject(upgradeTxt, 0x00ff00, 400); } else { LK.effects.flashObject(upgradeTxt, 0xff0000, 400); } } else { // Bounce upgrade var cost = 10 + upgrades.bounce * 10; if (coins >= cost) { upgrades.bounce++; coins -= cost; storage.upgrades = upgrades; storage.coins = coins; LK.effects.flashObject(upgradeTxt, 0x00ff00, 400); } else { LK.effects.flashObject(upgradeTxt, 0xff0000, 400); } } upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')'); coinTxt.setText('' + coins); } } }; game.up = function (x, y, obj) { if (gameState === 'aim' && isAiming) { isAiming = false; // Launch! launchGeek(aimPower, aimAngle); } }; function checkCollision(a, b) { var ab = a.getBounds(); var bb = b.getBounds(); return ab.x < bb.x + bb.width && ab.x + ab.width > bb.x && ab.y < bb.y + bb.height && ab.y + ab.height > bb.y; } // --- Main update loop --- game.update = function () { // Aiming phase if (gameState === 'aim' && isAiming) { // Power oscillates up and down aimTimer++; aimPower = 0.5 + 0.5 * Math.abs(Math.sin(aimTimer * 0.05)); // Angle oscillates between min and max aimAngle += aimDir * 0.012; if (aimAngle > aimMaxAngle) aimDir = -1; if (aimAngle < aimMinAngle) aimDir = 1; bat.setAngle(aimAngle); // Bat color flashes with power var color = 0x964b00 + Math.floor(aimPower * 0x002200); bat.children[0].tint = color; } // Update geek if (geek) geek.update(); // Camera follow: move all objects left as geek moves if (geek && geek.isFlying) { var camX = Math.max(0, geek.x - 350); // Move all except GUI and ground for (var i = 0; i < rocks.length; i++) rocks[i].x -= camX - (rocks[i].camX || 0); for (var i = 0; i < springs.length; i++) springs[i].x -= camX - (springs[i].camX || 0); if (bat && !bat.destroyed) bat.x -= camX - (bat.camX || 0); geek.x -= camX - (geek.camX || 0); // Store camX for next frame for (var i = 0; i < rocks.length; i++) rocks[i].camX = camX; for (var i = 0; i < springs.length; i++) springs[i].camX = camX; if (bat && !bat.destroyed) bat.camX = camX; geek.camX = camX; } // Collisions: obstacles if (geek && geek.isFlying && !geek.isStopped) { for (var i = 0; i < rocks.length; i++) { var rock = rocks[i]; if (!rock.hit && checkCollision(geek, rock)) { // Hit rock: lose speed, bounce up geek.vx *= 0.6; geek.vy = -Math.abs(geek.vy) * 0.7 - 8; geek.rotationSpeed *= 0.7; rock.hit = true; LK.effects.flashObject(rock, 0xff0000, 400); LK.getSound('bounce').play(); } } // Collisions: power-ups for (var i = 0; i < springs.length; i++) { var spring = springs[i]; if (!spring.used && checkCollision(geek, spring)) { // Hit spring: big bounce, gain coins geek.vy = -32 - 8 * upgrades.bounce; geek.vx *= 1.12; geek.rotationSpeed += 0.2; spring.used = true; runCoins += 5; LK.effects.flashObject(spring, 0xffd700, 500); LK.getSound('powerup').play(); } } } // Update GUI if (geek) { var dist = Math.floor(geek.distance / 10); scoreTxt.setText(dist + ' m'); } coinTxt.setText('' + coins); // If geek stopped, allow upgrades and restart if (geek && geek.isStopped && gameState !== 'stopped') { gameState = 'stopped'; upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')'); } }; // --- Game over handler --- LK.on('gameover', function () { // Reset run for next play resetRun(); }); // --- Music --- LK.playMusic('bgmusic', { fade: { start: 0, end: 1, duration: 1000 } }); // --- Start game --- resetRun();
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,440 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+var storage = LK.import("@upit/storage.v1", {
+ bestDistance: 0,
+ coins: 0,
+ upgrades: {
+ power: 1,
+ bounce: 1
+ }
+});
+
+/****
+* Classes
+****/
+// Bat class: the weapon to hit the geek
+var Bat = Container.expand(function () {
+ var self = Container.call(this);
+ var batSprite = self.attachAsset('bat', {
+ anchorX: 0.1,
+ anchorY: 0.5
+ });
+ self.swinging = false;
+ self.angle = -Math.PI / 4; // resting angle
+ self.power = 0;
+ self.maxPower = 1;
+ self.swingTween = null;
+ self.setAngle = function (a) {
+ self.rotation = a;
+ self.angle = a;
+ };
+ self.swing = function (onHit) {
+ if (self.swinging) return;
+ self.swinging = true;
+ // Animate swing
+ tween(self, {
+ rotation: Math.PI / 2
+ }, {
+ duration: 180,
+ easing: tween.cubicOut,
+ onFinish: function onFinish() {
+ self.swinging = false;
+ self.setAngle(-Math.PI / 4);
+ if (onHit) onHit();
+ }
+ });
+ };
+ return self;
+});
+// Geek class: the character to launch
+var Geek = Container.expand(function () {
+ var self = Container.call(this);
+ var geekSprite = self.attachAsset('geek', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.radius = geekSprite.width / 2;
+ self.vx = 0; // velocity x
+ self.vy = 0; // velocity y
+ self.rotationSpeed = 0;
+ self.isFlying = false;
+ self.isStopped = false;
+ self.distance = 0;
+ self.bounceCount = 0;
+ // For collision detection
+ self.getBounds = function () {
+ return {
+ x: self.x - self.radius,
+ y: self.y - self.radius,
+ width: self.radius * 2,
+ height: self.radius * 2
+ };
+ };
+ // Called every tick
+ self.update = function () {
+ if (!self.isFlying || self.isStopped) return;
+ // Physics
+ self.x += self.vx;
+ self.y += self.vy;
+ self.vy += 1.2; // gravity
+ // Air friction
+ self.vx *= 0.995;
+ self.vy *= 0.995;
+ // Rotation
+ self.rotation += self.rotationSpeed;
+ // Distance tracking
+ self.distance += self.vx;
+ // Ground collision
+ if (self.y + self.radius >= groundY) {
+ self.y = groundY - self.radius;
+ if (Math.abs(self.vy) > 8) {
+ // Bounce
+ self.vy = -Math.abs(self.vy) * (0.45 + 0.05 * upgrades.bounce);
+ self.vx *= 0.92;
+ self.rotationSpeed *= 0.7;
+ self.bounceCount++;
+ LK.getSound('bounce').play();
+ // Small upward nudge to avoid sticking
+ self.y -= 2;
+ } else {
+ // Stop
+ self.vy = 0;
+ self.vx = 0;
+ self.isStopped = true;
+ self.isFlying = false;
+ onGeekStopped();
+ }
+ }
+ };
+ return self;
+});
+// Obstacle (rock)
+var Rock = Container.expand(function () {
+ var self = Container.call(this);
+ var rockSprite = self.attachAsset('rock', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.radius = rockSprite.width / 2;
+ self.getBounds = function () {
+ return {
+ x: self.x - self.radius,
+ y: self.y - self.radius,
+ width: self.radius * 2,
+ height: self.radius * 2
+ };
+ };
+ return self;
+});
+// Power-up (spring)
+var Spring = Container.expand(function () {
+ var self = Container.call(this);
+ var springSprite = self.attachAsset('spring', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.radius = springSprite.width / 2;
+ self.getBounds = function () {
+ return {
+ x: self.x - self.radius,
+ y: self.y - self.radius,
+ width: self.radius * 2,
+ height: self.radius * 2
+ };
+ };
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x87ceeb // Sky blue
+});
+
+/****
+* Game Code
+****/
+// Music
+// Sound effects
+// Power-up (spring)
+// Obstacle (rock)
+// Ground
+// Bat (the weapon)
+// Geek (the character to launch)
+// --- Global variables ---
+var groundY = 2732 - 80; // ground top
+var geek, bat;
+var rocks = [];
+var springs = [];
+var upgrades = storage.upgrades || {
+ power: 1,
+ bounce: 1
+};
+var coins = storage.coins || 0;
+var bestDistance = storage.bestDistance || 0;
+var runDistance = 0;
+var runCoins = 0;
+var isAiming = false;
+var aimPower = 0;
+var aimAngle = -Math.PI / 4;
+var aimMaxPower = 1.5 + 0.2 * upgrades.power;
+var aimMinAngle = -Math.PI / 3;
+var aimMaxAngle = -Math.PI / 8;
+var aimDir = 1;
+var aimTimer = 0;
+var gameState = 'aim'; // 'aim', 'flying', 'stopped'
+// --- GUI ---
+var scoreTxt = new Text2('0 m', {
+ size: 100,
+ fill: "#fff"
+});
+scoreTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(scoreTxt);
+var coinTxt = new Text2('0', {
+ size: 70,
+ fill: 0xFFD700
+});
+coinTxt.anchor.set(1, 0);
+LK.gui.topRight.addChild(coinTxt);
+var bestTxt = new Text2('Best: 0 m', {
+ size: 60,
+ fill: "#fff"
+});
+bestTxt.anchor.set(0, 0);
+LK.gui.topLeft.addChild(bestTxt);
+var upgradeTxt = new Text2('', {
+ size: 60,
+ fill: "#fff"
+});
+upgradeTxt.anchor.set(0.5, 1);
+LK.gui.bottom.addChild(upgradeTxt);
+// --- Ground ---
+var ground = LK.getAsset('ground', {
+ anchorX: 0,
+ anchorY: 0,
+ x: 0,
+ y: groundY
+});
+game.addChild(ground);
+// --- Functions ---
+function resetRun() {
+ // Remove old objects
+ if (geek) geek.destroy();
+ if (bat) bat.destroy();
+ for (var i = 0; i < rocks.length; i++) rocks[i].destroy();
+ for (var i = 0; i < springs.length; i++) springs[i].destroy();
+ rocks = [];
+ springs = [];
+ runDistance = 0;
+ runCoins = 0;
+ aimPower = 0;
+ aimAngle = -Math.PI / 4;
+ aimDir = 1;
+ aimTimer = 0;
+ gameState = 'aim';
+ // Geek
+ geek = new Geek();
+ geek.x = 350;
+ geek.y = groundY - 120;
+ geek.rotation = 0;
+ game.addChild(geek);
+ // Bat
+ bat = new Bat();
+ bat.x = geek.x - 80;
+ bat.y = groundY - 120;
+ bat.setAngle(-Math.PI / 4);
+ game.addChild(bat);
+ // Obstacles
+ for (var i = 0; i < 3; i++) {
+ var rock = new Rock();
+ rock.x = 900 + 700 * i + Math.random() * 200;
+ rock.y = groundY - 30;
+ rocks.push(rock);
+ game.addChild(rock);
+ }
+ // Power-ups
+ for (var i = 0; i < 2; i++) {
+ var spring = new Spring();
+ spring.x = 1200 + 1000 * i + Math.random() * 300;
+ spring.y = groundY - 20;
+ springs.push(spring);
+ game.addChild(spring);
+ }
+ // GUI
+ scoreTxt.setText('0 m');
+ coinTxt.setText('' + coins);
+ bestTxt.setText('Best: ' + bestDistance + ' m');
+ upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')');
+}
+function onGeekStopped() {
+ // End of run
+ runDistance = Math.floor(geek.distance / 10);
+ if (runDistance > bestDistance) {
+ bestDistance = runDistance;
+ storage.bestDistance = bestDistance;
+ }
+ coins += Math.floor(runDistance / 10) + runCoins;
+ storage.coins = coins;
+ // Show game over
+ LK.effects.flashScreen(0x000000, 600);
+ LK.showGameOver();
+}
+function launchGeek(power, angle) {
+ // Bat swing animation
+ bat.swing(function () {
+ // Launch
+ var p = 38 * power * (1 + 0.15 * upgrades.power);
+ geek.vx = p * Math.cos(angle);
+ geek.vy = p * Math.sin(angle) - 10;
+ geek.rotationSpeed = 0.2 + 0.1 * power;
+ geek.isFlying = true;
+ gameState = 'flying';
+ LK.getSound('hit').play();
+ // Remove bat after swing
+ LK.setTimeout(function () {
+ bat.destroy();
+ }, 300);
+ });
+}
+// --- Input handling ---
+game.down = function (x, y, obj) {
+ if (gameState === 'aim') {
+ isAiming = true;
+ aimTimer = 0;
+ } else if (gameState === 'stopped') {
+ // Check if bottom area tapped for upgrades
+ if (y > 2732 - 300) {
+ // Alternate upgrades: tap left half for power, right half for bounce
+ if (x < 2048 / 2) {
+ // Power upgrade
+ var cost = 10 + upgrades.power * 10;
+ if (coins >= cost) {
+ upgrades.power++;
+ coins -= cost;
+ storage.upgrades = upgrades;
+ storage.coins = coins;
+ LK.effects.flashObject(upgradeTxt, 0x00ff00, 400);
+ } else {
+ LK.effects.flashObject(upgradeTxt, 0xff0000, 400);
+ }
+ } else {
+ // Bounce upgrade
+ var cost = 10 + upgrades.bounce * 10;
+ if (coins >= cost) {
+ upgrades.bounce++;
+ coins -= cost;
+ storage.upgrades = upgrades;
+ storage.coins = coins;
+ LK.effects.flashObject(upgradeTxt, 0x00ff00, 400);
+ } else {
+ LK.effects.flashObject(upgradeTxt, 0xff0000, 400);
+ }
+ }
+ upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')');
+ coinTxt.setText('' + coins);
+ }
+ }
+};
+game.up = function (x, y, obj) {
+ if (gameState === 'aim' && isAiming) {
+ isAiming = false;
+ // Launch!
+ launchGeek(aimPower, aimAngle);
+ }
+};
+function checkCollision(a, b) {
+ var ab = a.getBounds();
+ var bb = b.getBounds();
+ return ab.x < bb.x + bb.width && ab.x + ab.width > bb.x && ab.y < bb.y + bb.height && ab.y + ab.height > bb.y;
+}
+// --- Main update loop ---
+game.update = function () {
+ // Aiming phase
+ if (gameState === 'aim' && isAiming) {
+ // Power oscillates up and down
+ aimTimer++;
+ aimPower = 0.5 + 0.5 * Math.abs(Math.sin(aimTimer * 0.05));
+ // Angle oscillates between min and max
+ aimAngle += aimDir * 0.012;
+ if (aimAngle > aimMaxAngle) aimDir = -1;
+ if (aimAngle < aimMinAngle) aimDir = 1;
+ bat.setAngle(aimAngle);
+ // Bat color flashes with power
+ var color = 0x964b00 + Math.floor(aimPower * 0x002200);
+ bat.children[0].tint = color;
+ }
+ // Update geek
+ if (geek) geek.update();
+ // Camera follow: move all objects left as geek moves
+ if (geek && geek.isFlying) {
+ var camX = Math.max(0, geek.x - 350);
+ // Move all except GUI and ground
+ for (var i = 0; i < rocks.length; i++) rocks[i].x -= camX - (rocks[i].camX || 0);
+ for (var i = 0; i < springs.length; i++) springs[i].x -= camX - (springs[i].camX || 0);
+ if (bat && !bat.destroyed) bat.x -= camX - (bat.camX || 0);
+ geek.x -= camX - (geek.camX || 0);
+ // Store camX for next frame
+ for (var i = 0; i < rocks.length; i++) rocks[i].camX = camX;
+ for (var i = 0; i < springs.length; i++) springs[i].camX = camX;
+ if (bat && !bat.destroyed) bat.camX = camX;
+ geek.camX = camX;
+ }
+ // Collisions: obstacles
+ if (geek && geek.isFlying && !geek.isStopped) {
+ for (var i = 0; i < rocks.length; i++) {
+ var rock = rocks[i];
+ if (!rock.hit && checkCollision(geek, rock)) {
+ // Hit rock: lose speed, bounce up
+ geek.vx *= 0.6;
+ geek.vy = -Math.abs(geek.vy) * 0.7 - 8;
+ geek.rotationSpeed *= 0.7;
+ rock.hit = true;
+ LK.effects.flashObject(rock, 0xff0000, 400);
+ LK.getSound('bounce').play();
+ }
+ }
+ // Collisions: power-ups
+ for (var i = 0; i < springs.length; i++) {
+ var spring = springs[i];
+ if (!spring.used && checkCollision(geek, spring)) {
+ // Hit spring: big bounce, gain coins
+ geek.vy = -32 - 8 * upgrades.bounce;
+ geek.vx *= 1.12;
+ geek.rotationSpeed += 0.2;
+ spring.used = true;
+ runCoins += 5;
+ LK.effects.flashObject(spring, 0xffd700, 500);
+ LK.getSound('powerup').play();
+ }
+ }
+ }
+ // Update GUI
+ if (geek) {
+ var dist = Math.floor(geek.distance / 10);
+ scoreTxt.setText(dist + ' m');
+ }
+ coinTxt.setText('' + coins);
+ // If geek stopped, allow upgrades and restart
+ if (geek && geek.isStopped && gameState !== 'stopped') {
+ gameState = 'stopped';
+ upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')');
+ }
+};
+// --- Game over handler ---
+LK.on('gameover', function () {
+ // Reset run for next play
+ resetRun();
+});
+// --- Music ---
+LK.playMusic('bgmusic', {
+ fade: {
+ start: 0,
+ end: 1,
+ duration: 1000
+ }
+});
+// --- Start game ---
+resetRun();
\ No newline at end of file