User prompt
spawn puzzle
User prompt
make four rooms with each different shape and size but combined together
User prompt
set floor5 as level
User prompt
rooms are horizontal
User prompt
add rooms to this level
User prompt
when I get out of the door move me to one floor down
User prompt
show floor in the upper middle
User prompt
name this level as floor 6
User prompt
open door when unlocked
User prompt
unlock door when puzzle solved
User prompt
remove the key asset
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'destroy')' in or related to this line: 'keyObj.destroy();' Line Number: 440
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'destroy')' in or related to this line: 'keyObj.destroy();' Line Number: 438
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'destroy')' in or related to this line: 'keyObj.destroy();' Line Number: 436
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'destroy')' in or related to this line: 'keyObj.destroy();' Line Number: 436
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'destroy')' in or related to this line: 'keyObj.destroy();' Line Number: 434
Code edit (1 edits merged)
Please save this source code
User prompt
Haunted Floors: Escape the House
Initial prompt
ı want to build a horror game that takes place in a house with multiple stories (floors) and we see the rooms from the top
/**** * 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();
};