/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Ball: The player-controlled ball var Ball = Container.expand(function () { var self = Container.call(this); var ballSize = 0; self.setSize = function (size) { ballSize = size; if (self.ballAsset) { self.removeChild(self.ballAsset); } self.ballAsset = self.attachAsset('ball', { width: ballSize, height: ballSize, color: 0x3a8cff, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); }; self.radius = function () { return ballSize / 2; }; return self; }); // Finish: The goal area var Finish = Container.expand(function () { var self = Container.call(this); var finishSize = 0; self.setSize = function (size) { finishSize = size; if (self.finishAsset) { self.removeChild(self.finishAsset); } self.finishAsset = self.attachAsset('finish', { width: finishSize, height: finishSize, color: 0x44de83, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); }; return self; }); // MazeCell: Represents a single cell in the maze grid var MazeCell = Container.expand(function () { var self = Container.call(this); // Walls: {top, right, bottom, left} self.walls = [true, true, true, true]; self.visited = false; self.xIndex = 0; self.yIndex = 0; self.size = 0; self.wallGraphics = []; // Draws the cell walls self.draw = function () { // Remove old wall graphics for (var i = 0; i < self.wallGraphics.length; i++) { self.removeChild(self.wallGraphics[i]); } self.wallGraphics = []; var s = self.size; var wallColor = 0x222222; var wallThickness = Math.max(8, Math.floor(s * 0.12)); // Top wall if (self.walls[0]) { var topWall = LK.getAsset('wall', { width: s, height: wallThickness, color: wallColor, shape: 'box', anchorX: 0, anchorY: 0 }); topWall.x = 0; topWall.y = 0; self.addChild(topWall); self.wallGraphics.push(topWall); } // Right wall if (self.walls[1]) { var rightWall = LK.getAsset('wall', { width: wallThickness, height: s, color: wallColor, shape: 'box', anchorX: 0, anchorY: 0 }); rightWall.x = s - wallThickness; rightWall.y = 0; self.addChild(rightWall); self.wallGraphics.push(rightWall); } // Bottom wall if (self.walls[2]) { var bottomWall = LK.getAsset('wall', { width: s, height: wallThickness, color: wallColor, shape: 'box', anchorX: 0, anchorY: 0 }); bottomWall.x = 0; bottomWall.y = s - wallThickness; self.addChild(bottomWall); self.wallGraphics.push(bottomWall); } // Left wall if (self.walls[3]) { var leftWall = LK.getAsset('wall', { width: wallThickness, height: s, color: wallColor, shape: 'box', anchorX: 0, anchorY: 0 }); leftWall.x = 0; leftWall.y = 0; self.addChild(leftWall); self.wallGraphics.push(leftWall); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xf7f7f7 }); /**** * Game Code ****/ // Maze parameters var MAZE_COLS = 18; var MAZE_ROWS = 24; var MAZE_MARGIN = 40; // px margin around maze (reduce margin to fit bigger maze) var mazeCellSize = 0; var mazeOriginX = 0; var mazeOriginY = 0; var maze = []; var mazeContainer = new Container(); game.addChild(mazeContainer); // Ball and Finish var ball = null; var finish = null; // Ball physics var ballPos = { x: 0, y: 0 }; var ballVel = { x: 0, y: 0 }; var ballMaxSpeed = 32; var ballAccel = 2.2; var ballFriction = 0.96; // Gyro state var gyro = { x: 0, y: 0 }; // Timer var startTime = 0; var elapsedTime = 0; // Final score text removed as per requirements // Helper: Clamp function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } // Helper: Shuffle array function shuffle(arr) { for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var t = arr[i]; arr[i] = arr[j]; arr[j] = t; } return arr; } // Maze generation: Recursive backtracker with bias for normal difficulty route function generateMaze(cols, rows) { var grid = []; for (var y = 0; y < rows; y++) { var row = []; for (var x = 0; x < cols; x++) { var cell = new MazeCell(); cell.xIndex = x; cell.yIndex = y; cell.size = mazeCellSize; cell.visited = false; row.push(cell); } grid.push(row); } function cellAt(x, y) { if (x < 0 || y < 0 || x >= cols || y >= rows) return null; return grid[y][x]; } var stack = []; var current = grid[0][0]; current.visited = true; var visitedCount = 1; var totalCells = cols * rows; // Bias: prefer to move generally toward the finish, but not always var finishX = cols - 1; var finishY = rows - 1; while (visitedCount < totalCells) { // Find unvisited neighbors var neighbors = []; var dirs = [[0, -1, 0], // top [1, 0, 1], // right [0, 1, 2], // bottom [-1, 0, 3] // left ]; for (var d = 0; d < 4; d++) { var nx = current.xIndex + dirs[d][0]; var ny = current.yIndex + dirs[d][1]; var neighbor = cellAt(nx, ny); if (neighbor && !neighbor.visited) { neighbors.push({ cell: neighbor, dir: d }); } } if (neighbors.length > 0) { // Bias: 60% chance to pick neighbor that is closer to finish, else random var pick; if (Math.random() < 0.6) { // Sort neighbors by distance to finish, pick the closest neighbors.sort(function (a, b) { var da = Math.abs(a.cell.xIndex - finishX) + Math.abs(a.cell.yIndex - finishY); var db = Math.abs(b.cell.xIndex - finishX) + Math.abs(b.cell.yIndex - finishY); return da - db; }); // 50% chance to pick the closest, 50% chance to pick second closest (if exists) if (neighbors.length > 1 && Math.random() < 0.5) { pick = neighbors[1]; } else { pick = neighbors[0]; } } else { pick = neighbors[Math.floor(Math.random() * neighbors.length)]; } // Remove wall between current and neighbor current.walls[pick.dir] = false; var opp = (pick.dir + 2) % 4; pick.cell.walls[opp] = false; stack.push(current); current = pick.cell; current.visited = true; visitedCount++; } else if (stack.length > 0) { current = stack.pop(); } } // Draw all cells for (var y = 0; y < rows; y++) { for (var x = 0; x < cols; x++) { grid[y][x].draw(); } } return grid; } // Place maze in center of screen, calculate cell size function layoutMaze() { var availW = 2048 - MAZE_MARGIN * 2; var availH = 2732 - MAZE_MARGIN * 2; mazeCellSize = Math.floor(Math.min(availW / MAZE_COLS, availH / MAZE_ROWS)); var mazeW = mazeCellSize * MAZE_COLS; var mazeH = mazeCellSize * MAZE_ROWS; mazeOriginX = Math.floor((2048 - mazeW) / 2); mazeOriginY = Math.floor((2732 - mazeH) / 2); mazeContainer.x = mazeOriginX; mazeContainer.y = mazeOriginY; } // Remove all children from mazeContainer function clearMaze() { while (mazeContainer.children.length > 0) { mazeContainer.removeChild(mazeContainer.children[0]); } } // Place ball at start function placeBall() { if (ball) { ball.destroy(); } ball = new Ball(); ball.setSize(Math.floor(mazeCellSize * 0.55)); ballPos.x = mazeCellSize / 2; ballPos.y = mazeCellSize / 2; ball.x = ballPos.x; ball.y = ballPos.y; ballVel.x = 0; ballVel.y = 0; mazeContainer.addChild(ball); } // Place finish at bottom-right cell function placeFinish() { if (finish) { finish.destroy(); } finish = new Finish(); finish.setSize(Math.floor(mazeCellSize * 0.55)); // Pick a random cell in the maze that is not the start cell (0,0) var finishCellX = 0; var finishCellY = 0; while (finishCellX === 0 && finishCellY === 0) { finishCellX = Math.floor(Math.random() * MAZE_COLS); finishCellY = Math.floor(Math.random() * MAZE_ROWS); } var fx = finishCellX * mazeCellSize + mazeCellSize / 2; var fy = finishCellY * mazeCellSize + mazeCellSize / 2; finish.x = fx; finish.y = fy; mazeContainer.addChild(finish); // Store finish cell for win check finish.finishCellX = finishCellX; finish.finishCellY = finishCellY; } // Check collision between ball and maze walls function resolveBallMazeCollision() { var r = ball.radius(); var cx = Math.floor(ballPos.x / mazeCellSize); var cy = Math.floor(ballPos.y / mazeCellSize); // Clamp to maze bounds cx = clamp(cx, 0, MAZE_COLS - 1); cy = clamp(cy, 0, MAZE_ROWS - 1); var cell = maze[cy][cx]; var px = ballPos.x - cx * mazeCellSize; var py = ballPos.y - cy * mazeCellSize; var s = mazeCellSize; var wallThickness = Math.max(8, Math.floor(s * 0.12)); // Top wall if (cell.walls[0] && py - r < wallThickness) { ballPos.y = cy * mazeCellSize + wallThickness + r; ballVel.y = Math.max(0, ballVel.y); } // Right wall if (cell.walls[1] && px + r > s - wallThickness) { ballPos.x = cx * mazeCellSize + s - wallThickness - r; ballVel.x = Math.min(0, ballVel.x); } // Bottom wall if (cell.walls[2] && py + r > s - wallThickness) { ballPos.y = cy * mazeCellSize + s - wallThickness - r; ballVel.y = Math.min(0, ballVel.y); } // Left wall if (cell.walls[3] && px - r < wallThickness) { ballPos.x = cx * mazeCellSize + wallThickness + r; ballVel.x = Math.max(0, ballVel.x); } // Clamp to maze area ballPos.x = clamp(ballPos.x, r, MAZE_COLS * mazeCellSize - r); ballPos.y = clamp(ballPos.y, r, MAZE_ROWS * mazeCellSize - r); } // Check if ball reached finish function checkBallFinish() { // Use the actual finish cell position var fx = finish.finishCellX * mazeCellSize + mazeCellSize / 2; var fy = finish.finishCellY * mazeCellSize + mazeCellSize / 2; var dx = ballPos.x - fx; var dy = ballPos.y - fy; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < ball.radius() + finish.finishAsset.width / 2 - 6) { return true; } return false; } // Start new game function startGame() { clearMaze(); layoutMaze(); maze = generateMaze(MAZE_COLS, MAZE_ROWS); // Add all cells to container for (var y = 0; y < MAZE_ROWS; y++) { for (var x = 0; x < MAZE_COLS; x++) { var cell = maze[y][x]; cell.x = x * mazeCellSize; cell.y = y * mazeCellSize; mazeContainer.addChild(cell); } } placeFinish(); placeBall(); startTime = Date.now(); elapsedTime = 0; } // Gyroscope/motion support function handleDeviceMotion(event) { // event.accelerationIncludingGravity.{x,y,z} // On iOS, x is left/right, y is up/down, z is toward/away // On Android, axes may be swapped, so we allow both var ax = 0, ay = 0; if (event.accelerationIncludingGravity) { ax = event.accelerationIncludingGravity.x || 0; ay = event.accelerationIncludingGravity.y || 0; } // Heuristic: On portrait, invert y for natural tilt gyro.x = clamp(ax, -6, 6); gyro.y = clamp(-ay, -6, 6); } if (typeof window !== "undefined" && window.addEventListener) { window.addEventListener('devicemotion', handleDeviceMotion, true); } // Fallback: Touch drag to control ball if no gyro var dragging = false; var lastTouch = { x: 0, y: 0 }; game.down = function (x, y, obj) { // Only start drag if inside maze area var gx = x - mazeOriginX; var gy = y - mazeOriginY; if (gx >= 0 && gx < mazeCellSize * MAZE_COLS && gy >= 0 && gy < mazeCellSize * MAZE_ROWS) { dragging = true; lastTouch.x = x; lastTouch.y = y; } }; game.up = function (x, y, obj) { dragging = false; }; game.move = function (x, y, obj) { if (dragging) { var dx = x - lastTouch.x; var dy = y - lastTouch.y; // Simulate tilt by touch drag gyro.x = clamp(dx * 0.2, -6, 6); gyro.y = clamp(dy * 0.2, -6, 6); lastTouch.x = x; lastTouch.y = y; } }; // Main update loop game.update = function () { // Ball physics // Use gyro.x/gyro.y as acceleration ballVel.x += ballAccel * gyro.x * 0.18; ballVel.y += ballAccel * gyro.y * 0.18; // Friction ballVel.x *= ballFriction; ballVel.y *= ballFriction; // Clamp speed ballVel.x = clamp(ballVel.x, -ballMaxSpeed, ballMaxSpeed); ballVel.y = clamp(ballVel.y, -ballMaxSpeed, ballMaxSpeed); // Move ball ballPos.x += ballVel.x; ballPos.y += ballVel.y; resolveBallMazeCollision(); ball.x = ballPos.x; ball.y = ballPos.y; // Timer elapsedTime = (Date.now() - startTime) / 1000; // Win condition if (checkBallFinish()) { LK.getSound('finish_reached').play(); LK.effects.flashScreen(0x44de83, 800); LK.showYouWin(); } }; // Start game on load startGame(); // Reset game on game over or win LK.on('gameover', function () { startGame(); }); LK.on('youwin', function () { startGame(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Ball: The player-controlled ball
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballSize = 0;
self.setSize = function (size) {
ballSize = size;
if (self.ballAsset) {
self.removeChild(self.ballAsset);
}
self.ballAsset = self.attachAsset('ball', {
width: ballSize,
height: ballSize,
color: 0x3a8cff,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
};
self.radius = function () {
return ballSize / 2;
};
return self;
});
// Finish: The goal area
var Finish = Container.expand(function () {
var self = Container.call(this);
var finishSize = 0;
self.setSize = function (size) {
finishSize = size;
if (self.finishAsset) {
self.removeChild(self.finishAsset);
}
self.finishAsset = self.attachAsset('finish', {
width: finishSize,
height: finishSize,
color: 0x44de83,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
};
return self;
});
// MazeCell: Represents a single cell in the maze grid
var MazeCell = Container.expand(function () {
var self = Container.call(this);
// Walls: {top, right, bottom, left}
self.walls = [true, true, true, true];
self.visited = false;
self.xIndex = 0;
self.yIndex = 0;
self.size = 0;
self.wallGraphics = [];
// Draws the cell walls
self.draw = function () {
// Remove old wall graphics
for (var i = 0; i < self.wallGraphics.length; i++) {
self.removeChild(self.wallGraphics[i]);
}
self.wallGraphics = [];
var s = self.size;
var wallColor = 0x222222;
var wallThickness = Math.max(8, Math.floor(s * 0.12));
// Top wall
if (self.walls[0]) {
var topWall = LK.getAsset('wall', {
width: s,
height: wallThickness,
color: wallColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
topWall.x = 0;
topWall.y = 0;
self.addChild(topWall);
self.wallGraphics.push(topWall);
}
// Right wall
if (self.walls[1]) {
var rightWall = LK.getAsset('wall', {
width: wallThickness,
height: s,
color: wallColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
rightWall.x = s - wallThickness;
rightWall.y = 0;
self.addChild(rightWall);
self.wallGraphics.push(rightWall);
}
// Bottom wall
if (self.walls[2]) {
var bottomWall = LK.getAsset('wall', {
width: s,
height: wallThickness,
color: wallColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
bottomWall.x = 0;
bottomWall.y = s - wallThickness;
self.addChild(bottomWall);
self.wallGraphics.push(bottomWall);
}
// Left wall
if (self.walls[3]) {
var leftWall = LK.getAsset('wall', {
width: wallThickness,
height: s,
color: wallColor,
shape: 'box',
anchorX: 0,
anchorY: 0
});
leftWall.x = 0;
leftWall.y = 0;
self.addChild(leftWall);
self.wallGraphics.push(leftWall);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xf7f7f7
});
/****
* Game Code
****/
// Maze parameters
var MAZE_COLS = 18;
var MAZE_ROWS = 24;
var MAZE_MARGIN = 40; // px margin around maze (reduce margin to fit bigger maze)
var mazeCellSize = 0;
var mazeOriginX = 0;
var mazeOriginY = 0;
var maze = [];
var mazeContainer = new Container();
game.addChild(mazeContainer);
// Ball and Finish
var ball = null;
var finish = null;
// Ball physics
var ballPos = {
x: 0,
y: 0
};
var ballVel = {
x: 0,
y: 0
};
var ballMaxSpeed = 32;
var ballAccel = 2.2;
var ballFriction = 0.96;
// Gyro state
var gyro = {
x: 0,
y: 0
};
// Timer
var startTime = 0;
var elapsedTime = 0;
// Final score text removed as per requirements
// Helper: Clamp
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Helper: Shuffle array
function shuffle(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
return arr;
}
// Maze generation: Recursive backtracker with bias for normal difficulty route
function generateMaze(cols, rows) {
var grid = [];
for (var y = 0; y < rows; y++) {
var row = [];
for (var x = 0; x < cols; x++) {
var cell = new MazeCell();
cell.xIndex = x;
cell.yIndex = y;
cell.size = mazeCellSize;
cell.visited = false;
row.push(cell);
}
grid.push(row);
}
function cellAt(x, y) {
if (x < 0 || y < 0 || x >= cols || y >= rows) return null;
return grid[y][x];
}
var stack = [];
var current = grid[0][0];
current.visited = true;
var visitedCount = 1;
var totalCells = cols * rows;
// Bias: prefer to move generally toward the finish, but not always
var finishX = cols - 1;
var finishY = rows - 1;
while (visitedCount < totalCells) {
// Find unvisited neighbors
var neighbors = [];
var dirs = [[0, -1, 0],
// top
[1, 0, 1],
// right
[0, 1, 2],
// bottom
[-1, 0, 3] // left
];
for (var d = 0; d < 4; d++) {
var nx = current.xIndex + dirs[d][0];
var ny = current.yIndex + dirs[d][1];
var neighbor = cellAt(nx, ny);
if (neighbor && !neighbor.visited) {
neighbors.push({
cell: neighbor,
dir: d
});
}
}
if (neighbors.length > 0) {
// Bias: 60% chance to pick neighbor that is closer to finish, else random
var pick;
if (Math.random() < 0.6) {
// Sort neighbors by distance to finish, pick the closest
neighbors.sort(function (a, b) {
var da = Math.abs(a.cell.xIndex - finishX) + Math.abs(a.cell.yIndex - finishY);
var db = Math.abs(b.cell.xIndex - finishX) + Math.abs(b.cell.yIndex - finishY);
return da - db;
});
// 50% chance to pick the closest, 50% chance to pick second closest (if exists)
if (neighbors.length > 1 && Math.random() < 0.5) {
pick = neighbors[1];
} else {
pick = neighbors[0];
}
} else {
pick = neighbors[Math.floor(Math.random() * neighbors.length)];
}
// Remove wall between current and neighbor
current.walls[pick.dir] = false;
var opp = (pick.dir + 2) % 4;
pick.cell.walls[opp] = false;
stack.push(current);
current = pick.cell;
current.visited = true;
visitedCount++;
} else if (stack.length > 0) {
current = stack.pop();
}
}
// Draw all cells
for (var y = 0; y < rows; y++) {
for (var x = 0; x < cols; x++) {
grid[y][x].draw();
}
}
return grid;
}
// Place maze in center of screen, calculate cell size
function layoutMaze() {
var availW = 2048 - MAZE_MARGIN * 2;
var availH = 2732 - MAZE_MARGIN * 2;
mazeCellSize = Math.floor(Math.min(availW / MAZE_COLS, availH / MAZE_ROWS));
var mazeW = mazeCellSize * MAZE_COLS;
var mazeH = mazeCellSize * MAZE_ROWS;
mazeOriginX = Math.floor((2048 - mazeW) / 2);
mazeOriginY = Math.floor((2732 - mazeH) / 2);
mazeContainer.x = mazeOriginX;
mazeContainer.y = mazeOriginY;
}
// Remove all children from mazeContainer
function clearMaze() {
while (mazeContainer.children.length > 0) {
mazeContainer.removeChild(mazeContainer.children[0]);
}
}
// Place ball at start
function placeBall() {
if (ball) {
ball.destroy();
}
ball = new Ball();
ball.setSize(Math.floor(mazeCellSize * 0.55));
ballPos.x = mazeCellSize / 2;
ballPos.y = mazeCellSize / 2;
ball.x = ballPos.x;
ball.y = ballPos.y;
ballVel.x = 0;
ballVel.y = 0;
mazeContainer.addChild(ball);
}
// Place finish at bottom-right cell
function placeFinish() {
if (finish) {
finish.destroy();
}
finish = new Finish();
finish.setSize(Math.floor(mazeCellSize * 0.55));
// Pick a random cell in the maze that is not the start cell (0,0)
var finishCellX = 0;
var finishCellY = 0;
while (finishCellX === 0 && finishCellY === 0) {
finishCellX = Math.floor(Math.random() * MAZE_COLS);
finishCellY = Math.floor(Math.random() * MAZE_ROWS);
}
var fx = finishCellX * mazeCellSize + mazeCellSize / 2;
var fy = finishCellY * mazeCellSize + mazeCellSize / 2;
finish.x = fx;
finish.y = fy;
mazeContainer.addChild(finish);
// Store finish cell for win check
finish.finishCellX = finishCellX;
finish.finishCellY = finishCellY;
}
// Check collision between ball and maze walls
function resolveBallMazeCollision() {
var r = ball.radius();
var cx = Math.floor(ballPos.x / mazeCellSize);
var cy = Math.floor(ballPos.y / mazeCellSize);
// Clamp to maze bounds
cx = clamp(cx, 0, MAZE_COLS - 1);
cy = clamp(cy, 0, MAZE_ROWS - 1);
var cell = maze[cy][cx];
var px = ballPos.x - cx * mazeCellSize;
var py = ballPos.y - cy * mazeCellSize;
var s = mazeCellSize;
var wallThickness = Math.max(8, Math.floor(s * 0.12));
// Top wall
if (cell.walls[0] && py - r < wallThickness) {
ballPos.y = cy * mazeCellSize + wallThickness + r;
ballVel.y = Math.max(0, ballVel.y);
}
// Right wall
if (cell.walls[1] && px + r > s - wallThickness) {
ballPos.x = cx * mazeCellSize + s - wallThickness - r;
ballVel.x = Math.min(0, ballVel.x);
}
// Bottom wall
if (cell.walls[2] && py + r > s - wallThickness) {
ballPos.y = cy * mazeCellSize + s - wallThickness - r;
ballVel.y = Math.min(0, ballVel.y);
}
// Left wall
if (cell.walls[3] && px - r < wallThickness) {
ballPos.x = cx * mazeCellSize + wallThickness + r;
ballVel.x = Math.max(0, ballVel.x);
}
// Clamp to maze area
ballPos.x = clamp(ballPos.x, r, MAZE_COLS * mazeCellSize - r);
ballPos.y = clamp(ballPos.y, r, MAZE_ROWS * mazeCellSize - r);
}
// Check if ball reached finish
function checkBallFinish() {
// Use the actual finish cell position
var fx = finish.finishCellX * mazeCellSize + mazeCellSize / 2;
var fy = finish.finishCellY * mazeCellSize + mazeCellSize / 2;
var dx = ballPos.x - fx;
var dy = ballPos.y - fy;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < ball.radius() + finish.finishAsset.width / 2 - 6) {
return true;
}
return false;
}
// Start new game
function startGame() {
clearMaze();
layoutMaze();
maze = generateMaze(MAZE_COLS, MAZE_ROWS);
// Add all cells to container
for (var y = 0; y < MAZE_ROWS; y++) {
for (var x = 0; x < MAZE_COLS; x++) {
var cell = maze[y][x];
cell.x = x * mazeCellSize;
cell.y = y * mazeCellSize;
mazeContainer.addChild(cell);
}
}
placeFinish();
placeBall();
startTime = Date.now();
elapsedTime = 0;
}
// Gyroscope/motion support
function handleDeviceMotion(event) {
// event.accelerationIncludingGravity.{x,y,z}
// On iOS, x is left/right, y is up/down, z is toward/away
// On Android, axes may be swapped, so we allow both
var ax = 0,
ay = 0;
if (event.accelerationIncludingGravity) {
ax = event.accelerationIncludingGravity.x || 0;
ay = event.accelerationIncludingGravity.y || 0;
}
// Heuristic: On portrait, invert y for natural tilt
gyro.x = clamp(ax, -6, 6);
gyro.y = clamp(-ay, -6, 6);
}
if (typeof window !== "undefined" && window.addEventListener) {
window.addEventListener('devicemotion', handleDeviceMotion, true);
}
// Fallback: Touch drag to control ball if no gyro
var dragging = false;
var lastTouch = {
x: 0,
y: 0
};
game.down = function (x, y, obj) {
// Only start drag if inside maze area
var gx = x - mazeOriginX;
var gy = y - mazeOriginY;
if (gx >= 0 && gx < mazeCellSize * MAZE_COLS && gy >= 0 && gy < mazeCellSize * MAZE_ROWS) {
dragging = true;
lastTouch.x = x;
lastTouch.y = y;
}
};
game.up = function (x, y, obj) {
dragging = false;
};
game.move = function (x, y, obj) {
if (dragging) {
var dx = x - lastTouch.x;
var dy = y - lastTouch.y;
// Simulate tilt by touch drag
gyro.x = clamp(dx * 0.2, -6, 6);
gyro.y = clamp(dy * 0.2, -6, 6);
lastTouch.x = x;
lastTouch.y = y;
}
};
// Main update loop
game.update = function () {
// Ball physics
// Use gyro.x/gyro.y as acceleration
ballVel.x += ballAccel * gyro.x * 0.18;
ballVel.y += ballAccel * gyro.y * 0.18;
// Friction
ballVel.x *= ballFriction;
ballVel.y *= ballFriction;
// Clamp speed
ballVel.x = clamp(ballVel.x, -ballMaxSpeed, ballMaxSpeed);
ballVel.y = clamp(ballVel.y, -ballMaxSpeed, ballMaxSpeed);
// Move ball
ballPos.x += ballVel.x;
ballPos.y += ballVel.y;
resolveBallMazeCollision();
ball.x = ballPos.x;
ball.y = ballPos.y;
// Timer
elapsedTime = (Date.now() - startTime) / 1000;
// Win condition
if (checkBallFinish()) {
LK.getSound('finish_reached').play();
LK.effects.flashScreen(0x44de83, 800);
LK.showYouWin();
}
};
// Start game on load
startGame();
// Reset game on game over or win
LK.on('gameover', function () {
startGame();
});
LK.on('youwin', function () {
startGame();
});