Files
Projet_48h/web/assets/js/game.js
Sysy's e90a1fc497 Add glass palette, colored lasers & UI layout
Introduce tinted glass mechanic and colored lasers with drag-and-drop palette, plus UI layout and styling. CSS: new game-layout, toolbox, glass-palette, glass-item, cell-glass, laser color classes and many visual tweaks (user-select, drop outline, door/button states). JS: add laserColors, glassOptions, glassPlacements, palette creation, drag/drop/dblclick handlers, block browser drop, saveLaserSegment helper, colored laser tracing (red/blue/yellow/white) including mirror/door/button interactions, button/door grouping and initial mirror angles, and updates to loadGrid to render glass and colorized laser segments. HTML: move map into new main layout and add toolbox palette container. Overall enables placing colored glass to influence laser behavior and updates visuals/interaction accordingly.
2026-03-31 10:53:18 +02:00

556 lines
16 KiB
JavaScript

// 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);
}