/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var DoorRenderer = Container.expand(function () { var self = Container.call(this); self.doors = []; self.createDoor = function (x, y, roomNumber) { var doorContainer = new Container(); // Door frame - made bigger and more prominent var frame = LK.getAsset('door_frame', { anchorX: 0.5, anchorY: 1.0, width: 80, height: 100 }); frame.tint = 0x4a2c2a; doorContainer.addChild(frame); // Door panel - made more prominent var panel = LK.getAsset('door', { anchorX: 0.5, anchorY: 1.0, y: -8, width: 72, height: 80 }); panel.tint = 0x8b4513; doorContainer.addChild(panel); // Door handle var handle = LK.getAsset('door_handle', { anchorX: 0.5, anchorY: 0.5, x: 25, y: -40 }); handle.tint = 0xffd700; doorContainer.addChild(handle); // Door nameplate with dimensions var nameplate = LK.getAsset('door_nameplate', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -65 }); nameplate.tint = 0xc0c0c0; doorContainer.addChild(nameplate); // Room number text var numberText = new Text2(roomNumber.toString(), { size: 20, fill: 0x000000 }); numberText.anchor.set(0.5, 0.5); numberText.x = 0; numberText.y = -72; doorContainer.addChild(numberText); // Dimension text on nameplate var dimensionText = new Text2('80x100', { size: 12, fill: 0x000000 }); dimensionText.anchor.set(0.5, 0.5); dimensionText.x = 0; dimensionText.y = -58; doorContainer.addChild(dimensionText); doorContainer.x = x; doorContainer.y = y; return doorContainer; }; return self; }); var MazeGenerator = Container.expand(function () { var self = Container.call(this); self.generate = function (width, height) { var maze = { width: width, height: height, grid: [], startX: 1, startY: 1, endX: width - 2, endY: height - 2, rooms: [], currentRoom: 0 }; // Initialize maze with walls for (var y = 0; y < height; y++) { maze.grid[y] = []; for (var x = 0; x < width; x++) { maze.grid[y][x] = 1; // Wall } } // Create linear sequence of rooms like Doors game self.createLinearRooms(maze); // Ensure start and end are open maze.grid[maze.startY][maze.startX] = 0; maze.grid[maze.endY][maze.endX] = 2; // Exit return maze; }; self.createLinearRooms = function (maze) { // Create linear path with unique room shapes - only one way forward var roomTypes = ['hallway', 'lshaped', 'library', 'bedroom']; var currentX = 5; var currentY = 5; var connectionWidth = 2; maze.rooms = []; var roomNumber = 1; // Create 100 rooms in a linear sequence for (var i = 0; i < 100; i++) { var roomType = roomTypes[i % 4]; // Cycle through room types var room = self.createRoomByType(maze, roomType, currentX, currentY, roomNumber); if (room) { maze.rooms.push(room); // Connect to next room if not the last one if (i < 99) { var nextRoomX = currentX + room.width + connectionWidth + 3; var nextRoomY = currentY; // Ensure next room fits, otherwise move to next row if (nextRoomX + 8 >= maze.width) { nextRoomX = 5; nextRoomY = currentY + 10; // Move down for next row } // Create connection hallway self.createConnection(maze, room, nextRoomX, nextRoomY); currentX = nextRoomX; currentY = nextRoomY; } roomNumber++; } } // Set start in first room if (maze.rooms.length > 0) { maze.startX = maze.rooms[0].x + 1; maze.startY = maze.rooms[0].y + 1; // Set exit in last room var lastRoom = maze.rooms[maze.rooms.length - 1]; maze.endX = lastRoom.x + lastRoom.width - 2; maze.endY = lastRoom.y + lastRoom.height - 2; } }; self.createRoomByType = function (maze, roomType, startX, startY, roomNumber) { var room = null; switch (roomType) { case 'hallway': room = self.createHallway(maze, startX, startY, roomNumber); break; case 'lshaped': room = self.createLShaped(maze, startX, startY, roomNumber); break; case 'library': room = self.createLibrary(maze, startX, startY, roomNumber); break; case 'bedroom': room = self.createBedroom(maze, startX, startY, roomNumber); break; } return room; }; self.createHallway = function (maze, startX, startY, roomNumber) { var width = 8; var height = 3; if (startX + width >= maze.width || startY + height >= maze.height) return null; // Clear hallway area for (var y = startY; y < startY + height; y++) { for (var x = startX; x < startX + width; x++) { maze.grid[y][x] = 0; } } // Add shelves along the walls using raycasting approach // Place shelves on the top and bottom walls for (var x = startX + 1; x < startX + width - 1; x += 2) { // Top wall shelves if (startY > 0 && maze.grid[startY - 1][x] === 1) { maze.grid[startY][x] = 4; // Shelf marker } // Bottom wall shelves if (startY + height < maze.height && maze.grid[startY + height][x] === 1) { maze.grid[startY + height - 1][x] = 4; // Shelf marker } } // Add desks in the middle area if (startX + 3 < maze.width && startY + 1 < maze.height) { maze.grid[startY + 1][startX + 3] = 7; // Desk marker } return { x: startX, y: startY, width: width, height: height, number: roomNumber, type: 'hallway' }; }; self.createLShaped = function (maze, startX, startY, roomNumber) { var width = 6; var height = 6; if (startX + width >= maze.width || startY + height >= maze.height) return null; // Clear L-shaped area for (var y = startY; y < startY + height; y++) { for (var x = startX; x < startX + width; x++) { // Create L shape - full first 4 rows, then only first 3 columns if (y < startY + 4 || x < startX + 3) { maze.grid[y][x] = 0; } } } return { x: startX, y: startY, width: width, height: height, number: roomNumber, type: 'lshaped' }; }; self.createLibrary = function (maze, startX, startY, roomNumber) { var width = 7; var height = 5; if (startX + width >= maze.width || startY + height >= maze.height) return null; // Clear library area for (var y = startY; y < startY + height; y++) { for (var x = startX; x < startX + width; x++) { maze.grid[y][x] = 0; } } // Add book shelves along the walls for (var x = startX + 1; x < startX + width - 1; x += 2) { if (startY + 1 < maze.height) { maze.grid[startY + 1][x] = 4; // Shelf marker } if (startY + 3 < maze.height) { maze.grid[startY + 3][x] = 4; // Shelf marker } } // Add reading desk in center if (startY + 2 < maze.height && startX + 3 < maze.width) { maze.grid[startY + 2][startX + 3] = 7; // Desk marker } // Add small paintings on walls if (startY + 1 < maze.height && startX + 1 < maze.width) { maze.grid[startY + 1][startX + 1] = 6; // Painting marker } if (startY + 3 < maze.height && startX + 5 < maze.width) { maze.grid[startY + 3][startX + 5] = 6; // Painting marker } return { x: startX, y: startY, width: width, height: height, number: roomNumber, type: 'library' }; }; self.createBedroom = function (maze, startX, startY, roomNumber) { var width = 5; var height = 5; if (startX + width >= maze.width || startY + height >= maze.height) return null; // Clear bedroom area for (var y = startY; y < startY + height; y++) { for (var x = startX; x < startX + width; x++) { maze.grid[y][x] = 0; } } // Add beds along the walls if (startY + 1 < maze.height && startX + 1 < maze.width) { maze.grid[startY + 1][startX + 1] = 5; // Bed marker } if (startY + 1 < maze.height && startX + 3 < maze.width) { maze.grid[startY + 1][startX + 3] = 5; // Bed marker } // Add desk between beds if (startY + 3 < maze.height && startX + 2 < maze.width) { maze.grid[startY + 3][startX + 2] = 7; // Desk marker } // Add small paintings on walls if (startY + 2 < maze.height && startX + 1 < maze.width) { maze.grid[startY + 2][startX + 1] = 6; // Painting marker } if (startY + 2 < maze.height && startX + 3 < maze.width) { maze.grid[startY + 2][startX + 3] = 6; // Painting marker } return { x: startX, y: startY, width: width, height: height, number: roomNumber, type: 'bedroom' }; }; self.createConnection = function (maze, fromRoom, toX, toY) { var fromX = fromRoom.x + fromRoom.width; var fromY = fromRoom.y + Math.floor(fromRoom.height / 2); var toConnectX = toX - 1; var toConnectY = toY + 1; // Create horizontal connection var currentX = fromX; while (currentX < toConnectX && currentX < maze.width) { if (fromY >= 0 && fromY < maze.height && currentX >= 0) { maze.grid[fromY][currentX] = 0; } currentX++; } // Place door at connection end with room number if (toConnectX >= 0 && toConnectX < maze.width && fromY >= 0 && fromY < maze.height) { maze.grid[fromY][toConnectX] = 3; // Door // Store door number for rendering if (!maze.doorNumbers) maze.doorNumbers = {}; maze.doorNumbers[fromY + '_' + toConnectX] = fromRoom.number + 1; } }; return self; }); var Minimap = Container.expand(function () { var self = Container.call(this); self.cellSize = 8; self.mapCells = []; self.playerDot = null; self.setup = function (maze) { // Clear existing map for (var i = 0; i < self.mapCells.length; i++) { self.mapCells[i].destroy(); } self.mapCells = []; if (self.playerDot) { self.playerDot.destroy(); } // Create minimap cells for (var y = 0; y < maze.height; y++) { for (var x = 0; x < maze.width; x++) { var cell = null; if (maze.grid[y][x] === 1) { // Wall cell = LK.getAsset('wall', { width: self.cellSize, height: self.cellSize, anchorX: 0, anchorY: 0 }); cell.tint = 0x444444; } else if (maze.grid[y][x] === 2) { // Exit cell = LK.getAsset('exit', { width: self.cellSize, height: self.cellSize, anchorX: 0, anchorY: 0 }); cell.tint = 0x00FF00; } else if (maze.grid[y][x] === 3) { // Door cell = LK.getAsset('door', { width: self.cellSize, height: self.cellSize, anchorX: 0, anchorY: 0 }); cell.tint = 0x654321; } else { // Open path cell = LK.getAsset('floor', { width: self.cellSize, height: self.cellSize, anchorX: 0, anchorY: 0 }); cell.tint = 0x888888; } if (cell) { cell.x = x * self.cellSize; cell.y = y * self.cellSize; self.addChild(cell); self.mapCells.push(cell); } } } // Create player dot self.playerDot = LK.getAsset('player', { width: self.cellSize, height: self.cellSize, anchorX: 0.5, anchorY: 0.5 }); self.addChild(self.playerDot); }; self.updatePlayer = function (playerX, playerY) { if (self.playerDot) { self.playerDot.x = playerX * self.cellSize; self.playerDot.y = playerY * self.cellSize; } }; return self; }); var RaycastRenderer = Container.expand(function () { var self = Container.call(this); self.fov = Math.PI / 3; // 60 degrees field of view self.rayCount = 256; // Number of rays to cast (expanded for full screen) self.maxDistance = 15; // Reduced max distance self.wallHeight = 300; self.walls = []; self.render = function (playerX, playerY, playerAngle, maze) { // Initialize walls array if needed if (self.walls.length === 0) { for (var i = 0; i < self.rayCount; i++) { var wall = LK.getAsset('raycast_wall', { width: 8, height: self.wallHeight, anchorX: 0, anchorY: 0.5 }); wall.x = i * 8; wall.y = 2732 / 2; wall.visible = false; self.addChild(wall); self.walls.push(wall); } } var angleStep = self.fov / self.rayCount; var rayAngle = playerAngle - self.fov / 2; for (var i = 0; i < self.rayCount; i++) { var rayResult = self.castRay(playerX, playerY, rayAngle, maze); var wall = self.walls[i]; if (rayResult.distance < self.maxDistance) { // Calculate wall height based on distance var wallHeight = self.wallHeight / rayResult.distance; if (wallHeight > 600) wallHeight = 600; // Cap wall height // Update existing wall segment wall.height = wallHeight; wall.visible = true; // Add depth shading and color based on object type var shade = Math.max(0.2, 1 - rayResult.distance / self.maxDistance); var baseColor = 0x8B4513; // Default wall color if (rayResult.type === 3) baseColor = 0x4a2c2a; // Door color - darker brown for better visibility else if (rayResult.type === 4) baseColor = 0x654321; // Shelf color else if (rayResult.type === 5) baseColor = 0x800080; // Bed color else if (rayResult.type === 6) baseColor = 0xFFD700; // Painting color else if (rayResult.type === 7) baseColor = 0x8b4513; // Desk color wall.tint = self.interpolateColor(0x000000, baseColor, shade); } else { wall.visible = false; } rayAngle += angleStep; } }; self.castRay = function (x, y, angle, maze) { var dx = Math.cos(angle) * 0.1; var dy = Math.sin(angle) * 0.1; var distance = 0; var stepSize = 0.1; var maxSteps = 200; // Prevent infinite loops var steps = 0; var hitType = 0; // Track what we hit while (distance < self.maxDistance && steps < maxSteps) { x += dx; y += dy; distance += stepSize; steps++; var mapX = Math.floor(x); var mapY = Math.floor(y); if (mapX < 0 || mapX >= maze.width || mapY < 0 || mapY >= maze.height) { return { distance: distance, type: 1 }; } var cellType = maze.grid[mapY][mapX]; if (cellType === 1) { return { distance: distance, type: 1 }; } // Check for doors if (cellType === 3) { // Door return { distance: distance, type: 3 }; } // Check for furniture objects if (cellType === 4) { // Shelf return { distance: distance, type: 4 }; } if (cellType === 5) { // Bed return { distance: distance, type: 5 }; } if (cellType === 6) { // Painting return { distance: distance, type: 6 }; } if (cellType === 7) { // Desk return { distance: distance, type: 7 }; } } return { distance: self.maxDistance, type: 0 }; }; self.interpolateColor = function (color1, color2, factor) { var r1 = color1 >> 16 & 0xFF; var g1 = color1 >> 8 & 0xFF; var b1 = color1 & 0xFF; var r2 = color2 >> 16 & 0xFF; var g2 = color2 >> 8 & 0xFF; var b2 = color2 & 0xFF; var r = Math.floor(r1 + (r2 - r1) * factor); var g = Math.floor(g1 + (g2 - g1) * factor); var b = Math.floor(b1 + (b2 - b1) * factor); return r << 16 | g << 8 | b; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000, name: 'Maze Runner', description: 'Navigate through a first-person maze with unique room types including hallways, libraries, and bedrooms. Find doors to progress through 100 rooms and reach the exit!', tags: ['maze', 'first-person', '3d', 'adventure', 'puzzle', 'exploration', 'rooms', 'doors', 'navigation'] }); /**** * Game Code ****/ // Game variables - Reset all values on new game var currentLevel = 1; var playerX = 1.5; var playerY = 1.5; var playerAngle = 0; var moveSpeed = 0.05; // Reduced movement speed var turnSpeed = 0.04; // Reduced turn speed var maze = null; var mazeGenerator = new MazeGenerator(); var raycastRenderer = new RaycastRenderer(); var minimap = new Minimap(); var roomsCompleted = 0; // Reset room counter var doorColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff]; var breadcrumbs = []; var breadcrumbContainer = new Container(); // Touch controls var touchStartX = 0; var touchStartY = 0; var isTouching = false; var moveForward = false; var moveBackward = false; var turnLeft = false; var turnRight = false; // UI elements var levelText = new Text2('Level: ' + currentLevel, { size: 60, fill: 0xFFFFFF }); levelText.anchor.set(0.5, 0); LK.gui.top.addChild(levelText); var instructionText = new Text2('Find the exit!', { size: 40, fill: 0xFFFFFF }); instructionText.anchor.set(0.5, 0); instructionText.y = 80; LK.gui.top.addChild(instructionText); var roomText = new Text2('Room 1', { size: 50, fill: 0xFFFF00 }); roomText.anchor.set(0.5, 0); roomText.y = 140; LK.gui.top.addChild(roomText); var roomCounterText = new Text2('Rooms: ' + roomsCompleted + '/100', { size: 40, fill: 0xFFFFFF }); roomCounterText.anchor.set(0.5, 0); roomCounterText.y = 200; LK.gui.top.addChild(roomCounterText); // Compass UI var compassBg = LK.getAsset('compass_bg', { anchorX: 0.5, anchorY: 0.5 }); LK.gui.topRight.addChild(compassBg); compassBg.x = -80; compassBg.y = 80; var compassNeedle = LK.getAsset('compass_needle', { anchorX: 0.5, anchorY: 1.0 }); compassBg.addChild(compassNeedle); // Direction labels var northLabel = new Text2('N', { size: 20, fill: 0xFFFFFF }); northLabel.anchor.set(0.5, 0.5); northLabel.x = 0; northLabel.y = -40; compassBg.addChild(northLabel); var southLabel = new Text2('S', { size: 20, fill: 0xFFFFFF }); southLabel.anchor.set(0.5, 0.5); southLabel.x = 0; southLabel.y = 40; compassBg.addChild(southLabel); var eastLabel = new Text2('E', { size: 20, fill: 0xFFFFFF }); eastLabel.anchor.set(0.5, 0.5); eastLabel.x = 40; eastLabel.y = 0; compassBg.addChild(eastLabel); var westLabel = new Text2('W', { size: 20, fill: 0xFFFFFF }); westLabel.anchor.set(0.5, 0.5); westLabel.x = -40; westLabel.y = 0; compassBg.addChild(westLabel); // Direction indicator var directionText = new Text2('Facing: North', { size: 35, fill: 0x00FF00 }); directionText.anchor.set(0.5, 0); directionText.y = 250; LK.gui.top.addChild(directionText); // Door indicator var doorIndicator = new Text2('', { size: 40, fill: 0xFF0000 }); doorIndicator.anchor.set(0.5, 0.5); LK.gui.center.addChild(doorIndicator); doorIndicator.y = -200; // Control arrows var forwardArrow = new Text2('↑', { size: 120, fill: 0xFFFFFF }); forwardArrow.anchor.set(0.5, 0.5); forwardArrow.alpha = 0.7; LK.gui.bottomLeft.addChild(forwardArrow); forwardArrow.x = 150; forwardArrow.y = -150; var leftArrow = new Text2('←', { size: 120, fill: 0xFFFFFF }); leftArrow.anchor.set(0.5, 0.5); leftArrow.alpha = 0.7; LK.gui.bottomRight.addChild(leftArrow); leftArrow.x = -300; leftArrow.y = -150; var rightArrow = new Text2('→', { size: 120, fill: 0xFFFFFF }); rightArrow.anchor.set(0.5, 0.5); rightArrow.alpha = 0.7; LK.gui.bottomRight.addChild(rightArrow); rightArrow.x = -150; rightArrow.y = -150; // Initialize first level function initLevel() { // Reset all game state currentLevel = 1; roomsCompleted = 0; storage.currentLevel = 1; storage.roomsCompleted = 0; var mazeSize = 60; // Larger size to accommodate 100 rooms maze = mazeGenerator.generate(mazeSize, mazeSize); // Set player starting position playerX = maze.startX + 0.5; playerY = maze.startY + 0.5; playerAngle = 0; maze.currentRoom = 0; // Minimap removed as requested // Clear breadcrumbs for (var i = 0; i < breadcrumbs.length; i++) { breadcrumbs[i].destroy(); } breadcrumbs = []; breadcrumbContainer.removeChildren(); // Update UI levelText.setText('Level: ' + currentLevel); roomText.setText('Room 1'); roomCounterText.setText('Rooms: ' + roomsCompleted + '/100'); instructionText.setText('Find the exit!'); } // Helper functions function getDirectionName(angle) { // Normalize angle to 0-360 degrees var degrees = (angle * 180 / Math.PI + 360) % 360; if (degrees >= 337.5 || degrees < 22.5) return 'North'; if (degrees >= 22.5 && degrees < 67.5) return 'Northeast'; if (degrees >= 67.5 && degrees < 112.5) return 'East'; if (degrees >= 112.5 && degrees < 157.5) return 'Southeast'; if (degrees >= 157.5 && degrees < 202.5) return 'South'; if (degrees >= 202.5 && degrees < 247.5) return 'Southwest'; if (degrees >= 247.5 && degrees < 292.5) return 'West'; if (degrees >= 292.5 && degrees < 337.5) return 'Northwest'; return 'North'; } function findNearestDoor() { var nearestDoor = null; var minDistance = Infinity; var currentMapX = Math.floor(playerX); var currentMapY = Math.floor(playerY); var searchRadius = 8; for (var y = Math.max(0, currentMapY - searchRadius); y < Math.min(maze.height, currentMapY + searchRadius); y++) { for (var x = Math.max(0, currentMapX - searchRadius); x < Math.min(maze.width, currentMapX + searchRadius); x++) { if (maze.grid[y][x] === 3) { var distance = Math.sqrt((x - playerX) * (x - playerX) + (y - playerY) * (y - playerY)); if (distance < minDistance) { minDistance = distance; nearestDoor = { x: x, y: y, distance: distance }; } } } } return nearestDoor; } function getDoorDirection(doorX, doorY) { var dx = doorX - playerX; var dy = doorY - playerY; var angle = Math.atan2(dy, dx); return getDirectionName(angle); } // Control functions function handleMovement() { var newX = playerX; var newY = playerY; if (moveForward) { newX += Math.cos(playerAngle) * moveSpeed; newY += Math.sin(playerAngle) * moveSpeed; } if (moveBackward) { newX -= Math.cos(playerAngle) * moveSpeed; newY -= Math.sin(playerAngle) * moveSpeed; } // Collision detection var mapX = Math.floor(newX); var mapY = Math.floor(newY); if (mapX >= 0 && mapX < maze.width && mapY >= 0 && mapY < maze.height) { if (maze.grid[mapY][mapX] !== 1) { // Check for door interaction - doors automatically open on touch if (maze.grid[mapY][mapX] === 3) { // Door found - automatically open and increment room counter maze.grid[mapY][mapX] = 0; // Open the door roomsCompleted++; storage.roomsCompleted = roomsCompleted; roomCounterText.setText('Rooms: ' + roomsCompleted + '/100'); instructionText.setText('Door opened! Room ' + roomsCompleted + ' completed!'); LK.setTimeout(function () { instructionText.setText('Find the exit!'); }, 1000); // Check win condition if (roomsCompleted >= 100) { LK.showYouWin(); return; } } playerX = newX; playerY = newY; // Update current room display if (maze.rooms) { for (var r = 0; r < maze.rooms.length; r++) { var room = maze.rooms[r]; if (mapX >= room.x && mapX < room.x + room.width && mapY >= room.y && mapY < room.y + room.height) { if (maze.currentRoom !== r) { maze.currentRoom = r; var roomTypeText = room.type ? room.type.charAt(0).toUpperCase() + room.type.slice(1) : 'Room'; roomText.setText('Room ' + (r + 1) + ' - ' + roomTypeText); } break; } } } // Check if reached exit if (maze.grid[mapY][mapX] === 2) { completeLevel(); } } } if (turnLeft) { playerAngle -= turnSpeed; } if (turnRight) { playerAngle += turnSpeed; } // Normalize angle while (playerAngle < 0) playerAngle += Math.PI * 2; while (playerAngle >= Math.PI * 2) playerAngle -= Math.PI * 2; // Update compass needle compassNeedle.rotation = playerAngle; // Update direction text var currentDirection = getDirectionName(playerAngle); directionText.setText('Facing: ' + currentDirection); // Update door indicator var nearestDoor = findNearestDoor(); if (nearestDoor && nearestDoor.distance < 5) { var doorDirection = getDoorDirection(nearestDoor.x, nearestDoor.y); var doorDistance = Math.round(nearestDoor.distance * 10) / 10; doorIndicator.setText('Door: ' + doorDirection + ' (' + doorDistance + 'm)'); doorIndicator.visible = true; } else { doorIndicator.visible = false; } } function completeLevel() { LK.getSound('complete').play(); LK.setScore(currentLevel); currentLevel++; storage.currentLevel = currentLevel; if (currentLevel > 10) { LK.showYouWin(); } else { // Move to next level without flashing LK.setTimeout(function () { initLevel(); }, 500); } } // Arrow control event handlers forwardArrow.down = function (x, y, obj) { moveForward = true; forwardArrow.alpha = 1.0; }; forwardArrow.up = function (x, y, obj) { moveForward = false; forwardArrow.alpha = 0.7; }; leftArrow.down = function (x, y, obj) { turnLeft = true; leftArrow.alpha = 1.0; }; leftArrow.up = function (x, y, obj) { turnLeft = false; leftArrow.alpha = 0.7; }; rightArrow.down = function (x, y, obj) { turnRight = true; rightArrow.alpha = 1.0; }; rightArrow.up = function (x, y, obj) { turnRight = false; rightArrow.alpha = 0.7; }; // Touch event handlers game.down = function (x, y, obj) { isTouching = true; touchStartX = x; touchStartY = y; }; game.move = function (x, y, obj) { // Remove conflicting touch movement to allow arrow controls to work }; game.up = function (x, y, obj) { isTouching = false; moveForward = false; turnLeft = false; turnRight = false; }; // Add renderer to game game.addChild(raycastRenderer); raycastRenderer.x = 0; raycastRenderer.y = 0; // Add breadcrumb container game.addChild(breadcrumbContainer); // Breadcrumb trail variables var lastBreadcrumbX = -1; var lastBreadcrumbY = -1; var breadcrumbTimer = 0; // Main game loop var lastRenderTime = 0; var renderInterval = 33; // ~30fps to reduce load game.update = function () { if (maze) { handleMovement(); // Only render raycast every few frames to improve performance if (LK.ticks - lastRenderTime >= 2) { raycastRenderer.render(playerX, playerY, playerAngle, maze); lastRenderTime = LK.ticks; } // Add breadcrumb trail breadcrumbTimer++; if (breadcrumbTimer >= 60) { // Every second var currentMapX = Math.floor(playerX); var currentMapY = Math.floor(playerY); if (currentMapX !== lastBreadcrumbX || currentMapY !== lastBreadcrumbY) { var breadcrumb = LK.getAsset('breadcrumb', { anchorX: 0.5, anchorY: 0.5 }); breadcrumb.x = (currentMapX + 0.5) * 64; breadcrumb.y = (currentMapY + 0.5) * 64; breadcrumb.alpha = 0.7; breadcrumbContainer.addChild(breadcrumb); breadcrumbs.push(breadcrumb); lastBreadcrumbX = currentMapX; lastBreadcrumbY = currentMapY; breadcrumbTimer = 0; // Limit breadcrumbs to prevent memory issues if (breadcrumbs.length > 50) { var oldBreadcrumb = breadcrumbs.shift(); oldBreadcrumb.destroy(); } } } // Minimap update removed } }; // Start the game initLevel();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var DoorRenderer = Container.expand(function () {
var self = Container.call(this);
self.doors = [];
self.createDoor = function (x, y, roomNumber) {
var doorContainer = new Container();
// Door frame - made bigger and more prominent
var frame = LK.getAsset('door_frame', {
anchorX: 0.5,
anchorY: 1.0,
width: 80,
height: 100
});
frame.tint = 0x4a2c2a;
doorContainer.addChild(frame);
// Door panel - made more prominent
var panel = LK.getAsset('door', {
anchorX: 0.5,
anchorY: 1.0,
y: -8,
width: 72,
height: 80
});
panel.tint = 0x8b4513;
doorContainer.addChild(panel);
// Door handle
var handle = LK.getAsset('door_handle', {
anchorX: 0.5,
anchorY: 0.5,
x: 25,
y: -40
});
handle.tint = 0xffd700;
doorContainer.addChild(handle);
// Door nameplate with dimensions
var nameplate = LK.getAsset('door_nameplate', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -65
});
nameplate.tint = 0xc0c0c0;
doorContainer.addChild(nameplate);
// Room number text
var numberText = new Text2(roomNumber.toString(), {
size: 20,
fill: 0x000000
});
numberText.anchor.set(0.5, 0.5);
numberText.x = 0;
numberText.y = -72;
doorContainer.addChild(numberText);
// Dimension text on nameplate
var dimensionText = new Text2('80x100', {
size: 12,
fill: 0x000000
});
dimensionText.anchor.set(0.5, 0.5);
dimensionText.x = 0;
dimensionText.y = -58;
doorContainer.addChild(dimensionText);
doorContainer.x = x;
doorContainer.y = y;
return doorContainer;
};
return self;
});
var MazeGenerator = Container.expand(function () {
var self = Container.call(this);
self.generate = function (width, height) {
var maze = {
width: width,
height: height,
grid: [],
startX: 1,
startY: 1,
endX: width - 2,
endY: height - 2,
rooms: [],
currentRoom: 0
};
// Initialize maze with walls
for (var y = 0; y < height; y++) {
maze.grid[y] = [];
for (var x = 0; x < width; x++) {
maze.grid[y][x] = 1; // Wall
}
}
// Create linear sequence of rooms like Doors game
self.createLinearRooms(maze);
// Ensure start and end are open
maze.grid[maze.startY][maze.startX] = 0;
maze.grid[maze.endY][maze.endX] = 2; // Exit
return maze;
};
self.createLinearRooms = function (maze) {
// Create linear path with unique room shapes - only one way forward
var roomTypes = ['hallway', 'lshaped', 'library', 'bedroom'];
var currentX = 5;
var currentY = 5;
var connectionWidth = 2;
maze.rooms = [];
var roomNumber = 1;
// Create 100 rooms in a linear sequence
for (var i = 0; i < 100; i++) {
var roomType = roomTypes[i % 4]; // Cycle through room types
var room = self.createRoomByType(maze, roomType, currentX, currentY, roomNumber);
if (room) {
maze.rooms.push(room);
// Connect to next room if not the last one
if (i < 99) {
var nextRoomX = currentX + room.width + connectionWidth + 3;
var nextRoomY = currentY;
// Ensure next room fits, otherwise move to next row
if (nextRoomX + 8 >= maze.width) {
nextRoomX = 5;
nextRoomY = currentY + 10; // Move down for next row
}
// Create connection hallway
self.createConnection(maze, room, nextRoomX, nextRoomY);
currentX = nextRoomX;
currentY = nextRoomY;
}
roomNumber++;
}
}
// Set start in first room
if (maze.rooms.length > 0) {
maze.startX = maze.rooms[0].x + 1;
maze.startY = maze.rooms[0].y + 1;
// Set exit in last room
var lastRoom = maze.rooms[maze.rooms.length - 1];
maze.endX = lastRoom.x + lastRoom.width - 2;
maze.endY = lastRoom.y + lastRoom.height - 2;
}
};
self.createRoomByType = function (maze, roomType, startX, startY, roomNumber) {
var room = null;
switch (roomType) {
case 'hallway':
room = self.createHallway(maze, startX, startY, roomNumber);
break;
case 'lshaped':
room = self.createLShaped(maze, startX, startY, roomNumber);
break;
case 'library':
room = self.createLibrary(maze, startX, startY, roomNumber);
break;
case 'bedroom':
room = self.createBedroom(maze, startX, startY, roomNumber);
break;
}
return room;
};
self.createHallway = function (maze, startX, startY, roomNumber) {
var width = 8;
var height = 3;
if (startX + width >= maze.width || startY + height >= maze.height) return null;
// Clear hallway area
for (var y = startY; y < startY + height; y++) {
for (var x = startX; x < startX + width; x++) {
maze.grid[y][x] = 0;
}
}
// Add shelves along the walls using raycasting approach
// Place shelves on the top and bottom walls
for (var x = startX + 1; x < startX + width - 1; x += 2) {
// Top wall shelves
if (startY > 0 && maze.grid[startY - 1][x] === 1) {
maze.grid[startY][x] = 4; // Shelf marker
}
// Bottom wall shelves
if (startY + height < maze.height && maze.grid[startY + height][x] === 1) {
maze.grid[startY + height - 1][x] = 4; // Shelf marker
}
}
// Add desks in the middle area
if (startX + 3 < maze.width && startY + 1 < maze.height) {
maze.grid[startY + 1][startX + 3] = 7; // Desk marker
}
return {
x: startX,
y: startY,
width: width,
height: height,
number: roomNumber,
type: 'hallway'
};
};
self.createLShaped = function (maze, startX, startY, roomNumber) {
var width = 6;
var height = 6;
if (startX + width >= maze.width || startY + height >= maze.height) return null;
// Clear L-shaped area
for (var y = startY; y < startY + height; y++) {
for (var x = startX; x < startX + width; x++) {
// Create L shape - full first 4 rows, then only first 3 columns
if (y < startY + 4 || x < startX + 3) {
maze.grid[y][x] = 0;
}
}
}
return {
x: startX,
y: startY,
width: width,
height: height,
number: roomNumber,
type: 'lshaped'
};
};
self.createLibrary = function (maze, startX, startY, roomNumber) {
var width = 7;
var height = 5;
if (startX + width >= maze.width || startY + height >= maze.height) return null;
// Clear library area
for (var y = startY; y < startY + height; y++) {
for (var x = startX; x < startX + width; x++) {
maze.grid[y][x] = 0;
}
}
// Add book shelves along the walls
for (var x = startX + 1; x < startX + width - 1; x += 2) {
if (startY + 1 < maze.height) {
maze.grid[startY + 1][x] = 4; // Shelf marker
}
if (startY + 3 < maze.height) {
maze.grid[startY + 3][x] = 4; // Shelf marker
}
}
// Add reading desk in center
if (startY + 2 < maze.height && startX + 3 < maze.width) {
maze.grid[startY + 2][startX + 3] = 7; // Desk marker
}
// Add small paintings on walls
if (startY + 1 < maze.height && startX + 1 < maze.width) {
maze.grid[startY + 1][startX + 1] = 6; // Painting marker
}
if (startY + 3 < maze.height && startX + 5 < maze.width) {
maze.grid[startY + 3][startX + 5] = 6; // Painting marker
}
return {
x: startX,
y: startY,
width: width,
height: height,
number: roomNumber,
type: 'library'
};
};
self.createBedroom = function (maze, startX, startY, roomNumber) {
var width = 5;
var height = 5;
if (startX + width >= maze.width || startY + height >= maze.height) return null;
// Clear bedroom area
for (var y = startY; y < startY + height; y++) {
for (var x = startX; x < startX + width; x++) {
maze.grid[y][x] = 0;
}
}
// Add beds along the walls
if (startY + 1 < maze.height && startX + 1 < maze.width) {
maze.grid[startY + 1][startX + 1] = 5; // Bed marker
}
if (startY + 1 < maze.height && startX + 3 < maze.width) {
maze.grid[startY + 1][startX + 3] = 5; // Bed marker
}
// Add desk between beds
if (startY + 3 < maze.height && startX + 2 < maze.width) {
maze.grid[startY + 3][startX + 2] = 7; // Desk marker
}
// Add small paintings on walls
if (startY + 2 < maze.height && startX + 1 < maze.width) {
maze.grid[startY + 2][startX + 1] = 6; // Painting marker
}
if (startY + 2 < maze.height && startX + 3 < maze.width) {
maze.grid[startY + 2][startX + 3] = 6; // Painting marker
}
return {
x: startX,
y: startY,
width: width,
height: height,
number: roomNumber,
type: 'bedroom'
};
};
self.createConnection = function (maze, fromRoom, toX, toY) {
var fromX = fromRoom.x + fromRoom.width;
var fromY = fromRoom.y + Math.floor(fromRoom.height / 2);
var toConnectX = toX - 1;
var toConnectY = toY + 1;
// Create horizontal connection
var currentX = fromX;
while (currentX < toConnectX && currentX < maze.width) {
if (fromY >= 0 && fromY < maze.height && currentX >= 0) {
maze.grid[fromY][currentX] = 0;
}
currentX++;
}
// Place door at connection end with room number
if (toConnectX >= 0 && toConnectX < maze.width && fromY >= 0 && fromY < maze.height) {
maze.grid[fromY][toConnectX] = 3; // Door
// Store door number for rendering
if (!maze.doorNumbers) maze.doorNumbers = {};
maze.doorNumbers[fromY + '_' + toConnectX] = fromRoom.number + 1;
}
};
return self;
});
var Minimap = Container.expand(function () {
var self = Container.call(this);
self.cellSize = 8;
self.mapCells = [];
self.playerDot = null;
self.setup = function (maze) {
// Clear existing map
for (var i = 0; i < self.mapCells.length; i++) {
self.mapCells[i].destroy();
}
self.mapCells = [];
if (self.playerDot) {
self.playerDot.destroy();
}
// Create minimap cells
for (var y = 0; y < maze.height; y++) {
for (var x = 0; x < maze.width; x++) {
var cell = null;
if (maze.grid[y][x] === 1) {
// Wall
cell = LK.getAsset('wall', {
width: self.cellSize,
height: self.cellSize,
anchorX: 0,
anchorY: 0
});
cell.tint = 0x444444;
} else if (maze.grid[y][x] === 2) {
// Exit
cell = LK.getAsset('exit', {
width: self.cellSize,
height: self.cellSize,
anchorX: 0,
anchorY: 0
});
cell.tint = 0x00FF00;
} else if (maze.grid[y][x] === 3) {
// Door
cell = LK.getAsset('door', {
width: self.cellSize,
height: self.cellSize,
anchorX: 0,
anchorY: 0
});
cell.tint = 0x654321;
} else {
// Open path
cell = LK.getAsset('floor', {
width: self.cellSize,
height: self.cellSize,
anchorX: 0,
anchorY: 0
});
cell.tint = 0x888888;
}
if (cell) {
cell.x = x * self.cellSize;
cell.y = y * self.cellSize;
self.addChild(cell);
self.mapCells.push(cell);
}
}
}
// Create player dot
self.playerDot = LK.getAsset('player', {
width: self.cellSize,
height: self.cellSize,
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.playerDot);
};
self.updatePlayer = function (playerX, playerY) {
if (self.playerDot) {
self.playerDot.x = playerX * self.cellSize;
self.playerDot.y = playerY * self.cellSize;
}
};
return self;
});
var RaycastRenderer = Container.expand(function () {
var self = Container.call(this);
self.fov = Math.PI / 3; // 60 degrees field of view
self.rayCount = 256; // Number of rays to cast (expanded for full screen)
self.maxDistance = 15; // Reduced max distance
self.wallHeight = 300;
self.walls = [];
self.render = function (playerX, playerY, playerAngle, maze) {
// Initialize walls array if needed
if (self.walls.length === 0) {
for (var i = 0; i < self.rayCount; i++) {
var wall = LK.getAsset('raycast_wall', {
width: 8,
height: self.wallHeight,
anchorX: 0,
anchorY: 0.5
});
wall.x = i * 8;
wall.y = 2732 / 2;
wall.visible = false;
self.addChild(wall);
self.walls.push(wall);
}
}
var angleStep = self.fov / self.rayCount;
var rayAngle = playerAngle - self.fov / 2;
for (var i = 0; i < self.rayCount; i++) {
var rayResult = self.castRay(playerX, playerY, rayAngle, maze);
var wall = self.walls[i];
if (rayResult.distance < self.maxDistance) {
// Calculate wall height based on distance
var wallHeight = self.wallHeight / rayResult.distance;
if (wallHeight > 600) wallHeight = 600; // Cap wall height
// Update existing wall segment
wall.height = wallHeight;
wall.visible = true;
// Add depth shading and color based on object type
var shade = Math.max(0.2, 1 - rayResult.distance / self.maxDistance);
var baseColor = 0x8B4513; // Default wall color
if (rayResult.type === 3) baseColor = 0x4a2c2a; // Door color - darker brown for better visibility
else if (rayResult.type === 4) baseColor = 0x654321; // Shelf color
else if (rayResult.type === 5) baseColor = 0x800080; // Bed color
else if (rayResult.type === 6) baseColor = 0xFFD700; // Painting color
else if (rayResult.type === 7) baseColor = 0x8b4513; // Desk color
wall.tint = self.interpolateColor(0x000000, baseColor, shade);
} else {
wall.visible = false;
}
rayAngle += angleStep;
}
};
self.castRay = function (x, y, angle, maze) {
var dx = Math.cos(angle) * 0.1;
var dy = Math.sin(angle) * 0.1;
var distance = 0;
var stepSize = 0.1;
var maxSteps = 200; // Prevent infinite loops
var steps = 0;
var hitType = 0; // Track what we hit
while (distance < self.maxDistance && steps < maxSteps) {
x += dx;
y += dy;
distance += stepSize;
steps++;
var mapX = Math.floor(x);
var mapY = Math.floor(y);
if (mapX < 0 || mapX >= maze.width || mapY < 0 || mapY >= maze.height) {
return {
distance: distance,
type: 1
};
}
var cellType = maze.grid[mapY][mapX];
if (cellType === 1) {
return {
distance: distance,
type: 1
};
}
// Check for doors
if (cellType === 3) {
// Door
return {
distance: distance,
type: 3
};
}
// Check for furniture objects
if (cellType === 4) {
// Shelf
return {
distance: distance,
type: 4
};
}
if (cellType === 5) {
// Bed
return {
distance: distance,
type: 5
};
}
if (cellType === 6) {
// Painting
return {
distance: distance,
type: 6
};
}
if (cellType === 7) {
// Desk
return {
distance: distance,
type: 7
};
}
}
return {
distance: self.maxDistance,
type: 0
};
};
self.interpolateColor = function (color1, color2, factor) {
var r1 = color1 >> 16 & 0xFF;
var g1 = color1 >> 8 & 0xFF;
var b1 = color1 & 0xFF;
var r2 = color2 >> 16 & 0xFF;
var g2 = color2 >> 8 & 0xFF;
var b2 = color2 & 0xFF;
var r = Math.floor(r1 + (r2 - r1) * factor);
var g = Math.floor(g1 + (g2 - g1) * factor);
var b = Math.floor(b1 + (b2 - b1) * factor);
return r << 16 | g << 8 | b;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000,
name: 'Maze Runner',
description: 'Navigate through a first-person maze with unique room types including hallways, libraries, and bedrooms. Find doors to progress through 100 rooms and reach the exit!',
tags: ['maze', 'first-person', '3d', 'adventure', 'puzzle', 'exploration', 'rooms', 'doors', 'navigation']
});
/****
* Game Code
****/
// Game variables - Reset all values on new game
var currentLevel = 1;
var playerX = 1.5;
var playerY = 1.5;
var playerAngle = 0;
var moveSpeed = 0.05; // Reduced movement speed
var turnSpeed = 0.04; // Reduced turn speed
var maze = null;
var mazeGenerator = new MazeGenerator();
var raycastRenderer = new RaycastRenderer();
var minimap = new Minimap();
var roomsCompleted = 0; // Reset room counter
var doorColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff];
var breadcrumbs = [];
var breadcrumbContainer = new Container();
// Touch controls
var touchStartX = 0;
var touchStartY = 0;
var isTouching = false;
var moveForward = false;
var moveBackward = false;
var turnLeft = false;
var turnRight = false;
// UI elements
var levelText = new Text2('Level: ' + currentLevel, {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
var instructionText = new Text2('Find the exit!', {
size: 40,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0);
instructionText.y = 80;
LK.gui.top.addChild(instructionText);
var roomText = new Text2('Room 1', {
size: 50,
fill: 0xFFFF00
});
roomText.anchor.set(0.5, 0);
roomText.y = 140;
LK.gui.top.addChild(roomText);
var roomCounterText = new Text2('Rooms: ' + roomsCompleted + '/100', {
size: 40,
fill: 0xFFFFFF
});
roomCounterText.anchor.set(0.5, 0);
roomCounterText.y = 200;
LK.gui.top.addChild(roomCounterText);
// Compass UI
var compassBg = LK.getAsset('compass_bg', {
anchorX: 0.5,
anchorY: 0.5
});
LK.gui.topRight.addChild(compassBg);
compassBg.x = -80;
compassBg.y = 80;
var compassNeedle = LK.getAsset('compass_needle', {
anchorX: 0.5,
anchorY: 1.0
});
compassBg.addChild(compassNeedle);
// Direction labels
var northLabel = new Text2('N', {
size: 20,
fill: 0xFFFFFF
});
northLabel.anchor.set(0.5, 0.5);
northLabel.x = 0;
northLabel.y = -40;
compassBg.addChild(northLabel);
var southLabel = new Text2('S', {
size: 20,
fill: 0xFFFFFF
});
southLabel.anchor.set(0.5, 0.5);
southLabel.x = 0;
southLabel.y = 40;
compassBg.addChild(southLabel);
var eastLabel = new Text2('E', {
size: 20,
fill: 0xFFFFFF
});
eastLabel.anchor.set(0.5, 0.5);
eastLabel.x = 40;
eastLabel.y = 0;
compassBg.addChild(eastLabel);
var westLabel = new Text2('W', {
size: 20,
fill: 0xFFFFFF
});
westLabel.anchor.set(0.5, 0.5);
westLabel.x = -40;
westLabel.y = 0;
compassBg.addChild(westLabel);
// Direction indicator
var directionText = new Text2('Facing: North', {
size: 35,
fill: 0x00FF00
});
directionText.anchor.set(0.5, 0);
directionText.y = 250;
LK.gui.top.addChild(directionText);
// Door indicator
var doorIndicator = new Text2('', {
size: 40,
fill: 0xFF0000
});
doorIndicator.anchor.set(0.5, 0.5);
LK.gui.center.addChild(doorIndicator);
doorIndicator.y = -200;
// Control arrows
var forwardArrow = new Text2('↑', {
size: 120,
fill: 0xFFFFFF
});
forwardArrow.anchor.set(0.5, 0.5);
forwardArrow.alpha = 0.7;
LK.gui.bottomLeft.addChild(forwardArrow);
forwardArrow.x = 150;
forwardArrow.y = -150;
var leftArrow = new Text2('←', {
size: 120,
fill: 0xFFFFFF
});
leftArrow.anchor.set(0.5, 0.5);
leftArrow.alpha = 0.7;
LK.gui.bottomRight.addChild(leftArrow);
leftArrow.x = -300;
leftArrow.y = -150;
var rightArrow = new Text2('→', {
size: 120,
fill: 0xFFFFFF
});
rightArrow.anchor.set(0.5, 0.5);
rightArrow.alpha = 0.7;
LK.gui.bottomRight.addChild(rightArrow);
rightArrow.x = -150;
rightArrow.y = -150;
// Initialize first level
function initLevel() {
// Reset all game state
currentLevel = 1;
roomsCompleted = 0;
storage.currentLevel = 1;
storage.roomsCompleted = 0;
var mazeSize = 60; // Larger size to accommodate 100 rooms
maze = mazeGenerator.generate(mazeSize, mazeSize);
// Set player starting position
playerX = maze.startX + 0.5;
playerY = maze.startY + 0.5;
playerAngle = 0;
maze.currentRoom = 0;
// Minimap removed as requested
// Clear breadcrumbs
for (var i = 0; i < breadcrumbs.length; i++) {
breadcrumbs[i].destroy();
}
breadcrumbs = [];
breadcrumbContainer.removeChildren();
// Update UI
levelText.setText('Level: ' + currentLevel);
roomText.setText('Room 1');
roomCounterText.setText('Rooms: ' + roomsCompleted + '/100');
instructionText.setText('Find the exit!');
}
// Helper functions
function getDirectionName(angle) {
// Normalize angle to 0-360 degrees
var degrees = (angle * 180 / Math.PI + 360) % 360;
if (degrees >= 337.5 || degrees < 22.5) return 'North';
if (degrees >= 22.5 && degrees < 67.5) return 'Northeast';
if (degrees >= 67.5 && degrees < 112.5) return 'East';
if (degrees >= 112.5 && degrees < 157.5) return 'Southeast';
if (degrees >= 157.5 && degrees < 202.5) return 'South';
if (degrees >= 202.5 && degrees < 247.5) return 'Southwest';
if (degrees >= 247.5 && degrees < 292.5) return 'West';
if (degrees >= 292.5 && degrees < 337.5) return 'Northwest';
return 'North';
}
function findNearestDoor() {
var nearestDoor = null;
var minDistance = Infinity;
var currentMapX = Math.floor(playerX);
var currentMapY = Math.floor(playerY);
var searchRadius = 8;
for (var y = Math.max(0, currentMapY - searchRadius); y < Math.min(maze.height, currentMapY + searchRadius); y++) {
for (var x = Math.max(0, currentMapX - searchRadius); x < Math.min(maze.width, currentMapX + searchRadius); x++) {
if (maze.grid[y][x] === 3) {
var distance = Math.sqrt((x - playerX) * (x - playerX) + (y - playerY) * (y - playerY));
if (distance < minDistance) {
minDistance = distance;
nearestDoor = {
x: x,
y: y,
distance: distance
};
}
}
}
}
return nearestDoor;
}
function getDoorDirection(doorX, doorY) {
var dx = doorX - playerX;
var dy = doorY - playerY;
var angle = Math.atan2(dy, dx);
return getDirectionName(angle);
}
// Control functions
function handleMovement() {
var newX = playerX;
var newY = playerY;
if (moveForward) {
newX += Math.cos(playerAngle) * moveSpeed;
newY += Math.sin(playerAngle) * moveSpeed;
}
if (moveBackward) {
newX -= Math.cos(playerAngle) * moveSpeed;
newY -= Math.sin(playerAngle) * moveSpeed;
}
// Collision detection
var mapX = Math.floor(newX);
var mapY = Math.floor(newY);
if (mapX >= 0 && mapX < maze.width && mapY >= 0 && mapY < maze.height) {
if (maze.grid[mapY][mapX] !== 1) {
// Check for door interaction - doors automatically open on touch
if (maze.grid[mapY][mapX] === 3) {
// Door found - automatically open and increment room counter
maze.grid[mapY][mapX] = 0; // Open the door
roomsCompleted++;
storage.roomsCompleted = roomsCompleted;
roomCounterText.setText('Rooms: ' + roomsCompleted + '/100');
instructionText.setText('Door opened! Room ' + roomsCompleted + ' completed!');
LK.setTimeout(function () {
instructionText.setText('Find the exit!');
}, 1000);
// Check win condition
if (roomsCompleted >= 100) {
LK.showYouWin();
return;
}
}
playerX = newX;
playerY = newY;
// Update current room display
if (maze.rooms) {
for (var r = 0; r < maze.rooms.length; r++) {
var room = maze.rooms[r];
if (mapX >= room.x && mapX < room.x + room.width && mapY >= room.y && mapY < room.y + room.height) {
if (maze.currentRoom !== r) {
maze.currentRoom = r;
var roomTypeText = room.type ? room.type.charAt(0).toUpperCase() + room.type.slice(1) : 'Room';
roomText.setText('Room ' + (r + 1) + ' - ' + roomTypeText);
}
break;
}
}
}
// Check if reached exit
if (maze.grid[mapY][mapX] === 2) {
completeLevel();
}
}
}
if (turnLeft) {
playerAngle -= turnSpeed;
}
if (turnRight) {
playerAngle += turnSpeed;
}
// Normalize angle
while (playerAngle < 0) playerAngle += Math.PI * 2;
while (playerAngle >= Math.PI * 2) playerAngle -= Math.PI * 2;
// Update compass needle
compassNeedle.rotation = playerAngle;
// Update direction text
var currentDirection = getDirectionName(playerAngle);
directionText.setText('Facing: ' + currentDirection);
// Update door indicator
var nearestDoor = findNearestDoor();
if (nearestDoor && nearestDoor.distance < 5) {
var doorDirection = getDoorDirection(nearestDoor.x, nearestDoor.y);
var doorDistance = Math.round(nearestDoor.distance * 10) / 10;
doorIndicator.setText('Door: ' + doorDirection + ' (' + doorDistance + 'm)');
doorIndicator.visible = true;
} else {
doorIndicator.visible = false;
}
}
function completeLevel() {
LK.getSound('complete').play();
LK.setScore(currentLevel);
currentLevel++;
storage.currentLevel = currentLevel;
if (currentLevel > 10) {
LK.showYouWin();
} else {
// Move to next level without flashing
LK.setTimeout(function () {
initLevel();
}, 500);
}
}
// Arrow control event handlers
forwardArrow.down = function (x, y, obj) {
moveForward = true;
forwardArrow.alpha = 1.0;
};
forwardArrow.up = function (x, y, obj) {
moveForward = false;
forwardArrow.alpha = 0.7;
};
leftArrow.down = function (x, y, obj) {
turnLeft = true;
leftArrow.alpha = 1.0;
};
leftArrow.up = function (x, y, obj) {
turnLeft = false;
leftArrow.alpha = 0.7;
};
rightArrow.down = function (x, y, obj) {
turnRight = true;
rightArrow.alpha = 1.0;
};
rightArrow.up = function (x, y, obj) {
turnRight = false;
rightArrow.alpha = 0.7;
};
// Touch event handlers
game.down = function (x, y, obj) {
isTouching = true;
touchStartX = x;
touchStartY = y;
};
game.move = function (x, y, obj) {
// Remove conflicting touch movement to allow arrow controls to work
};
game.up = function (x, y, obj) {
isTouching = false;
moveForward = false;
turnLeft = false;
turnRight = false;
};
// Add renderer to game
game.addChild(raycastRenderer);
raycastRenderer.x = 0;
raycastRenderer.y = 0;
// Add breadcrumb container
game.addChild(breadcrumbContainer);
// Breadcrumb trail variables
var lastBreadcrumbX = -1;
var lastBreadcrumbY = -1;
var breadcrumbTimer = 0;
// Main game loop
var lastRenderTime = 0;
var renderInterval = 33; // ~30fps to reduce load
game.update = function () {
if (maze) {
handleMovement();
// Only render raycast every few frames to improve performance
if (LK.ticks - lastRenderTime >= 2) {
raycastRenderer.render(playerX, playerY, playerAngle, maze);
lastRenderTime = LK.ticks;
}
// Add breadcrumb trail
breadcrumbTimer++;
if (breadcrumbTimer >= 60) {
// Every second
var currentMapX = Math.floor(playerX);
var currentMapY = Math.floor(playerY);
if (currentMapX !== lastBreadcrumbX || currentMapY !== lastBreadcrumbY) {
var breadcrumb = LK.getAsset('breadcrumb', {
anchorX: 0.5,
anchorY: 0.5
});
breadcrumb.x = (currentMapX + 0.5) * 64;
breadcrumb.y = (currentMapY + 0.5) * 64;
breadcrumb.alpha = 0.7;
breadcrumbContainer.addChild(breadcrumb);
breadcrumbs.push(breadcrumb);
lastBreadcrumbX = currentMapX;
lastBreadcrumbY = currentMapY;
breadcrumbTimer = 0;
// Limit breadcrumbs to prevent memory issues
if (breadcrumbs.length > 50) {
var oldBreadcrumb = breadcrumbs.shift();
oldBreadcrumb.destroy();
}
}
}
// Minimap update removed
}
};
// Start the game
initLevel();