/****
* 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 = 2.0 + Math.random() * 4.0; // Random speed between 2.0 and 6.0
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
****/
// --- Game State ---
// Player
// Wall
// Door (locked)
// Door (unlocked)
// Key
// Puzzle object
// Enemy (ghost)
// Add background image to the game area
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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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();
// Play spooky background music
LK.playMusic('spooky');
// --- 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) {
// Play ghost sound effect
LK.getSound('ghost').play();
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 = 2.0 + Math.random() * 4.0; // Random speed between 2.0 and 6.0
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
****/
// --- Game State ---
// Player
// Wall
// Door (locked)
// Door (unlocked)
// Key
// Puzzle object
// Enemy (ghost)
// Add background image to the game area
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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost.x - player.x, 2) + Math.pow(ghost.y - player.y, 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 (player && Math.sqrt(Math.pow(ghost2.x - player.x, 2) + Math.pow(ghost2.y - player.y, 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();
// Play spooky background music
LK.playMusic('spooky');
// --- 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) {
// Play ghost sound effect
LK.getSound('ghost').play();
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();
};