From 34d213d5f4d30f3ce40d22d37421e8a1c27e5c75 Mon Sep 17 00:00:00 2001 From: Sysy's Date: Tue, 31 Mar 2026 14:05:35 +0200 Subject: [PATCH] Support limited glass inventory and yellow laser Introduce per-level glass inventory and UI for draggable glass pieces, including disabled state and count labels. glassOptions now hold objects with color, maxAmount and currentAmount; palette creation uses these values to enable/disable dragging, show remaining counts, and update on place/remove. Drag-and-drop logic now tracks a draggedGlassColor fallback, prevents placing when inventory is empty, decrements/increments inventory on place/remove, and rebuilds the palette. Reset inventory on level start and when advancing levels. Also adjust laser tracing: yellow lasers are saved as segments and terminate on target, and yellow interacts with demi-wall corners by saving the segment instead of reflecting (blue still reflects). Add CSS for .glass-item:disabled and .glass-item-label. Overall fixes inventory handling and yellow-laser behavior. --- web/assets/css/game.css | 18 ++++++ web/assets/js/game.js | 139 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 144 insertions(+), 13 deletions(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index c057872..cfb9c32 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -341,6 +341,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/js/game.js b/web/assets/js/game.js index 392e765..c5668f2 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -31,9 +31,21 @@ 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: 1, currentAmount: 1 }, + { color: laserColors.yellow, maxAmount: 2, currentAmount: 2 }, + ], ]; let levels = [ @@ -149,6 +161,7 @@ let activeRotatorButtons = {}; let rotatorIntervals = {}; let toggledDoors = {}; let poweredCaptors = {}; +let draggedGlassColor = null; function getCurrentLevel() { return levels[currentLevelIndex]; @@ -158,6 +171,30 @@ function getCurrentLevelConfig(configByLevel) { return configByLevel[currentLevelIndex] || {}; } +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); @@ -367,17 +404,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); } } @@ -388,7 +448,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; } @@ -397,6 +458,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"); }); @@ -410,7 +478,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) { @@ -421,8 +490,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(); }); @@ -432,7 +514,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(); } }); @@ -642,6 +733,17 @@ 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: @@ -743,25 +845,33 @@ function traceLaser() { break; case legend.demiWallCornerUpLeft: - if(currentLaserColor === laserColors.blue) { + if (currentLaserColor === laserColors.yellow) { + saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); + } else if (currentLaserColor === laserColors.blue) { 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) { 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) { 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) { laserDirection = reflectLaser(laserDirection, 315); } break; @@ -799,10 +909,12 @@ function nextLevel() { } initializeMirrorOrientations(); + glassPlacements = {}; + resetGlassInventory(); + createPalette(); loadGrid(); laserSegments = {}; mirrorOrientations = {}; - glassPlacements = {}; activatedButtons = {}; openedDoors = {}; toggledDoors = {}; @@ -813,6 +925,7 @@ function nextLevel() { winOverlay.style.visibility = "hidden"; } +resetGlassInventory(); createPalette(); initializeMirrorOrientations(); blockBrowserDrop();