// Legend of grid case const legend = { empty: 0, laser: 1, coloredLaser: 2, mirror: 3, door: 4, button: 5, wall: 6, demiWall: 7, target: 8, ligthLaser: 9, button2: 10, } const laserColors = { white: "white", red: "red", blue: "blue", yellow: "yellow", }; const glassOptions = [ laserColors.red, laserColors.blue, laserColors.yellow, ]; // Grid test let level1 = [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 3, 4, 0, 0, 3, 0, 7, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 3, 5, 3, 0, 8, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ] const initialMirrorAngles = { "8,4": 315, "2,4": 45, "2,8": 315, "6,8": 45, }; const buttonGroups = { "4,4": 1, "8,5": 2, }; const doorGroups = { "2,5": 1, "2,6": 2, }; let laserDirection = { dx: 0, dy: 0 }; let laserSegments = {}; let mirrorOrientations = {}; let glassPlacements = {}; let activatedButtons = {}; let openedDoors = {}; 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 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 reverseLaser(direction) { return { dx: direction.dx * -1, dy: direction.dy * -1, }; } function getButtonGroup(x, y) { const cellType = level1[y][x]; if (cellType === legend.button2) { return 2; } return buttonGroups[`${y},${x}`] || 1; } function getDoorGroup(x, y) { return doorGroups[`${y},${x}`] || 1; } function openDoorsFromButton(x, y) { const buttonGroup = getButtonGroup(x, y); for (let doorY = 0; doorY < level1.length; doorY++) { for (let doorX = 0; doorX < level1[doorY].length; doorX++) { if (level1[doorY][doorX] === legend.door && getDoorGroup(doorX, doorY) === buttonGroup) { openedDoors[`${doorY},${doorX}`] = true; } } } } function saveLaserSegment(x, y, direction, color) { laserSegments[`${y},${x}`] = { direction: { ...direction }, color: color, }; if (level1[y][x] === legend.empty) { level1[y][x] = legend.ligthLaser; } } 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 createPalette() { const palette = document.getElementById("glass-palette"); if (!palette) { return; } palette.innerHTML = ""; for (let i = 0; i < glassOptions.length; i++) { const glassColor = glassOptions[i]; const glassButton = document.createElement("button"); glassButton.type = "button"; glassButton.classList.add("glass-item", `glass-${glassColor}`); glassButton.draggable = true; glassButton.addEventListener("dragstart", (event) => { event.dataTransfer.effectAllowed = "copy"; event.dataTransfer.setData("text/plain", glassColor); event.dataTransfer.setData("application/x-glass-color", glassColor); }); palette.appendChild(glassButton); } } function addDropEvents(cell, x, y) { cell.addEventListener("dragover", (event) => { if (isLevelFinished) { return; } if (!event.dataTransfer.types.includes("application/x-glass-color")) { return; } if (level1[y][x] !== legend.empty && level1[y][x] !== legend.ligthLaser) { return; } event.preventDefault(); cell.classList.add("can-drop"); }); cell.addEventListener("dragleave", () => { cell.classList.remove("can-drop"); }); cell.addEventListener("drop", (event) => { if (isLevelFinished) { return; } const selectedColor = event.dataTransfer.getData("application/x-glass-color"); cell.classList.remove("can-drop"); if (!selectedColor) { return; } if (level1[y][x] !== legend.empty && level1[y][x] !== legend.ligthLaser) { return; } event.preventDefault(); glassPlacements[`${y},${x}`] = selectedColor; traceLaser(); }); cell.addEventListener("dblclick", () => { if (isLevelFinished) { return; } if (isGlassOnCell(x, y)) { delete glassPlacements[`${y},${x}`]; traceLaser(); } }); } function blockBrowserDrop() { document.addEventListener("dragover", (event) => { event.preventDefault(); }); document.addEventListener("drop", (event) => { event.preventDefault(); }); } function initializeMirrorOrientations() { mirrorOrientations = {}; // Reset for (let y = 0; y < level1.length; y++) { for (let x = 0; x < level1[y].length; x++) { if (level1[y][x] === legend.mirror) { mirrorOrientations[`${y},${x}`] = initialMirrorAngles[`${y},${x}`] || 0; } } } } // Function to print grid let mirrorCoordinates = []; function loadGrid() { const mapDiv = document.getElementById("map"); // Div with map in DOM mapDiv.innerHTML = ""; for (let y = 0; y < level1.length; y++) { const lign = document.createElement("div"); lign.classList.add("lign"); for (let x = 0; x < level1[y].length; x++) { const cell = document.createElement("div"); cell.classList.add("cell"); addDropEvents(cell, x, y); switch (level1[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: const currentAngle = mirrorOrientations[`${y},${x}`] || 0; const btnMirror = document.createElement("button"); btnMirror.classList.add("btn-mirror"); btnMirror.type = "button"; btnMirror.style.transform = `rotate(${currentAngle}deg)`; btnMirror.style.width = "100%"; btnMirror.onmousedown = (e) => { e.preventDefault(); e.stopPropagation(); if(!isLevelFinished){ rotateMirror(x, y, e.button === 2); } }; btnMirror.oncontextmenu = (e) => e.preventDefault(); cell.appendChild(btnMirror); cell.classList.add("mirror"); break; case legend.door: cell.classList.add("door"); if (openedDoors[`${y},${x}`]) { cell.classList.add("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.wall: cell.classList.add("wall"); break; case legend.demiWall: cell.classList.add("demi-wall"); break; case legend.target: cell.classList.add("target"); break; case legend.ligthLaser: cell.classList.add("empty"); break; } const segmentData = laserSegments[`${y},${x}`]; if (segmentData) { const segmentDirection = segmentData.direction; cell.classList.add("light-laser"); cell.classList.add(getLaserSegmentClass(segmentDirection)); cell.classList.add(getLaserColorClass(segmentData.color)); } drawGlassInCell(cell, x, y); lign.appendChild(cell); } mapDiv.appendChild(lign); } } loadGrid(); // Function to rotate mirror function rotateMirror(x, y, isRightClick) { const coordKey = `${y},${x}`; if (level1[y][x] !== legend.mirror) { return; } let currentAngle = mirrorOrientations[coordKey] || 0; // Rotation and normalize negative angles to [0, 360) currentAngle = (currentAngle + (isRightClick ? 22.5 : -22.5)) % 360; if (currentAngle < 0) { currentAngle += 360; } // Save mirrorOrientations[coordKey] = currentAngle; // Print laser light traceLaser(true); } // Function to trace let isLevelFinished = false; function traceLaser() { // Reset light laser from previous trace laserSegments = {}; activatedButtons = {}; openedDoors = {}; for (let y = 0; y < level1.length; y++) { for (let x = 0; x < level1[y].length; x++) { if (level1[y][x] === legend.ligthLaser) { level1[y][x] = legend.empty; } } } let startLaserX; let startLaserY; // Search laser for (let y = 0; y < level1.length; y++) { for (let x = 0; x < level1[y].length; x++) { if (level1[y][x] === legend.laser) { startLaserX = x; startLaserY = y; laserDirection = { dx: 1, dy: 0 }; break; } } if (startLaserX !== undefined) break; } // If laser not found -> return if (startLaserX === undefined) { return; } let currentX = startLaserX; let currentY = startLaserY; let laserActive = true; let currentLaserColor = laserColors.white; const maxIterations = 1000; // Prevent infinite loops let iterations = 0; isLevelFinished = false; while (laserActive && iterations < maxIterations) { iterations++; currentX += laserDirection.dx; currentY += laserDirection.dy; // Out of bounds if (currentX < 0 || currentX >= level1[0].length || currentY < 0 || currentY >= level1.length) { laserActive = false; break; } const cellType = level1[currentY][currentX]; const glassColor = glassPlacements[`${currentY},${currentX}`]; if (glassColor) { currentLaserColor = glassColor; } switch (cellType) { case legend.laser: case legend.coloredLaser: laserActive = false; 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.demiWall: if (currentLaserColor === laserColors.blue) { laserDirection = reverseLaser(laserDirection); } else if (currentLaserColor === laserColors.yellow) { saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else { laserActive = false; } break; case legend.door: if (openedDoors[`${currentY},${currentX}`]) { 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; default: saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); break; } } loadGrid(); if (isLevelFinished) { finish(); } } createPalette(); initializeMirrorOrientations(); blockBrowserDrop(); traceLaser(); // If level finishh -> call this function function finish() { setTimeout(() => { alert("Réussi !"); }, 100); }