User prompt
Rename game name and description and tags
User prompt
Make the door look more obvious and have a nameplate to show the dimensions of the door. Also add small details like small paintings, desks, and more
User prompt
I cant tell were is forward and were is back sometimes I dont know where the door is and progression is confusing.
User prompt
Make sure to reset all values and memory on new game. Make doors actually look like doors and add numbers on the doors. Make player move slower
User prompt
Make the hallway actually a hallway, use raycasting to add shelfs, beds, and paintings
User prompt
Make it so that theres only one way forward and unique room shapes: Hallway, L shaped, Library, bedroom
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'maze.grid[doorY][doorX + h] = 0')' in or related to this line: 'maze.grid[doorY][doorX + h] = 0;' Line Number: 89
User prompt
Make rooms proceedurally generated up to the 100th room, Make rooms smaller and dont do flashing lights
User prompt
Cannot move. Also remove minimap on top, then make it so that doors are seperately colors and automatically open on touch, and after each room add a counter, at 100 rooms the player wins
User prompt
Make individual rooms with doors between them, use the roblox game “doors” as a reference
User prompt
I meant the lower corners
User prompt
Make arrow larger and move arrows to the corners then remove the backward arrow and add rooms and doors
User prompt
Expand screen size to full as it is cut in half. Then make it so that the forward arrow is at the left and left and right are at the right side
User prompt
When doing more than 2 actions the entire application crashes
User prompt
game completely crashes when moving foward
User prompt
Add forward back, and left and right camera arrows
Code edit (1 edits merged)
Please save this source code
User prompt
Maze Runner 3D
Initial prompt
Make a 3d raycasting maze game
/**** * 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();