/****
* 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