From 04a0e1a9120e96d4174c9f0aacbcfff8c754d27d Mon Sep 17 00:00:00 2001 From: Pierre Date: Mon, 30 Mar 2026 12:22:56 +0200 Subject: [PATCH 01/36] Print grid on html + minimalist css (which will not be used) --- web/assets/css/index.css | 117 ++++++++++++++++++++++++++++++++++ web/assets/js/index.js | 79 +++++++++++++++++++++++ web/templates/view/index.html | 14 ++++ 3 files changed, 210 insertions(+) diff --git a/web/assets/css/index.css b/web/assets/css/index.css index e69de29..1244a0a 100644 --- a/web/assets/css/index.css +++ b/web/assets/css/index.css @@ -0,0 +1,117 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + width: 100%; + height: 100%; + overflow: hidden; +} + +body { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +main { + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + border-radius: 15px; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(4px); + min-width: fit-content; + flex-shrink: 0; +} + +.map { + display: flex; + flex-direction: column; + gap: 2px; + padding: 15px; + background: rgba(0, 0, 0, 0.2); + border-radius: 10px; + width: fit-content; + height: fit-content; +} + +.lign { + display: flex; + margin: 2px 2px; +} + +.cell { + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 5px; + width: 100%; + height: 100%; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + margin: 2px 2px; +} + +.empty { + background-color: rgba(200, 200, 200, 0.3); +} + +.empty:hover { + background-color: rgba(200, 200, 200, 0.5); +} + +.laser { + background-color: #FFD700; + border-color: #FFA500; +} + +.colored-laser { + background: linear-gradient(45deg, #FF1493, #00CED1); + box-shadow: inset 0 0 10px rgba(255, 20, 147, 0.6); + border-color: #FF1493; +} + +.mirror { + background: linear-gradient(45deg, #C0C0C0 25%, transparent 25%, transparent 75%, #C0C0C0 75%) / 10px 10px; + background-color: #E8E8E8; + border-color: #A9A9A9; +} + +.door { + background-color: #8B4513; + border-color: #654321; + position: relative; +} + +.door::after { + content: '🚪'; + font-size: 24px; +} + +.button { + background-color: #FF6347; + border-color: #DC143C; + border-radius: 50%; +} + +.wall { + background-color: #2F4F4F; + border-color: #000000; + box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.8); +} + +.demi-wall { + background: linear-gradient(to right, #2F4F4F 50%, rgba(200, 200, 200, 0.3) 50%); + border-color: #444444; +} + +.target { + background: radial-gradient(circle, #00FF00 30%, rgba(0, 255, 0, 0.3) 70%); + border-color: #00CC00; +} \ No newline at end of file diff --git a/web/assets/js/index.js b/web/assets/js/index.js index e69de29..7122392 100644 --- a/web/assets/js/index.js +++ b/web/assets/js/index.js @@ -0,0 +1,79 @@ +// Legend of grid case + +const legend = { + empty: 0, + laser: 1, + coloredLaser: 2, + mirror: 3, + door:4, + button: 5, + wall: 6, + demiWall: 7, + target: 8, +} + +// Grid test + +let grid = [ + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 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], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 8, 0], + [0, 0, 0, 0, 0, 0, 0, 0], +] + +// Function to print grid + +function loadGrid () { + const mapDiv = document.getElementById("map"); // Div with map in DOM + mapDiv.innerHTML = ""; + + for (let y = 0; y < grid.length; y++) { + const lign = document.createElement("div"); + lign.classList.add("lign"); + + for (let x = 0; x < grid[y].length; x++) { + const cell = document.createElement("div"); + cell.classList.add("cell"); + + switch (grid[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: + cell.classList.add("mirror"); + break; + case legend.door: + cell.classList.add("door"); + break; + case legend.button: + cell.classList.add("button"); + 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; + } + + lign.appendChild(cell); + } + + mapDiv.appendChild(lign); + } +} + +loadGrid(); \ No newline at end of file diff --git a/web/templates/view/index.html b/web/templates/view/index.html index e69de29..a99f86b 100644 --- a/web/templates/view/index.html +++ b/web/templates/view/index.html @@ -0,0 +1,14 @@ + + + + + + + Index + + +
+ + + + \ No newline at end of file From 001adb89bd5051d8295344a82b50ad6cae95853e Mon Sep 17 00:00:00 2001 From: Pierre Date: Mon, 30 Mar 2026 14:10:19 +0200 Subject: [PATCH 02/36] First version of laser --- web/assets/css/index.css | 91 ++++++++++++++++++++++++---------------- web/assets/js/index.js | 8 +++- 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/web/assets/css/index.css b/web/assets/css/index.css index 1244a0a..1e27b1f 100644 --- a/web/assets/css/index.css +++ b/web/assets/css/index.css @@ -11,21 +11,20 @@ html, body { } body { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: #1a1a1a; display: flex; align-items: center; justify-content: center; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-family: 'Arial', sans-serif; } main { display: flex; + flex-direction: column; align-items: center; justify-content: center; padding: 20px; - border-radius: 15px; - background: rgba(255, 255, 255, 0.1); - backdrop-filter: blur(4px); + border-radius: 10px; min-width: fit-content; flex-shrink: 0; } @@ -33,37 +32,40 @@ main { .map { display: flex; flex-direction: column; - gap: 2px; - padding: 15px; - background: rgba(0, 0, 0, 0.2); - border-radius: 10px; + gap: 0px; + padding: 10px; + background: #222222; + border-radius: 5px; width: fit-content; height: fit-content; } .lign { display: flex; - margin: 2px 2px; + gap: 0px; + margin: 0; } .cell { - border: 2px solid rgba(255, 255, 255, 0.3); - border-radius: 5px; - width: 100%; - height: 100%; + border: 1px solid #333333; + width: 35px; + height: 35px; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; - margin: 2px 2px; + margin: 0; + position: relative; + background-color: #2a2a2a; } .empty { - background-color: rgba(200, 200, 200, 0.3); + background-color: #2a2a2a; + border-color: #333333; } .empty:hover { - background-color: rgba(200, 200, 200, 0.5); + background-color: #333333; } .laser { @@ -72,46 +74,65 @@ main { } .colored-laser { - background: linear-gradient(45deg, #FF1493, #00CED1); - box-shadow: inset 0 0 10px rgba(255, 20, 147, 0.6); + background: linear-gradient(135deg, #FF1493 0%, #00CED1 100%); border-color: #FF1493; } .mirror { - background: linear-gradient(45deg, #C0C0C0 25%, transparent 25%, transparent 75%, #C0C0C0 75%) / 10px 10px; - background-color: #E8E8E8; - border-color: #A9A9A9; + background-color: #1a1a1a; + border-color: #444444; + position: relative; + overflow: hidden; +} + +.mirror::before { + content: ''; + position: absolute; + width: 100%; + height: 60%; + background: linear-gradient(45deg, transparent 48%, #CCCCCC 48%, #CCCCCC 52%, transparent 52%); + top: 50%; + transform: translateY(-50%); } .door { - background-color: #8B4513; + background: linear-gradient(135deg, #8B4513 0%, #654321 100%); border-color: #654321; - position: relative; } .door::after { - content: '🚪'; - font-size: 24px; + content: ''; + position: absolute; + width: 60%; + height: 60%; + border: 2px solid #FFD700; + border-radius: 3px; } .button { - background-color: #FF6347; - border-color: #DC143C; - border-radius: 50%; + background: radial-gradient(circle at 35% 35%, #FF4444 0%, #CC0000 100%); + border-color: #990000; } .wall { - background-color: #2F4F4F; + background: linear-gradient(135deg, #1a1a1a 0%, #0f0f0f 100%); border-color: #000000; - box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.8); } .demi-wall { - background: linear-gradient(to right, #2F4F4F 50%, rgba(200, 200, 200, 0.3) 50%); - border-color: #444444; + background: linear-gradient(90deg, #0f0f0f 0%, #0f0f0f 50%, #2a2a2a 50%, #2a2a2a 100%); + border-color: #333333; } .target { - background: radial-gradient(circle, #00FF00 30%, rgba(0, 255, 0, 0.3) 70%); - border-color: #00CC00; + background: radial-gradient(circle at 35% 35%, #00FF00 0%, #00CC00 50%, rgba(0, 255, 0, 0.2) 100%); + border-color: #00FF00; +} + +.light-laser { + margin-top: 14px; + height: 7px; + width: 35px; + background-color: red; + display: flex; } \ No newline at end of file diff --git a/web/assets/js/index.js b/web/assets/js/index.js index 7122392..231711b 100644 --- a/web/assets/js/index.js +++ b/web/assets/js/index.js @@ -10,18 +10,19 @@ const legend = { wall: 6, demiWall: 7, target: 8, + ligthLaser: 9, } // Grid test let grid = [ [0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 8, 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], [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 8, 0], + [1, 9, 9, 9, 9, 9, 3, 0], [0, 0, 0, 0, 0, 0, 0, 0], ] @@ -67,6 +68,9 @@ function loadGrid () { case legend.target: cell.classList.add("target"); break; + case legend.ligthLaser: + cell.classList.add("light-laser"); + break; } lign.appendChild(cell); From 5668d21b0d35beaba1898413622f0daa4b0c3d96 Mon Sep 17 00:00:00 2001 From: Pierre Date: Mon, 30 Mar 2026 14:16:30 +0200 Subject: [PATCH 03/36] End of grid --- web/assets/img/img_test_main_menu.png | Bin 0 -> 50181 bytes web/assets/js/index.js | 18 ++++++++++-------- web/templates/view/{index.html => game.html} | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 web/assets/img/img_test_main_menu.png rename web/templates/view/{index.html => game.html} (92%) diff --git a/web/assets/img/img_test_main_menu.png b/web/assets/img/img_test_main_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..595174288facc7258b08be49248a85c917e8f6d6 GIT binary patch literal 50181 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 Q!hPt#(cjAUow@S=07^4`z5oCK literal 0 HcmV?d00001 diff --git a/web/assets/js/index.js b/web/assets/js/index.js index 231711b..d0cfac7 100644 --- a/web/assets/js/index.js +++ b/web/assets/js/index.js @@ -16,14 +16,16 @@ const legend = { // Grid test let grid = [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 8, 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], - [0, 0, 0, 0, 0, 0, 0, 0], - [1, 9, 9, 9, 9, 9, 3, 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, 8, 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, 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, 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], ] // Function to print grid diff --git a/web/templates/view/index.html b/web/templates/view/game.html similarity index 92% rename from web/templates/view/index.html rename to web/templates/view/game.html index a99f86b..4bb92f6 100644 --- a/web/templates/view/index.html +++ b/web/templates/view/game.html @@ -4,7 +4,7 @@ - Index + Game
From 1be3750672e1a57c18efb7c36df08fc885370c9e Mon Sep 17 00:00:00 2001 From: Pierre Date: Mon, 30 Mar 2026 14:51:37 +0200 Subject: [PATCH 04/36] Change index.js and index.css to game.js and game.css --- web/assets/css/{index.css => game.css} | 4 ++-- web/assets/js/{index.js => game.js} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename web/assets/css/{index.css => game.css} (98%) rename web/assets/js/{index.js => game.js} (97%) diff --git a/web/assets/css/index.css b/web/assets/css/game.css similarity index 98% rename from web/assets/css/index.css rename to web/assets/css/game.css index 1e27b1f..e5e451b 100644 --- a/web/assets/css/index.css +++ b/web/assets/css/game.css @@ -48,8 +48,8 @@ main { .cell { border: 1px solid #333333; - width: 35px; - height: 35px; + width: 30px; + height: 30px; transition: all 0.2s ease; display: flex; align-items: center; diff --git a/web/assets/js/index.js b/web/assets/js/game.js similarity index 97% rename from web/assets/js/index.js rename to web/assets/js/game.js index d0cfac7..e234d2e 100644 --- a/web/assets/js/index.js +++ b/web/assets/js/game.js @@ -24,7 +24,7 @@ let grid = [ [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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 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], ] From 577357b090788f6159693d66b9b1c7fce8639126 Mon Sep 17 00:00:00 2001 From: Sysy's Date: Mon, 30 Mar 2026 12:05:24 +0200 Subject: [PATCH 05/36] =?UTF-8?q?Add=20rotateMirror=20to=20rotate=20elemen?= =?UTF-8?q?t=2045=C2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a small utility function in web/assets/js/index.js that increments an element's CSS rotation by 45 degrees. The function reads the element's inline transform (handling an empty value), parses the current rotation angle modulo 360, and sets the new rotate(angle+45) value. --- web/assets/js/index.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 web/assets/js/index.js diff --git a/web/assets/js/index.js b/web/assets/js/index.js new file mode 100644 index 0000000..42e3a2c --- /dev/null +++ b/web/assets/js/index.js @@ -0,0 +1,9 @@ +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 From a791415bd35d98afe26d4e5299e8f88b7f039094 Mon Sep 17 00:00:00 2001 From: Pierre Date: Mon, 30 Mar 2026 15:05:57 +0200 Subject: [PATCH 06/36] Rebase of feature/mirror + merge with the grid program --- web/assets/css/game.css | 10 ---------- web/assets/js/game.js | 21 +++++++++++++++++++-- web/templates/view/game.html | 4 ++-- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index e5e451b..bf9bc4c 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -85,16 +85,6 @@ main { overflow: hidden; } -.mirror::before { - content: ''; - position: absolute; - width: 100%; - height: 60%; - background: linear-gradient(45deg, transparent 48%, #CCCCCC 48%, #CCCCCC 52%, transparent 52%); - top: 50%; - transform: translateY(-50%); -} - .door { background: linear-gradient(135deg, #8B4513 0%, #654321 100%); border-color: #654321; diff --git a/web/assets/js/game.js b/web/assets/js/game.js index e234d2e..6d96f05 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -24,7 +24,7 @@ let grid = [ [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], - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 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], ] @@ -53,6 +53,12 @@ function loadGrid () { cell.classList.add("colored-laser"); break; case legend.mirror: + const btnMirror = document.createElement("button"); + btnMirror.classList.add("btn-mirror"); + btnMirror.addEventListener("click", () => rotateMirror(btnMirror)); + btnMirror.style.transform = "rotate(0deg)"; + btnMirror.style.width = "100%"; + cell.appendChild(btnMirror); cell.classList.add("mirror"); break; case legend.door: @@ -82,4 +88,15 @@ function loadGrid () { } } -loadGrid(); \ No newline at end of file +loadGrid(); + + +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 4bb92f6..c01d7d6 100644 --- a/web/templates/view/game.html +++ b/web/templates/view/game.html @@ -3,12 +3,12 @@ - + Game
- + \ No newline at end of file From 2dbfff770fb1c536b5bc5fe62cbccc3834db5451 Mon Sep 17 00:00:00 2001 From: Pierre Date: Mon, 30 Mar 2026 15:29:25 +0200 Subject: [PATCH 07/36] Grid enlargement --- web/assets/css/game.css | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index bf9bc4c..636c7c2 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -5,8 +5,8 @@ } html, body { - width: 100%; - height: 100%; + width: 100vw; + height: 100vh; overflow: hidden; } @@ -48,8 +48,10 @@ main { .cell { border: 1px solid #333333; - width: 30px; - height: 30px; + min-width: 60px; + width: auto; + min-height: 60px; + height: auto; transition: all 0.2s ease; display: flex; align-items: center; @@ -90,15 +92,6 @@ main { border-color: #654321; } -.door::after { - content: ''; - position: absolute; - width: 60%; - height: 60%; - border: 2px solid #FFD700; - border-radius: 3px; -} - .button { background: radial-gradient(circle at 35% 35%, #FF4444 0%, #CC0000 100%); border-color: #990000; From b10b6475d8803fee5a64b1f523ab6b9300ac5b24 Mon Sep 17 00:00:00 2001 From: Pierre Date: Mon, 30 Mar 2026 15:33:49 +0200 Subject: [PATCH 08/36] First version of button for mirrors --- web/assets/js/game.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/assets/js/game.js b/web/assets/js/game.js index 6d96f05..84934e2 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -17,8 +17,8 @@ const legend = { let grid = [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 8, 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, 8, 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, 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], @@ -29,6 +29,7 @@ let grid = [ ] // Function to print grid +let mirrorCoordinates = []; function loadGrid () { const mapDiv = document.getElementById("map"); // Div with map in DOM @@ -90,6 +91,7 @@ function loadGrid () { loadGrid(); +// Function to rotate mirror function rotateMirror(mirror) { let angle = 0; From 34fbb797c1cce48d91521e794ab1b415d83c7fb3 Mon Sep 17 00:00:00 2001 From: Pierre Date: Mon, 30 Mar 2026 16:48:53 +0200 Subject: [PATCH 09/36] Laser print and reflect --- web/assets/css/game.css | 5 +- web/assets/js/game.js | 195 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 178 insertions(+), 22 deletions(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index 636c7c2..607036e 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -113,9 +113,8 @@ main { } .light-laser { - margin-top: 14px; - height: 7px; + height: 2px; width: 35px; background-color: red; - display: flex; + background-size: 2px 35px; } \ No newline at end of file diff --git a/web/assets/js/game.js b/web/assets/js/game.js index 84934e2..e8f36d7 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -5,7 +5,7 @@ const legend = { laser: 1, coloredLaser: 2, mirror: 3, - door:4, + door: 4, button: 5, wall: 6, demiWall: 7, @@ -15,35 +15,51 @@ const legend = { // Grid test -let grid = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 3, 0, 0], +let level1 = [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 3, 0, 0, 0, 8, 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, 0, 0, 0, 0], + [0, 0, 3, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 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], ] +// Function to save initial orientation of mirrors +let laserDirection = { dx: 0, dy: 0 }; +let mirrorOrientations = {}; + +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}`] = 0; // Default angle + } + } + } +} + + // Function to print grid let mirrorCoordinates = []; -function loadGrid () { +function loadGrid() { const mapDiv = document.getElementById("map"); // Div with map in DOM mapDiv.innerHTML = ""; - for (let y = 0; y < grid.length; y++) { + for (let y = 0; y < level1.length; y++) { const lign = document.createElement("div"); lign.classList.add("lign"); - for (let x = 0; x < grid[y].length; x++) { + for (let x = 0; x < level1[y].length; x++) { const cell = document.createElement("div"); cell.classList.add("cell"); - switch (grid[y][x]) { + switch (level1[y][x]) { case legend.empty: cell.classList.add("empty"); break; @@ -54,10 +70,11 @@ function loadGrid () { 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.addEventListener("click", () => rotateMirror(btnMirror)); - btnMirror.style.transform = "rotate(0deg)"; + btnMirror.addEventListener("click", () => rotateMirror(btnMirror, x, y)); + btnMirror.style.transform = `rotate(${currentAngle}deg)`; btnMirror.style.width = "100%"; cell.appendChild(btnMirror); cell.classList.add("mirror"); @@ -82,10 +99,10 @@ function loadGrid () { break; } - lign.appendChild(cell); + lign.appendChild(cell); } - mapDiv.appendChild(lign); + mapDiv.appendChild(lign); } } @@ -93,12 +110,152 @@ loadGrid(); // Function to rotate mirror -function rotateMirror(mirror) { - let angle = 0; - if (mirror.style.transform == "") { - angle = 0; - } else { - angle = parseInt(mirror.style.transform.split("(")[1].split("deg")[0])%360; +function rotateMirror(mirrorElement, x, y) { + const coordKey = `${y},${x}`; + let currentAngle = mirrorOrientations[coordKey] || 0; + + // Rotation 45° + currentAngle = (currentAngle + 45) % 360; + mirrorOrientations[coordKey] = currentAngle; + + // New rotation + mirrorElement.style.transform = `rotate(${currentAngle}deg)`; + + // Print laser light + traceLaser(true); +} + + +// Function to trace + +let isLevelFinished = false; + +function traceLaser() { + // Reset light laser from previous trace + 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; + } + } } - mirror.style.transform = `rotate(${angle+45}deg)`; + + 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; + const maxIterations = 1000; // Prevent infinite loops + let iterations = 0; + + 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]; + + switch (cellType) { + case legend.laser: + case legend.coloredLaser: + laserActive = false; + break; + + case legend.empty: + level1[currentY][currentX] = legend.ligthLaser; + break; + + case legend.target: + level1[currentY][currentX] = legend.ligthLaser; + laserActive = false; + isLevelFinished = true; + break; + + case legend.mirror: + // Change direction based on mirror angle + const mirrorAngle = mirrorOrientations[`${currentY},${currentX}`] || 0; + + // 0° or 180°: reflect horizontal + if (mirrorAngle === 0 || mirrorAngle === 180) { + laserDirection.dy = -laserDirection.dy; + } else { + if (mirrorAngle === 90 || mirrorAngle === 270) { + laserDirection.dx = -laserDirection.dx; + } + + if (mirrorAngle === 45 || mirrorAngle === 225) { + const tempDx = laserDirection.dx; + laserDirection.dx = laserDirection.dy; + laserDirection.dy = tempDx; + } + + if (mirrorAngle === 135 || mirrorAngle === 315) { + const tempDx = laserDirection.dx; + laserDirection.dx = -laserDirection.dy; + laserDirection.dy = -tempDx; + } + } + break; + + case legend.wall: + laserActive = false; + break; + + case legend.demiWall: + laserActive = false; + break; + + case legend.door: + laserActive = false; + break; + + case legend.button: + level1[currentY][currentX] = legend.ligthLaser; + laserActive = false; + break; + + default: + level1[currentY][currentX] = legend.ligthLaser; + break; + } + } + + loadGrid(); + + if (isLevelFinished) { + finish(); + } +} + +traceLaser(); + +// If level finishh -> call this function +function finish() { + alert("Réussi !"); } \ No newline at end of file From 024b80d393a3e1fd0832d91fc76c268123b46150 Mon Sep 17 00:00:00 2001 From: Pierre Date: Mon, 30 Mar 2026 22:06:44 +0200 Subject: [PATCH 10/36] Upgrade of design of light of laser + add posibility to move a cursor and rotate mirror with keyboard --- web/assets/css/game.css | 122 +++++++++++++++++++++++++++++----------- web/assets/js/game.js | 102 ++++++++++++++++++++++++++++++--- 2 files changed, 182 insertions(+), 42 deletions(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index 607036e..f287f0d 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -4,7 +4,8 @@ box-sizing: border-box; } -html, body { +html, +body { width: 100vw; height: 100vh; overflow: hidden; @@ -29,6 +30,10 @@ main { flex-shrink: 0; } +/* ================================ + GRID +================================ */ + .map { display: flex; flex-direction: column; @@ -46,12 +51,14 @@ main { margin: 0; } +/* ================================ + CELLS +================================ */ + .cell { border: 1px solid #333333; - min-width: 60px; - width: auto; - min-height: 60px; - height: auto; + width: clamp(28px, 5.5vmin, 60px); + height: clamp(28px, 5.5vmin, 60px); transition: all 0.2s ease; display: flex; align-items: center; @@ -61,6 +68,10 @@ main { background-color: #2a2a2a; } +/* ================================ + CASES TYPE +================================ */ + .empty { background-color: #2a2a2a; border-color: #333333; @@ -72,12 +83,6 @@ main { .laser { background-color: #FFD700; - border-color: #FFA500; -} - -.colored-laser { - background: linear-gradient(135deg, #FF1493 0%, #00CED1 100%); - border-color: #FF1493; } .mirror { @@ -87,34 +92,85 @@ main { overflow: hidden; } -.door { - background: linear-gradient(135deg, #8B4513 0%, #654321 100%); - border-color: #654321; -} - -.button { - background: radial-gradient(circle at 35% 35%, #FF4444 0%, #CC0000 100%); - border-color: #990000; -} - .wall { - background: linear-gradient(135deg, #1a1a1a 0%, #0f0f0f 100%); - border-color: #000000; -} - -.demi-wall { - background: linear-gradient(90deg, #0f0f0f 0%, #0f0f0f 50%, #2a2a2a 50%, #2a2a2a 100%); - border-color: #333333; + background-color: #1a1a1a; } .target { - background: radial-gradient(circle at 35% 35%, #00FF00 0%, #00CC00 50%, rgba(0, 255, 0, 0.2) 100%); - border-color: #00FF00; + background: #00FF00; } +/* ================================ + CURSOR PLAYER +================================ */ + +.player-cursor { + outline: 2px solid #00e5ff; + outline-offset: -2px; + z-index: 10; +} + +.player-cursor.mirror { + outline-color: #FFD700; +} + +/* ================================ + LIGHT LASER +================================ */ + .light-laser { + position: relative; + background-color: #2a2a2a; +} + +.laser-horizontal { + background: linear-gradient(to bottom, transparent 0%, transparent 45%, red 45%, red 55%, transparent 55%, transparent 100%), #2a2a2a +} + +.laser-vertical { + background: linear-gradient(to right, transparent 0%, transparent 45%, red 45%, red 55%, transparent 55%, transparent 100%), #2a2a2a; +} + +/* ================================ + MIRROR +================================ */ + +.btn-mirror { + background: none; + border: none; + cursor: pointer; + height: 100%; + position: relative; +} + +.btn-mirror::after { + content: ''; + position: absolute; + top: 50%; + left: 10%; + width: 80%; height: 2px; - width: 35px; - background-color: red; - background-size: 2px 35px; + background-color: #aaaaaa; + transform-origin: center; +} + +/* ================================ + RESPONSIVE +================================ */ + +@media (max-width: 600px) { + .map { + padding: 5px; + border-radius: 3px; + } + + main { + padding: 8px; + } +} + +@media (max-height: 500px) { + .map { + padding: 4px; + } } \ No newline at end of file diff --git a/web/assets/js/game.js b/web/assets/js/game.js index e8f36d7..06371d6 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -28,6 +28,10 @@ let level1 = [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ] +// Player position +let playerX = 0; +let playerY = 0; + // Function to save initial orientation of mirrors let laserDirection = { dx: 0, dy: 0 }; let mirrorOrientations = {}; @@ -73,7 +77,6 @@ function loadGrid() { const currentAngle = mirrorOrientations[`${y},${x}`] || 0; const btnMirror = document.createElement("button"); btnMirror.classList.add("btn-mirror"); - btnMirror.addEventListener("click", () => rotateMirror(btnMirror, x, y)); btnMirror.style.transform = `rotate(${currentAngle}deg)`; btnMirror.style.width = "100%"; cell.appendChild(btnMirror); @@ -96,9 +99,18 @@ function loadGrid() { break; case legend.ligthLaser: cell.classList.add("light-laser"); + if (laserDirection.dx === 0) { + cell.classList.add("laser-vertical"); + } else { + cell.classList.add("laser-horizontal"); + } break; } + if (x === playerX && y === playerY) { + cell.classList.add("player-cursor"); + } + lign.appendChild(cell); } @@ -110,16 +122,21 @@ loadGrid(); // Function to rotate mirror -function rotateMirror(mirrorElement, x, y) { +function rotateMirror(x, y) { const coordKey = `${y},${x}`; + + // See if it's a mirror + if (level1[y][x] !== legend.mirror) { + return + }; + let currentAngle = mirrorOrientations[coordKey] || 0; - // Rotation 45° + // Rotation currentAngle = (currentAngle + 45) % 360; - mirrorOrientations[coordKey] = currentAngle; - // New rotation - mirrorElement.style.transform = `rotate(${currentAngle}deg)`; + // Save + mirrorOrientations[coordKey] = currentAngle; // Print laser light traceLaser(true); @@ -198,10 +215,8 @@ function traceLaser() { break; case legend.mirror: - // Change direction based on mirror angle const mirrorAngle = mirrorOrientations[`${currentY},${currentX}`] || 0; - // 0° or 180°: reflect horizontal if (mirrorAngle === 0 || mirrorAngle === 180) { laserDirection.dy = -laserDirection.dy; } else { @@ -257,5 +272,74 @@ traceLaser(); // If level finishh -> call this function function finish() { - alert("Réussi !"); + setTimeout(() => { + alert("Réussi !"); + }, 100); +} + + +// Get player inputs arrows, qwerty and azerty + +document.addEventListener("keydown", (e) => { + + // If level finish -> don't move + if (isLevelFinished === true) { + return; + } + + switch (e.key) { + case "ArrowUp": + movePlayer(0, -1); + break; + case "w": + movePlayer(0, -1); + break; + case "z": + movePlayer(0, -1); + break; + case "ArrowDown": + movePlayer(0, 1); + break; + case "s": + movePlayer(0, 1); + break; + case "ArrowLeft": + movePlayer(-1, 0); + break; + case "a": + movePlayer(-1, 0); + break; + case "q": + movePlayer(-1, 0); + break; + case "ArrowRight": + movePlayer(1, 0); + break; + case "d": + movePlayer(1, 0); + break; + case "Enter": + rotateMirror(playerX, playerY); + break; + } +}); + +// Player move + +function movePlayer(x, y) { + const newX = playerX + x; + const newY = playerY + y; + + if (newX < 0 || newX >= level1[0].length) { + return + }; + + if (newY < 0 || newY >= level1.length) { + return + }; + + playerX = newX; + playerY = newY; + + loadGrid(); } \ No newline at end of file From d0e282b41c9bb5e92ea57c0f1a8c2a076035b7fc Mon Sep 17 00:00:00 2001 From: M1n-0 Date: Tue, 31 Mar 2026 09:17:02 +0200 Subject: [PATCH 11/36] add .gitignore --- backend/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 backend/.gitignore diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..c41cc9e --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file From 4ba42f65664c86d2ce26e881eb99e807d9d381e8 Mon Sep 17 00:00:00 2001 From: Sysy's Date: Tue, 31 Mar 2026 09:20:28 +0200 Subject: [PATCH 12/36] Track laser segments and update UI theme Add per-cell laserSegments tracking and use it to render correct laser orientation: introduce laserSegments global, reset it at trace start, populate entries when tracing, and consult it in loadGrid to choose horizontal vs vertical classes. Update UI styling from a dark to a lighter theme (body and main backgrounds, cell/empty/mirror/wall colors and laser gradients), remove some borders/fit-content sizing, and add a "map" class to the map container in the HTML. These changes fix laser orientation rendering and refresh the game's visual theme. --- web/assets/css/game.css | 20 ++++++++------------ web/assets/js/game.js | 11 +++++++++-- web/templates/view/game.html | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index f287f0d..9b8e4d3 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -12,7 +12,7 @@ body { } body { - background: #1a1a1a; + background: #f7f7f7; display: flex; align-items: center; justify-content: center; @@ -39,10 +39,8 @@ main { flex-direction: column; gap: 0px; padding: 10px; - background: #222222; + background: #DADEEF; border-radius: 5px; - width: fit-content; - height: fit-content; } .lign { @@ -56,7 +54,6 @@ main { ================================ */ .cell { - border: 1px solid #333333; width: clamp(28px, 5.5vmin, 60px); height: clamp(28px, 5.5vmin, 60px); transition: all 0.2s ease; @@ -73,8 +70,8 @@ main { ================================ */ .empty { - background-color: #2a2a2a; - border-color: #333333; + background-color: #DADEEF; + border-color: #DADEEF; } .empty:hover { @@ -86,14 +83,14 @@ main { } .mirror { - background-color: #1a1a1a; + background-color: #DADEEF; border-color: #444444; position: relative; overflow: hidden; } .wall { - background-color: #1a1a1a; + background-color: #DADEEF; } .target { @@ -120,15 +117,14 @@ main { .light-laser { position: relative; - background-color: #2a2a2a; } .laser-horizontal { - background: linear-gradient(to bottom, transparent 0%, transparent 45%, red 45%, red 55%, transparent 55%, transparent 100%), #2a2a2a + background: linear-gradient(to bottom, transparent 0%, transparent 45%, red 45%, red 55%, transparent 55%, transparent 100%), #DADEEF } .laser-vertical { - background: linear-gradient(to right, transparent 0%, transparent 45%, red 45%, red 55%, transparent 55%, transparent 100%), #2a2a2a; + background: linear-gradient(to right, transparent 0%, transparent 45%, red 45%, red 55%, transparent 55%, transparent 100%), #DADEEF; } /* ================================ diff --git a/web/assets/js/game.js b/web/assets/js/game.js index 06371d6..3ff6d87 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -34,6 +34,7 @@ let playerY = 0; // Function to save initial orientation of mirrors let laserDirection = { dx: 0, dy: 0 }; +let laserSegments = {}; let mirrorOrientations = {}; function initializeMirrorOrientations() { @@ -99,7 +100,8 @@ function loadGrid() { break; case legend.ligthLaser: cell.classList.add("light-laser"); - if (laserDirection.dx === 0) { + const segmentDirection = laserSegments[`${y},${x}`]; + if (segmentDirection && segmentDirection.dx === 0) { cell.classList.add("laser-vertical"); } else { cell.classList.add("laser-horizontal"); @@ -149,6 +151,7 @@ let isLevelFinished = false; function traceLaser() { // Reset light laser from previous trace + laserSegments = {}; for (let y = 0; y < level1.length; y++) { for (let x = 0; x < level1[y].length; x++) { if (level1[y][x] === legend.ligthLaser) { @@ -206,10 +209,12 @@ function traceLaser() { case legend.empty: level1[currentY][currentX] = legend.ligthLaser; + laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; break; case legend.target: level1[currentY][currentX] = legend.ligthLaser; + laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; laserActive = false; isLevelFinished = true; break; @@ -252,11 +257,13 @@ function traceLaser() { case legend.button: level1[currentY][currentX] = legend.ligthLaser; + laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; laserActive = false; break; default: level1[currentY][currentX] = legend.ligthLaser; + laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; break; } } @@ -342,4 +349,4 @@ function movePlayer(x, y) { playerY = newY; loadGrid(); -} \ No newline at end of file +} diff --git a/web/templates/view/game.html b/web/templates/view/game.html index c01d7d6..d90f9e9 100644 --- a/web/templates/view/game.html +++ b/web/templates/view/game.html @@ -7,7 +7,7 @@ Game -
+
From 284e396d5f7d9ba7e96f2a9c330a924a0f5765ce Mon Sep 17 00:00:00 2001 From: Sysy's Date: Tue, 31 Mar 2026 09:32:59 +0200 Subject: [PATCH 13/36] Make mirror rotate on click --- web/assets/js/game.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/assets/js/game.js b/web/assets/js/game.js index 3ff6d87..a7dcafa 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -76,6 +76,9 @@ function loadGrid() { break; case legend.mirror: const currentAngle = mirrorOrientations[`${y},${x}`] || 0; + cell.onclick = () => { + rotateMirror(x, y); + } const btnMirror = document.createElement("button"); btnMirror.classList.add("btn-mirror"); btnMirror.style.transform = `rotate(${currentAngle}deg)`; From 76de2a5a5c3f2fc8ae107ac037441e26bc374ef2 Mon Sep 17 00:00:00 2001 From: Sysy's Date: Tue, 31 Mar 2026 09:40:25 +0200 Subject: [PATCH 14/36] Remove player + right click change rotation mirror --- web/assets/css/game.css | 16 +------ web/assets/js/game.js | 98 +++++++---------------------------------- 2 files changed, 17 insertions(+), 97 deletions(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index 9b8e4d3..3a7f56c 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -97,20 +97,6 @@ main { background: #00FF00; } -/* ================================ - CURSOR PLAYER -================================ */ - -.player-cursor { - outline: 2px solid #00e5ff; - outline-offset: -2px; - z-index: 10; -} - -.player-cursor.mirror { - outline-color: #FFD700; -} - /* ================================ LIGHT LASER ================================ */ @@ -169,4 +155,4 @@ main { .map { padding: 4px; } -} \ No newline at end of file +} diff --git a/web/assets/js/game.js b/web/assets/js/game.js index a7dcafa..f9337cf 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -28,10 +28,6 @@ let level1 = [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ] -// Player position -let playerX = 0; -let playerY = 0; - // Function to save initial orientation of mirrors let laserDirection = { dx: 0, dy: 0 }; let laserSegments = {}; @@ -76,13 +72,18 @@ function loadGrid() { break; case legend.mirror: const currentAngle = mirrorOrientations[`${y},${x}`] || 0; - cell.onclick = () => { - rotateMirror(x, y); - } 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(); + rotateMirror(x, y, e.button === 2); + }; + btnMirror.oncontextmenu = (e) => e.preventDefault(); + cell.appendChild(btnMirror); cell.classList.add("mirror"); break; @@ -112,10 +113,6 @@ function loadGrid() { break; } - if (x === playerX && y === playerY) { - cell.classList.add("player-cursor"); - } - lign.appendChild(cell); } @@ -127,18 +124,20 @@ loadGrid(); // Function to rotate mirror -function rotateMirror(x, y) { +function rotateMirror(x, y, isRightClick) { const coordKey = `${y},${x}`; - // See if it's a mirror if (level1[y][x] !== legend.mirror) { - return - }; + return; + } let currentAngle = mirrorOrientations[coordKey] || 0; - // Rotation - currentAngle = (currentAngle + 45) % 360; + // Rotation and normalize negative angles to [0, 360) + currentAngle = (currentAngle + (isRightClick ? 45 : -45)) % 360; + if (currentAngle < 0) { + currentAngle += 360; + } // Save mirrorOrientations[coordKey] = currentAngle; @@ -288,68 +287,3 @@ function finish() { } -// Get player inputs arrows, qwerty and azerty - -document.addEventListener("keydown", (e) => { - - // If level finish -> don't move - if (isLevelFinished === true) { - return; - } - - switch (e.key) { - case "ArrowUp": - movePlayer(0, -1); - break; - case "w": - movePlayer(0, -1); - break; - case "z": - movePlayer(0, -1); - break; - case "ArrowDown": - movePlayer(0, 1); - break; - case "s": - movePlayer(0, 1); - break; - case "ArrowLeft": - movePlayer(-1, 0); - break; - case "a": - movePlayer(-1, 0); - break; - case "q": - movePlayer(-1, 0); - break; - case "ArrowRight": - movePlayer(1, 0); - break; - case "d": - movePlayer(1, 0); - break; - case "Enter": - rotateMirror(playerX, playerY); - break; - } -}); - -// Player move - -function movePlayer(x, y) { - const newX = playerX + x; - const newY = playerY + y; - - if (newX < 0 || newX >= level1[0].length) { - return - }; - - if (newY < 0 || newY >= level1.length) { - return - }; - - playerX = newX; - playerY = newY; - - loadGrid(); -} From 09d54aa5259e9655b04ef538cb9a1be5c3124b22 Mon Sep 17 00:00:00 2001 From: Sysy's Date: Tue, 31 Mar 2026 09:46:29 +0200 Subject: [PATCH 15/36] Add diagonal lasers --- web/assets/css/game.css | 9 +++++- web/assets/js/game.js | 68 +++++++++++++++++++++++++---------------- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index 3a7f56c..065e5e3 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -71,7 +71,6 @@ main { .empty { background-color: #DADEEF; - border-color: #DADEEF; } .empty:hover { @@ -113,6 +112,14 @@ main { background: linear-gradient(to right, transparent 0%, transparent 45%, red 45%, red 55%, transparent 55%, transparent 100%), #DADEEF; } +.laser-diagonal-down { + background: linear-gradient(45deg, transparent 0%, transparent 46%, red 46%, red 54%, transparent 54%, transparent 100%), #DADEEF; +} + +.laser-diagonal-up { + background: linear-gradient(135deg, transparent 0%, transparent 46%, red 46%, red 54%, transparent 54%, transparent 100%), #DADEEF; +} + /* ================================ MIRROR ================================ */ diff --git a/web/assets/js/game.js b/web/assets/js/game.js index f9337cf..db6ea50 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -33,6 +33,45 @@ let laserDirection = { dx: 0, dy: 0 }; let laserSegments = {}; let mirrorOrientations = {}; +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 initializeMirrorOrientations() { mirrorOrientations = {}; // Reset for (let y = 0; y < level1.length; y++) { @@ -105,11 +144,7 @@ function loadGrid() { case legend.ligthLaser: cell.classList.add("light-laser"); const segmentDirection = laserSegments[`${y},${x}`]; - if (segmentDirection && segmentDirection.dx === 0) { - cell.classList.add("laser-vertical"); - } else { - cell.classList.add("laser-horizontal"); - } + cell.classList.add(getLaserSegmentClass(segmentDirection)); break; } @@ -134,7 +169,7 @@ function rotateMirror(x, y, isRightClick) { let currentAngle = mirrorOrientations[coordKey] || 0; // Rotation and normalize negative angles to [0, 360) - currentAngle = (currentAngle + (isRightClick ? 45 : -45)) % 360; + currentAngle = (currentAngle + (isRightClick ? 22.5 : -22.5)) % 360; if (currentAngle < 0) { currentAngle += 360; } @@ -223,26 +258,7 @@ function traceLaser() { case legend.mirror: const mirrorAngle = mirrorOrientations[`${currentY},${currentX}`] || 0; - - if (mirrorAngle === 0 || mirrorAngle === 180) { - laserDirection.dy = -laserDirection.dy; - } else { - if (mirrorAngle === 90 || mirrorAngle === 270) { - laserDirection.dx = -laserDirection.dx; - } - - if (mirrorAngle === 45 || mirrorAngle === 225) { - const tempDx = laserDirection.dx; - laserDirection.dx = laserDirection.dy; - laserDirection.dy = tempDx; - } - - if (mirrorAngle === 135 || mirrorAngle === 315) { - const tempDx = laserDirection.dx; - laserDirection.dx = -laserDirection.dy; - laserDirection.dy = -tempDx; - } - } + laserDirection = reflectLaser(laserDirection, mirrorAngle); break; case legend.wall: From 37aa7b50670419cca8ac5b2b8a1bec6f2da5592a Mon Sep 17 00:00:00 2001 From: Pierre Date: Tue, 31 Mar 2026 10:57:19 +0200 Subject: [PATCH 16/36] Map modified + sprites --- web/assets/css/game.css | 100 +++++++++++++++++--- web/assets/img/tiles/BlueMirror.svg | 9 ++ web/assets/img/tiles/BottomLeft-1.svg | 19 ++++ web/assets/img/tiles/BottomLeft-2.svg | 19 ++++ web/assets/img/tiles/BottomLeft.svg | 19 ++++ web/assets/img/tiles/BottomRight-1.svg | 19 ++++ web/assets/img/tiles/BottomRight-2.svg | 19 ++++ web/assets/img/tiles/BottomRight.svg | 19 ++++ web/assets/img/tiles/ButtonComplete.svg | 27 ++++++ web/assets/img/tiles/ButtonProfile.svg | 24 +++++ web/assets/img/tiles/ButtonQuarter.svg | 29 ++++++ web/assets/img/tiles/CableBottomLeft.svg | 22 +++++ web/assets/img/tiles/CableBottomRight.svg | 22 +++++ web/assets/img/tiles/CableH.svg | 22 +++++ web/assets/img/tiles/CableTopLeft.svg | 22 +++++ web/assets/img/tiles/CableTopRight.svg | 22 +++++ web/assets/img/tiles/CableV.svg | 22 +++++ web/assets/img/tiles/Capteur-1.svg | 22 +++++ web/assets/img/tiles/Capteur-2.svg | 22 +++++ web/assets/img/tiles/Capteur.svg | 22 +++++ web/assets/img/tiles/HorizontaleSemi.svg | 21 +++++ web/assets/img/tiles/Laser.svg | 44 +++++++++ web/assets/img/tiles/MetalDoor.svg | 46 +++++++++ web/assets/img/tiles/MetalMirror.svg | 15 +++ web/assets/img/tiles/Mirror.svg | 15 +++ web/assets/img/tiles/Prisme.svg | 3 + web/assets/img/tiles/RedMirror.svg | 9 ++ web/assets/img/tiles/TopLeft-1.svg | 19 ++++ web/assets/img/tiles/TopLeft-2.svg | 19 ++++ web/assets/img/tiles/TopLeft.svg | 19 ++++ web/assets/img/tiles/TopRight-1.svg | 19 ++++ web/assets/img/tiles/TopRight-2.svg | 19 ++++ web/assets/img/tiles/TopRight.svg | 19 ++++ web/assets/img/tiles/Trigger.svg | 24 +++++ web/assets/img/tiles/Tuile.svg | 21 +++++ web/assets/img/tiles/VerticaleSemi.svg | 21 +++++ web/assets/img/tiles/WoodenDoor.svg | 46 +++++++++ web/assets/img/tiles/WoodenMirror.svg | 15 +++ web/assets/img/tiles/YellowMirror.svg | 9 ++ web/assets/js/game.js | 108 ++++++++++++++-------- 40 files changed, 965 insertions(+), 47 deletions(-) create mode 100644 web/assets/img/tiles/BlueMirror.svg create mode 100644 web/assets/img/tiles/BottomLeft-1.svg create mode 100644 web/assets/img/tiles/BottomLeft-2.svg create mode 100644 web/assets/img/tiles/BottomLeft.svg create mode 100644 web/assets/img/tiles/BottomRight-1.svg create mode 100644 web/assets/img/tiles/BottomRight-2.svg create mode 100644 web/assets/img/tiles/BottomRight.svg create mode 100644 web/assets/img/tiles/ButtonComplete.svg create mode 100644 web/assets/img/tiles/ButtonProfile.svg create mode 100644 web/assets/img/tiles/ButtonQuarter.svg create mode 100644 web/assets/img/tiles/CableBottomLeft.svg create mode 100644 web/assets/img/tiles/CableBottomRight.svg create mode 100644 web/assets/img/tiles/CableH.svg create mode 100644 web/assets/img/tiles/CableTopLeft.svg create mode 100644 web/assets/img/tiles/CableTopRight.svg create mode 100644 web/assets/img/tiles/CableV.svg create mode 100644 web/assets/img/tiles/Capteur-1.svg create mode 100644 web/assets/img/tiles/Capteur-2.svg create mode 100644 web/assets/img/tiles/Capteur.svg create mode 100644 web/assets/img/tiles/HorizontaleSemi.svg create mode 100644 web/assets/img/tiles/Laser.svg create mode 100644 web/assets/img/tiles/MetalDoor.svg create mode 100644 web/assets/img/tiles/MetalMirror.svg create mode 100644 web/assets/img/tiles/Mirror.svg create mode 100644 web/assets/img/tiles/Prisme.svg create mode 100644 web/assets/img/tiles/RedMirror.svg create mode 100644 web/assets/img/tiles/TopLeft-1.svg create mode 100644 web/assets/img/tiles/TopLeft-2.svg create mode 100644 web/assets/img/tiles/TopLeft.svg create mode 100644 web/assets/img/tiles/TopRight-1.svg create mode 100644 web/assets/img/tiles/TopRight-2.svg create mode 100644 web/assets/img/tiles/TopRight.svg create mode 100644 web/assets/img/tiles/Trigger.svg create mode 100644 web/assets/img/tiles/Tuile.svg create mode 100644 web/assets/img/tiles/VerticaleSemi.svg create mode 100644 web/assets/img/tiles/WoodenDoor.svg create mode 100644 web/assets/img/tiles/WoodenMirror.svg create mode 100644 web/assets/img/tiles/YellowMirror.svg diff --git a/web/assets/css/game.css b/web/assets/css/game.css index 065e5e3..bff5d45 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -71,6 +71,7 @@ main { .empty { background-color: #DADEEF; + border-color: #DADEEF; } .empty:hover { @@ -78,24 +79,102 @@ main { } .laser { - background-color: #FFD700; + background-color: #DADEEF; + background-image: url("../img/tiles/Laser.svg"); + background-size: 80%; + background-repeat: no-repeat; + background-position: center; + transform: rotate(180deg); +} + +.colored-laser { + background-color: #DADEEF; + background-image: url("../img/tiles/Prisme.svg"); + background-size: 80%; + background-repeat: no-repeat; + background-position: center; } .mirror { background-color: #DADEEF; - border-color: #444444; position: relative; overflow: hidden; } .wall { background-color: #DADEEF; + background-image: url("../img/tiles/Tuile.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; +} + +.door { + background-color: #DADEEF; + background-image: url("../img/tiles/WoodenDoor.svg"); + transform: rotate(90deg); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; +} + +.button { + background-color: #DADEEF; + background-image: url("../img/tiles/ButtonComplete.svg"), url("../img/tiles/Tuile.svg"); + background-size: 100% 100%; + background-repeat: no-repeat; + background-position: center; +} + +.demi-wall { + background-color: #DADEEF; + background-image: url("../img/tiles/VerticaleSemi.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; } .target { - background: #00FF00; + background-color: #DADEEF; + background-image: url("../img/tiles/Trigger.svg"); + background-size: 80%; + background-repeat: no-repeat; + background-position: center; } +.demi-wall-corner-up-left { + background-color: #DADEEF; + background-image: url("../img/tiles/TopLeft.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; +} + +.demi-wall-corner-up-right { + background-color: #DADEEF; + background-image: url("../img/tiles/TopRight.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; +} + +.demi-wall-corner-down-left { + background-color: #DADEEF; + background-image: url("../img/tiles/BottomLeft.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; +} + +.demi-wall-corner-down-right { + background-color: #DADEEF; + background-image: url("../img/tiles/BottomRight.svg"); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; +} + + /* ================================ LIGHT LASER ================================ */ @@ -129,18 +208,17 @@ main { border: none; cursor: pointer; height: 100%; + width: 100%; position: relative; + display: flex; + align-items: center; + justify-content: center; } -.btn-mirror::after { - content: ''; - position: absolute; - top: 50%; - left: 10%; +.mirror-img { width: 80%; - height: 2px; - background-color: #aaaaaa; - transform-origin: center; + height: 80%; + object-fit: contain; } /* ================================ diff --git a/web/assets/img/tiles/BlueMirror.svg b/web/assets/img/tiles/BlueMirror.svg new file mode 100644 index 0000000..f86033a --- /dev/null +++ b/web/assets/img/tiles/BlueMirror.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/web/assets/img/tiles/BottomLeft-1.svg b/web/assets/img/tiles/BottomLeft-1.svg new file mode 100644 index 0000000..dbbb4da --- /dev/null +++ b/web/assets/img/tiles/BottomLeft-1.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/BottomLeft-2.svg b/web/assets/img/tiles/BottomLeft-2.svg new file mode 100644 index 0000000..b6c8f15 --- /dev/null +++ b/web/assets/img/tiles/BottomLeft-2.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/BottomLeft.svg b/web/assets/img/tiles/BottomLeft.svg new file mode 100644 index 0000000..15a864e --- /dev/null +++ b/web/assets/img/tiles/BottomLeft.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/BottomRight-1.svg b/web/assets/img/tiles/BottomRight-1.svg new file mode 100644 index 0000000..c316dc7 --- /dev/null +++ b/web/assets/img/tiles/BottomRight-1.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/BottomRight-2.svg b/web/assets/img/tiles/BottomRight-2.svg new file mode 100644 index 0000000..a1e5bcb --- /dev/null +++ b/web/assets/img/tiles/BottomRight-2.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/BottomRight.svg b/web/assets/img/tiles/BottomRight.svg new file mode 100644 index 0000000..3e8e4df --- /dev/null +++ b/web/assets/img/tiles/BottomRight.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/ButtonComplete.svg b/web/assets/img/tiles/ButtonComplete.svg new file mode 100644 index 0000000..440195e --- /dev/null +++ b/web/assets/img/tiles/ButtonComplete.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/ButtonProfile.svg b/web/assets/img/tiles/ButtonProfile.svg new file mode 100644 index 0000000..9f0380d --- /dev/null +++ b/web/assets/img/tiles/ButtonProfile.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/ButtonQuarter.svg b/web/assets/img/tiles/ButtonQuarter.svg new file mode 100644 index 0000000..9bb3afa --- /dev/null +++ b/web/assets/img/tiles/ButtonQuarter.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/CableBottomLeft.svg b/web/assets/img/tiles/CableBottomLeft.svg new file mode 100644 index 0000000..2de6044 --- /dev/null +++ b/web/assets/img/tiles/CableBottomLeft.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/CableBottomRight.svg b/web/assets/img/tiles/CableBottomRight.svg new file mode 100644 index 0000000..0435974 --- /dev/null +++ b/web/assets/img/tiles/CableBottomRight.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/CableH.svg b/web/assets/img/tiles/CableH.svg new file mode 100644 index 0000000..440786e --- /dev/null +++ b/web/assets/img/tiles/CableH.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/CableTopLeft.svg b/web/assets/img/tiles/CableTopLeft.svg new file mode 100644 index 0000000..66da788 --- /dev/null +++ b/web/assets/img/tiles/CableTopLeft.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/CableTopRight.svg b/web/assets/img/tiles/CableTopRight.svg new file mode 100644 index 0000000..6c7f58a --- /dev/null +++ b/web/assets/img/tiles/CableTopRight.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/CableV.svg b/web/assets/img/tiles/CableV.svg new file mode 100644 index 0000000..3b091be --- /dev/null +++ b/web/assets/img/tiles/CableV.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/Capteur-1.svg b/web/assets/img/tiles/Capteur-1.svg new file mode 100644 index 0000000..963bdb5 --- /dev/null +++ b/web/assets/img/tiles/Capteur-1.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/Capteur-2.svg b/web/assets/img/tiles/Capteur-2.svg new file mode 100644 index 0000000..8e27567 --- /dev/null +++ b/web/assets/img/tiles/Capteur-2.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/Capteur.svg b/web/assets/img/tiles/Capteur.svg new file mode 100644 index 0000000..931ae79 --- /dev/null +++ b/web/assets/img/tiles/Capteur.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/HorizontaleSemi.svg b/web/assets/img/tiles/HorizontaleSemi.svg new file mode 100644 index 0000000..d08d2bb --- /dev/null +++ b/web/assets/img/tiles/HorizontaleSemi.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/Laser.svg b/web/assets/img/tiles/Laser.svg new file mode 100644 index 0000000..354d5b8 --- /dev/null +++ b/web/assets/img/tiles/Laser.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/MetalDoor.svg b/web/assets/img/tiles/MetalDoor.svg new file mode 100644 index 0000000..c28b339 --- /dev/null +++ b/web/assets/img/tiles/MetalDoor.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/MetalMirror.svg b/web/assets/img/tiles/MetalMirror.svg new file mode 100644 index 0000000..8db4eb9 --- /dev/null +++ b/web/assets/img/tiles/MetalMirror.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/Mirror.svg b/web/assets/img/tiles/Mirror.svg new file mode 100644 index 0000000..56cc5f1 --- /dev/null +++ b/web/assets/img/tiles/Mirror.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/Prisme.svg b/web/assets/img/tiles/Prisme.svg new file mode 100644 index 0000000..1c05d06 --- /dev/null +++ b/web/assets/img/tiles/Prisme.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/assets/img/tiles/RedMirror.svg b/web/assets/img/tiles/RedMirror.svg new file mode 100644 index 0000000..acbbba0 --- /dev/null +++ b/web/assets/img/tiles/RedMirror.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/web/assets/img/tiles/TopLeft-1.svg b/web/assets/img/tiles/TopLeft-1.svg new file mode 100644 index 0000000..8a3dbc1 --- /dev/null +++ b/web/assets/img/tiles/TopLeft-1.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/TopLeft-2.svg b/web/assets/img/tiles/TopLeft-2.svg new file mode 100644 index 0000000..026a2ee --- /dev/null +++ b/web/assets/img/tiles/TopLeft-2.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/TopLeft.svg b/web/assets/img/tiles/TopLeft.svg new file mode 100644 index 0000000..ea2e90d --- /dev/null +++ b/web/assets/img/tiles/TopLeft.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/TopRight-1.svg b/web/assets/img/tiles/TopRight-1.svg new file mode 100644 index 0000000..d01eafe --- /dev/null +++ b/web/assets/img/tiles/TopRight-1.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/TopRight-2.svg b/web/assets/img/tiles/TopRight-2.svg new file mode 100644 index 0000000..c4e54a5 --- /dev/null +++ b/web/assets/img/tiles/TopRight-2.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/TopRight.svg b/web/assets/img/tiles/TopRight.svg new file mode 100644 index 0000000..81e21f4 --- /dev/null +++ b/web/assets/img/tiles/TopRight.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/Trigger.svg b/web/assets/img/tiles/Trigger.svg new file mode 100644 index 0000000..324fefa --- /dev/null +++ b/web/assets/img/tiles/Trigger.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/Tuile.svg b/web/assets/img/tiles/Tuile.svg new file mode 100644 index 0000000..2b7bce9 --- /dev/null +++ b/web/assets/img/tiles/Tuile.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/VerticaleSemi.svg b/web/assets/img/tiles/VerticaleSemi.svg new file mode 100644 index 0000000..3fa0309 --- /dev/null +++ b/web/assets/img/tiles/VerticaleSemi.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/WoodenDoor.svg b/web/assets/img/tiles/WoodenDoor.svg new file mode 100644 index 0000000..970a126 --- /dev/null +++ b/web/assets/img/tiles/WoodenDoor.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/WoodenMirror.svg b/web/assets/img/tiles/WoodenMirror.svg new file mode 100644 index 0000000..56cc5f1 --- /dev/null +++ b/web/assets/img/tiles/WoodenMirror.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/web/assets/img/tiles/YellowMirror.svg b/web/assets/img/tiles/YellowMirror.svg new file mode 100644 index 0000000..268a7a9 --- /dev/null +++ b/web/assets/img/tiles/YellowMirror.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/web/assets/js/game.js b/web/assets/js/game.js index db6ea50..c0e1e21 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -8,25 +8,32 @@ const legend = { door: 4, button: 5, wall: 6, - demiWall: 7, - target: 8, - ligthLaser: 9, + target: 7, + ligthLaser: 8, + demiWallCornerUpLeft: 9, + demiWallCornerUpRight: 10, + demiWallCornerDownLeft: 11, + demiWallCornerDownRight: 12 } // Grid test -let level1 = [ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 3, 0, 0, 0, 8, 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, 0, 0, 0, 0], - [0, 0, 3, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 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], -] +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, 0, 4, 0, 10, 6, 0, 0, 0, 0], + [0, 0, 0, 0, 7, 6, 6, 5, 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], + ] +]; + +let currentLevelIndex = 0; // Function to save initial orientation of mirrors let laserDirection = { dx: 0, dy: 0 }; @@ -74,9 +81,9 @@ function getLaserSegmentClass(segmentDirection) { 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) { + for (let y = 0; y < levels[currentLevelIndex].length; y++) { + for (let x = 0; x < levels[currentLevelIndex][y].length; x++) { + if (levels[currentLevelIndex][y][x] === legend.mirror) { mirrorOrientations[`${y},${x}`] = 0; // Default angle } } @@ -91,15 +98,15 @@ function loadGrid() { const mapDiv = document.getElementById("map"); // Div with map in DOM mapDiv.innerHTML = ""; - for (let y = 0; y < level1.length; y++) { + for (let y = 0; y < levels[currentLevelIndex].length; y++) { const lign = document.createElement("div"); lign.classList.add("lign"); - for (let x = 0; x < level1[y].length; x++) { + for (let x = 0; x < levels[currentLevelIndex][y].length; x++) { const cell = document.createElement("div"); cell.classList.add("cell"); - switch (level1[y][x]) { + switch (levels[currentLevelIndex][y][x]) { case legend.empty: cell.classList.add("empty"); break; @@ -115,7 +122,11 @@ function loadGrid() { btnMirror.classList.add("btn-mirror"); btnMirror.type = "button"; btnMirror.style.transform = `rotate(${currentAngle}deg)`; - btnMirror.style.width = "100%"; + const img = document.createElement("img"); + img.src = "../../assets/img/tiles/Mirror.svg"; + img.style.rotate = `${currentAngle}deg`; + img.className = "mirror-img"; + btnMirror.appendChild(img); btnMirror.onmousedown = (e) => { e.preventDefault(); e.stopPropagation(); @@ -146,6 +157,18 @@ function loadGrid() { const segmentDirection = laserSegments[`${y},${x}`]; cell.classList.add(getLaserSegmentClass(segmentDirection)); break; + case legend.demiWallCornerUpLeft: + cell.classList.add("demi-wall-corner-up-left"); + break; + case legend.demiWallCornerUpRight: + cell.classList.add("demi-wall-corner-up-right"); + break; + case legend.demiWallCornerDownLeft: + cell.classList.add("demi-wall-corner-down-left"); + break; + case legend.demiWallCornerDownRight: + cell.classList.add("demi-wall-corner-down-right"); + break; } lign.appendChild(cell); @@ -162,7 +185,7 @@ loadGrid(); function rotateMirror(x, y, isRightClick) { const coordKey = `${y},${x}`; - if (level1[y][x] !== legend.mirror) { + if (levels[currentLevelIndex][y][x] !== legend.mirror) { return; } @@ -189,10 +212,10 @@ let isLevelFinished = false; function traceLaser() { // Reset light laser from previous trace laserSegments = {}; - 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; + for (let y = 0; y < levels[currentLevelIndex].length; y++) { + for (let x = 0; x < levels[currentLevelIndex][y].length; x++) { + if (levels[currentLevelIndex][y][x] === legend.ligthLaser) { + levels[currentLevelIndex][y][x] = legend.empty; } } } @@ -201,9 +224,9 @@ function traceLaser() { 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) { + for (let y = 0; y < levels[currentLevelIndex].length; y++) { + for (let x = 0; x < levels[currentLevelIndex][y].length; x++) { + if (levels[currentLevelIndex][y][x] === legend.laser) { startLaserX = x; startLaserY = y; laserDirection = { dx: 1, dy: 0 }; @@ -231,12 +254,12 @@ function traceLaser() { currentY += laserDirection.dy; // Out of bounds - if (currentX < 0 || currentX >= level1[0].length || currentY < 0 || currentY >= level1.length) { + if (currentX < 0 || currentX >= levels[currentLevelIndex][0].length || currentY < 0 || currentY >= levels[currentLevelIndex].length) { laserActive = false; break; } - const cellType = level1[currentY][currentX]; + const cellType = levels[currentLevelIndex][currentY][currentX]; switch (cellType) { case legend.laser: @@ -245,12 +268,12 @@ function traceLaser() { break; case legend.empty: - level1[currentY][currentX] = legend.ligthLaser; + levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; break; case legend.target: - level1[currentY][currentX] = legend.ligthLaser; + levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; laserActive = false; isLevelFinished = true; @@ -274,13 +297,26 @@ function traceLaser() { break; case legend.button: - level1[currentY][currentX] = legend.ligthLaser; + levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; laserActive = false; break; + case legend.demiWallCornerUpLeft: + laserDirection = reflectLaser(laserDirection, 135); + break; + case legend.demiWallCornerUpRight: + laserDirection = reflectLaser(laserDirection, 90); + break; + case legend.demiWallCornerDownLeft: + laserDirection = reflectLaser(laserDirection, 135); + break; + case legend.demiWallCornerDownRight: + laserDirection = reflectLaser(laserDirection, 45); + break; + default: - level1[currentY][currentX] = legend.ligthLaser; + levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; break; } From 4fc21e7876b289294147b29f66b0fa1a8c4892b2 Mon Sep 17 00:00:00 2001 From: Pierre Date: Tue, 31 Mar 2026 11:32:40 +0200 Subject: [PATCH 17/36] Resolution merge conflicts --- web/assets/css/game.css | 57 ++++++++++++++++++++- web/assets/js/game.js | 108 +++++++++++++++++++++++++++++++++++----- 2 files changed, 150 insertions(+), 15 deletions(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index bff5d45..30c5e00 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -17,6 +17,7 @@ body { align-items: center; justify-content: center; font-family: 'Arial', sans-serif; + user-select: none; } main { @@ -28,6 +29,36 @@ main { border-radius: 10px; min-width: fit-content; flex-shrink: 0; + gap: 16px; +} + +.game-layout { + width: min(95vw, 1000px); +} + +.toolbox { + width: 100%; + background: #dfe5f8; + border-radius: 10px; + padding: 14px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.toolbox h2 { + font-size: 1rem; +} + +.toolbox p { + font-size: 0.9rem; + color: #334; +} + +.glass-palette { + display: flex; + gap: 10px; + flex-wrap: wrap; } /* ================================ @@ -63,6 +94,12 @@ main { margin: 0; position: relative; background-color: #2a2a2a; + user-select: none; + overflow: hidden; +} + +.cell.can-drop { + outline: 2px dashed rgba(0, 0, 0, 0.2); } /* ================================ @@ -71,7 +108,6 @@ main { .empty { background-color: #DADEEF; - border-color: #DADEEF; } .empty:hover { @@ -118,6 +154,10 @@ main { background-position: center; } +.door-open { + opacity: 0.5; +} + .button { background-color: #DADEEF; background-image: url("../img/tiles/ButtonComplete.svg"), url("../img/tiles/Tuile.svg"); @@ -126,6 +166,10 @@ main { background-position: center; } +.button-active { + opacity: 0.7; +} + .demi-wall { background-color: #DADEEF; background-image: url("../img/tiles/VerticaleSemi.svg"); @@ -174,7 +218,6 @@ main { background-position: center; } - /* ================================ LIGHT LASER ================================ */ @@ -221,6 +264,16 @@ main { object-fit: contain; } +.button-door { + background: none; + border: none; + cursor: pointer; + width: 100%; + height: 100%; + position: absolute; + inset: 0; +} + /* ================================ RESPONSIVE ================================ */ diff --git a/web/assets/js/game.js b/web/assets/js/game.js index c0e1e21..403fb7e 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -13,9 +13,23 @@ const legend = { demiWallCornerUpLeft: 9, demiWallCornerUpRight: 10, demiWallCornerDownLeft: 11, - demiWallCornerDownRight: 12 + demiWallCornerDownRight: 12, + doorOpen: 13, } +const laserColors = { + white: "white", + red: "red", + blue: "blue", + yellow: "yellow", +}; + +const glassOptions = [ + laserColors.red, + laserColors.blue, + laserColors.yellow, +]; + // Grid test let levels = [ @@ -31,6 +45,8 @@ 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], ] + + ]; let currentLevelIndex = 0; @@ -39,6 +55,8 @@ let currentLevelIndex = 0; let laserDirection = { dx: 0, dy: 0 }; let laserSegments = {}; let mirrorOrientations = {}; +let activatedButtons = {}; +let openedDoors = {}; function normalizeLaserDirection(dx, dy) { const epsilon = 0.0001; @@ -79,6 +97,31 @@ function getLaserSegmentClass(segmentDirection) { return "laser-diagonal-up"; } +function openAdjacentDoors(x, y) { + // Open adjacent doors (up, down, left, right) + const directions = [ + { dx: 0, dy: -1 }, + { dx: 0, dy: 1 }, + { dx: -1, dy: 0 }, + { dx: 1, dy: 0 } + ]; + + for (let dir of directions) { + const newX = x + dir.dx; + const newY = y + dir.dy; + + if (newX >= 0 && newX < levels[currentLevelIndex][0].length && + newY >= 0 && newY < levels[currentLevelIndex].length) { + if (levels[currentLevelIndex][newY][newX] === legend.door) { + openedDoors[`${newY},${newX}`] = true; + levels[currentLevelIndex][newY][newX] = legend.doorOpen; // Change state to open + } + } + } + + loadGrid(); // Refresh grid to show opened doors +} + function initializeMirrorOrientations() { mirrorOrientations = {}; // Reset for (let y = 0; y < levels[currentLevelIndex].length; y++) { @@ -139,16 +182,30 @@ function loadGrid() { 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"); + } + let button = document.createElement("button"); + button.classList.add("button-door"); + button.type = "button"; + button.onclick = () => { + activatedButtons[`${y},${x}`] = !activatedButtons[`${y},${x}`]; + if (activatedButtons[`${y},${x}`]) { + openAdjacentDoors(x, y); + } + traceLaser(); + }; + cell.appendChild(button); 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; @@ -169,6 +226,9 @@ function loadGrid() { case legend.demiWallCornerDownRight: cell.classList.add("demi-wall-corner-down-right"); break; + case legend.doorOpen: + cell.classList.add("door-open"); + break; } lign.appendChild(cell); @@ -212,6 +272,7 @@ let isLevelFinished = false; function traceLaser() { // Reset light laser from previous trace laserSegments = {}; + // Ne pas réinitialiser activatedButtons et openedDoors pour préserver l'état des boutons manuels for (let y = 0; y < levels[currentLevelIndex].length; y++) { for (let x = 0; x < levels[currentLevelIndex][y].length; x++) { if (levels[currentLevelIndex][y][x] === legend.ligthLaser) { @@ -288,15 +349,27 @@ function traceLaser() { laserActive = false; break; - case legend.demiWall: - laserActive = false; + case legend.door: + if (openedDoors[`${currentY},${currentX}`]) { + // La porte est ouverte, le laser la traverse + levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; + laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; + } else { + // La porte est fermée, le laser s'arrête + laserActive = false; + } break; - case legend.door: - laserActive = false; + case legend.doorOpen: + // Porte ouverte - le laser la traverse + levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; + laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; break; case legend.button: + // Activer le bouton et ouvrir les portes adjacentes + activatedButtons[`${currentY},${currentX}`] = true; + openAdjacentDoors(currentX, currentY); levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; laserActive = false; @@ -306,13 +379,13 @@ function traceLaser() { laserDirection = reflectLaser(laserDirection, 135); break; case legend.demiWallCornerUpRight: - laserDirection = reflectLaser(laserDirection, 90); + laserDirection = reflectLaser(laserDirection, 45); break; case legend.demiWallCornerDownLeft: - laserDirection = reflectLaser(laserDirection, 135); + laserDirection = reflectLaser(laserDirection, 45); break; case legend.demiWallCornerDownRight: - laserDirection = reflectLaser(laserDirection, 45); + laserDirection = reflectLaser(laserDirection, 315); break; default: @@ -331,11 +404,20 @@ function traceLaser() { traceLaser(); +// Reset level state +function resetLevel() { + activatedButtons = {}; + openedDoors = {}; + laserSegments = {}; + laserDirection = { dx: 0, dy: 0 }; + isLevelFinished = false; + initializeMirrorOrientations(); + traceLaser(); +} + // If level finishh -> call this function function finish() { setTimeout(() => { alert("Réussi !"); }, 100); } - - From abf097e117f6c564f6611313372285433bb4da66 Mon Sep 17 00:00:00 2001 From: Sysy's Date: Tue, 31 Mar 2026 11:52:30 +0200 Subject: [PATCH 18/36] Fix textures/button/lasers & add ads --- web/assets/css/game.css | 149 ++++--- web/assets/img/tiles/WoodenDoor.svg | 61 +-- web/assets/img/tiles/WoodenDoor_openned.svg | 53 +++ web/assets/js/game.js | 428 +++++++++++++------- web/templates/view/game.html | 24 +- 5 files changed, 476 insertions(+), 239 deletions(-) create mode 100644 web/assets/img/tiles/WoodenDoor_openned.svg diff --git a/web/assets/css/game.css b/web/assets/css/game.css index 30c5e00..e23411f 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -16,7 +16,7 @@ body { display: flex; align-items: center; justify-content: center; - font-family: 'Arial', sans-serif; + font-family: Arial, sans-serif; user-select: none; } @@ -61,29 +61,18 @@ main { flex-wrap: wrap; } -/* ================================ - GRID -================================ */ - .map { display: flex; flex-direction: column; - gap: 0px; padding: 10px; - background: #DADEEF; + background: #dadeef; border-radius: 5px; } .lign { display: flex; - gap: 0px; - margin: 0; } -/* ================================ - CELLS -================================ */ - .cell { width: clamp(28px, 5.5vmin, 60px); height: clamp(28px, 5.5vmin, 60px); @@ -91,7 +80,6 @@ main { display: flex; align-items: center; justify-content: center; - margin: 0; position: relative; background-color: #2a2a2a; user-select: none; @@ -102,12 +90,8 @@ main { outline: 2px dashed rgba(0, 0, 0, 0.2); } -/* ================================ - CASES TYPE -================================ */ - .empty { - background-color: #DADEEF; + background-color: #dadeef; } .empty:hover { @@ -148,14 +132,16 @@ main { .door { background-color: #DADEEF; background-image: url("../img/tiles/WoodenDoor.svg"); - transform: rotate(90deg); - background-size: 100%; + background-size: contain; background-repeat: no-repeat; - background-position: center; + background-position: end; } .door-open { - opacity: 0.5; + background-image: url("../img/tiles/WoodenDoor_openned.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: end; } .button { @@ -166,18 +152,18 @@ main { background-position: center; } -.button-active { - opacity: 0.7; -} - -.demi-wall { +.button-2 { background-color: #DADEEF; - background-image: url("../img/tiles/VerticaleSemi.svg"); - background-size: 100%; + background-image: url("../img/tiles/ButtonQuarter.svg"), url("../img/tiles/Tuile.svg"); + background-size: 100% 100%; background-repeat: no-repeat; background-position: center; } +.button-active { + opacity: 0.7; +} + .target { background-color: #DADEEF; background-image: url("../img/tiles/Trigger.svg"); @@ -218,41 +204,57 @@ main { background-position: center; } -/* ================================ - LIGHT LASER -================================ */ - -.light-laser { - position: relative; +.laser-overlay { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 2; } .laser-horizontal { - background: linear-gradient(to bottom, transparent 0%, transparent 45%, red 45%, red 55%, transparent 55%, transparent 100%), #DADEEF + --laser-color: red; + background: linear-gradient(to bottom, transparent 0%, transparent 45%, var(--laser-color) 45%, var(--laser-color) 55%, transparent 55%, transparent 100%); } .laser-vertical { - background: linear-gradient(to right, transparent 0%, transparent 45%, red 45%, red 55%, transparent 55%, transparent 100%), #DADEEF; + --laser-color: red; + background: linear-gradient(to right, transparent 0%, transparent 45%, var(--laser-color) 45%, var(--laser-color) 55%, transparent 55%, transparent 100%); } .laser-diagonal-down { - background: linear-gradient(45deg, transparent 0%, transparent 46%, red 46%, red 54%, transparent 54%, transparent 100%), #DADEEF; + --laser-color: red; + background: linear-gradient(45deg, transparent 0%, transparent 46%, var(--laser-color) 46%, var(--laser-color) 54%, transparent 54%, transparent 100%); } .laser-diagonal-up { - background: linear-gradient(135deg, transparent 0%, transparent 46%, red 46%, red 54%, transparent 54%, transparent 100%), #DADEEF; + --laser-color: red; + background: linear-gradient(135deg, transparent 0%, transparent 46%, var(--laser-color) 46%, var(--laser-color) 54%, transparent 54%, transparent 100%); } -/* ================================ - MIRROR -================================ */ +.laser-color-white { + --laser-color: #f8f8f8; +} + +.laser-color-red { + --laser-color: #ff3b30; +} + +.laser-color-blue { + --laser-color: #2d7ff9; +} + +.laser-color-yellow { + --laser-color: #ffd400; +} .btn-mirror { background: none; border: none; cursor: pointer; - height: 100%; width: 100%; + height: 100%; position: relative; + z-index: 3; display: flex; align-items: center; justify-content: center; @@ -262,35 +264,60 @@ main { width: 80%; height: 80%; object-fit: contain; + pointer-events: none; } -.button-door { - background: none; +.glass-item { + width: 54px; + height: 54px; border: none; - cursor: pointer; - width: 100%; - height: 100%; - position: absolute; - inset: 0; + border-radius: 10px; + cursor: grab; + position: relative; + box-shadow: inset 0 0 0 2px rgba(0, 0, 0, 0.1); + user-select: none; + color: #223; + font-size: 0.8rem; + font-weight: bold; } -/* ================================ - RESPONSIVE -================================ */ +.glass-item::after, +.cell-glass::after { + content: ""; + position: absolute; + inset: 10px; + border-radius: 8px; + background: rgba(255, 255, 255, 0.45); + border: 1px solid rgba(255, 255, 255, 0.8); +} + +.glass-red { + background: rgba(255, 59, 48, 0.85); +} + +.glass-blue { + background: rgba(45, 127, 249, 0.85); +} + +.glass-yellow { + background: rgba(255, 212, 0, 0.9); +} + +.cell-glass { + position: absolute; + inset: 5px; + border-radius: 8px; + opacity: 0.9; + pointer-events: none; + z-index: 4; +} @media (max-width: 600px) { .map { padding: 5px; - border-radius: 3px; } main { padding: 8px; } } - -@media (max-height: 500px) { - .map { - padding: 4px; - } -} diff --git a/web/assets/img/tiles/WoodenDoor.svg b/web/assets/img/tiles/WoodenDoor.svg index 970a126..bead2b3 100644 --- a/web/assets/img/tiles/WoodenDoor.svg +++ b/web/assets/img/tiles/WoodenDoor.svg @@ -1,46 +1,49 @@ - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - + - + + + + + - + + + + + - + + + + + - + + + + + + + + + - + \ No newline at end of file diff --git a/web/assets/img/tiles/WoodenDoor_openned.svg b/web/assets/img/tiles/WoodenDoor_openned.svg new file mode 100644 index 0000000..0c07c55 --- /dev/null +++ b/web/assets/img/tiles/WoodenDoor_openned.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/assets/js/game.js b/web/assets/js/game.js index 403fb7e..c98b140 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -15,7 +15,8 @@ const legend = { demiWallCornerDownLeft: 11, demiWallCornerDownRight: 12, doorOpen: 13, -} + button2: 14, +}; const laserColors = { white: "white", @@ -30,33 +31,46 @@ const glassOptions = [ laserColors.yellow, ]; -// Grid test - 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, 0, 4, 0, 10, 6, 0, 0, 0, 0], - [0, 0, 0, 0, 7, 6, 6, 5, 6, 0, 6, 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], - ] - - + ], ]; let currentLevelIndex = 0; -// Function to save initial orientation of mirrors +const initialMirrorAngles = { + "6,4": 315, +}; + +const buttonGroups = { + "4,6": 1, +}; + +const doorGroups = { + "4,7": 1, +}; + let laserDirection = { dx: 0, dy: 0 }; let laserSegments = {}; let mirrorOrientations = {}; +let glassPlacements = {}; let activatedButtons = {}; let openedDoors = {}; +let isLevelFinished = false; + +function getCurrentLevel() { + return levels[currentLevelIndex]; +} function normalizeLaserDirection(dx, dy) { const epsilon = 0.0001; @@ -77,6 +91,13 @@ function reflectLaser(direction, mirrorAngle) { return normalizeLaserDirection(reflectedDx, reflectedDy); } +function reverseLaser(direction) { + return { + dx: direction.dx * -1, + dy: direction.dy * -1, + }; +} + function getLaserSegmentClass(segmentDirection) { if (!segmentDirection) { return "laser-horizontal"; @@ -97,59 +118,190 @@ function getLaserSegmentClass(segmentDirection) { return "laser-diagonal-up"; } -function openAdjacentDoors(x, y) { - // Open adjacent doors (up, down, left, right) - const directions = [ - { dx: 0, dy: -1 }, - { dx: 0, dy: 1 }, - { dx: -1, dy: 0 }, - { dx: 1, dy: 0 } - ]; +function getLaserColorClass(color) { + return `laser-color-${color || laserColors.white}`; +} - for (let dir of directions) { - const newX = x + dir.dx; - const newY = y + dir.dy; - - if (newX >= 0 && newX < levels[currentLevelIndex][0].length && - newY >= 0 && newY < levels[currentLevelIndex].length) { - if (levels[currentLevelIndex][newY][newX] === legend.door) { - openedDoors[`${newY},${newX}`] = true; - levels[currentLevelIndex][newY][newX] = legend.doorOpen; // Change state to open +function getButtonGroup(x, y) { + const cellType = getCurrentLevel()[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); + const level = getCurrentLevel(); + + for (let doorY = 0; doorY < level.length; doorY++) { + for (let doorX = 0; doorX < level[doorY].length; doorX++) { + if (level[doorY][doorX] === legend.door && getDoorGroup(doorX, doorY) === buttonGroup) { + openedDoors[`${doorY},${doorX}`] = true; } } } +} - loadGrid(); // Refresh grid to show opened doors +function saveLaserSegment(x, y, direction, color) { + laserSegments[`${y},${x}`] = { + direction: { ...direction }, + color: color, + }; +} + +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 drawLaserInCell(cell, segmentData) { + if (!segmentData) { + return; + } + + const laserDiv = document.createElement("div"); + laserDiv.classList.add("laser-overlay"); + laserDiv.classList.add(getLaserSegmentClass(segmentData.direction)); + laserDiv.classList.add(getLaserColorClass(segmentData.color)); + cell.appendChild(laserDiv); +} + +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("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; + } + + const cellType = getCurrentLevel()[y][x]; + if (cellType !== legend.empty) { + 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 (getCurrentLevel()[y][x] !== legend.empty) { + 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 < levels[currentLevelIndex].length; y++) { - for (let x = 0; x < levels[currentLevelIndex][y].length; x++) { - if (levels[currentLevelIndex][y][x] === legend.mirror) { - mirrorOrientations[`${y},${x}`] = 0; // Default angle + mirrorOrientations = {}; + const level = getCurrentLevel(); + + for (let y = 0; y < level.length; y++) { + for (let x = 0; x < level[y].length; x++) { + if (level[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 + const mapDiv = document.getElementById("map"); + const level = getCurrentLevel(); mapDiv.innerHTML = ""; - for (let y = 0; y < levels[currentLevelIndex].length; y++) { + for (let y = 0; y < level.length; y++) { const lign = document.createElement("div"); lign.classList.add("lign"); - for (let x = 0; x < levels[currentLevelIndex][y].length; x++) { + for (let x = 0; x < level[y].length; x++) { const cell = document.createElement("div"); cell.classList.add("cell"); + addDropEvents(cell, x, y); - switch (levels[currentLevelIndex][y][x]) { + switch (level[y][x]) { case legend.empty: cell.classList.add("empty"); break; @@ -160,25 +312,25 @@ function loadGrid() { cell.classList.add("colored-laser"); break; case legend.mirror: + cell.classList.add("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)`; const img = document.createElement("img"); img.src = "../../assets/img/tiles/Mirror.svg"; - img.style.rotate = `${currentAngle}deg`; - img.className = "mirror-img"; + img.classList.add("mirror-img"); + img.style.transform = `rotate(${currentAngle}deg)`; btnMirror.appendChild(img); - btnMirror.onmousedown = (e) => { - e.preventDefault(); - e.stopPropagation(); - rotateMirror(x, y, e.button === 2); + btnMirror.onmousedown = (event) => { + event.preventDefault(); + event.stopPropagation(); + if (!isLevelFinished) { + rotateMirror(x, y, event.button === 2); + } }; - btnMirror.oncontextmenu = (e) => e.preventDefault(); - + btnMirror.oncontextmenu = (event) => event.preventDefault(); cell.appendChild(btnMirror); - cell.classList.add("mirror"); break; case legend.door: cell.classList.add("door"); @@ -186,22 +338,20 @@ function loadGrid() { cell.classList.add("door-open"); } break; + case legend.doorOpen: + cell.classList.add("door", "door-open"); + break; case legend.button: cell.classList.add("button"); if (activatedButtons[`${y},${x}`]) { cell.classList.add("button-active"); } - let button = document.createElement("button"); - button.classList.add("button-door"); - button.type = "button"; - button.onclick = () => { - activatedButtons[`${y},${x}`] = !activatedButtons[`${y},${x}`]; - if (activatedButtons[`${y},${x}`]) { - openAdjacentDoors(x, y); - } - traceLaser(); - }; - cell.appendChild(button); + 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"); @@ -209,11 +359,6 @@ function loadGrid() { case legend.target: cell.classList.add("target"); break; - case legend.ligthLaser: - cell.classList.add("light-laser"); - const segmentDirection = laserSegments[`${y},${x}`]; - cell.classList.add(getLaserSegmentClass(segmentDirection)); - break; case legend.demiWallCornerUpLeft: cell.classList.add("demi-wall-corner-up-left"); break; @@ -226,11 +371,10 @@ function loadGrid() { case legend.demiWallCornerDownRight: cell.classList.add("demi-wall-corner-down-right"); break; - case legend.doorOpen: - cell.classList.add("door-open"); - break; } + drawLaserInCell(cell, laserSegments[`${y},${x}`]); + drawGlassInCell(cell, x, y); lign.appendChild(cell); } @@ -238,89 +382,76 @@ function loadGrid() { } } -loadGrid(); - -// Function to rotate mirror - function rotateMirror(x, y, isRightClick) { const coordKey = `${y},${x}`; - if (levels[currentLevelIndex][y][x] !== legend.mirror) { + if (getCurrentLevel()[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); + traceLaser(); } - -// Function to trace - -let isLevelFinished = false; - function traceLaser() { - // Reset light laser from previous trace laserSegments = {}; - // Ne pas réinitialiser activatedButtons et openedDoors pour préserver l'état des boutons manuels - for (let y = 0; y < levels[currentLevelIndex].length; y++) { - for (let x = 0; x < levels[currentLevelIndex][y].length; x++) { - if (levels[currentLevelIndex][y][x] === legend.ligthLaser) { - levels[currentLevelIndex][y][x] = legend.empty; - } - } - } + activatedButtons = {}; + openedDoors = {}; + isLevelFinished = false; + const level = getCurrentLevel(); let startLaserX; let startLaserY; - // Search laser - for (let y = 0; y < levels[currentLevelIndex].length; y++) { - for (let x = 0; x < levels[currentLevelIndex][y].length; x++) { - if (levels[currentLevelIndex][y][x] === legend.laser) { + for (let y = 0; y < level.length; y++) { + for (let x = 0; x < level[y].length; x++) { + if (level[y][x] === legend.laser) { startLaserX = x; startLaserY = y; laserDirection = { dx: 1, dy: 0 }; break; } } - if (startLaserX !== undefined) break; + + if (startLaserX !== undefined) { + break; + } } - // If laser not found -> return if (startLaserX === undefined) { return; } let currentX = startLaserX; let currentY = startLaserY; + let currentLaserColor = laserColors.white; let laserActive = true; - const maxIterations = 1000; // Prevent infinite loops let iterations = 0; + const maxIterations = 1000; while (laserActive && iterations < maxIterations) { iterations++; - currentX += laserDirection.dx; currentY += laserDirection.dy; - // Out of bounds - if (currentX < 0 || currentX >= levels[currentLevelIndex][0].length || currentY < 0 || currentY >= levels[currentLevelIndex].length) { + if (currentX < 0 || currentX >= level[0].length || currentY < 0 || currentY >= level.length) { laserActive = false; break; } - const cellType = levels[currentLevelIndex][currentY][currentX]; + const cellType = level[currentY][currentX]; + const glassColor = glassPlacements[`${currentY},${currentX}`]; + + if (glassColor) { + currentLaserColor = glassColor; + } switch (cellType) { case legend.laser: @@ -329,68 +460,80 @@ function traceLaser() { break; case legend.empty: - levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; - laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; + saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); break; case legend.target: - levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; - laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; + saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); laserActive = false; isLevelFinished = true; break; case legend.mirror: - const mirrorAngle = mirrorOrientations[`${currentY},${currentX}`] || 0; - laserDirection = reflectLaser(laserDirection, mirrorAngle); + if (currentLaserColor === laserColors.yellow) { + saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); + } else { + const mirrorAngle = mirrorOrientations[`${currentY},${currentX}`] || 0; + laserDirection = reflectLaser(laserDirection, mirrorAngle); + } break; case legend.wall: - laserActive = false; - break; - - case legend.door: - if (openedDoors[`${currentY},${currentX}`]) { - // La porte est ouverte, le laser la traverse - levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; - laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; + if (currentLaserColor === laserColors.blue) { + laserDirection = reverseLaser(laserDirection); + } else if (currentLaserColor === laserColors.yellow) { + saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); } else { - // La porte est fermée, le laser s'arrête laserActive = false; } break; + case legend.door: case legend.doorOpen: - // Porte ouverte - le laser la traverse - levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; - laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; + if (openedDoors[`${currentY},${currentX}`] || cellType === legend.doorOpen) { + 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: - // Activer le bouton et ouvrir les portes adjacentes - activatedButtons[`${currentY},${currentX}`] = true; - openAdjacentDoors(currentX, currentY); - levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; - laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; - laserActive = false; + 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; case legend.demiWallCornerUpLeft: laserDirection = reflectLaser(laserDirection, 135); break; + case legend.demiWallCornerUpRight: laserDirection = reflectLaser(laserDirection, 45); break; + case legend.demiWallCornerDownLeft: - laserDirection = reflectLaser(laserDirection, 45); + laserDirection = reflectLaser(laserDirection, 225); break; + case legend.demiWallCornerDownRight: laserDirection = reflectLaser(laserDirection, 315); break; default: - levels[currentLevelIndex][currentY][currentX] = legend.ligthLaser; - laserSegments[`${currentY},${currentX}`] = { ...laserDirection }; + saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor); break; } } @@ -402,22 +545,13 @@ function traceLaser() { } } -traceLaser(); - -// Reset level state -function resetLevel() { - activatedButtons = {}; - openedDoors = {}; - laserSegments = {}; - laserDirection = { dx: 0, dy: 0 }; - isLevelFinished = false; - initializeMirrorOrientations(); - traceLaser(); -} - -// If level finishh -> call this function function finish() { setTimeout(() => { - alert("Réussi !"); + alert("Reussi !"); }, 100); } + +createPalette(); +initializeMirrorOrientations(); +blockBrowserDrop(); +traceLaser(); diff --git a/web/templates/view/game.html b/web/templates/view/game.html index d90f9e9..970a953 100644 --- a/web/templates/view/game.html +++ b/web/templates/view/game.html @@ -5,10 +5,30 @@ Game + + -
+ + + +
+
+ +
+

Vitres tintées

+
+
+
- \ No newline at end of file + From 94ddc4a00f51af7ed9672b32cbec396a44e9651e Mon Sep 17 00:00:00 2001 From: Sysy's Date: Tue, 31 Mar 2026 11:55:20 +0200 Subject: [PATCH 19/36] Make size bigger + add ads --- web/assets/css/game.css | 6 +++--- web/templates/view/game.html | 12 +++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index e23411f..7874e12 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -33,7 +33,7 @@ main { } .game-layout { - width: min(95vw, 1000px); + width: min(96vw, 1200px); } .toolbox { @@ -74,8 +74,8 @@ main { } .cell { - width: clamp(28px, 5.5vmin, 60px); - height: clamp(28px, 5.5vmin, 60px); + width: clamp(28px, 6.2vmin, 72px); + height: clamp(28px, 6.2vmin, 72px); transition: all 0.2s ease; display: flex; align-items: center; diff --git a/web/templates/view/game.html b/web/templates/view/game.html index 970a953..3f78612 100644 --- a/web/templates/view/game.html +++ b/web/templates/view/game.html @@ -28,7 +28,17 @@
- + + + From 43100e65e9c3d068547b114e940a0329d24ec0bf Mon Sep 17 00:00:00 2001 From: Sysy's Date: Tue, 31 Mar 2026 12:00:03 +0200 Subject: [PATCH 20/36] Add game title and update layout styles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adjust UI visuals: change page background to #FFF6E5, increase main container border-radius from 10px to 150px, and soften toolbox corners (5px → 15px). Add a new .game-title CSS rule (responsive font-size, weight, color, letter-spacing, centered) and insert an

Mirror Game

into the game template. These are visual polish changes only; no gameplay logic was modified. --- web/assets/css/game.css | 14 +++++++++++--- web/templates/view/game.html | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index 7874e12..be4d2d7 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -12,7 +12,7 @@ body { } body { - background: #f7f7f7; + background: #FFF6E5; display: flex; align-items: center; justify-content: center; @@ -26,7 +26,7 @@ main { align-items: center; justify-content: center; padding: 20px; - border-radius: 10px; + border-radius: 150px; min-width: fit-content; flex-shrink: 0; gap: 16px; @@ -36,6 +36,14 @@ main { width: min(96vw, 1200px); } +.game-title { + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 700; + color: #223; + letter-spacing: 0.04em; + text-align: center; +} + .toolbox { width: 100%; background: #dfe5f8; @@ -66,7 +74,7 @@ main { flex-direction: column; padding: 10px; background: #dadeef; - border-radius: 5px; + border-radius: 15px; } .lign { diff --git a/web/templates/view/game.html b/web/templates/view/game.html index 3f78612..acecbdd 100644 --- a/web/templates/view/game.html +++ b/web/templates/view/game.html @@ -21,6 +21,7 @@
+

Mirror Game

From 49045e4d76083d95813b0db13028b6e37055e1f1 Mon Sep 17 00:00:00 2001 From: Sysy's Date: Tue, 31 Mar 2026 12:07:37 +0200 Subject: [PATCH 21/36] Add win overlay --- web/assets/css/game.css | 45 ++++++++++++++++++++++++++++++++++++ web/assets/js/game.js | 3 ++- web/templates/view/game.html | 6 +++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/web/assets/css/game.css b/web/assets/css/game.css index be4d2d7..b7e5362 100644 --- a/web/assets/css/game.css +++ b/web/assets/css/game.css @@ -127,6 +127,12 @@ main { background-color: #DADEEF; position: relative; overflow: hidden; + -moz-transform: scaleX(-1); + -webkit-transform: scaleX(-1); + -o-transform: scaleX(-1); + transform: scaleX(-1); + -ms-filter: fliph; /*IE*/ + filter: fliph; /*IE*/ } .wall { @@ -329,3 +335,42 @@ main { padding: 8px; } } + +.win-overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 1000; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + visibility: hidden; + gap: 20px; + backdrop-filter: blur(6px); + +} + +.win-overlay h1 { + font-size: 2rem; + font-weight: 700; + color: #fff; + text-align: center; +} + +.win-overlay p { + font-size: 1.5rem; + font-weight: 400; + color: #fff; + text-align: center; +} + +.win-overlay button { + font-size: 1.5rem; + padding: 10px 20px; + border-radius: 5px; + background-color: #fff; + color: #000; + border: none; + cursor: pointer; +} diff --git a/web/assets/js/game.js b/web/assets/js/game.js index c98b140..36d57a7 100644 --- a/web/assets/js/game.js +++ b/web/assets/js/game.js @@ -547,7 +547,8 @@ function traceLaser() { function finish() { setTimeout(() => { - alert("Reussi !"); + const winOverlay = document.querySelector(".win-overlay"); + winOverlay.style.visibility = "visible"; }, 100); } diff --git a/web/templates/view/game.html b/web/templates/view/game.html index acecbdd..8ee4b57 100644 --- a/web/templates/view/game.html +++ b/web/templates/view/game.html @@ -9,6 +9,12 @@ +
+

You win!

+

You have completed the level.

+ +
+ @@ -56,6 +56,6 @@ - +