This commit is contained in:
M1n-0
2026-03-31 14:59:26 +02:00
8 changed files with 602 additions and 101 deletions

View File

@@ -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();

View File

@@ -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)`;
}