/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BeatIndicator = Container.expand(function () {
var self = Container.call(this);
var indicatorGraphics = self.attachAsset('beatIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicatorGraphics.alpha = 0.3;
self.pulse = function () {
indicatorGraphics.alpha = 0.8;
tween(indicatorGraphics, {
alpha: 0.3,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
indicatorGraphics.scaleX = 1;
indicatorGraphics.scaleY = 1;
}
});
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 25;
self.directionX = 0;
self.directionY = -1;
self.enemiesKilled = 0;
self.maxKills = 2;
self.bounceCount = 0;
self.maxBounces = 3;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Store previous position for collision detection
if (self.lastX === undefined) self.lastX = self.x;
if (self.lastY === undefined) self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check wall collisions and bounce
if (self.x <= 10 && self.directionX < 0) {
self.directionX = -self.directionX; // Bounce off left wall
self.x = 10;
self.bounceCount++;
}
if (self.x >= 2038 && self.directionX > 0) {
self.directionX = -self.directionX; // Bounce off right wall
self.x = 2038;
self.bounceCount++;
}
if (self.y <= 10 && self.directionY < 0) {
self.directionY = -self.directionY; // Bounce off top wall
self.y = 10;
self.bounceCount++;
}
if (self.y >= 2722 && self.directionY > 0) {
self.directionY = -self.directionY; // Bounce off bottom wall
self.y = 2722;
self.bounceCount++;
}
// Check wall object collisions
for (var w = 0; w < walls.length; w++) {
var wall = walls[w];
if (self.intersects(wall)) {
// Damage the wall
wall.health--;
LK.effects.flashObject(wall, 0xff0000, 200);
// Calculate bounce direction based on wall orientation
var wallWidth = wall.width || 150;
var wallHeight = wall.height || 20;
// Determine which side of the wall was hit
var dx = self.x - wall.x;
var dy = self.y - wall.y;
// If wall is more horizontal (wider than tall)
if (wallWidth > wallHeight) {
// Bounce vertically
if (Math.abs(dy) > Math.abs(dx)) {
self.directionY = -self.directionY;
self.y = wall.y + (dy > 0 ? wallHeight / 2 + 10 : -wallHeight / 2 - 10);
}
} else {
// Bounce horizontally
if (Math.abs(dx) > Math.abs(dy)) {
self.directionX = -self.directionX;
self.x = wall.x + (dx > 0 ? wallWidth / 2 + 10 : -wallWidth / 2 - 10);
}
}
self.bounceCount++;
break;
}
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = self.x;
self.targetY = self.y;
self.moveSpeed = 0.1;
self.isMoving = false;
self.shootTimer = 0;
self.shootInterval = 120 + Math.random() * 180; // Random interval between 2-5 seconds
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
self.isMoving = true;
tween(self, {
x: x,
y: y
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isMoving = false;
}
});
};
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.shootTimer++;
if (self.shootTimer >= self.shootInterval) {
// Shoot at random location
var randomX = Math.random() * 2048;
var randomY = Math.random() * 2732;
// Create enemy bullet
var enemyBullet = game.addChild(new EnemyBullet());
var dx = randomX - self.x;
var dy = randomY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
enemyBullet.directionX = dx / distance;
enemyBullet.directionY = dy / distance;
}
enemyBullet.x = self.x;
enemyBullet.y = self.y;
enemyBullets.push(enemyBullet);
// Reset timer with new random interval
self.shootTimer = 0;
self.shootInterval = 120 + Math.random() * 180;
}
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGraphics.tint = 0xff5722; // Orange/red tint to differentiate from player bullets
self.speed = 15;
self.directionX = 0;
self.directionY = 1;
self.bounceCount = 0;
self.maxBounces = 3;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Store previous position for collision detection
if (self.lastX === undefined) self.lastX = self.x;
if (self.lastY === undefined) self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check wall collisions and bounce
if (self.x <= 10 && self.directionX < 0) {
self.directionX = -self.directionX; // Bounce off left wall
self.x = 10;
self.bounceCount++;
}
if (self.x >= 2038 && self.directionX > 0) {
self.directionX = -self.directionX; // Bounce off right wall
self.x = 2038;
self.bounceCount++;
}
if (self.y <= 10 && self.directionY < 0) {
self.directionY = -self.directionY; // Bounce off top wall
self.y = 10;
self.bounceCount++;
}
if (self.y >= 2722 && self.directionY > 0) {
self.directionY = -self.directionY; // Bounce off bottom wall
self.y = 2722;
self.bounceCount++;
}
// Check wall object collisions
for (var w = 0; w < walls.length; w++) {
var wall = walls[w];
if (self.intersects(wall)) {
// Damage the wall
wall.health--;
LK.effects.flashObject(wall, 0xff0000, 200);
// Calculate bounce direction based on wall orientation
var wallWidth = wall.width || 150;
var wallHeight = wall.height || 20;
// Determine which side of the wall was hit
var dx = self.x - wall.x;
var dy = self.y - wall.y;
// If wall is more horizontal (wider than tall)
if (wallWidth > wallHeight) {
// Bounce vertically
if (Math.abs(dy) > Math.abs(dx)) {
self.directionY = -self.directionY;
self.y = wall.y + (dy > 0 ? wallHeight / 2 + 10 : -wallHeight / 2 - 10);
}
} else {
// Bounce horizontally
if (Math.abs(dx) > Math.abs(dy)) {
self.directionX = -self.directionX;
self.x = wall.x + (dx > 0 ? wallWidth / 2 + 10 : -wallWidth / 2 - 10);
}
}
self.bounceCount++;
break;
}
}
// Remove if bounced too many times
if (self.bounceCount >= self.maxBounces) {
self.shouldDestroy = true;
}
// Remove if off-screen (fallback)
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
// Mark for removal
self.shouldDestroy = true;
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var GiantEnemy = Container.expand(function () {
var self = Container.call(this);
var giantGraphics = self.attachAsset('giantEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 5;
self.speed = 2;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Move toward player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
return self;
});
var Lightning = Container.expand(function () {
var self = Container.call(this);
var lightningGraphics = self.attachAsset('lightning', {
anchorX: 0.5,
anchorY: 0.5
});
self.duration = 30; // Lightning lasts 0.5 seconds
self.timer = 0;
self.speedY = 15; // Speed of downward movement
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer++;
self.y += self.speedY; // Move lightning downwards
// Flash effect
if (self.timer % 5 === 0) {
lightningGraphics.alpha = lightningGraphics.alpha === 1 ? 0.7 : 1;
}
// Mark for destruction if it moves off-screen (bottom)
if (self.y > 2732 + lightningGraphics.height / 2) {
self.shouldDestroy = true;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.canShoot = true;
self.shootCooldown = 0;
return self;
});
var PredictionCircle = Container.expand(function () {
var self = Container.call(this);
var circleGraphics = self.attachAsset('predictionCircle', {
anchorX: 0.5,
anchorY: 0.5
});
circleGraphics.alpha = 0.7;
self.show = function () {
// Make circle visible and animate it
circleGraphics.alpha = 0.7;
circleGraphics.scaleX = 0.5;
circleGraphics.scaleY = 0.5;
// Animate appearance
tween(circleGraphics, {
scaleX: 1,
scaleY: 1,
alpha: 0.9
}, {
duration: 200,
easing: tween.easeOut
});
// Auto-hide after 2 seconds
tween(circleGraphics, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
wallGraphics.alpha = 0.8;
self.duration = 1800; // Wall lasts 30 seconds at 60fps
self.timer = 0;
self.health = 15; // Wall can take 15 hits before breaking
self.maxHealth = 15;
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer++;
// Flash warning when about to expire (last 3 seconds)
if (self.timer >= self.duration - 180 && self.timer % 20 === 0) {
wallGraphics.alpha = wallGraphics.alpha === 0.8 ? 0.3 : 0.8;
}
// Flash red when damaged (health-based visual feedback)
if (self.health < self.maxHealth) {
var healthRatio = self.health / self.maxHealth;
wallGraphics.alpha = 0.5 + healthRatio * 0.3; // Fade as health decreases
if (self.health <= 5) {
// Flash red when critically damaged
if (self.timer % 30 === 0) {
wallGraphics.tint = wallGraphics.tint === 0xffffff ? 0xff4444 : 0xffffff;
}
}
}
// Mark for destruction when health reaches zero
if (self.health <= 0) {
self.shouldDestroy = true;
}
// Mark for destruction when timer expires
if (self.timer >= self.duration) {
self.shouldDestroy = true;
}
};
return self;
});
var Warning = Container.expand(function () {
var self = Container.call(this);
var warningGraphics = self.attachAsset('warning', {
anchorX: 0.5,
anchorY: 0.5
});
self.timer = 120; // 2 seconds warning time
self.flashTimer = 0;
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer--;
self.flashTimer++;
// Flash warning every 10 frames
if (self.flashTimer % 10 === 0) {
warningGraphics.alpha = warningGraphics.alpha === 1 ? 0.3 : 1;
}
// Mark for destruction when timer expires
if (self.timer <= 0) {
self.shouldSpawnLightning = true;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game variables
var player;
var enemies = [];
var bullets = [];
var enemyBullets = [];
var predictionCircles = [];
var beatIndicator;
var beatTimer = 0;
var beatInterval = 300; // 5 seconds at 60fps
var nextBeatTime = 0;
var canShootWindow = false;
var shootWindow = 60; // 1 second window after beat
var shootWindowTimer = 0;
var totalKills = 0;
var bulletsPerShot = 1;
var enemyPositions = [{
x: 400,
y: 300
}, {
x: 1648,
y: 300
}, {
x: 1024,
y: 200
}, {
x: 600,
y: 500
}, {
x: 1448,
y: 500
}];
var currentWave = 0;
var enemyPredictionTimer = 0;
var enemyPredictionInterval = 180; // 3 seconds at 60fps
var giantEnemies = [];
var giantEnemyTimer = 0;
var giantEnemyInterval = 3600; // 60 seconds at 60fps (1 minute)
var warnings = [];
var lightnings = [];
var warningSpawnTimer = 0;
var warningSpawnInterval = 180; // 3 seconds at 60fps
// UI elements
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var shootStatusTxt = new Text2('', {
size: 45,
fill: 0xFFFF00
});
shootStatusTxt.anchor.set(0.5, 0);
shootStatusTxt.y = 140;
LK.gui.top.addChild(shootStatusTxt);
var enemyPredictionTxt = new Text2('', {
size: 40,
fill: 0xff9800
});
enemyPredictionTxt.anchor.set(0.5, 0);
enemyPredictionTxt.y = 190;
LK.gui.top.addChild(enemyPredictionTxt);
var bossCountdownTxt = new Text2('Next Boss: 60.0s', {
size: 45,
fill: 0xff0000
});
bossCountdownTxt.anchor.set(0.5, 0);
bossCountdownTxt.y = 240;
LK.gui.top.addChild(bossCountdownTxt);
// Player health system
var playerHealth = 3;
var maxPlayerHealth = 3;
// Shooting cooldown system (2 seconds = 120 frames at 60fps)
var shootCooldown = 0;
var shootCooldownMax = 120;
var canShoot = true;
var playerHealthTxt = new Text2('Lives: 3/3', {
size: 45,
fill: 0x00ff00
});
playerHealthTxt.anchor.set(0.5, 0);
playerHealthTxt.y = 290;
LK.gui.top.addChild(playerHealthTxt);
var shootCooldownTxt = new Text2('Shoot: Ready', {
size: 45,
fill: 0x00ff00
});
shootCooldownTxt.anchor.set(0.5, 0);
shootCooldownTxt.y = 390;
LK.gui.top.addChild(shootCooldownTxt);
var movementRequiredTxt = new Text2('', {
size: 50,
fill: 0xffff00
});
movementRequiredTxt.anchor.set(0.5, 0);
movementRequiredTxt.y = 340;
LK.gui.top.addChild(movementRequiredTxt);
// Movement buttons
var leftButton = new Text2('◀', {
size: 80,
fill: 0xffffff
});
leftButton.anchor.set(0.5, 0.5);
leftButton.x = 200;
leftButton.y = 200;
LK.gui.bottomLeft.addChild(leftButton);
var rightButton = new Text2('▶', {
size: 80,
fill: 0xffffff
});
rightButton.anchor.set(0.5, 0.5);
rightButton.x = -200;
rightButton.y = 200;
LK.gui.bottomRight.addChild(rightButton);
var upButton = new Text2('▲', {
size: 80,
fill: 0xffffff
});
upButton.anchor.set(0.5, 0.5);
upButton.x = 0;
upButton.y = 300;
LK.gui.bottom.addChild(upButton);
var downButton = new Text2('▼', {
size: 80,
fill: 0xffffff
});
downButton.anchor.set(0.5, 0.5);
upButton.x = 0;
upButton.y = 100;
LK.gui.bottom.addChild(downButton);
// Controls instructions in corner
var controlsTxt = new Text2('Controls:\n◀▶ Move\n▲▼ Up/Down\nTouch: Shoot', {
size: 30,
fill: 0xffffff
});
controlsTxt.anchor.set(1, 1);
controlsTxt.x = -20;
controlsTxt.y = -20;
LK.gui.bottomRight.addChild(controlsTxt);
// Shooting instruction text in corner
var shootInstructionTxt = new Text2('Solo puedes disparar cuando\nescuches un chasquido de dedos', {
size: 25,
fill: 0xffff00
});
shootInstructionTxt.anchor.set(0, 1);
shootInstructionTxt.x = 20;
shootInstructionTxt.y = -20;
LK.gui.bottomLeft.addChild(shootInstructionTxt);
// Wall system
var walls = [];
var wallPlacementMode = false;
var wallCooldown = 300; // 5 seconds at 60fps
var wallCooldownTimer = 0;
var canPlaceWall = true;
var maxWalls = 3;
// Wall placement button
var wallBtn = new Text2('PLACE WALL', {
size: 70,
fill: 0x795548
});
wallBtn.anchor.set(1, 0);
wallBtn.x = -20;
wallBtn.y = 120;
LK.gui.topRight.addChild(wallBtn);
// Wall status text
var wallStatusTxt = new Text2('Walls: 0/3 - Ready', {
size: 40,
fill: 0x795548
});
wallStatusTxt.anchor.set(0.5, 0);
wallStatusTxt.y = 440;
LK.gui.top.addChild(wallStatusTxt);
// Initialize player
player = game.addChild(new Player());
player.x = 1024;
player.y = 2400;
player.speed = 8;
// Movement state
var moveLeft = false;
var moveRight = false;
var moveUp = false;
var moveDown = false;
// Player movement tracking for time system
var playerLastX = 0;
var playerLastY = 0;
var isPlayerMoving = false;
// Forced movement system - player must move every 10 seconds
var timeSinceLastMovement = 0;
var maxTimeWithoutMovement = 600; // 10 seconds at 60fps
var movementRequired = false;
// Initialize beat indicator
beatIndicator = game.addChild(new BeatIndicator());
beatIndicator.x = 1024;
beatIndicator.y = 1366;
// Initialize enemies
function spawnEnemies() {
// Clear existing enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
enemies.splice(i, 1);
}
// Clear any remaining prediction circles
for (var c = predictionCircles.length - 1; c >= 0; c--) {
predictionCircles[c].destroy();
predictionCircles.splice(c, 1);
}
// Spawn new enemies - base amount plus bonus based on total kills
var baseEnemies = 3 + Math.floor(currentWave / 2);
var bonusEnemies = Math.floor(totalKills / 5); // 1 extra enemy per 5 kills
var numEnemies = Math.min(baseEnemies + bonusEnemies, 5);
for (var i = 0; i < numEnemies; i++) {
var enemy = game.addChild(new Enemy());
var pos = enemyPositions[i % enemyPositions.length];
enemy.x = pos.x;
enemy.y = pos.y;
enemies.push(enemy);
}
}
function moveEnemies() {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var newPos = enemyPositions[(enemies.indexOf(enemy) + currentWave + 1) % enemyPositions.length];
enemy.setTarget(newPos.x, newPos.y);
}
}
function createBullet(targetX, targetY) {
// Update bullets per shot based on total kills
bulletsPerShot = 1 + Math.floor(totalKills / 3); // 1 extra bullet per 3 kills
bulletsPerShot = Math.min(bulletsPerShot, 5); // Cap at 5 bullets
// Create multiple bullets
for (var b = 0; b < bulletsPerShot; b++) {
var bullet = game.addChild(new Bullet());
// Calculate direction to target first
var dx = targetX - player.x;
var dy = targetY - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Add spread for multiple bullets
var spreadAngle = 0;
if (bulletsPerShot > 1) {
var maxSpread = Math.PI / 6; // 30 degrees total spread
spreadAngle = (b - (bulletsPerShot - 1) / 2) * (maxSpread / (bulletsPerShot - 1));
}
if (distance > 0) {
var baseDirectionX = dx / distance;
var baseDirectionY = dy / distance;
// Apply spread rotation
bullet.directionX = baseDirectionX * Math.cos(spreadAngle) - baseDirectionY * Math.sin(spreadAngle);
bullet.directionY = baseDirectionX * Math.sin(spreadAngle) + baseDirectionY * Math.cos(spreadAngle);
}
// Position bullet in front of player (offset by player size + bullet size to avoid collision)
var offsetDistance = 80; // Player size (70) + bullet height (40) + small buffer
bullet.x = player.x + bullet.directionX * offsetDistance;
bullet.y = player.y + bullet.directionY * offsetDistance;
bullets.push(bullet);
}
LK.getSound('shoot').play();
}
function damagePlayer() {
playerHealth--;
playerHealthTxt.setText('Lives: ' + playerHealth + '/' + maxPlayerHealth);
// Flash player red when damaged
LK.effects.flashObject(player, 0xff0000, 500);
// Update health text color based on remaining health
if (playerHealth <= 1) {
playerHealthTxt.fill = 0xff0000; // Red when critical
} else if (playerHealth <= 2) {
playerHealthTxt.fill = 0xffa500; // Orange when low
}
// Check if player is dead
if (playerHealth <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return true; // Player is dead
}
return false; // Player is still alive
}
// Touch/click handling
game.down = function (x, y, obj) {
// Convert to GUI coordinates to check button presses
var guiPos = LK.gui.toLocal({
x: x,
y: y
});
// Check wall placement button (top right area)
if (guiPos.x > LK.gui.width - 250 && guiPos.y < 180 && guiPos.y > 100) {
// Wall placement button area
if (canPlaceWall && walls.length < maxWalls) {
wallPlacementMode = true;
}
} else if (wallPlacementMode) {
// Place wall at touch position
var wall = game.addChild(new Wall());
wall.x = x;
wall.y = y;
walls.push(wall);
wallPlacementMode = false;
canPlaceWall = false;
wallCooldownTimer = wallCooldown;
} else if (guiPos.x < 400 && guiPos.y > LK.gui.height - 400) {
// Left button area
moveLeft = true;
} else if (guiPos.x > LK.gui.width - 400 && guiPos.y > LK.gui.height - 400) {
// Right button area
moveRight = true;
} else if (guiPos.x > LK.gui.width / 2 - 100 && guiPos.x < LK.gui.width / 2 + 100 && guiPos.y > LK.gui.height - 500 && guiPos.y < LK.gui.height - 300) {
// Up button area
moveUp = true;
} else if (guiPos.x > LK.gui.width / 2 - 100 && guiPos.x < LK.gui.width / 2 + 100 && guiPos.y > LK.gui.height - 200) {
// Down button area
moveDown = true;
} else {
// Check if player can shoot (2-second cooldown)
if (canShoot) {
createBullet(x, y);
// Start cooldown
canShoot = false;
shootCooldown = shootCooldownMax;
}
}
};
// Touch release handling
game.up = function (x, y, obj) {
// Stop all movement when touch is released
moveLeft = false;
moveRight = false;
moveUp = false;
moveDown = false;
};
// Initialize game
spawnEnemies();
nextBeatTime = beatInterval;
// Start background music
LK.playMusic('musicabg3');
// Main game update loop
game.update = function () {
// Check if player is actually moving (position changed)
isPlayerMoving = player.x !== playerLastX || player.y !== playerLastY;
playerLastX = player.x;
playerLastY = player.y;
// Update forced movement timer
if (isPlayerMoving) {
// Player moved, reset timer
timeSinceLastMovement = 0;
movementRequired = false;
} else {
// Player hasn't moved, increment timer
timeSinceLastMovement++;
}
// Check if movement is required
if (timeSinceLastMovement >= maxTimeWithoutMovement && !movementRequired) {
movementRequired = true;
// Flash screen to indicate movement is required
LK.effects.flashScreen(0xffff00, 500);
}
// If movement has been required for too long, damage player
if (movementRequired && timeSinceLastMovement >= maxTimeWithoutMovement + 60) {
// 1 second grace period
if (damagePlayer()) {
return; // Player died, exit update loop
}
// Reset timer to give player another chance
timeSinceLastMovement = maxTimeWithoutMovement;
}
// Only advance timers if player is moving
if (isPlayerMoving) {
beatTimer++;
giantEnemyTimer++;
warningSpawnTimer++;
}
// Warning spawn system
if (warningSpawnTimer >= warningSpawnInterval) {
// Spawn warning at random position
var warning = game.addChild(new Warning());
warning.x = 100 + Math.random() * 1848; // Keep within screen bounds
warning.y = 100 + Math.random() * 2532;
warnings.push(warning);
warningSpawnTimer = 0;
// Decrease interval slightly over time to increase difficulty
warningSpawnInterval = Math.max(120, warningSpawnInterval - 2);
}
// Giant enemy spawn system
if (giantEnemyTimer >= giantEnemyInterval) {
// Spawn giant enemy at random edge of screen
var giantEnemy = game.addChild(new GiantEnemy());
var side = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
if (side === 0) {
// top
giantEnemy.x = Math.random() * 2048;
giantEnemy.y = 0;
} else if (side === 1) {
// right
giantEnemy.x = 2048;
giantEnemy.y = Math.random() * 2732;
} else if (side === 2) {
// bottom
giantEnemy.x = Math.random() * 2048;
giantEnemy.y = 2732;
} else {
// left
giantEnemy.x = 0;
giantEnemy.y = Math.random() * 2732;
}
giantEnemies.push(giantEnemy);
giantEnemyTimer = 0;
}
// Beat system
if (beatTimer >= nextBeatTime) {
// Beat occurred
beatIndicator.pulse();
LK.getSound('beat').play();
// Move enemies
moveEnemies();
currentWave++;
// Enable shooting window
canShootWindow = true;
shootWindowTimer = 0;
// Reset beat timer
beatTimer = 0;
nextBeatTime = beatInterval;
}
// Shooting window management
if (canShootWindow) {
shootWindowTimer++;
if (shootWindowTimer >= shootWindow) {
canShootWindow = false;
}
}
// Update player movement
if (moveLeft && player.x > 35) {
player.x -= player.speed;
}
if (moveRight && player.x < 2048 - 35) {
player.x += player.speed;
}
if (moveUp && player.y > 35) {
player.y -= player.speed;
}
if (moveDown && player.y < 2732 - 35) {
player.y += player.speed;
}
// Update wall system
if (wallCooldownTimer > 0) {
wallCooldownTimer--;
if (wallCooldownTimer <= 0) {
canPlaceWall = true;
}
}
// Update wall button appearance
if (wallPlacementMode) {
wallBtn.setText('TAP TO PLACE');
wallBtn.fill = 0xffff00;
} else if (walls.length >= maxWalls) {
wallBtn.setText('MAX WALLS');
wallBtn.fill = 0xff0000;
} else if (wallCooldownTimer > 0) {
wallBtn.setText('WALL: ' + (wallCooldownTimer / 60.0).toFixed(1) + 's');
wallBtn.fill = 0xff0000;
} else {
wallBtn.setText('PLACE WALL');
wallBtn.fill = 0x795548;
}
// Update wall status
if (canPlaceWall && walls.length < maxWalls) {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - Ready');
wallStatusTxt.fill = 0x00ff00;
} else if (wallCooldownTimer > 0) {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - ' + (wallCooldownTimer / 60.0).toFixed(1) + 's');
wallStatusTxt.fill = 0xff0000;
} else {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - Full');
wallStatusTxt.fill = 0xff0000;
}
// Update walls
for (var w = walls.length - 1; w >= 0; w--) {
var wall = walls[w];
if (wall.shouldDestroy) {
wall.destroy();
walls.splice(w, 1);
}
}
// Update shooting cooldown
if (shootCooldown > 0) {
shootCooldown--;
if (shootCooldown <= 0) {
canShoot = true;
}
}
// Update UI
var timeToNextBeat = (nextBeatTime - beatTimer) / 60.0;
if (canShootWindow) {
var windowTimeLeft = (shootWindow - shootWindowTimer) / 60.0;
shootStatusTxt.setText('SHOOT NOW! (' + windowTimeLeft.toFixed(1) + 's)');
} else {
shootStatusTxt.setText('');
}
// Update shooting cooldown status text
if (canShoot) {
shootCooldownTxt.setText('Shoot: Ready');
shootCooldownTxt.fill = 0x00ff00;
} else {
var shootTimeLeft = shootCooldown / 60.0;
shootCooldownTxt.setText('Shoot: ' + shootTimeLeft.toFixed(1) + 's');
shootCooldownTxt.fill = 0xff0000;
}
// Update movement requirement status
if (movementRequired) {
var graceTimeLeft = (maxTimeWithoutMovement + 60 - timeSinceLastMovement) / 60.0;
movementRequiredTxt.setText('¡MUÉVETE AHORA! ' + graceTimeLeft.toFixed(1) + 's');
movementRequiredTxt.fill = 0xff0000;
} else if (timeSinceLastMovement > maxTimeWithoutMovement * 0.7) {
// Warning at 70% of time
var timeLeft = (maxTimeWithoutMovement - timeSinceLastMovement) / 60.0;
movementRequiredTxt.setText('Muévete en: ' + timeLeft.toFixed(1) + 's');
movementRequiredTxt.fill = 0xffa500;
} else {
movementRequiredTxt.setText('');
}
// Update boss countdown
var timeToNextBoss = (giantEnemyInterval - giantEnemyTimer) / 60.0;
bossCountdownTxt.setText('Next Boss: ' + timeToNextBoss.toFixed(1) + 's');
// Enemy movement prediction system
enemyPredictionTimer++;
if (enemyPredictionTimer >= enemyPredictionInterval) {
// Clear existing prediction circles
for (var c = predictionCircles.length - 1; c >= 0; c--) {
predictionCircles[c].destroy();
predictionCircles.splice(c, 1);
}
// Announce where enemies will move
var nextPositions = [];
for (var k = 0; k < enemies.length; k++) {
var enemy = enemies[k];
var nextPosIndex = (enemies.indexOf(enemy) + currentWave + 1) % enemyPositions.length;
var nextPos = enemyPositions[nextPosIndex];
nextPositions.push('(' + Math.floor(nextPos.x) + ',' + Math.floor(nextPos.y) + ')');
// Create prediction circle at the target position
var predictionCircle = game.addChild(new PredictionCircle());
predictionCircle.x = nextPos.x;
predictionCircle.y = nextPos.y;
predictionCircles.push(predictionCircle);
predictionCircle.show();
}
if (nextPositions.length > 0) {
enemyPredictionTxt.setText('Enemies moving to: ' + nextPositions.join(', '));
}
enemyPredictionTimer = 0;
}
// Update bullets (only if player is moving)
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Check if bullet has bounced too many times
if (bullet.bounceCount >= bullet.maxBounces) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check bullet-player collision (player loses if hit by own bullet)
if (bullet.intersects(player)) {
// Damage player and remove bullet
if (damagePlayer()) {
return; // Player died, exit update loop
}
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Remove off-screen check since bullets now bounce
// Check bullet-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (bullet.intersects(enemy)) {
// Hit!
totalKills++;
bullet.enemiesKilled++;
LK.setScore(LK.getScore() + 100);
scoreTxt.setText('Score: ' + LK.getScore() + ' | Kills: ' + totalKills + ' | Bullets: ' + bulletsPerShot);
LK.getSound('hit').play();
LK.effects.flashObject(enemy, 0xffffff, 200);
// Remove enemy
enemy.destroy();
enemies.splice(j, 1);
// Only remove bullet if it has killed maximum enemies
if (bullet.enemiesKilled >= bullet.maxKills) {
bullet.destroy();
bullets.splice(i, 1);
}
break;
}
}
// Check bullet-giant enemy collisions
for (var g = giantEnemies.length - 1; g >= 0; g--) {
var giantEnemy = giantEnemies[g];
if (bullet.intersects(giantEnemy)) {
// Hit giant enemy
giantEnemy.health--;
bullet.enemiesKilled++;
LK.setScore(LK.getScore() + 50);
scoreTxt.setText('Score: ' + LK.getScore() + ' | Kills: ' + totalKills + ' | Bullets: ' + bulletsPerShot);
LK.getSound('hit').play();
LK.effects.flashObject(giantEnemy, 0xffffff, 200);
// Check if giant enemy is defeated
if (giantEnemy.health <= 0) {
totalKills++;
giantEnemy.destroy();
giantEnemies.splice(g, 1);
}
// Only remove bullet if it has killed maximum enemies
if (bullet.enemiesKilled >= bullet.maxKills) {
bullet.destroy();
bullets.splice(i, 1);
}
break;
}
}
}
// Check giant enemy-player collisions
for (var g = 0; g < giantEnemies.length; g++) {
var giantEnemy = giantEnemies[g];
if (giantEnemy.intersects(player)) {
// Add collision tracking to prevent multiple hits per frame
if (giantEnemy.lastPlayerHit === undefined) giantEnemy.lastPlayerHit = false;
if (!giantEnemy.lastPlayerHit) {
giantEnemy.lastPlayerHit = true;
if (damagePlayer()) {
return; // Player died, exit update loop
}
}
} else {
giantEnemy.lastPlayerHit = false;
}
}
// Update enemy bullets (only if player is moving)
if (isPlayerMoving) {
for (var eb = enemyBullets.length - 1; eb >= 0; eb--) {
var enemyBullet = enemyBullets[eb];
// Remove off-screen enemy bullets
if (enemyBullet.shouldDestroy) {
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
continue;
}
// Check enemy bullet-player collision
if (enemyBullet.intersects(player)) {
if (damagePlayer()) {
return; // Player died, exit update loop
}
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
continue;
}
}
}
// Update warnings
for (var w = warnings.length - 1; w >= 0; w--) {
var warning = warnings[w];
if (warning.shouldSpawnLightning) {
// Create lightning at warning position
var lightning = game.addChild(new Lightning());
lightning.x = warning.x;
lightning.y = warning.y;
lightnings.push(lightning);
// Remove warning
warning.destroy();
warnings.splice(w, 1);
}
}
// Update lightnings
for (var l = lightnings.length - 1; l >= 0; l--) {
var lightning = lightnings[l];
// Check lightning-player collision
if (lightning.intersects(player)) {
// Lightning kills player instantly
if (damagePlayer()) {
return; // Player died, exit update loop
}
}
// Remove lightning when timer expires
if (lightning.shouldDestroy) {
lightning.destroy();
lightnings.splice(l, 1);
}
}
// Check win condition - player wins at 10,000 points
if (LK.getScore() >= 10000) {
LK.showYouWin();
}
// Spawn new enemies if all are destroyed
if (enemies.length === 0) {
spawnEnemies();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BeatIndicator = Container.expand(function () {
var self = Container.call(this);
var indicatorGraphics = self.attachAsset('beatIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicatorGraphics.alpha = 0.3;
self.pulse = function () {
indicatorGraphics.alpha = 0.8;
tween(indicatorGraphics, {
alpha: 0.3,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
indicatorGraphics.scaleX = 1;
indicatorGraphics.scaleY = 1;
}
});
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 25;
self.directionX = 0;
self.directionY = -1;
self.enemiesKilled = 0;
self.maxKills = 2;
self.bounceCount = 0;
self.maxBounces = 3;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Store previous position for collision detection
if (self.lastX === undefined) self.lastX = self.x;
if (self.lastY === undefined) self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check wall collisions and bounce
if (self.x <= 10 && self.directionX < 0) {
self.directionX = -self.directionX; // Bounce off left wall
self.x = 10;
self.bounceCount++;
}
if (self.x >= 2038 && self.directionX > 0) {
self.directionX = -self.directionX; // Bounce off right wall
self.x = 2038;
self.bounceCount++;
}
if (self.y <= 10 && self.directionY < 0) {
self.directionY = -self.directionY; // Bounce off top wall
self.y = 10;
self.bounceCount++;
}
if (self.y >= 2722 && self.directionY > 0) {
self.directionY = -self.directionY; // Bounce off bottom wall
self.y = 2722;
self.bounceCount++;
}
// Check wall object collisions
for (var w = 0; w < walls.length; w++) {
var wall = walls[w];
if (self.intersects(wall)) {
// Damage the wall
wall.health--;
LK.effects.flashObject(wall, 0xff0000, 200);
// Calculate bounce direction based on wall orientation
var wallWidth = wall.width || 150;
var wallHeight = wall.height || 20;
// Determine which side of the wall was hit
var dx = self.x - wall.x;
var dy = self.y - wall.y;
// If wall is more horizontal (wider than tall)
if (wallWidth > wallHeight) {
// Bounce vertically
if (Math.abs(dy) > Math.abs(dx)) {
self.directionY = -self.directionY;
self.y = wall.y + (dy > 0 ? wallHeight / 2 + 10 : -wallHeight / 2 - 10);
}
} else {
// Bounce horizontally
if (Math.abs(dx) > Math.abs(dy)) {
self.directionX = -self.directionX;
self.x = wall.x + (dx > 0 ? wallWidth / 2 + 10 : -wallWidth / 2 - 10);
}
}
self.bounceCount++;
break;
}
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = self.x;
self.targetY = self.y;
self.moveSpeed = 0.1;
self.isMoving = false;
self.shootTimer = 0;
self.shootInterval = 120 + Math.random() * 180; // Random interval between 2-5 seconds
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
self.isMoving = true;
tween(self, {
x: x,
y: y
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isMoving = false;
}
});
};
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.shootTimer++;
if (self.shootTimer >= self.shootInterval) {
// Shoot at random location
var randomX = Math.random() * 2048;
var randomY = Math.random() * 2732;
// Create enemy bullet
var enemyBullet = game.addChild(new EnemyBullet());
var dx = randomX - self.x;
var dy = randomY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
enemyBullet.directionX = dx / distance;
enemyBullet.directionY = dy / distance;
}
enemyBullet.x = self.x;
enemyBullet.y = self.y;
enemyBullets.push(enemyBullet);
// Reset timer with new random interval
self.shootTimer = 0;
self.shootInterval = 120 + Math.random() * 180;
}
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGraphics.tint = 0xff5722; // Orange/red tint to differentiate from player bullets
self.speed = 15;
self.directionX = 0;
self.directionY = 1;
self.bounceCount = 0;
self.maxBounces = 3;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Store previous position for collision detection
if (self.lastX === undefined) self.lastX = self.x;
if (self.lastY === undefined) self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check wall collisions and bounce
if (self.x <= 10 && self.directionX < 0) {
self.directionX = -self.directionX; // Bounce off left wall
self.x = 10;
self.bounceCount++;
}
if (self.x >= 2038 && self.directionX > 0) {
self.directionX = -self.directionX; // Bounce off right wall
self.x = 2038;
self.bounceCount++;
}
if (self.y <= 10 && self.directionY < 0) {
self.directionY = -self.directionY; // Bounce off top wall
self.y = 10;
self.bounceCount++;
}
if (self.y >= 2722 && self.directionY > 0) {
self.directionY = -self.directionY; // Bounce off bottom wall
self.y = 2722;
self.bounceCount++;
}
// Check wall object collisions
for (var w = 0; w < walls.length; w++) {
var wall = walls[w];
if (self.intersects(wall)) {
// Damage the wall
wall.health--;
LK.effects.flashObject(wall, 0xff0000, 200);
// Calculate bounce direction based on wall orientation
var wallWidth = wall.width || 150;
var wallHeight = wall.height || 20;
// Determine which side of the wall was hit
var dx = self.x - wall.x;
var dy = self.y - wall.y;
// If wall is more horizontal (wider than tall)
if (wallWidth > wallHeight) {
// Bounce vertically
if (Math.abs(dy) > Math.abs(dx)) {
self.directionY = -self.directionY;
self.y = wall.y + (dy > 0 ? wallHeight / 2 + 10 : -wallHeight / 2 - 10);
}
} else {
// Bounce horizontally
if (Math.abs(dx) > Math.abs(dy)) {
self.directionX = -self.directionX;
self.x = wall.x + (dx > 0 ? wallWidth / 2 + 10 : -wallWidth / 2 - 10);
}
}
self.bounceCount++;
break;
}
}
// Remove if bounced too many times
if (self.bounceCount >= self.maxBounces) {
self.shouldDestroy = true;
}
// Remove if off-screen (fallback)
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
// Mark for removal
self.shouldDestroy = true;
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var GiantEnemy = Container.expand(function () {
var self = Container.call(this);
var giantGraphics = self.attachAsset('giantEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 5;
self.speed = 2;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Move toward player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
return self;
});
var Lightning = Container.expand(function () {
var self = Container.call(this);
var lightningGraphics = self.attachAsset('lightning', {
anchorX: 0.5,
anchorY: 0.5
});
self.duration = 30; // Lightning lasts 0.5 seconds
self.timer = 0;
self.speedY = 15; // Speed of downward movement
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer++;
self.y += self.speedY; // Move lightning downwards
// Flash effect
if (self.timer % 5 === 0) {
lightningGraphics.alpha = lightningGraphics.alpha === 1 ? 0.7 : 1;
}
// Mark for destruction if it moves off-screen (bottom)
if (self.y > 2732 + lightningGraphics.height / 2) {
self.shouldDestroy = true;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.canShoot = true;
self.shootCooldown = 0;
return self;
});
var PredictionCircle = Container.expand(function () {
var self = Container.call(this);
var circleGraphics = self.attachAsset('predictionCircle', {
anchorX: 0.5,
anchorY: 0.5
});
circleGraphics.alpha = 0.7;
self.show = function () {
// Make circle visible and animate it
circleGraphics.alpha = 0.7;
circleGraphics.scaleX = 0.5;
circleGraphics.scaleY = 0.5;
// Animate appearance
tween(circleGraphics, {
scaleX: 1,
scaleY: 1,
alpha: 0.9
}, {
duration: 200,
easing: tween.easeOut
});
// Auto-hide after 2 seconds
tween(circleGraphics, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
wallGraphics.alpha = 0.8;
self.duration = 1800; // Wall lasts 30 seconds at 60fps
self.timer = 0;
self.health = 15; // Wall can take 15 hits before breaking
self.maxHealth = 15;
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer++;
// Flash warning when about to expire (last 3 seconds)
if (self.timer >= self.duration - 180 && self.timer % 20 === 0) {
wallGraphics.alpha = wallGraphics.alpha === 0.8 ? 0.3 : 0.8;
}
// Flash red when damaged (health-based visual feedback)
if (self.health < self.maxHealth) {
var healthRatio = self.health / self.maxHealth;
wallGraphics.alpha = 0.5 + healthRatio * 0.3; // Fade as health decreases
if (self.health <= 5) {
// Flash red when critically damaged
if (self.timer % 30 === 0) {
wallGraphics.tint = wallGraphics.tint === 0xffffff ? 0xff4444 : 0xffffff;
}
}
}
// Mark for destruction when health reaches zero
if (self.health <= 0) {
self.shouldDestroy = true;
}
// Mark for destruction when timer expires
if (self.timer >= self.duration) {
self.shouldDestroy = true;
}
};
return self;
});
var Warning = Container.expand(function () {
var self = Container.call(this);
var warningGraphics = self.attachAsset('warning', {
anchorX: 0.5,
anchorY: 0.5
});
self.timer = 120; // 2 seconds warning time
self.flashTimer = 0;
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer--;
self.flashTimer++;
// Flash warning every 10 frames
if (self.flashTimer % 10 === 0) {
warningGraphics.alpha = warningGraphics.alpha === 1 ? 0.3 : 1;
}
// Mark for destruction when timer expires
if (self.timer <= 0) {
self.shouldSpawnLightning = true;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game variables
var player;
var enemies = [];
var bullets = [];
var enemyBullets = [];
var predictionCircles = [];
var beatIndicator;
var beatTimer = 0;
var beatInterval = 300; // 5 seconds at 60fps
var nextBeatTime = 0;
var canShootWindow = false;
var shootWindow = 60; // 1 second window after beat
var shootWindowTimer = 0;
var totalKills = 0;
var bulletsPerShot = 1;
var enemyPositions = [{
x: 400,
y: 300
}, {
x: 1648,
y: 300
}, {
x: 1024,
y: 200
}, {
x: 600,
y: 500
}, {
x: 1448,
y: 500
}];
var currentWave = 0;
var enemyPredictionTimer = 0;
var enemyPredictionInterval = 180; // 3 seconds at 60fps
var giantEnemies = [];
var giantEnemyTimer = 0;
var giantEnemyInterval = 3600; // 60 seconds at 60fps (1 minute)
var warnings = [];
var lightnings = [];
var warningSpawnTimer = 0;
var warningSpawnInterval = 180; // 3 seconds at 60fps
// UI elements
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var shootStatusTxt = new Text2('', {
size: 45,
fill: 0xFFFF00
});
shootStatusTxt.anchor.set(0.5, 0);
shootStatusTxt.y = 140;
LK.gui.top.addChild(shootStatusTxt);
var enemyPredictionTxt = new Text2('', {
size: 40,
fill: 0xff9800
});
enemyPredictionTxt.anchor.set(0.5, 0);
enemyPredictionTxt.y = 190;
LK.gui.top.addChild(enemyPredictionTxt);
var bossCountdownTxt = new Text2('Next Boss: 60.0s', {
size: 45,
fill: 0xff0000
});
bossCountdownTxt.anchor.set(0.5, 0);
bossCountdownTxt.y = 240;
LK.gui.top.addChild(bossCountdownTxt);
// Player health system
var playerHealth = 3;
var maxPlayerHealth = 3;
// Shooting cooldown system (2 seconds = 120 frames at 60fps)
var shootCooldown = 0;
var shootCooldownMax = 120;
var canShoot = true;
var playerHealthTxt = new Text2('Lives: 3/3', {
size: 45,
fill: 0x00ff00
});
playerHealthTxt.anchor.set(0.5, 0);
playerHealthTxt.y = 290;
LK.gui.top.addChild(playerHealthTxt);
var shootCooldownTxt = new Text2('Shoot: Ready', {
size: 45,
fill: 0x00ff00
});
shootCooldownTxt.anchor.set(0.5, 0);
shootCooldownTxt.y = 390;
LK.gui.top.addChild(shootCooldownTxt);
var movementRequiredTxt = new Text2('', {
size: 50,
fill: 0xffff00
});
movementRequiredTxt.anchor.set(0.5, 0);
movementRequiredTxt.y = 340;
LK.gui.top.addChild(movementRequiredTxt);
// Movement buttons
var leftButton = new Text2('◀', {
size: 80,
fill: 0xffffff
});
leftButton.anchor.set(0.5, 0.5);
leftButton.x = 200;
leftButton.y = 200;
LK.gui.bottomLeft.addChild(leftButton);
var rightButton = new Text2('▶', {
size: 80,
fill: 0xffffff
});
rightButton.anchor.set(0.5, 0.5);
rightButton.x = -200;
rightButton.y = 200;
LK.gui.bottomRight.addChild(rightButton);
var upButton = new Text2('▲', {
size: 80,
fill: 0xffffff
});
upButton.anchor.set(0.5, 0.5);
upButton.x = 0;
upButton.y = 300;
LK.gui.bottom.addChild(upButton);
var downButton = new Text2('▼', {
size: 80,
fill: 0xffffff
});
downButton.anchor.set(0.5, 0.5);
upButton.x = 0;
upButton.y = 100;
LK.gui.bottom.addChild(downButton);
// Controls instructions in corner
var controlsTxt = new Text2('Controls:\n◀▶ Move\n▲▼ Up/Down\nTouch: Shoot', {
size: 30,
fill: 0xffffff
});
controlsTxt.anchor.set(1, 1);
controlsTxt.x = -20;
controlsTxt.y = -20;
LK.gui.bottomRight.addChild(controlsTxt);
// Shooting instruction text in corner
var shootInstructionTxt = new Text2('Solo puedes disparar cuando\nescuches un chasquido de dedos', {
size: 25,
fill: 0xffff00
});
shootInstructionTxt.anchor.set(0, 1);
shootInstructionTxt.x = 20;
shootInstructionTxt.y = -20;
LK.gui.bottomLeft.addChild(shootInstructionTxt);
// Wall system
var walls = [];
var wallPlacementMode = false;
var wallCooldown = 300; // 5 seconds at 60fps
var wallCooldownTimer = 0;
var canPlaceWall = true;
var maxWalls = 3;
// Wall placement button
var wallBtn = new Text2('PLACE WALL', {
size: 70,
fill: 0x795548
});
wallBtn.anchor.set(1, 0);
wallBtn.x = -20;
wallBtn.y = 120;
LK.gui.topRight.addChild(wallBtn);
// Wall status text
var wallStatusTxt = new Text2('Walls: 0/3 - Ready', {
size: 40,
fill: 0x795548
});
wallStatusTxt.anchor.set(0.5, 0);
wallStatusTxt.y = 440;
LK.gui.top.addChild(wallStatusTxt);
// Initialize player
player = game.addChild(new Player());
player.x = 1024;
player.y = 2400;
player.speed = 8;
// Movement state
var moveLeft = false;
var moveRight = false;
var moveUp = false;
var moveDown = false;
// Player movement tracking for time system
var playerLastX = 0;
var playerLastY = 0;
var isPlayerMoving = false;
// Forced movement system - player must move every 10 seconds
var timeSinceLastMovement = 0;
var maxTimeWithoutMovement = 600; // 10 seconds at 60fps
var movementRequired = false;
// Initialize beat indicator
beatIndicator = game.addChild(new BeatIndicator());
beatIndicator.x = 1024;
beatIndicator.y = 1366;
// Initialize enemies
function spawnEnemies() {
// Clear existing enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
enemies.splice(i, 1);
}
// Clear any remaining prediction circles
for (var c = predictionCircles.length - 1; c >= 0; c--) {
predictionCircles[c].destroy();
predictionCircles.splice(c, 1);
}
// Spawn new enemies - base amount plus bonus based on total kills
var baseEnemies = 3 + Math.floor(currentWave / 2);
var bonusEnemies = Math.floor(totalKills / 5); // 1 extra enemy per 5 kills
var numEnemies = Math.min(baseEnemies + bonusEnemies, 5);
for (var i = 0; i < numEnemies; i++) {
var enemy = game.addChild(new Enemy());
var pos = enemyPositions[i % enemyPositions.length];
enemy.x = pos.x;
enemy.y = pos.y;
enemies.push(enemy);
}
}
function moveEnemies() {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var newPos = enemyPositions[(enemies.indexOf(enemy) + currentWave + 1) % enemyPositions.length];
enemy.setTarget(newPos.x, newPos.y);
}
}
function createBullet(targetX, targetY) {
// Update bullets per shot based on total kills
bulletsPerShot = 1 + Math.floor(totalKills / 3); // 1 extra bullet per 3 kills
bulletsPerShot = Math.min(bulletsPerShot, 5); // Cap at 5 bullets
// Create multiple bullets
for (var b = 0; b < bulletsPerShot; b++) {
var bullet = game.addChild(new Bullet());
// Calculate direction to target first
var dx = targetX - player.x;
var dy = targetY - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Add spread for multiple bullets
var spreadAngle = 0;
if (bulletsPerShot > 1) {
var maxSpread = Math.PI / 6; // 30 degrees total spread
spreadAngle = (b - (bulletsPerShot - 1) / 2) * (maxSpread / (bulletsPerShot - 1));
}
if (distance > 0) {
var baseDirectionX = dx / distance;
var baseDirectionY = dy / distance;
// Apply spread rotation
bullet.directionX = baseDirectionX * Math.cos(spreadAngle) - baseDirectionY * Math.sin(spreadAngle);
bullet.directionY = baseDirectionX * Math.sin(spreadAngle) + baseDirectionY * Math.cos(spreadAngle);
}
// Position bullet in front of player (offset by player size + bullet size to avoid collision)
var offsetDistance = 80; // Player size (70) + bullet height (40) + small buffer
bullet.x = player.x + bullet.directionX * offsetDistance;
bullet.y = player.y + bullet.directionY * offsetDistance;
bullets.push(bullet);
}
LK.getSound('shoot').play();
}
function damagePlayer() {
playerHealth--;
playerHealthTxt.setText('Lives: ' + playerHealth + '/' + maxPlayerHealth);
// Flash player red when damaged
LK.effects.flashObject(player, 0xff0000, 500);
// Update health text color based on remaining health
if (playerHealth <= 1) {
playerHealthTxt.fill = 0xff0000; // Red when critical
} else if (playerHealth <= 2) {
playerHealthTxt.fill = 0xffa500; // Orange when low
}
// Check if player is dead
if (playerHealth <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return true; // Player is dead
}
return false; // Player is still alive
}
// Touch/click handling
game.down = function (x, y, obj) {
// Convert to GUI coordinates to check button presses
var guiPos = LK.gui.toLocal({
x: x,
y: y
});
// Check wall placement button (top right area)
if (guiPos.x > LK.gui.width - 250 && guiPos.y < 180 && guiPos.y > 100) {
// Wall placement button area
if (canPlaceWall && walls.length < maxWalls) {
wallPlacementMode = true;
}
} else if (wallPlacementMode) {
// Place wall at touch position
var wall = game.addChild(new Wall());
wall.x = x;
wall.y = y;
walls.push(wall);
wallPlacementMode = false;
canPlaceWall = false;
wallCooldownTimer = wallCooldown;
} else if (guiPos.x < 400 && guiPos.y > LK.gui.height - 400) {
// Left button area
moveLeft = true;
} else if (guiPos.x > LK.gui.width - 400 && guiPos.y > LK.gui.height - 400) {
// Right button area
moveRight = true;
} else if (guiPos.x > LK.gui.width / 2 - 100 && guiPos.x < LK.gui.width / 2 + 100 && guiPos.y > LK.gui.height - 500 && guiPos.y < LK.gui.height - 300) {
// Up button area
moveUp = true;
} else if (guiPos.x > LK.gui.width / 2 - 100 && guiPos.x < LK.gui.width / 2 + 100 && guiPos.y > LK.gui.height - 200) {
// Down button area
moveDown = true;
} else {
// Check if player can shoot (2-second cooldown)
if (canShoot) {
createBullet(x, y);
// Start cooldown
canShoot = false;
shootCooldown = shootCooldownMax;
}
}
};
// Touch release handling
game.up = function (x, y, obj) {
// Stop all movement when touch is released
moveLeft = false;
moveRight = false;
moveUp = false;
moveDown = false;
};
// Initialize game
spawnEnemies();
nextBeatTime = beatInterval;
// Start background music
LK.playMusic('musicabg3');
// Main game update loop
game.update = function () {
// Check if player is actually moving (position changed)
isPlayerMoving = player.x !== playerLastX || player.y !== playerLastY;
playerLastX = player.x;
playerLastY = player.y;
// Update forced movement timer
if (isPlayerMoving) {
// Player moved, reset timer
timeSinceLastMovement = 0;
movementRequired = false;
} else {
// Player hasn't moved, increment timer
timeSinceLastMovement++;
}
// Check if movement is required
if (timeSinceLastMovement >= maxTimeWithoutMovement && !movementRequired) {
movementRequired = true;
// Flash screen to indicate movement is required
LK.effects.flashScreen(0xffff00, 500);
}
// If movement has been required for too long, damage player
if (movementRequired && timeSinceLastMovement >= maxTimeWithoutMovement + 60) {
// 1 second grace period
if (damagePlayer()) {
return; // Player died, exit update loop
}
// Reset timer to give player another chance
timeSinceLastMovement = maxTimeWithoutMovement;
}
// Only advance timers if player is moving
if (isPlayerMoving) {
beatTimer++;
giantEnemyTimer++;
warningSpawnTimer++;
}
// Warning spawn system
if (warningSpawnTimer >= warningSpawnInterval) {
// Spawn warning at random position
var warning = game.addChild(new Warning());
warning.x = 100 + Math.random() * 1848; // Keep within screen bounds
warning.y = 100 + Math.random() * 2532;
warnings.push(warning);
warningSpawnTimer = 0;
// Decrease interval slightly over time to increase difficulty
warningSpawnInterval = Math.max(120, warningSpawnInterval - 2);
}
// Giant enemy spawn system
if (giantEnemyTimer >= giantEnemyInterval) {
// Spawn giant enemy at random edge of screen
var giantEnemy = game.addChild(new GiantEnemy());
var side = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
if (side === 0) {
// top
giantEnemy.x = Math.random() * 2048;
giantEnemy.y = 0;
} else if (side === 1) {
// right
giantEnemy.x = 2048;
giantEnemy.y = Math.random() * 2732;
} else if (side === 2) {
// bottom
giantEnemy.x = Math.random() * 2048;
giantEnemy.y = 2732;
} else {
// left
giantEnemy.x = 0;
giantEnemy.y = Math.random() * 2732;
}
giantEnemies.push(giantEnemy);
giantEnemyTimer = 0;
}
// Beat system
if (beatTimer >= nextBeatTime) {
// Beat occurred
beatIndicator.pulse();
LK.getSound('beat').play();
// Move enemies
moveEnemies();
currentWave++;
// Enable shooting window
canShootWindow = true;
shootWindowTimer = 0;
// Reset beat timer
beatTimer = 0;
nextBeatTime = beatInterval;
}
// Shooting window management
if (canShootWindow) {
shootWindowTimer++;
if (shootWindowTimer >= shootWindow) {
canShootWindow = false;
}
}
// Update player movement
if (moveLeft && player.x > 35) {
player.x -= player.speed;
}
if (moveRight && player.x < 2048 - 35) {
player.x += player.speed;
}
if (moveUp && player.y > 35) {
player.y -= player.speed;
}
if (moveDown && player.y < 2732 - 35) {
player.y += player.speed;
}
// Update wall system
if (wallCooldownTimer > 0) {
wallCooldownTimer--;
if (wallCooldownTimer <= 0) {
canPlaceWall = true;
}
}
// Update wall button appearance
if (wallPlacementMode) {
wallBtn.setText('TAP TO PLACE');
wallBtn.fill = 0xffff00;
} else if (walls.length >= maxWalls) {
wallBtn.setText('MAX WALLS');
wallBtn.fill = 0xff0000;
} else if (wallCooldownTimer > 0) {
wallBtn.setText('WALL: ' + (wallCooldownTimer / 60.0).toFixed(1) + 's');
wallBtn.fill = 0xff0000;
} else {
wallBtn.setText('PLACE WALL');
wallBtn.fill = 0x795548;
}
// Update wall status
if (canPlaceWall && walls.length < maxWalls) {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - Ready');
wallStatusTxt.fill = 0x00ff00;
} else if (wallCooldownTimer > 0) {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - ' + (wallCooldownTimer / 60.0).toFixed(1) + 's');
wallStatusTxt.fill = 0xff0000;
} else {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - Full');
wallStatusTxt.fill = 0xff0000;
}
// Update walls
for (var w = walls.length - 1; w >= 0; w--) {
var wall = walls[w];
if (wall.shouldDestroy) {
wall.destroy();
walls.splice(w, 1);
}
}
// Update shooting cooldown
if (shootCooldown > 0) {
shootCooldown--;
if (shootCooldown <= 0) {
canShoot = true;
}
}
// Update UI
var timeToNextBeat = (nextBeatTime - beatTimer) / 60.0;
if (canShootWindow) {
var windowTimeLeft = (shootWindow - shootWindowTimer) / 60.0;
shootStatusTxt.setText('SHOOT NOW! (' + windowTimeLeft.toFixed(1) + 's)');
} else {
shootStatusTxt.setText('');
}
// Update shooting cooldown status text
if (canShoot) {
shootCooldownTxt.setText('Shoot: Ready');
shootCooldownTxt.fill = 0x00ff00;
} else {
var shootTimeLeft = shootCooldown / 60.0;
shootCooldownTxt.setText('Shoot: ' + shootTimeLeft.toFixed(1) + 's');
shootCooldownTxt.fill = 0xff0000;
}
// Update movement requirement status
if (movementRequired) {
var graceTimeLeft = (maxTimeWithoutMovement + 60 - timeSinceLastMovement) / 60.0;
movementRequiredTxt.setText('¡MUÉVETE AHORA! ' + graceTimeLeft.toFixed(1) + 's');
movementRequiredTxt.fill = 0xff0000;
} else if (timeSinceLastMovement > maxTimeWithoutMovement * 0.7) {
// Warning at 70% of time
var timeLeft = (maxTimeWithoutMovement - timeSinceLastMovement) / 60.0;
movementRequiredTxt.setText('Muévete en: ' + timeLeft.toFixed(1) + 's');
movementRequiredTxt.fill = 0xffa500;
} else {
movementRequiredTxt.setText('');
}
// Update boss countdown
var timeToNextBoss = (giantEnemyInterval - giantEnemyTimer) / 60.0;
bossCountdownTxt.setText('Next Boss: ' + timeToNextBoss.toFixed(1) + 's');
// Enemy movement prediction system
enemyPredictionTimer++;
if (enemyPredictionTimer >= enemyPredictionInterval) {
// Clear existing prediction circles
for (var c = predictionCircles.length - 1; c >= 0; c--) {
predictionCircles[c].destroy();
predictionCircles.splice(c, 1);
}
// Announce where enemies will move
var nextPositions = [];
for (var k = 0; k < enemies.length; k++) {
var enemy = enemies[k];
var nextPosIndex = (enemies.indexOf(enemy) + currentWave + 1) % enemyPositions.length;
var nextPos = enemyPositions[nextPosIndex];
nextPositions.push('(' + Math.floor(nextPos.x) + ',' + Math.floor(nextPos.y) + ')');
// Create prediction circle at the target position
var predictionCircle = game.addChild(new PredictionCircle());
predictionCircle.x = nextPos.x;
predictionCircle.y = nextPos.y;
predictionCircles.push(predictionCircle);
predictionCircle.show();
}
if (nextPositions.length > 0) {
enemyPredictionTxt.setText('Enemies moving to: ' + nextPositions.join(', '));
}
enemyPredictionTimer = 0;
}
// Update bullets (only if player is moving)
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Check if bullet has bounced too many times
if (bullet.bounceCount >= bullet.maxBounces) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check bullet-player collision (player loses if hit by own bullet)
if (bullet.intersects(player)) {
// Damage player and remove bullet
if (damagePlayer()) {
return; // Player died, exit update loop
}
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Remove off-screen check since bullets now bounce
// Check bullet-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (bullet.intersects(enemy)) {
// Hit!
totalKills++;
bullet.enemiesKilled++;
LK.setScore(LK.getScore() + 100);
scoreTxt.setText('Score: ' + LK.getScore() + ' | Kills: ' + totalKills + ' | Bullets: ' + bulletsPerShot);
LK.getSound('hit').play();
LK.effects.flashObject(enemy, 0xffffff, 200);
// Remove enemy
enemy.destroy();
enemies.splice(j, 1);
// Only remove bullet if it has killed maximum enemies
if (bullet.enemiesKilled >= bullet.maxKills) {
bullet.destroy();
bullets.splice(i, 1);
}
break;
}
}
// Check bullet-giant enemy collisions
for (var g = giantEnemies.length - 1; g >= 0; g--) {
var giantEnemy = giantEnemies[g];
if (bullet.intersects(giantEnemy)) {
// Hit giant enemy
giantEnemy.health--;
bullet.enemiesKilled++;
LK.setScore(LK.getScore() + 50);
scoreTxt.setText('Score: ' + LK.getScore() + ' | Kills: ' + totalKills + ' | Bullets: ' + bulletsPerShot);
LK.getSound('hit').play();
LK.effects.flashObject(giantEnemy, 0xffffff, 200);
// Check if giant enemy is defeated
if (giantEnemy.health <= 0) {
totalKills++;
giantEnemy.destroy();
giantEnemies.splice(g, 1);
}
// Only remove bullet if it has killed maximum enemies
if (bullet.enemiesKilled >= bullet.maxKills) {
bullet.destroy();
bullets.splice(i, 1);
}
break;
}
}
}
// Check giant enemy-player collisions
for (var g = 0; g < giantEnemies.length; g++) {
var giantEnemy = giantEnemies[g];
if (giantEnemy.intersects(player)) {
// Add collision tracking to prevent multiple hits per frame
if (giantEnemy.lastPlayerHit === undefined) giantEnemy.lastPlayerHit = false;
if (!giantEnemy.lastPlayerHit) {
giantEnemy.lastPlayerHit = true;
if (damagePlayer()) {
return; // Player died, exit update loop
}
}
} else {
giantEnemy.lastPlayerHit = false;
}
}
// Update enemy bullets (only if player is moving)
if (isPlayerMoving) {
for (var eb = enemyBullets.length - 1; eb >= 0; eb--) {
var enemyBullet = enemyBullets[eb];
// Remove off-screen enemy bullets
if (enemyBullet.shouldDestroy) {
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
continue;
}
// Check enemy bullet-player collision
if (enemyBullet.intersects(player)) {
if (damagePlayer()) {
return; // Player died, exit update loop
}
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
continue;
}
}
}
// Update warnings
for (var w = warnings.length - 1; w >= 0; w--) {
var warning = warnings[w];
if (warning.shouldSpawnLightning) {
// Create lightning at warning position
var lightning = game.addChild(new Lightning());
lightning.x = warning.x;
lightning.y = warning.y;
lightnings.push(lightning);
// Remove warning
warning.destroy();
warnings.splice(w, 1);
}
}
// Update lightnings
for (var l = lightnings.length - 1; l >= 0; l--) {
var lightning = lightnings[l];
// Check lightning-player collision
if (lightning.intersects(player)) {
// Lightning kills player instantly
if (damagePlayer()) {
return; // Player died, exit update loop
}
}
// Remove lightning when timer expires
if (lightning.shouldDestroy) {
lightning.destroy();
lightnings.splice(l, 1);
}
}
// Check win condition - player wins at 10,000 points
if (LK.getScore() >= 10000) {
LK.showYouWin();
}
// Spawn new enemies if all are destroyed
if (enemies.length === 0) {
spawnEnemies();
}
};