/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Ball = Container.expand(function () { var self = Container.call(this); var ballShadow = self.attachAsset('ballShadow', { anchorX: 0.5, anchorY: 0.5 }); ballShadow.alpha = 0.5; ballShadow.y = 8; // Offset shadow below ball for heavier appearance var ballGraphics = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); self.velocityX = 0; self.velocityY = 0; self.gravity = 1.2; // Stronger gravity for realistic falling self.airResistance = 0.995; // Air resistance instead of friction self.bounceForce = 0.8; // More realistic bounce with gravity self.maxSpeed = 20; self.mass = 1.0; // Add mass for realistic physics self.update = function () { // Apply realistic gravity acceleration self.velocityY += self.gravity; // Apply air resistance to horizontal movement only self.velocityX *= self.airResistance; // No air resistance on vertical movement to allow proper falling // Natural velocity dampening (no artificial cutoffs) var currentSpeed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY); // Limit max speed with realistic scaling if (currentSpeed > self.maxSpeed) { var scale = self.maxSpeed / currentSpeed; self.velocityX *= scale; self.velocityY *= scale; } // Store last position for impact detection if (self.lastY === undefined) self.lastY = self.y; var wasMovingDown = self.velocityY > 0; // Apply velocity directly for natural gravity-based movement self.x += self.velocityX; self.y += self.velocityY; // Ball remains rigid - no landing effects self.lastY = self.y; // Check maze boundaries and collisions checkBallCollisions(); }; self.addForce = function (forceX, forceY) { // Apply force based on mass (F = ma, so a = F/m) var accelerationX = forceX / self.mass; var accelerationY = forceY / self.mass; // Add to velocity (realistic momentum) self.velocityX += accelerationX; self.velocityY += accelerationY; // Stop any existing movement tweens to prevent conflicts tween.stop(self, { x: true, y: true }); }; return self; }); var MazeCell = Container.expand(function (cellType) { var self = Container.call(this); self.cellType = cellType || 'wall'; var cellGraphics; if (self.cellType === 'wall') { cellGraphics = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.cellType === 'path') { cellGraphics = self.attachAsset('path', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.cellType === 'blackhole') { cellGraphics = self.attachAsset('blackhole', { anchorX: 0.5, anchorY: 0.5 }); } return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Add background var background = game.addChild(LK.getAsset('background', { x: 0, y: 0, width: 2048, height: 2732 })); var ball; var maze = []; var mazeWidth = 20; var mazeHeight = 30; var cellSize = 60; var mazeContainer; var blackholeX, blackholeY; var timeLeft = 60; var gameWon = false; // UI Elements var timerText = new Text2('60', { size: 80, fill: 0xFFFFFF }); timerText.anchor.set(0.5, 0); LK.gui.top.addChild(timerText); var scoreText = new Text2('Level: 1', { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); scoreText.x = 150; scoreText.y = 20; LK.gui.topLeft.addChild(scoreText); // Initialize maze container mazeContainer = game.addChild(new Container()); mazeContainer.x = (2048 - mazeWidth * cellSize) / 2; mazeContainer.y = (2732 - mazeHeight * cellSize) / 2; // Generate initial maze function generateMaze() { // Clear existing maze for (var i = 0; i < maze.length; i++) { for (var j = 0; j < maze[i].length; j++) { if (maze[i][j]) { maze[i][j].destroy(); } } } maze = []; // Initialize maze grid (all walls initially) var mazeGrid = []; for (var y = 0; y < mazeHeight; y++) { mazeGrid[y] = []; maze[y] = []; for (var x = 0; x < mazeWidth; x++) { // 0 = wall, 1 = path, 2 = visited mazeGrid[y][x] = 0; maze[y][x] = null; } } // Recursive backtracking maze generation var stack = []; var currentX = 1; var currentY = 1; mazeGrid[currentY][currentX] = 1; // Mark starting cell as path stack.push({ x: currentX, y: currentY }); while (stack.length > 0) { var neighbors = []; var directions = [{ x: 0, y: -2 }, // North { x: 2, y: 0 }, // East { x: 0, y: 2 }, // South { x: -2, y: 0 } // West ]; // Find unvisited neighbors for (var i = 0; i < directions.length; i++) { var nx = currentX + directions[i].x; var ny = currentY + directions[i].y; if (nx > 0 && nx < mazeWidth - 1 && ny > 0 && ny < mazeHeight - 1 && mazeGrid[ny][nx] === 0) { neighbors.push({ x: nx, y: ny, dir: directions[i] }); } } if (neighbors.length > 0) { // Choose random neighbor var randomNeighbor = neighbors[Math.floor(Math.random() * neighbors.length)]; var nextX = randomNeighbor.x; var nextY = randomNeighbor.y; var wallX = currentX + randomNeighbor.dir.x / 2; var wallY = currentY + randomNeighbor.dir.y / 2; // Remove wall between current and next cell mazeGrid[wallY][wallX] = 1; mazeGrid[nextY][nextX] = 1; // Move to next cell currentX = nextX; currentY = nextY; stack.push({ x: currentX, y: currentY }); } else { // Backtrack var prev = stack.pop(); if (stack.length > 0) { currentX = stack[stack.length - 1].x; currentY = stack[stack.length - 1].y; } } } // Add some random openings for better gameplay for (var y = 2; y < mazeHeight - 2; y += 2) { for (var x = 2; x < mazeWidth - 2; x += 2) { if (Math.random() < 0.15) { // Create random opening var openingDirs = [{ x: 1, y: 0 }, { x: -1, y: 0 }, { x: 0, y: 1 }, { x: 0, y: -1 }]; var randomDir = openingDirs[Math.floor(Math.random() * openingDirs.length)]; var openX = x + randomDir.x; var openY = y + randomDir.y; if (openX > 0 && openX < mazeWidth - 1 && openY > 0 && openY < mazeHeight - 1) { mazeGrid[openY][openX] = 1; } } } } // Create visual maze from grid for (var y = 0; y < mazeHeight; y++) { for (var x = 0; x < mazeWidth; x++) { var cellType = mazeGrid[y][x] === 1 ? 'path' : 'wall'; var cell = new MazeCell(cellType); cell.x = x * cellSize + cellSize / 2; cell.y = y * cellSize + cellSize / 2; maze[y][x] = cell; mazeContainer.addChild(cell); } } // Place black hole exit placeBlackHole(); } function placeBlackHole() { // Find all path cells in the bottom areas of the maze var pathCells = []; // Start from bottom and work upward to find suitable path cells // Ensure we stay within maze bounds for (var y = Math.max(1, mazeHeight - 6); y <= Math.min(mazeHeight - 2, mazeHeight - 1); y++) { for (var x = 1; x < mazeWidth - 1; x++) { // Additional boundary check if (x >= 0 && x < mazeWidth && y >= 0 && y < mazeHeight && maze[y] && maze[y][x] && maze[y][x].cellType === 'path') { // Count how many neighboring cells are paths var neighborCount = 0; var directions = [{ x: 0, y: -1 }, { x: 1, y: 0 }, { x: 0, y: 1 }, { x: -1, y: 0 }]; for (var i = 0; i < directions.length; i++) { var nx = x + directions[i].x; var ny = y + directions[i].y; if (nx >= 0 && nx < mazeWidth && ny >= 0 && ny < mazeHeight && maze[ny] && maze[ny][nx] && maze[ny][nx].cellType === 'path') { neighborCount++; } } pathCells.push({ x: x, y: y, neighbors: neighborCount, priority: (mazeHeight - y) * 10 + (4 - neighborCount) // Prefer bottom cells with fewer neighbors }); } } } // If no path cells found in bottom area, expand search to entire maze if (pathCells.length === 0) { for (var y = 1; y < mazeHeight - 1; y++) { for (var x = 1; x < mazeWidth - 1; x++) { // Additional boundary check if (x >= 0 && x < mazeWidth && y >= 0 && y < mazeHeight && maze[y] && maze[y][x] && maze[y][x].cellType === 'path') { pathCells.push({ x: x, y: y, neighbors: 1, priority: 1 }); } } } } // If still no path cells found, create one by forcing a path within bounds if (pathCells.length === 0) { // Find middle of bottom row and force it to be a path, ensuring it's within bounds var bottomY = Math.max(1, Math.min(mazeHeight - 2, mazeHeight - 2)); var middleX = Math.max(1, Math.min(mazeWidth - 2, Math.floor(mazeWidth / 2))); if (maze[bottomY] && maze[bottomY][middleX]) { maze[bottomY][middleX].destroy(); var pathCell = new MazeCell('path'); pathCell.x = middleX * cellSize + cellSize / 2; pathCell.y = bottomY * cellSize + cellSize / 2; maze[bottomY][middleX] = pathCell; mazeContainer.addChild(pathCell); pathCells.push({ x: middleX, y: bottomY, neighbors: 1, priority: 100 }); } } if (pathCells.length > 0) { // Choose the path cell with highest priority (bottom-most with fewest neighbors) pathCells.sort(function (a, b) { return b.priority - a.priority; }); var bestCell = pathCells[0]; // Ensure black hole coordinates are within maze bounds blackholeX = Math.max(0, Math.min(mazeWidth - 1, bestCell.x)); blackholeY = Math.max(0, Math.min(mazeHeight - 1, bestCell.y)); // Replace path with black hole, with additional boundary checks if (blackholeX >= 0 && blackholeX < mazeWidth && blackholeY >= 0 && blackholeY < mazeHeight && maze[blackholeY] && maze[blackholeY][blackholeX]) { maze[blackholeY][blackholeX].destroy(); var blackhole = new MazeCell('blackhole'); blackhole.x = blackholeX * cellSize + cellSize / 2; blackhole.y = blackholeY * cellSize + cellSize / 2; maze[blackholeY][blackholeX] = blackhole; mazeContainer.addChild(blackhole); } } } function addBounceEffect() { // Ball remains rigid - no bounce effects } function checkBallCollisions() { var ballMazeX = Math.floor((ball.x - mazeContainer.x) / cellSize); var ballMazeY = Math.floor((ball.y - mazeContainer.y) / cellSize); // Check if ball is trying to move outside maze boundaries if (ballMazeX < 0 || ballMazeX >= mazeWidth || ballMazeY < 0 || ballMazeY >= mazeHeight) { // Keep ball within maze bounds and bounce if (ball.x < mazeContainer.x + cellSize / 2) { tween(ball, { x: mazeContainer.x + cellSize / 2 }, { duration: 50, easing: tween.easeOut }); ball.velocityX = Math.abs(ball.velocityX) * ball.bounceForce; // Add slight random variation for realism ball.velocityY += (Math.random() - 0.5) * 0.5; } if (ball.x > mazeContainer.x + (mazeWidth - 1) * cellSize + cellSize / 2) { tween(ball, { x: mazeContainer.x + (mazeWidth - 1) * cellSize + cellSize / 2 }, { duration: 50, easing: tween.easeOut }); ball.velocityX = -Math.abs(ball.velocityX) * ball.bounceForce; // Add slight random variation for realism ball.velocityY += (Math.random() - 0.5) * 0.5; } if (ball.y < mazeContainer.y + cellSize / 2) { tween(ball, { y: mazeContainer.y + cellSize / 2 }, { duration: 50, easing: tween.easeOut }); ball.velocityY = Math.abs(ball.velocityY) * ball.bounceForce; // Add slight random variation for realism ball.velocityX += (Math.random() - 0.5) * 0.5; } if (ball.y > mazeContainer.y + (mazeHeight - 1) * cellSize + cellSize / 2) { tween(ball, { y: mazeContainer.y + (mazeHeight - 1) * cellSize + cellSize / 2 }, { duration: 50, easing: tween.easeOut }); ball.velocityY = -Math.abs(ball.velocityY) * ball.bounceForce; // Add slight random variation for realism ball.velocityX += (Math.random() - 0.5) * 0.5; } // Ball remains rigid - no shake effects LK.getSound('bounce').play(); return; } var currentCell = maze[ballMazeY][ballMazeX]; // Check if ball is in a wall cell - if so, push it back to nearest path if (currentCell && currentCell.cellType === 'wall') { // Find the nearest path cell var nearestPath = null; var minDistance = Infinity; for (var y = 0; y < mazeHeight; y++) { for (var x = 0; x < mazeWidth; x++) { if (maze[y] && maze[y][x] && (maze[y][x].cellType === 'path' || maze[y][x].cellType === 'blackhole')) { var pathCenterX = mazeContainer.x + x * cellSize + cellSize / 2; var pathCenterY = mazeContainer.y + y * cellSize + cellSize / 2; var distance = Math.sqrt(Math.pow(ball.x - pathCenterX, 2) + Math.pow(ball.y - pathCenterY, 2)); if (distance < minDistance) { minDistance = distance; nearestPath = { x: pathCenterX, y: pathCenterY }; } } } } if (nearestPath) { // Push ball to nearest path var directionX = nearestPath.x - ball.x; var directionY = nearestPath.y - ball.y; var distance = Math.sqrt(directionX * directionX + directionY * directionY); if (distance > 0) { directionX /= distance; directionY /= distance; var targetX = nearestPath.x - directionX * 10; var targetY = nearestPath.y - directionY * 10; // Smooth tween to new position tween(ball, { x: targetX, y: targetY }, { duration: 100, easing: tween.easeOut }); } // Realistic wall collision with momentum conservation var impactSpeed = Math.sqrt(ball.velocityX * ball.velocityX + ball.velocityY * ball.velocityY); ball.velocityX = -ball.velocityX * ball.bounceForce; ball.velocityY = -ball.velocityY * ball.bounceForce; // Add energy loss and slight randomness for realism var energyLoss = 0.9; ball.velocityX *= energyLoss; ball.velocityY *= energyLoss; ball.velocityX += (Math.random() - 0.5) * 0.3; ball.velocityY += (Math.random() - 0.5) * 0.3; } // Ball remains rigid - no shake effects LK.getSound('bounce').play(); } else if (currentCell && currentCell.cellType === 'blackhole') { // Check if ball is close enough to black hole center var blackholeCenterX = mazeContainer.x + blackholeX * cellSize + cellSize / 2; var blackholeCenterY = mazeContainer.y + blackholeY * cellSize + cellSize / 2; var distance = Math.sqrt(Math.pow(ball.x - blackholeCenterX, 2) + Math.pow(ball.y - blackholeCenterY, 2)); if (distance < 30 && !gameWon) { gameWon = true; LK.getSound('win').play(); LK.setScore(LK.getScore() + 1); scoreText.setText('Level: ' + (LK.getScore() + 1)); // Flash effect LK.effects.flashScreen(0x00ff00, 1000); // Reset for next level LK.setTimeout(function () { resetLevel(); }, 1000); } } } function resetLevel() { gameWon = false; timeLeft = 60; // Reset ball position ball.x = mazeContainer.x + cellSize * 1.5; ball.y = mazeContainer.y + cellSize * 1.5; ball.velocityX = 0; ball.velocityY = 0; // Generate new maze generateMaze(); } // Initialize ball ball = game.addChild(new Ball()); ball.x = mazeContainer.x + cellSize * 1.5; ball.y = mazeContainer.y + cellSize * 1.5; // Touch controls game.down = function (x, y, obj) { if (gameWon) return; // Determine tap location and apply directional force var screenWidth = 2048; var tapX = x; var jumpForce = -15; // Realistic upward impulse var sideForce = 8; // Realistic horizontal impulse if (tapX < screenWidth / 3) { // Left tap - bounce left and up ball.addForce(-sideForce, jumpForce); } else if (tapX > screenWidth * 2 / 3) { // Right tap - bounce right and up ball.addForce(sideForce, jumpForce); } else { // Center tap - bounce straight up ball.addForce(0, jumpForce); } }; // Generate initial maze generateMaze(); // Timer countdown var timerInterval = LK.setInterval(function () { if (gameWon) return; timeLeft--; timerText.setText(timeLeft.toString()); if (timeLeft <= 0) { timeLeft = 60; generateMaze(); timerText.setText('60'); // Flash red to indicate maze change LK.effects.flashScreen(0xff0000, 500); } }, 1000); // Start background music LK.playMusic('1tik'); game.update = function () { // Game updates are handled by individual object update methods };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballShadow = self.attachAsset('ballShadow', {
anchorX: 0.5,
anchorY: 0.5
});
ballShadow.alpha = 0.5;
ballShadow.y = 8; // Offset shadow below ball for heavier appearance
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.gravity = 1.2; // Stronger gravity for realistic falling
self.airResistance = 0.995; // Air resistance instead of friction
self.bounceForce = 0.8; // More realistic bounce with gravity
self.maxSpeed = 20;
self.mass = 1.0; // Add mass for realistic physics
self.update = function () {
// Apply realistic gravity acceleration
self.velocityY += self.gravity;
// Apply air resistance to horizontal movement only
self.velocityX *= self.airResistance;
// No air resistance on vertical movement to allow proper falling
// Natural velocity dampening (no artificial cutoffs)
var currentSpeed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
// Limit max speed with realistic scaling
if (currentSpeed > self.maxSpeed) {
var scale = self.maxSpeed / currentSpeed;
self.velocityX *= scale;
self.velocityY *= scale;
}
// Store last position for impact detection
if (self.lastY === undefined) self.lastY = self.y;
var wasMovingDown = self.velocityY > 0;
// Apply velocity directly for natural gravity-based movement
self.x += self.velocityX;
self.y += self.velocityY;
// Ball remains rigid - no landing effects
self.lastY = self.y;
// Check maze boundaries and collisions
checkBallCollisions();
};
self.addForce = function (forceX, forceY) {
// Apply force based on mass (F = ma, so a = F/m)
var accelerationX = forceX / self.mass;
var accelerationY = forceY / self.mass;
// Add to velocity (realistic momentum)
self.velocityX += accelerationX;
self.velocityY += accelerationY;
// Stop any existing movement tweens to prevent conflicts
tween.stop(self, {
x: true,
y: true
});
};
return self;
});
var MazeCell = Container.expand(function (cellType) {
var self = Container.call(this);
self.cellType = cellType || 'wall';
var cellGraphics;
if (self.cellType === 'wall') {
cellGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.cellType === 'path') {
cellGraphics = self.attachAsset('path', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.cellType === 'blackhole') {
cellGraphics = self.attachAsset('blackhole', {
anchorX: 0.5,
anchorY: 0.5
});
}
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Add background
var background = game.addChild(LK.getAsset('background', {
x: 0,
y: 0,
width: 2048,
height: 2732
}));
var ball;
var maze = [];
var mazeWidth = 20;
var mazeHeight = 30;
var cellSize = 60;
var mazeContainer;
var blackholeX, blackholeY;
var timeLeft = 60;
var gameWon = false;
// UI Elements
var timerText = new Text2('60', {
size: 80,
fill: 0xFFFFFF
});
timerText.anchor.set(0.5, 0);
LK.gui.top.addChild(timerText);
var scoreText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
scoreText.x = 150;
scoreText.y = 20;
LK.gui.topLeft.addChild(scoreText);
// Initialize maze container
mazeContainer = game.addChild(new Container());
mazeContainer.x = (2048 - mazeWidth * cellSize) / 2;
mazeContainer.y = (2732 - mazeHeight * cellSize) / 2;
// Generate initial maze
function generateMaze() {
// Clear existing maze
for (var i = 0; i < maze.length; i++) {
for (var j = 0; j < maze[i].length; j++) {
if (maze[i][j]) {
maze[i][j].destroy();
}
}
}
maze = [];
// Initialize maze grid (all walls initially)
var mazeGrid = [];
for (var y = 0; y < mazeHeight; y++) {
mazeGrid[y] = [];
maze[y] = [];
for (var x = 0; x < mazeWidth; x++) {
// 0 = wall, 1 = path, 2 = visited
mazeGrid[y][x] = 0;
maze[y][x] = null;
}
}
// Recursive backtracking maze generation
var stack = [];
var currentX = 1;
var currentY = 1;
mazeGrid[currentY][currentX] = 1; // Mark starting cell as path
stack.push({
x: currentX,
y: currentY
});
while (stack.length > 0) {
var neighbors = [];
var directions = [{
x: 0,
y: -2
},
// North
{
x: 2,
y: 0
},
// East
{
x: 0,
y: 2
},
// South
{
x: -2,
y: 0
} // West
];
// Find unvisited neighbors
for (var i = 0; i < directions.length; i++) {
var nx = currentX + directions[i].x;
var ny = currentY + directions[i].y;
if (nx > 0 && nx < mazeWidth - 1 && ny > 0 && ny < mazeHeight - 1 && mazeGrid[ny][nx] === 0) {
neighbors.push({
x: nx,
y: ny,
dir: directions[i]
});
}
}
if (neighbors.length > 0) {
// Choose random neighbor
var randomNeighbor = neighbors[Math.floor(Math.random() * neighbors.length)];
var nextX = randomNeighbor.x;
var nextY = randomNeighbor.y;
var wallX = currentX + randomNeighbor.dir.x / 2;
var wallY = currentY + randomNeighbor.dir.y / 2;
// Remove wall between current and next cell
mazeGrid[wallY][wallX] = 1;
mazeGrid[nextY][nextX] = 1;
// Move to next cell
currentX = nextX;
currentY = nextY;
stack.push({
x: currentX,
y: currentY
});
} else {
// Backtrack
var prev = stack.pop();
if (stack.length > 0) {
currentX = stack[stack.length - 1].x;
currentY = stack[stack.length - 1].y;
}
}
}
// Add some random openings for better gameplay
for (var y = 2; y < mazeHeight - 2; y += 2) {
for (var x = 2; x < mazeWidth - 2; x += 2) {
if (Math.random() < 0.15) {
// Create random opening
var openingDirs = [{
x: 1,
y: 0
}, {
x: -1,
y: 0
}, {
x: 0,
y: 1
}, {
x: 0,
y: -1
}];
var randomDir = openingDirs[Math.floor(Math.random() * openingDirs.length)];
var openX = x + randomDir.x;
var openY = y + randomDir.y;
if (openX > 0 && openX < mazeWidth - 1 && openY > 0 && openY < mazeHeight - 1) {
mazeGrid[openY][openX] = 1;
}
}
}
}
// Create visual maze from grid
for (var y = 0; y < mazeHeight; y++) {
for (var x = 0; x < mazeWidth; x++) {
var cellType = mazeGrid[y][x] === 1 ? 'path' : 'wall';
var cell = new MazeCell(cellType);
cell.x = x * cellSize + cellSize / 2;
cell.y = y * cellSize + cellSize / 2;
maze[y][x] = cell;
mazeContainer.addChild(cell);
}
}
// Place black hole exit
placeBlackHole();
}
function placeBlackHole() {
// Find all path cells in the bottom areas of the maze
var pathCells = [];
// Start from bottom and work upward to find suitable path cells
// Ensure we stay within maze bounds
for (var y = Math.max(1, mazeHeight - 6); y <= Math.min(mazeHeight - 2, mazeHeight - 1); y++) {
for (var x = 1; x < mazeWidth - 1; x++) {
// Additional boundary check
if (x >= 0 && x < mazeWidth && y >= 0 && y < mazeHeight && maze[y] && maze[y][x] && maze[y][x].cellType === 'path') {
// Count how many neighboring cells are paths
var neighborCount = 0;
var directions = [{
x: 0,
y: -1
}, {
x: 1,
y: 0
}, {
x: 0,
y: 1
}, {
x: -1,
y: 0
}];
for (var i = 0; i < directions.length; i++) {
var nx = x + directions[i].x;
var ny = y + directions[i].y;
if (nx >= 0 && nx < mazeWidth && ny >= 0 && ny < mazeHeight && maze[ny] && maze[ny][nx] && maze[ny][nx].cellType === 'path') {
neighborCount++;
}
}
pathCells.push({
x: x,
y: y,
neighbors: neighborCount,
priority: (mazeHeight - y) * 10 + (4 - neighborCount) // Prefer bottom cells with fewer neighbors
});
}
}
}
// If no path cells found in bottom area, expand search to entire maze
if (pathCells.length === 0) {
for (var y = 1; y < mazeHeight - 1; y++) {
for (var x = 1; x < mazeWidth - 1; x++) {
// Additional boundary check
if (x >= 0 && x < mazeWidth && y >= 0 && y < mazeHeight && maze[y] && maze[y][x] && maze[y][x].cellType === 'path') {
pathCells.push({
x: x,
y: y,
neighbors: 1,
priority: 1
});
}
}
}
}
// If still no path cells found, create one by forcing a path within bounds
if (pathCells.length === 0) {
// Find middle of bottom row and force it to be a path, ensuring it's within bounds
var bottomY = Math.max(1, Math.min(mazeHeight - 2, mazeHeight - 2));
var middleX = Math.max(1, Math.min(mazeWidth - 2, Math.floor(mazeWidth / 2)));
if (maze[bottomY] && maze[bottomY][middleX]) {
maze[bottomY][middleX].destroy();
var pathCell = new MazeCell('path');
pathCell.x = middleX * cellSize + cellSize / 2;
pathCell.y = bottomY * cellSize + cellSize / 2;
maze[bottomY][middleX] = pathCell;
mazeContainer.addChild(pathCell);
pathCells.push({
x: middleX,
y: bottomY,
neighbors: 1,
priority: 100
});
}
}
if (pathCells.length > 0) {
// Choose the path cell with highest priority (bottom-most with fewest neighbors)
pathCells.sort(function (a, b) {
return b.priority - a.priority;
});
var bestCell = pathCells[0];
// Ensure black hole coordinates are within maze bounds
blackholeX = Math.max(0, Math.min(mazeWidth - 1, bestCell.x));
blackholeY = Math.max(0, Math.min(mazeHeight - 1, bestCell.y));
// Replace path with black hole, with additional boundary checks
if (blackholeX >= 0 && blackholeX < mazeWidth && blackholeY >= 0 && blackholeY < mazeHeight && maze[blackholeY] && maze[blackholeY][blackholeX]) {
maze[blackholeY][blackholeX].destroy();
var blackhole = new MazeCell('blackhole');
blackhole.x = blackholeX * cellSize + cellSize / 2;
blackhole.y = blackholeY * cellSize + cellSize / 2;
maze[blackholeY][blackholeX] = blackhole;
mazeContainer.addChild(blackhole);
}
}
}
function addBounceEffect() {
// Ball remains rigid - no bounce effects
}
function checkBallCollisions() {
var ballMazeX = Math.floor((ball.x - mazeContainer.x) / cellSize);
var ballMazeY = Math.floor((ball.y - mazeContainer.y) / cellSize);
// Check if ball is trying to move outside maze boundaries
if (ballMazeX < 0 || ballMazeX >= mazeWidth || ballMazeY < 0 || ballMazeY >= mazeHeight) {
// Keep ball within maze bounds and bounce
if (ball.x < mazeContainer.x + cellSize / 2) {
tween(ball, {
x: mazeContainer.x + cellSize / 2
}, {
duration: 50,
easing: tween.easeOut
});
ball.velocityX = Math.abs(ball.velocityX) * ball.bounceForce;
// Add slight random variation for realism
ball.velocityY += (Math.random() - 0.5) * 0.5;
}
if (ball.x > mazeContainer.x + (mazeWidth - 1) * cellSize + cellSize / 2) {
tween(ball, {
x: mazeContainer.x + (mazeWidth - 1) * cellSize + cellSize / 2
}, {
duration: 50,
easing: tween.easeOut
});
ball.velocityX = -Math.abs(ball.velocityX) * ball.bounceForce;
// Add slight random variation for realism
ball.velocityY += (Math.random() - 0.5) * 0.5;
}
if (ball.y < mazeContainer.y + cellSize / 2) {
tween(ball, {
y: mazeContainer.y + cellSize / 2
}, {
duration: 50,
easing: tween.easeOut
});
ball.velocityY = Math.abs(ball.velocityY) * ball.bounceForce;
// Add slight random variation for realism
ball.velocityX += (Math.random() - 0.5) * 0.5;
}
if (ball.y > mazeContainer.y + (mazeHeight - 1) * cellSize + cellSize / 2) {
tween(ball, {
y: mazeContainer.y + (mazeHeight - 1) * cellSize + cellSize / 2
}, {
duration: 50,
easing: tween.easeOut
});
ball.velocityY = -Math.abs(ball.velocityY) * ball.bounceForce;
// Add slight random variation for realism
ball.velocityX += (Math.random() - 0.5) * 0.5;
}
// Ball remains rigid - no shake effects
LK.getSound('bounce').play();
return;
}
var currentCell = maze[ballMazeY][ballMazeX];
// Check if ball is in a wall cell - if so, push it back to nearest path
if (currentCell && currentCell.cellType === 'wall') {
// Find the nearest path cell
var nearestPath = null;
var minDistance = Infinity;
for (var y = 0; y < mazeHeight; y++) {
for (var x = 0; x < mazeWidth; x++) {
if (maze[y] && maze[y][x] && (maze[y][x].cellType === 'path' || maze[y][x].cellType === 'blackhole')) {
var pathCenterX = mazeContainer.x + x * cellSize + cellSize / 2;
var pathCenterY = mazeContainer.y + y * cellSize + cellSize / 2;
var distance = Math.sqrt(Math.pow(ball.x - pathCenterX, 2) + Math.pow(ball.y - pathCenterY, 2));
if (distance < minDistance) {
minDistance = distance;
nearestPath = {
x: pathCenterX,
y: pathCenterY
};
}
}
}
}
if (nearestPath) {
// Push ball to nearest path
var directionX = nearestPath.x - ball.x;
var directionY = nearestPath.y - ball.y;
var distance = Math.sqrt(directionX * directionX + directionY * directionY);
if (distance > 0) {
directionX /= distance;
directionY /= distance;
var targetX = nearestPath.x - directionX * 10;
var targetY = nearestPath.y - directionY * 10;
// Smooth tween to new position
tween(ball, {
x: targetX,
y: targetY
}, {
duration: 100,
easing: tween.easeOut
});
}
// Realistic wall collision with momentum conservation
var impactSpeed = Math.sqrt(ball.velocityX * ball.velocityX + ball.velocityY * ball.velocityY);
ball.velocityX = -ball.velocityX * ball.bounceForce;
ball.velocityY = -ball.velocityY * ball.bounceForce;
// Add energy loss and slight randomness for realism
var energyLoss = 0.9;
ball.velocityX *= energyLoss;
ball.velocityY *= energyLoss;
ball.velocityX += (Math.random() - 0.5) * 0.3;
ball.velocityY += (Math.random() - 0.5) * 0.3;
}
// Ball remains rigid - no shake effects
LK.getSound('bounce').play();
} else if (currentCell && currentCell.cellType === 'blackhole') {
// Check if ball is close enough to black hole center
var blackholeCenterX = mazeContainer.x + blackholeX * cellSize + cellSize / 2;
var blackholeCenterY = mazeContainer.y + blackholeY * cellSize + cellSize / 2;
var distance = Math.sqrt(Math.pow(ball.x - blackholeCenterX, 2) + Math.pow(ball.y - blackholeCenterY, 2));
if (distance < 30 && !gameWon) {
gameWon = true;
LK.getSound('win').play();
LK.setScore(LK.getScore() + 1);
scoreText.setText('Level: ' + (LK.getScore() + 1));
// Flash effect
LK.effects.flashScreen(0x00ff00, 1000);
// Reset for next level
LK.setTimeout(function () {
resetLevel();
}, 1000);
}
}
}
function resetLevel() {
gameWon = false;
timeLeft = 60;
// Reset ball position
ball.x = mazeContainer.x + cellSize * 1.5;
ball.y = mazeContainer.y + cellSize * 1.5;
ball.velocityX = 0;
ball.velocityY = 0;
// Generate new maze
generateMaze();
}
// Initialize ball
ball = game.addChild(new Ball());
ball.x = mazeContainer.x + cellSize * 1.5;
ball.y = mazeContainer.y + cellSize * 1.5;
// Touch controls
game.down = function (x, y, obj) {
if (gameWon) return;
// Determine tap location and apply directional force
var screenWidth = 2048;
var tapX = x;
var jumpForce = -15; // Realistic upward impulse
var sideForce = 8; // Realistic horizontal impulse
if (tapX < screenWidth / 3) {
// Left tap - bounce left and up
ball.addForce(-sideForce, jumpForce);
} else if (tapX > screenWidth * 2 / 3) {
// Right tap - bounce right and up
ball.addForce(sideForce, jumpForce);
} else {
// Center tap - bounce straight up
ball.addForce(0, jumpForce);
}
};
// Generate initial maze
generateMaze();
// Timer countdown
var timerInterval = LK.setInterval(function () {
if (gameWon) return;
timeLeft--;
timerText.setText(timeLeft.toString());
if (timeLeft <= 0) {
timeLeft = 60;
generateMaze();
timerText.setText('60');
// Flash red to indicate maze change
LK.effects.flashScreen(0xff0000, 500);
}
}, 1000);
// Start background music
LK.playMusic('1tik');
game.update = function () {
// Game updates are handled by individual object update methods
};
lubang warp penuh warna. In-Game asset. 2d. High contrast. No shadows
warna pink dinding In-Game asset. 2d. High contrast. No shadows
bola penuh hitam. In-Game asset. 2d. High contrast. No shadows
malam bulan purnama diatas hutan dataran tinggi. di kejauhan nampak kota di malam hari. In-Game asset. 2d. High contrast. No shadows