// Legend of grid case const legend = { empty: 0, laser: 1, coloredLaser: 2, mirror: 3, door: 4, button: 5, wall: 6, target: 7, ligthLaser: 8, demiWallCornerUpLeft: 9, demiWallCornerUpRight: 10, demiWallCornerDownLeft: 11, demiWallCornerDownRight: 12, doorOpen: 13, button2: 14, captor: 15, cable: 16, captorTurn: 17, cableVertical: 18, captorTurnReturn: 19, rotatorButton: 20, cableTurn: 21, horizontalSemi: 22, cableTurnHorizontale : 23, cableTurnHorizontale2 : 24, captorTurnHorizontal : 25, wallSemiAngle: 26, }; const laserColors = { white: "white", red: "red", blue: "blue", yellow: "yellow", }; const glassOptions = [ [ { color: laserColors.red, maxAmount: 1, currentAmount: 1 }, { color: laserColors.blue, maxAmount: 1, currentAmount: 1 }, { color: laserColors.yellow, maxAmount: 1, currentAmount: 1 }, ], [ { color: laserColors.red, maxAmount: 1, currentAmount: 1 }, { color: laserColors.blue, maxAmount: 1, currentAmount: 1 }, { color: laserColors.yellow, maxAmount: 1, currentAmount: 1 }, ], [ { color: laserColors.red, maxAmount: 2, currentAmount: 2 }, { color: laserColors.blue, maxAmount: 2, currentAmount:2}, { color: laserColors.yellow, maxAmount: 2, currentAmount: 2 }, ], [ { color: laserColors.red, maxAmount: 2, currentAmount: 2 }, { color: laserColors.blue, maxAmount: 1, currentAmount: 1 }, { color: laserColors.yellow, maxAmount: 2, currentAmount: 2 }, ], ]; let levels = [ [ [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 6, 6, 1, 0, 5, 4, 0, 10, 6, 6, 6, 6, 6], [6, 6, 6, 6, 7, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6], [6, 6, 6, 6, 3, 0, 0, 0, 0, 12, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], ], [ [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 1, 0, 0, 0, 17, 0, 0, 3, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 18, 6, 6, 0, 6, 6, 6, 6, 6], [6, 6, 7, 0, 0, 0, 4, 0, 0, 12, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], ], [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 6, 6, 6, 6, 6, 0, 10, 6, 6, 11, 0, 0, 0, 0], [7, 1, 0, 0, 0, 4, 0, 0, 0, 3, 21, 0, 0, 0, 0], [0, 6, 6, 6, 6, 18, 6, 26, 22, 0, 18, 0, 0, 0, 0], [0, 6, 9, 0, 0, 19, 0, 0, 0, 12, 18, 0, 0, 0, 0], [0, 6, 0, 6, 6, 6, 6, 6, 6, 24, 23, 0, 0, 0, 0], [0, 6, 11, 0, 0, 0, 0, 0, 20, 23, 9, 0, 0, 0, 0], [0, 10, 6, 6, 6, 6, 6, 6, 6, 9, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ], [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 11, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 10, 6, 0, 0, 0, 0], [0, 0, 0, 3, 0, 16, 16, 20, 0, 3, 6, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 6, 0, 0, 0, 0], [0, 0, 0, 0, 12, 6, 6, 6, 6, 6, 9, 0, 0, 0, 0], [0, 0, 0, 0, 6, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 6, 0, 0, 3, 0, 0, 7, 0, 0, 0, 0], [0, 0, 0, 0, 6, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 10, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0], ], /* [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ], */ ]; let currentLevelIndex = 0; const initialMirrorAngles = [ { "6,4": 315, }, {}, { "2,9": 225, }, {} ]; const buttonGroups = [ { "4,6": 1, }, {}, { "9,4": 1, }, ]; const doorGroups = [ { "4,7": 1, }, { "4,6": 1, }, {}, ]; const captorGroups = [ {}, { "2,6": 1, }, {}, ]; const rotatorButtons = [ {}, {}, { "6,8": { mirrorX: 9, mirrorY: 2, step: 22.5, intervalMs: 1000 }, }, { "3,7": { mirrorX: 7, mirrorY: 7, step: -22.5, intervalMs: 1000 }, }, ]; let laserDirection = { dx: 0, dy: 0 }; let laserSegments = {}; let mirrorOrientations = {}; let glassPlacements = {}; let activatedButtons = {}; let openedDoors = {}; let isLevelFinished = false; let activeRotatorButtons = {}; let rotatorIntervals = {}; let toggledDoors = {}; let poweredCaptors = {}; let draggedGlassColor = null; let highestUnlockedLevelIndex = 0; const unlockedLevelsStorageKey = "mirror-game-highest-unlocked-level"; function getCurrentLevel() { return levels[currentLevelIndex]; } function getCurrentLevelConfig(configByLevel) { return configByLevel[currentLevelIndex] || {}; } function loadUnlockedLevels() { const savedValue = window.localStorage.getItem(unlockedLevelsStorageKey); const parsedValue = Number.parseInt(savedValue || "0", 10); if (Number.isNaN(parsedValue)) { highestUnlockedLevelIndex = 0; return; } highestUnlockedLevelIndex = Math.max(0, Math.min(parsedValue, levels.length - 1)); } function saveUnlockedLevels() { window.localStorage.setItem(unlockedLevelsStorageKey, String(highestUnlockedLevelIndex)); } function unlockLevel(levelIndex) { if (levelIndex <= highestUnlockedLevelIndex || levelIndex >= levels.length) { return; } highestUnlockedLevelIndex = levelIndex; saveUnlockedLevels(); renderLevelMenu(); } function toggleLevelMenu() { const levelMenuPanel = document.getElementById("level-menu-panel"); const levelMenuToggle = document.getElementById("level-menu-toggle"); if (!levelMenuPanel || !levelMenuToggle) { return; } const isOpening = levelMenuPanel.hasAttribute("hidden"); if (isOpening) { levelMenuPanel.removeAttribute("hidden"); } else { levelMenuPanel.setAttribute("hidden", ""); } levelMenuToggle.setAttribute("aria-expanded", String(isOpening)); } function renderLevelMenu() { const levelMenuList = document.getElementById("level-menu-list"); if (!levelMenuList) { return; } levelMenuList.innerHTML = ""; for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) { const levelButton = document.createElement("button"); const isUnlocked = levelIndex <= highestUnlockedLevelIndex; const isCurrentLevel = levelIndex === currentLevelIndex; levelButton.type = "button"; levelButton.classList.add("level-menu-item"); levelButton.textContent = isUnlocked ? `Level ${levelIndex + 1}` : `Level ${levelIndex + 1} - Locked`; levelButton.disabled = !isUnlocked; if (isCurrentLevel) { levelButton.classList.add("is-current"); } levelButton.addEventListener("click", () => { if (!isUnlocked) { return; } goToLevel(levelIndex); const levelMenuPanel = document.getElementById("level-menu-panel"); const levelMenuToggle = document.getElementById("level-menu-toggle"); if (levelMenuPanel) { levelMenuPanel.setAttribute("hidden", ""); } if (levelMenuToggle) { levelMenuToggle.setAttribute("aria-expanded", "false"); } }); levelMenuList.appendChild(levelButton); } } function setupLevelMenu() { const levelMenuToggle = document.getElementById("level-menu-toggle"); if (!levelMenuToggle) { return; } levelMenuToggle.addEventListener("click", toggleLevelMenu); renderLevelMenu(); } function getCurrentGlassOptions() { return glassOptions[currentLevelIndex] || []; } function getGlassOptionByColor(color) { const currentGlassOptions = getCurrentGlassOptions(); for (let i = 0; i < currentGlassOptions.length; i++) { if (currentGlassOptions[i].color === color) { return currentGlassOptions[i]; } } return null; } function resetGlassInventory() { const currentGlassOptions = getCurrentGlassOptions(); for (let i = 0; i < currentGlassOptions.length; i++) { currentGlassOptions[i].currentAmount = currentGlassOptions[i].maxAmount; } } function normalizeLaserDirection(dx, dy) { const epsilon = 0.0001; const normalizedDx = Math.abs(dx) < epsilon ? 0 : Math.sign(dx); const normalizedDy = Math.abs(dy) < epsilon ? 0 : Math.sign(dy); return { dx: normalizedDx, dy: normalizedDy }; } function reflectLaser(direction, mirrorAngle) { const mirrorRadians = mirrorAngle * (Math.PI / 180); const mirrorVectorX = Math.cos(mirrorRadians); const mirrorVectorY = Math.sin(mirrorRadians); const dotProduct = (direction.dx * mirrorVectorX) + (direction.dy * mirrorVectorY); const reflectedDx = (2 * dotProduct * mirrorVectorX) - direction.dx; const reflectedDy = (2 * dotProduct * mirrorVectorY) - direction.dy; return normalizeLaserDirection(reflectedDx, reflectedDy); } function reverseLaser(direction) { return { dx: direction.dx * -1, dy: direction.dy * -1, }; } function getLaserSegmentClass(segmentDirection) { if (!segmentDirection) { return "laser-horizontal"; } if (segmentDirection.dx === 0) { return "laser-vertical"; } if (segmentDirection.dy === 0) { return "laser-horizontal"; } if (segmentDirection.dx === segmentDirection.dy) { return "laser-diagonal-down"; } return "laser-diagonal-up"; } function getLaserColorClass(color) { return `laser-color-${color || laserColors.white}`; } function getButtonGroup(x, y) { const cellType = getCurrentLevel()[y][x]; if (cellType === legend.button2) { return 2; } return getCurrentLevelConfig(buttonGroups)[`${y},${x}`] || 1; } function getDoorGroup(x, y) { return getCurrentLevelConfig(doorGroups)[`${y},${x}`] || 1; } function getCaptorGroup(x, y) { return getCurrentLevelConfig(captorGroups)[`${y},${x}`] || getDoorGroup(x, y); } function openDoorsFromButton(x, y) { const buttonGroup = getButtonGroup(x, y); const level = getCurrentLevel(); for (let doorY = 0; doorY < level.length; doorY++) { for (let doorX = 0; doorX < level[doorY].length; doorX++) { if (level[doorY][doorX] === legend.door && getDoorGroup(doorX, doorY) === buttonGroup) { openedDoors[`${doorY},${doorX}`] = true; } } } } function toggleDoorsFromCaptor(x, y) { const captorGroup = getCaptorGroup(x, y); const level = getCurrentLevel(); for (let doorY = 0; doorY < level.length; doorY++) { for (let doorX = 0; doorX < level[doorY].length; doorX++) { const coordKey = `${doorY},${doorX}`; if (level[doorY][doorX] === legend.door && getDoorGroup(doorX, doorY) === captorGroup) { toggledDoors[coordKey] = !toggledDoors[coordKey]; } } } } function getRotatorButtonConfig(x, y) { return getCurrentLevelConfig(rotatorButtons)[`${y},${x}`]; } function isMirrorControlledByButton(x, y) { const rotatorEntries = Object.values(getCurrentLevelConfig(rotatorButtons)); for (let i = 0; i < rotatorEntries.length; i++) { const rotatorConfig = rotatorEntries[i]; if (rotatorConfig.mirrorX === x && rotatorConfig.mirrorY === y) { return true; } } return false; } function rotateMirrorStep(x, y, angleStep) { const coordKey = `${y},${x}`; if (getCurrentLevel()[y][x] !== legend.mirror) { return; } let currentAngle = mirrorOrientations[coordKey] || 0; currentAngle = (currentAngle + angleStep) % 360; if (currentAngle < 0) { currentAngle += 360; } mirrorOrientations[coordKey] = currentAngle; } function syncRotatorButtons() { const currentLevelRotatorButtons = getCurrentLevelConfig(rotatorButtons); const rotatorKeys = Object.keys(currentLevelRotatorButtons); for (let i = 0; i < rotatorKeys.length; i++) { const key = rotatorKeys[i]; const config = currentLevelRotatorButtons[key]; const isActive = activeRotatorButtons[key] === true; if (isActive && !rotatorIntervals[key]) { rotatorIntervals[key] = window.setInterval(() => { rotateMirrorStep(config.mirrorX, config.mirrorY, -22.5); traceLaser(); }, 1000); } if (!isActive && rotatorIntervals[key]) { window.clearInterval(rotatorIntervals[key]); delete rotatorIntervals[key]; } } } function stopAllRotatorButtons() { const intervalKeys = Object.keys(rotatorIntervals); for (let i = 0; i < intervalKeys.length; i++) { const key = intervalKeys[i]; window.clearInterval(rotatorIntervals[key]); } rotatorIntervals = {}; } function saveLaserSegment(x, y, direction, color) { laserSegments[`${y},${x}`] = { direction: { ...direction }, color: color, }; } function isGlassOnCell(x, y) { return glassPlacements[`${y},${x}`] !== undefined; } function drawGlassInCell(cell, x, y) { const glassColor = glassPlacements[`${y},${x}`]; if (!glassColor) { return; } const glassDiv = document.createElement("div"); glassDiv.classList.add("cell-glass", `glass-${glassColor}`); cell.appendChild(glassDiv); } function drawLaserInCell(cell, segmentData) { if (!segmentData) { return; } const laserDiv = document.createElement("div"); laserDiv.classList.add("laser-overlay"); laserDiv.classList.add(getLaserSegmentClass(segmentData.direction)); laserDiv.classList.add(getLaserColorClass(segmentData.color)); cell.appendChild(laserDiv); } function createPalette() { const palette = document.getElementById("glass-palette"); if (!palette) { return; } palette.innerHTML = ""; const currentGlassOptions = getCurrentGlassOptions(); for (let i = 0; i < currentGlassOptions.length; i++) { const glassOption = currentGlassOptions[i]; const glassColor = glassOption.color; const glassButton = document.createElement("button"); glassButton.type = "button"; glassButton.classList.add("glass-item", `glass-${glassColor}`); glassButton.draggable = glassOption.currentAmount > 0; if (glassOption.currentAmount <= 0) { glassButton.disabled = true; } const glassLabel = document.createElement("span"); glassLabel.classList.add("glass-item-label"); glassLabel.textContent = `${glassOption.currentAmount}/${glassOption.maxAmount}`; glassButton.appendChild(glassLabel); glassButton.addEventListener("dragstart", (event) => { if (glassOption.currentAmount <= 0) { event.preventDefault(); return; } draggedGlassColor = glassColor; event.dataTransfer.effectAllowed = "copy"; event.dataTransfer.setData("application/x-glass-color", glassColor); }); glassButton.addEventListener("dragend", () => { draggedGlassColor = null; }); palette.appendChild(glassButton); } } function addDropEvents(cell, x, y) { cell.addEventListener("dragover", (event) => { if (isLevelFinished) { return; } const transferTypes = event.dataTransfer ? Array.from(event.dataTransfer.types || []) : []; if (!draggedGlassColor) { return; } const cellType = getCurrentLevel()[y][x]; if (cellType !== legend.empty) { return; } const selectedColor = draggedGlassColor; const glassOption = getGlassOptionByColor(selectedColor); if (!glassOption || glassOption.currentAmount <= 0) { return; } event.preventDefault(); cell.classList.add("can-drop"); }); cell.addEventListener("dragleave", () => { cell.classList.remove("can-drop"); }); cell.addEventListener("drop", (event) => { if (isLevelFinished) { return; } const transferredColor = event.dataTransfer ? event.dataTransfer.getData("application/x-glass-color") : ""; const selectedColor = transferredColor || draggedGlassColor; cell.classList.remove("can-drop"); if (!selectedColor) { return; } if (getCurrentLevel()[y][x] !== legend.empty) { return; } if (isGlassOnCell(x, y)) { return; } const glassOption = getGlassOptionByColor(selectedColor); if (!glassOption || glassOption.currentAmount <= 0) { return; } event.preventDefault(); glassPlacements[`${y},${x}`] = selectedColor; glassOption.currentAmount--; draggedGlassColor = null; createPalette(); traceLaser(); }); cell.addEventListener("dblclick", () => { if (isLevelFinished) { return; } if (isGlassOnCell(x, y)) { const glassColor = glassPlacements[`${y},${x}`]; const glassOption = getGlassOptionByColor(glassColor); delete glassPlacements[`${y},${x}`]; if (glassOption) { glassOption.currentAmount = Math.min(glassOption.currentAmount + 1, glassOption.maxAmount); } createPalette(); traceLaser(); } }); } function blockBrowserDrop() { document.addEventListener("dragover", (event) => { event.preventDefault(); }); document.addEventListener("drop", (event) => { event.preventDefault(); }); } function initializeMirrorOrientations() { mirrorOrientations = {}; const level = getCurrentLevel(); for (let y = 0; y < level.length; y++) { for (let x = 0; x < level[y].length; x++) { if (level[y][x] === legend.mirror) { mirrorOrientations[`${y},${x}`] = getCurrentLevelConfig(initialMirrorAngles)[`${y},${x}`] || 0; } } } } function loadGrid() { const mapDiv = document.getElementById("map"); const level = getCurrentLevel(); mapDiv.innerHTML = ""; for (let y = 0; y < level.length; y++) { const lign = document.createElement("div"); lign.classList.add("lign"); for (let x = 0; x < level[y].length; x++) { const cell = document.createElement("div"); cell.classList.add("cell"); addDropEvents(cell, x, y); switch (level[y][x]) { case legend.empty: cell.classList.add("empty"); break; case legend.laser: cell.classList.add("laser"); break; case legend.coloredLaser: cell.classList.add("colored-laser"); break; case legend.mirror: cell.classList.add("mirror"); const currentAngle = mirrorOrientations[`${y},${x}`] || 0; const img = document.createElement("img"); img.src = "../../assets/img/tiles/Mirror.svg"; img.classList.add("mirror-img"); img.style.transform = `rotate(${currentAngle}deg)`; if (isMirrorControlledByButton(x, y)) { const mirrorDisplay = document.createElement("div"); mirrorDisplay.classList.add("btn-mirror", "btn-mirror-locked"); mirrorDisplay.appendChild(img); cell.appendChild(mirrorDisplay); } else { const btnMirror = document.createElement("button"); btnMirror.classList.add("btn-mirror"); btnMirror.type = "button"; btnMirror.appendChild(img); btnMirror.onmousedown = (event) => { event.preventDefault(); event.stopPropagation(); if (!isLevelFinished) { rotateMirror(x, y, event.button === 2); } }; btnMirror.oncontextmenu = (event) => event.preventDefault(); cell.appendChild(btnMirror); } break; case legend.door: cell.classList.add("door"); if (openedDoors[`${y},${x}`]) { cell.classList.add("door-open"); } break; case legend.doorOpen: cell.classList.add("door", "door-open"); break; case legend.button: cell.classList.add("button"); if (activatedButtons[`${y},${x}`]) { cell.classList.add("button-active"); } break; case legend.button2: cell.classList.add("button", "button-2"); if (activatedButtons[`${y},${x}`]) { cell.classList.add("button-active"); } break; case legend.rotatorButton: cell.classList.add("button", "button-rotator"); if (activatedButtons[`${y},${x}`]) { cell.classList.add("button-active"); } break; case legend.wall: cell.classList.add("wall"); break; case legend.target: cell.classList.add("target"); break; case legend.demiWallCornerUpLeft: cell.classList.add("demi-wall-corner-up-left"); break; case legend.demiWallCornerUpRight: cell.classList.add("demi-wall-corner-up-right"); break; case legend.demiWallCornerDownLeft: cell.classList.add("demi-wall-corner-down-left"); break; case legend.demiWallCornerDownRight: cell.classList.add("demi-wall-corner-down-right"); break; case legend.captor: cell.classList.add("captor"); break; case legend.cable: cell.classList.add("cable"); break; case legend.captorTurn: cell.classList.add("captor-turn"); break; case legend.cableVertical: cell.classList.add("cable-vertical"); break; case legend.captorTurnReturn: cell.classList.add("captor-turn-reverse"); break; case legend.cableTurn: cell.classList.add("cable-turn"); break; case legend.horizontalSemi: cell.classList.add("horizontal-semi"); break; case legend.cableTurnHorizontale: cell.classList.add("cable-turn-horizontale"); break; case legend.cableTurnHorizontale2: cell.classList.add("cable-turn-horizontale2"); break; case legend.captorTurnHorizontal: cell.classList.add("captor-turn-horizontale2"); break; case legend.wallSemiAngle: cell.classList.add("wall-semi-angle"); break; } drawLaserInCell(cell, laserSegments[`${y},${x}`]); drawGlassInCell(cell, x, y); lign.appendChild(cell); } mapDiv.appendChild(lign); } } function rotateMirror(x, y, isRightClick) { rotateMirrorStep(x, y, isRightClick ? 22.5 : -22.5); traceLaser(); } function traceLaser() { laserSegments = {}; activatedButtons = {}; openedDoors = { ...toggledDoors }; activeRotatorButtons = {}; isLevelFinished = false; const nextPoweredCaptors = {}; const level = getCurrentLevel(); let startLaserX; let startLaserY; for (let y = 0; y < level.length; y++) { for (let x = 0; x < level[y].length; x++) { if (level[y][x] === legend.laser) { startLaserX = x; startLaserY = y; laserDirection = { dx: 1, dy: 0 }; break; } } if (startLaserX !== undefined) { break; } } if (startLaserX === undefined) { return; } let currentX = startLaserX; let currentY = startLaserY; let currentLaserColor = laserColors.white; let laserActive = true; let iterations = 0; const maxIterations = 1000; while (laserActive && iterations < maxIterations) { iterations++; currentX += laserDirection.dx; currentY += laserDirection.dy; if (currentX < 0 || currentX >= level[0].length || currentY < 0 || currentY >= level.length) { laserActive = false; break; } const cellType = level[currentY][currentX]; const glassColor = glassPlacements[`${currentY},${currentX}`]; if (glassColor) { currentLaserColor = glassColor; } if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); if (cellType === legend.target) { laserActive = false; isLevelFinished = true; } continue; } switch (cellType) { case legend.laser: case legend.coloredLaser: saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); break; case legend.empty: saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); break; case legend.target: saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserActive = false; isLevelFinished = true; break; case legend.mirror: if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else { const mirrorAngle = mirrorOrientations[`${currentY},${currentX}`] || 0; laserDirection = reflectLaser(laserDirection, mirrorAngle); } break; case legend.wall: if (currentLaserColor === laserColors.blue) { laserDirection = reverseLaser(laserDirection); } else if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else { laserActive = false; } break; case legend.wallSemiAngle: if (currentLaserColor === laserColors.blue) { laserDirection = reverseLaser(laserDirection); } else if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else { laserActive = false; } break; case legend.horizontalSemi: if (currentLaserColor === laserColors.blue) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserDirection = reflectLaser(laserDirection, 0); } else if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else { laserActive = false; } break; case legend.cableVertical: case legend.cableTurn: case legend.door: case legend.doorOpen: if (openedDoors[`${currentY},${currentX}`] || cellType === legend.doorOpen) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else if (currentLaserColor === laserColors.blue) { laserDirection = reverseLaser(laserDirection); } else if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else { laserActive = false; } break; case legend.button: case legend.button2: if (currentLaserColor === laserColors.red) { activatedButtons[`${currentY},${currentX}`] = true; openDoorsFromButton(currentX, currentY); saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else if (currentLaserColor === laserColors.blue) { laserDirection = reverseLaser(laserDirection); } else { laserActive = false; } break; case legend.captor: if (currentLaserColor === laserColors.blue) { laserDirection = reverseLaser(laserDirection); } else if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else { laserActive = false; } break; case legend.captorTurn: if (currentLaserColor === laserColors.red) { const captorKey = `${currentY},${currentX}`; activatedButtons[captorKey] = true; nextPoweredCaptors[captorKey] = true; if (!poweredCaptors[captorKey]) { toggleDoorsFromCaptor(currentX, currentY); openedDoors = { ...toggledDoors }; } saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserActive = false; }else if(currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else { laserActive = false; } break; case legend.captorTurnReturn: if (currentLaserColor === laserColors.red) { const captorKey = `${currentY},${currentX}`; activatedButtons[captorKey] = true; nextPoweredCaptors[captorKey] = true; if (!poweredCaptors[captorKey]) { toggleDoorsFromCaptor(currentX, currentY); openedDoors = { ...toggledDoors }; } saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserActive = false; }else if(currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else { laserActive = false; } break; case legend.captorTurnHorizontal: if (currentLaserColor === laserColors.red) { const captorKey = `${currentY},${currentX}`; activatedButtons[captorKey] = true; nextPoweredCaptors[captorKey] = true; if (!poweredCaptors[captorKey]) { toggleDoorsFromCaptor(currentX, currentY); openedDoors = { ...toggledDoors }; } saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserActive = false; }else if(currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else { laserActive = false; } break; case legend.rotatorButton: if (currentLaserColor === laserColors.red) { const rotatorKey = `${currentY},${currentX}`; activatedButtons[rotatorKey] = true; activeRotatorButtons[rotatorKey] = true; saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserActive = false; } else if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else if (currentLaserColor === laserColors.blue) { laserDirection = reverseLaser(laserDirection); laserActive = false; } else { laserActive = false; } break; case legend.demiWallCornerUpLeft: if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else if (currentLaserColor === laserColors.blue) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserDirection = reflectLaser(laserDirection, 135); } break; case legend.demiWallCornerUpRight: if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else if (currentLaserColor === laserColors.blue) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserDirection = reflectLaser(laserDirection, 45); } break; case legend.demiWallCornerDownLeft: if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else if (currentLaserColor === laserColors.blue) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserDirection = reflectLaser(laserDirection, 225); } break; case legend.demiWallCornerDownRight: if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else if (currentLaserColor === laserColors.blue) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserDirection = reflectLaser(laserDirection, 315); } break; default: saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); break; } } poweredCaptors = nextPoweredCaptors; syncRotatorButtons(); loadGrid(); if (isLevelFinished) { finish(); } } function finish() { unlockLevel(currentLevelIndex + 1); setTimeout(() => { const winOverlay = document.querySelector(".win-overlay"); winOverlay.style.visibility = "visible"; }, 100); } function goToLevel(levelIndex) { currentLevelIndex = levelIndex; isLevelFinished = false; stopAllRotatorButtons(); mirrorOrientations = {}; laserSegments = {}; activatedButtons = {}; openedDoors = {}; toggledDoors = {}; poweredCaptors = {}; initializeMirrorOrientations(); glassPlacements = {}; resetGlassInventory(); createPalette(); loadGrid(); traceLaser(); const winOverlay = document.querySelector(".win-overlay"); winOverlay.style.visibility = "hidden"; renderLevelMenu(); } function nextLevel() { const nextLevelIndex = currentLevelIndex + 1 >= levels.length ? 0 : currentLevelIndex + 1; goToLevel(nextLevelIndex); } loadUnlockedLevels(); resetGlassInventory(); createPalette(); initializeMirrorOrientations(); blockBrowserDrop(); setupLevelMenu(); traceLaser();