Compare commits
65 Commits
11d47caf1e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d0e9f9951d | |||
| 6e546483f9 | |||
| ddcb13cd63 | |||
| 2eb0e64b30 | |||
| 6a5774667e | |||
|
|
3a6bd21b59 | ||
|
|
a5fa460a69 | ||
|
|
694de9e2d6 | ||
| 9fdfe27728 | |||
|
|
ea479f8225 | ||
|
|
6127e53708 | ||
|
|
2f9da6b38e | ||
|
|
2b8dc254bf | ||
|
|
068ad3dd92 | ||
|
|
954af3cded | ||
|
|
cfeded5079 | ||
|
|
ac9ba24857 | ||
|
|
b15ca69499 | ||
|
|
12e8ac34d4 | ||
|
|
4a21f85a37 | ||
|
|
1eb3aec524 | ||
|
|
647e5b9048 | ||
|
|
903a1de933 | ||
|
|
fa1ffc214c | ||
| 1145d26e9d | |||
|
|
c1cb862f4c | ||
|
|
6767f55640 | ||
|
|
f4c821ae97 | ||
|
|
67d1453c23 | ||
|
|
90a0de0429 | ||
|
|
d988aabac0 | ||
|
|
34d213d5f4 | ||
|
|
ab4043defa | ||
| 6229fe7b9e | |||
|
|
a9d2a0c9a5 | ||
|
|
9c7c2ddc1d | ||
|
|
edea5e0972 | ||
|
|
f3809db72b | ||
|
|
68b6493e50 | ||
| a267884dbe | |||
| 64b0e5e770 | |||
| 57b37d0139 | |||
| 4939b74fad | |||
| 7e5de16a02 | |||
|
|
49045e4d76 | ||
|
|
43100e65e9 | ||
|
|
94ddc4a00f | ||
|
|
abf097e117 | ||
| 4fc21e7876 | |||
| 37aa7b5067 | |||
|
|
09d54aa525 | ||
|
|
76de2a5a5c | ||
|
|
284e396d5f | ||
|
|
4ba42f6566 | ||
|
|
d0e282b41c | ||
| 024b80d393 | |||
| 34fbb797c1 | |||
| b10b6475d8 | |||
| 2dbfff770f | |||
| a791415bd3 | |||
|
|
577357b090 | ||
| 1be3750672 | |||
| 5668d21b0d | |||
| 001adb89bd | |||
| 04a0e1a912 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
Test
|
||||
backend/user.db
|
||||
116
README.md
116
README.md
@@ -1,35 +1,117 @@
|
||||
Règles :
|
||||
<div align="center">
|
||||
<h1 align="center">MirrorGame</h1>
|
||||
<p align="center">
|
||||
Browser puzzle game where you guide lasers with mirrors, tinted glass, doors, and triggers.
|
||||
<br />
|
||||
<br />
|
||||
<a href="https://git.ninolbt.com/Nono/Projet_48h/issues">⚠️ Report Bug</a>
|
||||
·
|
||||
<a href="https://git.ninolbt.com/Nono/Projet_48h/issues">💡 Request Feature</a>
|
||||
·
|
||||
<a href="https://gamegamegame.ninolbt.com/">🎮 Play Online</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/language-Rust%20%7C%20HTML%20%7C%20CSS%20%7C%20JavaScript-00ADD8?style=for-the-badge&labelColor=000000" />
|
||||
<img src="https://img.shields.io/badge/platform-Web-6E56CF?style=for-the-badge&labelColor=000000" />
|
||||
<img src="https://img.shields.io/badge/status-Playable-28A745?style=for-the-badge&labelColor=000000" />
|
||||
</p>
|
||||
<p align="center">
|
||||
<img src="./web/assets/img/image_game.png" alt="MirrorGame main menu" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
- Un rayon laser constant
|
||||
---
|
||||
|
||||
- Un ou plusieurs laser
|
||||
### 🔍 Overview
|
||||
|
||||
- Laser non movibles, peut-être allumé/éteins
|
||||
`MirrorGame` is a small laser-reflection puzzle game built during a game jam format.
|
||||
|
||||
- Mirroirs orientables
|
||||
The goal is to route a laser beam through each level by rotating mirrors, using tinted glass to change beam color, and interacting with doors, buttons, captors, and other grid-based puzzle elements until the beam reaches the target.
|
||||
|
||||
- Prisme (dédouble le laser ou le renvoi selon l'angle)
|
||||
The game is playable online and can also be run locally through the Rust server included in this repository.
|
||||
> Level progression is stored locally in your browser with `localStorage`.
|
||||
|
||||
- Vitre colorée fixe ou placable par le joueur (r,j,b)
|
||||
### ✨ Features
|
||||
|
||||
- Bouton allumable par clique souris (interaction porte, mirroir, etc)
|
||||
- **Laser puzzle gameplay**: Redirect beams across handcrafted levels to activate targets.
|
||||
- **Mirror rotation**: Rotate mirrors directly on the board to change the laser path.
|
||||
- **Colored glass placement**: Drag and drop red, blue, and yellow glass tiles onto the grid.
|
||||
- **Interactive mechanisms**: Trigger doors, buttons, captors, and rotating mirror systems.
|
||||
- **Multiple levels**: Progress through several unlockable puzzle stages.
|
||||
- **Browser save data**: Unlocked levels are persisted between sessions with `localStorage`.
|
||||
- **Rust-powered local server**: Lightweight backend serves the HTML, CSS, JS, and assets.
|
||||
|
||||
- Bouton allumable par laser spécifique (interaction porte, mirroir, etc)
|
||||
### 🎮 Live Demo
|
||||
|
||||
Play here: [MirrorGame](https://gamegamegame.ninolbt.com/)
|
||||
|
||||
Rayon :
|
||||
### 🖼️ Screenshot
|
||||
|
||||
- Blanc -> Rebondis seulement sur les mirroirs et s'arrête contre les murs
|
||||
Main menu:
|
||||
|
||||
- Rouge -> Allumage des récepteurs
|
||||

|
||||
|
||||
- Bleu -> Rebondis sur toutes les surfaces (murs compris)
|
||||
### 🧰 Requirements
|
||||
|
||||
- Jaune -> Traverse tout (mirroir compris)
|
||||
- Rust and Cargo
|
||||
- A modern desktop browser
|
||||
|
||||
---
|
||||
|
||||
Systeme de placement d'objet par grille
|
||||
### 🚀 Installation and usage
|
||||
|
||||
Comptes utilisateur
|
||||
#### Option 1 — Play online
|
||||
|
||||
Optionnel : timer, tableau de score,
|
||||
- Open [**MirrorGame**](https://gamegamegame.ninolbt.com/) in your browser.
|
||||
|
||||
#### Option 2 — Run locally from source
|
||||
|
||||
1. Install Rust using [rustup](https://rustup.rs/).
|
||||
2. Clone or download this repository.
|
||||
3. Open a terminal in the `backend` folder.
|
||||
4. Start the local server:
|
||||
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
|
||||
5. Open your browser at:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:3500
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📖 How to play
|
||||
|
||||
1. **Launch the game**
|
||||
- Open the online version or run the project locally.
|
||||
- Click **Play** from the main menu.
|
||||
|
||||
2. **Understand the goal**
|
||||
- Each level starts with a laser source.
|
||||
- Your objective is to make the beam reach the target tile.
|
||||
|
||||
3. **Rotate mirrors**
|
||||
- Click mirrors to rotate them and redirect the beam.
|
||||
- Some mirrors are controlled by puzzle mechanisms instead of direct input.
|
||||
|
||||
4. **Use tinted glass**
|
||||
- Drag red, blue, or yellow glass from the toolbox onto empty cells.
|
||||
- Double-click a placed glass tile to remove it and get it back.
|
||||
|
||||
5. **Solve interactions**
|
||||
- Red beams can activate buttons and captors.
|
||||
- Doors, rotators, and other level elements react depending on the puzzle setup.
|
||||
|
||||
6. **Progress through levels**
|
||||
- Completing a level unlocks the next one.
|
||||
- Use the level menu to revisit unlocked stages.
|
||||
|
||||
---
|
||||
|
||||
### 🛠️ Tech Stack
|
||||
|
||||
- **Backend**: Rust with `axum` and `tower-http`
|
||||
- **Frontend**: HTML, CSS, and vanilla JavaScript
|
||||
57
backend/Cargo.lock
generated
57
backend/Cargo.lock
generated
@@ -90,6 +90,7 @@ dependencies = [
|
||||
"axum",
|
||||
"axum-server",
|
||||
"serde",
|
||||
"sqlite",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing-subscriber",
|
||||
@@ -107,6 +108,16 @@ version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
@@ -135,6 +146,12 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -450,6 +467,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
@@ -563,6 +586,12 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
@@ -595,6 +624,34 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlite"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f66e9c01a11936154f3910dbba732c01f8b591543bc4d6672bddee79fd9c4783"
|
||||
dependencies = [
|
||||
"sqlite3-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlite3-src"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5b6d3c860886b0a33e69e421796a5f4a27f23597a182c2450f6d7ace5103120"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlite3-sys"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7781d97adc13a1d5081127a9ee29afad8427f3757bd984daf814d8265267039"
|
||||
dependencies = [
|
||||
"sqlite3-src",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
|
||||
@@ -10,3 +10,4 @@ serde = "1.0.228"
|
||||
tokio = { version = "1.50.0", features = ["full"] }
|
||||
tracing-subscriber = "0.3.23"
|
||||
tower-http = { version = "0.6", features = ["fs"] }
|
||||
sqlite = "*"
|
||||
|
||||
@@ -4,14 +4,18 @@ use std::path::Path;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{self, AsyncReadExt};
|
||||
use tower_http::services::ServeDir;
|
||||
mod user;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let db = user::DataBase::open(String::from("user"));
|
||||
db.create_table("users", "name TEXT, level TEXT, password TEXT");
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.nest_service("/assets", ServeDir::new("../web/assets"));
|
||||
.route("/game", get(game))
|
||||
.nest_service("/web/assets", ServeDir::new("../web/assets"));
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3500));
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 3500));
|
||||
println!("listening on {}", addr);
|
||||
axum_server::bind(addr)
|
||||
.serve(app.into_make_service())
|
||||
@@ -26,6 +30,27 @@ async fn handler() -> Html<String> {
|
||||
Html(html_content)
|
||||
}
|
||||
|
||||
async fn game() -> Html<String> {
|
||||
let html_content = read_html_from_file("../web/templates/view/game.html")
|
||||
.await
|
||||
.unwrap_or_else(|_| "<h1>Error loading HTML file</h1>".to_string());
|
||||
Html(html_content)
|
||||
}
|
||||
|
||||
async fn register() -> Html<String> {
|
||||
let html_content = read_html_from_file("../web/templates/view/register.html")
|
||||
.await
|
||||
.unwrap_or_else(|_| "<h1>Error loading HTML file</h1>".to_string());
|
||||
Html(html_content)
|
||||
}
|
||||
|
||||
async fn login() -> Html<String> {
|
||||
let html_content = read_html_from_file("../web/templates/view/login.html")
|
||||
.await
|
||||
.unwrap_or_else(|_| "<h1>Error loading HTML file</h1>".to_string());
|
||||
Html(html_content)
|
||||
}
|
||||
|
||||
async fn read_html_from_file<P: AsRef<Path>>(path: P) -> io::Result<String> {
|
||||
let mut file = File::open(path).await?;
|
||||
let mut contents = String::new();
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use sqlite::Connection;
|
||||
use sqlite::State;
|
||||
|
||||
pub struct User {
|
||||
name: String,
|
||||
level: u32,
|
||||
password: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn open(name: String, level: u32, password: String) -> User {
|
||||
User {name, level, password}
|
||||
pub fn open(name: String, level: u32) -> User {
|
||||
User {name, level}
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> String{return self.name.clone()}
|
||||
pub fn get_level(&self) -> u32{return self.level}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,11 +46,9 @@ impl DataBase {
|
||||
|
||||
pub fn check(&self, table: &str, arg: &str) -> bool {
|
||||
let mut end = false;
|
||||
self.connection.iterate("Select name FROM ".to_owned() + &table.to_string() + " WHERE name = " + &arg.to_string(), |pairs| {
|
||||
self.connection.iterate("Select name FROM ".to_owned() + &table.to_string() + " WHERE name = " + &format!("'{}'",&arg.to_string()), |pairs| {
|
||||
for &(name, value) in pairs.iter() {
|
||||
println!("{} = {}", name, value.unwrap());
|
||||
if name == "name" {
|
||||
println!("True");
|
||||
end = true;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,71 @@ body {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.level-menu-shell {
|
||||
position: fixed;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
z-index: 1100;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.level-menu-toggle {
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: #223;
|
||||
color: #fff;
|
||||
padding: 12px 18px;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 20px rgba(34, 51, 68, 0.18);
|
||||
}
|
||||
|
||||
.level-menu-panel {
|
||||
min-width: 180px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
border-radius: 18px;
|
||||
padding: 14px;
|
||||
box-shadow: 0 18px 40px rgba(34, 51, 68, 0.18);
|
||||
border: 1px solid rgba(34, 51, 68, 0.08);
|
||||
}
|
||||
|
||||
.level-menu-panel h2 {
|
||||
font-size: 0.95rem;
|
||||
color: #223;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.level-menu-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.level-menu-item {
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
background: #dfe5f8;
|
||||
color: #223;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.level-menu-item.is-current {
|
||||
background: #223;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.level-menu-item:disabled {
|
||||
background: #eceff7;
|
||||
color: #8a92a3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -108,7 +173,7 @@ main {
|
||||
|
||||
.laser {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/Laser.svg");
|
||||
background-image: url("/web/assets/img/tiles/Laser.svg");
|
||||
background-size: 80%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -117,7 +182,7 @@ main {
|
||||
|
||||
.colored-laser {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/Prisme.svg");
|
||||
background-image: url("/web/assets/img/tiles/Prisme.svg");
|
||||
background-size: 80%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -137,15 +202,31 @@ main {
|
||||
|
||||
.wall {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/Tuile.svg");
|
||||
background-image: url("/web/assets/img/tiles/Tuile.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.wall-semi-angle {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("/web/assets/img/tiles/BottomLeft.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.horizontal-semi {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("/web/assets/img/tiles/HorizontaleSemi.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom;
|
||||
}
|
||||
|
||||
.captor {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/Capteur-1.svg");
|
||||
background-image: url("/web/assets/img/tiles/Capteur-1.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -154,39 +235,97 @@ main {
|
||||
|
||||
.captor-turn {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/Capteur-2.svg");
|
||||
background-image: url("/web/assets/img/tiles/Capteur-2.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.cable {
|
||||
.captor-turn-reverse {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/CableV.svg");
|
||||
background-image: url("/web/assets/img/tiles/Capteur-2.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.captor-turn-horizontale2 {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("/web/assets/img/tiles/Capteur-1.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.captor-vertical-top {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("/web/assets/img/tiles/Capteur-1.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.cable-vertical {
|
||||
.cable {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/CableV.svg");
|
||||
background-image: url("/web/assets/img/tiles/CableH.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.cable-turn {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("/web/assets/img/tiles/CableBottomLeft.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.cable-turn-horizontale3 {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("/web/assets/img/tiles/CableTopRight.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.cable-vertical {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("/web/assets/img/tiles/CableV.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.cable-turn-horizontale {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("/web/assets/img/tiles/CableTopLeft.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.cable-turn-horizontale2 {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("/web/assets/img/tiles/CableTopLeft.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.door {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/WoodenDoor.svg");
|
||||
background-image: url("/web/assets/img/tiles/WoodenDoor.svg");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: end;
|
||||
}
|
||||
|
||||
.door-open {
|
||||
background-image: url("../img/tiles/WoodenDoor_openned.svg");
|
||||
background-image: url("/web/assets/img/tiles/WoodenDoor_openned.svg");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: end;
|
||||
@@ -194,7 +333,7 @@ main {
|
||||
|
||||
.button {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/ButtonComplete.svg"), url("../img/tiles/Tuile.svg");
|
||||
background-image: url("/web/assets/img/tiles/ButtonComplete.svg"), url("/web/assets/img/tiles/Tuile.svg");
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -202,7 +341,7 @@ main {
|
||||
|
||||
.button-2 {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/ButtonQuarter.svg"), url("../img/tiles/Tuile.svg");
|
||||
background-image: url("/web/assets/img/tiles/ButtonQuarter.svg"), url("/web/assets/img/tiles/Tuile.svg");
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -210,7 +349,7 @@ main {
|
||||
|
||||
.button-rotator {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/ButtonProfile.svg"), url("../img/tiles/Tuile.svg");
|
||||
background-image: url("/web/assets/img/tiles/ButtonProfile.svg"), url("/web/assets/img/tiles/Tuile.svg");
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -222,7 +361,7 @@ main {
|
||||
|
||||
.target {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/Trigger.svg");
|
||||
background-image: url("/web/assets/img/tiles/Trigger.svg");
|
||||
background-size: 80%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -230,7 +369,7 @@ main {
|
||||
|
||||
.demi-wall-corner-up-left {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/TopLeft.svg");
|
||||
background-image: url("/web/assets/img/tiles/TopLeft.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -238,7 +377,7 @@ main {
|
||||
|
||||
.demi-wall-corner-up-right {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/TopRight.svg");
|
||||
background-image: url("/web/assets/img/tiles/TopRight.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -246,7 +385,7 @@ main {
|
||||
|
||||
.demi-wall-corner-down-left {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/BottomLeft.svg");
|
||||
background-image: url("/web/assets/img/tiles/BottomLeftAngle.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -254,7 +393,7 @@ main {
|
||||
|
||||
.demi-wall-corner-down-right {
|
||||
background-color: #DADEEF;
|
||||
background-image: url("../img/tiles/BottomRight.svg");
|
||||
background-image: url("/web/assets/img/tiles/BottomRightAngle.svg");
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -341,6 +480,24 @@ main {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.glass-item:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.glass-item-label {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
bottom: 3px;
|
||||
z-index: 2;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
color: #223;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
padding: 1px 5px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.glass-item::after,
|
||||
.cell-glass::after {
|
||||
content: "";
|
||||
|
||||
@@ -67,6 +67,10 @@ body {
|
||||
aspect-ratio: 9 / 16;
|
||||
}
|
||||
|
||||
.leftadsbanner.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 90vw;
|
||||
text-align: left;
|
||||
@@ -198,6 +202,11 @@ body {
|
||||
margin-right: 0.45em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.adsbanner,
|
||||
.leftadsbanner {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-play-button {
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 99L1 2.41406L97.5859 99L1 99Z" fill="url(#paint0_linear_17_89)" stroke="url(#paint1_linear_17_89)" stroke-width="2"/>
|
||||
<path d="M1 99L1 1.61816L99 50.6182V99L1 99Z" fill="url(#paint0_linear_35_19)" stroke="url(#paint1_linear_35_19)" stroke-width="2"/>
|
||||
<circle cx="5.5" cy="94.5" r="1.5" transform="rotate(-180 5.5 94.5)" fill="#4A4A4A"/>
|
||||
<circle cx="5.5" cy="94.5" r="1" transform="rotate(-180 5.5 94.5)" fill="#898989"/>
|
||||
<circle cx="5.5" cy="13.5" r="1.5" transform="rotate(-180 5.5 13.5)" fill="#4A4A4A"/>
|
||||
<circle cx="5.5" cy="13.5" r="1" transform="rotate(-180 5.5 13.5)" fill="#898989"/>
|
||||
<circle cx="86.5" cy="94.5" r="1.5" transform="rotate(-180 86.5 94.5)" fill="#4A4A4A"/>
|
||||
<circle cx="86.5" cy="94.5" r="1" transform="rotate(-180 86.5 94.5)" fill="#898989"/>
|
||||
<circle cx="4.5" cy="8.5" r="1.5" transform="rotate(-180 4.5 8.5)" fill="#4A4A4A"/>
|
||||
<circle cx="4.5" cy="8.5" r="1" transform="rotate(-180 4.5 8.5)" fill="#898989"/>
|
||||
<circle cx="94.5" cy="94.5" r="1.5" transform="rotate(-180 94.5 94.5)" fill="#4A4A4A"/>
|
||||
<circle cx="94.5" cy="94.5" r="1" transform="rotate(-180 94.5 94.5)" fill="#898989"/>
|
||||
<circle cx="94.5" cy="53.5" r="1.5" transform="rotate(-180 94.5 53.5)" fill="#4A4A4A"/>
|
||||
<circle cx="94.5" cy="53.5" r="1" transform="rotate(-180 94.5 53.5)" fill="#898989"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_17_89" x1="0" y1="0" x2="100" y2="100" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint0_linear_35_19" x1="0" y1="0" x2="40" y2="80" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="#747474"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_17_89" x1="100" y1="100" x2="0" y2="0" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint1_linear_35_19" x1="100" y1="50" x2="60" y2="-30" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A8A8A8"/>
|
||||
<stop offset="1" stop-color="#848484"/>
|
||||
</linearGradient>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.3 KiB |
19
web/assets/img/tiles/BottomLeftAngle.svg
Normal file
19
web/assets/img/tiles/BottomLeftAngle.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 99L1 2.41406L97.5859 99L1 99Z" fill="url(#paint0_linear_17_89)" stroke="url(#paint1_linear_17_89)" stroke-width="2"/>
|
||||
<circle cx="5.5" cy="94.5" r="1.5" transform="rotate(-180 5.5 94.5)" fill="#4A4A4A"/>
|
||||
<circle cx="5.5" cy="94.5" r="1" transform="rotate(-180 5.5 94.5)" fill="#898989"/>
|
||||
<circle cx="5.5" cy="13.5" r="1.5" transform="rotate(-180 5.5 13.5)" fill="#4A4A4A"/>
|
||||
<circle cx="5.5" cy="13.5" r="1" transform="rotate(-180 5.5 13.5)" fill="#898989"/>
|
||||
<circle cx="86.5" cy="94.5" r="1.5" transform="rotate(-180 86.5 94.5)" fill="#4A4A4A"/>
|
||||
<circle cx="86.5" cy="94.5" r="1" transform="rotate(-180 86.5 94.5)" fill="#898989"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_17_89" x1="0" y1="0" x2="100" y2="100" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="#747474"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_17_89" x1="100" y1="100" x2="0" y2="0" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A8A8A8"/>
|
||||
<stop offset="1" stop-color="#848484"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,17 +1,19 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M99 99H2.41406L99 2.41406V99Z" fill="url(#paint0_linear_19_190)" stroke="url(#paint1_linear_19_190)" stroke-width="2"/>
|
||||
<path d="M99 99H1V50.6182L99 1.61816V99Z" fill="url(#paint0_linear_35_30)" stroke="url(#paint1_linear_35_30)" stroke-width="2"/>
|
||||
<circle cx="94.5" cy="94.5" r="1.5" transform="rotate(90 94.5 94.5)" fill="#4A4A4A"/>
|
||||
<circle cx="94.5" cy="94.5" r="1" transform="rotate(90 94.5 94.5)" fill="#898989"/>
|
||||
<circle cx="13.5" cy="94.5" r="1.5" transform="rotate(90 13.5 94.5)" fill="#4A4A4A"/>
|
||||
<circle cx="13.5" cy="94.5" r="1" transform="rotate(90 13.5 94.5)" fill="#898989"/>
|
||||
<circle cx="94.5" cy="13.5" r="1.5" transform="rotate(90 94.5 13.5)" fill="#4A4A4A"/>
|
||||
<circle cx="94.5" cy="13.5" r="1" transform="rotate(90 94.5 13.5)" fill="#898989"/>
|
||||
<circle cx="5.5" cy="94.5" r="1.5" transform="rotate(90 5.5 94.5)" fill="#4A4A4A"/>
|
||||
<circle cx="5.5" cy="94.5" r="1" transform="rotate(90 5.5 94.5)" fill="#898989"/>
|
||||
<circle cx="5.5" cy="53.5" r="1.5" transform="rotate(90 5.5 53.5)" fill="#4A4A4A"/>
|
||||
<circle cx="5.5" cy="53.5" r="1" transform="rotate(90 5.5 53.5)" fill="#898989"/>
|
||||
<circle cx="94.5" cy="9.5" r="1.5" transform="rotate(90 94.5 9.5)" fill="#4A4A4A"/>
|
||||
<circle cx="94.5" cy="9.5" r="1" transform="rotate(90 94.5 9.5)" fill="#898989"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_19_190" x1="0" y1="0" x2="100" y2="100" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint0_linear_35_30" x1="8.9049e-06" y1="1.50801e-06" x2="40" y2="80" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="#747474"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_19_190" x1="100" y1="100" x2="0" y2="0" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint1_linear_35_30" x1="100" y1="50" x2="60" y2="-30" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A8A8A8"/>
|
||||
<stop offset="1" stop-color="#848484"/>
|
||||
</linearGradient>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.3 KiB |
19
web/assets/img/tiles/BottomRightAngle.svg
Normal file
19
web/assets/img/tiles/BottomRightAngle.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M99 99H2.41406L99 2.41406V99Z" fill="url(#paint0_linear_19_190)" stroke="url(#paint1_linear_19_190)" stroke-width="2"/>
|
||||
<circle cx="94.5" cy="94.5" r="1.5" transform="rotate(90 94.5 94.5)" fill="#4A4A4A"/>
|
||||
<circle cx="94.5" cy="94.5" r="1" transform="rotate(90 94.5 94.5)" fill="#898989"/>
|
||||
<circle cx="13.5" cy="94.5" r="1.5" transform="rotate(90 13.5 94.5)" fill="#4A4A4A"/>
|
||||
<circle cx="13.5" cy="94.5" r="1" transform="rotate(90 13.5 94.5)" fill="#898989"/>
|
||||
<circle cx="94.5" cy="13.5" r="1.5" transform="rotate(90 94.5 13.5)" fill="#4A4A4A"/>
|
||||
<circle cx="94.5" cy="13.5" r="1" transform="rotate(90 94.5 13.5)" fill="#898989"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_19_190" x1="0" y1="0" x2="100" y2="100" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="#747474"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_19_190" x1="100" y1="100" x2="0" y2="0" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A8A8A8"/>
|
||||
<stop offset="1" stop-color="#848484"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -20,6 +20,16 @@ const legend = {
|
||||
cable: 16,
|
||||
captorTurn: 17,
|
||||
cableVertical: 18,
|
||||
captorTurnReturn: 19,
|
||||
rotatorButton: 20,
|
||||
cableTurn: 21,
|
||||
horizontalSemi: 22,
|
||||
cableTurnHorizontale: 23,
|
||||
cableTurnHorizontale2: 24,
|
||||
captorTurnHorizontal: 25,
|
||||
wallSemiAngle: 26,
|
||||
cableTurnHorizontale3: 27,
|
||||
captorVerticalTop: 28
|
||||
};
|
||||
|
||||
const laserColors = {
|
||||
@@ -30,34 +40,69 @@ const laserColors = {
|
||||
};
|
||||
|
||||
const glassOptions = [
|
||||
laserColors.red,
|
||||
laserColors.blue,
|
||||
laserColors.yellow,
|
||||
[
|
||||
{ color: laserColors.red, maxAmount: 1, currentAmount: 1 },
|
||||
{ color: laserColors.blue, maxAmount: 1, currentAmount: 1 },
|
||||
{ color: laserColors.yellow, maxAmount: 0, currentAmount: 0 },
|
||||
],
|
||||
[
|
||||
{ color: laserColors.red, maxAmount: 1, currentAmount: 1 },
|
||||
{ color: laserColors.blue, maxAmount: 1, currentAmount: 1 },
|
||||
{ color: laserColors.yellow, maxAmount: 1, currentAmount: 1 },
|
||||
],
|
||||
[
|
||||
{ color: laserColors.red, maxAmount: 1, currentAmount: 1 },
|
||||
{ color: laserColors.blue, maxAmount: 2, currentAmount: 2 },
|
||||
{ color: laserColors.yellow, maxAmount: 1, currentAmount: 1 },
|
||||
],
|
||||
[
|
||||
{ color: laserColors.red, maxAmount: 2, currentAmount: 2 },
|
||||
{ color: laserColors.blue, maxAmount: 1, currentAmount: 1 },
|
||||
{ color: laserColors.yellow, maxAmount: 2, currentAmount: 2 },
|
||||
],
|
||||
[
|
||||
{ color: laserColors.red, maxAmount: 3, currentAmount: 3 },
|
||||
{ color: laserColors.blue, maxAmount: 2, currentAmount: 2 },
|
||||
{ color: laserColors.yellow, maxAmount: 1, currentAmount: 1 },
|
||||
],
|
||||
];
|
||||
|
||||
let levels = [
|
||||
[
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 11, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 5, 4, 0, 10, 6, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 7, 6, 6, 6, 6, 0, 6, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 3, 0, 0, 0, 0, 12, 6, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 9, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 1, 0, 5, 4, 0, 10, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 7, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 3, 0, 0, 0, 0, 12, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
],
|
||||
|
||||
[
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 1, 0, 0, 0, 17, 0, 0, 3, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 18, 6, 6, 0, 6, 6, 6, 6, 6],
|
||||
[6, 6, 7, 0, 0, 0, 4, 0, 0, 12, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
|
||||
],
|
||||
|
||||
[
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 11, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 17, 0, 0, 3, 6, 0, 0, 0, 0],
|
||||
[0, 0, 6, 6, 6, 6, 18, 6, 6, 0, 6, 0, 0, 0, 0],
|
||||
[0, 0, 7, 0, 0, 0, 4, 0, 0, 12, 6, 0, 0, 0, 0],
|
||||
[0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 9, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 11, 0, 0, 0, 0],
|
||||
[7, 1, 0, 0, 0, 4, 0, 0, 0, 3, 21, 0, 0, 0, 0],
|
||||
[0, 6, 6, 6, 6, 18, 6, 6, 6, 0, 18, 0, 0, 0, 0],
|
||||
[0, 6, 9, 0, 0, 19, 0, 0, 0, 12, 18, 0, 0, 0, 0],
|
||||
[0, 6, 0, 6, 6, 6, 6, 6, 6, 24, 23, 0, 0, 0, 0],
|
||||
[0, 6, 11, 0, 0, 0, 0, 0, 20, 23, 9, 0, 0, 0, 0],
|
||||
[0, 10, 6, 6, 6, 6, 6, 6, 6, 9, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
],
|
||||
@@ -75,18 +120,17 @@ let levels = [
|
||||
[0, 0, 0, 0, 10, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0],
|
||||
],
|
||||
|
||||
/*
|
||||
[
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 11, 0],
|
||||
[0, 12, 6, 9, 0, 10, 6, 9, 0, 0, 0, 3, 0, 17, 0],
|
||||
[12, 6, 9, 0, 0, 0, 6, 0, 7, 0, 0, 0, 0, 18, 0],
|
||||
[6, 9, 0, 0, 0, 0, 6, 11, 0, 0, 12, 0, 0, 18, 0],
|
||||
[1, 0, 0, 0, 0, 12, 6, 6, 11, 0, 0, 3, 0, 18, 0],
|
||||
[10, 6, 11, 0, 0, 6, 0, 0, 10, 6, 6, 6, 6, 18, 0],
|
||||
[0, 10, 6, 28, 6, 9, 0, 0, 0, 10, 6, 6, 24, 23, 0],
|
||||
[0, 0, 10, 27, 16, 3, 4, 0, 0, 0, 10, 24, 23, 0, 0],
|
||||
[0, 0, 0, 0, 10, 6, 18, 6, 11, 0, 12, 18, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 10, 27, 16, 16, 16, 16, 23, 0, 0, 0],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -98,9 +142,10 @@ const initialMirrorAngles = [
|
||||
},
|
||||
{},
|
||||
{
|
||||
"3,4": 315,
|
||||
"7,8": 0,
|
||||
"2,9": 225,
|
||||
},
|
||||
{},
|
||||
{}
|
||||
];
|
||||
|
||||
const buttonGroups = [
|
||||
@@ -108,7 +153,10 @@ const buttonGroups = [
|
||||
"4,6": 1,
|
||||
},
|
||||
{},
|
||||
{},
|
||||
{
|
||||
"9,4": 1,
|
||||
},
|
||||
{}
|
||||
];
|
||||
|
||||
const doorGroups = [
|
||||
@@ -119,6 +167,7 @@ const doorGroups = [
|
||||
"4,6": 1,
|
||||
},
|
||||
{},
|
||||
{}
|
||||
];
|
||||
|
||||
const captorGroups = [
|
||||
@@ -127,14 +176,19 @@ const captorGroups = [
|
||||
"2,6": 1,
|
||||
},
|
||||
{},
|
||||
{}
|
||||
];
|
||||
|
||||
const rotatorButtons = [
|
||||
{},
|
||||
{},
|
||||
{
|
||||
"6,8": { mirrorX: 9, mirrorY: 2, step: 22.5, intervalMs: 1000 },
|
||||
},
|
||||
{
|
||||
"3,7": { mirrorX: 7, mirrorY: 7, step: -22.5, intervalMs: 1000 },
|
||||
},
|
||||
{}
|
||||
];
|
||||
|
||||
let laserDirection = { dx: 0, dy: 0 };
|
||||
@@ -148,6 +202,9 @@ let activeRotatorButtons = {};
|
||||
let rotatorIntervals = {};
|
||||
let toggledDoors = {};
|
||||
let poweredCaptors = {};
|
||||
let draggedGlassColor = null;
|
||||
let highestUnlockedLevelIndex = 0;
|
||||
const unlockedLevelsStorageKey = "mirror-game-highest-unlocked-level";
|
||||
|
||||
function getCurrentLevel() {
|
||||
return levels[currentLevelIndex];
|
||||
@@ -157,6 +214,131 @@ function getCurrentLevelConfig(configByLevel) {
|
||||
return configByLevel[currentLevelIndex] || {};
|
||||
}
|
||||
|
||||
function loadUnlockedLevels() {
|
||||
const savedValue = window.localStorage.getItem(unlockedLevelsStorageKey);
|
||||
const parsedValue = Number.parseInt(savedValue || "0", 10);
|
||||
|
||||
if (Number.isNaN(parsedValue)) {
|
||||
highestUnlockedLevelIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
highestUnlockedLevelIndex = Math.max(0, Math.min(parsedValue, levels.length - 1));
|
||||
}
|
||||
|
||||
function saveUnlockedLevels() {
|
||||
window.localStorage.setItem(unlockedLevelsStorageKey, String(highestUnlockedLevelIndex));
|
||||
}
|
||||
|
||||
function unlockLevel(levelIndex) {
|
||||
if (levelIndex <= highestUnlockedLevelIndex || levelIndex >= levels.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
highestUnlockedLevelIndex = levelIndex;
|
||||
saveUnlockedLevels();
|
||||
renderLevelMenu();
|
||||
}
|
||||
|
||||
function toggleLevelMenu() {
|
||||
const levelMenuPanel = document.getElementById("level-menu-panel");
|
||||
const levelMenuToggle = document.getElementById("level-menu-toggle");
|
||||
|
||||
if (!levelMenuPanel || !levelMenuToggle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isOpening = levelMenuPanel.hasAttribute("hidden");
|
||||
|
||||
if (isOpening) {
|
||||
levelMenuPanel.removeAttribute("hidden");
|
||||
} else {
|
||||
levelMenuPanel.setAttribute("hidden", "");
|
||||
}
|
||||
|
||||
levelMenuToggle.setAttribute("aria-expanded", String(isOpening));
|
||||
}
|
||||
|
||||
function renderLevelMenu() {
|
||||
const levelMenuList = document.getElementById("level-menu-list");
|
||||
|
||||
if (!levelMenuList) {
|
||||
return;
|
||||
}
|
||||
|
||||
levelMenuList.innerHTML = "";
|
||||
|
||||
for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) {
|
||||
const levelButton = document.createElement("button");
|
||||
const isUnlocked = levelIndex <= highestUnlockedLevelIndex;
|
||||
const isCurrentLevel = levelIndex === currentLevelIndex;
|
||||
|
||||
levelButton.type = "button";
|
||||
levelButton.classList.add("level-menu-item");
|
||||
levelButton.textContent = isUnlocked ? `Level ${levelIndex + 1}` : `Level ${levelIndex + 1} - Locked`;
|
||||
levelButton.disabled = !isUnlocked;
|
||||
|
||||
if (isCurrentLevel) {
|
||||
levelButton.classList.add("is-current");
|
||||
}
|
||||
|
||||
levelButton.addEventListener("click", () => {
|
||||
if (!isUnlocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
goToLevel(levelIndex);
|
||||
const levelMenuPanel = document.getElementById("level-menu-panel");
|
||||
const levelMenuToggle = document.getElementById("level-menu-toggle");
|
||||
|
||||
if (levelMenuPanel) {
|
||||
levelMenuPanel.setAttribute("hidden", "");
|
||||
}
|
||||
|
||||
if (levelMenuToggle) {
|
||||
levelMenuToggle.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
});
|
||||
|
||||
levelMenuList.appendChild(levelButton);
|
||||
}
|
||||
}
|
||||
|
||||
function setupLevelMenu() {
|
||||
const levelMenuToggle = document.getElementById("level-menu-toggle");
|
||||
|
||||
if (!levelMenuToggle) {
|
||||
return;
|
||||
}
|
||||
|
||||
levelMenuToggle.addEventListener("click", toggleLevelMenu);
|
||||
renderLevelMenu();
|
||||
}
|
||||
|
||||
function getCurrentGlassOptions() {
|
||||
return glassOptions[currentLevelIndex] || [];
|
||||
}
|
||||
|
||||
function getGlassOptionByColor(color) {
|
||||
const currentGlassOptions = getCurrentGlassOptions();
|
||||
|
||||
for (let i = 0; i < currentGlassOptions.length; i++) {
|
||||
if (currentGlassOptions[i].color === color) {
|
||||
return currentGlassOptions[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function resetGlassInventory() {
|
||||
const currentGlassOptions = getCurrentGlassOptions();
|
||||
|
||||
for (let i = 0; i < currentGlassOptions.length; i++) {
|
||||
currentGlassOptions[i].currentAmount = currentGlassOptions[i].maxAmount;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeLaserDirection(dx, dy) {
|
||||
const epsilon = 0.0001;
|
||||
const normalizedDx = Math.abs(dx) < epsilon ? 0 : Math.sign(dx);
|
||||
@@ -366,17 +548,40 @@ function createPalette() {
|
||||
|
||||
palette.innerHTML = "";
|
||||
|
||||
for (let i = 0; i < glassOptions.length; i++) {
|
||||
const glassColor = glassOptions[i];
|
||||
const currentGlassOptions = getCurrentGlassOptions();
|
||||
|
||||
for (let i = 0; i < currentGlassOptions.length; i++) {
|
||||
const glassOption = currentGlassOptions[i];
|
||||
const glassColor = glassOption.color;
|
||||
const glassButton = document.createElement("button");
|
||||
glassButton.type = "button";
|
||||
glassButton.classList.add("glass-item", `glass-${glassColor}`);
|
||||
glassButton.draggable = true;
|
||||
glassButton.draggable = glassOption.currentAmount > 0;
|
||||
|
||||
if (glassOption.currentAmount <= 0) {
|
||||
glassButton.disabled = true;
|
||||
}
|
||||
|
||||
const glassLabel = document.createElement("span");
|
||||
glassLabel.classList.add("glass-item-label");
|
||||
glassLabel.textContent = `${glassOption.currentAmount}/${glassOption.maxAmount}`;
|
||||
glassButton.appendChild(glassLabel);
|
||||
|
||||
glassButton.addEventListener("dragstart", (event) => {
|
||||
if (glassOption.currentAmount <= 0) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
draggedGlassColor = glassColor;
|
||||
event.dataTransfer.effectAllowed = "copy";
|
||||
event.dataTransfer.setData("application/x-glass-color", glassColor);
|
||||
});
|
||||
|
||||
glassButton.addEventListener("dragend", () => {
|
||||
draggedGlassColor = null;
|
||||
});
|
||||
|
||||
palette.appendChild(glassButton);
|
||||
}
|
||||
}
|
||||
@@ -387,7 +592,8 @@ function addDropEvents(cell, x, y) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.dataTransfer.types.includes("application/x-glass-color")) {
|
||||
const transferTypes = event.dataTransfer ? Array.from(event.dataTransfer.types || []) : [];
|
||||
if (!draggedGlassColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -396,6 +602,13 @@ function addDropEvents(cell, x, y) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedColor = draggedGlassColor;
|
||||
const glassOption = getGlassOptionByColor(selectedColor);
|
||||
|
||||
if (!glassOption || glassOption.currentAmount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
cell.classList.add("can-drop");
|
||||
});
|
||||
@@ -409,7 +622,8 @@ function addDropEvents(cell, x, y) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedColor = event.dataTransfer.getData("application/x-glass-color");
|
||||
const transferredColor = event.dataTransfer ? event.dataTransfer.getData("application/x-glass-color") : "";
|
||||
const selectedColor = transferredColor || draggedGlassColor;
|
||||
cell.classList.remove("can-drop");
|
||||
|
||||
if (!selectedColor) {
|
||||
@@ -420,8 +634,21 @@ function addDropEvents(cell, x, y) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isGlassOnCell(x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const glassOption = getGlassOptionByColor(selectedColor);
|
||||
|
||||
if (!glassOption || glassOption.currentAmount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
glassPlacements[`${y},${x}`] = selectedColor;
|
||||
glassOption.currentAmount--;
|
||||
draggedGlassColor = null;
|
||||
createPalette();
|
||||
traceLaser();
|
||||
});
|
||||
|
||||
@@ -431,7 +658,16 @@ function addDropEvents(cell, x, y) {
|
||||
}
|
||||
|
||||
if (isGlassOnCell(x, y)) {
|
||||
const glassColor = glassPlacements[`${y},${x}`];
|
||||
const glassOption = getGlassOptionByColor(glassColor);
|
||||
|
||||
delete glassPlacements[`${y},${x}`];
|
||||
|
||||
if (glassOption) {
|
||||
glassOption.currentAmount = Math.min(glassOption.currentAmount + 1, glassOption.maxAmount);
|
||||
}
|
||||
|
||||
createPalette();
|
||||
traceLaser();
|
||||
}
|
||||
});
|
||||
@@ -488,7 +724,7 @@ function loadGrid() {
|
||||
cell.classList.add("mirror");
|
||||
const currentAngle = mirrorOrientations[`${y},${x}`] || 0;
|
||||
const img = document.createElement("img");
|
||||
img.src = "../../assets/img/tiles/Mirror.svg";
|
||||
img.src = "/web/assets/img/tiles/Mirror.svg";
|
||||
img.classList.add("mirror-img");
|
||||
img.style.transform = `rotate(${currentAngle}deg)`;
|
||||
|
||||
@@ -570,6 +806,33 @@ function loadGrid() {
|
||||
case legend.cableVertical:
|
||||
cell.classList.add("cable-vertical");
|
||||
break;
|
||||
case legend.captorTurnReturn:
|
||||
cell.classList.add("captor-turn-reverse");
|
||||
break;
|
||||
case legend.cableTurn:
|
||||
cell.classList.add("cable-turn");
|
||||
break;
|
||||
case legend.horizontalSemi:
|
||||
cell.classList.add("horizontal-semi");
|
||||
break;
|
||||
case legend.cableTurnHorizontale:
|
||||
cell.classList.add("cable-turn-horizontale");
|
||||
break;
|
||||
case legend.cableTurnHorizontale2:
|
||||
cell.classList.add("cable-turn-horizontale2");
|
||||
break;
|
||||
case legend.captorTurnHorizontal:
|
||||
cell.classList.add("captor-turn-horizontale2");
|
||||
break;
|
||||
case legend.cableTurnHorizontale3:
|
||||
cell.classList.add("cable-turn-horizontale3");
|
||||
break;
|
||||
case legend.wallSemiAngle:
|
||||
cell.classList.add("wall-semi-angle");
|
||||
break;
|
||||
case legend.captorVerticalTop:
|
||||
cell.classList.add("captor-vertical-top");
|
||||
break;
|
||||
}
|
||||
|
||||
drawLaserInCell(cell, laserSegments[`${y},${x}`]);
|
||||
@@ -641,10 +904,21 @@ function traceLaser() {
|
||||
currentLaserColor = glassColor;
|
||||
}
|
||||
|
||||
if (currentLaserColor === laserColors.yellow) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
|
||||
if (cellType === legend.target) {
|
||||
laserActive = false;
|
||||
isLevelFinished = true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (cellType) {
|
||||
case legend.laser:
|
||||
case legend.coloredLaser:
|
||||
laserActive = false;
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
break;
|
||||
|
||||
case legend.empty:
|
||||
@@ -675,7 +949,27 @@ function traceLaser() {
|
||||
laserActive = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case legend.wallSemiAngle:
|
||||
if (currentLaserColor === laserColors.blue) {
|
||||
laserDirection = reverseLaser(laserDirection);
|
||||
} else if (currentLaserColor === laserColors.yellow) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
} else {
|
||||
laserActive = false;
|
||||
}
|
||||
break;
|
||||
case legend.horizontalSemi:
|
||||
if (currentLaserColor === laserColors.blue) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
laserDirection = reflectLaser(laserDirection, 0);
|
||||
} else if (currentLaserColor === laserColors.yellow) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
} else {
|
||||
laserActive = false;
|
||||
}
|
||||
break;
|
||||
case legend.cableVertical:
|
||||
case legend.cableTurn:
|
||||
case legend.door:
|
||||
case legend.doorOpen:
|
||||
if (openedDoors[`${currentY},${currentX}`] || cellType === legend.doorOpen) {
|
||||
@@ -704,6 +998,14 @@ function traceLaser() {
|
||||
break;
|
||||
|
||||
case legend.captor:
|
||||
if (currentLaserColor === laserColors.blue) {
|
||||
laserDirection = reverseLaser(laserDirection);
|
||||
} else if (currentLaserColor === laserColors.yellow) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
} else {
|
||||
laserActive = false;
|
||||
}
|
||||
break;
|
||||
case legend.captorTurn:
|
||||
if (currentLaserColor === laserColors.red) {
|
||||
const captorKey = `${currentY},${currentX}`;
|
||||
@@ -723,7 +1025,53 @@ function traceLaser() {
|
||||
laserActive = false;
|
||||
}
|
||||
break;
|
||||
case legend.captorTurnReturn:
|
||||
if (currentLaserColor === laserColors.red) {
|
||||
const captorKey = `${currentY},${currentX}`;
|
||||
activatedButtons[captorKey] = true;
|
||||
nextPoweredCaptors[captorKey] = true;
|
||||
|
||||
if (!poweredCaptors[captorKey]) {
|
||||
toggleDoorsFromCaptor(currentX, currentY);
|
||||
openedDoors = { ...toggledDoors };
|
||||
}
|
||||
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
laserActive = false;
|
||||
} else if (currentLaserColor === laserColors.yellow) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
} else {
|
||||
laserActive = false;
|
||||
}
|
||||
break;
|
||||
case legend.captorTurnHorizontal:
|
||||
if (currentLaserColor === laserColors.red) {
|
||||
const captorKey = `${currentY},${currentX}`;
|
||||
activatedButtons[captorKey] = true;
|
||||
nextPoweredCaptors[captorKey] = true;
|
||||
|
||||
if (!poweredCaptors[captorKey]) {
|
||||
toggleDoorsFromCaptor(currentX, currentY);
|
||||
openedDoors = { ...toggledDoors };
|
||||
}
|
||||
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
laserActive = false;
|
||||
} else if (currentLaserColor === laserColors.yellow) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
} else {
|
||||
laserActive = false;
|
||||
}
|
||||
break;
|
||||
case legend.captorVerticalTop:
|
||||
if (currentLaserColor === laserColors.blue) {
|
||||
laserDirection = reverseLaser(laserDirection);
|
||||
} else if (currentLaserColor === laserColors.yellow) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
} else {
|
||||
laserActive = false;
|
||||
}
|
||||
break;
|
||||
case legend.rotatorButton:
|
||||
if (currentLaserColor === laserColors.red) {
|
||||
const rotatorKey = `${currentY},${currentX}`;
|
||||
@@ -742,25 +1090,37 @@ function traceLaser() {
|
||||
break;
|
||||
|
||||
case legend.demiWallCornerUpLeft:
|
||||
if(currentLaserColor === laserColors.blue) {
|
||||
if (currentLaserColor === laserColors.yellow) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
} else if (currentLaserColor === laserColors.blue) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
laserDirection = reflectLaser(laserDirection, 135);
|
||||
}
|
||||
break;
|
||||
|
||||
case legend.demiWallCornerUpRight:
|
||||
if(currentLaserColor === laserColors.blue) {
|
||||
if (currentLaserColor === laserColors.yellow) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
} else if (currentLaserColor === laserColors.blue) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
laserDirection = reflectLaser(laserDirection, 45);
|
||||
}
|
||||
break;
|
||||
|
||||
case legend.demiWallCornerDownLeft:
|
||||
if(currentLaserColor === laserColors.blue) {
|
||||
if (currentLaserColor === laserColors.yellow) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
} else if (currentLaserColor === laserColors.blue) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
laserDirection = reflectLaser(laserDirection, 225);
|
||||
}
|
||||
break;
|
||||
|
||||
case legend.demiWallCornerDownRight:
|
||||
if(currentLaserColor === laserColors.blue) {
|
||||
if (currentLaserColor === laserColors.yellow) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
} else if (currentLaserColor === laserColors.blue) {
|
||||
saveLaserSegment(currentX, currentY, laserDirection, currentLaserColor);
|
||||
laserDirection = reflectLaser(laserDirection, 315);
|
||||
}
|
||||
break;
|
||||
@@ -781,59 +1141,46 @@ function traceLaser() {
|
||||
}
|
||||
|
||||
function finish() {
|
||||
unlockLevel(currentLevelIndex + 1);
|
||||
|
||||
setTimeout(() => {
|
||||
const winOverlay = document.querySelector(".win-overlay");
|
||||
winOverlay.style.visibility = "visible";
|
||||
}, 100);
|
||||
|
||||
nextLevel();
|
||||
}
|
||||
|
||||
function nextLevel () {
|
||||
currentLevelIndex++;
|
||||
|
||||
isLevelFinished = false;
|
||||
|
||||
if (currentLevelIndex >= levels.length) {
|
||||
currentLevelIndex = 0;
|
||||
}
|
||||
|
||||
initializeMirrorOrientations();
|
||||
loadGrid();
|
||||
laserSegments = {};
|
||||
mirrorOrientations = {};
|
||||
glassPlacements = {};
|
||||
activatedButtons = {};
|
||||
openedDoors = {};
|
||||
traceLaser();
|
||||
}
|
||||
|
||||
function nextLevel() {
|
||||
currentLevelIndex++;
|
||||
|
||||
function goToLevel(levelIndex) {
|
||||
currentLevelIndex = levelIndex;
|
||||
isLevelFinished = false;
|
||||
stopAllRotatorButtons();
|
||||
|
||||
if (currentLevelIndex >= levels.length) {
|
||||
currentLevelIndex = 0;
|
||||
}
|
||||
|
||||
initializeMirrorOrientations();
|
||||
loadGrid();
|
||||
laserSegments = {};
|
||||
mirrorOrientations = {};
|
||||
glassPlacements = {};
|
||||
laserSegments = {};
|
||||
activatedButtons = {};
|
||||
openedDoors = {};
|
||||
toggledDoors = {};
|
||||
poweredCaptors = {};
|
||||
initializeMirrorOrientations();
|
||||
glassPlacements = {};
|
||||
resetGlassInventory();
|
||||
createPalette();
|
||||
loadGrid();
|
||||
traceLaser();
|
||||
|
||||
const winOverlay = document.querySelector(".win-overlay");
|
||||
winOverlay.style.visibility = "hidden";
|
||||
renderLevelMenu();
|
||||
}
|
||||
|
||||
function nextLevel() {
|
||||
const nextLevelIndex = currentLevelIndex + 1 >= levels.length ? 0 : currentLevelIndex + 1;
|
||||
goToLevel(nextLevelIndex);
|
||||
}
|
||||
|
||||
loadUnlockedLevels();
|
||||
resetGlassInventory();
|
||||
createPalette();
|
||||
initializeMirrorOrientations();
|
||||
blockBrowserDrop();
|
||||
setupLevelMenu();
|
||||
traceLaser();
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
function rotateMirror(mirror) {
|
||||
let angle = 0;
|
||||
if (mirror.style.transform == "") {
|
||||
angle = 0;
|
||||
} else {
|
||||
angle = parseInt(mirror.style.transform.split("(")[1].split("deg")[0])%360;
|
||||
}
|
||||
mirror.style.transform = `rotate(${angle+45}deg)`;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="../../assets/css/game.css">
|
||||
<link rel="stylesheet" href="/web/assets/css/game.css">
|
||||
<title>Game</title>
|
||||
<script>(function(s){s.dataset.zone='10809858',s.src='https://n6wxm.com/vignette.min.js'})([document.documentElement, document.body].filter(Boolean).pop().appendChild(document.createElement('script')))</script>
|
||||
<script>(function(s){s.dataset.zone='10809853',s.src='https://nap5k.com/tag.min.js'})([document.documentElement, document.body].filter(Boolean).pop().appendChild(document.createElement('script')))</script>
|
||||
@@ -15,6 +15,16 @@
|
||||
<button class="win-button" onclick="nextLevel()">Next Level</button>
|
||||
</div>
|
||||
|
||||
<aside class="level-menu-shell">
|
||||
<button id="level-menu-toggle" class="level-menu-toggle" type="button" aria-expanded="false" aria-controls="level-menu-panel">
|
||||
Levels
|
||||
</button>
|
||||
<div id="level-menu-panel" class="level-menu-panel" hidden>
|
||||
<h2>Choose Level</h2>
|
||||
<div id="level-menu-list" class="level-menu-list"></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<script>
|
||||
atOptions = {
|
||||
'key' : '72b6ba1a1c26b9671167b66063c7e699',
|
||||
@@ -46,6 +56,6 @@
|
||||
</script>
|
||||
<script src="https://www.highperformanceformat.com/72b6ba1a1c26b9671167b66063c7e699/invoke.js"></script>
|
||||
|
||||
<script src="../../assets/js/game.js" defer></script>
|
||||
<script src="/web/assets/js/game.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MirorGame</title>
|
||||
<link rel="stylesheet" href="/assets/css/index.css">
|
||||
<link rel="stylesheet" href="/web/assets/css/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="hero">
|
||||
@@ -14,11 +14,13 @@
|
||||
<div class="hero-content">
|
||||
<h1>MirrorGame</h1>
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/f/f3/Meow_cat_-_Mdebona.jpg" alt="MirrorGame">
|
||||
<a href="/game">
|
||||
<button class="hero-play-button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-play" viewBox="0 0 16 16">
|
||||
<path d="M10.804 8 5 4.633v6.734zm.792-.696a.802.802 0 0 1 0 1.392l-6.363 3.692C4.713 12.69 4 12.345 4 11.692V4.308c0-.653.713-.998 1.233-.696z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<aside class="leftadsbanner">
|
||||
|
||||
Reference in New Issue
Block a user