diff --git a/web/assets/css/game.css b/web/assets/css/game.css index e1467dc..36c842a 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -20,6 +20,71 @@ body { user-select: none; } +.level-menu-shell { + position: fixed; + top: 24px; + right: 24px; + z-index: 1100; + display: flex; + align-items: flex-start; + gap: 10px; +} + +.level-menu-toggle { + border: none; + border-radius: 999px; + background: #223; + color: #fff; + padding: 12px 18px; + font-size: 0.95rem; + font-weight: 700; + cursor: pointer; + box-shadow: 0 8px 20px rgba(34, 51, 68, 0.18); +} + +.level-menu-panel { + min-width: 180px; + background: rgba(255, 255, 255, 0.96); + border-radius: 18px; + padding: 14px; + box-shadow: 0 18px 40px rgba(34, 51, 68, 0.18); + border: 1px solid rgba(34, 51, 68, 0.08); +} + +.level-menu-panel h2 { + font-size: 0.95rem; + color: #223; + margin-bottom: 10px; +} + +.level-menu-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.level-menu-item { + border: none; + border-radius: 10px; + padding: 10px 12px; + text-align: left; + background: #dfe5f8; + color: #223; + font-weight: 700; + cursor: pointer; +} + +.level-menu-item.is-current { + background: #223; + color: #fff; +} + +.level-menu-item:disabled { + background: #eceff7; + color: #8a92a3; + cursor: not-allowed; +} + main { display: flex; flex-direction: column; @@ -143,6 +208,22 @@ main { background-position: center; } +.wall-semi-angle { + background-color: #DADEEF; + background-image: url("/web/assets/img/tiles/BottomLeft.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; +} + +.horizontal-semi { + background-color: #DADEEF; + background-image: url("/web/assets/img/tiles/HorizontaleSemi.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: bottom; +} + .captor { background-color: #DADEEF; background-image: url("/web/assets/img/tiles/Capteur-1.svg"); @@ -160,13 +241,37 @@ main { background-position: center; } -.cable { +.captor-turn-reverse { background-color: #DADEEF; - background-image: url("/web/assets/img/tiles/CableV.svg"); + background-image: url("/web/assets/img/tiles/Capteur-2.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; + transform: rotate(180deg); +} + +.captor-turn-horizontale2 { + background-color: #DADEEF; + background-image: url("/web/assets/img/tiles/Capteur-1.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; +} + +.cable { + background-color: #DADEEF; + background-image: url("/web/assets/img/tiles/CableH.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; +} + +.cable-turn { + background-color: #DADEEF; + background-image: url("/web/assets/img/tiles/CableBottomLeft.svg"); background-size: 100%; background-repeat: no-repeat; background-position: center; - transform: rotate(90deg); } .cable-vertical { @@ -177,6 +282,23 @@ main { background-position: center; } +.cable-turn-horizontale { + background-color: #DADEEF; + background-image: url("/web/assets/img/tiles/CableTopLeft.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; +} + +.cable-turn-horizontale2 { + background-color: #DADEEF; + background-image: url("/web/assets/img/tiles/CableTopLeft.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; + transform: rotate(180deg); +} + .door { background-color: #DADEEF; background-image: url("/web/assets/img/tiles/WoodenDoor.svg"); @@ -246,7 +368,7 @@ main { .demi-wall-corner-down-left { background-color: #DADEEF; - background-image: url("/web/assets/img/tiles/BottomLeft.svg"); + background-image: url("/web/assets/img/tiles/BottomLeftAngle.svg"); background-size: 100%; background-repeat: no-repeat; background-position: center; @@ -254,7 +376,7 @@ main { .demi-wall-corner-down-right { background-color: #DADEEF; - background-image: url("/web/assets/img/tiles/BottomRight.svg"); + background-image: url("/web/assets/img/tiles/BottomRightAngle.svg"); background-size: 100%; background-repeat: no-repeat; background-position: center; @@ -341,6 +463,24 @@ main { font-weight: bold; } +.glass-item:disabled { + cursor: not-allowed; + opacity: 0.45; +} + +.glass-item-label { + position: absolute; + right: 4px; + bottom: 3px; + z-index: 2; + font-size: 0.7rem; + font-weight: 700; + color: #223; + background: rgba(255, 255, 255, 0.88); + padding: 1px 5px; + border-radius: 999px; +} + .glass-item::after, .cell-glass::after { content: ""; diff --git a/web/assets/img/tiles/BottomLeft.svg b/web/assets/img/tiles/BottomLeft.svg index 15a864e..1a1e438 100644 --- a/web/assets/img/tiles/BottomLeft.svg +++ b/web/assets/img/tiles/BottomLeft.svg @@ -1,17 +1,19 @@ - + - - - - + + + + + + - + - + diff --git a/web/assets/img/tiles/BottomLeftAngle.svg b/web/assets/img/tiles/BottomLeftAngle.svg new file mode 100644 index 0000000..15a864e --- /dev/null +++ b/web/assets/img/tiles/BottomLeftAngle.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/BottomRight.svg b/web/assets/img/tiles/BottomRight.svg index 3e8e4df..cd4ff05 100644 --- a/web/assets/img/tiles/BottomRight.svg +++ b/web/assets/img/tiles/BottomRight.svg @@ -1,17 +1,19 @@ - + - - - - + + + + + + - + - + diff --git a/web/assets/img/tiles/BottomRightAngle.svg b/web/assets/img/tiles/BottomRightAngle.svg new file mode 100644 index 0000000..3e8e4df --- /dev/null +++ b/web/assets/img/tiles/BottomRightAngle.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/js/game.js b/web/assets/js/game.js index 2de89a9..7cfc869 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -20,6 +20,14 @@ const legend = { cable: 16, captorTurn: 17, cableVertical: 18, + captorTurnReturn: 19, + rotatorButton: 20, + cableTurn: 21, + horizontalSemi: 22, + cableTurnHorizontale : 23, + cableTurnHorizontale2 : 24, + captorTurnHorizontal : 25, + wallSemiAngle: 26, }; const laserColors = { @@ -30,34 +38,64 @@ const laserColors = { }; const glassOptions = [ - laserColors.red, - laserColors.blue, - laserColors.yellow, + [ + { 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 = [ [ - [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, 5, 4, 0, 10, 6, 0, 0, 0, 0], - [0, 0, 0, 0, 7, 6, 6, 6, 6, 0, 6, 0, 0, 0, 0], - [0, 0, 0, 0, 3, 0, 0, 0, 0, 12, 6, 0, 0, 0, 0], - [0, 0, 0, 0, 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], + [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, 0, 6, 6, 6, 6, 6, 6, 6, 6, 11, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 17, 0, 0, 3, 6, 0, 0, 0, 0], - [0, 0, 6, 6, 6, 6, 18, 6, 6, 0, 6, 0, 0, 0, 0], - [0, 0, 7, 0, 0, 0, 4, 0, 0, 12, 6, 0, 0, 0, 0], - [0, 0, 6, 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, 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], ], @@ -87,9 +125,7 @@ let levels = [ [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 = [ @@ -98,9 +134,9 @@ const initialMirrorAngles = [ }, {}, { - "3,4": 315, - "7,8": 0, + "2,9": 225, }, + {} ]; const buttonGroups = [ @@ -108,7 +144,9 @@ const buttonGroups = [ "4,6": 1, }, {}, - {}, + { + "9,4": 1, + }, ]; const doorGroups = [ @@ -132,6 +170,9 @@ const captorGroups = [ const rotatorButtons = [ {}, {}, + { + "6,8": { mirrorX: 9, mirrorY: 2, step: 22.5, intervalMs: 1000 }, + }, { "3,7": { mirrorX: 7, mirrorY: 7, step: -22.5, intervalMs: 1000 }, }, @@ -148,6 +189,9 @@ 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]; @@ -157,6 +201,131 @@ 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); @@ -366,17 +535,40 @@ function createPalette() { palette.innerHTML = ""; - for (let i = 0; i < glassOptions.length; i++) { - const glassColor = glassOptions[i]; + 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 = true; + 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); } } @@ -387,7 +579,8 @@ function addDropEvents(cell, x, y) { return; } - if (!event.dataTransfer.types.includes("application/x-glass-color")) { + const transferTypes = event.dataTransfer ? Array.from(event.dataTransfer.types || []) : []; + if (!draggedGlassColor) { return; } @@ -396,6 +589,13 @@ function addDropEvents(cell, x, y) { return; } + const selectedColor = draggedGlassColor; + const glassOption = getGlassOptionByColor(selectedColor); + + if (!glassOption || glassOption.currentAmount <= 0) { + return; + } + event.preventDefault(); cell.classList.add("can-drop"); }); @@ -409,7 +609,8 @@ function addDropEvents(cell, x, y) { return; } - const selectedColor = event.dataTransfer.getData("application/x-glass-color"); + const transferredColor = event.dataTransfer ? event.dataTransfer.getData("application/x-glass-color") : ""; + const selectedColor = transferredColor || draggedGlassColor; cell.classList.remove("can-drop"); if (!selectedColor) { @@ -420,8 +621,21 @@ function addDropEvents(cell, x, y) { 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(); }); @@ -431,7 +645,16 @@ function addDropEvents(cell, x, y) { } 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(); } }); @@ -570,6 +793,27 @@ function loadGrid() { 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}`]); @@ -641,10 +885,21 @@ function traceLaser() { 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: - laserActive = false; + saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); break; case legend.empty: @@ -675,7 +930,27 @@ function traceLaser() { 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) { @@ -704,6 +979,14 @@ function traceLaser() { 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}`; @@ -723,7 +1006,44 @@ function traceLaser() { 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}`; @@ -742,25 +1062,37 @@ function traceLaser() { break; case legend.demiWallCornerUpLeft: - if(currentLaserColor === laserColors.blue) { + 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.blue) { + 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.blue) { + 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.blue) { + if (currentLaserColor === laserColors.yellow) { + saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); + } else if (currentLaserColor === laserColors.blue) { + saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserDirection = reflectLaser(laserDirection, 315); } break; @@ -781,60 +1113,46 @@ function traceLaser() { } function finish() { + unlockLevel(currentLevelIndex + 1); + setTimeout(() => { const winOverlay = document.querySelector(".win-overlay"); winOverlay.style.visibility = "visible"; }, 100); - - nextLevel(); } -function nextLevel () { - currentLevelIndex++; - - isLevelFinished = false; - - if (currentLevelIndex >= levels.length) { - currentLevelIndex = 0; - } - - initializeMirrorOrientations(); - loadGrid(); - laserSegments = {}; - mirrorOrientations = {}; - glassPlacements = {}; - activatedButtons = {}; - openedDoors = {}; - traceLaser(); -} - -function nextLevel() { - currentLevelIndex++; - +function goToLevel(levelIndex) { + currentLevelIndex = levelIndex; isLevelFinished = false; stopAllRotatorButtons(); - if (currentLevelIndex >= levels.length) { - currentLevelIndex = 0; - } - - initializeMirrorOrientations(); - loadGrid(); - laserSegments = {}; mirrorOrientations = {}; - glassPlacements = {}; + 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(); -traceLaser(); - +setupLevelMenu(); +traceLaser(); diff --git a/web/assets/js/index.js b/web/assets/js/index.js deleted file mode 100644 index 42e3a2c..0000000 --- a/web/assets/js/index.js +++ /dev/null @@ -1,9 +0,0 @@ -function rotateMirror(mirror) { - let angle = 0; - if (mirror.style.transform == "") { - angle = 0; - } else { - angle = parseInt(mirror.style.transform.split("(")[1].split("deg")[0])%360; - } - mirror.style.transform = `rotate(${angle+45}deg)`; -} \ No newline at end of file diff --git a/web/templates/view/game.html b/web/templates/view/game.html index e9f0a68..980f415 100644 --- a/web/templates/view/game.html +++ b/web/templates/view/game.html @@ -3,7 +3,7 @@ - + Game @@ -15,6 +15,16 @@ + + - +