/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Door class var Door = Container.expand(function () { var self = Container.call(this); self.locked = true; self.doorAsset = self.attachAsset('door_locked', { anchorX: 0.5, anchorY: 0.5 }); self.unlock = function () { self.locked = false; self.doorAsset.destroy(); self.doorAsset = self.attachAsset('door_unlocked', { anchorX: 0.5, anchorY: 0.5 }); }; return self; }); // Enemy (ghost) class var Ghost = Container.expand(function () { var self = Container.call(this); self.attachAsset('ghost', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 4.4; self.dirX = 0; self.dirY = 0; self.changeDirCooldown = 0; // Helper to pick a new random direction (including diagonals, but not standing still) function pickRandomDirection() { var dirs = [{ x: 1, y: 0 }, // right { x: -1, y: 0 }, // left { x: 0, y: 1 }, // down { x: 0, y: -1 }, // up { x: 1, y: 1 }, // down-right { x: -1, y: 1 }, // down-left { x: 1, y: -1 }, // up-right { x: -1, y: -1 } // up-left ]; var idx = Math.floor(Math.random() * dirs.length); self.dirX = dirs[idx].x; self.dirY = dirs[idx].y; } pickRandomDirection(); self.changeDirCooldown = 60 + Math.floor(Math.random() * 60); // 1-2 seconds self.update = function () { // Save last position for possible collision logic if (typeof self.lastX === "undefined") self.lastX = self.x; if (typeof self.lastY === "undefined") self.lastY = self.y; // If player exists, chase the player if (typeof player !== "undefined" && player) { var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { // Normalize direction var nx = dx / dist; var ny = dy / dist; // Move towards player self.x += self.speed * nx; self.y += self.speed * ny; } } else { // Fallback: random movement if no player self.x += self.speed * self.dirX; self.y += self.speed * self.dirY; } // Stay inside game area (bounce if hit edge) var bounced = false; if (self.x < 120) { self.x = 120; bounced = true; } if (self.x > 2048 - 120) { self.x = 2048 - 120; bounced = true; } if (self.y < 120) { self.y = 120; bounced = true; } if (self.y > 2732 - 120) { self.y = 2732 - 120; bounced = true; } // (No random direction change needed for chase mode) self.lastX = self.x; self.lastY = self.y; }; return self; }); // Key class (was Puzzle) var Key = Container.expand(function () { var self = Container.call(this); self.solved = false; self.attachAsset('puzzle', { anchorX: 0.5, anchorY: 0.5 }); return self; }); // Player class var Player = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.canMove = true; self.moveTo = function (x, y) { if (!self.canMove) return; // Clamp to game area var px = Math.max(60, Math.min(2048 - 60, x)); var py = Math.max(60, Math.min(2732 - 60, y)); self.x = px; self.y = py; }; return self; }); // Wall class var Wall = Container.expand(function () { var self = Container.call(this); var wallAsset = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); self.isSolid = true; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Add background image to the game area // Enemy (ghost) // Puzzle object // Key // Door (unlocked) // Door (locked) // Wall // Player // --- Game State --- var backgroundImg = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChild(backgroundImg); var currentFloor = 0; // 0: bottom, 1: 2nd, 2: 3rd, 3: 4th, 4: 5th, 5: top var floors = []; var player; var dragPlayer = false; var lastPlayerPos = { x: 0, y: 0 }; var keyObj = null; var doorObj = null; var ghostObj = null; var messageTxt = null; var floorTxt = null; var keySolved = false; var gameOver = false; // --- Room/Floor Layouts --- // Floor 0: Bottom floor (simple room, 1 puzzle, 1 ghost) function createFloor0() { var floor = { walls: [], doors: [], keys: [], puzzles: [], enemies: [] }; // Rectangle room // Door (random position, not under floor list area) var door = new Door(); do { door.x = 200 + Math.random() * (2048 - 400); door.y = 200 + Math.random() * (2732 - 400); } while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list floor.doors.push(door); // Key (random position, not under floor list area) var key = new Key(); do { key.x = 200 + Math.random() * (2048 - 400); key.y = 200 + Math.random() * (2732 - 400); } while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list floor.puzzles.push(key); // Ghost (bottom right, but not close to player) var ghost = new Ghost(); do { ghost.x = 200 + Math.random() * (2048 - 400); ghost.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost); // Second ghost (top left, but not close to player) var ghost2 = new Ghost(); do { ghost2.x = 200 + Math.random() * (2048 - 400); ghost2.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost2); return floor; } // Floor 1: 2nd floor (L-shaped room, 1 puzzle, 1 ghost) // Floor 5: Four rooms, each with different shape and size, combined together var floor = { walls: [], doors: [], keys: [], puzzles: [], enemies: [] }; // --- Room 1: Large Rectangle (Top Left) --- // Top wall var wallR1Top = new Wall(); wallR1Top.x = 350; wallR1Top.y = 200; wallR1Top.width = 600; wallR1Top.height = 60; floor.walls.push(wallR1Top); // Left wall var wallR1Left = new Wall(); wallR1Left.x = 70; wallR1Left.y = 500; wallR1Left.width = 60; wallR1Left.height = 600; floor.walls.push(wallR1Left); // Bottom wall var wallR1Bottom = new Wall(); wallR1Bottom.x = 350; wallR1Bottom.y = 800; wallR1Bottom.width = 600; wallR1Bottom.height = 60; floor.walls.push(wallR1Bottom); // Right wall (shared with Room 2, has a door) // Shorten wall segments to leave a gap for the door at y=470 var wallR1RightA = new Wall(); wallR1RightA.x = 650; wallR1RightA.y = 370; // move up wallR1RightA.width = 60; wallR1RightA.height = 120; // shorter, ends above the door floor.walls.push(wallR1RightA); var wallR1RightB = new Wall(); wallR1RightB.x = 650; wallR1RightB.y = 730; wallR1RightB.width = 60; wallR1RightB.height = 200; floor.walls.push(wallR1RightB); // --- Room 2: Tall Rectangle (Top Right) --- // Top wall var wallR2Top = new Wall(); wallR2Top.x = 1200; wallR2Top.y = 200; wallR2Top.width = 600; wallR2Top.height = 60; floor.walls.push(wallR2Top); // Right wall var wallR2Right = new Wall(); wallR2Right.x = 1550; wallR2Right.y = 600; wallR2Right.width = 60; wallR2Right.height = 800; floor.walls.push(wallR2Right); // Bottom wall (shared with Room 3, has a door) // Shorten wall segments to leave a gap for the door at x=1200, y=1100 var wallR2BottomA = new Wall(); wallR2BottomA.x = 1075; wallR2BottomA.y = 1000; wallR2BottomA.width = 150; wallR2BottomA.height = 60; floor.walls.push(wallR2BottomA); var wallR2BottomB = new Wall(); wallR2BottomB.x = 1575; wallR2BottomB.y = 1000; wallR2BottomB.width = 150; wallR2BottomB.height = 60; floor.walls.push(wallR2BottomB); // Left wall (shared with Room 1, has a door) var wallR2LeftA = new Wall(); wallR2LeftA.x = 850; wallR2LeftA.y = 470; wallR2LeftA.width = 60; wallR2LeftA.height = 200; floor.walls.push(wallR2LeftA); var wallR2LeftB = new Wall(); wallR2LeftB.x = 850; wallR2LeftB.y = 730; wallR2LeftB.width = 60; wallR2LeftB.height = 200; floor.walls.push(wallR2LeftB); // --- Room 3: Square (Bottom Right) --- // Top wall (shared with Room 2, has a door) var wallR3TopA = new Wall(); wallR3TopA.x = 1200; wallR3TopA.y = 1200; wallR3TopA.width = 250; wallR3TopA.height = 60; floor.walls.push(wallR3TopA); var wallR3TopB = new Wall(); wallR3TopB.x = 1450; wallR3TopB.y = 1200; wallR3TopB.width = 250; wallR3TopB.height = 60; floor.walls.push(wallR3TopB); // Right wall var wallR3Right = new Wall(); wallR3Right.x = 1550; wallR3Right.y = 1500; wallR3Right.width = 60; wallR3Right.height = 600; floor.walls.push(wallR3Right); // Bottom wall var wallR3Bottom = new Wall(); wallR3Bottom.x = 1200; wallR3Bottom.y = 1800; wallR3Bottom.width = 600; wallR3Bottom.height = 60; floor.walls.push(wallR3Bottom); // Left wall (shared with Room 4, has a door) // Shorten wall segments to leave a gap for the door at y=1700 var wallR3LeftA = new Wall(); wallR3LeftA.x = 850; wallR3LeftA.y = 1300; wallR3LeftA.width = 60; wallR3LeftA.height = 120; floor.walls.push(wallR3LeftA); var wallR3LeftB = new Wall(); wallR3LeftB.x = 850; wallR3LeftB.y = 1820; wallR3LeftB.width = 60; wallR3LeftB.height = 120; floor.walls.push(wallR3LeftB); // --- Room 4: L-Shape (Bottom Left) --- // Top wall var wallR4Top = new Wall(); wallR4Top.x = 350; wallR4Top.y = 1200; wallR4Top.width = 600; wallR4Top.height = 60; floor.walls.push(wallR4Top); // Left wall var wallR4Left = new Wall(); wallR4Left.x = 70; wallR4Left.y = 1500; wallR4Left.width = 60; wallR4Left.height = 600; floor.walls.push(wallR4Left); // Bottom wall var wallR4Bottom = new Wall(); wallR4Bottom.x = 350; wallR4Bottom.y = 1800; wallR4Bottom.width = 600; wallR4Bottom.height = 60; floor.walls.push(wallR4Bottom); // Right wall (shared with Room 3, has a door) // Shorten wall segments to leave a gap for the door at y=1700 var wallR4RightA = new Wall(); wallR4RightA.x = 650; wallR4RightA.y = 1300; wallR4RightA.width = 60; wallR4RightA.height = 120; floor.walls.push(wallR4RightA); var wallR4RightB = new Wall(); wallR4RightB.x = 650; wallR4RightB.y = 1820; wallR4RightB.width = 60; wallR4RightB.height = 120; floor.walls.push(wallR4RightB); // --- Doors between rooms --- // Door between Room 1 and Room 2 (top horizontal) var door1 = new Door(); door1.x = 750; door1.y = 470; floor.doors.push(door1); // Door between Room 2 and Room 3 (vertical) var door2 = new Door(); door2.x = 1200; door2.y = 1100; floor.doors.push(door2); // Door between Room 3 and Room 4 (bottom horizontal) var door3 = new Door(); door3.x = 750; door3.y = 1700; floor.doors.push(door3); // Door between Room 4 and Room 1 (vertical) var door4 = new Door(); // Move door out of the wall: shift x to be just right of the wall (wall at x=70, wall thickness=60, room width=600, so wall edge at x=100, room starts at x=100, door at x=350 is center of wall, move to x=350+90=440) door4.x = 350 + 90; // move further right of wall, so door is fully reachable door4.y = 1000; floor.doors.push(door4); // Shorten bottom wall of Room 1 to leave a gap for the door at x=350, y=1000 // (wallR1Bottom is at y=800, so we need to adjust left wall of Room 4) var wallR4LeftA = new Wall(); wallR4LeftA.x = 70; wallR4LeftA.y = 1200; wallR4LeftA.width = 60; wallR4LeftA.height = 200; floor.walls.push(wallR4LeftA); var wallR4LeftB = new Wall(); wallR4LeftB.x = 70; wallR4LeftB.y = 1500; wallR4LeftB.width = 60; wallR4LeftB.height = 600; floor.walls.push(wallR4LeftB); // --- Key in Room 3 (bottom right) --- var key = new Key(); key.x = 1400; key.y = 1600; floor.puzzles.push(key); // --- Ghost in Room 2 (top right) --- var ghost = new Ghost(); ghost.x = 1300; ghost.y = 600; floor.enemies.push(ghost); function createFloor1() { var floor = { walls: [], doors: [], keys: [], puzzles: [], enemies: [] }; // L-shape: Top wall // Door (random position, not under floor list area) var door = new Door(); do { door.x = 200 + Math.random() * (2048 - 400); door.y = 200 + Math.random() * (2732 - 400); } while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list floor.doors.push(door); // Key (random position, not under floor list area) var key = new Key(); do { key.x = 200 + Math.random() * (2048 - 400); key.y = 200 + Math.random() * (2732 - 400); } while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list floor.puzzles.push(key); // Ghost (random, not close to player) var ghost = new Ghost(); do { ghost.x = 200 + Math.random() * (2048 - 400); ghost.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost); // Second ghost (random, not close to player) var ghost2 = new Ghost(); do { ghost2.x = 200 + Math.random() * (2048 - 400); ghost2.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost2); return floor; } // Floor 2: 3rd floor (T-shaped, 1 puzzle, 1 ghost) function createFloor2() { var floor = { walls: [], doors: [], keys: [], puzzles: [], enemies: [] }; // T-shape: Top wall // Door (random position, not under floor list area) var door = new Door(); do { door.x = 200 + Math.random() * (2048 - 400); door.y = 200 + Math.random() * (2732 - 400); } while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list floor.doors.push(door); // Key (random position, not under floor list area) var key = new Key(); do { key.x = 200 + Math.random() * (2048 - 400); key.y = 200 + Math.random() * (2732 - 400); } while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list floor.puzzles.push(key); // Ghost (random, not close to player) var ghost = new Ghost(); do { ghost.x = 200 + Math.random() * (2048 - 400); ghost.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost); // Second ghost (random, not close to player) var ghost2 = new Ghost(); do { ghost2.x = 200 + Math.random() * (2048 - 400); ghost2.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost2); return floor; } // Floor 3: 4th floor (U-shaped, 1 puzzle, 1 ghost) function createFloor3() { var floor = { walls: [], doors: [], keys: [], puzzles: [], enemies: [] }; // U-shape: Top wall // Door (random position, not under floor list area) var door = new Door(); do { door.x = 200 + Math.random() * (2048 - 400); door.y = 200 + Math.random() * (2732 - 400); } while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list floor.doors.push(door); // Key (random position, not under floor list area) var key = new Key(); do { key.x = 200 + Math.random() * (2048 - 400); key.y = 200 + Math.random() * (2732 - 400); } while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list floor.puzzles.push(key); // Ghost (random, not close to player) var ghost = new Ghost(); do { ghost.x = 200 + Math.random() * (2048 - 400); ghost.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost); // Second ghost (random, not close to player) var ghost2 = new Ghost(); do { ghost2.x = 200 + Math.random() * (2048 - 400); ghost2.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost2); return floor; } // Floor 4: 5th floor (original floor 0, four rooms) function createFloor4() { // Floor 5: Four rooms, each with different shape and size, combined together var floor = { walls: [], doors: [], keys: [], puzzles: [], enemies: [] }; // --- Room 1: Large Rectangle (Top Left) --- // --- Doors between rooms --- // Only keep one main door for this floor var door = new Door(); do { door.x = 200 + Math.random() * (2048 - 400); door.y = 200 + Math.random() * (2732 - 400); } while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list floor.doors.push(door); // Shorten bottom wall of Room 1 to leave a gap for the door at x=350, y=1000 // (wallR1Bottom is at y=800, so we need to adjust left wall of Room 4) // --- Key in Room 3 (random position) --- var key = new Key(); do { key.x = 200 + Math.random() * (2048 - 400); key.y = 200 + Math.random() * (2732 - 400); } while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list floor.puzzles.push(key); // --- Ghost 1 (random, not close to player) --- var ghost = new Ghost(); do { ghost.x = 200 + Math.random() * (2048 - 400); ghost.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost); // --- Ghost 2 (random, not close to player) --- var ghost2 = new Ghost(); do { ghost2.x = 200 + Math.random() * (2048 - 400); ghost2.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost2); return floor; } // Floor 5: Top floor (original floor 1, exit) function createFloor5() { var floor = { walls: [], doors: [], keys: [], puzzles: [], enemies: [] }; // Walls (rectangle room) // Exit Door (top center, unlocked if puzzle solved) var door = new Door(); door.x = 2048 / 2; door.y = 400; door.locked = false; door.doorAsset.destroy(); door.doorAsset = door.attachAsset('door_unlocked', { anchorX: 0.5, anchorY: 0.5 }); floor.doors.push(door); // Key in a random position var key = new Key(); do { key.x = 200 + Math.random() * (2048 - 400); key.y = 200 + Math.random() * (2732 - 400); } while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list floor.puzzles.push(key); // Ghost (random, not close to player) var ghost = new Ghost(); do { ghost.x = 200 + Math.random() * (2048 - 400); ghost.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost); // Second ghost (random, not close to player) var ghost2 = new Ghost(); do { ghost2.x = 200 + Math.random() * (2048 - 400); ghost2.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost2); // Upstairs: 1 room, exit door (unlocked if puzzle solved), 1 ghost var floor = { walls: [], doors: [], keys: [], puzzles: [], enemies: [] }; // Walls (rectangle room) // Exit Door (top center, unlocked if puzzle solved) var door = new Door(); door.x = 2048 / 2; door.y = 400; door.locked = false; door.doorAsset.destroy(); door.doorAsset = door.attachAsset('door_unlocked', { anchorX: 0.5, anchorY: 0.5 }); floor.doors.push(door); // Key in the center of the room var key = new Key(); key.x = 2048 / 2; key.y = 2732 / 2; floor.puzzles.push(key); // Ghost (patrols horizontally) var ghost = new Ghost(); ghost.x = 2048 / 2; ghost.y = 1200; floor.enemies.push(ghost); return floor; } // --- Helper Functions --- function clearFloor() { // Remove all objects from game if (floors[currentFloor]) { var f = floors[currentFloor]; for (var i = 0; i < f.walls.length; ++i) f.walls[i].destroy(); for (var i = 0; i < f.doors.length; ++i) f.doors[i].destroy(); for (var i = 0; i < f.keys.length; ++i) f.keys[i].destroy(); for (var i = 0; i < f.puzzles.length; ++i) f.puzzles[i].destroy(); for (var i = 0; i < f.enemies.length; ++i) f.enemies[i].destroy(); } if (doorObj) { if (typeof doorObj.destroy === "function") { doorObj.destroy(); } doorObj = null; } if (keyObj) { if (typeof keyObj.destroy === "function") { keyObj.destroy(); } keyObj = null; } if (ghostObj) { ghostObj.destroy(); ghostObj = null; } } function setupFloor(floorNum) { clearFloor(); var f = floors[floorNum]; // Add walls for (var i = 0; i < f.walls.length; ++i) game.addChild(f.walls[i]); // Add doors for (var i = 0; i < f.doors.length; ++i) game.addChild(f.doors[i]); // Add puzzles for (var i = 0; i < f.puzzles.length; ++i) game.addChild(f.puzzles[i]); // Add enemies for (var i = 0; i < f.enemies.length; ++i) game.addChild(f.enemies[i]); // For easy reference // Set doorObj to the first locked door, or the first door if none are locked doorObj = null; for (var i = 0; i < f.doors.length; ++i) { if (f.doors[i].locked) { doorObj = f.doors[i]; break; } } if (!doorObj && f.doors.length) doorObj = f.doors[0]; keyObj = null; // Set keyObj to the first unsolved key, or null if all solved keyObj = null; for (var i = 0; i < f.puzzles.length; ++i) { if (!f.puzzles[i].solved) { keyObj = f.puzzles[i]; break; } } ghostObj = f.enemies.length ? f.enemies[0] : null; } // --- GUI --- messageTxt = new Text2('', { size: 90, fill: 0xFFCCCC }); messageTxt.anchor.set(0.5, 0); LK.gui.top.addChild(messageTxt); floorTxt = new Text2('Floor 1', { size: 70, fill: 0xCCCCFF }); floorTxt.anchor.set(0.5, 0); LK.gui.top.addChild(floorTxt); // --- Game Setup --- floors[0] = createFloor0(); floors[1] = createFloor1(); floors[2] = createFloor2(); floors[3] = createFloor3(); floors[4] = createFloor4(); floors[5] = createFloor5(); // Add 14 more floors (floors 6 to 19) for (var i = 6; i < 20; ++i) { // Each new floor is a random room with 1 door, 1 key, 2 ghosts floors[i] = function () { var floor = { walls: [], doors: [], keys: [], puzzles: [], enemies: [] }; // Door (random position, not under floor list area) var door = new Door(); do { door.x = 200 + Math.random() * (2048 - 400); door.y = 200 + Math.random() * (2732 - 400); } while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list floor.doors.push(door); // Key (random position, not under floor list area) var key = new Key(); do { key.x = 200 + Math.random() * (2048 - 400); key.y = 200 + Math.random() * (2732 - 400); } while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list floor.puzzles.push(key); // Ghost 1 (random, not close to player start) var ghost = new Ghost(); do { ghost.x = 200 + Math.random() * (2048 - 400); ghost.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost); // Ghost 2 (random, not close to player start) var ghost2 = new Ghost(); do { ghost2.x = 200 + Math.random() * (2048 - 400); ghost2.y = 200 + Math.random() * (2732 - 400); } while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400); floor.enemies.push(ghost2); return floor; }(); } player = new Player(); player.x = 2048 / 2; player.y = 2732 - 700; game.addChild(player); setupFloor(0); updateFloorText(); // --- Life Counter GUI (bottom left) --- // Heart icon asset (using emoji for now, can be replaced with image asset if available) var heartIcons = []; var maxHearts = 6; // reasonable max for layout, can be increased if needed function updateHearts(lives) { // Remove old hearts for (var i = 0; i < heartIcons.length; ++i) { if (heartIcons[i] && typeof heartIcons[i].destroy === "function") { heartIcons[i].destroy(); } } heartIcons = []; // Add new hearts var spacing = 80; var size = 90; for (var i = 0; i < lives; ++i) { var heart = new Text2("❤", { size: size, fill: 0xFF3B3B }); heart.anchor.set(0, 1); // left align, bottom heart.x = i * spacing; heart.y = 0; LK.gui.bottomLeft.addChild(heart); heartIcons.push(heart); } } // Initialize with 1 life updateHearts(1); // --- Floor List GUI (top right) --- var floorListContainer = new Container(); floorListContainer.x = 0; floorListContainer.y = 0; LK.gui.topRight.addChild(floorListContainer); function updateFloorList() { // Remove old children if (floorListContainer && floorListContainer.children) { while (floorListContainer.children.length) { floorListContainer.children[0].destroy(); } } // List from top (highest floor) to bottom (lowest) var y = 40; for (var i = floors.length - 1; i >= 0; --i) { var label = "Floor " + (i + 1); var txt = new Text2(label, { size: 60, fill: i === currentFloor ? "#FFD700" : "#CCCCFF" }); txt.anchor.set(1, 0); // right align txt.x = 0; txt.y = y; if (floorListContainer && typeof floorListContainer.addChild === "function") { floorListContainer.addChild(txt); } y += 90; } } updateFloorList(); function updateFloorText() { floorTxt.setText('Floor ' + (currentFloor + 1)); updateFloorList(); } // --- Movement & Controls --- function canMoveTo(x, y) { // Check collision with walls var f = floors[currentFloor]; for (var i = 0; i < f.walls.length; ++i) { var wall = f.walls[i]; // Simple AABB collision var dx = Math.abs(x - wall.x); var dy = Math.abs(y - wall.y); if (dx < wall.width / 2 + 60 && dy < wall.height / 2 + 60) { return false; } } return true; } function handleMove(x, y, obj) { if (gameOver) return; if (!dragPlayer) return; // Clamp to game area var px = Math.max(60, Math.min(2048 - 60, x)); var py = Math.max(60, Math.min(2732 - 60, y)); // Only move if not colliding with wall if (canMoveTo(px, py)) { player.moveTo(px, py); } } game.down = function (x, y, obj) { if (gameOver) return; // Only start drag if touch/click is on player var dx = x - player.x; var dy = y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 100) { dragPlayer = true; lastPlayerPos.x = player.x; lastPlayerPos.y = player.y; handleMove(x, y, obj); } }; game.move = function (x, y, obj) { handleMove(x, y, obj); }; game.up = function (x, y, obj) { dragPlayer = false; }; // --- Puzzle Interaction --- function tryCollectKey() { if (!keyObj || keyObj.solved) return; // Simple key: tap key to collect keyObj.solved = true; keySolved = true; messageTxt.setText("You found a key!\nThe door unlocked."); // Unlock the corresponding door for the collected key var f = floors[currentFloor]; if (f && f.puzzles && f.doors) { // Find the index of the collected key in the floor's puzzles array var keyIdx = -1; for (var i = 0; i < f.puzzles.length; ++i) { if (f.puzzles[i] === keyObj) { keyIdx = i; break; } } // Unlock the door with the same index (if exists and locked) if (keyIdx >= 0 && f.doors[keyIdx] && f.doors[keyIdx].locked) { f.doors[keyIdx].unlock(); } } // Fade out the key when collected if (keyObj) { tween(keyObj, { alpha: 0 }, { duration: 800 }); } LK.setTimeout(function () { messageTxt.setText(''); }, 1500); } // --- Door Interaction --- function tryOpenDoor() { if (!doorObj) return; if (doorObj.locked) { messageTxt.setText("The door is locked."); LK.setTimeout(function () { messageTxt.setText(''); }, 1200); } else { // Go to next floor or win if (currentFloor < floors.length - 1) { // Go upstairs currentFloor++; setupFloor(currentFloor); player.x = 2048 / 2; player.y = 2732 - 700; updateFloorText(); // If reached floor 20 (index 19), win the game if (currentFloor === 19) { LK.showYouWin(); return; } messageTxt.setText("You ascend to the next floor..."); LK.setTimeout(function () { messageTxt.setText(''); }, 1200); } else { // Win! LK.showYouWin(); } } } // --- Enemy (Ghost) Collision --- function checkGhostCollision() { if (!ghostObj) return; var dx = player.x - ghostObj.x; var dy = player.y - ghostObj.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 90) { // Only trigger on new contact if (!player.lastGhostContact || !player.lastGhostContact[ghostObj] || !player.lastGhostContact[ghostObj].wasColliding) { if (typeof player.lives === "undefined") player.lives = 1; player.lives -= 1; updateHearts(player.lives); if (player.lives <= 0) { gameOver = true; player.canMove = false; messageTxt.setText("A ghost caught you!"); LK.effects.flashScreen(0x990000, 1200); LK.setTimeout(function () { LK.showGameOver(); }, 1200); } else { messageTxt.setText("A ghost hit you! Lives left: " + player.lives); LK.effects.flashScreen(0x990000, 600); player.canMove = false; // Briefly pause movement after hit LK.setTimeout(function () { player.canMove = true; messageTxt.setText(''); }, 900); } // Track collision state for this ghost if (!player.lastGhostContact) player.lastGhostContact = {}; player.lastGhostContact[ghostObj] = { wasColliding: true }; } } else { // Not colliding, reset collision state for this ghost if (player.lastGhostContact && player.lastGhostContact[ghostObj]) { player.lastGhostContact[ghostObj].wasColliding = false; } } } // --- Main Game Update --- game.update = function () { if (gameOver) return; // Ghost patrol if (ghostObj && ghostObj.update) ghostObj.update(); // Check for key interaction (support multiple keys per floor) var f = floors[currentFloor]; if (f && f.puzzles) { for (var i = 0; i < f.puzzles.length; ++i) { var k = f.puzzles[i]; if (!k.solved) { var dx = player.x - k.x; var dy = player.y - k.y; if (Math.sqrt(dx * dx + dy * dy) < 100 && dragPlayer) { keyObj = k; tryCollectKey(); break; } } } } // Check for door interaction (support multiple doors per floor) if (f && f.doors) { for (var i = 0; i < f.doors.length; ++i) { var dr = f.doors[i]; var dx = player.x - dr.x; var dy = player.y - dr.y; if (Math.abs(dx) < 80 && Math.abs(dy) < 80 && dragPlayer) { doorObj = dr; if (dr.locked) { tryOpenDoor(); } else { // Move player through the door (to the other side of the door) if (currentFloor < floors.length - 1) { // Go upstairs currentFloor++; setupFloor(currentFloor); player.x = 2048 / 2; player.y = 2732 - 700; updateFloorText(); // If reached floor 20 (index 19), win the game if (currentFloor === 19) { LK.showYouWin(); return; } // Give extra life on floor 10 (index 9) if (currentFloor === 9 && typeof player !== "undefined" && player) { if (typeof player.lives === "undefined") player.lives = 1; player.lives += 1; updateHearts(player.lives); messageTxt.setText("You gained an extra life!"); LK.setTimeout(function () { messageTxt.setText(''); }, 1500); } messageTxt.setText("You ascend to the next floor..."); LK.setTimeout(function () { messageTxt.setText(''); }, 1200); } else if (currentFloor > 0) { // Move player to one floor down currentFloor--; setupFloor(currentFloor); player.x = 2048 / 2; player.y = 2732 - 700; updateFloorText(); messageTxt.setText("You descend to the previous floor..."); LK.setTimeout(function () { messageTxt.setText(''); }, 1200); } } break; } } } // Check for ghost collision checkGhostCollision(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Door class
var Door = Container.expand(function () {
var self = Container.call(this);
self.locked = true;
self.doorAsset = self.attachAsset('door_locked', {
anchorX: 0.5,
anchorY: 0.5
});
self.unlock = function () {
self.locked = false;
self.doorAsset.destroy();
self.doorAsset = self.attachAsset('door_unlocked', {
anchorX: 0.5,
anchorY: 0.5
});
};
return self;
});
// Enemy (ghost) class
var Ghost = Container.expand(function () {
var self = Container.call(this);
self.attachAsset('ghost', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4.4;
self.dirX = 0;
self.dirY = 0;
self.changeDirCooldown = 0;
// Helper to pick a new random direction (including diagonals, but not standing still)
function pickRandomDirection() {
var dirs = [{
x: 1,
y: 0
},
// right
{
x: -1,
y: 0
},
// left
{
x: 0,
y: 1
},
// down
{
x: 0,
y: -1
},
// up
{
x: 1,
y: 1
},
// down-right
{
x: -1,
y: 1
},
// down-left
{
x: 1,
y: -1
},
// up-right
{
x: -1,
y: -1
} // up-left
];
var idx = Math.floor(Math.random() * dirs.length);
self.dirX = dirs[idx].x;
self.dirY = dirs[idx].y;
}
pickRandomDirection();
self.changeDirCooldown = 60 + Math.floor(Math.random() * 60); // 1-2 seconds
self.update = function () {
// Save last position for possible collision logic
if (typeof self.lastX === "undefined") self.lastX = self.x;
if (typeof self.lastY === "undefined") self.lastY = self.y;
// If player exists, chase the player
if (typeof player !== "undefined" && player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
// Normalize direction
var nx = dx / dist;
var ny = dy / dist;
// Move towards player
self.x += self.speed * nx;
self.y += self.speed * ny;
}
} else {
// Fallback: random movement if no player
self.x += self.speed * self.dirX;
self.y += self.speed * self.dirY;
}
// Stay inside game area (bounce if hit edge)
var bounced = false;
if (self.x < 120) {
self.x = 120;
bounced = true;
}
if (self.x > 2048 - 120) {
self.x = 2048 - 120;
bounced = true;
}
if (self.y < 120) {
self.y = 120;
bounced = true;
}
if (self.y > 2732 - 120) {
self.y = 2732 - 120;
bounced = true;
}
// (No random direction change needed for chase mode)
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
// Key class (was Puzzle)
var Key = Container.expand(function () {
var self = Container.call(this);
self.solved = false;
self.attachAsset('puzzle', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.canMove = true;
self.moveTo = function (x, y) {
if (!self.canMove) return;
// Clamp to game area
var px = Math.max(60, Math.min(2048 - 60, x));
var py = Math.max(60, Math.min(2732 - 60, y));
self.x = px;
self.y = py;
};
return self;
});
// Wall class
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallAsset = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
self.isSolid = true;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Add background image to the game area
// Enemy (ghost)
// Puzzle object
// Key
// Door (unlocked)
// Door (locked)
// Wall
// Player
// --- Game State ---
var backgroundImg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(backgroundImg);
var currentFloor = 0; // 0: bottom, 1: 2nd, 2: 3rd, 3: 4th, 4: 5th, 5: top
var floors = [];
var player;
var dragPlayer = false;
var lastPlayerPos = {
x: 0,
y: 0
};
var keyObj = null;
var doorObj = null;
var ghostObj = null;
var messageTxt = null;
var floorTxt = null;
var keySolved = false;
var gameOver = false;
// --- Room/Floor Layouts ---
// Floor 0: Bottom floor (simple room, 1 puzzle, 1 ghost)
function createFloor0() {
var floor = {
walls: [],
doors: [],
keys: [],
puzzles: [],
enemies: []
};
// Rectangle room
// Door (random position, not under floor list area)
var door = new Door();
do {
door.x = 200 + Math.random() * (2048 - 400);
door.y = 200 + Math.random() * (2732 - 400);
} while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list
floor.doors.push(door);
// Key (random position, not under floor list area)
var key = new Key();
do {
key.x = 200 + Math.random() * (2048 - 400);
key.y = 200 + Math.random() * (2732 - 400);
} while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list
floor.puzzles.push(key);
// Ghost (bottom right, but not close to player)
var ghost = new Ghost();
do {
ghost.x = 200 + Math.random() * (2048 - 400);
ghost.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost);
// Second ghost (top left, but not close to player)
var ghost2 = new Ghost();
do {
ghost2.x = 200 + Math.random() * (2048 - 400);
ghost2.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost2);
return floor;
}
// Floor 1: 2nd floor (L-shaped room, 1 puzzle, 1 ghost)
// Floor 5: Four rooms, each with different shape and size, combined together
var floor = {
walls: [],
doors: [],
keys: [],
puzzles: [],
enemies: []
};
// --- Room 1: Large Rectangle (Top Left) ---
// Top wall
var wallR1Top = new Wall();
wallR1Top.x = 350;
wallR1Top.y = 200;
wallR1Top.width = 600;
wallR1Top.height = 60;
floor.walls.push(wallR1Top);
// Left wall
var wallR1Left = new Wall();
wallR1Left.x = 70;
wallR1Left.y = 500;
wallR1Left.width = 60;
wallR1Left.height = 600;
floor.walls.push(wallR1Left);
// Bottom wall
var wallR1Bottom = new Wall();
wallR1Bottom.x = 350;
wallR1Bottom.y = 800;
wallR1Bottom.width = 600;
wallR1Bottom.height = 60;
floor.walls.push(wallR1Bottom);
// Right wall (shared with Room 2, has a door)
// Shorten wall segments to leave a gap for the door at y=470
var wallR1RightA = new Wall();
wallR1RightA.x = 650;
wallR1RightA.y = 370; // move up
wallR1RightA.width = 60;
wallR1RightA.height = 120; // shorter, ends above the door
floor.walls.push(wallR1RightA);
var wallR1RightB = new Wall();
wallR1RightB.x = 650;
wallR1RightB.y = 730;
wallR1RightB.width = 60;
wallR1RightB.height = 200;
floor.walls.push(wallR1RightB);
// --- Room 2: Tall Rectangle (Top Right) ---
// Top wall
var wallR2Top = new Wall();
wallR2Top.x = 1200;
wallR2Top.y = 200;
wallR2Top.width = 600;
wallR2Top.height = 60;
floor.walls.push(wallR2Top);
// Right wall
var wallR2Right = new Wall();
wallR2Right.x = 1550;
wallR2Right.y = 600;
wallR2Right.width = 60;
wallR2Right.height = 800;
floor.walls.push(wallR2Right);
// Bottom wall (shared with Room 3, has a door)
// Shorten wall segments to leave a gap for the door at x=1200, y=1100
var wallR2BottomA = new Wall();
wallR2BottomA.x = 1075;
wallR2BottomA.y = 1000;
wallR2BottomA.width = 150;
wallR2BottomA.height = 60;
floor.walls.push(wallR2BottomA);
var wallR2BottomB = new Wall();
wallR2BottomB.x = 1575;
wallR2BottomB.y = 1000;
wallR2BottomB.width = 150;
wallR2BottomB.height = 60;
floor.walls.push(wallR2BottomB);
// Left wall (shared with Room 1, has a door)
var wallR2LeftA = new Wall();
wallR2LeftA.x = 850;
wallR2LeftA.y = 470;
wallR2LeftA.width = 60;
wallR2LeftA.height = 200;
floor.walls.push(wallR2LeftA);
var wallR2LeftB = new Wall();
wallR2LeftB.x = 850;
wallR2LeftB.y = 730;
wallR2LeftB.width = 60;
wallR2LeftB.height = 200;
floor.walls.push(wallR2LeftB);
// --- Room 3: Square (Bottom Right) ---
// Top wall (shared with Room 2, has a door)
var wallR3TopA = new Wall();
wallR3TopA.x = 1200;
wallR3TopA.y = 1200;
wallR3TopA.width = 250;
wallR3TopA.height = 60;
floor.walls.push(wallR3TopA);
var wallR3TopB = new Wall();
wallR3TopB.x = 1450;
wallR3TopB.y = 1200;
wallR3TopB.width = 250;
wallR3TopB.height = 60;
floor.walls.push(wallR3TopB);
// Right wall
var wallR3Right = new Wall();
wallR3Right.x = 1550;
wallR3Right.y = 1500;
wallR3Right.width = 60;
wallR3Right.height = 600;
floor.walls.push(wallR3Right);
// Bottom wall
var wallR3Bottom = new Wall();
wallR3Bottom.x = 1200;
wallR3Bottom.y = 1800;
wallR3Bottom.width = 600;
wallR3Bottom.height = 60;
floor.walls.push(wallR3Bottom);
// Left wall (shared with Room 4, has a door)
// Shorten wall segments to leave a gap for the door at y=1700
var wallR3LeftA = new Wall();
wallR3LeftA.x = 850;
wallR3LeftA.y = 1300;
wallR3LeftA.width = 60;
wallR3LeftA.height = 120;
floor.walls.push(wallR3LeftA);
var wallR3LeftB = new Wall();
wallR3LeftB.x = 850;
wallR3LeftB.y = 1820;
wallR3LeftB.width = 60;
wallR3LeftB.height = 120;
floor.walls.push(wallR3LeftB);
// --- Room 4: L-Shape (Bottom Left) ---
// Top wall
var wallR4Top = new Wall();
wallR4Top.x = 350;
wallR4Top.y = 1200;
wallR4Top.width = 600;
wallR4Top.height = 60;
floor.walls.push(wallR4Top);
// Left wall
var wallR4Left = new Wall();
wallR4Left.x = 70;
wallR4Left.y = 1500;
wallR4Left.width = 60;
wallR4Left.height = 600;
floor.walls.push(wallR4Left);
// Bottom wall
var wallR4Bottom = new Wall();
wallR4Bottom.x = 350;
wallR4Bottom.y = 1800;
wallR4Bottom.width = 600;
wallR4Bottom.height = 60;
floor.walls.push(wallR4Bottom);
// Right wall (shared with Room 3, has a door)
// Shorten wall segments to leave a gap for the door at y=1700
var wallR4RightA = new Wall();
wallR4RightA.x = 650;
wallR4RightA.y = 1300;
wallR4RightA.width = 60;
wallR4RightA.height = 120;
floor.walls.push(wallR4RightA);
var wallR4RightB = new Wall();
wallR4RightB.x = 650;
wallR4RightB.y = 1820;
wallR4RightB.width = 60;
wallR4RightB.height = 120;
floor.walls.push(wallR4RightB);
// --- Doors between rooms ---
// Door between Room 1 and Room 2 (top horizontal)
var door1 = new Door();
door1.x = 750;
door1.y = 470;
floor.doors.push(door1);
// Door between Room 2 and Room 3 (vertical)
var door2 = new Door();
door2.x = 1200;
door2.y = 1100;
floor.doors.push(door2);
// Door between Room 3 and Room 4 (bottom horizontal)
var door3 = new Door();
door3.x = 750;
door3.y = 1700;
floor.doors.push(door3);
// Door between Room 4 and Room 1 (vertical)
var door4 = new Door();
// Move door out of the wall: shift x to be just right of the wall (wall at x=70, wall thickness=60, room width=600, so wall edge at x=100, room starts at x=100, door at x=350 is center of wall, move to x=350+90=440)
door4.x = 350 + 90; // move further right of wall, so door is fully reachable
door4.y = 1000;
floor.doors.push(door4);
// Shorten bottom wall of Room 1 to leave a gap for the door at x=350, y=1000
// (wallR1Bottom is at y=800, so we need to adjust left wall of Room 4)
var wallR4LeftA = new Wall();
wallR4LeftA.x = 70;
wallR4LeftA.y = 1200;
wallR4LeftA.width = 60;
wallR4LeftA.height = 200;
floor.walls.push(wallR4LeftA);
var wallR4LeftB = new Wall();
wallR4LeftB.x = 70;
wallR4LeftB.y = 1500;
wallR4LeftB.width = 60;
wallR4LeftB.height = 600;
floor.walls.push(wallR4LeftB);
// --- Key in Room 3 (bottom right) ---
var key = new Key();
key.x = 1400;
key.y = 1600;
floor.puzzles.push(key);
// --- Ghost in Room 2 (top right) ---
var ghost = new Ghost();
ghost.x = 1300;
ghost.y = 600;
floor.enemies.push(ghost);
function createFloor1() {
var floor = {
walls: [],
doors: [],
keys: [],
puzzles: [],
enemies: []
};
// L-shape: Top wall
// Door (random position, not under floor list area)
var door = new Door();
do {
door.x = 200 + Math.random() * (2048 - 400);
door.y = 200 + Math.random() * (2732 - 400);
} while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list
floor.doors.push(door);
// Key (random position, not under floor list area)
var key = new Key();
do {
key.x = 200 + Math.random() * (2048 - 400);
key.y = 200 + Math.random() * (2732 - 400);
} while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list
floor.puzzles.push(key);
// Ghost (random, not close to player)
var ghost = new Ghost();
do {
ghost.x = 200 + Math.random() * (2048 - 400);
ghost.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost);
// Second ghost (random, not close to player)
var ghost2 = new Ghost();
do {
ghost2.x = 200 + Math.random() * (2048 - 400);
ghost2.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost2);
return floor;
}
// Floor 2: 3rd floor (T-shaped, 1 puzzle, 1 ghost)
function createFloor2() {
var floor = {
walls: [],
doors: [],
keys: [],
puzzles: [],
enemies: []
};
// T-shape: Top wall
// Door (random position, not under floor list area)
var door = new Door();
do {
door.x = 200 + Math.random() * (2048 - 400);
door.y = 200 + Math.random() * (2732 - 400);
} while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list
floor.doors.push(door);
// Key (random position, not under floor list area)
var key = new Key();
do {
key.x = 200 + Math.random() * (2048 - 400);
key.y = 200 + Math.random() * (2732 - 400);
} while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list
floor.puzzles.push(key);
// Ghost (random, not close to player)
var ghost = new Ghost();
do {
ghost.x = 200 + Math.random() * (2048 - 400);
ghost.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost);
// Second ghost (random, not close to player)
var ghost2 = new Ghost();
do {
ghost2.x = 200 + Math.random() * (2048 - 400);
ghost2.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost2);
return floor;
}
// Floor 3: 4th floor (U-shaped, 1 puzzle, 1 ghost)
function createFloor3() {
var floor = {
walls: [],
doors: [],
keys: [],
puzzles: [],
enemies: []
};
// U-shape: Top wall
// Door (random position, not under floor list area)
var door = new Door();
do {
door.x = 200 + Math.random() * (2048 - 400);
door.y = 200 + Math.random() * (2732 - 400);
} while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list
floor.doors.push(door);
// Key (random position, not under floor list area)
var key = new Key();
do {
key.x = 200 + Math.random() * (2048 - 400);
key.y = 200 + Math.random() * (2732 - 400);
} while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list
floor.puzzles.push(key);
// Ghost (random, not close to player)
var ghost = new Ghost();
do {
ghost.x = 200 + Math.random() * (2048 - 400);
ghost.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost);
// Second ghost (random, not close to player)
var ghost2 = new Ghost();
do {
ghost2.x = 200 + Math.random() * (2048 - 400);
ghost2.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost2);
return floor;
}
// Floor 4: 5th floor (original floor 0, four rooms)
function createFloor4() {
// Floor 5: Four rooms, each with different shape and size, combined together
var floor = {
walls: [],
doors: [],
keys: [],
puzzles: [],
enemies: []
};
// --- Room 1: Large Rectangle (Top Left) ---
// --- Doors between rooms ---
// Only keep one main door for this floor
var door = new Door();
do {
door.x = 200 + Math.random() * (2048 - 400);
door.y = 200 + Math.random() * (2732 - 400);
} while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list
floor.doors.push(door);
// Shorten bottom wall of Room 1 to leave a gap for the door at x=350, y=1000
// (wallR1Bottom is at y=800, so we need to adjust left wall of Room 4)
// --- Key in Room 3 (random position) ---
var key = new Key();
do {
key.x = 200 + Math.random() * (2048 - 400);
key.y = 200 + Math.random() * (2732 - 400);
} while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list
floor.puzzles.push(key);
// --- Ghost 1 (random, not close to player) ---
var ghost = new Ghost();
do {
ghost.x = 200 + Math.random() * (2048 - 400);
ghost.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost);
// --- Ghost 2 (random, not close to player) ---
var ghost2 = new Ghost();
do {
ghost2.x = 200 + Math.random() * (2048 - 400);
ghost2.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost2);
return floor;
}
// Floor 5: Top floor (original floor 1, exit)
function createFloor5() {
var floor = {
walls: [],
doors: [],
keys: [],
puzzles: [],
enemies: []
};
// Walls (rectangle room)
// Exit Door (top center, unlocked if puzzle solved)
var door = new Door();
door.x = 2048 / 2;
door.y = 400;
door.locked = false;
door.doorAsset.destroy();
door.doorAsset = door.attachAsset('door_unlocked', {
anchorX: 0.5,
anchorY: 0.5
});
floor.doors.push(door);
// Key in a random position
var key = new Key();
do {
key.x = 200 + Math.random() * (2048 - 400);
key.y = 200 + Math.random() * (2732 - 400);
} while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list
floor.puzzles.push(key);
// Ghost (random, not close to player)
var ghost = new Ghost();
do {
ghost.x = 200 + Math.random() * (2048 - 400);
ghost.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost);
// Second ghost (random, not close to player)
var ghost2 = new Ghost();
do {
ghost2.x = 200 + Math.random() * (2048 - 400);
ghost2.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost2);
// Upstairs: 1 room, exit door (unlocked if puzzle solved), 1 ghost
var floor = {
walls: [],
doors: [],
keys: [],
puzzles: [],
enemies: []
};
// Walls (rectangle room)
// Exit Door (top center, unlocked if puzzle solved)
var door = new Door();
door.x = 2048 / 2;
door.y = 400;
door.locked = false;
door.doorAsset.destroy();
door.doorAsset = door.attachAsset('door_unlocked', {
anchorX: 0.5,
anchorY: 0.5
});
floor.doors.push(door);
// Key in the center of the room
var key = new Key();
key.x = 2048 / 2;
key.y = 2732 / 2;
floor.puzzles.push(key);
// Ghost (patrols horizontally)
var ghost = new Ghost();
ghost.x = 2048 / 2;
ghost.y = 1200;
floor.enemies.push(ghost);
return floor;
}
// --- Helper Functions ---
function clearFloor() {
// Remove all objects from game
if (floors[currentFloor]) {
var f = floors[currentFloor];
for (var i = 0; i < f.walls.length; ++i) f.walls[i].destroy();
for (var i = 0; i < f.doors.length; ++i) f.doors[i].destroy();
for (var i = 0; i < f.keys.length; ++i) f.keys[i].destroy();
for (var i = 0; i < f.puzzles.length; ++i) f.puzzles[i].destroy();
for (var i = 0; i < f.enemies.length; ++i) f.enemies[i].destroy();
}
if (doorObj) {
if (typeof doorObj.destroy === "function") {
doorObj.destroy();
}
doorObj = null;
}
if (keyObj) {
if (typeof keyObj.destroy === "function") {
keyObj.destroy();
}
keyObj = null;
}
if (ghostObj) {
ghostObj.destroy();
ghostObj = null;
}
}
function setupFloor(floorNum) {
clearFloor();
var f = floors[floorNum];
// Add walls
for (var i = 0; i < f.walls.length; ++i) game.addChild(f.walls[i]);
// Add doors
for (var i = 0; i < f.doors.length; ++i) game.addChild(f.doors[i]);
// Add puzzles
for (var i = 0; i < f.puzzles.length; ++i) game.addChild(f.puzzles[i]);
// Add enemies
for (var i = 0; i < f.enemies.length; ++i) game.addChild(f.enemies[i]);
// For easy reference
// Set doorObj to the first locked door, or the first door if none are locked
doorObj = null;
for (var i = 0; i < f.doors.length; ++i) {
if (f.doors[i].locked) {
doorObj = f.doors[i];
break;
}
}
if (!doorObj && f.doors.length) doorObj = f.doors[0];
keyObj = null;
// Set keyObj to the first unsolved key, or null if all solved
keyObj = null;
for (var i = 0; i < f.puzzles.length; ++i) {
if (!f.puzzles[i].solved) {
keyObj = f.puzzles[i];
break;
}
}
ghostObj = f.enemies.length ? f.enemies[0] : null;
}
// --- GUI ---
messageTxt = new Text2('', {
size: 90,
fill: 0xFFCCCC
});
messageTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(messageTxt);
floorTxt = new Text2('Floor 1', {
size: 70,
fill: 0xCCCCFF
});
floorTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(floorTxt);
// --- Game Setup ---
floors[0] = createFloor0();
floors[1] = createFloor1();
floors[2] = createFloor2();
floors[3] = createFloor3();
floors[4] = createFloor4();
floors[5] = createFloor5();
// Add 14 more floors (floors 6 to 19)
for (var i = 6; i < 20; ++i) {
// Each new floor is a random room with 1 door, 1 key, 2 ghosts
floors[i] = function () {
var floor = {
walls: [],
doors: [],
keys: [],
puzzles: [],
enemies: []
};
// Door (random position, not under floor list area)
var door = new Door();
do {
door.x = 200 + Math.random() * (2048 - 400);
door.y = 200 + Math.random() * (2732 - 400);
} while (door.x > 1700 && door.y < 1000); // avoid top right area under floor list
floor.doors.push(door);
// Key (random position, not under floor list area)
var key = new Key();
do {
key.x = 200 + Math.random() * (2048 - 400);
key.y = 200 + Math.random() * (2732 - 400);
} while (key.x > 1700 && key.y < 1000); // avoid top right area under floor list
floor.puzzles.push(key);
// Ghost 1 (random, not close to player start)
var ghost = new Ghost();
do {
ghost.x = 200 + Math.random() * (2048 - 400);
ghost.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost.x - 2048 / 2, 2) + Math.pow(ghost.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost);
// Ghost 2 (random, not close to player start)
var ghost2 = new Ghost();
do {
ghost2.x = 200 + Math.random() * (2048 - 400);
ghost2.y = 200 + Math.random() * (2732 - 400);
} while (Math.sqrt(Math.pow(ghost2.x - 2048 / 2, 2) + Math.pow(ghost2.y - (2732 - 700), 2)) < 400);
floor.enemies.push(ghost2);
return floor;
}();
}
player = new Player();
player.x = 2048 / 2;
player.y = 2732 - 700;
game.addChild(player);
setupFloor(0);
updateFloorText();
// --- Life Counter GUI (bottom left) ---
// Heart icon asset (using emoji for now, can be replaced with image asset if available)
var heartIcons = [];
var maxHearts = 6; // reasonable max for layout, can be increased if needed
function updateHearts(lives) {
// Remove old hearts
for (var i = 0; i < heartIcons.length; ++i) {
if (heartIcons[i] && typeof heartIcons[i].destroy === "function") {
heartIcons[i].destroy();
}
}
heartIcons = [];
// Add new hearts
var spacing = 80;
var size = 90;
for (var i = 0; i < lives; ++i) {
var heart = new Text2("❤", {
size: size,
fill: 0xFF3B3B
});
heart.anchor.set(0, 1); // left align, bottom
heart.x = i * spacing;
heart.y = 0;
LK.gui.bottomLeft.addChild(heart);
heartIcons.push(heart);
}
}
// Initialize with 1 life
updateHearts(1);
// --- Floor List GUI (top right) ---
var floorListContainer = new Container();
floorListContainer.x = 0;
floorListContainer.y = 0;
LK.gui.topRight.addChild(floorListContainer);
function updateFloorList() {
// Remove old children
if (floorListContainer && floorListContainer.children) {
while (floorListContainer.children.length) {
floorListContainer.children[0].destroy();
}
}
// List from top (highest floor) to bottom (lowest)
var y = 40;
for (var i = floors.length - 1; i >= 0; --i) {
var label = "Floor " + (i + 1);
var txt = new Text2(label, {
size: 60,
fill: i === currentFloor ? "#FFD700" : "#CCCCFF"
});
txt.anchor.set(1, 0); // right align
txt.x = 0;
txt.y = y;
if (floorListContainer && typeof floorListContainer.addChild === "function") {
floorListContainer.addChild(txt);
}
y += 90;
}
}
updateFloorList();
function updateFloorText() {
floorTxt.setText('Floor ' + (currentFloor + 1));
updateFloorList();
}
// --- Movement & Controls ---
function canMoveTo(x, y) {
// Check collision with walls
var f = floors[currentFloor];
for (var i = 0; i < f.walls.length; ++i) {
var wall = f.walls[i];
// Simple AABB collision
var dx = Math.abs(x - wall.x);
var dy = Math.abs(y - wall.y);
if (dx < wall.width / 2 + 60 && dy < wall.height / 2 + 60) {
return false;
}
}
return true;
}
function handleMove(x, y, obj) {
if (gameOver) return;
if (!dragPlayer) return;
// Clamp to game area
var px = Math.max(60, Math.min(2048 - 60, x));
var py = Math.max(60, Math.min(2732 - 60, y));
// Only move if not colliding with wall
if (canMoveTo(px, py)) {
player.moveTo(px, py);
}
}
game.down = function (x, y, obj) {
if (gameOver) return;
// Only start drag if touch/click is on player
var dx = x - player.x;
var dy = y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100) {
dragPlayer = true;
lastPlayerPos.x = player.x;
lastPlayerPos.y = player.y;
handleMove(x, y, obj);
}
};
game.move = function (x, y, obj) {
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
dragPlayer = false;
};
// --- Puzzle Interaction ---
function tryCollectKey() {
if (!keyObj || keyObj.solved) return;
// Simple key: tap key to collect
keyObj.solved = true;
keySolved = true;
messageTxt.setText("You found a key!\nThe door unlocked.");
// Unlock the corresponding door for the collected key
var f = floors[currentFloor];
if (f && f.puzzles && f.doors) {
// Find the index of the collected key in the floor's puzzles array
var keyIdx = -1;
for (var i = 0; i < f.puzzles.length; ++i) {
if (f.puzzles[i] === keyObj) {
keyIdx = i;
break;
}
}
// Unlock the door with the same index (if exists and locked)
if (keyIdx >= 0 && f.doors[keyIdx] && f.doors[keyIdx].locked) {
f.doors[keyIdx].unlock();
}
}
// Fade out the key when collected
if (keyObj) {
tween(keyObj, {
alpha: 0
}, {
duration: 800
});
}
LK.setTimeout(function () {
messageTxt.setText('');
}, 1500);
}
// --- Door Interaction ---
function tryOpenDoor() {
if (!doorObj) return;
if (doorObj.locked) {
messageTxt.setText("The door is locked.");
LK.setTimeout(function () {
messageTxt.setText('');
}, 1200);
} else {
// Go to next floor or win
if (currentFloor < floors.length - 1) {
// Go upstairs
currentFloor++;
setupFloor(currentFloor);
player.x = 2048 / 2;
player.y = 2732 - 700;
updateFloorText();
// If reached floor 20 (index 19), win the game
if (currentFloor === 19) {
LK.showYouWin();
return;
}
messageTxt.setText("You ascend to the next floor...");
LK.setTimeout(function () {
messageTxt.setText('');
}, 1200);
} else {
// Win!
LK.showYouWin();
}
}
}
// --- Enemy (Ghost) Collision ---
function checkGhostCollision() {
if (!ghostObj) return;
var dx = player.x - ghostObj.x;
var dy = player.y - ghostObj.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 90) {
// Only trigger on new contact
if (!player.lastGhostContact || !player.lastGhostContact[ghostObj] || !player.lastGhostContact[ghostObj].wasColliding) {
if (typeof player.lives === "undefined") player.lives = 1;
player.lives -= 1;
updateHearts(player.lives);
if (player.lives <= 0) {
gameOver = true;
player.canMove = false;
messageTxt.setText("A ghost caught you!");
LK.effects.flashScreen(0x990000, 1200);
LK.setTimeout(function () {
LK.showGameOver();
}, 1200);
} else {
messageTxt.setText("A ghost hit you! Lives left: " + player.lives);
LK.effects.flashScreen(0x990000, 600);
player.canMove = false;
// Briefly pause movement after hit
LK.setTimeout(function () {
player.canMove = true;
messageTxt.setText('');
}, 900);
}
// Track collision state for this ghost
if (!player.lastGhostContact) player.lastGhostContact = {};
player.lastGhostContact[ghostObj] = {
wasColliding: true
};
}
} else {
// Not colliding, reset collision state for this ghost
if (player.lastGhostContact && player.lastGhostContact[ghostObj]) {
player.lastGhostContact[ghostObj].wasColliding = false;
}
}
}
// --- Main Game Update ---
game.update = function () {
if (gameOver) return;
// Ghost patrol
if (ghostObj && ghostObj.update) ghostObj.update();
// Check for key interaction (support multiple keys per floor)
var f = floors[currentFloor];
if (f && f.puzzles) {
for (var i = 0; i < f.puzzles.length; ++i) {
var k = f.puzzles[i];
if (!k.solved) {
var dx = player.x - k.x;
var dy = player.y - k.y;
if (Math.sqrt(dx * dx + dy * dy) < 100 && dragPlayer) {
keyObj = k;
tryCollectKey();
break;
}
}
}
}
// Check for door interaction (support multiple doors per floor)
if (f && f.doors) {
for (var i = 0; i < f.doors.length; ++i) {
var dr = f.doors[i];
var dx = player.x - dr.x;
var dy = player.y - dr.y;
if (Math.abs(dx) < 80 && Math.abs(dy) < 80 && dragPlayer) {
doorObj = dr;
if (dr.locked) {
tryOpenDoor();
} else {
// Move player through the door (to the other side of the door)
if (currentFloor < floors.length - 1) {
// Go upstairs
currentFloor++;
setupFloor(currentFloor);
player.x = 2048 / 2;
player.y = 2732 - 700;
updateFloorText();
// If reached floor 20 (index 19), win the game
if (currentFloor === 19) {
LK.showYouWin();
return;
}
// Give extra life on floor 10 (index 9)
if (currentFloor === 9 && typeof player !== "undefined" && player) {
if (typeof player.lives === "undefined") player.lives = 1;
player.lives += 1;
updateHearts(player.lives);
messageTxt.setText("You gained an extra life!");
LK.setTimeout(function () {
messageTxt.setText('');
}, 1500);
}
messageTxt.setText("You ascend to the next floor...");
LK.setTimeout(function () {
messageTxt.setText('');
}, 1200);
} else if (currentFloor > 0) {
// Move player to one floor down
currentFloor--;
setupFloor(currentFloor);
player.x = 2048 / 2;
player.y = 2732 - 700;
updateFloorText();
messageTxt.setText("You descend to the previous floor...");
LK.setTimeout(function () {
messageTxt.setText('');
}, 1200);
}
}
break;
}
}
}
// Check for ghost collision
checkGhostCollision();
};