/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speedX = 0; self.speedY = 0; self.speed = 8; self.update = function () { // Store previous position for wall collision detection var prevX = self.x; var prevY = self.y; self.x += self.speedX; self.y += self.speedY; // Check if bullet hit a wall var mazeX = Math.floor((self.x - MAZE_START_X) / CELL_SIZE); var mazeY = Math.floor((self.y - MAZE_START_Y) / CELL_SIZE); // If bullet is inside maze bounds and hits a wall, destroy it if (mazeX >= 0 && mazeX < MAZE_WIDTH && mazeY >= 0 && mazeY < MAZE_HEIGHT) { if (maze[mazeY][mazeX] === 1) { self.destroy(); return; } } // Remove bullet if it goes off screen if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) { self.destroy(); } }; self.setDirection = function (dirX, dirY) { var length = Math.sqrt(dirX * dirX + dirY * dirY); if (length > 0) { self.speedX = dirX / length * self.speed; self.speedY = dirY / length * self.speed; } }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.targetX = 0; self.targetY = 0; self.moveTimer = 0; self.moveDelay = 60; // Change direction every 60 frames self.directions = [{ x: 0, y: -1 }, // up { x: 1, y: 0 }, // right { x: 0, y: 1 }, // down { x: -1, y: 0 } // left ]; self.update = function () { // Only update every 3 frames to reduce computation if (LK.ticks % 3 !== 0) return; // AI movement - change direction periodically self.moveTimer++; if (self.moveTimer >= self.moveDelay) { self.moveTimer = 0; self.chooseNewDirection(); } var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 2) { // Simplified movement - don't check walls every frame self.x += dx > 0 ? self.speed : dx < 0 ? -self.speed : 0; self.y += dy > 0 ? self.speed : dy < 0 ? -self.speed : 0; } else { // Reached target, choose new direction self.chooseNewDirection(); } }; self.chooseNewDirection = function () { var currentMazeX = Math.floor((self.x - MAZE_START_X) / CELL_SIZE); var currentMazeY = Math.floor((self.y - MAZE_START_Y) / CELL_SIZE); var validDirections = []; // Check each direction for valid movement for (var i = 0; i < self.directions.length; i++) { var dir = self.directions[i]; var newMazeX = currentMazeX + dir.x; var newMazeY = currentMazeY + dir.y; if (newMazeX >= 0 && newMazeX < MAZE_WIDTH && newMazeY >= 0 && newMazeY < MAZE_HEIGHT) { if (maze[newMazeY][newMazeX] === 0) { validDirections.push(dir); } } } if (validDirections.length > 0) { var chosenDir = validDirections[Math.floor(Math.random() * validDirections.length)]; var targetMazeX = currentMazeX + chosenDir.x; var targetMazeY = currentMazeY + chosenDir.y; self.targetX = MAZE_START_X + targetMazeX * CELL_SIZE + CELL_SIZE / 2; self.targetY = MAZE_START_Y + targetMazeY * CELL_SIZE + CELL_SIZE / 2; } }; return self; }); var GoldenBall = Container.expand(function () { var self = Container.call(this); var roseGraphics = self.attachAsset('goldenBall', { anchorX: 0.5, anchorY: 0.5 }); self.collected = false; self.update = function () { // Initialize floating animation on first update if (!self.floatingInitialized && !self.collected) { self.floatingInitialized = true; self.baseY = self.y; // Start vertical floating animation tween(roseGraphics, { y: -20 }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { // Float back down tween(roseGraphics, { y: 20 }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { // Reset and repeat roseGraphics.y = 0; self.floatingInitialized = false; } }); } }); } }; self.collect = function () { if (!self.collected) { self.collected = true; tween(roseGraphics, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { self.visible = false; } }); } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 4; self.targetX = 0; self.targetY = 0; self.facingRight = true; // Track which direction player is facing self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 2) { // Update facing direction based on horizontal movement if (Math.abs(dx) > 1) { // Only change facing if significant horizontal movement if (dx > 0 && !self.facingRight) { // Moving right, currently facing left self.facingRight = true; tween(playerGraphics, { scaleX: 1 }, { duration: 200, easing: tween.easeOut }); } else if (dx < 0 && self.facingRight) { // Moving left, currently facing right self.facingRight = false; tween(playerGraphics, { scaleX: -1 }, { duration: 200, easing: tween.easeOut }); } } // Calculate next position var nextX = self.x + dx / distance * self.speed; var nextY = self.y + dy / distance * self.speed; // Check if next position is valid (not a wall) var mazeX = Math.floor((nextX - MAZE_START_X) / CELL_SIZE); var mazeY = Math.floor((nextY - MAZE_START_Y) / CELL_SIZE); // Check if next position is within maze bounds if (mazeX >= 0 && mazeX < MAZE_WIDTH && mazeY >= 0 && mazeY < MAZE_HEIGHT) { // Inside maze - only move if not a wall if (maze[mazeY][mazeX] === 0) { self.x = nextX; self.y = nextY; } } else { // Outside maze bounds - allow free movement anywhere on screen if (nextX >= 0 && nextX <= 2048 && nextY >= 0 && nextY <= 2732) { self.x = nextX; self.y = nextY; } } } }; self.setTarget = function (x, y) { self.targetX = x; self.targetY = y; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a1a1a }); /**** * Game Code ****/ // Set background color explicitly game.setBackgroundColor(0x000000); // Add background image var background = game.addChild(LK.getAsset('background', { x: 0, y: 0, width: 2048, height: 2732, alpha: 1.0, tint: 0xffffff })); // Send background to the back so other elements appear on top game.setChildIndex(background, 0); // Make background more sharp and visible background.alpha = 1.0; background.scale.set(1.0, 1.0); var CELL_SIZE = 48; var MAZE_WIDTH = 16; var MAZE_HEIGHT = 20; var MAZE_START_X = (2048 - MAZE_WIDTH * CELL_SIZE) / 2; var MAZE_START_Y = (2732 - MAZE_HEIGHT * CELL_SIZE) / 2; var maze = []; var walls = []; var floors = []; var player; var goldenBalls = []; var totalBalls = 0; var collectedBalls = 0; var bullets = []; var enemies = []; var enemyCount = 3; var swipeStart = { x: 0, y: 0 }; var isSwipping = false; // Tap controls - no drag node needed // Score display var scoreText = new Text2('Roses: 0/0', { size: 50, fill: 0xFFD700 }); scoreText.anchor.set(1, 0); LK.gui.topRight.addChild(scoreText); scoreText.x = -20; scoreText.y = 80; function generateMaze() { // Initialize maze with walls for (var y = 0; y < MAZE_HEIGHT; y++) { maze[y] = []; for (var x = 0; x < MAZE_WIDTH; x++) { maze[y][x] = 1; // 1 = wall, 0 = path } } // Generate maze without dead ends using modified algorithm var startX = 1; var startY = 1; maze[startY][startX] = 0; var directions = [{ x: 0, y: -2 }, // up { x: 2, y: 0 }, // right { x: 0, y: 2 }, // down { x: -2, y: 0 } // left ]; // First pass: create main paths using recursive backtracking var stack = []; stack.push({ x: startX, y: startY }); while (stack.length > 0) { var current = stack[stack.length - 1]; var neighbors = []; for (var i = 0; i < directions.length; i++) { var nx = current.x + directions[i].x; var ny = current.y + directions[i].y; if (nx > 0 && nx < MAZE_WIDTH - 1 && ny > 0 && ny < MAZE_HEIGHT - 1 && maze[ny][nx] === 1) { neighbors.push({ x: nx, y: ny, dir: directions[i] }); } } if (neighbors.length > 0) { var next = neighbors[Math.floor(Math.random() * neighbors.length)]; maze[next.y][next.x] = 0; maze[current.y + next.dir.y / 2][current.x + next.dir.x / 2] = 0; stack.push({ x: next.x, y: next.y }); } else { stack.pop(); } } // Second pass: add loops to eliminate dead ends for (var attempts = 0; attempts < 20; attempts++) { // Find all dead ends (cells with only one neighbor) var deadEnds = []; for (var y = 1; y < MAZE_HEIGHT - 1; y += 2) { for (var x = 1; x < MAZE_WIDTH - 1; x += 2) { if (maze[y][x] === 0) { var pathCount = 0; var possibleConnections = []; // Check all four directions if (y > 1 && maze[y - 1][x] === 0) pathCount++; if (y < MAZE_HEIGHT - 2 && maze[y + 1][x] === 0) pathCount++; if (x > 1 && maze[y][x - 1] === 0) pathCount++; if (x < MAZE_WIDTH - 2 && maze[y][x + 1] === 0) pathCount++; // If it's a dead end, try to connect it to another path if (pathCount === 1) { // Try to connect to nearby paths for (var i = 0; i < directions.length; i++) { var newX = x + directions[i].x; var newY = y + directions[i].y; if (newX > 0 && newX < MAZE_WIDTH - 1 && newY > 0 && newY < MAZE_HEIGHT - 1) { if (maze[newY][newX] === 0) { var wallX = x + directions[i].x / 2; var wallY = y + directions[i].y / 2; if (maze[wallY][wallX] === 1) { possibleConnections.push({ wallX: wallX, wallY: wallY }); } } } } if (possibleConnections.length > 0) { var connection = possibleConnections[Math.floor(Math.random() * possibleConnections.length)]; maze[connection.wallY][connection.wallX] = 0; } } } } } } // Create entrance var entranceX = Math.floor(Math.random() * (MAZE_WIDTH - 2)) + 1; maze[0][entranceX] = 0; return { x: entranceX, y: 0 }; } function createMazeVisuals() { for (var y = 0; y < MAZE_HEIGHT; y++) { for (var x = 0; x < MAZE_WIDTH; x++) { var cellX = MAZE_START_X + x * CELL_SIZE; var cellY = MAZE_START_Y + y * CELL_SIZE; if (maze[y][x] === 1) { var wall = game.addChild(LK.getAsset('wall', { x: cellX, y: cellY })); walls.push(wall); } else { var floor = game.addChild(LK.getAsset('floor', { x: cellX, y: cellY })); floors.push(floor); } } } } function spawnEnemies() { var spawnedEnemies = 0; var attempts = 0; var maxAttempts = 100; while (spawnedEnemies < enemyCount && attempts < maxAttempts) { var x = Math.floor(Math.random() * MAZE_WIDTH); var y = Math.floor(Math.random() * MAZE_HEIGHT); // Make sure enemy spawns in a path cell and not too close to player start if (maze[y][x] === 0 && (Math.abs(x - entrance.x) > 3 || Math.abs(y - entrance.y) > 3)) { var cellX = MAZE_START_X + x * CELL_SIZE + CELL_SIZE / 2; var cellY = MAZE_START_Y + y * CELL_SIZE + CELL_SIZE / 2; var enemy = game.addChild(new Enemy()); enemy.x = cellX; enemy.y = cellY; enemy.targetX = cellX; enemy.targetY = cellY; enemy.mazeX = x; enemy.mazeY = y; enemies.push(enemy); spawnedEnemies++; } attempts++; } } function placeGoldenRoses() { var roseCount = Math.floor(MAZE_WIDTH * MAZE_HEIGHT * 0.02); // 2% of maze cells var placedRoses = 0; while (placedRoses < roseCount) { var x = Math.floor(Math.random() * MAZE_WIDTH); var y = Math.floor(Math.random() * MAZE_HEIGHT); if (maze[y][x] === 0) { var cellX = MAZE_START_X + x * CELL_SIZE + CELL_SIZE / 2; var cellY = MAZE_START_Y + y * CELL_SIZE + CELL_SIZE / 2; var rose = game.addChild(new GoldenBall()); rose.x = cellX; rose.y = cellY; rose.mazeX = x; rose.mazeY = y; goldenBalls.push(rose); placedRoses++; } } totalBalls = goldenBalls.length; updateScoreText(); } function updateScoreText() { scoreText.setText('Roses: ' + collectedBalls + '/' + totalBalls); } function isValidPosition(x, y) { var mazeX = Math.floor((x - MAZE_START_X) / CELL_SIZE); var mazeY = Math.floor((y - MAZE_START_Y) / CELL_SIZE); if (mazeX < 0 || mazeX >= MAZE_WIDTH || mazeY < 0 || mazeY >= MAZE_HEIGHT) { return false; } return maze[mazeY][mazeX] === 0; } function getValidTargetPosition(targetX, targetY) { var currentMazeX = Math.floor((player.x - MAZE_START_X) / CELL_SIZE); var currentMazeY = Math.floor((player.y - MAZE_START_Y) / CELL_SIZE); var targetMazeX = Math.floor((targetX - MAZE_START_X) / CELL_SIZE); var targetMazeY = Math.floor((targetY - MAZE_START_Y) / CELL_SIZE); if (targetMazeX < 0 || targetMazeX >= MAZE_WIDTH || targetMazeY < 0 || targetMazeY >= MAZE_HEIGHT) { return { x: player.x, y: player.y }; } if (maze[targetMazeY][targetMazeX] === 1) { return { x: player.x, y: player.y }; } var cellCenterX = MAZE_START_X + targetMazeX * CELL_SIZE + CELL_SIZE / 2; var cellCenterY = MAZE_START_Y + targetMazeY * CELL_SIZE + CELL_SIZE / 2; return { x: cellCenterX, y: cellCenterY }; } // Initialize maze var entrance = generateMaze(); createMazeVisuals(); // Create player player = game.addChild(new Player()); player.x = MAZE_START_X + entrance.x * CELL_SIZE + CELL_SIZE / 2; player.y = MAZE_START_Y + entrance.y * CELL_SIZE + CELL_SIZE / 2; player.setTarget(player.x, player.y); // Place golden roses placeGoldenRoses(); // Spawn enemies spawnEnemies(); // Touch controls - swipe to shoot, tap to move game.down = function (x, y, obj) { swipeStart.x = x; swipeStart.y = y; isSwipping = true; }; game.move = function (x, y, obj) { // Track swipe movement but don't move player during swipe }; game.up = function (x, y, obj) { if (isSwipping) { var swipeEndX = x; var swipeEndY = y; var swipeDistanceX = swipeEndX - swipeStart.x; var swipeDistanceY = swipeEndY - swipeStart.y; var swipeDistance = Math.sqrt(swipeDistanceX * swipeDistanceX + swipeDistanceY * swipeDistanceY); if (swipeDistance > 30) { // Minimum swipe distance // Create bullet and shoot in swipe direction var bullet = game.addChild(new Bullet()); bullet.x = player.x; bullet.y = player.y; bullet.setDirection(swipeDistanceX, swipeDistanceY); bullets.push(bullet); LK.getSound('shoot').play(); } else { // Short tap - move player to tap position (no maze validation) player.setTarget(x, y); } isSwipping = false; } }; // Game update loop game.update = function () { // Clean up destroyed bullets for (var b = bullets.length - 1; b >= 0; b--) { var bullet = bullets[b]; if (!bullet.parent) { // Bullet has been destroyed bullets.splice(b, 1); } } // Only check collisions every other frame to reduce load if (LK.ticks % 2 === 0) { // Check player-enemy collision for (var e = 0; e < enemies.length; e++) { var enemy = enemies[e]; if (player.intersects(enemy)) { // Player touched enemy - game over LK.showGameOver(); return; } } } // Check bullet-enemy collision (keep this every frame for responsiveness) for (var b = bullets.length - 1; b >= 0; b--) { var bullet = bullets[b]; var hitEnemy = false; for (var e = enemies.length - 1; e >= 0; e--) { var enemy = enemies[e]; if (bullet.intersects(enemy)) { // Store enemy position for respawn var respawnX = enemy.x; var respawnY = enemy.y; var respawnMazeX = enemy.mazeX; var respawnMazeY = enemy.mazeY; // Calculate knockback direction (opposite to bullet direction) var knockbackX = -bullet.speedX * 15; // Amplify knockback distance var knockbackY = -bullet.speedY * 15; // Calculate final knockback position (outside maze) var finalX = enemy.x + knockbackX; var finalY = enemy.y + knockbackY; // Ensure knockback goes outside screen boundaries if (finalX > -100 && finalX < 2148) { finalX = finalX < 1024 ? -200 : 2248; } if (finalY > -100 && finalY < 2832) { finalY = finalY < 1366 ? -200 : 2932; } // Remove bullet immediately bullet.destroy(); bullets.splice(b, 1); // Add score for defeating enemy LK.setScore(LK.getScore() + 10); // Animate enemy knockback with tween tween(enemy, { x: finalX, y: finalY, rotation: Math.PI * 4, // Spin while being knocked back scaleX: 0.5, scaleY: 0.5, alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { // Remove enemy from arrays and destroy for (var idx = enemies.length - 1; idx >= 0; idx--) { if (enemies[idx] === enemy) { enemies.splice(idx, 1); break; } } enemy.destroy(); // Schedule enemy respawn after 5 seconds using tween var dummyObject = {}; tween(dummyObject, {}, { duration: 5000, onFinish: function onFinish() { // Respawn enemy at same position var newEnemy = game.addChild(new Enemy()); newEnemy.x = respawnX; newEnemy.y = respawnY; newEnemy.targetX = respawnX; newEnemy.targetY = respawnY; newEnemy.mazeX = respawnMazeX; newEnemy.mazeY = respawnMazeY; enemies.push(newEnemy); } }); } }); hitEnemy = true; break; } } if (hitEnemy) break; } // Check rose collection every 3 frames if (LK.ticks % 3 === 0) { for (var i = 0; i < goldenBalls.length; i++) { var rose = goldenBalls[i]; if (!rose.collected && player.intersects(rose)) { rose.collect(); collectedBalls++; LK.setScore(collectedBalls); updateScoreText(); LK.getSound('collect').play(); // Check win condition if (collectedBalls >= totalBalls) { LK.getSound('complete').play(); LK.setTimeout(function () { LK.showYouWin(); }, 500); } } } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = 0;
self.speedY = 0;
self.speed = 8;
self.update = function () {
// Store previous position for wall collision detection
var prevX = self.x;
var prevY = self.y;
self.x += self.speedX;
self.y += self.speedY;
// Check if bullet hit a wall
var mazeX = Math.floor((self.x - MAZE_START_X) / CELL_SIZE);
var mazeY = Math.floor((self.y - MAZE_START_Y) / CELL_SIZE);
// If bullet is inside maze bounds and hits a wall, destroy it
if (mazeX >= 0 && mazeX < MAZE_WIDTH && mazeY >= 0 && mazeY < MAZE_HEIGHT) {
if (maze[mazeY][mazeX] === 1) {
self.destroy();
return;
}
}
// Remove bullet if it goes off screen
if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) {
self.destroy();
}
};
self.setDirection = function (dirX, dirY) {
var length = Math.sqrt(dirX * dirX + dirY * dirY);
if (length > 0) {
self.speedX = dirX / length * self.speed;
self.speedY = dirY / length * self.speed;
}
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.targetX = 0;
self.targetY = 0;
self.moveTimer = 0;
self.moveDelay = 60; // Change direction every 60 frames
self.directions = [{
x: 0,
y: -1
},
// up
{
x: 1,
y: 0
},
// right
{
x: 0,
y: 1
},
// down
{
x: -1,
y: 0
} // left
];
self.update = function () {
// Only update every 3 frames to reduce computation
if (LK.ticks % 3 !== 0) return;
// AI movement - change direction periodically
self.moveTimer++;
if (self.moveTimer >= self.moveDelay) {
self.moveTimer = 0;
self.chooseNewDirection();
}
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 2) {
// Simplified movement - don't check walls every frame
self.x += dx > 0 ? self.speed : dx < 0 ? -self.speed : 0;
self.y += dy > 0 ? self.speed : dy < 0 ? -self.speed : 0;
} else {
// Reached target, choose new direction
self.chooseNewDirection();
}
};
self.chooseNewDirection = function () {
var currentMazeX = Math.floor((self.x - MAZE_START_X) / CELL_SIZE);
var currentMazeY = Math.floor((self.y - MAZE_START_Y) / CELL_SIZE);
var validDirections = [];
// Check each direction for valid movement
for (var i = 0; i < self.directions.length; i++) {
var dir = self.directions[i];
var newMazeX = currentMazeX + dir.x;
var newMazeY = currentMazeY + dir.y;
if (newMazeX >= 0 && newMazeX < MAZE_WIDTH && newMazeY >= 0 && newMazeY < MAZE_HEIGHT) {
if (maze[newMazeY][newMazeX] === 0) {
validDirections.push(dir);
}
}
}
if (validDirections.length > 0) {
var chosenDir = validDirections[Math.floor(Math.random() * validDirections.length)];
var targetMazeX = currentMazeX + chosenDir.x;
var targetMazeY = currentMazeY + chosenDir.y;
self.targetX = MAZE_START_X + targetMazeX * CELL_SIZE + CELL_SIZE / 2;
self.targetY = MAZE_START_Y + targetMazeY * CELL_SIZE + CELL_SIZE / 2;
}
};
return self;
});
var GoldenBall = Container.expand(function () {
var self = Container.call(this);
var roseGraphics = self.attachAsset('goldenBall', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self.update = function () {
// Initialize floating animation on first update
if (!self.floatingInitialized && !self.collected) {
self.floatingInitialized = true;
self.baseY = self.y;
// Start vertical floating animation
tween(roseGraphics, {
y: -20
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Float back down
tween(roseGraphics, {
y: 20
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Reset and repeat
roseGraphics.y = 0;
self.floatingInitialized = false;
}
});
}
});
}
};
self.collect = function () {
if (!self.collected) {
self.collected = true;
tween(roseGraphics, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.visible = false;
}
});
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.targetX = 0;
self.targetY = 0;
self.facingRight = true; // Track which direction player is facing
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 2) {
// Update facing direction based on horizontal movement
if (Math.abs(dx) > 1) {
// Only change facing if significant horizontal movement
if (dx > 0 && !self.facingRight) {
// Moving right, currently facing left
self.facingRight = true;
tween(playerGraphics, {
scaleX: 1
}, {
duration: 200,
easing: tween.easeOut
});
} else if (dx < 0 && self.facingRight) {
// Moving left, currently facing right
self.facingRight = false;
tween(playerGraphics, {
scaleX: -1
}, {
duration: 200,
easing: tween.easeOut
});
}
}
// Calculate next position
var nextX = self.x + dx / distance * self.speed;
var nextY = self.y + dy / distance * self.speed;
// Check if next position is valid (not a wall)
var mazeX = Math.floor((nextX - MAZE_START_X) / CELL_SIZE);
var mazeY = Math.floor((nextY - MAZE_START_Y) / CELL_SIZE);
// Check if next position is within maze bounds
if (mazeX >= 0 && mazeX < MAZE_WIDTH && mazeY >= 0 && mazeY < MAZE_HEIGHT) {
// Inside maze - only move if not a wall
if (maze[mazeY][mazeX] === 0) {
self.x = nextX;
self.y = nextY;
}
} else {
// Outside maze bounds - allow free movement anywhere on screen
if (nextX >= 0 && nextX <= 2048 && nextY >= 0 && nextY <= 2732) {
self.x = nextX;
self.y = nextY;
}
}
}
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// Set background color explicitly
game.setBackgroundColor(0x000000);
// Add background image
var background = game.addChild(LK.getAsset('background', {
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 1.0,
tint: 0xffffff
}));
// Send background to the back so other elements appear on top
game.setChildIndex(background, 0);
// Make background more sharp and visible
background.alpha = 1.0;
background.scale.set(1.0, 1.0);
var CELL_SIZE = 48;
var MAZE_WIDTH = 16;
var MAZE_HEIGHT = 20;
var MAZE_START_X = (2048 - MAZE_WIDTH * CELL_SIZE) / 2;
var MAZE_START_Y = (2732 - MAZE_HEIGHT * CELL_SIZE) / 2;
var maze = [];
var walls = [];
var floors = [];
var player;
var goldenBalls = [];
var totalBalls = 0;
var collectedBalls = 0;
var bullets = [];
var enemies = [];
var enemyCount = 3;
var swipeStart = {
x: 0,
y: 0
};
var isSwipping = false;
// Tap controls - no drag node needed
// Score display
var scoreText = new Text2('Roses: 0/0', {
size: 50,
fill: 0xFFD700
});
scoreText.anchor.set(1, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -20;
scoreText.y = 80;
function generateMaze() {
// Initialize maze with walls
for (var y = 0; y < MAZE_HEIGHT; y++) {
maze[y] = [];
for (var x = 0; x < MAZE_WIDTH; x++) {
maze[y][x] = 1; // 1 = wall, 0 = path
}
}
// Generate maze without dead ends using modified algorithm
var startX = 1;
var startY = 1;
maze[startY][startX] = 0;
var directions = [{
x: 0,
y: -2
},
// up
{
x: 2,
y: 0
},
// right
{
x: 0,
y: 2
},
// down
{
x: -2,
y: 0
} // left
];
// First pass: create main paths using recursive backtracking
var stack = [];
stack.push({
x: startX,
y: startY
});
while (stack.length > 0) {
var current = stack[stack.length - 1];
var neighbors = [];
for (var i = 0; i < directions.length; i++) {
var nx = current.x + directions[i].x;
var ny = current.y + directions[i].y;
if (nx > 0 && nx < MAZE_WIDTH - 1 && ny > 0 && ny < MAZE_HEIGHT - 1 && maze[ny][nx] === 1) {
neighbors.push({
x: nx,
y: ny,
dir: directions[i]
});
}
}
if (neighbors.length > 0) {
var next = neighbors[Math.floor(Math.random() * neighbors.length)];
maze[next.y][next.x] = 0;
maze[current.y + next.dir.y / 2][current.x + next.dir.x / 2] = 0;
stack.push({
x: next.x,
y: next.y
});
} else {
stack.pop();
}
}
// Second pass: add loops to eliminate dead ends
for (var attempts = 0; attempts < 20; attempts++) {
// Find all dead ends (cells with only one neighbor)
var deadEnds = [];
for (var y = 1; y < MAZE_HEIGHT - 1; y += 2) {
for (var x = 1; x < MAZE_WIDTH - 1; x += 2) {
if (maze[y][x] === 0) {
var pathCount = 0;
var possibleConnections = [];
// Check all four directions
if (y > 1 && maze[y - 1][x] === 0) pathCount++;
if (y < MAZE_HEIGHT - 2 && maze[y + 1][x] === 0) pathCount++;
if (x > 1 && maze[y][x - 1] === 0) pathCount++;
if (x < MAZE_WIDTH - 2 && maze[y][x + 1] === 0) pathCount++;
// If it's a dead end, try to connect it to another path
if (pathCount === 1) {
// Try to connect to nearby paths
for (var i = 0; i < directions.length; i++) {
var newX = x + directions[i].x;
var newY = y + directions[i].y;
if (newX > 0 && newX < MAZE_WIDTH - 1 && newY > 0 && newY < MAZE_HEIGHT - 1) {
if (maze[newY][newX] === 0) {
var wallX = x + directions[i].x / 2;
var wallY = y + directions[i].y / 2;
if (maze[wallY][wallX] === 1) {
possibleConnections.push({
wallX: wallX,
wallY: wallY
});
}
}
}
}
if (possibleConnections.length > 0) {
var connection = possibleConnections[Math.floor(Math.random() * possibleConnections.length)];
maze[connection.wallY][connection.wallX] = 0;
}
}
}
}
}
}
// Create entrance
var entranceX = Math.floor(Math.random() * (MAZE_WIDTH - 2)) + 1;
maze[0][entranceX] = 0;
return {
x: entranceX,
y: 0
};
}
function createMazeVisuals() {
for (var y = 0; y < MAZE_HEIGHT; y++) {
for (var x = 0; x < MAZE_WIDTH; x++) {
var cellX = MAZE_START_X + x * CELL_SIZE;
var cellY = MAZE_START_Y + y * CELL_SIZE;
if (maze[y][x] === 1) {
var wall = game.addChild(LK.getAsset('wall', {
x: cellX,
y: cellY
}));
walls.push(wall);
} else {
var floor = game.addChild(LK.getAsset('floor', {
x: cellX,
y: cellY
}));
floors.push(floor);
}
}
}
}
function spawnEnemies() {
var spawnedEnemies = 0;
var attempts = 0;
var maxAttempts = 100;
while (spawnedEnemies < enemyCount && attempts < maxAttempts) {
var x = Math.floor(Math.random() * MAZE_WIDTH);
var y = Math.floor(Math.random() * MAZE_HEIGHT);
// Make sure enemy spawns in a path cell and not too close to player start
if (maze[y][x] === 0 && (Math.abs(x - entrance.x) > 3 || Math.abs(y - entrance.y) > 3)) {
var cellX = MAZE_START_X + x * CELL_SIZE + CELL_SIZE / 2;
var cellY = MAZE_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
var enemy = game.addChild(new Enemy());
enemy.x = cellX;
enemy.y = cellY;
enemy.targetX = cellX;
enemy.targetY = cellY;
enemy.mazeX = x;
enemy.mazeY = y;
enemies.push(enemy);
spawnedEnemies++;
}
attempts++;
}
}
function placeGoldenRoses() {
var roseCount = Math.floor(MAZE_WIDTH * MAZE_HEIGHT * 0.02); // 2% of maze cells
var placedRoses = 0;
while (placedRoses < roseCount) {
var x = Math.floor(Math.random() * MAZE_WIDTH);
var y = Math.floor(Math.random() * MAZE_HEIGHT);
if (maze[y][x] === 0) {
var cellX = MAZE_START_X + x * CELL_SIZE + CELL_SIZE / 2;
var cellY = MAZE_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
var rose = game.addChild(new GoldenBall());
rose.x = cellX;
rose.y = cellY;
rose.mazeX = x;
rose.mazeY = y;
goldenBalls.push(rose);
placedRoses++;
}
}
totalBalls = goldenBalls.length;
updateScoreText();
}
function updateScoreText() {
scoreText.setText('Roses: ' + collectedBalls + '/' + totalBalls);
}
function isValidPosition(x, y) {
var mazeX = Math.floor((x - MAZE_START_X) / CELL_SIZE);
var mazeY = Math.floor((y - MAZE_START_Y) / CELL_SIZE);
if (mazeX < 0 || mazeX >= MAZE_WIDTH || mazeY < 0 || mazeY >= MAZE_HEIGHT) {
return false;
}
return maze[mazeY][mazeX] === 0;
}
function getValidTargetPosition(targetX, targetY) {
var currentMazeX = Math.floor((player.x - MAZE_START_X) / CELL_SIZE);
var currentMazeY = Math.floor((player.y - MAZE_START_Y) / CELL_SIZE);
var targetMazeX = Math.floor((targetX - MAZE_START_X) / CELL_SIZE);
var targetMazeY = Math.floor((targetY - MAZE_START_Y) / CELL_SIZE);
if (targetMazeX < 0 || targetMazeX >= MAZE_WIDTH || targetMazeY < 0 || targetMazeY >= MAZE_HEIGHT) {
return {
x: player.x,
y: player.y
};
}
if (maze[targetMazeY][targetMazeX] === 1) {
return {
x: player.x,
y: player.y
};
}
var cellCenterX = MAZE_START_X + targetMazeX * CELL_SIZE + CELL_SIZE / 2;
var cellCenterY = MAZE_START_Y + targetMazeY * CELL_SIZE + CELL_SIZE / 2;
return {
x: cellCenterX,
y: cellCenterY
};
}
// Initialize maze
var entrance = generateMaze();
createMazeVisuals();
// Create player
player = game.addChild(new Player());
player.x = MAZE_START_X + entrance.x * CELL_SIZE + CELL_SIZE / 2;
player.y = MAZE_START_Y + entrance.y * CELL_SIZE + CELL_SIZE / 2;
player.setTarget(player.x, player.y);
// Place golden roses
placeGoldenRoses();
// Spawn enemies
spawnEnemies();
// Touch controls - swipe to shoot, tap to move
game.down = function (x, y, obj) {
swipeStart.x = x;
swipeStart.y = y;
isSwipping = true;
};
game.move = function (x, y, obj) {
// Track swipe movement but don't move player during swipe
};
game.up = function (x, y, obj) {
if (isSwipping) {
var swipeEndX = x;
var swipeEndY = y;
var swipeDistanceX = swipeEndX - swipeStart.x;
var swipeDistanceY = swipeEndY - swipeStart.y;
var swipeDistance = Math.sqrt(swipeDistanceX * swipeDistanceX + swipeDistanceY * swipeDistanceY);
if (swipeDistance > 30) {
// Minimum swipe distance
// Create bullet and shoot in swipe direction
var bullet = game.addChild(new Bullet());
bullet.x = player.x;
bullet.y = player.y;
bullet.setDirection(swipeDistanceX, swipeDistanceY);
bullets.push(bullet);
LK.getSound('shoot').play();
} else {
// Short tap - move player to tap position (no maze validation)
player.setTarget(x, y);
}
isSwipping = false;
}
};
// Game update loop
game.update = function () {
// Clean up destroyed bullets
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
if (!bullet.parent) {
// Bullet has been destroyed
bullets.splice(b, 1);
}
}
// Only check collisions every other frame to reduce load
if (LK.ticks % 2 === 0) {
// Check player-enemy collision
for (var e = 0; e < enemies.length; e++) {
var enemy = enemies[e];
if (player.intersects(enemy)) {
// Player touched enemy - game over
LK.showGameOver();
return;
}
}
}
// Check bullet-enemy collision (keep this every frame for responsiveness)
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
var hitEnemy = false;
for (var e = enemies.length - 1; e >= 0; e--) {
var enemy = enemies[e];
if (bullet.intersects(enemy)) {
// Store enemy position for respawn
var respawnX = enemy.x;
var respawnY = enemy.y;
var respawnMazeX = enemy.mazeX;
var respawnMazeY = enemy.mazeY;
// Calculate knockback direction (opposite to bullet direction)
var knockbackX = -bullet.speedX * 15; // Amplify knockback distance
var knockbackY = -bullet.speedY * 15;
// Calculate final knockback position (outside maze)
var finalX = enemy.x + knockbackX;
var finalY = enemy.y + knockbackY;
// Ensure knockback goes outside screen boundaries
if (finalX > -100 && finalX < 2148) {
finalX = finalX < 1024 ? -200 : 2248;
}
if (finalY > -100 && finalY < 2832) {
finalY = finalY < 1366 ? -200 : 2932;
}
// Remove bullet immediately
bullet.destroy();
bullets.splice(b, 1);
// Add score for defeating enemy
LK.setScore(LK.getScore() + 10);
// Animate enemy knockback with tween
tween(enemy, {
x: finalX,
y: finalY,
rotation: Math.PI * 4,
// Spin while being knocked back
scaleX: 0.5,
scaleY: 0.5,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove enemy from arrays and destroy
for (var idx = enemies.length - 1; idx >= 0; idx--) {
if (enemies[idx] === enemy) {
enemies.splice(idx, 1);
break;
}
}
enemy.destroy();
// Schedule enemy respawn after 5 seconds using tween
var dummyObject = {};
tween(dummyObject, {}, {
duration: 5000,
onFinish: function onFinish() {
// Respawn enemy at same position
var newEnemy = game.addChild(new Enemy());
newEnemy.x = respawnX;
newEnemy.y = respawnY;
newEnemy.targetX = respawnX;
newEnemy.targetY = respawnY;
newEnemy.mazeX = respawnMazeX;
newEnemy.mazeY = respawnMazeY;
enemies.push(newEnemy);
}
});
}
});
hitEnemy = true;
break;
}
}
if (hitEnemy) break;
}
// Check rose collection every 3 frames
if (LK.ticks % 3 === 0) {
for (var i = 0; i < goldenBalls.length; i++) {
var rose = goldenBalls[i];
if (!rose.collected && player.intersects(rose)) {
rose.collect();
collectedBalls++;
LK.setScore(collectedBalls);
updateScoreText();
LK.getSound('collect').play();
// Check win condition
if (collectedBalls >= totalBalls) {
LK.getSound('complete').play();
LK.setTimeout(function () {
LK.showYouWin();
}, 500);
}
}
}
}
};
2d side scroller bee. In-Game asset. 2d. High contrast. No shadows
big evil wasp. In-Game asset. 2d. High contrast. No shadows
2d side scroller rose fresh rose flower. In-Game asset. 2d. High contrast. No shadows
image 2d classic full warna tentang hamparan bunga bunga liar disuatu lembah. In-Game asset. 2d. High contrast. No shadows