From 03ca2cbfad12b768cdd6a1d2d6f2626ef4f15ff5 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 24 Jan 2025 22:55:27 +0000 Subject: [PATCH 01/78] initial: maze game --- Cargo.toml | 17 +- LICENSE_APACHE | 201 ------------------- LICENSE_MIT | 25 --- cognitive-games | 1 + index.html | 66 +++++++ src/lib.rs | 498 +++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 571 insertions(+), 237 deletions(-) delete mode 100644 LICENSE_APACHE delete mode 100644 LICENSE_MIT create mode 160000 cognitive-games create mode 100644 index.html diff --git a/Cargo.toml b/Cargo.toml index 2383054..4ff2cc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "{{project-name}}" +name = "cognitive-games" version = "0.1.0" -authors = ["{{authors}}"] +authors = ["Umar Sharief"] edition = "2018" [lib] @@ -18,6 +18,19 @@ wasm-bindgen = "0.2.84" # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. console_error_panic_hook = { version = "0.1.7", optional = true } +js-sys = "0.3.77" +web-sys = { version = "0.3", features = [ + "Document", + "Element", + "HtmlElement", + "Window", + "MouseEvent", + "DomTokenList", + "console", + "HtmlDialogElement", + "CustomEvent", + "CustomEventInit", +]} [dev-dependencies] wasm-bindgen-test = "0.3.34" diff --git a/LICENSE_APACHE b/LICENSE_APACHE deleted file mode 100644 index 11069ed..0000000 --- a/LICENSE_APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/LICENSE_MIT b/LICENSE_MIT deleted file mode 100644 index a15266c..0000000 --- a/LICENSE_MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2018 {{authors}} - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/cognitive-games b/cognitive-games new file mode 160000 index 0000000..e395998 --- /dev/null +++ b/cognitive-games @@ -0,0 +1 @@ +Subproject commit e395998a79e6206821aeb10196e39cc48b7b77c4 diff --git a/index.html b/index.html new file mode 100644 index 0000000..5cab30b --- /dev/null +++ b/index.html @@ -0,0 +1,66 @@ + + + + + WASM Maze Game + + + +
+ Level: 1 | + Completed: 0 | + Time: 5:00 +
+
+ + + \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 3437786..ff16a84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,493 @@ -mod utils; - use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -extern "C" { - fn alert(s: &str); +use web_sys::{console, Document, Element, HtmlElement}; +use js_sys::Math; +use std::{cell::RefCell, collections::HashSet, rc::Rc}; +#[wasm_bindgen(start)] +pub fn start() { + console_error_panic_hook::set_once(); } - #[wasm_bindgen] -pub fn greet() { - alert("Hello, {{project-name}}!"); +#[derive(Clone)] +pub struct MazeGame { + // Game state + size: usize, + level: usize, + mazes_completed: usize, + + // Maze elements + walls: Vec, + current_position: (usize, usize), + key_position: (usize, usize), + door_position: (usize, usize), + visited: HashSet<(usize, usize)>, + has_key: bool, + + // Timer state + time_remaining: i32, + last_tick: f64, + + // DOM reference + document: Document, } +#[wasm_bindgen] +impl MazeGame { + #[wasm_bindgen(constructor)] + pub fn new() -> Result { + console::log_1(&"Creating new game".into()); + let window = web_sys::window().expect("no global window exists"); + let document = window.document().expect("no document exists"); + let size = 2; + let mut game = MazeGame::create_maze(size, document); + game.render()?; + game.start()?; + Ok(game) + } + + fn create_maze(size: usize, document: Document) -> MazeGame { + let mut walls = vec![false; size * size * 4]; // Start with no walls + + // Add random walls + for i in 0..walls.len() { + walls[i] = Math::random() < 0.5; + } + + // Generate door positions + let door_x = (Math::random() * (size as f64)).floor() as usize; + let door_y = (Math::random() * (size as f64)).floor() as usize; + + let mut attempts = 0; + // Generate key position that's accessible without going through door + let key_position = loop { + let key_x = (Math::random() * (size as f64)).floor() as usize; + let key_y = (Math::random() * (size as f64)).floor() as usize; + let pos = (key_x, key_y); + + // Ensure key is not at start + if pos == (0, 0) { + continue; + } + + // Create a temporary set of walls for path checking + let test_walls = walls.clone(); + let mut visited = HashSet::new(); + visited.insert((0, 0)); + + // Flood fill from start position + let mut stack = vec![(0, 0)]; + while let Some(current) = stack.pop() { + for (dx, dy) in &[(0, 1), (0, -1), (1, 0), (-1, 0)] { + let next_x = current.0 as i32 + dx; + let next_y = current.1 as i32 + dy; + + if next_x >= 0 && next_x < size as i32 && + next_y >= 0 && next_y < size as i32 { + let next = (next_x as usize, next_y as usize); + + // Skip if it's the door position + if next == (door_x, door_y) { + continue; + } + + // Check if path is not blocked by wall + let wall_idx = (current.1 * size + current.0) * 4 + + if *dx > 0 { 1 } else if *dx < 0 { 3 } + else if *dy > 0 { 2 } else { 0 }; + + if !test_walls[wall_idx] && !visited.contains(&next) { + visited.insert(next); + stack.push(next); + } + } + } + } + + // If we can reach the key position without going through door + if visited.contains(&pos) { + break pos; + } + + attempts += 1; + if attempts > 10 { + // Fallback to a safe position if random generation fails + break (1, 0); + } + }; + + + // Generate door position after key is placed + let door_position = loop { + let door_x = (Math::random() * (size as f64)).floor() as usize; + let door_y = (Math::random() * (size as f64)).floor() as usize; + let pos = (door_x, door_y); + + // Ensure door is not at start and not at key position + if pos != (0, 0) && pos != key_position { + break pos; + } + }; + + let mut game = MazeGame { + size, + walls, + current_position: (0, 0), + key_position, + door_position, + visited: HashSet::new(), + has_key: false, + level: 1, + mazes_completed: 0, + document, + time_remaining: 300, + last_tick: js_sys::Date::now() / 1000.0, + }; + + // Clear path function - ensures a 2-cell wide path + fn clear_path(walls: &mut Vec, from: (usize, usize), to: (usize, usize), size: usize) { + let mut current = from; + while current != to { + let dx = (to.0 as i32 - current.0 as i32).signum(); + let dy = (to.1 as i32 - current.1 as i32).signum(); + + // Clear both current cell's wall and neighbor's wall + if dx != 0 { + let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; + walls[wall_idx] = false; + // Clear adjacent cell's opposite wall if not at edge + if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { + let next_x = (current.0 as i32 + dx) as usize; + let adj_wall_idx = (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; + walls[adj_wall_idx] = false; + + // Always clear an escape route (up or down) + let escape_dir = if current.1 > 0 { 0 } else { 2 }; // up if not at top, down otherwise + walls[(current.1 * size + current.0) * 4 + escape_dir] = false; + if escape_dir == 0 && current.1 > 0 { + // Clear the corresponding wall in the cell above + walls[((current.1 - 1) * size + current.0) * 4 + 2] = false; + } else if escape_dir == 2 && current.1 + 1 < size { + // Clear the corresponding wall in the cell below + walls[((current.1 + 1) * size + current.0) * 4 + 0] = false; + } + } + current.0 = (current.0 as i32 + dx) as usize; + } else if dy != 0 { + let wall_idx = (current.1 * size + current.0) * 4 + if dy > 0 { 2 } else { 0 }; + walls[wall_idx] = false; + // Clear adjacent cell's opposite wall if not at edge + if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { + let next_y = (current.1 as i32 + dy) as usize; + let adj_wall_idx = (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; + walls[adj_wall_idx] = false; + } + current.1 = (current.1 as i32 + dy) as usize; + } + // Ensure escape route from the destination + let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right + for (dx, dy) in escape_dirs.iter() { + let next_x = to.0 as i32 + dx; + let next_y = to.1 as i32 + dy; + if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { + let wall_idx = (to.1 * size + to.0) * 4 + + if *dy < 0 { 0 } else if *dx > 0 { 1 } else if *dy > 0 { 2 } else { 3 }; + walls[wall_idx] = false; + } + } + } + } + + // Clear paths with extra space around them + clear_path(&mut game.walls, (0, 0), key_position, size); + clear_path(&mut game.walls, key_position, door_position, size); + + game.visited.insert((0, 0)); + game + } + pub fn render(&self) -> Result<(), JsValue> { + let maze = self.document.get_element_by_id("maze").unwrap(); + maze.set_attribute("style", &format!("grid-template-columns: repeat({}, 60px)", self.size))?; + maze.set_inner_html(""); + + for y in 0..self.size { + for x in 0..self.size { + let cell = self.create_cell(x, y)?; + maze.append_child(&cell)?; + } + } + + // Update stats + if let Some(level_el) = self.document.get_element_by_id("level") { + level_el.set_inner_html(&self.level.to_string()); + } + if let Some(completed_el) = self.document.get_element_by_id("completed") { + completed_el.set_inner_html(&self.mazes_completed.to_string()); + } + if let Some(timer_el) = self.document.get_element_by_id("timer") { + let minutes = self.time_remaining / 60; + let seconds = self.time_remaining % 60; + timer_el.set_inner_html(&format!("{}:{:02} !", minutes, seconds)); // Removed v3 suffix + } + Ok(()) + } + #[wasm_bindgen] + pub fn start(&mut self) -> Result<(), JsValue> { + let game_state = Rc::new(RefCell::new(self.clone())); + + // Add cell click handler + let click_handler = { + let game_state = game_state.clone(); + Closure::wrap(Box::new(move |event: web_sys::CustomEvent| { + if let Ok(mut game) = game_state.try_borrow_mut() { + let coords = event.detail().as_string().unwrap(); + let mut coords = coords.split(','); + let x = coords.next().unwrap().parse::().unwrap(); + let y = coords.next().unwrap().parse::().unwrap(); + + let result = game.try_move(x, y); + game.render().unwrap(); + + if let Some(window) = web_sys::window() { + match result { + -1 => window.alert_with_message("Hit a wall! Starting over.").unwrap(), + 2 => window.alert_with_message("Level complete!").unwrap(), + _ => {} + } + } + } + }) as Box) + }; + if let Some(maze_el) = self.document.get_element_by_id("maze") { + maze_el.add_event_listener_with_callback( + "cell-click", + click_handler.as_ref().unchecked_ref() + )?; + click_handler.forget(); + } + let f = { + let game_state = game_state.clone(); + Closure::wrap(Box::new(move || { + if let Ok(mut game) = game_state.try_borrow_mut() { + let now = js_sys::Date::now() / 1000.0; + let delta = (now - game.last_tick) as i32; + if delta >= 1 { + game.time_remaining -= 1; + game.last_tick = now; + + if game.time_remaining <= 0 { + // Reset maze and timer + let new_game = MazeGame::create_maze(game.size, game.document.clone()); + game.walls = new_game.walls; + game.key_position = new_game.key_position; + game.door_position = new_game.door_position; + game.reset_position(); + game.time_remaining = 300; + game.last_tick = now; + + game.render().unwrap(); + web_sys::window() + .unwrap() + .alert_with_message("Time's up! Moving to next maze...") + .unwrap(); + } + + // Update timer display + if let Some(timer_el) = game.document.get_element_by_id("timer") { + let minutes = game.time_remaining / 60; + let seconds = game.time_remaining % 60; + timer_el.set_inner_html(&format!("{}:{:02}", minutes, seconds)); + } + } + } + }) as Box) + }; + + // Set up interval timer + let window = web_sys::window().unwrap(); + console::log_1(&"Setting up interval...".into()); + let result = window.set_interval_with_callback_and_timeout_and_arguments_0( + f.as_ref().unchecked_ref(), + 1000, + ); + + match result { + Ok(_) => console::log_1(&"Interval set up successfully".into()), + Err(e) => console::log_2(&"Failed to set up interval:".into(), &e), + } + + f.forget(); + console::log_1(&"Setup complete".into()); + Ok(()) + } + fn create_cell(&self, x: usize, y: usize) -> Result { + let cell = self.document.create_element("div")?; + cell.set_class_name("cell"); + + // Add visited and current classes + if self.visited.contains(&(x, y)) { + cell.class_list().add_1("visited")?; + } + if (x, y) == self.current_position { + cell.class_list().add_1("current")?; + } + + // Add key and door symbols - only one instance of the key should exist + if (x, y) == self.key_position && !self.has_key { + cell.set_inner_html("πŸ”‘"); + } else if (x, y) == self.current_position && self.has_key { + cell.set_inner_html("πŸ”‘"); + } else if (x, y) == self.door_position { + cell.set_inner_html("πŸšͺ"); + } + + // Add chevrons for adjacent cells + if self.is_adjacent(x, y) { + let chevron = self.document.create_element("span")?; + chevron.set_class_name("chevron"); + let direction = match (x as i32 - self.current_position.0 as i32, + y as i32 - self.current_position.1 as i32) { + (1, 0) => "right", + (-1, 0) => "left", + (0, 1) => "down", + (0, -1) => "up", + _ => unreachable!() + }; + chevron.class_list().add_1(direction)?; + chevron.set_text_content(Some("β€Ί")); + cell.append_child(&chevron)?; + } + + let x = x.clone(); + let y = y.clone(); + let click_callback = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| { + if let Some(window) = web_sys::window() { + if let Some(doc) = window.document() { + if let Some(maze_el) = doc.get_element_by_id("maze") { + let event_init = web_sys::CustomEventInit::new(); + event_init.set_detail(&JsValue::from_str(&format!("{},{}", x, y))); + let event = web_sys::CustomEvent::new_with_event_init_dict( + "cell-click", + &event_init + ).unwrap(); + maze_el.dispatch_event(&event).unwrap(); + } + } + } + }) as Box); + let cell_element: &HtmlElement = cell.dyn_ref().unwrap(); + cell_element.set_onclick(Some(click_callback.as_ref().unchecked_ref())); + click_callback.forget(); + Ok(cell) + } + fn is_adjacent(&self, x: usize, y: usize) -> bool { + let current_x = self.current_position.0; + let current_y = self.current_position.1; + + // Check if target position is adjacent (up, down, left, right) + let dx = if x >= current_x { x - current_x } else { current_x - x }; + let dy = if y >= current_y { y - current_y } else { current_y - y }; + + // Only one coordinate can change by 1, the other must be 0 + (dx == 1 && dy == 0) || (dx == 0 && dy == 1) + } + + fn get_wall_index(&self, from_x: usize, from_y: usize, to_x: usize, to_y: usize) -> usize { + let cell_walls = 4; // each cell has 4 possible walls + let base_index = (from_y * self.size + from_x) * cell_walls; + + if to_x > from_x { + base_index + 1 // right wall + } else if to_x < from_x { + base_index + 3 // left wall + } else if to_y > from_y { + base_index + 2 // bottom wall + } else { + base_index + 0 // top wall + } + } + + fn level_up(&mut self) { + self.size += 1; + self.level += 1; + self.mazes_completed = 0; + + // Create new maze with increased size + let new_game = MazeGame::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.current_position = (0, 0); + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.visited.clear(); + self.visited.insert((0, 0)); + self.has_key = false; + self.render().expect("Failed to render new level"); + } + + #[wasm_bindgen] + pub fn try_move(&mut self, x: usize, y: usize) -> i32 { + if !self.is_adjacent(x, y) { + return 0; // Invalid move + } + + // Check for wall + let wall_idx = self.get_wall_index( + self.current_position.0, + self.current_position.1, + x, + y + ); + if self.walls[wall_idx] { + self.reset_position(); + return -1; // Hit wall + } + + // Update position + self.current_position = (x, y); + self.visited.insert((x, y)); + + // Check for key + if (x, y) == self.key_position { + self.has_key = true; + } + + // Check win condition + if (x, y) == self.door_position && self.has_key { + self.mazes_completed += 1; + + if self.mazes_completed >= (self.size - 1).pow(2) { + self.level_up(); + } else { + // New maze - reset timer and maze + self.reset(); + } + return 2; // Won + } + 1 // Valid move + } + #[wasm_bindgen] + pub fn reset(&mut self) { + let new_game = MazeGame::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.reset_position(); + + // Reset timer state completely + self.time_remaining = 300; + self.last_tick = js_sys::Date::now() / 1000.0; + + // Force timer display update + if let Some(timer_el) = self.document.get_element_by_id("timer") { + timer_el.set_inner_html("5:00"); + } + + // Update display + self.render().expect("Failed to render reset"); + } + fn reset_position(&mut self) { + self.current_position = (0, 0); + self.visited.clear(); + self.visited.insert((0, 0)); + self.has_key = false; + self.render().expect("Failed to render position reset"); + } + // Additional helper methods... +} \ No newline at end of file From ef95ee1ffff304da853902cd7a39c3e8c46ce493 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 24 Jan 2025 23:03:05 +0000 Subject: [PATCH 02/78] remove unnecessary --- cognitive-games | 1 - 1 file changed, 1 deletion(-) delete mode 160000 cognitive-games diff --git a/cognitive-games b/cognitive-games deleted file mode 160000 index e395998..0000000 --- a/cognitive-games +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e395998a79e6206821aeb10196e39cc48b7b77c4 From 48b30be7bb1246fef3c6ba9b358ac7deccefeba9 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:06:01 +0000 Subject: [PATCH 03/78] prepare for cloudflare pages --- src/lib.rs | 289 ++++++++++++++++++++++++++++---------------------- wrangler.toml | 11 ++ 2 files changed, 171 insertions(+), 129 deletions(-) create mode 100644 wrangler.toml diff --git a/src/lib.rs b/src/lib.rs index ff16a84..7eccc29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ -use wasm_bindgen::prelude::*; -use web_sys::{console, Document, Element, HtmlElement}; use js_sys::Math; use std::{cell::RefCell, collections::HashSet, rc::Rc}; +use wasm_bindgen::prelude::*; +use web_sys::{console, Document, Element, HtmlElement}; #[wasm_bindgen(start)] pub fn start() { console_error_panic_hook::set_once(); @@ -13,7 +13,7 @@ pub struct MazeGame { size: usize, level: usize, mazes_completed: usize, - + // Maze elements walls: Vec, current_position: (usize, usize), @@ -21,16 +21,16 @@ pub struct MazeGame { door_position: (usize, usize), visited: HashSet<(usize, usize)>, has_key: bool, - + // Timer state time_remaining: i32, last_tick: f64, - + // DOM reference document: Document, } #[wasm_bindgen] -impl MazeGame { +impl MazeGame { #[wasm_bindgen(constructor)] pub fn new() -> Result { console::log_1(&"Creating new game".into()); @@ -44,88 +44,93 @@ impl MazeGame { } fn create_maze(size: usize, document: Document) -> MazeGame { - let mut walls = vec![false; size * size * 4]; // Start with no walls - + let mut walls = vec![false; size * size * 4]; // Start with no walls + // Add random walls for i in 0..walls.len() { walls[i] = Math::random() < 0.5; } - + // Generate door positions let door_x = (Math::random() * (size as f64)).floor() as usize; let door_y = (Math::random() * (size as f64)).floor() as usize; - + let mut attempts = 0; - // Generate key position that's accessible without going through door - let key_position = loop { - let key_x = (Math::random() * (size as f64)).floor() as usize; - let key_y = (Math::random() * (size as f64)).floor() as usize; - let pos = (key_x, key_y); - - // Ensure key is not at start - if pos == (0, 0) { - continue; - } + // Generate key position that's accessible without going through door + let key_position = loop { + let key_x = (Math::random() * (size as f64)).floor() as usize; + let key_y = (Math::random() * (size as f64)).floor() as usize; + let pos = (key_x, key_y); - // Create a temporary set of walls for path checking - let test_walls = walls.clone(); - let mut visited = HashSet::new(); - visited.insert((0, 0)); - - // Flood fill from start position - let mut stack = vec![(0, 0)]; - while let Some(current) = stack.pop() { - for (dx, dy) in &[(0, 1), (0, -1), (1, 0), (-1, 0)] { - let next_x = current.0 as i32 + dx; - let next_y = current.1 as i32 + dy; - - if next_x >= 0 && next_x < size as i32 && - next_y >= 0 && next_y < size as i32 { - let next = (next_x as usize, next_y as usize); - - // Skip if it's the door position - if next == (door_x, door_y) { - continue; - } - - // Check if path is not blocked by wall - let wall_idx = (current.1 * size + current.0) * 4 + - if *dx > 0 { 1 } else if *dx < 0 { 3 } - else if *dy > 0 { 2 } else { 0 }; - - if !test_walls[wall_idx] && !visited.contains(&next) { - visited.insert(next); - stack.push(next); + // Ensure key is not at start + if pos == (0, 0) { + continue; + } + + // Create a temporary set of walls for path checking + let test_walls = walls.clone(); + let mut visited = HashSet::new(); + visited.insert((0, 0)); + + // Flood fill from start position + let mut stack = vec![(0, 0)]; + while let Some(current) = stack.pop() { + for (dx, dy) in &[(0, 1), (0, -1), (1, 0), (-1, 0)] { + let next_x = current.0 as i32 + dx; + let next_y = current.1 as i32 + dy; + + if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { + let next = (next_x as usize, next_y as usize); + + // Skip if it's the door position + if next == (door_x, door_y) { + continue; + } + + // Check if path is not blocked by wall + let wall_idx = (current.1 * size + current.0) * 4 + + if *dx > 0 { + 1 + } else if *dx < 0 { + 3 + } else if *dy > 0 { + 2 + } else { + 0 + }; + + if !test_walls[wall_idx] && !visited.contains(&next) { + visited.insert(next); + stack.push(next); + } } } } - } - - // If we can reach the key position without going through door - if visited.contains(&pos) { - break pos; - } - - attempts += 1; - if attempts > 10 { - // Fallback to a safe position if random generation fails - break (1, 0); - } - }; - + // If we can reach the key position without going through door + if visited.contains(&pos) { + break pos; + } + + attempts += 1; + if attempts > 10 { + // Fallback to a safe position if random generation fails + break (1, 0); + } + }; + // Generate door position after key is placed let door_position = loop { let door_x = (Math::random() * (size as f64)).floor() as usize; let door_y = (Math::random() * (size as f64)).floor() as usize; let pos = (door_x, door_y); - + // Ensure door is not at start and not at key position if pos != (0, 0) && pos != key_position { break pos; } }; - + let mut game = MazeGame { size, walls, @@ -140,14 +145,19 @@ impl MazeGame { time_remaining: 300, last_tick: js_sys::Date::now() / 1000.0, }; - + // Clear path function - ensures a 2-cell wide path - fn clear_path(walls: &mut Vec, from: (usize, usize), to: (usize, usize), size: usize) { + fn clear_path( + walls: &mut Vec, + from: (usize, usize), + to: (usize, usize), + size: usize, + ) { let mut current = from; while current != to { let dx = (to.0 as i32 - current.0 as i32).signum(); let dy = (to.1 as i32 - current.1 as i32).signum(); - + // Clear both current cell's wall and neighbor's wall if dx != 0 { let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; @@ -155,9 +165,10 @@ impl MazeGame { // Clear adjacent cell's opposite wall if not at edge if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { let next_x = (current.0 as i32 + dx) as usize; - let adj_wall_idx = (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; + let adj_wall_idx = + (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; walls[adj_wall_idx] = false; - + // Always clear an escape route (up or down) let escape_dir = if current.1 > 0 { 0 } else { 2 }; // up if not at top, down otherwise walls[(current.1 * size + current.0) * 4 + escape_dir] = false; @@ -176,35 +187,47 @@ impl MazeGame { // Clear adjacent cell's opposite wall if not at edge if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { let next_y = (current.1 as i32 + dy) as usize; - let adj_wall_idx = (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; + let adj_wall_idx = + (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; walls[adj_wall_idx] = false; } current.1 = (current.1 as i32 + dy) as usize; } - // Ensure escape route from the destination - let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right - for (dx, dy) in escape_dirs.iter() { - let next_x = to.0 as i32 + dx; - let next_y = to.1 as i32 + dy; - if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { - let wall_idx = (to.1 * size + to.0) * 4 + - if *dy < 0 { 0 } else if *dx > 0 { 1 } else if *dy > 0 { 2 } else { 3 }; - walls[wall_idx] = false; - } - } + // Ensure escape route from the destination + let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right + for (dx, dy) in escape_dirs.iter() { + let next_x = to.0 as i32 + dx; + let next_y = to.1 as i32 + dy; + if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { + let wall_idx = (to.1 * size + to.0) * 4 + + if *dy < 0 { + 0 + } else if *dx > 0 { + 1 + } else if *dy > 0 { + 2 + } else { + 3 + }; + walls[wall_idx] = false; + } + } } } - + // Clear paths with extra space around them clear_path(&mut game.walls, (0, 0), key_position, size); clear_path(&mut game.walls, key_position, door_position, size); - + game.visited.insert((0, 0)); game } - pub fn render(&self) -> Result<(), JsValue> { + pub fn render(&self) -> Result<(), JsValue> { let maze = self.document.get_element_by_id("maze").unwrap(); - maze.set_attribute("style", &format!("grid-template-columns: repeat({}, 60px)", self.size))?; + maze.set_attribute( + "style", + &format!("grid-template-columns: repeat({}, 60px)", self.size), + )?; maze.set_inner_html(""); for y in 0..self.size { @@ -225,13 +248,13 @@ impl MazeGame { let minutes = self.time_remaining / 60; let seconds = self.time_remaining % 60; timer_el.set_inner_html(&format!("{}:{:02} !", minutes, seconds)); // Removed v3 suffix - } + } Ok(()) } #[wasm_bindgen] pub fn start(&mut self) -> Result<(), JsValue> { let game_state = Rc::new(RefCell::new(self.clone())); - + // Add cell click handler let click_handler = { let game_state = game_state.clone(); @@ -241,13 +264,15 @@ impl MazeGame { let mut coords = coords.split(','); let x = coords.next().unwrap().parse::().unwrap(); let y = coords.next().unwrap().parse::().unwrap(); - + let result = game.try_move(x, y); game.render().unwrap(); - + if let Some(window) = web_sys::window() { match result { - -1 => window.alert_with_message("Hit a wall! Starting over.").unwrap(), + -1 => window + .alert_with_message("Hit a wall! Starting over.") + .unwrap(), 2 => window.alert_with_message("Level complete!").unwrap(), _ => {} } @@ -258,7 +283,7 @@ impl MazeGame { if let Some(maze_el) = self.document.get_element_by_id("maze") { maze_el.add_event_listener_with_callback( "cell-click", - click_handler.as_ref().unchecked_ref() + click_handler.as_ref().unchecked_ref(), )?; click_handler.forget(); } @@ -271,7 +296,7 @@ impl MazeGame { if delta >= 1 { game.time_remaining -= 1; game.last_tick = now; - + if game.time_remaining <= 0 { // Reset maze and timer let new_game = MazeGame::create_maze(game.size, game.document.clone()); @@ -281,14 +306,14 @@ impl MazeGame { game.reset_position(); game.time_remaining = 300; game.last_tick = now; - + game.render().unwrap(); web_sys::window() .unwrap() .alert_with_message("Time's up! Moving to next maze...") .unwrap(); } - + // Update timer display if let Some(timer_el) = game.document.get_element_by_id("timer") { let minutes = game.time_remaining / 60; @@ -299,7 +324,7 @@ impl MazeGame { } }) as Box) }; - + // Set up interval timer let window = web_sys::window().unwrap(); console::log_1(&"Setting up interval...".into()); @@ -307,12 +332,12 @@ impl MazeGame { f.as_ref().unchecked_ref(), 1000, ); - + match result { Ok(_) => console::log_1(&"Interval set up successfully".into()), Err(e) => console::log_2(&"Failed to set up interval:".into(), &e), } - + f.forget(); console::log_1(&"Setup complete".into()); Ok(()) @@ -320,7 +345,7 @@ impl MazeGame { fn create_cell(&self, x: usize, y: usize) -> Result { let cell = self.document.create_element("div")?; cell.set_class_name("cell"); - + // Add visited and current classes if self.visited.contains(&(x, y)) { cell.class_list().add_1("visited")?; @@ -328,7 +353,7 @@ impl MazeGame { if (x, y) == self.current_position { cell.class_list().add_1("current")?; } - + // Add key and door symbols - only one instance of the key should exist if (x, y) == self.key_position && !self.has_key { cell.set_inner_html("πŸ”‘"); @@ -336,19 +361,21 @@ impl MazeGame { cell.set_inner_html("πŸ”‘"); } else if (x, y) == self.door_position { cell.set_inner_html("πŸšͺ"); - } - + } + // Add chevrons for adjacent cells if self.is_adjacent(x, y) { let chevron = self.document.create_element("span")?; chevron.set_class_name("chevron"); - let direction = match (x as i32 - self.current_position.0 as i32, - y as i32 - self.current_position.1 as i32) { + let direction = match ( + x as i32 - self.current_position.0 as i32, + y as i32 - self.current_position.1 as i32, + ) { (1, 0) => "right", (-1, 0) => "left", (0, 1) => "down", (0, -1) => "up", - _ => unreachable!() + _ => unreachable!(), }; chevron.class_list().add_1(direction)?; chevron.set_text_content(Some("β€Ί")); @@ -356,7 +383,7 @@ impl MazeGame { } let x = x.clone(); - let y = y.clone(); + let y = y.clone(); let click_callback = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| { if let Some(window) = web_sys::window() { if let Some(doc) = window.document() { @@ -365,8 +392,9 @@ impl MazeGame { event_init.set_detail(&JsValue::from_str(&format!("{},{}", x, y))); let event = web_sys::CustomEvent::new_with_event_init_dict( "cell-click", - &event_init - ).unwrap(); + &event_init, + ) + .unwrap(); maze_el.dispatch_event(&event).unwrap(); } } @@ -380,11 +408,19 @@ impl MazeGame { fn is_adjacent(&self, x: usize, y: usize) -> bool { let current_x = self.current_position.0; let current_y = self.current_position.1; - + // Check if target position is adjacent (up, down, left, right) - let dx = if x >= current_x { x - current_x } else { current_x - x }; - let dy = if y >= current_y { y - current_y } else { current_y - y }; - + let dx = if x >= current_x { + x - current_x + } else { + current_x - x + }; + let dy = if y >= current_y { + y - current_y + } else { + current_y - y + }; + // Only one coordinate can change by 1, the other must be 0 (dx == 1 && dy == 0) || (dx == 0 && dy == 1) } @@ -392,7 +428,7 @@ impl MazeGame { fn get_wall_index(&self, from_x: usize, from_y: usize, to_x: usize, to_y: usize) -> usize { let cell_walls = 4; // each cell has 4 possible walls let base_index = (from_y * self.size + from_x) * cell_walls; - + if to_x > from_x { base_index + 1 // right wall } else if to_x < from_x { @@ -408,7 +444,7 @@ impl MazeGame { self.size += 1; self.level += 1; self.mazes_completed = 0; - + // Create new maze with increased size let new_game = MazeGame::create_maze(self.size, self.document.clone()); self.walls = new_game.walls; @@ -426,32 +462,27 @@ impl MazeGame { if !self.is_adjacent(x, y) { return 0; // Invalid move } - + // Check for wall - let wall_idx = self.get_wall_index( - self.current_position.0, - self.current_position.1, - x, - y - ); + let wall_idx = self.get_wall_index(self.current_position.0, self.current_position.1, x, y); if self.walls[wall_idx] { self.reset_position(); return -1; // Hit wall } - + // Update position self.current_position = (x, y); self.visited.insert((x, y)); - + // Check for key if (x, y) == self.key_position { self.has_key = true; } - + // Check win condition if (x, y) == self.door_position && self.has_key { self.mazes_completed += 1; - + if self.mazes_completed >= (self.size - 1).pow(2) { self.level_up(); } else { @@ -469,16 +500,16 @@ impl MazeGame { self.key_position = new_game.key_position; self.door_position = new_game.door_position; self.reset_position(); - + // Reset timer state completely self.time_remaining = 300; self.last_tick = js_sys::Date::now() / 1000.0; - + // Force timer display update if let Some(timer_el) = self.document.get_element_by_id("timer") { timer_el.set_inner_html("5:00"); } - + // Update display self.render().expect("Failed to render reset"); } @@ -490,4 +521,4 @@ impl MazeGame { self.render().expect("Failed to render position reset"); } // Additional helper methods... -} \ No newline at end of file +} diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..99cbacb --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,11 @@ +name = "wasm-pack-template" +compatibility_date = "2024-01-25" + +[build] +command = "curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh && wasm-pack build --target web" +output_directory = "pkg"name = "wasm-pack-template" +compatibility_date = "2024-01-25" + +[build] +command = "curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh && wasm-pack build --target web" +output_directory = "pkg" \ No newline at end of file From 4da49e45373137c2ddca17c2c36c7d483dd132a0 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Sat, 25 Jan 2025 12:38:06 +0000 Subject: [PATCH 04/78] make index.html move to `pkg` on build --- index.html | 2 +- wrangler.toml | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 wrangler.toml diff --git a/index.html b/index.html index 5cab30b..cff4835 100644 --- a/index.html +++ b/index.html @@ -52,7 +52,7 @@
- - \ No newline at end of file diff --git a/pkg/index.html b/pkg/index.html new file mode 100644 index 0000000..6aaf255 --- /dev/null +++ b/pkg/index.html @@ -0,0 +1,107 @@ + + + + + + + WASM Maze Game + + + + +
+ Level: 1 | + Completed: 0 | + Time: 5:00 +
+
+ + + + \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 705a54d..0d73327 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,8 @@ use web_sys::{console, Document, Element, HtmlElement}; #[wasm_bindgen(start)] pub fn start() { console_error_panic_hook::set_once(); + let game = MazeGame::new().expect("Failed to create game"); + game.render().expect("Failed to render game"); } #[wasm_bindgen] #[derive(Clone, Serialize, Deserialize)] From 75187e7390ec6339c20860a4d2883b244734036e Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 31 Jan 2025 23:18:45 +0000 Subject: [PATCH 09/78] use webpack --- .appveyor.yml | 11 - .github/dependabot.yml | 8 - .gitignore | 9 +- .travis.yml | 69 - Cargo.lock | 360 +++ Cargo.toml | 54 +- README.md | 92 +- js/index.js | 1 + package-lock.json | 5116 ++++++++++++++++++++++++++++++++++++ package.json | 18 + src/lib.rs | 16 +- src/utils.rs | 10 - {pkg => static}/index.html | 6 +- tests/app.rs | 35 + tests/web.rs | 13 - webpack.config.js | 34 + 16 files changed, 5641 insertions(+), 211 deletions(-) delete mode 100644 .appveyor.yml delete mode 100644 .github/dependabot.yml delete mode 100644 .travis.yml create mode 100644 Cargo.lock create mode 100644 js/index.js create mode 100644 package-lock.json create mode 100644 package.json delete mode 100644 src/utils.rs rename {pkg => static}/index.html (95%) create mode 100644 tests/app.rs delete mode 100644 tests/web.rs create mode 100644 webpack.config.js diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 50910bd..0000000 --- a/.appveyor.yml +++ /dev/null @@ -1,11 +0,0 @@ -install: - - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - - if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly - - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - - rustc -V - - cargo -V - -build: false - -test_script: - - cargo test --locked diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 7377d37..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: 2 -updates: -- package-ecosystem: cargo - directory: "/" - schedule: - interval: daily - time: "08:00" - open-pull-requests-limit: 10 diff --git a/.gitignore b/.gitignore index 4e30131..33a3be3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ +node_modules +/dist /target -**/*.rs.bk -Cargo.lock -bin/ -pkg/ -wasm-pack.log +/pkg +/wasm-pack.log diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7a91325..0000000 --- a/.travis.yml +++ /dev/null @@ -1,69 +0,0 @@ -language: rust -sudo: false - -cache: cargo - -matrix: - include: - - # Builds with wasm-pack. - - rust: beta - env: RUST_BACKTRACE=1 - addons: - firefox: latest - chrome: stable - before_script: - - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) - - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) - - cargo install-update -a - - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f - script: - - cargo generate --git . --name testing - # Having a broken Cargo.toml (in that it has curlies in fields) anywhere - # in any of our parent dirs is problematic. - - mv Cargo.toml Cargo.toml.tmpl - - cd testing - - wasm-pack build - - wasm-pack test --chrome --firefox --headless - - # Builds on nightly. - - rust: nightly - env: RUST_BACKTRACE=1 - before_script: - - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) - - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) - - cargo install-update -a - - rustup target add wasm32-unknown-unknown - script: - - cargo generate --git . --name testing - - mv Cargo.toml Cargo.toml.tmpl - - cd testing - - cargo check - - cargo check --target wasm32-unknown-unknown - - cargo check --no-default-features - - cargo check --target wasm32-unknown-unknown --no-default-features - - cargo check --no-default-features --features console_error_panic_hook - - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook - - cargo check --no-default-features --features "console_error_panic_hook wee_alloc" - - cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc" - - # Builds on beta. - - rust: beta - env: RUST_BACKTRACE=1 - before_script: - - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) - - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) - - cargo install-update -a - - rustup target add wasm32-unknown-unknown - script: - - cargo generate --git . --name testing - - mv Cargo.toml Cargo.toml.tmpl - - cd testing - - cargo check - - cargo check --target wasm32-unknown-unknown - - cargo check --no-default-features - - cargo check --target wasm32-unknown-unknown --no-default-features - - cargo check --no-default-features --features console_error_panic_hook - - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook - # Note: no enabling the `wee_alloc` feature here because it requires - # nightly for now. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e60e55c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,360 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cognitive-games" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "futures", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", + "wee_alloc", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", +] + +[[package]] +name = "futures" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2 1.0.93", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.38", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.38", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2 1.0.93", + "quote 1.0.38", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83420b37346c311b9ed822af41ec2e82839bfe99867ec6c54e2da43b7538771c" +dependencies = [ + "cfg-if 0.1.10", + "futures", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote 1.0.38", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.38", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d9693b63a742d481c7f80587e057920e568317b2806988c59cd71618bc26c1" +dependencies = [ + "console_error_panic_hook", + "futures", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.2.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0789dac148a8840bbcf9efe13905463b733fa96543bfbf263790535c11af7ba5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 019f6f9..342229e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,43 +1,53 @@ [package] name = "cognitive-games" +description = "Brain training games with Rust and WebAssembly" version = "0.1.0" authors = ["Umar Sharief"] +categories = ["wasm"] +readme = "README.md" edition = "2018" [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] + +[profile.release] +lto = true [features] -default = ["console_error_panic_hook"] +# If you uncomment this line, it will enable `wee_alloc`: +#default = ["wee_alloc"] [dependencies] -wasm-bindgen = "0.2.84" +wasm-bindgen = "0.2.45" +js-sys = "0.3.22" +wee_alloc = { version = "0.4.2", optional = true } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde-wasm-bindgen = "0.5" -# The `console_error_panic_hook` crate provides better debugging of panics by -# logging them with `console.error`. This is great for development, but requires -# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for -# code size when deploying. -console_error_panic_hook = { version = "0.1.7", optional = true } -js-sys = "0.3.77" -web-sys = { version = "0.3", features = [ +[dependencies.web-sys] +version = "0.3.22" +features = [ + "console", "Document", "Element", "HtmlElement", "Window", + "Event", + "EventTarget", + "KeyboardEvent", "MouseEvent", - "DomTokenList", - "console", - "HtmlDialogElement", - "CustomEvent", + "CustomEvent", "CustomEventInit", "Storage", -]} -serde = { version = "1.0", features = ["derive"] } -serde-wasm-bindgen = "0.5" + "DomTokenList", + "Performance" +] -[dev-dependencies] -wasm-bindgen-test = "0.3.34" +[target."cfg(debug_assertions)".dependencies] +console_error_panic_hook = "0.1.5" -[profile.release] -# Tell `rustc` to optimize for small code size. -opt-level = "s" +[dev-dependencies] +wasm-bindgen-test = "0.2.45" +futures = "0.1.27" +wasm-bindgen-futures = "0.3.22" diff --git a/README.md b/README.md index 6b68408..680f386 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,48 @@ -
+## How to install -

wasm-pack-template

- - A template for kick starting a Rust and WebAssembly project using wasm-pack. - -

- Build Status -

- -

- Tutorial - | - Chat -

- - Built with πŸ¦€πŸ•Έ by The Rust and WebAssembly Working Group -
- -## About - -[**πŸ“š Read this template tutorial! πŸ“š**][template-docs] - -This template is designed for compiling Rust libraries into WebAssembly and -publishing the resulting package to NPM. - -Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other -templates and usages of `wasm-pack`. - -[tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html -[template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html - -## 🚴 Usage - -### πŸ‘ Use `cargo generate` to Clone this Template +```sh +npm install +``` -[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate) +## How to run in debug mode -``` -cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project -cd my-project +```sh +# Builds the project and opens it in a new browser tab. Auto-reloads when the project changes. +npm start ``` -### πŸ› οΈ Build with `wasm-pack build` +## How to build in release mode -``` -wasm-pack build +```sh +# Builds the project and places it into the `dist` folder. +npm run build ``` -### πŸ”¬ Test in Headless Browsers with `wasm-pack test` +## How to run unit tests -``` -wasm-pack test --headless --firefox -``` +```sh +# Runs tests in Firefox +npm test -- --firefox -### 🎁 Publish to NPM with `wasm-pack publish` +# Runs tests in Chrome +npm test -- --chrome -``` -wasm-pack publish +# Runs tests in Safari +npm test -- --safari ``` -## πŸ”‹ Batteries Included +## What does each file do? -* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating - between WebAssembly and JavaScript. -* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) - for logging panic messages to the developer console. -* `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you +* `Cargo.toml` contains the standard Rust metadata. You put your Rust dependencies in here. You must change this file with your details (name, description, version, authors, categories) -## License +* `package.json` contains the standard npm metadata. You put your JavaScript dependencies in here. You must change this file with your details (author, name, version) -Licensed under either of +* `webpack.config.js` contains the Webpack configuration. You shouldn't need to change this, unless you have very special needs. -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +* The `js` folder contains your JavaScript code (`index.js` is used to hook everything into Webpack, you don't need to change it). -at your option. +* The `src` folder contains your Rust code. -### Contribution +* The `static` folder contains any files that you want copied as-is into the final build. It contains an `index.html` file which loads the `index.js` file. -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the Apache-2.0 -license, shall be dual licensed as above, without any additional terms or -conditions. +* The `tests` folder contains your Rust unit tests. diff --git a/js/index.js b/js/index.js new file mode 100644 index 0000000..9db81ab --- /dev/null +++ b/js/index.js @@ -0,0 +1 @@ +import("../pkg/index.js").catch(console.error); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1507dfb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5116 @@ +{ + "name": "rust-webpack-template", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rust-webpack-template", + "version": "0.1.0", + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "^1.1.0", + "copy-webpack-plugin": "^5.0.3", + "rimraf": "^3.0.0", + "webpack": "^5.97.1", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.1.tgz", + "integrity": "sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.13.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.0.tgz", + "integrity": "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", + "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@wasm-tool/wasm-pack-plugin": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@wasm-tool/wasm-pack-plugin/-/wasm-pack-plugin-1.7.0.tgz", + "integrity": "sha512-WikzYsw7nTd5CZxH75h7NxM/FLJAgqfWt+/gk3EL3wYKxiIlpMIYPja+sHQl3ARiicIYy4BDfxkbAVjRYlouTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.1", + "command-exists": "^1.2.7", + "watchpack": "^2.1.1", + "which": "^2.0.2" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": ">=5.0.0" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true, + "license": "ISC" + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "node_modules/cacache/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001696", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", + "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", + "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "node_modules/copy-concurrently/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz", + "integrity": "sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacache": "^12.0.3", + "find-cache-dir": "^2.1.0", + "glob-parent": "^3.1.0", + "globby": "^7.1.1", + "is-glob": "^4.0.1", + "loader-utils": "^1.2.3", + "minimatch": "^3.0.4", + "normalize-path": "^3.0.0", + "p-limit": "^2.2.1", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "webpack-log": "^2.0.0" + }, + "engines": { + "node": ">= 6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cyclist": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.2.tgz", + "integrity": "sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.90", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.90.tgz", + "integrity": "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==", + "dev": true, + "license": "ISC" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", + "deprecated": "This module is no longer supported.", + "dev": true, + "license": "ISC" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz", + "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz", + "integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", + "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.53.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", + "integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "node_modules/move-concurrently/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==", + "dev": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.1.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/spdy-transport/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/spdy/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/spdy/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "figgy-pudding": "^3.5.1" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", + "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webpack": { + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.0.tgz", + "integrity": "sha512-90SqqYXA2SK36KcT6o1bvwvZfJFcmoamqeJY7+boioffX9g9C0wjjJRGUrQIuh43pb0ttX7+ssavmj/WN2RHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.21.2", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.7", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-server/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/webpack-log/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..730de69 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "author": "You ", + "name": "rust-webpack-template", + "version": "0.1.0", + "scripts": { + "build": "rimraf dist pkg && webpack", + "start": "rimraf dist pkg && webpack-dev-server --open", + "test": "cargo test && wasm-pack test --headless" + }, + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "^1.1.0", + "copy-webpack-plugin": "^5.0.3", + "rimraf": "^3.0.0", + "webpack": "^5.97.1", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.0" + } +} diff --git a/src/lib.rs b/src/lib.rs index 0d73327..362f2e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,11 +3,18 @@ use serde::{Deserialize, Serialize}; use std::{cell::RefCell, collections::HashSet, rc::Rc}; use wasm_bindgen::prelude::*; use web_sys::{console, Document, Element, HtmlElement}; + +#[cfg(feature = "wee_alloc")] +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + #[wasm_bindgen(start)] -pub fn start() { +pub fn main_js() -> Result<(), JsValue> { + #[cfg(debug_assertions)] console_error_panic_hook::set_once(); - let game = MazeGame::new().expect("Failed to create game"); - game.render().expect("Failed to render game"); + let game = MazeGame::new()?; + game.render()?; + Ok(()) } #[wasm_bindgen] #[derive(Clone, Serialize, Deserialize)] @@ -537,5 +544,4 @@ impl MazeGame { self.has_key = false; self.render().expect("Failed to render position reset"); } - // Additional helper methods... -} +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index b1d7929..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub fn set_panic_hook() { - // When the `console_error_panic_hook` feature is enabled, we can call the - // `set_panic_hook` function at least once during initialization, and then - // we will get better error messages if our code ever panics. - // - // For more details see - // https://github.com/rustwasm/console_error_panic_hook#readme - #[cfg(feature = "console_error_panic_hook")] - console_error_panic_hook::set_once(); -} diff --git a/pkg/index.html b/static/index.html similarity index 95% rename from pkg/index.html rename to static/index.html index 6aaf255..4b77829 100644 --- a/pkg/index.html +++ b/static/index.html @@ -91,6 +91,7 @@ +
Level: 1 | @@ -98,10 +99,7 @@ Time: 5:00
- + \ No newline at end of file diff --git a/tests/app.rs b/tests/app.rs new file mode 100644 index 0000000..9223aa3 --- /dev/null +++ b/tests/app.rs @@ -0,0 +1,35 @@ +use wasm_bindgen_test::{wasm_bindgen_test_configure, wasm_bindgen_test}; +use futures::prelude::*; +use wasm_bindgen::JsValue; +use wasm_bindgen_futures::JsFuture; + +wasm_bindgen_test_configure!(run_in_browser); + + +// This runs a unit test in native Rust, so it can only use Rust APIs. +#[test] +fn rust_test() { + assert_eq!(1, 1); +} + + +// This runs a unit test in the browser, so it can use browser APIs. +#[wasm_bindgen_test] +fn web_test() { + assert_eq!(1, 1); +} + + +// This runs a unit test in the browser, and in addition it supports asynchronous Future APIs. +#[wasm_bindgen_test(async)] +fn async_test() -> impl Future { + // Creates a JavaScript Promise which will asynchronously resolve with the value 42. + let promise = js_sys::Promise::resolve(&JsValue::from(42)); + + // Converts that Promise into a Future. + // The unit test will wait for the Future to resolve. + JsFuture::from(promise) + .map(|x| { + assert_eq!(x, 42); + }) +} diff --git a/tests/web.rs b/tests/web.rs deleted file mode 100644 index de5c1da..0000000 --- a/tests/web.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Test suite for the Web and headless browsers. - -#![cfg(target_arch = "wasm32")] - -extern crate wasm_bindgen_test; -use wasm_bindgen_test::*; - -wasm_bindgen_test_configure!(run_in_browser); - -#[wasm_bindgen_test] -fn pass() { - assert_eq!(1 + 1, 2); -} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..234e9d6 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,34 @@ +const path = require("path"); +const CopyPlugin = require("copy-webpack-plugin"); +const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); + +const dist = path.resolve(__dirname, "dist"); + +module.exports = { + mode: "production", + entry: { + index: "./js/index.js" + }, + output: { + path: dist, + filename: "[name].js" + }, + experiments: { + asyncWebAssembly: true + }, + devServer: { + static: { + directory: dist + }, + open: true + }, + plugins: [ + new CopyPlugin([ + path.resolve(__dirname, "static") + ]), + + new WasmPackPlugin({ + crateDirectory: __dirname, + }), + ] +}; \ No newline at end of file From 682f4c5029ce6f6cf40d0d9073bb4a7b8bfbbc04 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Sat, 1 Feb 2025 18:15:24 +0000 Subject: [PATCH 10/78] refactor config --- package-lock.json | 31 +++++++++++++------------------ package.json | 7 ++++--- webpack.config.js | 35 ++++++++++++----------------------- 3 files changed, 29 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1507dfb..4898bc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,9 @@ "name": "rust-webpack-template", "version": "0.1.0", "devDependencies": { + "@types/copy-webpack-plugin": "^8.0.1", "@wasm-tool/wasm-pack-plugin": "^1.1.0", "copy-webpack-plugin": "^5.0.3", - "rimraf": "^3.0.0", "webpack": "^5.97.1", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.0" @@ -196,6 +196,18 @@ "@types/node": "*" } }, + "node_modules/@types/copy-webpack-plugin": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@types/copy-webpack-plugin/-/copy-webpack-plugin-8.0.1.tgz", + "integrity": "sha512-TwEeGse0/wq+t3SFW0DEwroMS/cDkwVZT+vj7tMAYTp7llt/yz6NuW2n04X2M5P/kSfBQOORhrHAN2mqZdmybg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "tapable": "^2.0.0", + "webpack": "^5.1.0" + } + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -3679,23 +3691,6 @@ "node": ">= 4" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/run-applescript": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", diff --git a/package.json b/package.json index 730de69..ff32511 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,15 @@ "name": "rust-webpack-template", "version": "0.1.0", "scripts": { - "build": "rimraf dist pkg && webpack", - "start": "rimraf dist pkg && webpack-dev-server --open", + "build": "webpack --mode=production", + "start": "webpack serve --mode development", "test": "cargo test && wasm-pack test --headless" }, + "type": "module", "devDependencies": { + "@types/copy-webpack-plugin": "^8.0.1", "@wasm-tool/wasm-pack-plugin": "^1.1.0", "copy-webpack-plugin": "^5.0.3", - "rimraf": "^3.0.0", "webpack": "^5.97.1", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.0" diff --git a/webpack.config.js b/webpack.config.js index 234e9d6..ed4d3fe 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,34 +1,23 @@ -const path = require("path"); -const CopyPlugin = require("copy-webpack-plugin"); -const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); - -const dist = path.resolve(__dirname, "dist"); - -module.exports = { - mode: "production", - entry: { - index: "./js/index.js" - }, +import { resolve } from "path"; +import CopyPlugin from "copy-webpack-plugin"; +import WasmPackPlugin from "@wasm-tool/wasm-pack-plugin"; +export default { + entry: "./js/index.js", output: { - path: dist, - filename: "[name].js" + path: resolve("dist"), + filename: "index.js" }, experiments: { asyncWebAssembly: true }, devServer: { - static: { - directory: dist - }, - open: true + static: "dist", + open: true, }, plugins: [ - new CopyPlugin([ - path.resolve(__dirname, "static") - ]), - + new CopyPlugin(["static"]), new WasmPackPlugin({ - crateDirectory: __dirname, + crateDirectory: ".", }), ] -}; \ No newline at end of file +} \ No newline at end of file From 2c1c5045cf191d3d797f938e4d63220a953ae7a1 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Sat, 1 Feb 2025 22:26:51 +0000 Subject: [PATCH 11/78] fix warnings | fix positioning & orientation of pointers (a replacement for chevrons) --- Cargo.toml | 12 +- LICENSE | 0 package-lock.json | 1329 +++++++++------------------------------------ package.json | 2 +- src/lib.rs | 40 +- static/index.html | 70 ++- webpack.config.js | 7 +- 7 files changed, 355 insertions(+), 1105 deletions(-) create mode 100644 LICENSE diff --git a/Cargo.toml b/Cargo.toml index 342229e..5fb2d22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ authors = ["Umar Sharief"] categories = ["wasm"] readme = "README.md" edition = "2018" +license = "MIT" +repository = "https://github.com/noneofyourbusiness1415252/cognitive-games" # Add repository [lib] crate-type = ["cdylib"] @@ -14,8 +16,8 @@ crate-type = ["cdylib"] lto = true [features] -# If you uncomment this line, it will enable `wee_alloc`: -#default = ["wee_alloc"] +default = ["console_error_panic_hook"] +wee_alloc = ["dep:wee_alloc"] [dependencies] wasm-bindgen = "0.2.45" @@ -24,6 +26,7 @@ wee_alloc = { version = "0.4.2", optional = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde-wasm-bindgen = "0.5" +console_error_panic_hook = { version = "0.1.5", optional = true } [dependencies.web-sys] version = "0.3.22" @@ -44,10 +47,7 @@ features = [ "Performance" ] -[target."cfg(debug_assertions)".dependencies] -console_error_panic_hook = "0.1.5" - [dev-dependencies] wasm-bindgen-test = "0.2.45" futures = "0.1.27" -wasm-bindgen-futures = "0.3.22" +wasm-bindgen-futures = "0.3.22" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json index 4898bc7..dfa5287 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "devDependencies": { "@types/copy-webpack-plugin": "^8.0.1", "@wasm-tool/wasm-pack-plugin": "^1.1.0", - "copy-webpack-plugin": "^5.0.3", + "copy-webpack-plugin": "^12.0.2", "webpack": "^5.97.1", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.0" @@ -154,6 +154,57 @@ "dev": true, "license": "MIT" }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -690,16 +741,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": ">=5.0.0" - } - }, "node_modules/ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", @@ -752,16 +793,6 @@ "ajv": "^6.9.1" } }, - "node_modules/ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -802,43 +833,6 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true, - "license": "ISC" - }, - "node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -846,16 +840,6 @@ "dev": true, "license": "MIT" }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -869,13 +853,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true, - "license": "MIT" - }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -928,17 +905,6 @@ "multicast-dns": "^7.2.5" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1018,44 +984,6 @@ "node": ">= 0.8" } }, - "node_modules/cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "node_modules/cacache/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", @@ -1161,13 +1089,6 @@ "node": ">= 6" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, - "license": "ISC" - }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -1231,13 +1152,6 @@ "dev": true, "license": "MIT" }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true, - "license": "MIT" - }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -1291,29 +1205,6 @@ ], "license": "MIT" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", @@ -1385,65 +1276,29 @@ "dev": true, "license": "MIT" }, - "node_modules/copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "node_modules/copy-concurrently/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/copy-webpack-plugin": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz", - "integrity": "sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", "dev": true, "license": "MIT", "dependencies": { - "cacache": "^12.0.3", - "find-cache-dir": "^2.1.0", - "glob-parent": "^3.1.0", - "globby": "^7.1.1", - "is-glob": "^4.0.1", - "loader-utils": "^1.2.3", - "minimatch": "^3.0.4", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", "normalize-path": "^3.0.0", - "p-limit": "^2.2.1", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "webpack-log": "^2.0.0" + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" }, "engines": { - "node": ">= 6.9.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "webpack": "^5.1.0" } }, "node_modules/core-util-is": { @@ -1468,13 +1323,6 @@ "node": ">= 8" } }, - "node_modules/cyclist": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.2.tgz", - "integrity": "sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==", - "dev": true, - "license": "MIT" - }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -1556,19 +1404,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -1597,19 +1432,6 @@ "node": ">= 0.4" } }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1624,16 +1446,6 @@ "dev": true, "license": "ISC" }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1644,16 +1456,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enhanced-resolve": { "version": "5.18.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", @@ -1920,6 +1722,36 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -1954,6 +1786,16 @@ "node": ">= 4.9.1" } }, + "node_modules/fastq": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", + "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -1967,14 +1809,6 @@ "node": ">=0.8.0" } }, - "node_modules/figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "deprecated": "This module is no longer supported.", - "dev": true, - "license": "ISC" - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2007,34 +1841,6 @@ "node": ">= 0.8" } }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -2045,17 +1851,6 @@ "flat": "cli.js" } }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -2097,38 +1892,6 @@ "node": ">= 0.6" } }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2193,50 +1956,17 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.0" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" } }, "node_modules/glob-to-regexp": { @@ -2247,21 +1977,24 @@ "license": "BSD-2-Clause" }, "node_modules/globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", "dev": true, "license": "MIT", "dependencies": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/gopd": { @@ -2434,19 +2167,15 @@ "node": ">=0.10.0" } }, - "node_modules/iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "license": "MIT" - }, - "node_modules/ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 4" + } }, "node_modules/import-local": { "version": "3.2.0", @@ -2531,35 +2260,6 @@ "node": ">=8" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true, - "license": "ISC" - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2818,19 +2518,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -2862,69 +2549,6 @@ "node": ">=6.11.5" } }, - "node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2982,6 +2606,16 @@ "dev": true, "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3059,94 +2693,6 @@ "dev": true, "license": "ISC" }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "node_modules/move-concurrently/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3255,16 +2801,6 @@ "node": ">= 0.8" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/open": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", @@ -3300,19 +2836,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/p-retry": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", @@ -3341,18 +2864,6 @@ "node": ">=6" } }, - "node_modules/parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3363,33 +2874,6 @@ "node": ">= 0.8" } }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3415,16 +2899,16 @@ "license": "MIT" }, "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", "dev": true, "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/picocolors": { @@ -3447,29 +2931,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -3477,13 +2938,6 @@ "dev": true, "license": "MIT" }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true, - "license": "ISC" - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3498,40 +2952,6 @@ "node": ">= 0.10" } }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3542,6 +2962,27 @@ "node": ">=6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -3691,6 +3132,17 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/run-applescript": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", @@ -3704,14 +3156,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==", + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, - "license": "ISC", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { - "aproba": "^1.1.1" + "queue-microtask": "^1.2.2" } }, "node_modules/safe-buffer": { @@ -3729,20 +3195,62 @@ "license": "MIT" }, "node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 4" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" } }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -3758,20 +3266,10 @@ "license": "MIT", "dependencies": { "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "node-forge": "^1" + }, + "engines": { + "node": ">=10" } }, "node_modules/send": { @@ -3817,9 +3315,9 @@ "license": "MIT" }, "node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4044,13 +3542,16 @@ } }, "node_modules/slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/sockjs": { @@ -4183,16 +3684,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ssri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", - "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "figgy-pudding": "^3.5.1" - } - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -4203,24 +3694,6 @@ "node": ">= 0.8" } }, - "node_modules/stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", - "dev": true, - "license": "MIT" - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -4321,73 +3794,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", - "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/thingies": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", @@ -4401,17 +3807,6 @@ "tslib": "^2" } }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -4480,13 +3875,6 @@ "node": ">= 0.6" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true, - "license": "MIT" - }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -4494,24 +3882,17 @@ "dev": true, "license": "MIT" }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/unpipe": { @@ -4756,63 +4137,6 @@ } } }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", - "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/webpack-dev-server": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.0.tgz", @@ -4870,36 +4194,6 @@ } } }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-server/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, "node_modules/webpack-dev-server/node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -4910,58 +4204,6 @@ "node": ">= 10" } }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", - "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-log/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/webpack-merge": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", @@ -5054,13 +4296,6 @@ "dev": true, "license": "MIT" }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -5082,30 +4317,6 @@ "optional": true } } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" } } } diff --git a/package.json b/package.json index ff32511..08312b4 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "devDependencies": { "@types/copy-webpack-plugin": "^8.0.1", "@wasm-tool/wasm-pack-plugin": "^1.1.0", - "copy-webpack-plugin": "^5.0.3", + "copy-webpack-plugin": "^12.0.2", "webpack": "^5.97.1", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.0" diff --git a/src/lib.rs b/src/lib.rs index 362f2e0..d9836f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -371,6 +371,26 @@ impl MazeGame { } if (x, y) == self.current_position { cell.class_list().add_1("current")?; + for (dx, dy) in [(1, 0), (-1, 0), (0, 1), (0, -1)].iter() { + let next_x = (x as i32 + dx) as usize; + let next_y = (y as i32 + dy) as usize; + + // Check if the adjacent cell is within bounds and accessible + if next_x < self.size && next_y < self.size && self.is_adjacent(next_x, next_y) { + let pointer = self.document.create_element("span")?; + pointer.set_class_name("pointer"); + let direction = match (*dx, *dy) { + (1, 0) => "right", + (-1, 0) => "left", + (0, 1) => "down", + (0, -1) => "up", + _ => unreachable!(), + }; + pointer.class_list().add_1(direction)?; + pointer.set_text_content(Some("⏢")); + cell.append_child(&pointer)?; + } + } } // Add key and door symbols - only one instance of the key should exist @@ -381,26 +401,6 @@ impl MazeGame { } else if (x, y) == self.door_position { cell.set_inner_html("πŸšͺ"); } - - // Add chevrons for adjacent cells - if self.is_adjacent(x, y) { - let chevron = self.document.create_element("span")?; - chevron.set_class_name("chevron"); - let direction = match ( - x as i32 - self.current_position.0 as i32, - y as i32 - self.current_position.1 as i32, - ) { - (1, 0) => "right", - (-1, 0) => "left", - (0, 1) => "down", - (0, -1) => "up", - _ => unreachable!(), - }; - chevron.class_list().add_1(direction)?; - chevron.set_text_content(Some("β€Ί")); - cell.append_child(&chevron)?; - } - let x = x.clone(); let y = y.clone(); let click_callback = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| { diff --git a/static/index.html b/static/index.html index 4b77829..cade016 100644 --- a/static/index.html +++ b/static/index.html @@ -17,7 +17,7 @@ --key-color: #704214; --door-color: #4a2308; } - + /* Dark theme - pure black */ @media (prefers-color-scheme: dark) { :root { @@ -31,18 +31,21 @@ --door-color: #5c4423; } } - + body { background-color: var(--bg-color); color: var(--text-color); } - + + /* Replace the existing grid and pointer styles with: */ + /* Replace the existing grid and pointer styles with: */ .grid { display: grid; - gap: 0.125rem; + gap: 0; + /* Remove gap between cells */ margin: 1.25rem auto; } - + .cell { width: var(--cell-size); height: var(--cell-size); @@ -55,34 +58,65 @@ position: relative; cursor: pointer; } - + .visited { background-color: var(--visited-color); } - + .current { background-color: var(--current-color); } - - .chevron { + + .pointer { position: absolute; + font-size: calc(var(--cell-size) * 0.4); + line-height: 1; color: var(--chevron-color); - font-size: calc(var(--cell-size) * 0.33); + transform-origin: center; + pointer-events: none; + z-index: 1; + } + + .pointer.right { + transform: rotate(90deg); + left: calc(100%); + /* Align with border */ + top: 50%; + translate: 0 -50%; } - - .chevron.up { top: 0.3125rem; } - .chevron.down { bottom: 0.3125rem; } - .chevron.left { left: 0.3125rem; } - .chevron.right { right: 0.3125rem; } - + + .pointer.left { + transform: rotate(-90deg); + right: calc(100%); + /* Align with border */ + top: 50%; + translate: 0 -50%; + } + + .pointer.down { + transform: rotate(180deg); + top: calc(100%); + /* Align with border */ + left: 50%; + translate: -50% 0; + } + + .pointer.up { + transform: rotate(0deg); + bottom: calc(100%); + /* Align with border */ + left: 50%; + translate: -50% 0; + } + .key { color: var(--key-color); } - + .door { color: var(--door-color); } - + #stats { color: var(--text-color); margin: 1rem; diff --git a/webpack.config.js b/webpack.config.js index ed4d3fe..95a8185 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,9 +13,14 @@ export default { devServer: { static: "dist", open: true, + port: 80, }, plugins: [ - new CopyPlugin(["static"]), + new CopyPlugin({ + patterns: [ + { from: "static" }, + ] + }), new WasmPackPlugin({ crateDirectory: ".", }), From d72385818e9572ce01915855222f371297e98ecf Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Sat, 1 Feb 2025 23:55:35 +0000 Subject: [PATCH 12/78] feat(ui): replace HTML pointer elements with CSS pseudo-elements - Remove individual pointer HTML elements - Add CSS pseudo-elements for all 4 directions using ::before, ::after on cell and span - Fix z-index and positioning to show pointers above cells - Align pointer bases exactly with cell borders - Add clip-path to grid to hide out-of-bounds pointers --- src/lib.rs | 25 +++++---------------- static/index.html | 56 +++++++++++++++++++++++------------------------ 2 files changed, 33 insertions(+), 48 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d9836f5..71f0243 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -370,26 +370,11 @@ impl MazeGame { cell.class_list().add_1("visited")?; } if (x, y) == self.current_position { - cell.class_list().add_1("current")?; - for (dx, dy) in [(1, 0), (-1, 0), (0, 1), (0, -1)].iter() { - let next_x = (x as i32 + dx) as usize; - let next_y = (y as i32 + dy) as usize; - - // Check if the adjacent cell is within bounds and accessible - if next_x < self.size && next_y < self.size && self.is_adjacent(next_x, next_y) { - let pointer = self.document.create_element("span")?; - pointer.set_class_name("pointer"); - let direction = match (*dx, *dy) { - (1, 0) => "right", - (-1, 0) => "left", - (0, 1) => "down", - (0, -1) => "up", - _ => unreachable!(), - }; - pointer.class_list().add_1(direction)?; - pointer.set_text_content(Some("⏢")); - cell.append_child(&pointer)?; - } + if (x, y) == self.current_position { + cell.class_list().add_1("current")?; + // Add an empty span for the left/right pseudo-elements + let span = self.document.create_element("span")?; + cell.append_child(&span)?; } } diff --git a/static/index.html b/static/index.html index cade016..e0fbcb5 100644 --- a/static/index.html +++ b/static/index.html @@ -44,6 +44,8 @@ gap: 0; /* Remove gap between cells */ margin: 1.25rem auto; + position: relative; + clip-path: inset(0); } .cell { @@ -67,46 +69,44 @@ background-color: var(--current-color); } - .pointer { + .current::before, + .current::after, + .current > span::before, + .current > span::after { + content: "⏢"; position: absolute; font-size: calc(var(--cell-size) * 0.4); - line-height: 1; color: var(--chevron-color); - transform-origin: center; - pointer-events: none; + line-height: 1; z-index: 1; } - .pointer.right { - transform: rotate(90deg); - left: calc(100%); - /* Align with border */ - top: 50%; - translate: 0 -50%; + .current::before { + /* up */ + bottom: 100%; + left: 50%; + transform: translateX(-50%); } - .pointer.left { - transform: rotate(-90deg); - right: calc(100%); - /* Align with border */ - top: 50%; - translate: 0 -50%; + .current::after { + /* down */ + top: 100%; + left: 50%; + transform: translateX(-50%) rotate(180deg); } - .pointer.down { - transform: rotate(180deg); - top: calc(100%); - /* Align with border */ - left: 50%; - translate: -50% 0; + .current > span::before { + /* left */ + right: 100%; + top: 50%; + transform: translateY(-50%) rotate(-90deg); } - .pointer.up { - transform: rotate(0deg); - bottom: calc(100%); - /* Align with border */ - left: 50%; - translate: -50% 0; + .current > span::after { + /* right */ + left: 100%; + top: 50%; + transform: translateY(-50%) rotate(90deg); } .key { From ba69a50502bf05b228d3018b2d273e0939c0c5aa Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Sun, 2 Feb 2025 13:34:27 +0000 Subject: [PATCH 13/78] fix: improve DOM manipulation efficiency and event handling - Optimize cell creation by only regenerating grid when size changes - Fix type conversion for HTML collection indices (usize to u32) - Improve cell state management with proper span element handling - Implement efficient event delegation for cell clicks - Reduce unnecessary DOM updates by checking content before modification - Use text nodes for content updates to improve performance --- Cargo.toml | 4 +- src/lib.rs | 123 +++++++++++++++++++++++++++++++++++++--------- static/index.html | 8 +-- 3 files changed, 107 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5fb2d22..a25b931 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ features = [ "Document", "Element", "HtmlElement", + "HtmlCollection", "Window", "Event", "EventTarget", @@ -44,7 +45,8 @@ features = [ "CustomEventInit", "Storage", "DomTokenList", - "Performance" + "Performance", + "Text", ] [dev-dependencies] diff --git a/src/lib.rs b/src/lib.rs index 71f0243..570b80c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -246,20 +246,36 @@ impl MazeGame { } pub fn render(&self) -> Result<(), JsValue> { let maze = self.document.get_element_by_id("maze").unwrap(); - maze.set_attribute( - "style", - &format!("grid-template-columns: repeat({}, 60px)", self.size), - )?; - maze.set_inner_html(""); + // Only regenerate grid if size changed + if maze.children().length() as usize != self.size * self.size { + maze.set_attribute( + "style", + &format!("grid-template-columns: repeat({}, 60px)", self.size), + )?; + maze.set_inner_html(""); + + // Create cells only once + for _ in 0..(self.size * self.size) { + let cell = self.document.create_element("div")?; + cell.set_class_name("cell"); + let span = self.document.create_element("span")?; + cell.append_child(&span)?; + maze.append_child(&cell)?; + } + } + + // Update existing cells for y in 0..self.size { for x in 0..self.size { - let cell = self.create_cell(x, y)?; - maze.append_child(&cell)?; + let index = (y * self.size + x) as u32; // Convert to u32 for item() call + if let Some(cell) = maze.children().item(index) { + self.update_cell_state(&cell, x, y)?; + } } } - // Update stats + // Update stats (unchanged) if let Some(level_el) = self.document.get_element_by_id("level") { level_el.set_inner_html(&self.level.to_string()); } @@ -269,35 +285,96 @@ impl MazeGame { if let Some(timer_el) = self.document.get_element_by_id("timer") { let minutes = self.time_remaining / 60; let seconds = self.time_remaining % 60; - timer_el.set_inner_html(&format!("{}:{:02} !", minutes, seconds)); // Removed v3 suffix + timer_el.set_inner_html(&format!("{}:{:02}", minutes, seconds)); } Ok(()) } - #[wasm_bindgen] +fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { + // Reset base class + cell.set_class_name("cell"); + + // Update state classes + if self.visited.contains(&(x, y)) { + cell.class_list().add_1("visited")?; + } + if (x, y) == self.current_position { + cell.class_list().add_1("current")?; + // Ensure span exists for pseudo-elements + if cell.children().length() == 0 { + let span = self.document.create_element("span")?; + cell.append_child(&span)?; + } + } else { + // Remove span if not current position + while cell.children().length() > 0 { + if let Some(child) = cell.first_child() { + cell.remove_child(&child)?; + } + } + } + + // Update content only if needed + let content = if (x, y) == self.key_position && !self.has_key { + "πŸ”‘" + } else if (x, y) == self.current_position && self.has_key { + "πŸ”‘" + } else if (x, y) == self.door_position { + "πŸšͺ" + } else { + "" + }; + + if cell.inner_html() != content { + cell.set_inner_html(content); + // Re-add span if this is current position (since inner_html clears children) + if (x, y) == self.current_position { + let span = self.document.create_element("span")?; + cell.append_child(&span)?; + } + } + + Ok(()) +} #[wasm_bindgen] pub fn start(&mut self) -> Result<(), JsValue> { let game_state = Rc::new(RefCell::new(self.clone())); - // Add cell click handler let click_handler = { let game_state = game_state.clone(); - Closure::wrap(Box::new(move |event: web_sys::CustomEvent| { + Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { if let Ok(mut game) = game_state.try_borrow_mut() { - let coords = event.detail().as_string().unwrap(); - let mut coords = coords.split(','); - let x = coords.next().unwrap().parse::().unwrap(); - let y = coords.next().unwrap().parse::().unwrap(); - - // Add the try_move call and only render on valid moves - let result = game.try_move(x, y); - if result != 0 { - game.render().unwrap(); + if let Some(target) = event.target() { + if let Some(element) = target.dyn_ref::() { + if let Ok(Some(maze_el)) = element.closest("#maze") { + // Find clicked cell index + let children = maze_el.children(); + let cell_index = (0..children.length()) + .find(|&i| { + children + .item(i) + .map(|cell| cell.is_same_node(Some(element))) + .unwrap_or(false) + }) + .unwrap_or(0) + as usize; + + let size = game.size; + let x = cell_index % size; + let y = cell_index / size; + + let result = game.try_move(x, y); + if result != 0 { + game.render().unwrap(); + } + } + } } } }) as Box) }; + // Attach single click handler to maze container if let Some(maze_el) = self.document.get_element_by_id("maze") { maze_el.add_event_listener_with_callback( - "cell-click", + "click", click_handler.as_ref().unchecked_ref(), )?; click_handler.forget(); @@ -529,4 +606,4 @@ impl MazeGame { self.has_key = false; self.render().expect("Failed to render position reset"); } -} \ No newline at end of file +} diff --git a/static/index.html b/static/index.html index e0fbcb5..200e37b 100644 --- a/static/index.html +++ b/static/index.html @@ -13,7 +13,7 @@ --text-color: #808080; --visited-color: #262626; --current-color: #404040; - --chevron-color: #4d4d4d; + --pointer-color: #4d4d4d; --key-color: #704214; --door-color: #4a2308; } @@ -26,7 +26,7 @@ --text-color: #787878; --visited-color: #0a0a0a; --current-color: #1a1a1a; - --chevron-color: #404040; + --pointer-color: #404040; --key-color: #8b6b43; --door-color: #5c4423; } @@ -74,10 +74,10 @@ .current > span::before, .current > span::after { content: "⏢"; + pointer-events: none; position: absolute; font-size: calc(var(--cell-size) * 0.4); - color: var(--chevron-color); - line-height: 1; + color: var(--pointer-color); z-index: 1; } From 756c9a4d2a4c0b13d1bf52e3a66b9e50da4390f0 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Sun, 2 Feb 2025 13:43:01 +0000 Subject: [PATCH 14/78] eradicate innerHTML --- src/lib.rs | 154 ++++++++++++++++++----------------------------------- 1 file changed, 52 insertions(+), 102 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 570b80c..7b32d95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ use js_sys::Math; use serde::{Deserialize, Serialize}; use std::{cell::RefCell, collections::HashSet, rc::Rc}; use wasm_bindgen::prelude::*; -use web_sys::{console, Document, Element, HtmlElement}; +use web_sys::{console, Document, Element}; #[cfg(feature = "wee_alloc")] #[global_allocator] @@ -246,95 +246,93 @@ impl MazeGame { } pub fn render(&self) -> Result<(), JsValue> { let maze = self.document.get_element_by_id("maze").unwrap(); - + // Only regenerate grid if size changed if maze.children().length() as usize != self.size * self.size { maze.set_attribute( "style", &format!("grid-template-columns: repeat({}, 60px)", self.size), )?; - maze.set_inner_html(""); - + + // Clear existing content safely + while let Some(child) = maze.first_child() { + maze.remove_child(&child)?; + } + // Create cells only once for _ in 0..(self.size * self.size) { let cell = self.document.create_element("div")?; cell.set_class_name("cell"); let span = self.document.create_element("span")?; + let content = self.document.create_text_node(""); + cell.append_child(&content)?; cell.append_child(&span)?; maze.append_child(&cell)?; } } - + // Update existing cells for y in 0..self.size { for x in 0..self.size { - let index = (y * self.size + x) as u32; // Convert to u32 for item() call + let index = (y * self.size + x) as u32; if let Some(cell) = maze.children().item(index) { self.update_cell_state(&cell, x, y)?; } } } - - // Update stats (unchanged) + + // Update stats if let Some(level_el) = self.document.get_element_by_id("level") { - level_el.set_inner_html(&self.level.to_string()); + level_el.set_text_content(Some(&self.level.to_string())); } if let Some(completed_el) = self.document.get_element_by_id("completed") { - completed_el.set_inner_html(&self.mazes_completed.to_string()); + completed_el.set_text_content(Some(&self.mazes_completed.to_string())); } if let Some(timer_el) = self.document.get_element_by_id("timer") { let minutes = self.time_remaining / 60; let seconds = self.time_remaining % 60; - timer_el.set_inner_html(&format!("{}:{:02}", minutes, seconds)); + timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); } Ok(()) } -fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { - // Reset base class - cell.set_class_name("cell"); - // Update state classes - if self.visited.contains(&(x, y)) { - cell.class_list().add_1("visited")?; - } - if (x, y) == self.current_position { - cell.class_list().add_1("current")?; - // Ensure span exists for pseudo-elements - if cell.children().length() == 0 { - let span = self.document.create_element("span")?; - cell.append_child(&span)?; + fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { + // Reset base class + cell.set_class_name("cell"); + + // Update state classes + if self.visited.contains(&(x, y)) { + cell.class_list().add_1("visited")?; } - } else { - // Remove span if not current position - while cell.children().length() > 0 { - if let Some(child) = cell.first_child() { - cell.remove_child(&child)?; + if (x, y) == self.current_position { + cell.class_list().add_1("current")?; + // Ensure span exists for pseudo-elements + if cell.children().length() == 0 { + let span = self.document.create_element("span")?; + cell.append_child(&span)?; } } - } - - // Update content only if needed - let content = if (x, y) == self.key_position && !self.has_key { - "πŸ”‘" - } else if (x, y) == self.current_position && self.has_key { - "πŸ”‘" - } else if (x, y) == self.door_position { - "πŸšͺ" - } else { - "" - }; - - if cell.inner_html() != content { - cell.set_inner_html(content); - // Re-add span if this is current position (since inner_html clears children) - if (x, y) == self.current_position { - let span = self.document.create_element("span")?; - cell.append_child(&span)?; + + // Update content + let content = if (x, y) == self.key_position && !self.has_key { + "πŸ”‘" + } else if (x, y) == self.current_position && self.has_key { + "πŸ”‘" + } else if (x, y) == self.door_position { + "πŸšͺ" + } else { + "" + }; + + // Update text content if it's different + if let Some(first_child) = cell.first_child() { + if first_child.text_content().unwrap_or_default() != content { + first_child.set_text_content(Some(content)); + } } - } - - Ok(()) -} #[wasm_bindgen] + + Ok(()) + } #[wasm_bindgen] pub fn start(&mut self) -> Result<(), JsValue> { let game_state = Rc::new(RefCell::new(self.clone())); @@ -406,7 +404,7 @@ fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), Js if let Some(timer_el) = game.document.get_element_by_id("timer") { let minutes = game.time_remaining / 60; let seconds = game.time_remaining % 60; - timer_el.set_inner_html(&format!("{}:{:02}", minutes, seconds)); + timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); } game.render().unwrap_or_else(|_| { @@ -438,54 +436,6 @@ fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), Js console::log_1(&"Setup complete".into()); Ok(()) } - fn create_cell(&self, x: usize, y: usize) -> Result { - let cell = self.document.create_element("div")?; - cell.set_class_name("cell"); - - // Add visited and current classes - if self.visited.contains(&(x, y)) { - cell.class_list().add_1("visited")?; - } - if (x, y) == self.current_position { - if (x, y) == self.current_position { - cell.class_list().add_1("current")?; - // Add an empty span for the left/right pseudo-elements - let span = self.document.create_element("span")?; - cell.append_child(&span)?; - } - } - - // Add key and door symbols - only one instance of the key should exist - if (x, y) == self.key_position && !self.has_key { - cell.set_inner_html("πŸ”‘"); - } else if (x, y) == self.current_position && self.has_key { - cell.set_inner_html("πŸ”‘"); - } else if (x, y) == self.door_position { - cell.set_inner_html("πŸšͺ"); - } - let x = x.clone(); - let y = y.clone(); - let click_callback = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| { - if let Some(window) = web_sys::window() { - if let Some(doc) = window.document() { - if let Some(maze_el) = doc.get_element_by_id("maze") { - let event_init = web_sys::CustomEventInit::new(); - event_init.set_detail(&JsValue::from_str(&format!("{},{}", x, y))); - let event = web_sys::CustomEvent::new_with_event_init_dict( - "cell-click", - &event_init, - ) - .unwrap(); - maze_el.dispatch_event(&event).unwrap(); - } - } - } - }) as Box); - let cell_element: &HtmlElement = cell.dyn_ref().unwrap(); - cell_element.set_onclick(Some(click_callback.as_ref().unchecked_ref())); - click_callback.forget(); - Ok(cell) - } fn is_adjacent(&self, x: usize, y: usize) -> bool { let current_x = self.current_position.0; let current_y = self.current_position.1; @@ -593,7 +543,7 @@ fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), Js // Force timer display update if let Some(timer_el) = self.document.get_element_by_id("timer") { - timer_el.set_inner_html("5:00"); + timer_el.set_text_content(Some("5:00")); } // Update display From 3a68966b4dcdbd301dc3a08fae2386a057bdcc1a Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Wed, 5 Feb 2025 23:32:51 +0000 Subject: [PATCH 15/78] refactor: modularization (#1) * refactor: initial modularization of the codebase Split monolithic lib.rs into a more maintainable structure: - Create games/ directory for game implementations - Move Perception (Seek) game into games/perception.rs - Create utils.rs for shared functionality - Update lib.rs to re-export necessary items This refactoring sets up the foundation for: 1. Adding remaining cognitive test modules 2. Better separation of concerns 3. Easier maintenance and testing 4. More idiomatic Rust module structure * break down the Perception module further Game state management (saving/loading/creation) Rendering logic Movement and maze interaction Core game mechanics (walls, positions, etc.) * fix compilation * extracting the timer-related functionality into a new module * Create a new input.rs module for handling user interactions * Removed redundant game.render()? call since it's already done in new() --- src/games/mod.rs | 1 + src/games/perception/input.rs | 53 +++ src/games/perception/maze.rs | 156 +++++++++ src/games/perception/mod.rs | 112 +++++++ src/games/perception/movement.rs | 88 +++++ src/games/perception/render.rs | 95 ++++++ src/games/perception/state.rs | 34 ++ src/games/perception/timer.rs | 70 ++++ src/lib.rs | 554 +------------------------------ 9 files changed, 614 insertions(+), 549 deletions(-) create mode 100644 src/games/mod.rs create mode 100644 src/games/perception/input.rs create mode 100644 src/games/perception/maze.rs create mode 100644 src/games/perception/mod.rs create mode 100644 src/games/perception/movement.rs create mode 100644 src/games/perception/render.rs create mode 100644 src/games/perception/state.rs create mode 100644 src/games/perception/timer.rs diff --git a/src/games/mod.rs b/src/games/mod.rs new file mode 100644 index 0000000..17c310e --- /dev/null +++ b/src/games/mod.rs @@ -0,0 +1 @@ +pub mod perception; \ No newline at end of file diff --git a/src/games/perception/input.rs b/src/games/perception/input.rs new file mode 100644 index 0000000..d8dd3ad --- /dev/null +++ b/src/games/perception/input.rs @@ -0,0 +1,53 @@ +use super::Perception; +use std::{cell::RefCell, rc::Rc}; +use wasm_bindgen::prelude::*; +use web_sys::Element; + +impl Perception { + pub(super) fn setup_click_handler(game_state: Rc>) -> Result<(), JsValue> { + let click_handler = { + let game_state = game_state.clone(); + Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { + if let Ok(mut game) = game_state.try_borrow_mut() { + if let Some(target) = event.target() { + if let Some(element) = target.dyn_ref::() { + if let Ok(Some(maze_el)) = element.closest("#maze") { + // Find clicked cell index + let children = maze_el.children(); + let cell_index = (0..children.length()) + .find(|&i| { + children + .item(i) + .map(|cell| cell.is_same_node(Some(element))) + .unwrap_or(false) + }) + .unwrap_or(0) + as usize; + + let size = game.size; + let x = cell_index % size; + let y = cell_index / size; + + let result = game.try_move(x, y); + if result != 0 { + game.render().unwrap(); + } + } + } + } + } + }) as Box) + }; + + // Attach single click handler to maze container + if let Some(maze_el) = game_state.borrow().document.get_element_by_id("maze") { + maze_el.add_event_listener_with_callback( + "click", + click_handler.as_ref().unchecked_ref(), + )?; + click_handler.forget(); + } + + Ok(()) + } +} \ No newline at end of file diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs new file mode 100644 index 0000000..7baa441 --- /dev/null +++ b/src/games/perception/maze.rs @@ -0,0 +1,156 @@ +use std::collections::HashSet; + +use js_sys::Math; +use web_sys::Document; +use super::Perception; + +impl Perception { + pub(super) fn create_maze(size: usize, document: Document) -> Self { + let mut walls = vec![false; size * size * 4]; // Start with no walls + // Add random walls + for i in 0..walls.len() { + walls[i] = Math::random() < 0.5; + } + + let (waypoint1, key_position, waypoint2, door_position) = if size == 2 { + let key_pos = loop { + let pos = ( + (Math::random() * 2.0).floor() as usize, + (Math::random() * 2.0).floor() as usize, + ); + if pos != (0, 0) { + break pos; + }; + }; + // Get random non-start, non-key position for door + let door_pos = loop { + let pos = ( + (Math::random() * 2.0).floor() as usize, + (Math::random() * 2.0).floor() as usize, + ); + if pos != (0, 0) && pos != key_pos { + break pos; + } + }; + (key_pos, key_pos, door_pos, door_pos) + } else { + let (mut key_pos, mut door_pos); + loop { + key_pos = ( + ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, + ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, + ); + + door_pos = ( + ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, + ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, + ); + + if key_pos != door_pos && key_pos != (0, 0) && door_pos != (0, 0) { + break; + } + } + ( + ( + (Math::random() * (size as f64)).floor() as usize, + (Math::random() * (size as f64)).floor() as usize, + ), + key_pos, + ( + (Math::random() * (size as f64)).floor() as usize, + (Math::random() * (size as f64)).floor() as usize, + ), + door_pos, + ) + }; + + let mut game = Self { + size, + walls, + current_position: (0, 0), + key_position, + door_position, + visited: HashSet::new(), + has_key: false, + level: 1, + mazes_completed: 0, + document, + time_remaining: 300, + last_tick: js_sys::Date::now() / 1000.0, + }; + // Create path through waypoints + clear_path(&mut game.walls, (0, 0), waypoint1, size); + clear_path(&mut game.walls, waypoint1, key_position, size); + clear_path(&mut game.walls, key_position, waypoint2, size); + clear_path(&mut game.walls, waypoint2, door_position, size); + game.visited.insert((0, 0)); + game + } +} +fn clear_path( + walls: &mut Vec, + from: (usize, usize), + to: (usize, usize), + size: usize, +) { + let mut current = from; + // Calculate minimum required path length (Manhattan distance * 1.5) + while current != to { + let dx = (to.0 as i32 - current.0 as i32).signum(); + let dy = (to.1 as i32 - current.1 as i32).signum(); + // Clear both current cell's wall and neighbor's wall + if dx != 0 { + let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; + walls[wall_idx] = false; + // Clear adjacent cell's opposite wall if not at edge + if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { + let next_x = (current.0 as i32 + dx) as usize; + let adj_wall_idx = + (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; + walls[adj_wall_idx] = false; + + // Always clear an escape route (up or down) + let escape_dir = if current.1 > 0 { 0 } else { 2 }; // up if not at top, down otherwise + walls[(current.1 * size + current.0) * 4 + escape_dir] = false; + if escape_dir == 0 && current.1 > 0 { + // Clear the corresponding wall in the cell above + walls[((current.1 - 1) * size + current.0) * 4 + 2] = false; + } else if escape_dir == 2 && current.1 + 1 < size { + // Clear the corresponding wall in the cell below + walls[((current.1 + 1) * size + current.0) * 4 + 0] = false; + } + } + current.0 = (current.0 as i32 + dx) as usize; + } else if dy != 0 { + let wall_idx = (current.1 * size + current.0) * 4 + if dy > 0 { 2 } else { 0 }; + walls[wall_idx] = false; + // Clear adjacent cell's opposite wall if not at edge + if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { + let next_y = (current.1 as i32 + dy) as usize; + let adj_wall_idx = + (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; + walls[adj_wall_idx] = false; + } + current.1 = (current.1 as i32 + dy) as usize; + } + // Ensure escape route from the destination + let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right + for (dx, dy) in escape_dirs.iter() { + let next_x = to.0 as i32 + dx; + let next_y = to.1 as i32 + dy; + if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { + let wall_idx = (to.1 * size + to.0) * 4 + + if *dy < 0 { + 0 + } else if *dx > 0 { + 1 + } else if *dy > 0 { + 2 + } else { + 3 + }; + walls[wall_idx] = false; + } + } + } +} diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs new file mode 100644 index 0000000..ba5fdf2 --- /dev/null +++ b/src/games/perception/mod.rs @@ -0,0 +1,112 @@ +mod state; +mod render; +mod maze; +mod movement; +mod timer; +mod input; + +use serde::{Deserialize, Serialize}; +use std::{cell::RefCell, collections::HashSet, rc::Rc}; +use wasm_bindgen::prelude::*; +use web_sys::{console, Document, Element}; + +fn get_document() -> Document { + web_sys::window() + .expect("no global window exists") + .document() + .expect("no document exists") +} + +#[wasm_bindgen] +#[derive(Clone, Serialize, Deserialize)] +pub struct Perception { + // Game state + size: usize, + level: usize, + #[serde(default)] + mazes_completed: usize, + + // Maze elements + walls: Vec, + current_position: (usize, usize), + key_position: (usize, usize), + door_position: (usize, usize), + visited: HashSet<(usize, usize)>, + has_key: bool, + + // Timer state + time_remaining: i32, + last_tick: f64, + + #[serde(skip, default = "get_document")] + document: Document, +} + +#[wasm_bindgen] +impl Perception { + #[wasm_bindgen(constructor)] + pub fn new() -> Result { + let document = get_document(); + let storage = web_sys::window() + .expect("no global window exists") + .local_storage()? + .expect("no local storage"); + + let mut game = if let Some(state) = storage.get_item("maze_state")? { + let last_save = storage + .get_item("maze_time")? + .unwrap_or_else(|| "0".to_string()) + .parse::() + .unwrap_or(0.0); + + if js_sys::Date::now() - last_save > 300000.0 { + Self::create_maze(2, document) + } else { + serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)? + } + } else { + Self::create_maze(2, document) + }; + + game.render()?; + game.start()?; + Ok(game) + } + #[wasm_bindgen] + pub fn start(&mut self) -> Result<(), JsValue> { + let game_state = Rc::new(RefCell::new(self.clone())); + + Self::setup_click_handler(game_state.clone())?; + Self::setup_timer(game_state)?; + + console::log_1(&"Setup complete".into()); + Ok(()) + } + #[wasm_bindgen] + pub fn reset(&mut self) { + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.reset_position(); + + // Reset timer state completely + self.time_remaining = 300; + self.last_tick = js_sys::Date::now() / 1000.0; + + // Force timer display update + if let Some(timer_el) = self.document.get_element_by_id("timer") { + timer_el.set_text_content(Some("5:00")); + } + + // Update display + self.render().expect("Failed to render reset"); + } + fn reset_position(&mut self) { + self.current_position = (0, 0); + self.visited.clear(); + self.visited.insert((0, 0)); + self.has_key = false; + self.render().expect("Failed to render position reset"); + } +} \ No newline at end of file diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs new file mode 100644 index 0000000..6925b34 --- /dev/null +++ b/src/games/perception/movement.rs @@ -0,0 +1,88 @@ +use super::Perception; + +impl Perception { + pub(super) fn is_adjacent(&self, x: usize, y: usize) -> bool { + let current_x = self.current_position.0; + let current_y = self.current_position.1; + + // Check if target position is adjacent (up, down, left, right) + let dx = if x >= current_x { + x - current_x + } else { + current_x - x + }; + let dy = if y >= current_y { + y - current_y + } else { + current_y - y + }; + + // Only one coordinate can change by 1, the other must be 0 + (dx == 1 && dy == 0) || (dx == 0 && dy == 1) + } + + pub(super) fn get_wall_index(&self, from_x: usize, from_y: usize, to_x: usize, to_y: usize) -> usize { + let cell_walls = 4; // each cell has 4 possible walls + let base_index = (from_y * self.size + from_x) * cell_walls; + + if to_x > from_x { + base_index + 1 // right wall + } else if to_x < from_x { + base_index + 3 // left wall + } else if to_y > from_y { + base_index + 2 // bottom wall + } else { + base_index + 0 // top wall + } + } + + pub(super) fn try_move(&mut self, x: usize, y: usize) -> i32 { + if !self.is_adjacent(x, y) { + return 0; + } + + let wall_idx = self.get_wall_index(self.current_position.0, self.current_position.1, x, y); + + // Block access to door position if key not collected + if (x, y) == self.door_position && !self.has_key { + return 0; + } + + if self.walls[wall_idx] { + self.reset_position(); + return -1; + } + + self.current_position = (x, y); + self.visited.insert((x, y)); + + if (x, y) == self.key_position { + self.has_key = true; + // When key is collected, make door accessible + let door_x = self.door_position.0; + let door_y = self.door_position.1; + let base_idx = (door_y * self.size + door_x) * 4; + for i in 0..4 { + self.walls[base_idx + i] = false; + } + } + + if (x, y) == self.door_position && self.has_key { + // Simplified level up - increase size immediately + self.size += 1; + self.level += 1; + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.current_position = (0, 0); + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.visited.clear(); + self.visited.insert((0, 0)); + self.has_key = false; + self.time_remaining = 300; + self.last_tick = js_sys::Date::now() / 1000.0; + return 2; + } + 1 + } +} \ No newline at end of file diff --git a/src/games/perception/render.rs b/src/games/perception/render.rs new file mode 100644 index 0000000..52987b0 --- /dev/null +++ b/src/games/perception/render.rs @@ -0,0 +1,95 @@ +use super::Perception; +use wasm_bindgen::prelude::*; +use web_sys::Element; + +impl Perception { + pub(crate) fn render(&self) -> Result<(), JsValue> { + let maze = self.document.get_element_by_id("maze").unwrap(); + + // Only regenerate grid if size changed + if maze.children().length() as usize != self.size * self.size { + maze.set_attribute( + "style", + &format!("grid-template-columns: repeat({}, 60px)", self.size), + )?; + + // Clear existing content safely + while let Some(child) = maze.first_child() { + maze.remove_child(&child)?; + } + + // Create cells only once + for _ in 0..(self.size * self.size) { + let cell = self.document.create_element("div")?; + cell.set_class_name("cell"); + let span = self.document.create_element("span")?; + let content = self.document.create_text_node(""); + cell.append_child(&content)?; + cell.append_child(&span)?; + maze.append_child(&cell)?; + } + } + + // Update existing cells + for y in 0..self.size { + for x in 0..self.size { + let index = (y * self.size + x) as u32; + if let Some(cell) = maze.children().item(index) { + self.update_cell_state(&cell, x, y)?; + } + } + } + + // Update stats + if let Some(level_el) = self.document.get_element_by_id("level") { + level_el.set_text_content(Some(&self.level.to_string())); + } + if let Some(completed_el) = self.document.get_element_by_id("completed") { + completed_el.set_text_content(Some(&self.mazes_completed.to_string())); + } + if let Some(timer_el) = self.document.get_element_by_id("timer") { + let minutes = self.time_remaining / 60; + let seconds = self.time_remaining % 60; + timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); + } + Ok(()) + } + + fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { + // Reset base class + cell.set_class_name("cell"); + + // Update state classes + if self.visited.contains(&(x, y)) { + cell.class_list().add_1("visited")?; + } + if (x, y) == self.current_position { + cell.class_list().add_1("current")?; + // Ensure span exists for pseudo-elements + if cell.children().length() == 0 { + let span = self.document.create_element("span")?; + cell.append_child(&span)?; + } + } + + // Update content + let content = if (x, y) == self.key_position && !self.has_key { + "πŸ”‘" + } else if (x, y) == self.current_position && self.has_key { + "πŸ”‘" + } else if (x, y) == self.door_position { + "πŸšͺ" + } else { + "" + }; + + // Update text content if it's different + if let Some(first_child) = cell.first_child() { + if first_child.text_content().unwrap_or_default() != content { + first_child.set_text_content(Some(content)); + } + } + + Ok(()) + } +} \ No newline at end of file diff --git a/src/games/perception/state.rs b/src/games/perception/state.rs new file mode 100644 index 0000000..64ce695 --- /dev/null +++ b/src/games/perception/state.rs @@ -0,0 +1,34 @@ +use super::Perception; +use js_sys::Date; +use wasm_bindgen::prelude::*; +use web_sys::Storage; + +impl Perception { + pub(super) fn load_state(storage: &Storage) -> Result, JsValue> { + if let Some(state) = storage.get_item("maze_state")? { + let last_save = storage + .get_item("maze_time")? + .unwrap_or_else(|| "0".to_string()) + .parse::() + .unwrap_or(0.0); + + let game: Self = serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)?; + Ok(Some((game, last_save))) + } else { + Ok(None) + } + } + + pub(super) fn save_state(&self) -> Result<(), JsValue> { + let window = web_sys::window().expect("no global window exists"); + let storage = window.local_storage()?.expect("no local storage exists"); + + let state = serde_wasm_bindgen::to_value(&self)?; + let state_json = js_sys::JSON::stringify(&state)?.as_string().unwrap(); + storage.set_item("maze_state", &state_json)?; + storage.set_item("maze_time", &Date::now().to_string())?; + storage.set_item("maze_level", &self.level.to_string())?; + + Ok(()) + } +} \ No newline at end of file diff --git a/src/games/perception/timer.rs b/src/games/perception/timer.rs new file mode 100644 index 0000000..ef95529 --- /dev/null +++ b/src/games/perception/timer.rs @@ -0,0 +1,70 @@ +use wasm_bindgen::prelude::*; +use web_sys::{console, Document}; +use std::{rc::Rc, cell::RefCell}; +use super::Perception; + +impl Perception { + pub(super) fn setup_timer(game_state: Rc>) -> Result<(), JsValue> { + let timer_callback = { + let game_state = game_state.clone(); + Closure::wrap(Box::new(move || { + if let Ok(mut game) = game_state.try_borrow_mut() { + let now = js_sys::Date::now() / 1000.0; + let delta = (now - game.last_tick) as i32; + if delta >= 1 { + game.update_timer(now); + } + } + }) as Box) + }; + + let window = web_sys::window().unwrap(); + console::log_1(&"Setting up interval...".into()); + + let result = window.set_interval_with_callback_and_timeout_and_arguments_0( + timer_callback.as_ref().unchecked_ref(), + 1000, + ); + + match result { + Ok(_) => console::log_1(&"Interval set up successfully".into()), + Err(e) => console::log_2(&"Failed to set up interval:".into(), &e), + } + + timer_callback.forget(); + Ok(()) + } + + fn update_timer(&mut self, now: f64) { + self.time_remaining -= 1; + self.last_tick = now; + + if self.time_remaining <= 0 { + self.reset_on_timeout(now); + } + + self.update_timer_display(); + self.save_state().unwrap_or_else(|_| { + console::log_1(&"Failed to save game state".into()); + }); + } + + fn update_timer_display(&self) { + if let Some(timer_el) = self.document.get_element_by_id("timer") { + let minutes = self.time_remaining / 60; + let seconds = self.time_remaining % 60; + timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); + } + } + + fn reset_on_timeout(&mut self, now: f64) { + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.reset_position(); + self.time_remaining = 300; + self.last_tick = now; + self.render().unwrap(); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 7b32d95..d56cd55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,7 @@ -use js_sys::Math; -use serde::{Deserialize, Serialize}; -use std::{cell::RefCell, collections::HashSet, rc::Rc}; -use wasm_bindgen::prelude::*; -use web_sys::{console, Document, Element}; +mod games; +pub use games::perception::Perception; +use wasm_bindgen::{prelude::*, JsValue}; #[cfg(feature = "wee_alloc")] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; @@ -12,548 +10,6 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; pub fn main_js() -> Result<(), JsValue> { #[cfg(debug_assertions)] console_error_panic_hook::set_once(); - let game = MazeGame::new()?; - game.render()?; + Perception::new()?; Ok(()) -} -#[wasm_bindgen] -#[derive(Clone, Serialize, Deserialize)] -pub struct MazeGame { - // Game state - size: usize, - level: usize, - #[serde(default)] - mazes_completed: usize, - - // Maze elements - walls: Vec, - current_position: (usize, usize), - key_position: (usize, usize), - door_position: (usize, usize), - visited: HashSet<(usize, usize)>, - has_key: bool, - - // Timer state - time_remaining: i32, - last_tick: f64, - - #[serde(skip, default = "get_document")] - document: Document, -} - -fn get_document() -> Document { - web_sys::window() - .expect("no global window exists") - .document() - .expect("no document exists") -} -#[wasm_bindgen] -impl MazeGame { - #[wasm_bindgen(constructor)] - pub fn new() -> Result { - let document = get_document(); - let storage = web_sys::window() - .expect("no global window exists") - .local_storage()? - .expect("no local storage"); - - let mut game = if let Some(state) = storage.get_item("maze_state")? { - let last_save = storage - .get_item("maze_time")? - .unwrap_or_else(|| "0".to_string()) - .parse::() - .unwrap_or(0.0); - - if js_sys::Date::now() - last_save > 300000.0 { - MazeGame::create_maze(2, document) - } else { - serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)? - } - } else { - MazeGame::create_maze(2, document) - }; - - game.render()?; - game.start()?; - Ok(game) - } - fn save_state(&self) -> Result<(), JsValue> { - let window = web_sys::window().expect("no global window exists"); - let storage = window.local_storage()?.expect("no local storage exists"); - - // Save game state - let state = serde_wasm_bindgen::to_value(&self)?; - let state_json = js_sys::JSON::stringify(&state)?.as_string().unwrap(); - storage.set_item("maze_state", &state_json)?; - - // Save current time - storage.set_item("maze_time", &js_sys::Date::now().to_string())?; - - // Save level separately - storage.set_item("maze_level", &self.level.to_string())?; - - Ok(()) - } - fn create_maze(size: usize, document: Document) -> MazeGame { - let mut walls = vec![false; size * size * 4]; // Start with no walls - // Add random walls - for i in 0..walls.len() { - walls[i] = Math::random() < 0.5; - } - - let (waypoint1, key_position, waypoint2, door_position) = if size == 2 { - let key_pos = loop { - let pos = ( - (Math::random() * 2.0).floor() as usize, - (Math::random() * 2.0).floor() as usize, - ); - if pos != (0, 0) { - break pos; - }; - }; - // Get random non-start, non-key position for door - let door_pos = loop { - let pos = ( - (Math::random() * 2.0).floor() as usize, - (Math::random() * 2.0).floor() as usize, - ); - if pos != (0, 0) && pos != key_pos { - break pos; - } - }; - (key_pos, key_pos, door_pos, door_pos) - } else { - let (mut key_pos, mut door_pos); - loop { - key_pos = ( - ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, - ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, - ); - - door_pos = ( - ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, - ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, - ); - - if key_pos != door_pos && key_pos != (0, 0) && door_pos != (0, 0) { - break; - } - } - ( - ( - (Math::random() * (size as f64)).floor() as usize, - (Math::random() * (size as f64)).floor() as usize, - ), - key_pos, - ( - (Math::random() * (size as f64)).floor() as usize, - (Math::random() * (size as f64)).floor() as usize, - ), - door_pos, - ) - }; - - let mut game = MazeGame { - size, - walls, - current_position: (0, 0), - key_position, - door_position, - visited: HashSet::new(), - has_key: false, - level: 1, - mazes_completed: 0, - document, - time_remaining: 300, - last_tick: js_sys::Date::now() / 1000.0, - }; - - // Clear path function - ensures a 2-cell wide path - fn clear_path( - walls: &mut Vec, - from: (usize, usize), - to: (usize, usize), - size: usize, - ) { - let mut current = from; - // Calculate minimum required path length (Manhattan distance * 1.5) - while current != to { - let dx = (to.0 as i32 - current.0 as i32).signum(); - let dy = (to.1 as i32 - current.1 as i32).signum(); - // Clear both current cell's wall and neighbor's wall - if dx != 0 { - let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; - walls[wall_idx] = false; - // Clear adjacent cell's opposite wall if not at edge - if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { - let next_x = (current.0 as i32 + dx) as usize; - let adj_wall_idx = - (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; - walls[adj_wall_idx] = false; - - // Always clear an escape route (up or down) - let escape_dir = if current.1 > 0 { 0 } else { 2 }; // up if not at top, down otherwise - walls[(current.1 * size + current.0) * 4 + escape_dir] = false; - if escape_dir == 0 && current.1 > 0 { - // Clear the corresponding wall in the cell above - walls[((current.1 - 1) * size + current.0) * 4 + 2] = false; - } else if escape_dir == 2 && current.1 + 1 < size { - // Clear the corresponding wall in the cell below - walls[((current.1 + 1) * size + current.0) * 4 + 0] = false; - } - } - current.0 = (current.0 as i32 + dx) as usize; - } else if dy != 0 { - let wall_idx = (current.1 * size + current.0) * 4 + if dy > 0 { 2 } else { 0 }; - walls[wall_idx] = false; - // Clear adjacent cell's opposite wall if not at edge - if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { - let next_y = (current.1 as i32 + dy) as usize; - let adj_wall_idx = - (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; - walls[adj_wall_idx] = false; - } - current.1 = (current.1 as i32 + dy) as usize; - } - // Ensure escape route from the destination - let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right - for (dx, dy) in escape_dirs.iter() { - let next_x = to.0 as i32 + dx; - let next_y = to.1 as i32 + dy; - if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { - let wall_idx = (to.1 * size + to.0) * 4 - + if *dy < 0 { - 0 - } else if *dx > 0 { - 1 - } else if *dy > 0 { - 2 - } else { - 3 - }; - walls[wall_idx] = false; - } - } - } - } - // Create path through waypoints - clear_path(&mut game.walls, (0, 0), waypoint1, size); - clear_path(&mut game.walls, waypoint1, key_position, size); - clear_path(&mut game.walls, key_position, waypoint2, size); - clear_path(&mut game.walls, waypoint2, door_position, size); - game.visited.insert((0, 0)); - game - } - pub fn render(&self) -> Result<(), JsValue> { - let maze = self.document.get_element_by_id("maze").unwrap(); - - // Only regenerate grid if size changed - if maze.children().length() as usize != self.size * self.size { - maze.set_attribute( - "style", - &format!("grid-template-columns: repeat({}, 60px)", self.size), - )?; - - // Clear existing content safely - while let Some(child) = maze.first_child() { - maze.remove_child(&child)?; - } - - // Create cells only once - for _ in 0..(self.size * self.size) { - let cell = self.document.create_element("div")?; - cell.set_class_name("cell"); - let span = self.document.create_element("span")?; - let content = self.document.create_text_node(""); - cell.append_child(&content)?; - cell.append_child(&span)?; - maze.append_child(&cell)?; - } - } - - // Update existing cells - for y in 0..self.size { - for x in 0..self.size { - let index = (y * self.size + x) as u32; - if let Some(cell) = maze.children().item(index) { - self.update_cell_state(&cell, x, y)?; - } - } - } - - // Update stats - if let Some(level_el) = self.document.get_element_by_id("level") { - level_el.set_text_content(Some(&self.level.to_string())); - } - if let Some(completed_el) = self.document.get_element_by_id("completed") { - completed_el.set_text_content(Some(&self.mazes_completed.to_string())); - } - if let Some(timer_el) = self.document.get_element_by_id("timer") { - let minutes = self.time_remaining / 60; - let seconds = self.time_remaining % 60; - timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); - } - Ok(()) - } - - fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { - // Reset base class - cell.set_class_name("cell"); - - // Update state classes - if self.visited.contains(&(x, y)) { - cell.class_list().add_1("visited")?; - } - if (x, y) == self.current_position { - cell.class_list().add_1("current")?; - // Ensure span exists for pseudo-elements - if cell.children().length() == 0 { - let span = self.document.create_element("span")?; - cell.append_child(&span)?; - } - } - - // Update content - let content = if (x, y) == self.key_position && !self.has_key { - "πŸ”‘" - } else if (x, y) == self.current_position && self.has_key { - "πŸ”‘" - } else if (x, y) == self.door_position { - "πŸšͺ" - } else { - "" - }; - - // Update text content if it's different - if let Some(first_child) = cell.first_child() { - if first_child.text_content().unwrap_or_default() != content { - first_child.set_text_content(Some(content)); - } - } - - Ok(()) - } #[wasm_bindgen] - pub fn start(&mut self) -> Result<(), JsValue> { - let game_state = Rc::new(RefCell::new(self.clone())); - - let click_handler = { - let game_state = game_state.clone(); - Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { - if let Ok(mut game) = game_state.try_borrow_mut() { - if let Some(target) = event.target() { - if let Some(element) = target.dyn_ref::() { - if let Ok(Some(maze_el)) = element.closest("#maze") { - // Find clicked cell index - let children = maze_el.children(); - let cell_index = (0..children.length()) - .find(|&i| { - children - .item(i) - .map(|cell| cell.is_same_node(Some(element))) - .unwrap_or(false) - }) - .unwrap_or(0) - as usize; - - let size = game.size; - let x = cell_index % size; - let y = cell_index / size; - - let result = game.try_move(x, y); - if result != 0 { - game.render().unwrap(); - } - } - } - } - } - }) as Box) - }; - // Attach single click handler to maze container - if let Some(maze_el) = self.document.get_element_by_id("maze") { - maze_el.add_event_listener_with_callback( - "click", - click_handler.as_ref().unchecked_ref(), - )?; - click_handler.forget(); - } - let f = { - let game_state = game_state.clone(); - Closure::wrap(Box::new(move || { - if let Ok(mut game) = game_state.try_borrow_mut() { - let now = js_sys::Date::now() / 1000.0; - let delta = (now - game.last_tick) as i32; - if delta >= 1 { - game.time_remaining -= 1; - game.last_tick = now; - - if game.time_remaining <= 0 { - let new_game = MazeGame::create_maze(game.size, game.document.clone()); - game.walls = new_game.walls; - game.key_position = new_game.key_position; - game.door_position = new_game.door_position; - game.reset_position(); - game.time_remaining = 300; - game.last_tick = now; - game.render().unwrap(); - } - - game.save_state().unwrap_or_else(|_| { - console::log_1(&"Failed to save game state".into()); - }); - if let Some(timer_el) = game.document.get_element_by_id("timer") { - let minutes = game.time_remaining / 60; - let seconds = game.time_remaining % 60; - timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); - } - - game.render().unwrap_or_else(|_| { - console::log_1(&"Failed to render timer update".into()); - }); - - game.save_state().unwrap_or_else(|_| { - console::log_1(&"Failed to save game state".into()); - }); - } - } - }) as Box) - }; - - // Set up interval timer - let window = web_sys::window().unwrap(); - console::log_1(&"Setting up interval...".into()); - let result = window.set_interval_with_callback_and_timeout_and_arguments_0( - f.as_ref().unchecked_ref(), - 1000, - ); - - match result { - Ok(_) => console::log_1(&"Interval set up successfully".into()), - Err(e) => console::log_2(&"Failed to set up interval:".into(), &e), - } - - f.forget(); - console::log_1(&"Setup complete".into()); - Ok(()) - } - fn is_adjacent(&self, x: usize, y: usize) -> bool { - let current_x = self.current_position.0; - let current_y = self.current_position.1; - - // Check if target position is adjacent (up, down, left, right) - let dx = if x >= current_x { - x - current_x - } else { - current_x - x - }; - let dy = if y >= current_y { - y - current_y - } else { - current_y - y - }; - - // Only one coordinate can change by 1, the other must be 0 - (dx == 1 && dy == 0) || (dx == 0 && dy == 1) - } - - fn get_wall_index(&self, from_x: usize, from_y: usize, to_x: usize, to_y: usize) -> usize { - let cell_walls = 4; // each cell has 4 possible walls - let base_index = (from_y * self.size + from_x) * cell_walls; - - if to_x > from_x { - base_index + 1 // right wall - } else if to_x < from_x { - base_index + 3 // left wall - } else if to_y > from_y { - base_index + 2 // bottom wall - } else { - base_index + 0 // top wall - } - } - #[wasm_bindgen] - pub fn try_move(&mut self, x: usize, y: usize) -> i32 { - let result = self.try_move_internal(x, y); - if result != 0 { - self.save_state().unwrap_or_else(|_| { - console::log_1(&"Failed to save game state".into()); - }); - } - result - } - fn try_move_internal(&mut self, x: usize, y: usize) -> i32 { - if !self.is_adjacent(x, y) { - return 0; - } - - let wall_idx = self.get_wall_index(self.current_position.0, self.current_position.1, x, y); - - // Block access to door position if key not collected - if (x, y) == self.door_position && !self.has_key { - return 0; - } - - if self.walls[wall_idx] { - self.reset_position(); - return -1; - } - - self.current_position = (x, y); - self.visited.insert((x, y)); - - if (x, y) == self.key_position { - self.has_key = true; - // When key is collected, make door accessible - let door_x = self.door_position.0; - let door_y = self.door_position.1; - let base_idx = (door_y * self.size + door_x) * 4; - for i in 0..4 { - self.walls[base_idx + i] = false; - } - } - - if (x, y) == self.door_position && self.has_key { - // Simplified level up - increase size immediately - self.size += 1; - self.level += 1; - let new_game = MazeGame::create_maze(self.size, self.document.clone()); - self.walls = new_game.walls; - self.current_position = (0, 0); - self.key_position = new_game.key_position; - self.door_position = new_game.door_position; - self.visited.clear(); - self.visited.insert((0, 0)); - self.has_key = false; - self.time_remaining = 300; - self.last_tick = js_sys::Date::now() / 1000.0; - return 2; - } - 1 - } - #[wasm_bindgen] - pub fn reset(&mut self) { - let new_game = MazeGame::create_maze(self.size, self.document.clone()); - self.walls = new_game.walls; - self.key_position = new_game.key_position; - self.door_position = new_game.door_position; - self.reset_position(); - - // Reset timer state completely - self.time_remaining = 300; - self.last_tick = js_sys::Date::now() / 1000.0; - - // Force timer display update - if let Some(timer_el) = self.document.get_element_by_id("timer") { - timer_el.set_text_content(Some("5:00")); - } - - // Update display - self.render().expect("Failed to render reset"); - } - fn reset_position(&mut self) { - self.current_position = (0, 0); - self.visited.clear(); - self.visited.insert((0, 0)); - self.has_key = false; - self.render().expect("Failed to render position reset"); - } -} +} \ No newline at end of file From c85684585b03e2926bd186bd14e69135f4c8d164 Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Thu, 6 Feb 2025 21:49:44 +0000 Subject: [PATCH 16/78] Refactor Perception module: clean up state management and input handling --- package.json | 1 + src/games/mod.rs | 2 +- src/games/perception/input.rs | 106 +++++------ src/games/perception/maze.rs | 305 +++++++++++++++---------------- src/games/perception/mod.rs | 224 +++++++++++------------ src/games/perception/movement.rs | 182 +++++++++--------- src/games/perception/render.rs | 193 +++++++++---------- src/games/perception/state.rs | 52 ++---- src/games/perception/timer.rs | 137 +++++++------- src/lib.rs | 2 +- tests/app.rs | 12 +- 11 files changed, 598 insertions(+), 618 deletions(-) diff --git a/package.json b/package.json index 08312b4..d5db656 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "build": "webpack --mode=production", "start": "webpack serve --mode development", + "prepare": "wget -qO- https://sh.rustup.rs | sh -s -- --default-toolchain nightly --profile minimal -y && . $HOME/.cargo/env && npm run build", "test": "cargo test && wasm-pack test --headless" }, "type": "module", diff --git a/src/games/mod.rs b/src/games/mod.rs index 17c310e..d9db8c2 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -1 +1 @@ -pub mod perception; \ No newline at end of file +pub mod perception; diff --git a/src/games/perception/input.rs b/src/games/perception/input.rs index d8dd3ad..4cdc965 100644 --- a/src/games/perception/input.rs +++ b/src/games/perception/input.rs @@ -1,53 +1,53 @@ -use super::Perception; -use std::{cell::RefCell, rc::Rc}; -use wasm_bindgen::prelude::*; -use web_sys::Element; - -impl Perception { - pub(super) fn setup_click_handler(game_state: Rc>) -> Result<(), JsValue> { - let click_handler = { - let game_state = game_state.clone(); - Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { - if let Ok(mut game) = game_state.try_borrow_mut() { - if let Some(target) = event.target() { - if let Some(element) = target.dyn_ref::() { - if let Ok(Some(maze_el)) = element.closest("#maze") { - // Find clicked cell index - let children = maze_el.children(); - let cell_index = (0..children.length()) - .find(|&i| { - children - .item(i) - .map(|cell| cell.is_same_node(Some(element))) - .unwrap_or(false) - }) - .unwrap_or(0) - as usize; - - let size = game.size; - let x = cell_index % size; - let y = cell_index / size; - - let result = game.try_move(x, y); - if result != 0 { - game.render().unwrap(); - } - } - } - } - } - }) as Box) - }; - - // Attach single click handler to maze container - if let Some(maze_el) = game_state.borrow().document.get_element_by_id("maze") { - maze_el.add_event_listener_with_callback( - "click", - click_handler.as_ref().unchecked_ref(), - )?; - click_handler.forget(); - } - - Ok(()) - } -} \ No newline at end of file +use super::Perception; +use std::{cell::RefCell, rc::Rc}; +use wasm_bindgen::prelude::*; +use web_sys::Element; + +impl Perception { + pub(super) fn setup_click_handler(game_state: Rc>) -> Result<(), JsValue> { + let click_handler = { + let game_state = game_state.clone(); + Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { + if let Ok(mut game) = game_state.try_borrow_mut() { + if let Some(target) = event.target() { + if let Some(element) = target.dyn_ref::() { + if let Ok(Some(maze_el)) = element.closest("#maze") { + // Find clicked cell index + let children = maze_el.children(); + let cell_index = (0..children.length()) + .find(|&i| { + children + .item(i) + .map(|cell| cell.is_same_node(Some(element))) + .unwrap_or(false) + }) + .unwrap_or(0) + as usize; + + let size = game.size; + let x = cell_index % size; + let y = cell_index / size; + + let result = game.try_move(x, y); + if result != 0 { + game.render().unwrap(); + } + } + } + } + } + }) as Box) + }; + + // Attach single click handler to maze container + if let Some(maze_el) = game_state.borrow().document.get_element_by_id("maze") { + maze_el.add_event_listener_with_callback( + "click", + click_handler.as_ref().unchecked_ref(), + )?; + click_handler.forget(); + } + + Ok(()) + } +} diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index 7baa441..b27d201 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -1,156 +1,149 @@ -use std::collections::HashSet; - -use js_sys::Math; -use web_sys::Document; -use super::Perception; - -impl Perception { - pub(super) fn create_maze(size: usize, document: Document) -> Self { - let mut walls = vec![false; size * size * 4]; // Start with no walls - // Add random walls - for i in 0..walls.len() { - walls[i] = Math::random() < 0.5; - } - - let (waypoint1, key_position, waypoint2, door_position) = if size == 2 { - let key_pos = loop { - let pos = ( - (Math::random() * 2.0).floor() as usize, - (Math::random() * 2.0).floor() as usize, - ); - if pos != (0, 0) { - break pos; - }; - }; - // Get random non-start, non-key position for door - let door_pos = loop { - let pos = ( - (Math::random() * 2.0).floor() as usize, - (Math::random() * 2.0).floor() as usize, - ); - if pos != (0, 0) && pos != key_pos { - break pos; - } - }; - (key_pos, key_pos, door_pos, door_pos) - } else { - let (mut key_pos, mut door_pos); - loop { - key_pos = ( - ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, - ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, - ); - - door_pos = ( - ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, - ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, - ); - - if key_pos != door_pos && key_pos != (0, 0) && door_pos != (0, 0) { - break; - } - } - ( - ( - (Math::random() * (size as f64)).floor() as usize, - (Math::random() * (size as f64)).floor() as usize, - ), - key_pos, - ( - (Math::random() * (size as f64)).floor() as usize, - (Math::random() * (size as f64)).floor() as usize, - ), - door_pos, - ) - }; - - let mut game = Self { - size, - walls, - current_position: (0, 0), - key_position, - door_position, - visited: HashSet::new(), - has_key: false, - level: 1, - mazes_completed: 0, - document, - time_remaining: 300, - last_tick: js_sys::Date::now() / 1000.0, - }; - // Create path through waypoints - clear_path(&mut game.walls, (0, 0), waypoint1, size); - clear_path(&mut game.walls, waypoint1, key_position, size); - clear_path(&mut game.walls, key_position, waypoint2, size); - clear_path(&mut game.walls, waypoint2, door_position, size); - game.visited.insert((0, 0)); - game - } -} -fn clear_path( - walls: &mut Vec, - from: (usize, usize), - to: (usize, usize), - size: usize, -) { - let mut current = from; - // Calculate minimum required path length (Manhattan distance * 1.5) - while current != to { - let dx = (to.0 as i32 - current.0 as i32).signum(); - let dy = (to.1 as i32 - current.1 as i32).signum(); - // Clear both current cell's wall and neighbor's wall - if dx != 0 { - let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; - walls[wall_idx] = false; - // Clear adjacent cell's opposite wall if not at edge - if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { - let next_x = (current.0 as i32 + dx) as usize; - let adj_wall_idx = - (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; - walls[adj_wall_idx] = false; - - // Always clear an escape route (up or down) - let escape_dir = if current.1 > 0 { 0 } else { 2 }; // up if not at top, down otherwise - walls[(current.1 * size + current.0) * 4 + escape_dir] = false; - if escape_dir == 0 && current.1 > 0 { - // Clear the corresponding wall in the cell above - walls[((current.1 - 1) * size + current.0) * 4 + 2] = false; - } else if escape_dir == 2 && current.1 + 1 < size { - // Clear the corresponding wall in the cell below - walls[((current.1 + 1) * size + current.0) * 4 + 0] = false; - } - } - current.0 = (current.0 as i32 + dx) as usize; - } else if dy != 0 { - let wall_idx = (current.1 * size + current.0) * 4 + if dy > 0 { 2 } else { 0 }; - walls[wall_idx] = false; - // Clear adjacent cell's opposite wall if not at edge - if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { - let next_y = (current.1 as i32 + dy) as usize; - let adj_wall_idx = - (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; - walls[adj_wall_idx] = false; - } - current.1 = (current.1 as i32 + dy) as usize; - } - // Ensure escape route from the destination - let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right - for (dx, dy) in escape_dirs.iter() { - let next_x = to.0 as i32 + dx; - let next_y = to.1 as i32 + dy; - if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { - let wall_idx = (to.1 * size + to.0) * 4 - + if *dy < 0 { - 0 - } else if *dx > 0 { - 1 - } else if *dy > 0 { - 2 - } else { - 3 - }; - walls[wall_idx] = false; - } - } - } -} +use std::collections::HashSet; + +use super::Perception; +use js_sys::Math; +use web_sys::Document; + +impl Perception { + pub(super) fn create_maze(size: usize, document: Document) -> Self { + let mut walls = vec![false; size * size * 4]; // Start with no walls + // Add random walls + for i in 0..walls.len() { + walls[i] = Math::random() < 0.5; + } + + let (waypoint1, key_position, waypoint2, door_position) = if size == 2 { + let key_pos = loop { + let pos = ( + (Math::random() * 2.0).floor() as usize, + (Math::random() * 2.0).floor() as usize, + ); + if pos != (0, 0) { + break pos; + }; + }; + // Get random non-start, non-key position for door + let door_pos = loop { + let pos = ( + (Math::random() * 2.0).floor() as usize, + (Math::random() * 2.0).floor() as usize, + ); + if pos != (0, 0) && pos != key_pos { + break pos; + } + }; + (key_pos, key_pos, door_pos, door_pos) + } else { + let (mut key_pos, mut door_pos); + loop { + key_pos = ( + ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, + ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, + ); + + door_pos = ( + ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, + ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, + ); + + if key_pos != door_pos && key_pos != (0, 0) && door_pos != (0, 0) { + break; + } + } + ( + ( + (Math::random() * (size as f64)).floor() as usize, + (Math::random() * (size as f64)).floor() as usize, + ), + key_pos, + ( + (Math::random() * (size as f64)).floor() as usize, + (Math::random() * (size as f64)).floor() as usize, + ), + door_pos, + ) + }; + + let mut game = Self { + size, + walls, + current_position: (0, 0), + key_position, + door_position, + visited: HashSet::new(), + has_key: false, + level: 1, + mazes_completed: 0, + document, + time_remaining: 300, + last_tick: js_sys::Date::now() / 1000.0, + }; + // Create path through waypoints + clear_path(&mut game.walls, (0, 0), waypoint1, size); + clear_path(&mut game.walls, waypoint1, key_position, size); + clear_path(&mut game.walls, key_position, waypoint2, size); + clear_path(&mut game.walls, waypoint2, door_position, size); + game.visited.insert((0, 0)); + game + } +} +fn clear_path(walls: &mut Vec, from: (usize, usize), to: (usize, usize), size: usize) { + let mut current = from; + // Calculate minimum required path length (Manhattan distance * 1.5) + while current != to { + let dx = (to.0 as i32 - current.0 as i32).signum(); + let dy = (to.1 as i32 - current.1 as i32).signum(); + // Clear both current cell's wall and neighbor's wall + if dx != 0 { + let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; + walls[wall_idx] = false; + // Clear adjacent cell's opposite wall if not at edge + if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { + let next_x = (current.0 as i32 + dx) as usize; + let adj_wall_idx = (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; + walls[adj_wall_idx] = false; + + // Always clear an escape route (up or down) + let escape_dir = if current.1 > 0 { 0 } else { 2 }; // up if not at top, down otherwise + walls[(current.1 * size + current.0) * 4 + escape_dir] = false; + if escape_dir == 0 && current.1 > 0 { + // Clear the corresponding wall in the cell above + walls[((current.1 - 1) * size + current.0) * 4 + 2] = false; + } else if escape_dir == 2 && current.1 + 1 < size { + // Clear the corresponding wall in the cell below + walls[((current.1 + 1) * size + current.0) * 4] = false; + } + } + current.0 = (current.0 as i32 + dx) as usize; + } else if dy != 0 { + let wall_idx = (current.1 * size + current.0) * 4 + if dy > 0 { 2 } else { 0 }; + walls[wall_idx] = false; + // Clear adjacent cell's opposite wall if not at edge + if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { + let next_y = (current.1 as i32 + dy) as usize; + let adj_wall_idx = (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; + walls[adj_wall_idx] = false; + } + current.1 = (current.1 as i32 + dy) as usize; + } + // Ensure escape route from the destination + let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right + for (dx, dy) in escape_dirs.iter() { + let next_x = to.0 as i32 + dx; + let next_y = to.1 as i32 + dy; + if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { + let wall_idx = (to.1 * size + to.0) * 4 + + if *dy < 0 { + 0 + } else if *dx > 0 { + 1 + } else if *dy > 0 { + 2 + } else { + 3 + }; + walls[wall_idx] = false; + } + } + } +} diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index ba5fdf2..6565919 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -1,112 +1,112 @@ -mod state; -mod render; -mod maze; -mod movement; -mod timer; -mod input; - -use serde::{Deserialize, Serialize}; -use std::{cell::RefCell, collections::HashSet, rc::Rc}; -use wasm_bindgen::prelude::*; -use web_sys::{console, Document, Element}; - -fn get_document() -> Document { - web_sys::window() - .expect("no global window exists") - .document() - .expect("no document exists") -} - -#[wasm_bindgen] -#[derive(Clone, Serialize, Deserialize)] -pub struct Perception { - // Game state - size: usize, - level: usize, - #[serde(default)] - mazes_completed: usize, - - // Maze elements - walls: Vec, - current_position: (usize, usize), - key_position: (usize, usize), - door_position: (usize, usize), - visited: HashSet<(usize, usize)>, - has_key: bool, - - // Timer state - time_remaining: i32, - last_tick: f64, - - #[serde(skip, default = "get_document")] - document: Document, -} - -#[wasm_bindgen] -impl Perception { - #[wasm_bindgen(constructor)] - pub fn new() -> Result { - let document = get_document(); - let storage = web_sys::window() - .expect("no global window exists") - .local_storage()? - .expect("no local storage"); - - let mut game = if let Some(state) = storage.get_item("maze_state")? { - let last_save = storage - .get_item("maze_time")? - .unwrap_or_else(|| "0".to_string()) - .parse::() - .unwrap_or(0.0); - - if js_sys::Date::now() - last_save > 300000.0 { - Self::create_maze(2, document) - } else { - serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)? - } - } else { - Self::create_maze(2, document) - }; - - game.render()?; - game.start()?; - Ok(game) - } - #[wasm_bindgen] - pub fn start(&mut self) -> Result<(), JsValue> { - let game_state = Rc::new(RefCell::new(self.clone())); - - Self::setup_click_handler(game_state.clone())?; - Self::setup_timer(game_state)?; - - console::log_1(&"Setup complete".into()); - Ok(()) - } - #[wasm_bindgen] - pub fn reset(&mut self) { - let new_game = Self::create_maze(self.size, self.document.clone()); - self.walls = new_game.walls; - self.key_position = new_game.key_position; - self.door_position = new_game.door_position; - self.reset_position(); - - // Reset timer state completely - self.time_remaining = 300; - self.last_tick = js_sys::Date::now() / 1000.0; - - // Force timer display update - if let Some(timer_el) = self.document.get_element_by_id("timer") { - timer_el.set_text_content(Some("5:00")); - } - - // Update display - self.render().expect("Failed to render reset"); - } - fn reset_position(&mut self) { - self.current_position = (0, 0); - self.visited.clear(); - self.visited.insert((0, 0)); - self.has_key = false; - self.render().expect("Failed to render position reset"); - } -} \ No newline at end of file +mod input; +mod maze; +mod movement; +mod render; +mod state; +mod timer; + +use serde::{Deserialize, Serialize}; +use std::{cell::RefCell, collections::HashSet, rc::Rc}; +use wasm_bindgen::prelude::*; +use web_sys::{console, Document}; + +fn get_document() -> Document { + web_sys::window() + .expect("no global window exists") + .document() + .expect("no document exists") +} + +#[wasm_bindgen] +#[derive(Clone, Serialize, Deserialize)] +pub struct Perception { + // Game state + size: usize, + level: usize, + #[serde(default)] + mazes_completed: usize, + + // Maze elements + walls: Vec, + current_position: (usize, usize), + key_position: (usize, usize), + door_position: (usize, usize), + visited: HashSet<(usize, usize)>, + has_key: bool, + + // Timer state + time_remaining: i32, + last_tick: f64, + + #[serde(skip, default = "get_document")] + document: Document, +} + +#[wasm_bindgen] +impl Perception { + #[wasm_bindgen(constructor)] + pub fn new() -> Result { + let document = get_document(); + let storage = web_sys::window() + .expect("no global window exists") + .local_storage()? + .expect("no local storage"); + + let mut game = if let Some(state) = storage.get_item("maze_state")? { + let last_save = storage + .get_item("maze_time")? + .unwrap_or_else(|| "0".to_string()) + .parse::() + .unwrap_or(0.0); + + if js_sys::Date::now() - last_save > 300000.0 { + Self::create_maze(2, document) + } else { + serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)? + } + } else { + Self::create_maze(2, document) + }; + + game.render()?; + game.start()?; + Ok(game) + } + #[wasm_bindgen] + pub fn start(&mut self) -> Result<(), JsValue> { + let game_state = Rc::new(RefCell::new(self.clone())); + + Self::setup_click_handler(game_state.clone())?; + Self::setup_timer(game_state)?; + + console::log_1(&"Setup complete".into()); + Ok(()) + } + #[wasm_bindgen] + pub fn reset(&mut self) { + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.reset_position(); + + // Reset timer state completely + self.time_remaining = 300; + self.last_tick = js_sys::Date::now() / 1000.0; + + // Force timer display update + if let Some(timer_el) = self.document.get_element_by_id("timer") { + timer_el.set_text_content(Some("5:00")); + } + + // Update display + self.render().expect("Failed to render reset"); + } + fn reset_position(&mut self) { + self.current_position = (0, 0); + self.visited.clear(); + self.visited.insert((0, 0)); + self.has_key = false; + self.render().expect("Failed to render position reset"); + } +} diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index 6925b34..b2b7930 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -1,88 +1,94 @@ -use super::Perception; - -impl Perception { - pub(super) fn is_adjacent(&self, x: usize, y: usize) -> bool { - let current_x = self.current_position.0; - let current_y = self.current_position.1; - - // Check if target position is adjacent (up, down, left, right) - let dx = if x >= current_x { - x - current_x - } else { - current_x - x - }; - let dy = if y >= current_y { - y - current_y - } else { - current_y - y - }; - - // Only one coordinate can change by 1, the other must be 0 - (dx == 1 && dy == 0) || (dx == 0 && dy == 1) - } - - pub(super) fn get_wall_index(&self, from_x: usize, from_y: usize, to_x: usize, to_y: usize) -> usize { - let cell_walls = 4; // each cell has 4 possible walls - let base_index = (from_y * self.size + from_x) * cell_walls; - - if to_x > from_x { - base_index + 1 // right wall - } else if to_x < from_x { - base_index + 3 // left wall - } else if to_y > from_y { - base_index + 2 // bottom wall - } else { - base_index + 0 // top wall - } - } - - pub(super) fn try_move(&mut self, x: usize, y: usize) -> i32 { - if !self.is_adjacent(x, y) { - return 0; - } - - let wall_idx = self.get_wall_index(self.current_position.0, self.current_position.1, x, y); - - // Block access to door position if key not collected - if (x, y) == self.door_position && !self.has_key { - return 0; - } - - if self.walls[wall_idx] { - self.reset_position(); - return -1; - } - - self.current_position = (x, y); - self.visited.insert((x, y)); - - if (x, y) == self.key_position { - self.has_key = true; - // When key is collected, make door accessible - let door_x = self.door_position.0; - let door_y = self.door_position.1; - let base_idx = (door_y * self.size + door_x) * 4; - for i in 0..4 { - self.walls[base_idx + i] = false; - } - } - - if (x, y) == self.door_position && self.has_key { - // Simplified level up - increase size immediately - self.size += 1; - self.level += 1; - let new_game = Self::create_maze(self.size, self.document.clone()); - self.walls = new_game.walls; - self.current_position = (0, 0); - self.key_position = new_game.key_position; - self.door_position = new_game.door_position; - self.visited.clear(); - self.visited.insert((0, 0)); - self.has_key = false; - self.time_remaining = 300; - self.last_tick = js_sys::Date::now() / 1000.0; - return 2; - } - 1 - } -} \ No newline at end of file +use super::Perception; + +impl Perception { + pub(super) fn is_adjacent(&self, x: usize, y: usize) -> bool { + let current_x = self.current_position.0; + let current_y = self.current_position.1; + + // Check if target position is adjacent (up, down, left, right) + let dx = if x >= current_x { + x - current_x + } else { + current_x - x + }; + let dy = if y >= current_y { + y - current_y + } else { + current_y - y + }; + + // Only one coordinate can change by 1, the other must be 0 + (dx == 1 && dy == 0) || (dx == 0 && dy == 1) + } + + pub(super) fn get_wall_index( + &self, + from_x: usize, + from_y: usize, + to_x: usize, + to_y: usize, + ) -> usize { + let cell_walls = 4; // each cell has 4 possible walls + let base_index = (from_y * self.size + from_x) * cell_walls; + + if to_x > from_x { + base_index + 1 // right wall + } else if to_x < from_x { + base_index + 3 // left wall + } else if to_y > from_y { + base_index + 2 // bottom wall + } else { + base_index // top wall + } + } + + pub(super) fn try_move(&mut self, x: usize, y: usize) -> i32 { + if !self.is_adjacent(x, y) { + return 0; + } + + let wall_idx = self.get_wall_index(self.current_position.0, self.current_position.1, x, y); + + // Block access to door position if key not collected + if (x, y) == self.door_position && !self.has_key { + return 0; + } + + if self.walls[wall_idx] { + self.reset_position(); + return -1; + } + + self.current_position = (x, y); + self.visited.insert((x, y)); + + if (x, y) == self.key_position { + self.has_key = true; + // When key is collected, make door accessible + let door_x = self.door_position.0; + let door_y = self.door_position.1; + let base_idx = (door_y * self.size + door_x) * 4; + for i in 0..4 { + self.walls[base_idx + i] = false; + } + } + + if (x, y) == self.door_position && self.has_key { + // Simplified level up - increase size immediately + self.size += 1; + self.level += 1; + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.current_position = (0, 0); + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.visited.clear(); + self.visited.insert((0, 0)); + self.has_key = false; + self.time_remaining = 300; + self.last_tick = js_sys::Date::now() / 1000.0; + return 2; + } + 1 + } +} diff --git a/src/games/perception/render.rs b/src/games/perception/render.rs index 52987b0..88d4ca1 100644 --- a/src/games/perception/render.rs +++ b/src/games/perception/render.rs @@ -1,95 +1,98 @@ -use super::Perception; -use wasm_bindgen::prelude::*; -use web_sys::Element; - -impl Perception { - pub(crate) fn render(&self) -> Result<(), JsValue> { - let maze = self.document.get_element_by_id("maze").unwrap(); - - // Only regenerate grid if size changed - if maze.children().length() as usize != self.size * self.size { - maze.set_attribute( - "style", - &format!("grid-template-columns: repeat({}, 60px)", self.size), - )?; - - // Clear existing content safely - while let Some(child) = maze.first_child() { - maze.remove_child(&child)?; - } - - // Create cells only once - for _ in 0..(self.size * self.size) { - let cell = self.document.create_element("div")?; - cell.set_class_name("cell"); - let span = self.document.create_element("span")?; - let content = self.document.create_text_node(""); - cell.append_child(&content)?; - cell.append_child(&span)?; - maze.append_child(&cell)?; - } - } - - // Update existing cells - for y in 0..self.size { - for x in 0..self.size { - let index = (y * self.size + x) as u32; - if let Some(cell) = maze.children().item(index) { - self.update_cell_state(&cell, x, y)?; - } - } - } - - // Update stats - if let Some(level_el) = self.document.get_element_by_id("level") { - level_el.set_text_content(Some(&self.level.to_string())); - } - if let Some(completed_el) = self.document.get_element_by_id("completed") { - completed_el.set_text_content(Some(&self.mazes_completed.to_string())); - } - if let Some(timer_el) = self.document.get_element_by_id("timer") { - let minutes = self.time_remaining / 60; - let seconds = self.time_remaining % 60; - timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); - } - Ok(()) - } - - fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { - // Reset base class - cell.set_class_name("cell"); - - // Update state classes - if self.visited.contains(&(x, y)) { - cell.class_list().add_1("visited")?; - } - if (x, y) == self.current_position { - cell.class_list().add_1("current")?; - // Ensure span exists for pseudo-elements - if cell.children().length() == 0 { - let span = self.document.create_element("span")?; - cell.append_child(&span)?; - } - } - - // Update content - let content = if (x, y) == self.key_position && !self.has_key { - "πŸ”‘" - } else if (x, y) == self.current_position && self.has_key { - "πŸ”‘" - } else if (x, y) == self.door_position { - "πŸšͺ" - } else { - "" - }; - - // Update text content if it's different - if let Some(first_child) = cell.first_child() { - if first_child.text_content().unwrap_or_default() != content { - first_child.set_text_content(Some(content)); - } - } - - Ok(()) - } -} \ No newline at end of file +use super::Perception; +use wasm_bindgen::prelude::*; +use web_sys::Element; + +impl Perception { + pub(crate) fn render(&self) -> Result<(), JsValue> { + let maze = self.document.get_element_by_id("maze").unwrap(); + + // Only regenerate grid if size changed + if maze.children().length() as usize != self.size * self.size { + maze.set_attribute( + "style", + &format!("grid-template-columns: repeat({}, 60px)", self.size), + )?; + + // Clear existing content safely + while let Some(child) = maze.first_child() { + maze.remove_child(&child)?; + } + + // Create cells only once + for _ in 0..(self.size * self.size) { + let cell = self.document.create_element("div")?; + cell.set_class_name("cell"); + let span = self.document.create_element("span")?; + let content = self.document.create_text_node(""); + cell.append_child(&content)?; + cell.append_child(&span)?; + maze.append_child(&cell)?; + } + } + + // Update existing cells + for y in 0..self.size { + for x in 0..self.size { + let index = (y * self.size + x) as u32; + if let Some(cell) = maze.children().item(index) { + self.update_cell_state(&cell, x, y)?; + } + } + } + + // Update stats + if let Some(level_el) = self.document.get_element_by_id("level") { + level_el.set_text_content(Some(&self.level.to_string())); + } + if let Some(completed_el) = self.document.get_element_by_id("completed") { + completed_el.set_text_content(Some(&self.mazes_completed.to_string())); + } + if let Some(timer_el) = self.document.get_element_by_id("timer") { + let minutes = self.time_remaining / 60; + let seconds = self.time_remaining % 60; + timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); + } + Ok(()) + } + + fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { + // Reset base class + cell.set_class_name("cell"); + + // Update state classes + if self.visited.contains(&(x, y)) { + cell.class_list().add_1("visited")?; + } + if (x, y) == self.current_position { + cell.class_list().add_1("current")?; + // Ensure span exists for pseudo-elements + if cell.children().length() == 0 { + let span = self.document.create_element("span")?; + cell.append_child(&span)?; + } + } + + let content = if (x, y) == self.key_position && !self.has_key { + "πŸ”‘" + } else if (x, y) == self.door_position { + "πŸšͺ" + } else if (x, y) == self.current_position { + if self.has_key { + "πŸ”‘" + } else { + "πŸ‘€" + } + } else { + "" + }; + + // Update text content if it's different + if let Some(first_child) = cell.first_child() { + if first_child.text_content().unwrap_or_default() != content { + first_child.set_text_content(Some(content)); + } + } + + Ok(()) + } +} diff --git a/src/games/perception/state.rs b/src/games/perception/state.rs index 64ce695..1f2200d 100644 --- a/src/games/perception/state.rs +++ b/src/games/perception/state.rs @@ -1,34 +1,18 @@ -use super::Perception; -use js_sys::Date; -use wasm_bindgen::prelude::*; -use web_sys::Storage; - -impl Perception { - pub(super) fn load_state(storage: &Storage) -> Result, JsValue> { - if let Some(state) = storage.get_item("maze_state")? { - let last_save = storage - .get_item("maze_time")? - .unwrap_or_else(|| "0".to_string()) - .parse::() - .unwrap_or(0.0); - - let game: Self = serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)?; - Ok(Some((game, last_save))) - } else { - Ok(None) - } - } - - pub(super) fn save_state(&self) -> Result<(), JsValue> { - let window = web_sys::window().expect("no global window exists"); - let storage = window.local_storage()?.expect("no local storage exists"); - - let state = serde_wasm_bindgen::to_value(&self)?; - let state_json = js_sys::JSON::stringify(&state)?.as_string().unwrap(); - storage.set_item("maze_state", &state_json)?; - storage.set_item("maze_time", &Date::now().to_string())?; - storage.set_item("maze_level", &self.level.to_string())?; - - Ok(()) - } -} \ No newline at end of file +use super::Perception; +use js_sys::Date; +use wasm_bindgen::prelude::*; + +impl Perception { + pub(super) fn save_state(&self) -> Result<(), JsValue> { + let window = web_sys::window().expect("no global window exists"); + let storage = window.local_storage()?.expect("no local storage exists"); + + let state = serde_wasm_bindgen::to_value(&self)?; + let state_json = js_sys::JSON::stringify(&state)?.as_string().unwrap(); + storage.set_item("maze_state", &state_json)?; + storage.set_item("maze_time", &Date::now().to_string())?; + storage.set_item("maze_level", &self.level.to_string())?; + + Ok(()) + } +} diff --git a/src/games/perception/timer.rs b/src/games/perception/timer.rs index ef95529..91a911b 100644 --- a/src/games/perception/timer.rs +++ b/src/games/perception/timer.rs @@ -1,70 +1,67 @@ -use wasm_bindgen::prelude::*; -use web_sys::{console, Document}; -use std::{rc::Rc, cell::RefCell}; -use super::Perception; - -impl Perception { - pub(super) fn setup_timer(game_state: Rc>) -> Result<(), JsValue> { - let timer_callback = { - let game_state = game_state.clone(); - Closure::wrap(Box::new(move || { - if let Ok(mut game) = game_state.try_borrow_mut() { - let now = js_sys::Date::now() / 1000.0; - let delta = (now - game.last_tick) as i32; - if delta >= 1 { - game.update_timer(now); - } - } - }) as Box) - }; - - let window = web_sys::window().unwrap(); - console::log_1(&"Setting up interval...".into()); - - let result = window.set_interval_with_callback_and_timeout_and_arguments_0( - timer_callback.as_ref().unchecked_ref(), - 1000, - ); - - match result { - Ok(_) => console::log_1(&"Interval set up successfully".into()), - Err(e) => console::log_2(&"Failed to set up interval:".into(), &e), - } - - timer_callback.forget(); - Ok(()) - } - - fn update_timer(&mut self, now: f64) { - self.time_remaining -= 1; - self.last_tick = now; - - if self.time_remaining <= 0 { - self.reset_on_timeout(now); - } - - self.update_timer_display(); - self.save_state().unwrap_or_else(|_| { - console::log_1(&"Failed to save game state".into()); - }); - } - - fn update_timer_display(&self) { - if let Some(timer_el) = self.document.get_element_by_id("timer") { - let minutes = self.time_remaining / 60; - let seconds = self.time_remaining % 60; - timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); - } - } - - fn reset_on_timeout(&mut self, now: f64) { - let new_game = Self::create_maze(self.size, self.document.clone()); - self.walls = new_game.walls; - self.key_position = new_game.key_position; - self.door_position = new_game.door_position; - self.reset_position(); - self.time_remaining = 300; - self.last_tick = now; - self.render().unwrap(); - } -} \ No newline at end of file +use super::Perception; +use std::{cell::RefCell, rc::Rc}; +use wasm_bindgen::prelude::*; +use web_sys::console; + +impl Perception { + pub(super) fn setup_timer(game_state: Rc>) -> Result<(), JsValue> { + let timer_callback = { + let game_state = game_state.clone(); + let performance = web_sys::window() + .unwrap() + .performance() + .expect("performance should be available"); + + Closure::wrap(Box::new(move || { + if let Ok(mut game) = game_state.try_borrow_mut() { + let now = performance.now() / 1000.0; + let delta = (now - game.last_tick) as i32; + if delta >= 1 { + game.update_timer(now); + } + } + }) as Box) + }; + + let window = web_sys::window().unwrap(); + + window.set_interval_with_callback_and_timeout_and_arguments_0( + timer_callback.as_ref().unchecked_ref(), + 1000, + )?; + timer_callback.forget(); + Ok(()) + } + fn update_timer(&mut self, now: f64) { + self.time_remaining -= 1; + self.last_tick = now; + + if self.time_remaining <= 0 { + self.reset_on_timeout(now); + } + + self.update_timer_display(); + self.save_state().unwrap_or_else(|_| { + console::log_1(&"Failed to save game state".into()); + }); + } + + fn update_timer_display(&self) { + if let Some(timer_el) = self.document.get_element_by_id("timer") { + let minutes = self.time_remaining / 60; + let seconds = self.time_remaining % 60; + timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); + } + } + + fn reset_on_timeout(&mut self, now: f64) { + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.reset_position(); + self.time_remaining = 300; + self.last_tick = now; + self.render().unwrap(); + } +} diff --git a/src/lib.rs b/src/lib.rs index d56cd55..4082f1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,4 +12,4 @@ pub fn main_js() -> Result<(), JsValue> { console_error_panic_hook::set_once(); Perception::new()?; Ok(()) -} \ No newline at end of file +} diff --git a/tests/app.rs b/tests/app.rs index 9223aa3..eb6aacb 100644 --- a/tests/app.rs +++ b/tests/app.rs @@ -1,25 +1,22 @@ -use wasm_bindgen_test::{wasm_bindgen_test_configure, wasm_bindgen_test}; use futures::prelude::*; use wasm_bindgen::JsValue; use wasm_bindgen_futures::JsFuture; +use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; wasm_bindgen_test_configure!(run_in_browser); - // This runs a unit test in native Rust, so it can only use Rust APIs. #[test] fn rust_test() { assert_eq!(1, 1); } - // This runs a unit test in the browser, so it can use browser APIs. #[wasm_bindgen_test] fn web_test() { assert_eq!(1, 1); } - // This runs a unit test in the browser, and in addition it supports asynchronous Future APIs. #[wasm_bindgen_test(async)] fn async_test() -> impl Future { @@ -28,8 +25,7 @@ fn async_test() -> impl Future { // Converts that Promise into a Future. // The unit test will wait for the Future to resolve. - JsFuture::from(promise) - .map(|x| { - assert_eq!(x, 42); - }) + JsFuture::from(promise).map(|x| { + assert_eq!(x, 42); + }) } From 14be4e6830249f5958ca67ee671fc2332efae74e Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sat, 8 Feb 2025 08:27:35 +0000 Subject: [PATCH 17/78] feat(numeracy): implement modular numeracy game with expression generation and state management --- package.json | 4 +- src/games/numeracy/expression.rs | 32 ++++++++++++++++ src/games/numeracy/level.rs | 26 +++++++++++++ src/games/numeracy/mod.rs | 36 +++++++++++++++++ src/games/numeracy/state.rs | 0 static/numeracy.html | 66 ++++++++++++++++++++++++++++++++ 6 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 src/games/numeracy/expression.rs create mode 100644 src/games/numeracy/level.rs create mode 100644 src/games/numeracy/mod.rs create mode 100644 src/games/numeracy/state.rs create mode 100644 static/numeracy.html diff --git a/package.json b/package.json index d5db656..f8f09ab 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "name": "rust-webpack-template", "version": "0.1.0", "scripts": { - "build": "webpack --mode=production", - "start": "webpack serve --mode development", + "build": "command -v cargo | . ~/.cargo/env && webpack --mode=production", + "start": "webpack serve --mode=development", "prepare": "wget -qO- https://sh.rustup.rs | sh -s -- --default-toolchain nightly --profile minimal -y && . $HOME/.cargo/env && npm run build", "test": "cargo test && wasm-pack test --headless" }, diff --git a/src/games/numeracy/expression.rs b/src/games/numeracy/expression.rs new file mode 100644 index 0000000..cfcdb05 --- /dev/null +++ b/src/games/numeracy/expression.rs @@ -0,0 +1,32 @@ +use rand::Rng; + +#[derive(Clone, Debug, PartialEq)] +pub struct Expression { + pub text: String, + pub value: f64, +} + +impl Expression { + pub fn new(level: u32) -> Self { + let mut rng = rand::thread_rng(); + let ops = ["+", "-", "*", "/"]; + + let complexity = (level as f64 * 1.5).ceil() as i32; + let a = rng.gen_range(1..=complexity * 5); + let b = rng.gen_range(1..=complexity * 3); + let op = ops[rng.gen_range(0..ops.len())]; + + let (text, value) = match op { + "+" => (format!("{} + {}", a, b), (a + b) as f64), + "-" => (format!("{} - {}", a, b), (a - b) as f64), + "*" => (format!("{} Γ— {}", a, b), (a * b) as f64), + "/" => { + let product = a * b; + (format!("{} Γ· {}", product, a), b as f64) + } + _ => unreachable!(), + }; + + Expression { text, value } + } +} \ No newline at end of file diff --git a/src/games/numeracy/level.rs b/src/games/numeracy/level.rs new file mode 100644 index 0000000..b0dc19b --- /dev/null +++ b/src/games/numeracy/level.rs @@ -0,0 +1,26 @@ +use super::expression::Expression; + +pub struct Level { + pub number: u32, + expressions_per_round: usize, +} + +impl Level { + pub fn new(number: u32) -> Self { + Self { + number, + expressions_per_round: 3, + } + } + + pub fn generate_expressions(&self) -> Vec { + (0..self.expressions_per_round) + .map(|_| Expression::new(self.number)) + .collect() + } + + pub fn check_order(expressions: &[Expression]) -> bool { + let values: Vec = expressions.iter().map(|e| e.value).collect(); + values.windows(2).all(|w| w[0] <= w[1]) + } +} \ No newline at end of file diff --git a/src/games/numeracy/mod.rs b/src/games/numeracy/mod.rs new file mode 100644 index 0000000..9003638 --- /dev/null +++ b/src/games/numeracy/mod.rs @@ -0,0 +1,36 @@ +use wasm_bindgen::prelude::*; +use web_sys::{Document, Element, HtmlElement}; + +mod expression; +mod level; +mod state; + +use expression::Expression; +use level::Level; +use state::GameState; + +#[wasm_bindgen] +pub struct Numeracy { + state: GameState, + document: Document, + container: HtmlElement, +} + +#[wasm_bindgen] +impl Numeracy { + #[wasm_bindgen(constructor)] + pub fn new() -> Result { + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let container = document + .get_element_by_id("game-container") + .unwrap() + .dyn_into::()?; + + Ok(Numeracy { + state: GameState::new(), + document, + container, + }) + } +} \ No newline at end of file diff --git a/src/games/numeracy/state.rs b/src/games/numeracy/state.rs new file mode 100644 index 0000000..e69de29 diff --git a/static/numeracy.html b/static/numeracy.html new file mode 100644 index 0000000..9f02582 --- /dev/null +++ b/static/numeracy.html @@ -0,0 +1,66 @@ + + + + + + Numeracy - Cognitive Games + + + +
+ Level: 1 | + Score: 0 | + Time: 5:00 +
+
+ + + \ No newline at end of file From 72abf11d0a98733af5e4eb85e72b0f882267d793 Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sat, 8 Feb 2025 09:15:44 +0000 Subject: [PATCH 18/78] feat(numeracy): add numeracy game module and integrate with main application --- Cargo.toml | 1 + src/games/mod.rs | 1 + src/games/numeracy/expression.rs | 9 ++- src/games/numeracy/level.rs | 2 +- src/games/numeracy/mod.rs | 2 +- src/games/numeracy/state.rs | 119 +++++++++++++++++++++++++++++++ src/lib.rs | 19 ++++- 7 files changed, 143 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a25b931..4e7fbf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ features = [ "CustomEventInit", "Storage", "DomTokenList", + "Location", "Performance", "Text", ] diff --git a/src/games/mod.rs b/src/games/mod.rs index d9db8c2..0cbca92 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -1 +1,2 @@ pub mod perception; +pub mod numeracy; diff --git a/src/games/numeracy/expression.rs b/src/games/numeracy/expression.rs index cfcdb05..f1aa7e2 100644 --- a/src/games/numeracy/expression.rs +++ b/src/games/numeracy/expression.rs @@ -1,4 +1,4 @@ -use rand::Rng; +use js_sys::Math; #[derive(Clone, Debug, PartialEq)] pub struct Expression { @@ -8,13 +8,12 @@ pub struct Expression { impl Expression { pub fn new(level: u32) -> Self { - let mut rng = rand::thread_rng(); let ops = ["+", "-", "*", "/"]; let complexity = (level as f64 * 1.5).ceil() as i32; - let a = rng.gen_range(1..=complexity * 5); - let b = rng.gen_range(1..=complexity * 3); - let op = ops[rng.gen_range(0..ops.len())]; + let a = (Math::random() * (complexity * 5) as f64).floor() as i32 + 1; + let b = (Math::random() * (complexity * 3) as f64).floor() as i32 + 1; + let op = ops[(Math::random() * 4.0).floor() as usize]; let (text, value) = match op { "+" => (format!("{} + {}", a, b), (a + b) as f64), diff --git a/src/games/numeracy/level.rs b/src/games/numeracy/level.rs index b0dc19b..0fcf071 100644 --- a/src/games/numeracy/level.rs +++ b/src/games/numeracy/level.rs @@ -1,5 +1,5 @@ use super::expression::Expression; - +#[derive(Debug)] pub struct Level { pub number: u32, expressions_per_round: usize, diff --git a/src/games/numeracy/mod.rs b/src/games/numeracy/mod.rs index 9003638..a9d9766 100644 --- a/src/games/numeracy/mod.rs +++ b/src/games/numeracy/mod.rs @@ -1,5 +1,5 @@ use wasm_bindgen::prelude::*; -use web_sys::{Document, Element, HtmlElement}; +use web_sys::{Document, HtmlElement}; // Removed unused Element import mod expression; mod level; diff --git a/src/games/numeracy/state.rs b/src/games/numeracy/state.rs index e69de29..b1ed459 100644 --- a/src/games/numeracy/state.rs +++ b/src/games/numeracy/state.rs @@ -0,0 +1,119 @@ +use super::{Expression, Level}; +use std::time::{Duration, Instant}; + +#[derive(Debug)] +pub struct GameState { + pub level: Level, + pub expressions: Vec, + pub selected_indices: Vec, + pub score: i32, + pub round_start: Option, + pub level_start: Option, + pub completed_rounds: u32, +} + +impl GameState { + pub fn new() -> Self { + let level = Level::new(1); + let expressions = level.generate_expressions(); + + Self { + level, + expressions, + selected_indices: Vec::new(), + score: 0, + round_start: None, + level_start: None, + completed_rounds: 0, + } + } + + pub fn start_level(&mut self) { + self.level_start = Some(Instant::now()); + self.start_round(); + } + + pub fn start_round(&mut self) { + self.expressions = self.level.generate_expressions(); + self.selected_indices.clear(); + self.round_start = Some(Instant::now()); + } + + pub fn toggle_selection(&mut self, index: usize) -> bool { + if let Some(pos) = self.selected_indices.iter().position(|&i| i == index) { + self.selected_indices.remove(pos); + true + } else if self.selected_indices.len() < 3 { + self.selected_indices.push(index); + true + } else { + false + } + } + + pub fn check_current_round(&self) -> bool { + if self.selected_indices.len() != 3 { + return false; + } + + let selected_expressions: Vec = self + .selected_indices + .iter() + .map(|&i| self.expressions[i].clone()) + .collect(); + + Level::check_order(&selected_expressions) + } + + pub fn get_round_time_remaining(&self) -> Option { + self.round_start.map(|start| { + let elapsed = start.elapsed(); + if elapsed >= Duration::from_secs(15) { + Duration::from_secs(0) + } else { + Duration::from_secs(15) - elapsed + } + }) + } + + pub fn get_level_time_remaining(&self) -> Option { + self.level_start.map(|start| { + let elapsed = start.elapsed(); + if elapsed >= Duration::from_secs(300) { + Duration::from_secs(0) + } else { + Duration::from_secs(300) - elapsed + } + }) + } + + pub fn update_score(&mut self, round_success: bool) { + let time_bonus = self.get_round_time_remaining() + .map(|t| t.as_secs() as i32) + .unwrap_or(0); + + self.score += if round_success { + 10 + time_bonus + } else { + -5 + }; + + self.completed_rounds += 1; + } + + pub fn should_adjust_level(&self) -> Option { + if self.completed_rounds >= 10 { + let success_rate = (self.score as f64) / (self.completed_rounds as f64); + + Some(if success_rate > 0.8 { + 1 + } else if success_rate < 0.4 { + -1 + } else { + 0 + }) + } else { + None + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4082f1d..afee30d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,10 @@ mod games; pub use games::perception::Perception; +pub use games::numeracy::Numeracy; use wasm_bindgen::{prelude::*, JsValue}; +use web_sys::window; + #[cfg(feature = "wee_alloc")] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; @@ -10,6 +13,16 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; pub fn main_js() -> Result<(), JsValue> { #[cfg(debug_assertions)] console_error_panic_hook::set_once(); - Perception::new()?; - Ok(()) -} + + // Check which page we're on + let window = web_sys::window().expect("no global window exists"); + let document = window.document().expect("no document on window"); + let location = window.location(); + let path = location.pathname().expect("pathname should exist"); + + // Return the created instance rather than discarding it + Ok(match path.as_str() { + "/numeracy.html" => { Numeracy::new()?; }, + _ => { Perception::new()?; }, + }) +} \ No newline at end of file From 6acb33fc1684fb3c70624aae147024e9f0b5399a Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sat, 8 Feb 2025 15:37:59 +0000 Subject: [PATCH 19/78] feat(numeracy): implement bubble rendering and stats update in numeracy game --- src/games/numeracy/mod.rs | 52 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/games/numeracy/mod.rs b/src/games/numeracy/mod.rs index a9d9766..8b08b3a 100644 --- a/src/games/numeracy/mod.rs +++ b/src/games/numeracy/mod.rs @@ -1,5 +1,5 @@ use wasm_bindgen::prelude::*; -use web_sys::{Document, HtmlElement}; // Removed unused Element import +use web_sys::{Document, HtmlElement, Event}; mod expression; mod level; @@ -33,4 +33,54 @@ impl Numeracy { container, }) } + + fn render_bubbles(&self) -> Result<(), JsValue> { + self.container.set_inner_html(""); + + for (i, expr) in self.state.expressions.iter().enumerate() { + let bubble = self.document.create_element("div")?; + bubble.set_class_name("bubble"); + bubble.set_text_content(Some(&expr.text)); + + let index = i.to_string(); + bubble.set_attribute("data-index", &index)?; + + if self.state.selected_indices.contains(&i) { + bubble.set_attribute("class", "bubble selected")?; + } + + let handler = Closure::wrap(Box::new(move |_event: Event| { + // Selection logic will be handled here + }) as Box); + + bubble.add_event_listener_with_callback("click", handler.as_ref().unchecked_ref())?; + handler.forget(); + + self.container.append_child(&bubble)?; + } + Ok(()) + } + + fn update_stats(&self) -> Result<(), JsValue> { + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + + if let Some(level_elem) = document.get_element_by_id("level") { + level_elem.set_text_content(Some(&self.state.level.number.to_string())); + } + + if let Some(score_elem) = document.get_element_by_id("score") { + score_elem.set_text_content(Some(&self.state.score.to_string())); + } + + Ok(()) + } + + #[wasm_bindgen] + pub fn start(&mut self) -> Result<(), JsValue> { + self.state.start_level(); + self.render_bubbles()?; + self.update_stats()?; + Ok(()) + } } \ No newline at end of file From 279eec5e8cdb91c8dd75e9e54ce1f33508c45dfd Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sat, 8 Feb 2025 16:35:38 +0000 Subject: [PATCH 20/78] feat(numeracy): enhance state management with Rc and add timer functionality --- src/games/numeracy/mod.rs | 110 ++++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 15 deletions(-) diff --git a/src/games/numeracy/mod.rs b/src/games/numeracy/mod.rs index 8b08b3a..d505552 100644 --- a/src/games/numeracy/mod.rs +++ b/src/games/numeracy/mod.rs @@ -1,5 +1,7 @@ use wasm_bindgen::prelude::*; -use web_sys::{Document, HtmlElement, Event}; +use web_sys::{Document, HtmlElement, Event, Element}; +use std::rc::Rc; +use std::cell::RefCell; mod expression; mod level; @@ -11,7 +13,7 @@ use state::GameState; #[wasm_bindgen] pub struct Numeracy { - state: GameState, + state: Rc>, // Changed to Rc> for shared ownership document: Document, container: HtmlElement, } @@ -28,7 +30,7 @@ impl Numeracy { .dyn_into::()?; Ok(Numeracy { - state: GameState::new(), + state: Rc::new(RefCell::new(GameState::new())), document, container, }) @@ -36,21 +38,36 @@ impl Numeracy { fn render_bubbles(&self) -> Result<(), JsValue> { self.container.set_inner_html(""); + let state_ref = self.state.borrow(); - for (i, expr) in self.state.expressions.iter().enumerate() { - let bubble = self.document.create_element("div")?; + for (i, expr) in state_ref.expressions.iter().enumerate() { + let bubble: Element = self.document.create_element("div")?; bubble.set_class_name("bubble"); bubble.set_text_content(Some(&expr.text)); let index = i.to_string(); bubble.set_attribute("data-index", &index)?; - if self.state.selected_indices.contains(&i) { + if state_ref.selected_indices.contains(&i) { bubble.set_attribute("class", "bubble selected")?; } + let state = self.state.clone(); + // Create a weak reference to the bubble + let bubble_ref = bubble.clone(); let handler = Closure::wrap(Box::new(move |_event: Event| { - // Selection logic will be handled here + let mut state = state.borrow_mut(); + if state.toggle_selection(i) { + let is_selected = state.selected_indices.contains(&i); + let class = if is_selected { "bubble selected" } else { "bubble" }; + bubble_ref.set_attribute("class", class).unwrap(); + + if state.selected_indices.len() == 3 { + let round_success = state.check_current_round(); + state.update_score(round_success); + state.start_round(); + } + } }) as Box); bubble.add_event_listener_with_callback("click", handler.as_ref().unchecked_ref())?; @@ -62,25 +79,88 @@ impl Numeracy { } fn update_stats(&self) -> Result<(), JsValue> { - let window = web_sys::window().unwrap(); - let document = window.document().unwrap(); + let state = self.state.borrow(); + + if let Some(level_elem) = self.document.get_element_by_id("level") { + level_elem.set_text_content(Some(&state.level.number.to_string())); + } - if let Some(level_elem) = document.get_element_by_id("level") { - level_elem.set_text_content(Some(&self.state.level.number.to_string())); + if let Some(score_elem) = self.document.get_element_by_id("score") { + score_elem.set_text_content(Some(&state.score.to_string())); } - if let Some(score_elem) = document.get_element_by_id("score") { - score_elem.set_text_content(Some(&self.state.score.to_string())); + Ok(()) + } + + fn update_timer(&self) -> Result<(), JsValue> { + let state = self.state.borrow(); + + if let Some(timer_elem) = self.document.get_element_by_id("timer") { + if let Some(remaining) = state.get_round_time_remaining() { + let seconds = remaining.as_secs(); + let text = format!("{}:{:02}", seconds / 60, seconds % 60); + timer_elem.set_text_content(Some(&text)); + } + } + Ok(()) + } + + fn start_timer(&self) -> Result<(), JsValue> { + let window = web_sys::window().unwrap(); + let state = self.state.clone(); + let document = self.document.clone(); + let container = self.container.clone(); + + let closure = Closure::wrap(Box::new(move || { + let this = Numeracy { + state: state.clone(), + document: document.clone(), + container: container.clone(), + }; + this.update_timer().unwrap(); + this.check_time_limits().unwrap(); + }) as Box); + + window.set_interval_with_callback_and_timeout_and_arguments_0( + closure.as_ref().unchecked_ref(), + 1000, + )?; + + closure.forget(); + Ok(()) + } + + fn check_time_limits(&self) -> Result<(), JsValue> { + { + let mut state = self.state.borrow_mut(); + + if state.get_round_time_remaining().unwrap().as_secs() == 0 { + state.update_score(false); + state.start_round(); + } + } // Release borrow before render_bubbles + self.render_bubbles()?; + + { + let mut state = self.state.borrow_mut(); + if state.get_level_time_remaining().unwrap().as_secs() == 0 { + if let Some(level_change) = state.should_adjust_level() { + let new_level = (state.level.number as i32 + level_change) as u32; + state.level = Level::new(new_level); + } + state.start_level(); + } } Ok(()) } #[wasm_bindgen] - pub fn start(&mut self) -> Result<(), JsValue> { - self.state.start_level(); + pub fn start(&self) -> Result<(), JsValue> { + self.state.borrow_mut().start_level(); self.render_bubbles()?; self.update_stats()?; + self.start_timer()?; Ok(()) } } \ No newline at end of file From 94c1648653c43c003ca0e6dbb61d57b9da07270b Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sat, 8 Feb 2025 18:16:09 +0000 Subject: [PATCH 21/78] feat(numeracy): update timer functionality to use performance.now() and adjust time calculations --- src/games/numeracy/mod.rs | 22 +++++++++++++--------- src/games/numeracy/state.rs | 37 +++++++++++++++++++++---------------- src/lib.rs | 2 +- static/numeracy.html | 2 +- 4 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/games/numeracy/mod.rs b/src/games/numeracy/mod.rs index d505552..b6e2d72 100644 --- a/src/games/numeracy/mod.rs +++ b/src/games/numeracy/mod.rs @@ -97,7 +97,7 @@ impl Numeracy { if let Some(timer_elem) = self.document.get_element_by_id("timer") { if let Some(remaining) = state.get_round_time_remaining() { - let seconds = remaining.as_secs(); + let seconds = (remaining / 1000.0) as u32; let text = format!("{}:{:02}", seconds / 60, seconds % 60); timer_elem.set_text_content(Some(&text)); } @@ -134,21 +134,25 @@ impl Numeracy { { let mut state = self.state.borrow_mut(); - if state.get_round_time_remaining().unwrap().as_secs() == 0 { - state.update_score(false); - state.start_round(); + if let Some(remaining) = state.get_round_time_remaining() { + if remaining <= 0.0 { + state.update_score(false); + state.start_round(); + } } } // Release borrow before render_bubbles self.render_bubbles()?; { let mut state = self.state.borrow_mut(); - if state.get_level_time_remaining().unwrap().as_secs() == 0 { - if let Some(level_change) = state.should_adjust_level() { - let new_level = (state.level.number as i32 + level_change) as u32; - state.level = Level::new(new_level); + if let Some(remaining) = state.get_level_time_remaining() { + if remaining <= 0.0 { + if let Some(level_change) = state.should_adjust_level() { + let new_level = (state.level.number as i32 + level_change) as u32; + state.level = Level::new(new_level); + } + state.start_level(); } - state.start_level(); } } diff --git a/src/games/numeracy/state.rs b/src/games/numeracy/state.rs index b1ed459..faf3595 100644 --- a/src/games/numeracy/state.rs +++ b/src/games/numeracy/state.rs @@ -1,5 +1,6 @@ use super::{Expression, Level}; -use std::time::{Duration, Instant}; +use wasm_bindgen::JsValue; +use web_sys::Performance; #[derive(Debug)] pub struct GameState { @@ -7,13 +8,16 @@ pub struct GameState { pub expressions: Vec, pub selected_indices: Vec, pub score: i32, - pub round_start: Option, - pub level_start: Option, + pub round_start: Option, + pub level_start: Option, pub completed_rounds: u32, + performance: Performance, } impl GameState { pub fn new() -> Self { + let window = web_sys::window().unwrap(); + let performance = window.performance().unwrap(); let level = Level::new(1); let expressions = level.generate_expressions(); @@ -25,18 +29,19 @@ impl GameState { round_start: None, level_start: None, completed_rounds: 0, + performance, } } pub fn start_level(&mut self) { - self.level_start = Some(Instant::now()); + self.level_start = Some(self.performance.now()); self.start_round(); } pub fn start_round(&mut self) { self.expressions = self.level.generate_expressions(); self.selected_indices.clear(); - self.round_start = Some(Instant::now()); + self.round_start = Some(self.performance.now()); } pub fn toggle_selection(&mut self, index: usize) -> bool { @@ -65,31 +70,31 @@ impl GameState { Level::check_order(&selected_expressions) } - pub fn get_round_time_remaining(&self) -> Option { + pub fn get_round_time_remaining(&self) -> Option { self.round_start.map(|start| { - let elapsed = start.elapsed(); - if elapsed >= Duration::from_secs(15) { - Duration::from_secs(0) + let elapsed = self.performance.now() - start; + if elapsed >= 15000.0 { + 0.0 } else { - Duration::from_secs(15) - elapsed + 15000.0 - elapsed } }) } - pub fn get_level_time_remaining(&self) -> Option { + pub fn get_level_time_remaining(&self) -> Option { self.level_start.map(|start| { - let elapsed = start.elapsed(); - if elapsed >= Duration::from_secs(300) { - Duration::from_secs(0) + let elapsed = self.performance.now() - start; + if elapsed >= 300000.0 { + 0.0 } else { - Duration::from_secs(300) - elapsed + 300000.0 - elapsed } }) } pub fn update_score(&mut self, round_success: bool) { let time_bonus = self.get_round_time_remaining() - .map(|t| t.as_secs() as i32) + .map(|t| (t / 1000.0) as i32) .unwrap_or(0); self.score += if round_success { diff --git a/src/lib.rs b/src/lib.rs index afee30d..ed77e2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ pub fn main_js() -> Result<(), JsValue> { // Return the created instance rather than discarding it Ok(match path.as_str() { - "/numeracy.html" => { Numeracy::new()?; }, + "/numeracy.html" => { let game = Numeracy::new()?; game.start()?; }, _ => { Perception::new()?; }, }) } \ No newline at end of file diff --git a/static/numeracy.html b/static/numeracy.html index 9f02582..1b93b96 100644 --- a/static/numeracy.html +++ b/static/numeracy.html @@ -61,6 +61,6 @@ Time: 5:00
- + \ No newline at end of file From 265da9ed39c5c7750d9f4772c25547291602d756 Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sat, 8 Feb 2025 18:53:54 +0000 Subject: [PATCH 22/78] feat(numeracy): integrate local storage for level persistence and update level management logic --- src/games/numeracy/mod.rs | 16 ++-------------- src/games/numeracy/state.rs | 29 +++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/games/numeracy/mod.rs b/src/games/numeracy/mod.rs index b6e2d72..7816df9 100644 --- a/src/games/numeracy/mod.rs +++ b/src/games/numeracy/mod.rs @@ -140,21 +140,9 @@ impl Numeracy { state.start_round(); } } - } // Release borrow before render_bubbles - self.render_bubbles()?; - - { - let mut state = self.state.borrow_mut(); - if let Some(remaining) = state.get_level_time_remaining() { - if remaining <= 0.0 { - if let Some(level_change) = state.should_adjust_level() { - let new_level = (state.level.number as i32 + level_change) as u32; - state.level = Level::new(new_level); - } - state.start_level(); - } - } } + self.render_bubbles()?; + self.update_stats()?; Ok(()) } diff --git a/src/games/numeracy/state.rs b/src/games/numeracy/state.rs index faf3595..ab6a496 100644 --- a/src/games/numeracy/state.rs +++ b/src/games/numeracy/state.rs @@ -1,6 +1,6 @@ use super::{Expression, Level}; use wasm_bindgen::JsValue; -use web_sys::Performance; +use web_sys::{Performance, Storage, Window}; #[derive(Debug)] pub struct GameState { @@ -12,15 +12,23 @@ pub struct GameState { pub level_start: Option, pub completed_rounds: u32, performance: Performance, + storage: Storage, } impl GameState { pub fn new() -> Self { let window = web_sys::window().unwrap(); - let performance = window.performance().unwrap(); - let level = Level::new(1); - let expressions = level.generate_expressions(); + let storage = window.local_storage().unwrap().unwrap(); + let level_number = storage + .get_item("numeracy_level") + .unwrap() + .and_then(|s| s.parse().ok()) + .unwrap_or(1); + let level = Level::new(level_number); + let expressions = level.generate_expressions(); + let performance = window.performance().unwrap(); + Self { level, expressions, @@ -30,6 +38,7 @@ impl GameState { level_start: None, completed_rounds: 0, performance, + storage, } } @@ -98,8 +107,20 @@ impl GameState { .unwrap_or(0); self.score += if round_success { + // Evaluate level change after each round + if self.score > 0 { + let new_level = self.level.number + 1; + self.level = Level::new(new_level); + self.storage.set_item("numeracy_level", &new_level.to_string()).unwrap(); + } 10 + time_bonus } else { + // Drop a level on failure, but not below 1 + if self.level.number > 1 { + let new_level = self.level.number - 1; + self.level = Level::new(new_level); + self.storage.set_item("numeracy_level", &new_level.to_string()).unwrap(); + } -5 }; From 980ad09e2735b7c23cb7cdae4c0836abb248401a Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sun, 9 Feb 2025 12:25:34 +0000 Subject: [PATCH 23/78] Revert "Refactor Perception module: clean up state management and input handling" This reverts commit c85684585b03e2926bd186bd14e69135f4c8d164. --- src/games/perception/input.rs | 106 +++++------ src/games/perception/maze.rs | 305 ++++++++++++++++--------------- src/games/perception/mod.rs | 224 +++++++++++------------ src/games/perception/movement.rs | 182 +++++++++--------- src/games/perception/render.rs | 193 ++++++++++--------- src/games/perception/state.rs | 52 ++++-- src/games/perception/timer.rs | 137 +++++++------- tests/app.rs | 12 +- 8 files changed, 616 insertions(+), 595 deletions(-) diff --git a/src/games/perception/input.rs b/src/games/perception/input.rs index 4cdc965..d8dd3ad 100644 --- a/src/games/perception/input.rs +++ b/src/games/perception/input.rs @@ -1,53 +1,53 @@ -use super::Perception; -use std::{cell::RefCell, rc::Rc}; -use wasm_bindgen::prelude::*; -use web_sys::Element; - -impl Perception { - pub(super) fn setup_click_handler(game_state: Rc>) -> Result<(), JsValue> { - let click_handler = { - let game_state = game_state.clone(); - Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { - if let Ok(mut game) = game_state.try_borrow_mut() { - if let Some(target) = event.target() { - if let Some(element) = target.dyn_ref::() { - if let Ok(Some(maze_el)) = element.closest("#maze") { - // Find clicked cell index - let children = maze_el.children(); - let cell_index = (0..children.length()) - .find(|&i| { - children - .item(i) - .map(|cell| cell.is_same_node(Some(element))) - .unwrap_or(false) - }) - .unwrap_or(0) - as usize; - - let size = game.size; - let x = cell_index % size; - let y = cell_index / size; - - let result = game.try_move(x, y); - if result != 0 { - game.render().unwrap(); - } - } - } - } - } - }) as Box) - }; - - // Attach single click handler to maze container - if let Some(maze_el) = game_state.borrow().document.get_element_by_id("maze") { - maze_el.add_event_listener_with_callback( - "click", - click_handler.as_ref().unchecked_ref(), - )?; - click_handler.forget(); - } - - Ok(()) - } -} +use super::Perception; +use std::{cell::RefCell, rc::Rc}; +use wasm_bindgen::prelude::*; +use web_sys::Element; + +impl Perception { + pub(super) fn setup_click_handler(game_state: Rc>) -> Result<(), JsValue> { + let click_handler = { + let game_state = game_state.clone(); + Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { + if let Ok(mut game) = game_state.try_borrow_mut() { + if let Some(target) = event.target() { + if let Some(element) = target.dyn_ref::() { + if let Ok(Some(maze_el)) = element.closest("#maze") { + // Find clicked cell index + let children = maze_el.children(); + let cell_index = (0..children.length()) + .find(|&i| { + children + .item(i) + .map(|cell| cell.is_same_node(Some(element))) + .unwrap_or(false) + }) + .unwrap_or(0) + as usize; + + let size = game.size; + let x = cell_index % size; + let y = cell_index / size; + + let result = game.try_move(x, y); + if result != 0 { + game.render().unwrap(); + } + } + } + } + } + }) as Box) + }; + + // Attach single click handler to maze container + if let Some(maze_el) = game_state.borrow().document.get_element_by_id("maze") { + maze_el.add_event_listener_with_callback( + "click", + click_handler.as_ref().unchecked_ref(), + )?; + click_handler.forget(); + } + + Ok(()) + } +} \ No newline at end of file diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index b27d201..7baa441 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -1,149 +1,156 @@ -use std::collections::HashSet; - -use super::Perception; -use js_sys::Math; -use web_sys::Document; - -impl Perception { - pub(super) fn create_maze(size: usize, document: Document) -> Self { - let mut walls = vec![false; size * size * 4]; // Start with no walls - // Add random walls - for i in 0..walls.len() { - walls[i] = Math::random() < 0.5; - } - - let (waypoint1, key_position, waypoint2, door_position) = if size == 2 { - let key_pos = loop { - let pos = ( - (Math::random() * 2.0).floor() as usize, - (Math::random() * 2.0).floor() as usize, - ); - if pos != (0, 0) { - break pos; - }; - }; - // Get random non-start, non-key position for door - let door_pos = loop { - let pos = ( - (Math::random() * 2.0).floor() as usize, - (Math::random() * 2.0).floor() as usize, - ); - if pos != (0, 0) && pos != key_pos { - break pos; - } - }; - (key_pos, key_pos, door_pos, door_pos) - } else { - let (mut key_pos, mut door_pos); - loop { - key_pos = ( - ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, - ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, - ); - - door_pos = ( - ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, - ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, - ); - - if key_pos != door_pos && key_pos != (0, 0) && door_pos != (0, 0) { - break; - } - } - ( - ( - (Math::random() * (size as f64)).floor() as usize, - (Math::random() * (size as f64)).floor() as usize, - ), - key_pos, - ( - (Math::random() * (size as f64)).floor() as usize, - (Math::random() * (size as f64)).floor() as usize, - ), - door_pos, - ) - }; - - let mut game = Self { - size, - walls, - current_position: (0, 0), - key_position, - door_position, - visited: HashSet::new(), - has_key: false, - level: 1, - mazes_completed: 0, - document, - time_remaining: 300, - last_tick: js_sys::Date::now() / 1000.0, - }; - // Create path through waypoints - clear_path(&mut game.walls, (0, 0), waypoint1, size); - clear_path(&mut game.walls, waypoint1, key_position, size); - clear_path(&mut game.walls, key_position, waypoint2, size); - clear_path(&mut game.walls, waypoint2, door_position, size); - game.visited.insert((0, 0)); - game - } -} -fn clear_path(walls: &mut Vec, from: (usize, usize), to: (usize, usize), size: usize) { - let mut current = from; - // Calculate minimum required path length (Manhattan distance * 1.5) - while current != to { - let dx = (to.0 as i32 - current.0 as i32).signum(); - let dy = (to.1 as i32 - current.1 as i32).signum(); - // Clear both current cell's wall and neighbor's wall - if dx != 0 { - let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; - walls[wall_idx] = false; - // Clear adjacent cell's opposite wall if not at edge - if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { - let next_x = (current.0 as i32 + dx) as usize; - let adj_wall_idx = (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; - walls[adj_wall_idx] = false; - - // Always clear an escape route (up or down) - let escape_dir = if current.1 > 0 { 0 } else { 2 }; // up if not at top, down otherwise - walls[(current.1 * size + current.0) * 4 + escape_dir] = false; - if escape_dir == 0 && current.1 > 0 { - // Clear the corresponding wall in the cell above - walls[((current.1 - 1) * size + current.0) * 4 + 2] = false; - } else if escape_dir == 2 && current.1 + 1 < size { - // Clear the corresponding wall in the cell below - walls[((current.1 + 1) * size + current.0) * 4] = false; - } - } - current.0 = (current.0 as i32 + dx) as usize; - } else if dy != 0 { - let wall_idx = (current.1 * size + current.0) * 4 + if dy > 0 { 2 } else { 0 }; - walls[wall_idx] = false; - // Clear adjacent cell's opposite wall if not at edge - if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { - let next_y = (current.1 as i32 + dy) as usize; - let adj_wall_idx = (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; - walls[adj_wall_idx] = false; - } - current.1 = (current.1 as i32 + dy) as usize; - } - // Ensure escape route from the destination - let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right - for (dx, dy) in escape_dirs.iter() { - let next_x = to.0 as i32 + dx; - let next_y = to.1 as i32 + dy; - if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { - let wall_idx = (to.1 * size + to.0) * 4 - + if *dy < 0 { - 0 - } else if *dx > 0 { - 1 - } else if *dy > 0 { - 2 - } else { - 3 - }; - walls[wall_idx] = false; - } - } - } -} +use std::collections::HashSet; + +use js_sys::Math; +use web_sys::Document; +use super::Perception; + +impl Perception { + pub(super) fn create_maze(size: usize, document: Document) -> Self { + let mut walls = vec![false; size * size * 4]; // Start with no walls + // Add random walls + for i in 0..walls.len() { + walls[i] = Math::random() < 0.5; + } + + let (waypoint1, key_position, waypoint2, door_position) = if size == 2 { + let key_pos = loop { + let pos = ( + (Math::random() * 2.0).floor() as usize, + (Math::random() * 2.0).floor() as usize, + ); + if pos != (0, 0) { + break pos; + }; + }; + // Get random non-start, non-key position for door + let door_pos = loop { + let pos = ( + (Math::random() * 2.0).floor() as usize, + (Math::random() * 2.0).floor() as usize, + ); + if pos != (0, 0) && pos != key_pos { + break pos; + } + }; + (key_pos, key_pos, door_pos, door_pos) + } else { + let (mut key_pos, mut door_pos); + loop { + key_pos = ( + ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, + ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, + ); + + door_pos = ( + ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, + ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, + ); + + if key_pos != door_pos && key_pos != (0, 0) && door_pos != (0, 0) { + break; + } + } + ( + ( + (Math::random() * (size as f64)).floor() as usize, + (Math::random() * (size as f64)).floor() as usize, + ), + key_pos, + ( + (Math::random() * (size as f64)).floor() as usize, + (Math::random() * (size as f64)).floor() as usize, + ), + door_pos, + ) + }; + + let mut game = Self { + size, + walls, + current_position: (0, 0), + key_position, + door_position, + visited: HashSet::new(), + has_key: false, + level: 1, + mazes_completed: 0, + document, + time_remaining: 300, + last_tick: js_sys::Date::now() / 1000.0, + }; + // Create path through waypoints + clear_path(&mut game.walls, (0, 0), waypoint1, size); + clear_path(&mut game.walls, waypoint1, key_position, size); + clear_path(&mut game.walls, key_position, waypoint2, size); + clear_path(&mut game.walls, waypoint2, door_position, size); + game.visited.insert((0, 0)); + game + } +} +fn clear_path( + walls: &mut Vec, + from: (usize, usize), + to: (usize, usize), + size: usize, +) { + let mut current = from; + // Calculate minimum required path length (Manhattan distance * 1.5) + while current != to { + let dx = (to.0 as i32 - current.0 as i32).signum(); + let dy = (to.1 as i32 - current.1 as i32).signum(); + // Clear both current cell's wall and neighbor's wall + if dx != 0 { + let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; + walls[wall_idx] = false; + // Clear adjacent cell's opposite wall if not at edge + if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { + let next_x = (current.0 as i32 + dx) as usize; + let adj_wall_idx = + (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; + walls[adj_wall_idx] = false; + + // Always clear an escape route (up or down) + let escape_dir = if current.1 > 0 { 0 } else { 2 }; // up if not at top, down otherwise + walls[(current.1 * size + current.0) * 4 + escape_dir] = false; + if escape_dir == 0 && current.1 > 0 { + // Clear the corresponding wall in the cell above + walls[((current.1 - 1) * size + current.0) * 4 + 2] = false; + } else if escape_dir == 2 && current.1 + 1 < size { + // Clear the corresponding wall in the cell below + walls[((current.1 + 1) * size + current.0) * 4 + 0] = false; + } + } + current.0 = (current.0 as i32 + dx) as usize; + } else if dy != 0 { + let wall_idx = (current.1 * size + current.0) * 4 + if dy > 0 { 2 } else { 0 }; + walls[wall_idx] = false; + // Clear adjacent cell's opposite wall if not at edge + if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { + let next_y = (current.1 as i32 + dy) as usize; + let adj_wall_idx = + (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; + walls[adj_wall_idx] = false; + } + current.1 = (current.1 as i32 + dy) as usize; + } + // Ensure escape route from the destination + let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right + for (dx, dy) in escape_dirs.iter() { + let next_x = to.0 as i32 + dx; + let next_y = to.1 as i32 + dy; + if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { + let wall_idx = (to.1 * size + to.0) * 4 + + if *dy < 0 { + 0 + } else if *dx > 0 { + 1 + } else if *dy > 0 { + 2 + } else { + 3 + }; + walls[wall_idx] = false; + } + } + } +} diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index 6565919..ba5fdf2 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -1,112 +1,112 @@ -mod input; -mod maze; -mod movement; -mod render; -mod state; -mod timer; - -use serde::{Deserialize, Serialize}; -use std::{cell::RefCell, collections::HashSet, rc::Rc}; -use wasm_bindgen::prelude::*; -use web_sys::{console, Document}; - -fn get_document() -> Document { - web_sys::window() - .expect("no global window exists") - .document() - .expect("no document exists") -} - -#[wasm_bindgen] -#[derive(Clone, Serialize, Deserialize)] -pub struct Perception { - // Game state - size: usize, - level: usize, - #[serde(default)] - mazes_completed: usize, - - // Maze elements - walls: Vec, - current_position: (usize, usize), - key_position: (usize, usize), - door_position: (usize, usize), - visited: HashSet<(usize, usize)>, - has_key: bool, - - // Timer state - time_remaining: i32, - last_tick: f64, - - #[serde(skip, default = "get_document")] - document: Document, -} - -#[wasm_bindgen] -impl Perception { - #[wasm_bindgen(constructor)] - pub fn new() -> Result { - let document = get_document(); - let storage = web_sys::window() - .expect("no global window exists") - .local_storage()? - .expect("no local storage"); - - let mut game = if let Some(state) = storage.get_item("maze_state")? { - let last_save = storage - .get_item("maze_time")? - .unwrap_or_else(|| "0".to_string()) - .parse::() - .unwrap_or(0.0); - - if js_sys::Date::now() - last_save > 300000.0 { - Self::create_maze(2, document) - } else { - serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)? - } - } else { - Self::create_maze(2, document) - }; - - game.render()?; - game.start()?; - Ok(game) - } - #[wasm_bindgen] - pub fn start(&mut self) -> Result<(), JsValue> { - let game_state = Rc::new(RefCell::new(self.clone())); - - Self::setup_click_handler(game_state.clone())?; - Self::setup_timer(game_state)?; - - console::log_1(&"Setup complete".into()); - Ok(()) - } - #[wasm_bindgen] - pub fn reset(&mut self) { - let new_game = Self::create_maze(self.size, self.document.clone()); - self.walls = new_game.walls; - self.key_position = new_game.key_position; - self.door_position = new_game.door_position; - self.reset_position(); - - // Reset timer state completely - self.time_remaining = 300; - self.last_tick = js_sys::Date::now() / 1000.0; - - // Force timer display update - if let Some(timer_el) = self.document.get_element_by_id("timer") { - timer_el.set_text_content(Some("5:00")); - } - - // Update display - self.render().expect("Failed to render reset"); - } - fn reset_position(&mut self) { - self.current_position = (0, 0); - self.visited.clear(); - self.visited.insert((0, 0)); - self.has_key = false; - self.render().expect("Failed to render position reset"); - } -} +mod state; +mod render; +mod maze; +mod movement; +mod timer; +mod input; + +use serde::{Deserialize, Serialize}; +use std::{cell::RefCell, collections::HashSet, rc::Rc}; +use wasm_bindgen::prelude::*; +use web_sys::{console, Document, Element}; + +fn get_document() -> Document { + web_sys::window() + .expect("no global window exists") + .document() + .expect("no document exists") +} + +#[wasm_bindgen] +#[derive(Clone, Serialize, Deserialize)] +pub struct Perception { + // Game state + size: usize, + level: usize, + #[serde(default)] + mazes_completed: usize, + + // Maze elements + walls: Vec, + current_position: (usize, usize), + key_position: (usize, usize), + door_position: (usize, usize), + visited: HashSet<(usize, usize)>, + has_key: bool, + + // Timer state + time_remaining: i32, + last_tick: f64, + + #[serde(skip, default = "get_document")] + document: Document, +} + +#[wasm_bindgen] +impl Perception { + #[wasm_bindgen(constructor)] + pub fn new() -> Result { + let document = get_document(); + let storage = web_sys::window() + .expect("no global window exists") + .local_storage()? + .expect("no local storage"); + + let mut game = if let Some(state) = storage.get_item("maze_state")? { + let last_save = storage + .get_item("maze_time")? + .unwrap_or_else(|| "0".to_string()) + .parse::() + .unwrap_or(0.0); + + if js_sys::Date::now() - last_save > 300000.0 { + Self::create_maze(2, document) + } else { + serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)? + } + } else { + Self::create_maze(2, document) + }; + + game.render()?; + game.start()?; + Ok(game) + } + #[wasm_bindgen] + pub fn start(&mut self) -> Result<(), JsValue> { + let game_state = Rc::new(RefCell::new(self.clone())); + + Self::setup_click_handler(game_state.clone())?; + Self::setup_timer(game_state)?; + + console::log_1(&"Setup complete".into()); + Ok(()) + } + #[wasm_bindgen] + pub fn reset(&mut self) { + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.reset_position(); + + // Reset timer state completely + self.time_remaining = 300; + self.last_tick = js_sys::Date::now() / 1000.0; + + // Force timer display update + if let Some(timer_el) = self.document.get_element_by_id("timer") { + timer_el.set_text_content(Some("5:00")); + } + + // Update display + self.render().expect("Failed to render reset"); + } + fn reset_position(&mut self) { + self.current_position = (0, 0); + self.visited.clear(); + self.visited.insert((0, 0)); + self.has_key = false; + self.render().expect("Failed to render position reset"); + } +} \ No newline at end of file diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index b2b7930..6925b34 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -1,94 +1,88 @@ -use super::Perception; - -impl Perception { - pub(super) fn is_adjacent(&self, x: usize, y: usize) -> bool { - let current_x = self.current_position.0; - let current_y = self.current_position.1; - - // Check if target position is adjacent (up, down, left, right) - let dx = if x >= current_x { - x - current_x - } else { - current_x - x - }; - let dy = if y >= current_y { - y - current_y - } else { - current_y - y - }; - - // Only one coordinate can change by 1, the other must be 0 - (dx == 1 && dy == 0) || (dx == 0 && dy == 1) - } - - pub(super) fn get_wall_index( - &self, - from_x: usize, - from_y: usize, - to_x: usize, - to_y: usize, - ) -> usize { - let cell_walls = 4; // each cell has 4 possible walls - let base_index = (from_y * self.size + from_x) * cell_walls; - - if to_x > from_x { - base_index + 1 // right wall - } else if to_x < from_x { - base_index + 3 // left wall - } else if to_y > from_y { - base_index + 2 // bottom wall - } else { - base_index // top wall - } - } - - pub(super) fn try_move(&mut self, x: usize, y: usize) -> i32 { - if !self.is_adjacent(x, y) { - return 0; - } - - let wall_idx = self.get_wall_index(self.current_position.0, self.current_position.1, x, y); - - // Block access to door position if key not collected - if (x, y) == self.door_position && !self.has_key { - return 0; - } - - if self.walls[wall_idx] { - self.reset_position(); - return -1; - } - - self.current_position = (x, y); - self.visited.insert((x, y)); - - if (x, y) == self.key_position { - self.has_key = true; - // When key is collected, make door accessible - let door_x = self.door_position.0; - let door_y = self.door_position.1; - let base_idx = (door_y * self.size + door_x) * 4; - for i in 0..4 { - self.walls[base_idx + i] = false; - } - } - - if (x, y) == self.door_position && self.has_key { - // Simplified level up - increase size immediately - self.size += 1; - self.level += 1; - let new_game = Self::create_maze(self.size, self.document.clone()); - self.walls = new_game.walls; - self.current_position = (0, 0); - self.key_position = new_game.key_position; - self.door_position = new_game.door_position; - self.visited.clear(); - self.visited.insert((0, 0)); - self.has_key = false; - self.time_remaining = 300; - self.last_tick = js_sys::Date::now() / 1000.0; - return 2; - } - 1 - } -} +use super::Perception; + +impl Perception { + pub(super) fn is_adjacent(&self, x: usize, y: usize) -> bool { + let current_x = self.current_position.0; + let current_y = self.current_position.1; + + // Check if target position is adjacent (up, down, left, right) + let dx = if x >= current_x { + x - current_x + } else { + current_x - x + }; + let dy = if y >= current_y { + y - current_y + } else { + current_y - y + }; + + // Only one coordinate can change by 1, the other must be 0 + (dx == 1 && dy == 0) || (dx == 0 && dy == 1) + } + + pub(super) fn get_wall_index(&self, from_x: usize, from_y: usize, to_x: usize, to_y: usize) -> usize { + let cell_walls = 4; // each cell has 4 possible walls + let base_index = (from_y * self.size + from_x) * cell_walls; + + if to_x > from_x { + base_index + 1 // right wall + } else if to_x < from_x { + base_index + 3 // left wall + } else if to_y > from_y { + base_index + 2 // bottom wall + } else { + base_index + 0 // top wall + } + } + + pub(super) fn try_move(&mut self, x: usize, y: usize) -> i32 { + if !self.is_adjacent(x, y) { + return 0; + } + + let wall_idx = self.get_wall_index(self.current_position.0, self.current_position.1, x, y); + + // Block access to door position if key not collected + if (x, y) == self.door_position && !self.has_key { + return 0; + } + + if self.walls[wall_idx] { + self.reset_position(); + return -1; + } + + self.current_position = (x, y); + self.visited.insert((x, y)); + + if (x, y) == self.key_position { + self.has_key = true; + // When key is collected, make door accessible + let door_x = self.door_position.0; + let door_y = self.door_position.1; + let base_idx = (door_y * self.size + door_x) * 4; + for i in 0..4 { + self.walls[base_idx + i] = false; + } + } + + if (x, y) == self.door_position && self.has_key { + // Simplified level up - increase size immediately + self.size += 1; + self.level += 1; + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.current_position = (0, 0); + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.visited.clear(); + self.visited.insert((0, 0)); + self.has_key = false; + self.time_remaining = 300; + self.last_tick = js_sys::Date::now() / 1000.0; + return 2; + } + 1 + } +} \ No newline at end of file diff --git a/src/games/perception/render.rs b/src/games/perception/render.rs index 88d4ca1..52987b0 100644 --- a/src/games/perception/render.rs +++ b/src/games/perception/render.rs @@ -1,98 +1,95 @@ -use super::Perception; -use wasm_bindgen::prelude::*; -use web_sys::Element; - -impl Perception { - pub(crate) fn render(&self) -> Result<(), JsValue> { - let maze = self.document.get_element_by_id("maze").unwrap(); - - // Only regenerate grid if size changed - if maze.children().length() as usize != self.size * self.size { - maze.set_attribute( - "style", - &format!("grid-template-columns: repeat({}, 60px)", self.size), - )?; - - // Clear existing content safely - while let Some(child) = maze.first_child() { - maze.remove_child(&child)?; - } - - // Create cells only once - for _ in 0..(self.size * self.size) { - let cell = self.document.create_element("div")?; - cell.set_class_name("cell"); - let span = self.document.create_element("span")?; - let content = self.document.create_text_node(""); - cell.append_child(&content)?; - cell.append_child(&span)?; - maze.append_child(&cell)?; - } - } - - // Update existing cells - for y in 0..self.size { - for x in 0..self.size { - let index = (y * self.size + x) as u32; - if let Some(cell) = maze.children().item(index) { - self.update_cell_state(&cell, x, y)?; - } - } - } - - // Update stats - if let Some(level_el) = self.document.get_element_by_id("level") { - level_el.set_text_content(Some(&self.level.to_string())); - } - if let Some(completed_el) = self.document.get_element_by_id("completed") { - completed_el.set_text_content(Some(&self.mazes_completed.to_string())); - } - if let Some(timer_el) = self.document.get_element_by_id("timer") { - let minutes = self.time_remaining / 60; - let seconds = self.time_remaining % 60; - timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); - } - Ok(()) - } - - fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { - // Reset base class - cell.set_class_name("cell"); - - // Update state classes - if self.visited.contains(&(x, y)) { - cell.class_list().add_1("visited")?; - } - if (x, y) == self.current_position { - cell.class_list().add_1("current")?; - // Ensure span exists for pseudo-elements - if cell.children().length() == 0 { - let span = self.document.create_element("span")?; - cell.append_child(&span)?; - } - } - - let content = if (x, y) == self.key_position && !self.has_key { - "πŸ”‘" - } else if (x, y) == self.door_position { - "πŸšͺ" - } else if (x, y) == self.current_position { - if self.has_key { - "πŸ”‘" - } else { - "πŸ‘€" - } - } else { - "" - }; - - // Update text content if it's different - if let Some(first_child) = cell.first_child() { - if first_child.text_content().unwrap_or_default() != content { - first_child.set_text_content(Some(content)); - } - } - - Ok(()) - } -} +use super::Perception; +use wasm_bindgen::prelude::*; +use web_sys::Element; + +impl Perception { + pub(crate) fn render(&self) -> Result<(), JsValue> { + let maze = self.document.get_element_by_id("maze").unwrap(); + + // Only regenerate grid if size changed + if maze.children().length() as usize != self.size * self.size { + maze.set_attribute( + "style", + &format!("grid-template-columns: repeat({}, 60px)", self.size), + )?; + + // Clear existing content safely + while let Some(child) = maze.first_child() { + maze.remove_child(&child)?; + } + + // Create cells only once + for _ in 0..(self.size * self.size) { + let cell = self.document.create_element("div")?; + cell.set_class_name("cell"); + let span = self.document.create_element("span")?; + let content = self.document.create_text_node(""); + cell.append_child(&content)?; + cell.append_child(&span)?; + maze.append_child(&cell)?; + } + } + + // Update existing cells + for y in 0..self.size { + for x in 0..self.size { + let index = (y * self.size + x) as u32; + if let Some(cell) = maze.children().item(index) { + self.update_cell_state(&cell, x, y)?; + } + } + } + + // Update stats + if let Some(level_el) = self.document.get_element_by_id("level") { + level_el.set_text_content(Some(&self.level.to_string())); + } + if let Some(completed_el) = self.document.get_element_by_id("completed") { + completed_el.set_text_content(Some(&self.mazes_completed.to_string())); + } + if let Some(timer_el) = self.document.get_element_by_id("timer") { + let minutes = self.time_remaining / 60; + let seconds = self.time_remaining % 60; + timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); + } + Ok(()) + } + + fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { + // Reset base class + cell.set_class_name("cell"); + + // Update state classes + if self.visited.contains(&(x, y)) { + cell.class_list().add_1("visited")?; + } + if (x, y) == self.current_position { + cell.class_list().add_1("current")?; + // Ensure span exists for pseudo-elements + if cell.children().length() == 0 { + let span = self.document.create_element("span")?; + cell.append_child(&span)?; + } + } + + // Update content + let content = if (x, y) == self.key_position && !self.has_key { + "πŸ”‘" + } else if (x, y) == self.current_position && self.has_key { + "πŸ”‘" + } else if (x, y) == self.door_position { + "πŸšͺ" + } else { + "" + }; + + // Update text content if it's different + if let Some(first_child) = cell.first_child() { + if first_child.text_content().unwrap_or_default() != content { + first_child.set_text_content(Some(content)); + } + } + + Ok(()) + } +} \ No newline at end of file diff --git a/src/games/perception/state.rs b/src/games/perception/state.rs index 1f2200d..64ce695 100644 --- a/src/games/perception/state.rs +++ b/src/games/perception/state.rs @@ -1,18 +1,34 @@ -use super::Perception; -use js_sys::Date; -use wasm_bindgen::prelude::*; - -impl Perception { - pub(super) fn save_state(&self) -> Result<(), JsValue> { - let window = web_sys::window().expect("no global window exists"); - let storage = window.local_storage()?.expect("no local storage exists"); - - let state = serde_wasm_bindgen::to_value(&self)?; - let state_json = js_sys::JSON::stringify(&state)?.as_string().unwrap(); - storage.set_item("maze_state", &state_json)?; - storage.set_item("maze_time", &Date::now().to_string())?; - storage.set_item("maze_level", &self.level.to_string())?; - - Ok(()) - } -} +use super::Perception; +use js_sys::Date; +use wasm_bindgen::prelude::*; +use web_sys::Storage; + +impl Perception { + pub(super) fn load_state(storage: &Storage) -> Result, JsValue> { + if let Some(state) = storage.get_item("maze_state")? { + let last_save = storage + .get_item("maze_time")? + .unwrap_or_else(|| "0".to_string()) + .parse::() + .unwrap_or(0.0); + + let game: Self = serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)?; + Ok(Some((game, last_save))) + } else { + Ok(None) + } + } + + pub(super) fn save_state(&self) -> Result<(), JsValue> { + let window = web_sys::window().expect("no global window exists"); + let storage = window.local_storage()?.expect("no local storage exists"); + + let state = serde_wasm_bindgen::to_value(&self)?; + let state_json = js_sys::JSON::stringify(&state)?.as_string().unwrap(); + storage.set_item("maze_state", &state_json)?; + storage.set_item("maze_time", &Date::now().to_string())?; + storage.set_item("maze_level", &self.level.to_string())?; + + Ok(()) + } +} \ No newline at end of file diff --git a/src/games/perception/timer.rs b/src/games/perception/timer.rs index 91a911b..ef95529 100644 --- a/src/games/perception/timer.rs +++ b/src/games/perception/timer.rs @@ -1,67 +1,70 @@ -use super::Perception; -use std::{cell::RefCell, rc::Rc}; -use wasm_bindgen::prelude::*; -use web_sys::console; - -impl Perception { - pub(super) fn setup_timer(game_state: Rc>) -> Result<(), JsValue> { - let timer_callback = { - let game_state = game_state.clone(); - let performance = web_sys::window() - .unwrap() - .performance() - .expect("performance should be available"); - - Closure::wrap(Box::new(move || { - if let Ok(mut game) = game_state.try_borrow_mut() { - let now = performance.now() / 1000.0; - let delta = (now - game.last_tick) as i32; - if delta >= 1 { - game.update_timer(now); - } - } - }) as Box) - }; - - let window = web_sys::window().unwrap(); - - window.set_interval_with_callback_and_timeout_and_arguments_0( - timer_callback.as_ref().unchecked_ref(), - 1000, - )?; - timer_callback.forget(); - Ok(()) - } - fn update_timer(&mut self, now: f64) { - self.time_remaining -= 1; - self.last_tick = now; - - if self.time_remaining <= 0 { - self.reset_on_timeout(now); - } - - self.update_timer_display(); - self.save_state().unwrap_or_else(|_| { - console::log_1(&"Failed to save game state".into()); - }); - } - - fn update_timer_display(&self) { - if let Some(timer_el) = self.document.get_element_by_id("timer") { - let minutes = self.time_remaining / 60; - let seconds = self.time_remaining % 60; - timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); - } - } - - fn reset_on_timeout(&mut self, now: f64) { - let new_game = Self::create_maze(self.size, self.document.clone()); - self.walls = new_game.walls; - self.key_position = new_game.key_position; - self.door_position = new_game.door_position; - self.reset_position(); - self.time_remaining = 300; - self.last_tick = now; - self.render().unwrap(); - } -} +use wasm_bindgen::prelude::*; +use web_sys::{console, Document}; +use std::{rc::Rc, cell::RefCell}; +use super::Perception; + +impl Perception { + pub(super) fn setup_timer(game_state: Rc>) -> Result<(), JsValue> { + let timer_callback = { + let game_state = game_state.clone(); + Closure::wrap(Box::new(move || { + if let Ok(mut game) = game_state.try_borrow_mut() { + let now = js_sys::Date::now() / 1000.0; + let delta = (now - game.last_tick) as i32; + if delta >= 1 { + game.update_timer(now); + } + } + }) as Box) + }; + + let window = web_sys::window().unwrap(); + console::log_1(&"Setting up interval...".into()); + + let result = window.set_interval_with_callback_and_timeout_and_arguments_0( + timer_callback.as_ref().unchecked_ref(), + 1000, + ); + + match result { + Ok(_) => console::log_1(&"Interval set up successfully".into()), + Err(e) => console::log_2(&"Failed to set up interval:".into(), &e), + } + + timer_callback.forget(); + Ok(()) + } + + fn update_timer(&mut self, now: f64) { + self.time_remaining -= 1; + self.last_tick = now; + + if self.time_remaining <= 0 { + self.reset_on_timeout(now); + } + + self.update_timer_display(); + self.save_state().unwrap_or_else(|_| { + console::log_1(&"Failed to save game state".into()); + }); + } + + fn update_timer_display(&self) { + if let Some(timer_el) = self.document.get_element_by_id("timer") { + let minutes = self.time_remaining / 60; + let seconds = self.time_remaining % 60; + timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); + } + } + + fn reset_on_timeout(&mut self, now: f64) { + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.reset_position(); + self.time_remaining = 300; + self.last_tick = now; + self.render().unwrap(); + } +} \ No newline at end of file diff --git a/tests/app.rs b/tests/app.rs index eb6aacb..9223aa3 100644 --- a/tests/app.rs +++ b/tests/app.rs @@ -1,22 +1,25 @@ +use wasm_bindgen_test::{wasm_bindgen_test_configure, wasm_bindgen_test}; use futures::prelude::*; use wasm_bindgen::JsValue; use wasm_bindgen_futures::JsFuture; -use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure}; wasm_bindgen_test_configure!(run_in_browser); + // This runs a unit test in native Rust, so it can only use Rust APIs. #[test] fn rust_test() { assert_eq!(1, 1); } + // This runs a unit test in the browser, so it can use browser APIs. #[wasm_bindgen_test] fn web_test() { assert_eq!(1, 1); } + // This runs a unit test in the browser, and in addition it supports asynchronous Future APIs. #[wasm_bindgen_test(async)] fn async_test() -> impl Future { @@ -25,7 +28,8 @@ fn async_test() -> impl Future { // Converts that Promise into a Future. // The unit test will wait for the Future to resolve. - JsFuture::from(promise).map(|x| { - assert_eq!(x, 42); - }) + JsFuture::from(promise) + .map(|x| { + assert_eq!(x, 42); + }) } From 819e331fd7bb832736fae920a947d41e161b338f Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sun, 9 Feb 2025 12:40:52 +0000 Subject: [PATCH 24/78] fix(numeracy): make all answers positive integers todo: simple decimals at later levels --- src/games/numeracy/expression.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/games/numeracy/expression.rs b/src/games/numeracy/expression.rs index f1aa7e2..e20159a 100644 --- a/src/games/numeracy/expression.rs +++ b/src/games/numeracy/expression.rs @@ -8,21 +8,21 @@ pub struct Expression { impl Expression { pub fn new(level: u32) -> Self { - let ops = ["+", "-", "*", "/"]; + let ops = ['+', '-', '*', '/']; let complexity = (level as f64 * 1.5).ceil() as i32; - let a = (Math::random() * (complexity * 5) as f64).floor() as i32 + 1; + let a = (Math::random() * (complexity * 8) as f64).floor() as i32 + complexity; let b = (Math::random() * (complexity * 3) as f64).floor() as i32 + 1; let op = ops[(Math::random() * 4.0).floor() as usize]; let (text, value) = match op { - "+" => (format!("{} + {}", a, b), (a + b) as f64), - "-" => (format!("{} - {}", a, b), (a - b) as f64), - "*" => (format!("{} Γ— {}", a, b), (a * b) as f64), - "/" => { + '+' => (format!("{} + {}", a, b), (a + b) as f64), + '-' => (format!("{} - {}", a, b), (a - b) as f64), // a is always > b + '*' => (format!("{} Γ— {}", a, b), (a * b) as f64), + '/' => { let product = a * b; - (format!("{} Γ· {}", product, a), b as f64) - } + (format!("{} Γ· {}", product, b), a as f64) // Division always results in a + }, _ => unreachable!(), }; From 8d44eb0b7d8b7a87a197df65ebe11c98b1795e1f Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sun, 9 Feb 2025 13:10:44 +0000 Subject: [PATCH 25/78] run clippy --- src/games/mod.rs | 2 +- src/games/numeracy/expression.rs | 22 +-- src/games/numeracy/level.rs | 2 +- src/games/numeracy/mod.rs | 48 ++--- src/games/numeracy/state.rs | 48 ++--- src/games/perception/input.rs | 105 ++++++----- src/games/perception/maze.rs | 302 +++++++++++++++---------------- src/games/perception/mod.rs | 224 +++++++++++------------ src/games/perception/movement.rs | 182 ++++++++++--------- src/games/perception/render.rs | 190 +++++++++---------- src/games/perception/state.rs | 52 ++---- src/games/perception/timer.rs | 140 +++++++------- src/lib.rs | 24 ++- tests/app.rs | 35 ---- 14 files changed, 652 insertions(+), 724 deletions(-) delete mode 100644 tests/app.rs diff --git a/src/games/mod.rs b/src/games/mod.rs index 0cbca92..49192de 100644 --- a/src/games/mod.rs +++ b/src/games/mod.rs @@ -1,2 +1,2 @@ -pub mod perception; pub mod numeracy; +pub mod perception; diff --git a/src/games/numeracy/expression.rs b/src/games/numeracy/expression.rs index e20159a..0ff2d81 100644 --- a/src/games/numeracy/expression.rs +++ b/src/games/numeracy/expression.rs @@ -9,23 +9,23 @@ pub struct Expression { impl Expression { pub fn new(level: u32) -> Self { let ops = ['+', '-', '*', '/']; - - let complexity = (level as f64 * 1.5).ceil() as i32; - let a = (Math::random() * (complexity * 8) as f64).floor() as i32 + complexity; - let b = (Math::random() * (complexity * 3) as f64).floor() as i32 + 1; + + let complexity = (f64::from(level) * 1.5).ceil() as i32; + let a = (Math::random() * f64::from(complexity * 8)).floor() as i32 + complexity; + let b = (Math::random() * f64::from(complexity * 3)).floor() as i32 + 1; let op = ops[(Math::random() * 4.0).floor() as usize]; - + let (text, value) = match op { - '+' => (format!("{} + {}", a, b), (a + b) as f64), - '-' => (format!("{} - {}", a, b), (a - b) as f64), // a is always > b - '*' => (format!("{} Γ— {}", a, b), (a * b) as f64), + '+' => (format!("{a} + {b}"), f64::from(a + b)), + '-' => (format!("{a} - {b}"), f64::from(a - b)), // a is always > b + '*' => (format!("{a} Γ— {b}"), f64::from(a * b)), '/' => { let product = a * b; - (format!("{} Γ· {}", product, b), a as f64) // Division always results in a - }, + (format!("{product} Γ· {b}"), f64::from(a)) // Division always results in a + } _ => unreachable!(), }; Expression { text, value } } -} \ No newline at end of file +} diff --git a/src/games/numeracy/level.rs b/src/games/numeracy/level.rs index 0fcf071..aa29343 100644 --- a/src/games/numeracy/level.rs +++ b/src/games/numeracy/level.rs @@ -23,4 +23,4 @@ impl Level { let values: Vec = expressions.iter().map(|e| e.value).collect(); values.windows(2).all(|w| w[0] <= w[1]) } -} \ No newline at end of file +} diff --git a/src/games/numeracy/mod.rs b/src/games/numeracy/mod.rs index 7816df9..d2511cb 100644 --- a/src/games/numeracy/mod.rs +++ b/src/games/numeracy/mod.rs @@ -1,7 +1,7 @@ -use wasm_bindgen::prelude::*; -use web_sys::{Document, HtmlElement, Event, Element}; -use std::rc::Rc; use std::cell::RefCell; +use std::rc::Rc; +use wasm_bindgen::prelude::*; +use web_sys::{Document, Element, Event, HtmlElement}; mod expression; mod level; @@ -13,7 +13,7 @@ use state::GameState; #[wasm_bindgen] pub struct Numeracy { - state: Rc>, // Changed to Rc> for shared ownership + state: Rc>, // Changed to Rc> for shared ownership document: Document, container: HtmlElement, } @@ -39,19 +39,19 @@ impl Numeracy { fn render_bubbles(&self) -> Result<(), JsValue> { self.container.set_inner_html(""); let state_ref = self.state.borrow(); - + for (i, expr) in state_ref.expressions.iter().enumerate() { let bubble: Element = self.document.create_element("div")?; bubble.set_class_name("bubble"); bubble.set_text_content(Some(&expr.text)); - + let index = i.to_string(); bubble.set_attribute("data-index", &index)?; - + if state_ref.selected_indices.contains(&i) { bubble.set_attribute("class", "bubble selected")?; } - + let state = self.state.clone(); // Create a weak reference to the bubble let bubble_ref = bubble.clone(); @@ -59,9 +59,13 @@ impl Numeracy { let mut state = state.borrow_mut(); if state.toggle_selection(i) { let is_selected = state.selected_indices.contains(&i); - let class = if is_selected { "bubble selected" } else { "bubble" }; + let class = if is_selected { + "bubble selected" + } else { + "bubble" + }; bubble_ref.set_attribute("class", class).unwrap(); - + if state.selected_indices.len() == 3 { let round_success = state.check_current_round(); state.update_score(round_success); @@ -69,10 +73,10 @@ impl Numeracy { } } }) as Box); - + bubble.add_event_listener_with_callback("click", handler.as_ref().unchecked_ref())?; handler.forget(); - + self.container.append_child(&bubble)?; } Ok(()) @@ -80,21 +84,21 @@ impl Numeracy { fn update_stats(&self) -> Result<(), JsValue> { let state = self.state.borrow(); - + if let Some(level_elem) = self.document.get_element_by_id("level") { level_elem.set_text_content(Some(&state.level.number.to_string())); } - + if let Some(score_elem) = self.document.get_element_by_id("score") { score_elem.set_text_content(Some(&state.score.to_string())); } - + Ok(()) } fn update_timer(&self) -> Result<(), JsValue> { let state = self.state.borrow(); - + if let Some(timer_elem) = self.document.get_element_by_id("timer") { if let Some(remaining) = state.get_round_time_remaining() { let seconds = (remaining / 1000.0) as u32; @@ -110,7 +114,7 @@ impl Numeracy { let state = self.state.clone(); let document = self.document.clone(); let container = self.container.clone(); - + let closure = Closure::wrap(Box::new(move || { let this = Numeracy { state: state.clone(), @@ -120,12 +124,12 @@ impl Numeracy { this.update_timer().unwrap(); this.check_time_limits().unwrap(); }) as Box); - + window.set_interval_with_callback_and_timeout_and_arguments_0( closure.as_ref().unchecked_ref(), 1000, )?; - + closure.forget(); Ok(()) } @@ -133,7 +137,7 @@ impl Numeracy { fn check_time_limits(&self) -> Result<(), JsValue> { { let mut state = self.state.borrow_mut(); - + if let Some(remaining) = state.get_round_time_remaining() { if remaining <= 0.0 { state.update_score(false); @@ -143,7 +147,7 @@ impl Numeracy { } self.render_bubbles()?; self.update_stats()?; - + Ok(()) } @@ -155,4 +159,4 @@ impl Numeracy { self.start_timer()?; Ok(()) } -} \ No newline at end of file +} diff --git a/src/games/numeracy/state.rs b/src/games/numeracy/state.rs index ab6a496..a37a9e6 100644 --- a/src/games/numeracy/state.rs +++ b/src/games/numeracy/state.rs @@ -1,6 +1,5 @@ use super::{Expression, Level}; -use wasm_bindgen::JsValue; -use web_sys::{Performance, Storage, Window}; +use web_sys::{Performance, Storage}; #[derive(Debug)] pub struct GameState { @@ -24,7 +23,7 @@ impl GameState { .unwrap() .and_then(|s| s.parse().ok()) .unwrap_or(1); - + let level = Level::new(level_number); let expressions = level.generate_expressions(); let performance = window.performance().unwrap(); @@ -90,28 +89,19 @@ impl GameState { }) } - pub fn get_level_time_remaining(&self) -> Option { - self.level_start.map(|start| { - let elapsed = self.performance.now() - start; - if elapsed >= 300000.0 { - 0.0 - } else { - 300000.0 - elapsed - } - }) - } - pub fn update_score(&mut self, round_success: bool) { - let time_bonus = self.get_round_time_remaining() - .map(|t| (t / 1000.0) as i32) - .unwrap_or(0); + let time_bonus = self + .get_round_time_remaining() + .map_or(0, |t| (t / 1000.0) as i32); self.score += if round_success { // Evaluate level change after each round if self.score > 0 { let new_level = self.level.number + 1; self.level = Level::new(new_level); - self.storage.set_item("numeracy_level", &new_level.to_string()).unwrap(); + self.storage + .set_item("numeracy_level", &new_level.to_string()) + .unwrap(); } 10 + time_bonus } else { @@ -119,27 +109,13 @@ impl GameState { if self.level.number > 1 { let new_level = self.level.number - 1; self.level = Level::new(new_level); - self.storage.set_item("numeracy_level", &new_level.to_string()).unwrap(); + self.storage + .set_item("numeracy_level", &new_level.to_string()) + .unwrap(); } -5 }; self.completed_rounds += 1; } - - pub fn should_adjust_level(&self) -> Option { - if self.completed_rounds >= 10 { - let success_rate = (self.score as f64) / (self.completed_rounds as f64); - - Some(if success_rate > 0.8 { - 1 - } else if success_rate < 0.4 { - -1 - } else { - 0 - }) - } else { - None - } - } -} \ No newline at end of file +} diff --git a/src/games/perception/input.rs b/src/games/perception/input.rs index d8dd3ad..2118d22 100644 --- a/src/games/perception/input.rs +++ b/src/games/perception/input.rs @@ -1,53 +1,52 @@ -use super::Perception; -use std::{cell::RefCell, rc::Rc}; -use wasm_bindgen::prelude::*; -use web_sys::Element; - -impl Perception { - pub(super) fn setup_click_handler(game_state: Rc>) -> Result<(), JsValue> { - let click_handler = { - let game_state = game_state.clone(); - Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { - if let Ok(mut game) = game_state.try_borrow_mut() { - if let Some(target) = event.target() { - if let Some(element) = target.dyn_ref::() { - if let Ok(Some(maze_el)) = element.closest("#maze") { - // Find clicked cell index - let children = maze_el.children(); - let cell_index = (0..children.length()) - .find(|&i| { - children - .item(i) - .map(|cell| cell.is_same_node(Some(element))) - .unwrap_or(false) - }) - .unwrap_or(0) - as usize; - - let size = game.size; - let x = cell_index % size; - let y = cell_index / size; - - let result = game.try_move(x, y); - if result != 0 { - game.render().unwrap(); - } - } - } - } - } - }) as Box) - }; - - // Attach single click handler to maze container - if let Some(maze_el) = game_state.borrow().document.get_element_by_id("maze") { - maze_el.add_event_listener_with_callback( - "click", - click_handler.as_ref().unchecked_ref(), - )?; - click_handler.forget(); - } - - Ok(()) - } -} \ No newline at end of file +use super::Perception; +use std::{cell::RefCell, rc::Rc}; +use wasm_bindgen::prelude::*; +use web_sys::Element; + +impl Perception { + pub(super) fn setup_click_handler(game_state: Rc>) -> Result<(), JsValue> { + let click_handler = { + let game_state = game_state.clone(); + Closure::wrap(Box::new(move |event: web_sys::MouseEvent| { + if let Ok(mut game) = game_state.try_borrow_mut() { + if let Some(target) = event.target() { + if let Some(element) = target.dyn_ref::() { + if let Ok(Some(maze_el)) = element.closest("#maze") { + // Find clicked cell index + let children = maze_el.children(); + let cell_index = (0..children.length()) + .find(|&i| { + children + .item(i) + .is_some_and(|cell| cell.is_same_node(Some(element))) + }) + .unwrap_or(0) + as usize; + + let size = game.size; + let x = cell_index % size; + let y = cell_index / size; + + let result = game.try_move(x, y); + if result != 0 { + game.render().unwrap(); + } + } + } + } + } + }) as Box) + }; + + // Attach single click handler to maze container + if let Some(maze_el) = game_state.borrow().document.get_element_by_id("maze") { + maze_el.add_event_listener_with_callback( + "click", + click_handler.as_ref().unchecked_ref(), + )?; + click_handler.forget(); + } + + Ok(()) + } +} diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index 7baa441..8257884 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -1,156 +1,146 @@ -use std::collections::HashSet; - -use js_sys::Math; -use web_sys::Document; -use super::Perception; - -impl Perception { - pub(super) fn create_maze(size: usize, document: Document) -> Self { - let mut walls = vec![false; size * size * 4]; // Start with no walls - // Add random walls - for i in 0..walls.len() { - walls[i] = Math::random() < 0.5; - } - - let (waypoint1, key_position, waypoint2, door_position) = if size == 2 { - let key_pos = loop { - let pos = ( - (Math::random() * 2.0).floor() as usize, - (Math::random() * 2.0).floor() as usize, - ); - if pos != (0, 0) { - break pos; - }; - }; - // Get random non-start, non-key position for door - let door_pos = loop { - let pos = ( - (Math::random() * 2.0).floor() as usize, - (Math::random() * 2.0).floor() as usize, - ); - if pos != (0, 0) && pos != key_pos { - break pos; - } - }; - (key_pos, key_pos, door_pos, door_pos) - } else { - let (mut key_pos, mut door_pos); - loop { - key_pos = ( - ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, - ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, - ); - - door_pos = ( - ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, - ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, - ); - - if key_pos != door_pos && key_pos != (0, 0) && door_pos != (0, 0) { - break; - } - } - ( - ( - (Math::random() * (size as f64)).floor() as usize, - (Math::random() * (size as f64)).floor() as usize, - ), - key_pos, - ( - (Math::random() * (size as f64)).floor() as usize, - (Math::random() * (size as f64)).floor() as usize, - ), - door_pos, - ) - }; - - let mut game = Self { - size, - walls, - current_position: (0, 0), - key_position, - door_position, - visited: HashSet::new(), - has_key: false, - level: 1, - mazes_completed: 0, - document, - time_remaining: 300, - last_tick: js_sys::Date::now() / 1000.0, - }; - // Create path through waypoints - clear_path(&mut game.walls, (0, 0), waypoint1, size); - clear_path(&mut game.walls, waypoint1, key_position, size); - clear_path(&mut game.walls, key_position, waypoint2, size); - clear_path(&mut game.walls, waypoint2, door_position, size); - game.visited.insert((0, 0)); - game - } -} -fn clear_path( - walls: &mut Vec, - from: (usize, usize), - to: (usize, usize), - size: usize, -) { - let mut current = from; - // Calculate minimum required path length (Manhattan distance * 1.5) - while current != to { - let dx = (to.0 as i32 - current.0 as i32).signum(); - let dy = (to.1 as i32 - current.1 as i32).signum(); - // Clear both current cell's wall and neighbor's wall - if dx != 0 { - let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; - walls[wall_idx] = false; - // Clear adjacent cell's opposite wall if not at edge - if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { - let next_x = (current.0 as i32 + dx) as usize; - let adj_wall_idx = - (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; - walls[adj_wall_idx] = false; - - // Always clear an escape route (up or down) - let escape_dir = if current.1 > 0 { 0 } else { 2 }; // up if not at top, down otherwise - walls[(current.1 * size + current.0) * 4 + escape_dir] = false; - if escape_dir == 0 && current.1 > 0 { - // Clear the corresponding wall in the cell above - walls[((current.1 - 1) * size + current.0) * 4 + 2] = false; - } else if escape_dir == 2 && current.1 + 1 < size { - // Clear the corresponding wall in the cell below - walls[((current.1 + 1) * size + current.0) * 4 + 0] = false; - } - } - current.0 = (current.0 as i32 + dx) as usize; - } else if dy != 0 { - let wall_idx = (current.1 * size + current.0) * 4 + if dy > 0 { 2 } else { 0 }; - walls[wall_idx] = false; - // Clear adjacent cell's opposite wall if not at edge - if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { - let next_y = (current.1 as i32 + dy) as usize; - let adj_wall_idx = - (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; - walls[adj_wall_idx] = false; - } - current.1 = (current.1 as i32 + dy) as usize; - } - // Ensure escape route from the destination - let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right - for (dx, dy) in escape_dirs.iter() { - let next_x = to.0 as i32 + dx; - let next_y = to.1 as i32 + dy; - if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { - let wall_idx = (to.1 * size + to.0) * 4 - + if *dy < 0 { - 0 - } else if *dx > 0 { - 1 - } else if *dy > 0 { - 2 - } else { - 3 - }; - walls[wall_idx] = false; - } - } - } -} +use std::collections::HashSet; + +use super::Perception; +use js_sys::Math; +use web_sys::Document; + +impl Perception { + pub(super) fn create_maze(size: usize, document: Document) -> Self { + let walls = (0..size * size * 4) + .map(|_| Math::random() < 0.5) + .collect::>(); + let (waypoint1, key_position, waypoint2, door_position) = if size == 2 { + let key_pos = loop { + let pos = ( + (Math::random() * 2.0).floor() as usize, + (Math::random() * 2.0).floor() as usize, + ); + if pos != (0, 0) { + break pos; + } + }; + // Get random non-start, non-key position for door + let door_pos = loop { + let pos = ( + (Math::random() * 2.0).floor() as usize, + (Math::random() * 2.0).floor() as usize, + ); + if pos != (0, 0) && pos != key_pos { + break pos; + } + }; + (key_pos, key_pos, door_pos, door_pos) + } else { + let (mut key_pos, mut door_pos); + loop { + key_pos = ( + ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, + ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, + ); + + door_pos = ( + ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, + ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, + ); + + if key_pos != door_pos && key_pos != (0, 0) && door_pos != (0, 0) { + break; + } + } + ( + ( + (Math::random() * (size as f64)).floor() as usize, + (Math::random() * (size as f64)).floor() as usize, + ), + key_pos, + ( + (Math::random() * (size as f64)).floor() as usize, + (Math::random() * (size as f64)).floor() as usize, + ), + door_pos, + ) + }; + + let mut game = Self { + size, + walls, + current_position: (0, 0), + key_position, + door_position, + visited: HashSet::new(), + has_key: false, + level: 1, + mazes_completed: 0, + document, + time_remaining: 300, + last_tick: js_sys::Date::now() / 1000.0, + }; + // Create path through waypoints + clear_path(&mut game.walls, (0, 0), waypoint1, size); + clear_path(&mut game.walls, waypoint1, key_position, size); + clear_path(&mut game.walls, key_position, waypoint2, size); + clear_path(&mut game.walls, waypoint2, door_position, size); + game.visited.insert((0, 0)); + game + } +} +fn clear_path(walls: &mut [bool], from: (usize, usize), to: (usize, usize), size: usize) { + let mut current = from; + // Calculate minimum required path length (Manhattan distance * 1.5) + while current != to { + let dx = (to.0 as i32 - current.0 as i32).signum(); + let dy = (to.1 as i32 - current.1 as i32).signum(); + // Clear both current cell's wall and neighbor's wall + if dx != 0 { + let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; + walls[wall_idx] = false; + // Clear adjacent cell's opposite wall if not at edge + if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { + let next_x = (current.0 as i32 + dx) as usize; + let adj_wall_idx = (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; + walls[adj_wall_idx] = false; + + // Always clear an escape route (up or down) + let escape_dir = if current.1 > 0 { 0 } else { 2 }; // up if not at top, down otherwise + walls[(current.1 * size + current.0) * 4 + escape_dir] = false; + if escape_dir == 0 && current.1 > 0 { + // Clear the corresponding wall in the cell above + walls[((current.1 - 1) * size + current.0) * 4 + 2] = false; + } else if escape_dir == 2 && current.1 + 1 < size { + // Clear the corresponding wall in the cell below + walls[(current.1 + 1) * size + current.0 * 4] = false; + } + } + current.0 = (current.0 as i32 + dx) as usize; + } else if dy != 0 { + let wall_idx = (current.1 * size + current.0) * 4 + if dy > 0 { 2 } else { 0 }; + walls[wall_idx] = false; + // Clear adjacent cell's opposite wall if not at edge + if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { + let next_y = (current.1 as i32 + dy) as usize; + let adj_wall_idx = (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; + walls[adj_wall_idx] = false; + } + current.1 = (current.1 as i32 + dy) as usize; + } + // Ensure escape route from the destination + let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right + for (dx, dy) in &escape_dirs { + let next_x = to.0 as i32 + dx; + let next_y = to.1 as i32 + dy; + if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { + let wall_idx = (to.1 * size + to.0) * 4 + + if *dy < 0 { + 0 + } else if *dx > 0 { + 1 + } else if *dy > 0 { + 2 + } else { + 3 + }; + walls[wall_idx] = false; + } + } + } +} diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index ba5fdf2..6565919 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -1,112 +1,112 @@ -mod state; -mod render; -mod maze; -mod movement; -mod timer; -mod input; - -use serde::{Deserialize, Serialize}; -use std::{cell::RefCell, collections::HashSet, rc::Rc}; -use wasm_bindgen::prelude::*; -use web_sys::{console, Document, Element}; - -fn get_document() -> Document { - web_sys::window() - .expect("no global window exists") - .document() - .expect("no document exists") -} - -#[wasm_bindgen] -#[derive(Clone, Serialize, Deserialize)] -pub struct Perception { - // Game state - size: usize, - level: usize, - #[serde(default)] - mazes_completed: usize, - - // Maze elements - walls: Vec, - current_position: (usize, usize), - key_position: (usize, usize), - door_position: (usize, usize), - visited: HashSet<(usize, usize)>, - has_key: bool, - - // Timer state - time_remaining: i32, - last_tick: f64, - - #[serde(skip, default = "get_document")] - document: Document, -} - -#[wasm_bindgen] -impl Perception { - #[wasm_bindgen(constructor)] - pub fn new() -> Result { - let document = get_document(); - let storage = web_sys::window() - .expect("no global window exists") - .local_storage()? - .expect("no local storage"); - - let mut game = if let Some(state) = storage.get_item("maze_state")? { - let last_save = storage - .get_item("maze_time")? - .unwrap_or_else(|| "0".to_string()) - .parse::() - .unwrap_or(0.0); - - if js_sys::Date::now() - last_save > 300000.0 { - Self::create_maze(2, document) - } else { - serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)? - } - } else { - Self::create_maze(2, document) - }; - - game.render()?; - game.start()?; - Ok(game) - } - #[wasm_bindgen] - pub fn start(&mut self) -> Result<(), JsValue> { - let game_state = Rc::new(RefCell::new(self.clone())); - - Self::setup_click_handler(game_state.clone())?; - Self::setup_timer(game_state)?; - - console::log_1(&"Setup complete".into()); - Ok(()) - } - #[wasm_bindgen] - pub fn reset(&mut self) { - let new_game = Self::create_maze(self.size, self.document.clone()); - self.walls = new_game.walls; - self.key_position = new_game.key_position; - self.door_position = new_game.door_position; - self.reset_position(); - - // Reset timer state completely - self.time_remaining = 300; - self.last_tick = js_sys::Date::now() / 1000.0; - - // Force timer display update - if let Some(timer_el) = self.document.get_element_by_id("timer") { - timer_el.set_text_content(Some("5:00")); - } - - // Update display - self.render().expect("Failed to render reset"); - } - fn reset_position(&mut self) { - self.current_position = (0, 0); - self.visited.clear(); - self.visited.insert((0, 0)); - self.has_key = false; - self.render().expect("Failed to render position reset"); - } -} \ No newline at end of file +mod input; +mod maze; +mod movement; +mod render; +mod state; +mod timer; + +use serde::{Deserialize, Serialize}; +use std::{cell::RefCell, collections::HashSet, rc::Rc}; +use wasm_bindgen::prelude::*; +use web_sys::{console, Document}; + +fn get_document() -> Document { + web_sys::window() + .expect("no global window exists") + .document() + .expect("no document exists") +} + +#[wasm_bindgen] +#[derive(Clone, Serialize, Deserialize)] +pub struct Perception { + // Game state + size: usize, + level: usize, + #[serde(default)] + mazes_completed: usize, + + // Maze elements + walls: Vec, + current_position: (usize, usize), + key_position: (usize, usize), + door_position: (usize, usize), + visited: HashSet<(usize, usize)>, + has_key: bool, + + // Timer state + time_remaining: i32, + last_tick: f64, + + #[serde(skip, default = "get_document")] + document: Document, +} + +#[wasm_bindgen] +impl Perception { + #[wasm_bindgen(constructor)] + pub fn new() -> Result { + let document = get_document(); + let storage = web_sys::window() + .expect("no global window exists") + .local_storage()? + .expect("no local storage"); + + let mut game = if let Some(state) = storage.get_item("maze_state")? { + let last_save = storage + .get_item("maze_time")? + .unwrap_or_else(|| "0".to_string()) + .parse::() + .unwrap_or(0.0); + + if js_sys::Date::now() - last_save > 300000.0 { + Self::create_maze(2, document) + } else { + serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)? + } + } else { + Self::create_maze(2, document) + }; + + game.render()?; + game.start()?; + Ok(game) + } + #[wasm_bindgen] + pub fn start(&mut self) -> Result<(), JsValue> { + let game_state = Rc::new(RefCell::new(self.clone())); + + Self::setup_click_handler(game_state.clone())?; + Self::setup_timer(game_state)?; + + console::log_1(&"Setup complete".into()); + Ok(()) + } + #[wasm_bindgen] + pub fn reset(&mut self) { + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.reset_position(); + + // Reset timer state completely + self.time_remaining = 300; + self.last_tick = js_sys::Date::now() / 1000.0; + + // Force timer display update + if let Some(timer_el) = self.document.get_element_by_id("timer") { + timer_el.set_text_content(Some("5:00")); + } + + // Update display + self.render().expect("Failed to render reset"); + } + fn reset_position(&mut self) { + self.current_position = (0, 0); + self.visited.clear(); + self.visited.insert((0, 0)); + self.has_key = false; + self.render().expect("Failed to render position reset"); + } +} diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index 6925b34..b2b7930 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -1,88 +1,94 @@ -use super::Perception; - -impl Perception { - pub(super) fn is_adjacent(&self, x: usize, y: usize) -> bool { - let current_x = self.current_position.0; - let current_y = self.current_position.1; - - // Check if target position is adjacent (up, down, left, right) - let dx = if x >= current_x { - x - current_x - } else { - current_x - x - }; - let dy = if y >= current_y { - y - current_y - } else { - current_y - y - }; - - // Only one coordinate can change by 1, the other must be 0 - (dx == 1 && dy == 0) || (dx == 0 && dy == 1) - } - - pub(super) fn get_wall_index(&self, from_x: usize, from_y: usize, to_x: usize, to_y: usize) -> usize { - let cell_walls = 4; // each cell has 4 possible walls - let base_index = (from_y * self.size + from_x) * cell_walls; - - if to_x > from_x { - base_index + 1 // right wall - } else if to_x < from_x { - base_index + 3 // left wall - } else if to_y > from_y { - base_index + 2 // bottom wall - } else { - base_index + 0 // top wall - } - } - - pub(super) fn try_move(&mut self, x: usize, y: usize) -> i32 { - if !self.is_adjacent(x, y) { - return 0; - } - - let wall_idx = self.get_wall_index(self.current_position.0, self.current_position.1, x, y); - - // Block access to door position if key not collected - if (x, y) == self.door_position && !self.has_key { - return 0; - } - - if self.walls[wall_idx] { - self.reset_position(); - return -1; - } - - self.current_position = (x, y); - self.visited.insert((x, y)); - - if (x, y) == self.key_position { - self.has_key = true; - // When key is collected, make door accessible - let door_x = self.door_position.0; - let door_y = self.door_position.1; - let base_idx = (door_y * self.size + door_x) * 4; - for i in 0..4 { - self.walls[base_idx + i] = false; - } - } - - if (x, y) == self.door_position && self.has_key { - // Simplified level up - increase size immediately - self.size += 1; - self.level += 1; - let new_game = Self::create_maze(self.size, self.document.clone()); - self.walls = new_game.walls; - self.current_position = (0, 0); - self.key_position = new_game.key_position; - self.door_position = new_game.door_position; - self.visited.clear(); - self.visited.insert((0, 0)); - self.has_key = false; - self.time_remaining = 300; - self.last_tick = js_sys::Date::now() / 1000.0; - return 2; - } - 1 - } -} \ No newline at end of file +use super::Perception; + +impl Perception { + pub(super) fn is_adjacent(&self, x: usize, y: usize) -> bool { + let current_x = self.current_position.0; + let current_y = self.current_position.1; + + // Check if target position is adjacent (up, down, left, right) + let dx = if x >= current_x { + x - current_x + } else { + current_x - x + }; + let dy = if y >= current_y { + y - current_y + } else { + current_y - y + }; + + // Only one coordinate can change by 1, the other must be 0 + (dx == 1 && dy == 0) || (dx == 0 && dy == 1) + } + + pub(super) fn get_wall_index( + &self, + from_x: usize, + from_y: usize, + to_x: usize, + to_y: usize, + ) -> usize { + let cell_walls = 4; // each cell has 4 possible walls + let base_index = (from_y * self.size + from_x) * cell_walls; + + if to_x > from_x { + base_index + 1 // right wall + } else if to_x < from_x { + base_index + 3 // left wall + } else if to_y > from_y { + base_index + 2 // bottom wall + } else { + base_index // top wall + } + } + + pub(super) fn try_move(&mut self, x: usize, y: usize) -> i32 { + if !self.is_adjacent(x, y) { + return 0; + } + + let wall_idx = self.get_wall_index(self.current_position.0, self.current_position.1, x, y); + + // Block access to door position if key not collected + if (x, y) == self.door_position && !self.has_key { + return 0; + } + + if self.walls[wall_idx] { + self.reset_position(); + return -1; + } + + self.current_position = (x, y); + self.visited.insert((x, y)); + + if (x, y) == self.key_position { + self.has_key = true; + // When key is collected, make door accessible + let door_x = self.door_position.0; + let door_y = self.door_position.1; + let base_idx = (door_y * self.size + door_x) * 4; + for i in 0..4 { + self.walls[base_idx + i] = false; + } + } + + if (x, y) == self.door_position && self.has_key { + // Simplified level up - increase size immediately + self.size += 1; + self.level += 1; + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.current_position = (0, 0); + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.visited.clear(); + self.visited.insert((0, 0)); + self.has_key = false; + self.time_remaining = 300; + self.last_tick = js_sys::Date::now() / 1000.0; + return 2; + } + 1 + } +} diff --git a/src/games/perception/render.rs b/src/games/perception/render.rs index 52987b0..040a0e8 100644 --- a/src/games/perception/render.rs +++ b/src/games/perception/render.rs @@ -1,95 +1,95 @@ -use super::Perception; -use wasm_bindgen::prelude::*; -use web_sys::Element; - -impl Perception { - pub(crate) fn render(&self) -> Result<(), JsValue> { - let maze = self.document.get_element_by_id("maze").unwrap(); - - // Only regenerate grid if size changed - if maze.children().length() as usize != self.size * self.size { - maze.set_attribute( - "style", - &format!("grid-template-columns: repeat({}, 60px)", self.size), - )?; - - // Clear existing content safely - while let Some(child) = maze.first_child() { - maze.remove_child(&child)?; - } - - // Create cells only once - for _ in 0..(self.size * self.size) { - let cell = self.document.create_element("div")?; - cell.set_class_name("cell"); - let span = self.document.create_element("span")?; - let content = self.document.create_text_node(""); - cell.append_child(&content)?; - cell.append_child(&span)?; - maze.append_child(&cell)?; - } - } - - // Update existing cells - for y in 0..self.size { - for x in 0..self.size { - let index = (y * self.size + x) as u32; - if let Some(cell) = maze.children().item(index) { - self.update_cell_state(&cell, x, y)?; - } - } - } - - // Update stats - if let Some(level_el) = self.document.get_element_by_id("level") { - level_el.set_text_content(Some(&self.level.to_string())); - } - if let Some(completed_el) = self.document.get_element_by_id("completed") { - completed_el.set_text_content(Some(&self.mazes_completed.to_string())); - } - if let Some(timer_el) = self.document.get_element_by_id("timer") { - let minutes = self.time_remaining / 60; - let seconds = self.time_remaining % 60; - timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); - } - Ok(()) - } - - fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { - // Reset base class - cell.set_class_name("cell"); - - // Update state classes - if self.visited.contains(&(x, y)) { - cell.class_list().add_1("visited")?; - } - if (x, y) == self.current_position { - cell.class_list().add_1("current")?; - // Ensure span exists for pseudo-elements - if cell.children().length() == 0 { - let span = self.document.create_element("span")?; - cell.append_child(&span)?; - } - } - - // Update content - let content = if (x, y) == self.key_position && !self.has_key { - "πŸ”‘" - } else if (x, y) == self.current_position && self.has_key { - "πŸ”‘" - } else if (x, y) == self.door_position { - "πŸšͺ" - } else { - "" - }; - - // Update text content if it's different - if let Some(first_child) = cell.first_child() { - if first_child.text_content().unwrap_or_default() != content { - first_child.set_text_content(Some(content)); - } - } - - Ok(()) - } -} \ No newline at end of file +use super::Perception; +use wasm_bindgen::prelude::*; +use web_sys::Element; + +impl Perception { + pub(crate) fn render(&self) -> Result<(), JsValue> { + let maze = self.document.get_element_by_id("maze").unwrap(); + + // Only regenerate grid if size changed + if maze.children().length() as usize != self.size * self.size { + maze.set_attribute( + "style", + &format!("grid-template-columns: repeat({}, 60px)", self.size), + )?; + + // Clear existing content safely + while let Some(child) = maze.first_child() { + maze.remove_child(&child)?; + } + + // Create cells only once + for _ in 0..(self.size * self.size) { + let cell = self.document.create_element("div")?; + cell.set_class_name("cell"); + let span = self.document.create_element("span")?; + let content = self.document.create_text_node(""); + cell.append_child(&content)?; + cell.append_child(&span)?; + maze.append_child(&cell)?; + } + } + + // Update existing cells + for y in 0..self.size { + for x in 0..self.size { + let index = (y * self.size + x) as u32; + if let Some(cell) = maze.children().item(index) { + self.update_cell_state(&cell, x, y)?; + } + } + } + + // Update stats + if let Some(level_el) = self.document.get_element_by_id("level") { + level_el.set_text_content(Some(&self.level.to_string())); + } + if let Some(completed_el) = self.document.get_element_by_id("completed") { + completed_el.set_text_content(Some(&self.mazes_completed.to_string())); + } + if let Some(timer_el) = self.document.get_element_by_id("timer") { + let minutes = self.time_remaining / 60; + let seconds = self.time_remaining % 60; + timer_el.set_text_content(Some(&format!("{minutes}:{seconds:02}"))); + } + Ok(()) + } + + fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { + // Reset base class + cell.set_class_name("cell"); + + // Update state classes + if self.visited.contains(&(x, y)) { + cell.class_list().add_1("visited")?; + } + if (x, y) == self.current_position { + cell.class_list().add_1("current")?; + // Ensure span exists for pseudo-elements + if cell.children().length() == 0 { + let span = self.document.create_element("span")?; + cell.append_child(&span)?; + } + } + + // Update content + let content = if (x, y) == self.key_position && !self.has_key + || (x, y) == self.current_position && self.has_key + { + "πŸ”‘" + } else if (x, y) == self.door_position { + "πŸšͺ" + } else { + "" + }; + + // Update text content if it's different + if let Some(first_child) = cell.first_child() { + if first_child.text_content().unwrap_or_default() != content { + first_child.set_text_content(Some(content)); + } + } + + Ok(()) + } +} diff --git a/src/games/perception/state.rs b/src/games/perception/state.rs index 64ce695..1f2200d 100644 --- a/src/games/perception/state.rs +++ b/src/games/perception/state.rs @@ -1,34 +1,18 @@ -use super::Perception; -use js_sys::Date; -use wasm_bindgen::prelude::*; -use web_sys::Storage; - -impl Perception { - pub(super) fn load_state(storage: &Storage) -> Result, JsValue> { - if let Some(state) = storage.get_item("maze_state")? { - let last_save = storage - .get_item("maze_time")? - .unwrap_or_else(|| "0".to_string()) - .parse::() - .unwrap_or(0.0); - - let game: Self = serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)?; - Ok(Some((game, last_save))) - } else { - Ok(None) - } - } - - pub(super) fn save_state(&self) -> Result<(), JsValue> { - let window = web_sys::window().expect("no global window exists"); - let storage = window.local_storage()?.expect("no local storage exists"); - - let state = serde_wasm_bindgen::to_value(&self)?; - let state_json = js_sys::JSON::stringify(&state)?.as_string().unwrap(); - storage.set_item("maze_state", &state_json)?; - storage.set_item("maze_time", &Date::now().to_string())?; - storage.set_item("maze_level", &self.level.to_string())?; - - Ok(()) - } -} \ No newline at end of file +use super::Perception; +use js_sys::Date; +use wasm_bindgen::prelude::*; + +impl Perception { + pub(super) fn save_state(&self) -> Result<(), JsValue> { + let window = web_sys::window().expect("no global window exists"); + let storage = window.local_storage()?.expect("no local storage exists"); + + let state = serde_wasm_bindgen::to_value(&self)?; + let state_json = js_sys::JSON::stringify(&state)?.as_string().unwrap(); + storage.set_item("maze_state", &state_json)?; + storage.set_item("maze_time", &Date::now().to_string())?; + storage.set_item("maze_level", &self.level.to_string())?; + + Ok(()) + } +} diff --git a/src/games/perception/timer.rs b/src/games/perception/timer.rs index ef95529..1eae4f8 100644 --- a/src/games/perception/timer.rs +++ b/src/games/perception/timer.rs @@ -1,70 +1,70 @@ -use wasm_bindgen::prelude::*; -use web_sys::{console, Document}; -use std::{rc::Rc, cell::RefCell}; -use super::Perception; - -impl Perception { - pub(super) fn setup_timer(game_state: Rc>) -> Result<(), JsValue> { - let timer_callback = { - let game_state = game_state.clone(); - Closure::wrap(Box::new(move || { - if let Ok(mut game) = game_state.try_borrow_mut() { - let now = js_sys::Date::now() / 1000.0; - let delta = (now - game.last_tick) as i32; - if delta >= 1 { - game.update_timer(now); - } - } - }) as Box) - }; - - let window = web_sys::window().unwrap(); - console::log_1(&"Setting up interval...".into()); - - let result = window.set_interval_with_callback_and_timeout_and_arguments_0( - timer_callback.as_ref().unchecked_ref(), - 1000, - ); - - match result { - Ok(_) => console::log_1(&"Interval set up successfully".into()), - Err(e) => console::log_2(&"Failed to set up interval:".into(), &e), - } - - timer_callback.forget(); - Ok(()) - } - - fn update_timer(&mut self, now: f64) { - self.time_remaining -= 1; - self.last_tick = now; - - if self.time_remaining <= 0 { - self.reset_on_timeout(now); - } - - self.update_timer_display(); - self.save_state().unwrap_or_else(|_| { - console::log_1(&"Failed to save game state".into()); - }); - } - - fn update_timer_display(&self) { - if let Some(timer_el) = self.document.get_element_by_id("timer") { - let minutes = self.time_remaining / 60; - let seconds = self.time_remaining % 60; - timer_el.set_text_content(Some(&format!("{}:{:02}", minutes, seconds))); - } - } - - fn reset_on_timeout(&mut self, now: f64) { - let new_game = Self::create_maze(self.size, self.document.clone()); - self.walls = new_game.walls; - self.key_position = new_game.key_position; - self.door_position = new_game.door_position; - self.reset_position(); - self.time_remaining = 300; - self.last_tick = now; - self.render().unwrap(); - } -} \ No newline at end of file +use super::Perception; +use std::{cell::RefCell, rc::Rc}; +use wasm_bindgen::prelude::*; +use web_sys::console; + +impl Perception { + pub(super) fn setup_timer(game_state: Rc>) -> Result<(), JsValue> { + let timer_callback = { + let game_state = game_state.clone(); + Closure::wrap(Box::new(move || { + if let Ok(mut game) = game_state.try_borrow_mut() { + let now = js_sys::Date::now() / 1000.0; + let delta = (now - game.last_tick) as i32; + if delta >= 1 { + game.update_timer(now); + } + } + }) as Box) + }; + + let window = web_sys::window().unwrap(); + console::log_1(&"Setting up interval...".into()); + + let result = window.set_interval_with_callback_and_timeout_and_arguments_0( + timer_callback.as_ref().unchecked_ref(), + 1000, + ); + + match result { + Ok(_) => console::log_1(&"Interval set up successfully".into()), + Err(e) => console::log_2(&"Failed to set up interval:".into(), &e), + } + + timer_callback.forget(); + Ok(()) + } + + fn update_timer(&mut self, now: f64) { + self.time_remaining -= 1; + self.last_tick = now; + + if self.time_remaining <= 0 { + self.reset_on_timeout(now); + } + + self.update_timer_display(); + self.save_state().unwrap_or_else(|_| { + console::log_1(&"Failed to save game state".into()); + }); + } + + fn update_timer_display(&self) { + if let Some(timer_el) = self.document.get_element_by_id("timer") { + let minutes = self.time_remaining / 60; + let seconds = self.time_remaining % 60; + timer_el.set_text_content(Some(&format!("{minutes}:{seconds:02}"))); + } + } + + fn reset_on_timeout(&mut self, now: f64) { + let new_game = Self::create_maze(self.size, self.document.clone()); + self.walls = new_game.walls; + self.key_position = new_game.key_position; + self.door_position = new_game.door_position; + self.reset_position(); + self.time_remaining = 300; + self.last_tick = now; + self.render().unwrap(); + } +} diff --git a/src/lib.rs b/src/lib.rs index ed77e2d..21f62ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,8 @@ mod games; -pub use games::perception::Perception; pub use games::numeracy::Numeracy; +pub use games::perception::Perception; use wasm_bindgen::{prelude::*, JsValue}; -use web_sys::window; #[cfg(feature = "wee_alloc")] #[global_allocator] @@ -13,16 +12,21 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; pub fn main_js() -> Result<(), JsValue> { #[cfg(debug_assertions)] console_error_panic_hook::set_once(); - + // Check which page we're on let window = web_sys::window().expect("no global window exists"); - let document = window.document().expect("no document on window"); let location = window.location(); let path = location.pathname().expect("pathname should exist"); - + // Return the created instance rather than discarding it - Ok(match path.as_str() { - "/numeracy.html" => { let game = Numeracy::new()?; game.start()?; }, - _ => { Perception::new()?; }, - }) -} \ No newline at end of file + match path.as_str() { + "/numeracy.html" => { + let game = Numeracy::new()?; + game.start()?; + } + _ => { + Perception::new()?; + } + } + Ok(()) +} diff --git a/tests/app.rs b/tests/app.rs deleted file mode 100644 index 9223aa3..0000000 --- a/tests/app.rs +++ /dev/null @@ -1,35 +0,0 @@ -use wasm_bindgen_test::{wasm_bindgen_test_configure, wasm_bindgen_test}; -use futures::prelude::*; -use wasm_bindgen::JsValue; -use wasm_bindgen_futures::JsFuture; - -wasm_bindgen_test_configure!(run_in_browser); - - -// This runs a unit test in native Rust, so it can only use Rust APIs. -#[test] -fn rust_test() { - assert_eq!(1, 1); -} - - -// This runs a unit test in the browser, so it can use browser APIs. -#[wasm_bindgen_test] -fn web_test() { - assert_eq!(1, 1); -} - - -// This runs a unit test in the browser, and in addition it supports asynchronous Future APIs. -#[wasm_bindgen_test(async)] -fn async_test() -> impl Future { - // Creates a JavaScript Promise which will asynchronously resolve with the value 42. - let promise = js_sys::Promise::resolve(&JsValue::from(42)); - - // Converts that Promise into a Future. - // The unit test will wait for the Future to resolve. - JsFuture::from(promise) - .map(|x| { - assert_eq!(x, 42); - }) -} From 68d5b25ba535521701a2292a864fbd86f7104b40 Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sun, 9 Feb 2025 13:29:06 +0000 Subject: [PATCH 26/78] try optimising using features that may break compat --- webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webpack.config.js b/webpack.config.js index 95a8185..9679afd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -23,6 +23,7 @@ export default { }), new WasmPackPlugin({ crateDirectory: ".", + extraArgs: "--weak-refs --reference-types", }), ] } \ No newline at end of file From e05022f6470cfe725e4de3f09c548be221666d7a Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sun, 9 Feb 2025 16:38:01 +0000 Subject: [PATCH 27/78] feat(favicon): add SVG favicon and configure webpack to serve it --- static/favicon.svg | 45 +++++++++++++++++++++++++++++++++++++++++++++ webpack.config.js | 14 +++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 static/favicon.svg diff --git a/static/favicon.svg b/static/favicon.svg new file mode 100644 index 0000000..ff5f224 --- /dev/null +++ b/static/favicon.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + 123 + + + + + + + + + ABC + + diff --git a/webpack.config.js b/webpack.config.js index 9679afd..f638d6e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -11,7 +11,19 @@ export default { asyncWebAssembly: true }, devServer: { - static: "dist", + setupMiddlewares(middlewares, devServer) { + if (!devServer) { + throw new Error('webpack-dev-server is not defined'); + } + + // Add custom middleware to serve the favicon.ico file with SVG headers + devServer.app.get('/favicon.ico', (req, res) => { + res.setHeader('Content-Type', 'image/svg+xml'); + res.sendFile(resolve('static/favicon.svg')); + }); + + return middlewares; + }, open: true, port: 80, }, From 699f36c9bab56f88d4be571f85c8a1c29ce4b1ae Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sun, 9 Feb 2025 16:56:17 +0000 Subject: [PATCH 28/78] ln static/favicon.svg static/favicon.ico --- _headers | 2 ++ static/favicon.ico | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 _headers create mode 100644 static/favicon.ico diff --git a/_headers b/_headers new file mode 100644 index 0000000..a9f90ec --- /dev/null +++ b/_headers @@ -0,0 +1,2 @@ +/static/favicon.ico + Content-Type: image/svg+xml \ No newline at end of file diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..ff5f224 --- /dev/null +++ b/static/favicon.ico @@ -0,0 +1,45 @@ + + + + + + + + + + + 123 + + + + + + + + + ABC + + From 02ed5b371f8028c606efc2a43212d023c04da24d Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sun, 9 Feb 2025 21:14:42 +0000 Subject: [PATCH 29/78] feat(styles): share styles and improve contrast --- static/index.html | 26 +++++------------- static/numeracy.html | 14 +--------- static/styles.css | 65 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 32 deletions(-) create mode 100644 static/styles.css diff --git a/static/index.html b/static/index.html index 200e37b..3bbbc40 100644 --- a/static/index.html +++ b/static/index.html @@ -5,44 +5,31 @@ WASM Maze Game + diff --git a/static/styles.css b/static/styles.css index 456d164..7770705 100644 --- a/static/styles.css +++ b/static/styles.css @@ -1,30 +1,27 @@ -/* Shared styles for cognitive games */ :root { - --bg-color: #595959; + /* Light theme */ + --bg-color: #666666; --text-color: #000000; - --border-color: #333333; - --visited-color: #4d4d4d; - --current-color: #404040; + --border-color: #000000; + --visited-color: #4B0082; --pointer-color: #000000; --key-color: #000000; --door-color: #000000; - --bubble-color: #404040; - --bubble-selected: #303030; + --bubble-color: #737373; /* Lighter to maintain contrast with black text */ + --bubble-selected: #999999; /* Lighter selected state */ } -/* Dark theme */ @media (prefers-color-scheme: dark) { :root { --bg-color: #000000; - --text-color: #959595; - --border-color: #959595; - --visited-color: #1a1a1a; - --current-color: #2a2a2a; - --pointer-color: #959595; - --key-color: #959595; - --door-color: #959595; - --bubble-color: #2a2a2a; - --bubble-selected: #404040; + --text-color: #8c8c8c; /* Meets 4.5:1 for small text */ + --border-color: #8c8c8c; + --visited-color: #a066ff; /* Lighter purple meeting 3:1 in dark mode */ + --pointer-color: #8c8c8c; + --key-color: #8c8c8c; + --door-color: #8c8c8c; + --bubble-color: #333333; + --bubble-selected: #4d4d4d; } } @@ -33,33 +30,14 @@ body { cursor: url("data:image/svg+xml,123") 3 3, auto; color: var(--text-color); margin: 0; -} - -/* Dark theme */ -@media (prefers-color-scheme: dark) { - :root { - --bg-color: #000000; - --text-color: #959595; - --border-color: #959595; - --visited-color: #1a1a1a; - --current-color: #2a2a2a; - --pointer-color: #959595; - --key-color: #959595; - --door-color: #959595; - --bubble-color: #2a2a2a; - --bubble-selected: #404040; - } -} - -body { - background-color: var(--bg-color); - color: var(--text-color); font-family: system-ui, -apple-system, sans-serif; - margin: 0; + font-size: 1rem; /* 16px base */ + line-height: 1.5; } #stats { color: var(--text-color); margin: 1rem; text-align: center; + font-size: 1.5rem; /* 24px - qualifies as large text */ } \ No newline at end of file From 49043389b1356db6d5872217b156ab48032c96a9 Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Mon, 10 Feb 2025 17:12:39 +0000 Subject: [PATCH 31/78] feat(numeracy): adjust complexity scaling and remove score tracking --- src/games/numeracy/expression.rs | 11 +++++----- src/games/numeracy/mod.rs | 5 +---- src/games/numeracy/state.rs | 35 ++++++++++++++++---------------- static/styles.css | 35 ++++++++++++++++++++++++-------- 4 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/games/numeracy/expression.rs b/src/games/numeracy/expression.rs index 0ff2d81..c3c328a 100644 --- a/src/games/numeracy/expression.rs +++ b/src/games/numeracy/expression.rs @@ -10,18 +10,19 @@ impl Expression { pub fn new(level: u32) -> Self { let ops = ['+', '-', '*', '/']; - let complexity = (f64::from(level) * 1.5).ceil() as i32; - let a = (Math::random() * f64::from(complexity * 8)).floor() as i32 + complexity; - let b = (Math::random() * f64::from(complexity * 3)).floor() as i32 + 1; + // More gradual complexity increase + let complexity = (f64::from(level) * 1.2).ceil() as i32; + let a = (Math::random() * f64::from(complexity * 5)).floor() as i32 + complexity; + let b = (Math::random() * f64::from(complexity * 2)).floor() as i32 + 1; let op = ops[(Math::random() * 4.0).floor() as usize]; let (text, value) = match op { '+' => (format!("{a} + {b}"), f64::from(a + b)), - '-' => (format!("{a} - {b}"), f64::from(a - b)), // a is always > b + '-' => (format!("{a} - {b}"), f64::from(a - b)), '*' => (format!("{a} Γ— {b}"), f64::from(a * b)), '/' => { let product = a * b; - (format!("{product} Γ· {b}"), f64::from(a)) // Division always results in a + (format!("{product} Γ· {b}"), f64::from(a)) } _ => unreachable!(), }; diff --git a/src/games/numeracy/mod.rs b/src/games/numeracy/mod.rs index d2511cb..109e087 100644 --- a/src/games/numeracy/mod.rs +++ b/src/games/numeracy/mod.rs @@ -89,10 +89,7 @@ impl Numeracy { level_elem.set_text_content(Some(&state.level.number.to_string())); } - if let Some(score_elem) = self.document.get_element_by_id("score") { - score_elem.set_text_content(Some(&state.score.to_string())); - } - + // Removed score element update Ok(()) } diff --git a/src/games/numeracy/state.rs b/src/games/numeracy/state.rs index a37a9e6..a56b43d 100644 --- a/src/games/numeracy/state.rs +++ b/src/games/numeracy/state.rs @@ -6,7 +6,6 @@ pub struct GameState { pub level: Level, pub expressions: Vec, pub selected_indices: Vec, - pub score: i32, pub round_start: Option, pub level_start: Option, pub completed_rounds: u32, @@ -32,7 +31,6 @@ impl GameState { level, expressions, selected_indices: Vec::new(), - score: 0, round_start: None, level_start: None, completed_rounds: 0, @@ -94,27 +92,28 @@ impl GameState { .get_round_time_remaining() .map_or(0, |t| (t / 1000.0) as i32); - self.score += if round_success { - // Evaluate level change after each round - if self.score > 0 { - let new_level = self.level.number + 1; - self.level = Level::new(new_level); - self.storage - .set_item("numeracy_level", &new_level.to_string()) - .unwrap(); - } - 10 + time_bonus - } else { - // Drop a level on failure, but not below 1 - if self.level.number > 1 { - let new_level = self.level.number - 1; + if round_success { + // Calculate level jumps based on time bonus using a mathematical formula + let level_jump = if time_bonus > 0 { + ((time_bonus as f64 * 0.2).floor() as u32).min(3) + } else { + 0 + }; + + if level_jump > 0 { + let new_level = self.level.number + level_jump; self.level = Level::new(new_level); self.storage .set_item("numeracy_level", &new_level.to_string()) .unwrap(); } - -5 - }; + } else if self.level.number > 1 { + let new_level = self.level.number - 1; + self.level = Level::new(new_level); + self.storage + .set_item("numeracy_level", &new_level.to_string()) + .unwrap(); + } self.completed_rounds += 1; } diff --git a/static/styles.css b/static/styles.css index 7770705..94a5dd3 100644 --- a/static/styles.css +++ b/static/styles.css @@ -7,21 +7,26 @@ --pointer-color: #000000; --key-color: #000000; --door-color: #000000; - --bubble-color: #737373; /* Lighter to maintain contrast with black text */ - --bubble-selected: #999999; /* Lighter selected state */ + --bubble-color: #737373; + --bubble-selected: #999999; + + /* Cursor variables */ + --cursor-stroke: #000000; + --cursor-fill: none; + --cursor-size: 24; + --cursor-opacity: 1; } @media (prefers-color-scheme: dark) { :root { --bg-color: #000000; - --text-color: #8c8c8c; /* Meets 4.5:1 for small text */ + --text-color: #8c8c8c; --border-color: #8c8c8c; - --visited-color: #a066ff; /* Lighter purple meeting 3:1 in dark mode */ + --visited-color: #a066ff; --pointer-color: #8c8c8c; --key-color: #8c8c8c; --door-color: #8c8c8c; - --bubble-color: #333333; - --bubble-selected: #4d4d4d; + --cursor-stroke: #8c8c8c; } } @@ -30,8 +35,8 @@ body { cursor: url("data:image/svg+xml,123") 3 3, auto; color: var(--text-color); margin: 0; - font-family: system-ui, -apple-system, sans-serif; - font-size: 1rem; /* 16px base */ + font-family: sans-serif; + font-size: 1rem; line-height: 1.5; } @@ -40,4 +45,18 @@ body { margin: 1rem; text-align: center; font-size: 1.5rem; /* 24px - qualifies as large text */ +} +.cell:hover { + --cursor-stroke: #4A90E2; + --cursor-fill: rgba(74, 144, 226, 0.1); + --cursor-size: 28; + --cursor-opacity: 0.9; +} + +/* Add active state styles */ +.cell:active { + --cursor-stroke: #2563EB; + --cursor-fill: rgba(37, 99, 235, 0.2); + --cursor-size: 22; + --cursor-opacity: 1; } \ No newline at end of file From d68b0b1080149a742e47b1a0e56e8072b7d90dc6 Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Mon, 10 Feb 2025 17:37:07 +0000 Subject: [PATCH 32/78] refactor(perception): clean up whitespace and improve code readability in maze logic --- src/games/perception/maze.rs | 8 ++++---- src/games/perception/mod.rs | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index c8b6eb8..1d0f4cd 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -89,12 +89,12 @@ fn clear_path(walls: &mut [bool], from: (usize, usize), to: (usize, usize), size while current != to { let dx = (to.0 as i32 - current.0 as i32).signum(); let dy = (to.1 as i32 - current.1 as i32).signum(); - + // Handle horizontal movement if dx != 0 { let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; walls[wall_idx] = false; - + // Clear adjacent cell's opposite wall if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { let next_x = (current.0 as i32 + dx) as usize; @@ -107,7 +107,7 @@ fn clear_path(walls: &mut [bool], from: (usize, usize), to: (usize, usize), size else if dy != 0 { let wall_idx = (current.1 * size + current.0) * 4 + if dy > 0 { 2 } else { 0 }; walls[wall_idx] = false; - + // Clear adjacent cell's opposite wall if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { let next_y = (current.1 as i32 + dy) as usize; @@ -117,7 +117,7 @@ fn clear_path(walls: &mut [bool], from: (usize, usize), to: (usize, usize), size } } } - + // Ensure at least one escape route from the destination let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right for (dx, dy) in &escape_dirs { diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index 69c2ebc..86b3cec 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -87,8 +87,9 @@ impl Perception { game.reset_to_level_one().unwrap(); } }) as Box); - - reset_btn.add_event_listener_with_callback("click", handler.as_ref().unchecked_ref())?; + + reset_btn + .add_event_listener_with_callback("click", handler.as_ref().unchecked_ref())?; handler.forget(); } @@ -129,13 +130,13 @@ impl Perception { self.size = 2; // Level 1 starts with size 2 self.level = 1; self.mazes_completed = 0; - + // Create new level 1 maze let new_game = Self::create_maze(self.size, self.document.clone()); self.walls = new_game.walls; self.key_position = new_game.key_position; self.door_position = new_game.door_position; - + // Reset position and timer self.reset_position(); self.time_remaining = 300; @@ -151,7 +152,7 @@ impl Perception { if let Some(timer_el) = self.document.get_element_by_id("timer") { timer_el.set_text_content(Some("5:00")); } - + // Show/hide reset button based on level if let Some(reset_btn) = self.document.get_element_by_id("reset-level") { reset_btn.set_attribute("hidden", "")?; From 67a55d6ff6815fd95a14f2de66464c4a668d78b2 Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Mon, 10 Feb 2025 20:51:53 +0000 Subject: [PATCH 33/78] feat(numeracy): enhance expression generation with decimal support and improve level jump calculation --- src/games/numeracy/expression.rs | 33 +++++++++++++++++++++----------- src/games/numeracy/state.rs | 2 +- static/numeracy.html | 1 + 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/games/numeracy/expression.rs b/src/games/numeracy/expression.rs index c3c328a..be2fecf 100644 --- a/src/games/numeracy/expression.rs +++ b/src/games/numeracy/expression.rs @@ -9,24 +9,35 @@ pub struct Expression { impl Expression { pub fn new(level: u32) -> Self { let ops = ['+', '-', '*', '/']; - - // More gradual complexity increase + let denominators = [2, 4, 5, 8]; + let use_decimals = level > 3 && Math::random() < 0.3; + let complexity = (f64::from(level) * 1.2).ceil() as i32; let a = (Math::random() * f64::from(complexity * 5)).floor() as i32 + complexity; let b = (Math::random() * f64::from(complexity * 2)).floor() as i32 + 1; - let op = ops[(Math::random() * 4.0).floor() as usize]; - let (text, value) = match op { - '+' => (format!("{a} + {b}"), f64::from(a + b)), - '-' => (format!("{a} - {b}"), f64::from(a - b)), - '*' => (format!("{a} Γ— {b}"), f64::from(a * b)), - '/' => { - let product = a * b; - (format!("{product} Γ· {b}"), f64::from(a)) + let make_decimal = |n: i32| + if use_decimals { + let d = denominators[(Math::random() * 4.0) as usize]; + let val = (n * d) as f64 / d as f64; + (val, val.to_string()) + } else { + (n.into(), n.to_string()) } + ; + + let (a_val, a_text) = make_decimal(a); + let (b_val, b_text) = make_decimal(b); + let op = ops[(Math::random() * 4.0) as usize]; + + let (text, value) = match op { + '+' => (format!("{a_text} + {b_text}"), a_val + b_val), + '-' => (format!("{a_text} - {b_text}"), a_val - b_val), + '*' => (format!("{a_text} Γ— {b_text}"), a_val * b_val), + '/' => (format!("{} Γ· {b_text}", a_val * b_val), a_val), _ => unreachable!(), }; - Expression { text, value } + Self { text, value } } } diff --git a/src/games/numeracy/state.rs b/src/games/numeracy/state.rs index a56b43d..17b7382 100644 --- a/src/games/numeracy/state.rs +++ b/src/games/numeracy/state.rs @@ -95,7 +95,7 @@ impl GameState { if round_success { // Calculate level jumps based on time bonus using a mathematical formula let level_jump = if time_bonus > 0 { - ((time_bonus as f64 * 0.2).floor() as u32).min(3) + ((f64::from(time_bonus) * 0.2).floor() as u32).min(3) } else { 0 }; diff --git a/static/numeracy.html b/static/numeracy.html index 6dfb0e1..1547bf2 100644 --- a/static/numeracy.html +++ b/static/numeracy.html @@ -37,6 +37,7 @@ transition: background-color 0.2s; font-size: 2rem; /* 32px */ color: var(--text-color); + user-select: none; } .bubble.selected { From 53903b6fe3faf45a76f8ca1ac17a6beab0945466 Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Wed, 12 Feb 2025 00:12:48 +0000 Subject: [PATCH 34/78] feat(perception): maze generation is optimised and leads to harder --- src/games/numeracy/expression.rs | 8 +- src/games/numeracy/mod.rs | 69 +++++----- src/games/perception/maze.rs | 208 +++++++++++++------------------ src/games/perception/mod.rs | 7 +- src/games/perception/movement.rs | 5 +- static/index.html | 2 +- 6 files changed, 134 insertions(+), 165 deletions(-) diff --git a/src/games/numeracy/expression.rs b/src/games/numeracy/expression.rs index be2fecf..80259f9 100644 --- a/src/games/numeracy/expression.rs +++ b/src/games/numeracy/expression.rs @@ -11,20 +11,20 @@ impl Expression { let ops = ['+', '-', '*', '/']; let denominators = [2, 4, 5, 8]; let use_decimals = level > 3 && Math::random() < 0.3; - + let complexity = (f64::from(level) * 1.2).ceil() as i32; let a = (Math::random() * f64::from(complexity * 5)).floor() as i32 + complexity; let b = (Math::random() * f64::from(complexity * 2)).floor() as i32 + 1; - let make_decimal = |n: i32| + let make_decimal = |n: i32| { if use_decimals { let d = denominators[(Math::random() * 4.0) as usize]; - let val = (n * d) as f64 / d as f64; + let val = f64::from(n * d) / f64::from(d); (val, val.to_string()) } else { (n.into(), n.to_string()) } - ; + }; let (a_val, a_text) = make_decimal(a); let (b_val, b_text) = make_decimal(b); diff --git a/src/games/numeracy/mod.rs b/src/games/numeracy/mod.rs index 109e087..2ece777 100644 --- a/src/games/numeracy/mod.rs +++ b/src/games/numeracy/mod.rs @@ -37,47 +37,52 @@ impl Numeracy { } fn render_bubbles(&self) -> Result<(), JsValue> { - self.container.set_inner_html(""); let state_ref = self.state.borrow(); + let children = self.container.children(); - for (i, expr) in state_ref.expressions.iter().enumerate() { - let bubble: Element = self.document.create_element("div")?; - bubble.set_class_name("bubble"); - bubble.set_text_content(Some(&expr.text)); + for i in 0..children.length() { + if let Some(bubble) = children.item(i) { + let bubble: Element = bubble.dyn_into::()?; - let index = i.to_string(); - bubble.set_attribute("data-index", &index)?; + if let Some(expr) = state_ref.expressions.get(i as usize) { + bubble.set_text_content(Some(&expr.text)); - if state_ref.selected_indices.contains(&i) { - bubble.set_attribute("class", "bubble selected")?; - } - - let state = self.state.clone(); - // Create a weak reference to the bubble - let bubble_ref = bubble.clone(); - let handler = Closure::wrap(Box::new(move |_event: Event| { - let mut state = state.borrow_mut(); - if state.toggle_selection(i) { - let is_selected = state.selected_indices.contains(&i); - let class = if is_selected { + let class = if state_ref.selected_indices.contains(&(i as usize)) { "bubble selected" } else { "bubble" }; - bubble_ref.set_attribute("class", class).unwrap(); - - if state.selected_indices.len() == 3 { - let round_success = state.check_current_round(); - state.update_score(round_success); - state.start_round(); - } + bubble.set_attribute("class", class)?; + + let state = self.state.clone(); + let bubble_ref = bubble.clone(); + let i = i as usize; + let handler = Closure::wrap(Box::new(move |_event: Event| { + let mut state = state.borrow_mut(); + if state.toggle_selection(i) { + let is_selected = state.selected_indices.contains(&i); + let class = if is_selected { + "bubble selected" + } else { + "bubble" + }; + bubble_ref.set_attribute("class", class).unwrap(); + + if state.selected_indices.len() == 3 { + let round_success = state.check_current_round(); + state.update_score(round_success); + state.start_round(); + } + } + }) as Box); + + bubble.add_event_listener_with_callback( + "click", + handler.as_ref().unchecked_ref(), + )?; + handler.forget(); } - }) as Box); - - bubble.add_event_listener_with_callback("click", handler.as_ref().unchecked_ref())?; - handler.forget(); - - self.container.append_child(&bubble)?; + } } Ok(()) } diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index 1d0f4cd..4ab0f40 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -1,140 +1,102 @@ +use js_sys::Math; use std::collections::HashSet; +use web_sys::Document; use super::Perception; -use js_sys::Math; -use web_sys::Document; impl Perception { pub(super) fn create_maze(size: usize, document: Document) -> Self { - let walls = (0..size * size * 4) - .map(|_| Math::random() < 0.5) - .collect::>(); - let (waypoint1, key_position, waypoint2, door_position) = if size == 2 { - let key_pos = loop { - let pos = ( - (Math::random() * 2.0).floor() as usize, - (Math::random() * 2.0).floor() as usize, - ); - if pos != (0, 0) { - break pos; - } - }; - // Get random non-start, non-key position for door - let door_pos = loop { - let pos = ( - (Math::random() * 2.0).floor() as usize, - (Math::random() * 2.0).floor() as usize, - ); - if pos != (0, 0) && pos != key_pos { - break pos; - } - }; - (key_pos, key_pos, door_pos, door_pos) - } else { - let (mut key_pos, mut door_pos); - loop { - key_pos = ( - ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, - ((size as f64) * 0.6 + Math::random() * (size as f64) * 0.3).floor() as usize, - ); - - door_pos = ( - ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, - ((size as f64) * 0.7 + Math::random() * (size as f64) * 0.2).floor() as usize, - ); - - if key_pos != door_pos && key_pos != (0, 0) && door_pos != (0, 0) { - break; - } + let mut walls = vec![true; size * size * 4]; // Initialize all walls as true + let mut visited = HashSet::new(); + let mut stack = Vec::new(); + + // Helper function to get random int in range + let random_int = |max: usize| -> usize { (Math::random() * max as f64).floor() as usize }; + + // Generate random start position + let start_x = random_int(size); + let start_y = random_int(size); + let start_pos = (start_x, start_y); + + visited.insert(start_pos); + stack.push(start_pos); + + // Generate maze using iterative DFS + while let Some(¤t) = stack.last() { + let (x, y) = current; + let mut neighbors = Vec::new(); + + // Check all possible neighbors + if x > 0 && !visited.contains(&(x - 1, y)) { + neighbors.push((x - 1, y, 3)); + } + if x < size - 1 && !visited.contains(&(x + 1, y)) { + neighbors.push((x + 1, y, 1)); } - ( - ( - (Math::random() * (size as f64)).floor() as usize, - (Math::random() * (size as f64)).floor() as usize, - ), - key_pos, - ( - (Math::random() * (size as f64)).floor() as usize, - (Math::random() * (size as f64)).floor() as usize, - ), - door_pos, - ) - }; - - let mut game = Self { + if y > 0 && !visited.contains(&(x, y - 1)) { + neighbors.push((x, y - 1, 0)); + } + if y < size - 1 && !visited.contains(&(x, y + 1)) { + neighbors.push((x, y + 1, 2)); + } + + if neighbors.is_empty() { + stack.pop(); + } else { + // Choose random unvisited neighbor + let (next_x, next_y, wall_dir) = neighbors[random_int(neighbors.len())]; + + // Remove wall between current and chosen cell + let cell_walls = 4; + let current_idx = (y * size + x) * cell_walls; + walls[current_idx + wall_dir] = false; + + // Remove opposite wall of neighbor + let opposite_wall = match wall_dir { + 0 => 2, // top -> bottom + 1 => 3, // right -> left + 2 => 0, // bottom -> top + 3 => 1, // left -> right + _ => unreachable!(), + }; + let neighbor_idx = (next_y * size + next_x) * cell_walls; + walls[neighbor_idx + opposite_wall] = false; + + visited.insert((next_x, next_y)); + stack.push((next_x, next_y)); + } + } + + // Generate key and door positions (ensuring they're different from start and each other) + let mut available_positions: Vec<(usize, usize)> = (0..size) + .flat_map(|y| (0..size).map(move |x| (x, y))) + .filter(|&pos| pos != start_pos) + .collect(); + + let key_idx = random_int(available_positions.len()); + let key_position = available_positions.remove(key_idx); + + let door_idx = random_int(available_positions.len()); + let door_position = available_positions[door_idx]; + + // Create initial visited set with just start position + let mut initial_visited = HashSet::new(); + initial_visited.insert(start_pos); + + Perception { size, + level: 1, + mazes_completed: 0, walls, - current_position: (0, 0), + current_position: start_pos, + start_position: start_pos, key_position, door_position, - visited: HashSet::new(), + visited: initial_visited, has_key: false, - level: 1, - mazes_completed: 0, - document, time_remaining: 300, last_tick: js_sys::Date::now() / 1000.0, - }; - // Create path through waypoints - clear_path(&mut game.walls, (0, 0), waypoint1, size); - clear_path(&mut game.walls, waypoint1, key_position, size); - clear_path(&mut game.walls, key_position, waypoint2, size); - clear_path(&mut game.walls, waypoint2, door_position, size); - game.visited.insert((0, 0)); - game - } -} -fn clear_path(walls: &mut [bool], from: (usize, usize), to: (usize, usize), size: usize) { - let mut current = from; - while current != to { - let dx = (to.0 as i32 - current.0 as i32).signum(); - let dy = (to.1 as i32 - current.1 as i32).signum(); - - // Handle horizontal movement - if dx != 0 { - let wall_idx = (current.1 * size + current.0) * 4 + if dx > 0 { 1 } else { 3 }; - walls[wall_idx] = false; - - // Clear adjacent cell's opposite wall - if (dx > 0 && current.0 + 1 < size) || (dx < 0 && current.0 > 0) { - let next_x = (current.0 as i32 + dx) as usize; - let adj_wall_idx = (current.1 * size + next_x) * 4 + if dx > 0 { 3 } else { 1 }; - walls[adj_wall_idx] = false; - current.0 = next_x; - } - } - // Handle vertical movement - else if dy != 0 { - let wall_idx = (current.1 * size + current.0) * 4 + if dy > 0 { 2 } else { 0 }; - walls[wall_idx] = false; - - // Clear adjacent cell's opposite wall - if (dy > 0 && current.1 + 1 < size) || (dy < 0 && current.1 > 0) { - let next_y = (current.1 as i32 + dy) as usize; - let adj_wall_idx = (next_y * size + current.0) * 4 + if dy > 0 { 0 } else { 2 }; - walls[adj_wall_idx] = false; - current.1 = next_y; - } - } - } - - // Ensure at least one escape route from the destination - let escape_dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]; // up, down, left, right - for (dx, dy) in &escape_dirs { - let next_x = to.0 as i32 + dx; - let next_y = to.1 as i32 + dy; - if next_x >= 0 && next_x < size as i32 && next_y >= 0 && next_y < size as i32 { - let wall_idx = (to.1 * size + to.0) * 4 - + if *dy < 0 { - 0 - } else if *dx > 0 { - 1 - } else if *dy > 0 { - 2 - } else { - 3 - }; - walls[wall_idx] = false; + document, } } } diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index 86b3cec..522be4b 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -29,6 +29,7 @@ pub struct Perception { // Maze elements walls: Vec, current_position: (usize, usize), + start_position: (usize, usize), key_position: (usize, usize), door_position: (usize, usize), visited: HashSet<(usize, usize)>, @@ -117,16 +118,16 @@ impl Perception { self.render().expect("Failed to render reset"); } fn reset_position(&mut self) { - self.current_position = (0, 0); + self.current_position = self.start_position; self.visited.clear(); - self.visited.insert((0, 0)); + self.visited.insert(self.start_position); self.has_key = false; self.render().expect("Failed to render position reset"); } #[wasm_bindgen] pub fn reset_to_level_one(&mut self) -> Result<(), JsValue> { // Only reset if above level 1 - if self.size > 2 { + if self.size > 1 { self.size = 2; // Level 1 starts with size 2 self.level = 1; self.mazes_completed = 0; diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index b2b7930..998ce49 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -79,11 +79,12 @@ impl Perception { self.level += 1; let new_game = Self::create_maze(self.size, self.document.clone()); self.walls = new_game.walls; - self.current_position = (0, 0); + self.current_position = new_game.start_position; // Use start_position from new maze + self.start_position = new_game.start_position; // Also update start_position self.key_position = new_game.key_position; self.door_position = new_game.door_position; self.visited.clear(); - self.visited.insert((0, 0)); + self.visited.insert(new_game.start_position); // Insert correct start position self.has_key = false; self.time_remaining = 300; self.last_tick = js_sys::Date::now() / 1000.0; diff --git a/static/index.html b/static/index.html index 28371a7..84fc91b 100644 --- a/static/index.html +++ b/static/index.html @@ -114,7 +114,7 @@ Time: 5:00
- + From 992689a954dd9773c6b81259c17d0c337a16b0d1 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Wed, 12 Feb 2025 14:08:31 +0000 Subject: [PATCH 35/78] Update numeracy.html --- static/numeracy.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/static/numeracy.html b/static/numeracy.html index 1547bf2..524dba9 100644 --- a/static/numeracy.html +++ b/static/numeracy.html @@ -57,7 +57,11 @@ Score: 0 | Time: 5:00 -
+
+
+
+
+
- \ No newline at end of file + From f1d0e764e2c7de2c8db75c024e89c6bdb44bf24d Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Wed, 12 Feb 2025 14:34:06 +0000 Subject: [PATCH 36/78] indicate current position --- src/games/perception/render.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/games/perception/render.rs b/src/games/perception/render.rs index 040a0e8..bdabcbe 100644 --- a/src/games/perception/render.rs +++ b/src/games/perception/render.rs @@ -79,7 +79,11 @@ impl Perception { "πŸ”‘" } else if (x, y) == self.door_position { "πŸšͺ" - } else { + } + else if (x, y) == self.current_position && !self.has_key { + "πŸ‘€" + } + else { "" }; From 1489185475287555a5e33c2d577e2517f14d1cbd Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:11:20 +0000 Subject: [PATCH 37/78] fix maze gen --- src/games/perception/maze.rs | 168 +++++++++++++++++++---------------- 1 file changed, 93 insertions(+), 75 deletions(-) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index 4ab0f40..a83f309 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -4,99 +4,117 @@ use web_sys::Document; use super::Perception; -impl Perception { - pub(super) fn create_maze(size: usize, document: Document) -> Self { - let mut walls = vec![true; size * size * 4]; // Initialize all walls as true - let mut visited = HashSet::new(); - let mut stack = Vec::new(); - - // Helper function to get random int in range - let random_int = |max: usize| -> usize { (Math::random() * max as f64).floor() as usize }; - - // Generate random start position - let start_x = random_int(size); - let start_y = random_int(size); - let start_pos = (start_x, start_y); +struct DisjointSet { + parent: Vec, + rank: Vec, +} - visited.insert(start_pos); - stack.push(start_pos); +impl DisjointSet { + fn new(size: usize) -> Self { + Self { + parent: (0..size).collect(), + rank: vec![0; size], + } + } - // Generate maze using iterative DFS - while let Some(¤t) = stack.last() { - let (x, y) = current; - let mut neighbors = Vec::new(); + fn find(&mut self, x: usize) -> usize { + if self.parent[x] != x { + self.parent[x] = self.find(self.parent[x]); + } + self.parent[x] + } - // Check all possible neighbors - if x > 0 && !visited.contains(&(x - 1, y)) { - neighbors.push((x - 1, y, 3)); - } - if x < size - 1 && !visited.contains(&(x + 1, y)) { - neighbors.push((x + 1, y, 1)); - } - if y > 0 && !visited.contains(&(x, y - 1)) { - neighbors.push((x, y - 1, 0)); + fn union(&mut self, x: usize, y: usize) { + let root_x = self.find(x); + let root_y = self.find(y); + + if root_x != root_y { + match self.rank[root_x].cmp(&self.rank[root_y]) { + std::cmp::Ordering::Less => self.parent[root_x] = root_y, + std::cmp::Ordering::Greater => self.parent[root_y] = root_x, + std::cmp::Ordering::Equal => { + self.parent[root_y] = root_x; + self.rank[root_x] += 1; + } } - if y < size - 1 && !visited.contains(&(x, y + 1)) { - neighbors.push((x, y + 1, 2)); - } - - if neighbors.is_empty() { - stack.pop(); - } else { - // Choose random unvisited neighbor - let (next_x, next_y, wall_dir) = neighbors[random_int(neighbors.len())]; - - // Remove wall between current and chosen cell - let cell_walls = 4; - let current_idx = (y * size + x) * cell_walls; - walls[current_idx + wall_dir] = false; - - // Remove opposite wall of neighbor - let opposite_wall = match wall_dir { - 0 => 2, // top -> bottom - 1 => 3, // right -> left - 2 => 0, // bottom -> top - 3 => 1, // left -> right - _ => unreachable!(), - }; - let neighbor_idx = (next_y * size + next_x) * cell_walls; - walls[neighbor_idx + opposite_wall] = false; + } + } +} - visited.insert((next_x, next_y)); - stack.push((next_x, next_y)); +impl Perception { + pub(super) fn create_maze(size: usize, document: Document) -> Self { + let total_cells = size * size; + let mut walls = vec![true; total_cells * 4]; + let mut ds = DisjointSet::new(total_cells); + let mut edges = Vec::new(); + + // Generate all possible edges + for y in 0..size { + for x in 0..size { + let cell = y * size + x; + if x < size - 1 { + edges.push((cell, cell + 1, cell * 4 + 1)); // right wall + } + if y < size - 1 { + edges.push((cell, cell + size, cell * 4 + 2)); // bottom wall + } } } - // Generate key and door positions (ensuring they're different from start and each other) - let mut available_positions: Vec<(usize, usize)> = (0..size) - .flat_map(|y| (0..size).map(move |x| (x, y))) - .filter(|&pos| pos != start_pos) - .collect(); - - let key_idx = random_int(available_positions.len()); - let key_position = available_positions.remove(key_idx); + // Shuffle edges + for i in (1..edges.len()).rev() { + let j = (Math::random() * (i + 1) as f64) as usize; + edges.swap(i, j); + } - let door_idx = random_int(available_positions.len()); - let door_position = available_positions[door_idx]; + // Create maze using Kruskal's algorithm + for (cell1, cell2, wall_idx) in edges { + if ds.find(cell1) != ds.find(cell2) { + walls[wall_idx] = false; + // Mirror wall removal (if removing right wall of cell1, remove left wall of cell2) + if wall_idx % 4 == 1 { // right wall + walls[wall_idx + 2] = false; // left wall of adjacent cell + } else if wall_idx % 4 == 2 { // bottom wall + walls[wall_idx + size * 4 - 2] = false; // top wall of cell below + } + ds.union(cell1, cell2); + } + } - // Create initial visited set with just start position - let mut initial_visited = HashSet::new(); - initial_visited.insert(start_pos); + // Generate start position in the first third + let start_x = (Math::random() * (size as f64 / 3.0)) as usize; + let start_y = (Math::random() * (size as f64 / 3.0)) as usize; + let start_position = (start_x, start_y); + + // Generate key position in the middle third + let key_x = (Math::random() * (size as f64 / 3.0)) as usize + size / 3; + let key_y = (Math::random() * (size as f64 / 3.0)) as usize + size / 3; + let key_position = (key_x, key_y); + + // Generate door position in the last third + let door_x = (Math::random() * (size as f64 / 3.0)) as usize + (2 * size) / 3; + let door_y = (Math::random() * (size as f64 / 3.0)) as usize + (2 * size) / 3; + let door_position = (door_x, door_y); + + // Make door position initially inaccessible by adding walls around it + let door_cell = door_y * size + door_x; + for i in 0..4 { + walls[door_cell * 4 + i] = true; + } - Perception { + Self { size, - level: 1, - mazes_completed: 0, walls, - current_position: start_pos, - start_position: start_pos, + current_position: start_position, + start_position, key_position, door_position, - visited: initial_visited, has_key: false, + visited: HashSet::from([start_position]), + level: 1, time_remaining: 300, last_tick: js_sys::Date::now() / 1000.0, document, } } -} +} \ No newline at end of file From 0ebb1cc93d03164ca616c72ae3b7d4bbf1d43866 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:39:32 +0000 Subject: [PATCH 38/78] perception: fix init --- src/games/perception/maze.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index a83f309..0d1c5f6 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -115,6 +115,7 @@ impl Perception { time_remaining: 300, last_tick: js_sys::Date::now() / 1000.0, document, + mazes_completed: 0, } } } \ No newline at end of file From 3757c5080d133dec56a5f587c511f04dd0de5e28 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Wed, 12 Feb 2025 19:20:01 +0000 Subject: [PATCH 39/78] perception/maze: don't block out the door --- src/games/perception/maze.rs | 67 +++++++++++++++--------------------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index 0d1c5f6..e0f499f 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -42,66 +42,53 @@ impl DisjointSet { } impl Perception { + fn random_position(size: usize, sector: usize) -> (usize, usize) { + let sector_size = size / 3; + let offset = sector * sector_size; + ( + (Math::random() * sector_size as f64) as usize + offset, + (Math::random() * sector_size as f64) as usize + offset, + ) + } + pub(super) fn create_maze(size: usize, document: Document) -> Self { let total_cells = size * size; let mut walls = vec![true; total_cells * 4]; let mut ds = DisjointSet::new(total_cells); - let mut edges = Vec::new(); + let mut edges = Vec::with_capacity(2 * size * (size - 1)); // Generate all possible edges - for y in 0..size { - for x in 0..size { - let cell = y * size + x; - if x < size - 1 { - edges.push((cell, cell + 1, cell * 4 + 1)); // right wall - } - if y < size - 1 { - edges.push((cell, cell + size, cell * 4 + 2)); // bottom wall - } + for cell in 0..total_cells { + let x = cell % size; + let y = cell / size; + if x < size - 1 { + edges.push((cell, cell + 1, cell * 4 + 1)); + } + if y < size - 1 { + edges.push((cell, cell + size, cell * 4 + 2)); } } - // Shuffle edges + // Fisher-Yates shuffle for i in (1..edges.len()).rev() { - let j = (Math::random() * (i + 1) as f64) as usize; - edges.swap(i, j); + edges.swap(i, (Math::random() * (i + 1) as f64) as usize); } + // Generate positions in different sectors + let start_position = Self::random_position(size, 0); + let key_position = Self::random_position(size, 1); + let door_position = Self::random_position(size, 2); + // Create maze using Kruskal's algorithm for (cell1, cell2, wall_idx) in edges { if ds.find(cell1) != ds.find(cell2) { walls[wall_idx] = false; - // Mirror wall removal (if removing right wall of cell1, remove left wall of cell2) - if wall_idx % 4 == 1 { // right wall - walls[wall_idx + 2] = false; // left wall of adjacent cell - } else if wall_idx % 4 == 2 { // bottom wall - walls[wall_idx + size * 4 - 2] = false; // top wall of cell below - } + // Mirror wall removal + walls[wall_idx + if wall_idx % 4 == 1 { 2 } else { size * 4 - 2 }] = false; ds.union(cell1, cell2); } } - // Generate start position in the first third - let start_x = (Math::random() * (size as f64 / 3.0)) as usize; - let start_y = (Math::random() * (size as f64 / 3.0)) as usize; - let start_position = (start_x, start_y); - - // Generate key position in the middle third - let key_x = (Math::random() * (size as f64 / 3.0)) as usize + size / 3; - let key_y = (Math::random() * (size as f64 / 3.0)) as usize + size / 3; - let key_position = (key_x, key_y); - - // Generate door position in the last third - let door_x = (Math::random() * (size as f64 / 3.0)) as usize + (2 * size) / 3; - let door_y = (Math::random() * (size as f64 / 3.0)) as usize + (2 * size) / 3; - let door_position = (door_x, door_y); - - // Make door position initially inaccessible by adding walls around it - let door_cell = door_y * size + door_x; - for i in 0..4 { - walls[door_cell * 4 + i] = true; - } - Self { size, walls, From 5d6ca4ebba7dde389e56d6c2ab90be4d0c22fea0 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Wed, 12 Feb 2025 19:36:57 +0000 Subject: [PATCH 40/78] fix: small maze position generation --- src/games/perception/maze.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index e0f499f..7b1afd2 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -43,12 +43,11 @@ impl DisjointSet { impl Perception { fn random_position(size: usize, sector: usize) -> (usize, usize) { - let sector_size = size / 3; - let offset = sector * sector_size; - ( - (Math::random() * sector_size as f64) as usize + offset, - (Math::random() * sector_size as f64) as usize + offset, - ) + let total_cells = size * size; + let sector_size = (total_cells / 3).max(1); + let start = sector * sector_size; + let cell_index = start + (Math::random() * sector_size as f64) as usize; + (cell_index % size, cell_index / size) } pub(super) fn create_maze(size: usize, document: Document) -> Self { From 3d20e6716fab7d53db76cee6a0b318c1b4b76dd1 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Wed, 12 Feb 2025 19:52:27 +0000 Subject: [PATCH 41/78] Special handling for 2x2 --- src/games/perception/maze.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index 7b1afd2..51eb101 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -43,11 +43,21 @@ impl DisjointSet { impl Perception { fn random_position(size: usize, sector: usize) -> (usize, usize) { - let total_cells = size * size; - let sector_size = (total_cells / 3).max(1); - let start = sector * sector_size; - let cell_index = start + (Math::random() * sector_size as f64) as usize; - (cell_index % size, cell_index / size) + if size <= 2 { + // Special handling for 2x2: + // start: (0,0), key: (1,0), door: (1,1) + return match sector { + 0 => (0, 0), + 1 => (1, 0), + _ => (1, 1), + }; + } + + let third = size / 3.max(1); + let x_offset = sector * third; + let x = x_offset + (Math::random() * third as f64) as usize; + let y = (Math::random() * size as f64) as usize; + (x.min(size - 1), y) } pub(super) fn create_maze(size: usize, document: Document) -> Self { From 0abbb2fff9fead79d1c31691a89a0826329eb276 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:47:14 +0000 Subject: [PATCH 42/78] implement create_maze O(n) idiomatic for Rust path from start -> key -> door, taking into account that movement.rs doesn't let you move to the door square until you have reached the key. start, key and door positions are different always as difficult as possible for the size --- src/games/perception/maze.rs | 193 +++++++++++++++++++---------------- 1 file changed, 105 insertions(+), 88 deletions(-) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index 51eb101..c5c6e02 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -2,116 +2,133 @@ use js_sys::Math; use std::collections::HashSet; use web_sys::Document; -use super::Perception; - -struct DisjointSet { - parent: Vec, - rank: Vec, -} - -impl DisjointSet { - fn new(size: usize) -> Self { - Self { - parent: (0..size).collect(), - rank: vec![0; size], - } - } - - fn find(&mut self, x: usize) -> usize { - if self.parent[x] != x { - self.parent[x] = self.find(self.parent[x]); - } - self.parent[x] - } - - fn union(&mut self, x: usize, y: usize) { - let root_x = self.find(x); - let root_y = self.find(y); - - if root_x != root_y { - match self.rank[root_x].cmp(&self.rank[root_y]) { - std::cmp::Ordering::Less => self.parent[root_x] = root_y, - std::cmp::Ordering::Greater => self.parent[root_y] = root_x, - std::cmp::Ordering::Equal => { - self.parent[root_y] = root_x; - self.rank[root_x] += 1; - } - } - } - } -} - impl Perception { - fn random_position(size: usize, sector: usize) -> (usize, usize) { - if size <= 2 { - // Special handling for 2x2: - // start: (0,0), key: (1,0), door: (1,1) - return match sector { - 0 => (0, 0), - 1 => (1, 0), - _ => (1, 1), - }; - } - - let third = size / 3.max(1); - let x_offset = sector * third; - let x = x_offset + (Math::random() * third as f64) as usize; - let y = (Math::random() * size as f64) as usize; - (x.min(size - 1), y) - } - pub(super) fn create_maze(size: usize, document: Document) -> Self { - let total_cells = size * size; - let mut walls = vec![true; total_cells * 4]; - let mut ds = DisjointSet::new(total_cells); - let mut edges = Vec::with_capacity(2 * size * (size - 1)); + let total_walls = size * size * 4; + let mut walls = vec![true; total_walls]; + let mut visited = HashSet::new(); + + // Generate random positions for start, key, and door + let start_x = (Math::random() * size as f64) as usize; + let start_y = (Math::random() * size as f64) as usize; + let start_position = (start_x, start_y); + + // Place key in opposite quadrant from start + let key_x = if start_x < size / 2 { + size/2 + (Math::random() * (size/2) as f64) as usize + } else { + (Math::random() * (size/2) as f64) as usize + }; + let key_y = if start_y < size / 2 { + size/2 + (Math::random() * (size/2) as f64) as usize + } else { + (Math::random() * (size/2) as f64) as usize + }; + let key_position = (key_x, key_y); - // Generate all possible edges - for cell in 0..total_cells { - let x = cell % size; - let y = cell / size; - if x < size - 1 { - edges.push((cell, cell + 1, cell * 4 + 1)); - } - if y < size - 1 { - edges.push((cell, cell + size, cell * 4 + 2)); - } - } + // Place door far from both start and key + let door_x = (start_x + size/2) % size; + let door_y = (key_y + size/2) % size; + let door_position = (door_x, door_y); - // Fisher-Yates shuffle - for i in (1..edges.len()).rev() { - edges.swap(i, (Math::random() * (i + 1) as f64) as usize); - } + // Wilson's algorithm for maze generation + let mut unvisited: HashSet<(usize, usize)> = (0..size) + .flat_map(|y| (0..size).map(move |x| (x, y))) + .collect(); + + // Start with initial cell + visited.insert(start_position); + unvisited.remove(&start_position); - // Generate positions in different sectors - let start_position = Self::random_position(size, 0); - let key_position = Self::random_position(size, 1); - let door_position = Self::random_position(size, 2); + // Generate paths until all cells are connected + while !unvisited.is_empty() { + let mut current = *unvisited.iter().next().unwrap(); + let mut path = vec![current]; + + // Random walk until we hit a visited cell + while !visited.contains(¤t) { + let directions = [ + (0, -1), (1, 0), (0, 1), (-1, 0) + ]; + + let valid_directions: Vec<(i32, i32)> = directions + .iter() + .filter(|(dx, dy)| { + let new_x = current.0 as i32 + dx; + let new_y = current.1 as i32 + dy; + new_x >= 0 && new_x < size as i32 && + new_y >= 0 && new_y < size as i32 + }) + .cloned() + .collect(); + + let (dx, dy) = valid_directions[ + (Math::random() * valid_directions.len() as f64) as usize + ]; + + current = ( + (current.0 as i32 + dx) as usize, + (current.1 as i32 + dy) as usize + ); + + path.push(current); + } - // Create maze using Kruskal's algorithm - for (cell1, cell2, wall_idx) in edges { - if ds.find(cell1) != ds.find(cell2) { + // Carve the path + for window in path.windows(2) { + let from = window[0]; + let to = window[1]; + let wall_idx = Self::get_wall_index(&Self::default(), from.0, from.1, to.0, to.1); + walls[wall_idx] = false; + // Remove opposite wall too + let wall_idx = Self::get_wall_index(&Self::default(), to.0, to.1, from.0, from.1); walls[wall_idx] = false; - // Mirror wall removal - walls[wall_idx + if wall_idx % 4 == 1 { 2 } else { size * 4 - 2 }] = false; - ds.union(cell1, cell2); + visited.insert(from); + unvisited.remove(&from); } } + // Initialize with start position visited + visited.clear(); + visited.insert(start_position); + Self { size, walls, + visited, current_position: start_position, start_position, key_position, door_position, has_key: false, - visited: HashSet::from([start_position]), + document, level: 1, + mazes_completed: 0, time_remaining: 300, last_tick: js_sys::Date::now() / 1000.0, - document, + } + } +} + +impl Default for Perception { + fn default() -> Self { + Self { + size: 0, + walls: Vec::new(), + visited: HashSet::new(), + current_position: (0, 0), + start_position: (0, 0), + key_position: (0, 0), + door_position: (0, 0), + has_key: false, + document: web_sys::window() + .unwrap() + .document() + .unwrap(), + level: 1, mazes_completed: 0, + time_remaining: 300, + last_tick: 0.0, } } } \ No newline at end of file From cada1a5660b5036de8c13d4562d38f42bbd9a805 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Wed, 12 Feb 2025 22:04:31 +0000 Subject: [PATCH 43/78] oops --- src/games/perception/maze.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index c5c6e02..c3c7ea4 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -1,6 +1,7 @@ use js_sys::Math; use std::collections::HashSet; use web_sys::Document; +use crate::Perception; impl Perception { pub(super) fn create_maze(size: usize, document: Document) -> Self { From 16dd9a5c611e8b69c134365002117888bd3b903d Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Wed, 12 Feb 2025 23:02:40 +0000 Subject: [PATCH 44/78] implement create_maze, ensuring: O(n) idiomatic for Rust outsource whatever possible to other crates (N.B. browsers doesn't support OS random) path from start -> key -> door, taking into account that movement.rs doesn't let you move to the door square until you have reached the key. must work for all size square mazes including 2x2 start position is not fixed output guaranteed to be as difficult as possible for the size --- src/games/perception/maze.rs | 253 ++++++++++++++++++++--------------- 1 file changed, 143 insertions(+), 110 deletions(-) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index c3c7ea4..dd4f23c 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -1,135 +1,168 @@ +use crate::Perception; +use js_sys::Date; use js_sys::Math; -use std::collections::HashSet; +use std::collections::{HashSet, VecDeque}; use web_sys::Document; -use crate::Perception; impl Perception { pub(super) fn create_maze(size: usize, document: Document) -> Self { - let total_walls = size * size * 4; - let mut walls = vec![true; total_walls]; - let mut visited = HashSet::new(); - - // Generate random positions for start, key, and door - let start_x = (Math::random() * size as f64) as usize; - let start_y = (Math::random() * size as f64) as usize; - let start_position = (start_x, start_y); - - // Place key in opposite quadrant from start - let key_x = if start_x < size / 2 { - size/2 + (Math::random() * (size/2) as f64) as usize - } else { - (Math::random() * (size/2) as f64) as usize - }; - let key_y = if start_y < size / 2 { - size/2 + (Math::random() * (size/2) as f64) as usize - } else { - (Math::random() * (size/2) as f64) as usize - }; - let key_position = (key_x, key_y); + // Number of cells and walls per cell (top, right, bottom, left) + let total_cells = size * size; + let wall_per_cell = 4; + // Initialize all walls to true (wall exists) + let mut walls = vec![true; total_cells * wall_per_cell]; + + // Create a visited grid for DFS maze generation + let mut visited_cells = vec![false; total_cells]; + + // Utility: convert (r, c) into index + let idx = |r: usize, c: usize| r * size + c; - // Place door far from both start and key - let door_x = (start_x + size/2) % size; - let door_y = (key_y + size/2) % size; - let door_position = (door_x, door_y); + // Random start cell for DFS + let start_row = (Math::random() * size as f64).floor() as usize; + let start_col = (Math::random() * size as f64).floor() as usize; - // Wilson's algorithm for maze generation - let mut unvisited: HashSet<(usize, usize)> = (0..size) - .flat_map(|y| (0..size).map(move |x| (x, y))) - .collect(); - - // Start with initial cell - visited.insert(start_position); - unvisited.remove(&start_position); + // Stack for DFS; push starting cell. + let mut stack = vec![(start_row, start_col)]; + visited_cells[idx(start_row, start_col)] = true; - // Generate paths until all cells are connected - while !unvisited.is_empty() { - let mut current = *unvisited.iter().next().unwrap(); - let mut path = vec![current]; - - // Random walk until we hit a visited cell - while !visited.contains(¤t) { - let directions = [ - (0, -1), (1, 0), (0, 1), (-1, 0) - ]; - - let valid_directions: Vec<(i32, i32)> = directions - .iter() - .filter(|(dx, dy)| { - let new_x = current.0 as i32 + dx; - let new_y = current.1 as i32 + dy; - new_x >= 0 && new_x < size as i32 && - new_y >= 0 && new_y < size as i32 - }) - .cloned() - .collect(); - - let (dx, dy) = valid_directions[ - (Math::random() * valid_directions.len() as f64) as usize - ]; - - current = ( - (current.0 as i32 + dx) as usize, - (current.1 as i32 + dy) as usize - ); - - path.push(current); + // Directions: (dr, dc, current wall index, neighbor wall index) + let directions = [ + (-1, 0, 0, 2), // Up: remove top for current, bottom for neighbor + (0, 1, 1, 3), // Right: remove right for current, left for neighbor + (1, 0, 2, 0), // Down: remove bottom for current, top for neighbor + (0, -1, 3, 1), // Left: remove left for current, right for neighbor + ]; + + // Iterative DFS to generate spanning tree maze + while let Some((r, c)) = stack.last().copied() { + // Collect unvisited valid neighbors in a vector + let mut neighbors = Vec::new(); + for &(dr, dc, cur_wall, nb_wall) in &directions { + let new_r = r as isize + dr; + let new_c = c as isize + dc; + if new_r >= 0 && new_r < size as isize && new_c >= 0 && new_c < size as isize { + let new_r = new_r as usize; + let new_c = new_c as usize; + if !visited_cells[idx(new_r, new_c)] { + neighbors.push((new_r, new_c, cur_wall, nb_wall)); + } + } } - // Carve the path - for window in path.windows(2) { - let from = window[0]; - let to = window[1]; - let wall_idx = Self::get_wall_index(&Self::default(), from.0, from.1, to.0, to.1); - walls[wall_idx] = false; - // Remove opposite wall too - let wall_idx = Self::get_wall_index(&Self::default(), to.0, to.1, from.0, from.1); - walls[wall_idx] = false; - visited.insert(from); - unvisited.remove(&from); + if !neighbors.is_empty() { + // Shuffle neighbors using js_sys::Math::random + neighbors.sort_by(|_, _| { + if Math::random() < 0.5 { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + } + }); + let (nr, nc, cur_wall, nb_wall) = neighbors[0]; + // Remove wall between current and neighbor + let cell_base = idx(r, c) * wall_per_cell; + walls[cell_base + cur_wall] = false; + let nb_base = idx(nr, nc) * wall_per_cell; + walls[nb_base + nb_wall] = false; + // Mark neighbor as visited and push it to the stack + visited_cells[idx(nr, nc)] = true; + stack.push((nr, nc)); + } else { + stack.pop(); } } - // Initialize with start position visited - visited.clear(); - visited.insert(start_position); + // --- BFS helper to find furthest cell and parents --- + // Returns: (furthest_cell, parent mapping vector) + let bfs = |start_cell: (usize, usize)| { + let mut dist = vec![None; total_cells]; + let mut parent = vec![None; total_cells]; + let start_index = idx(start_cell.0, start_cell.1); + let mut queue = VecDeque::new(); + dist[start_index] = Some(0); + queue.push_back(start_index); + + while let Some(current) = queue.pop_front() { + let r = current / size; + let c = current % size; + // Check all four directions + for &(dr, dc, cur_wall, _) in &directions { + let nr = r as isize + dr; + let nc = c as isize + dc; + if nr >= 0 && nr < size as isize && nc >= 0 && nc < size as isize { + let nr = nr as usize; + let nc = nc as usize; + let neighbor_index = idx(nr, nc); + // If the wall between current and neighbor is open then move there. + let cell_base = current * wall_per_cell; + if !walls[cell_base + cur_wall] && dist[neighbor_index].is_none() { + dist[neighbor_index] = Some(dist[current].unwrap() + 1); + parent[neighbor_index] = Some(current); + queue.push_back(neighbor_index); + } + } + } + } + // Find furthest cell from start_cell + let (furthest_index, _) = dist + .iter() + .enumerate() + .filter_map(|(i, d)| d.map(|distance| (i, distance))) + .max_by_key(|&(_, distance)| distance) + .unwrap(); + ((furthest_index / size, furthest_index % size), parent) + }; + + // --- Find longest path (maze diameter) --- + // First BFS from the random start chosen in DFS: + let (cell_a, _) = bfs((start_row, start_col)); + // Second BFS starting from cell_a to find the furthest cell_b and record parents + let (cell_b, parent_map) = bfs(cell_a); + // Reconstruct the path from cell_b back to cell_a + let mut path = Vec::new(); + let mut current = idx(cell_b.0, cell_b.1); + path.push(current); + while let Some(p) = parent_map[current] { + path.push(p); + current = p; + } + path.reverse(); // Now path is from cell_a to cell_b + + // --- Set start, door, key --- + // start is one end and door is at the other. + let start_cell = cell_a; + let door_cell = cell_b; + // Choose key from an intermediate cell in the path. + let key_cell = if path.len() >= 3 { + // valid random index in (0,len-1) excluding endpoints: indices 1..path.len()-1 + let key_idx = 1 + ((Math::random() * ((path.len() - 2) as f64)).floor() as usize); + let key = path[key_idx]; + (key / size, key % size) + } else { + // if path too short, use start_cell (this rarely happens except in tiny labyrinths) + start_cell + }; + + // --- Initialize remaining fields --- + // Current position starts at start_cell and visited is initialized with it. + let mut visited = HashSet::new(); + visited.insert(start_cell); Self { + document, size, walls, + current_position: start_cell, + start_position: start_cell, + key_position: key_cell, + door_position: door_cell, visited, - current_position: start_position, - start_position, - key_position, - door_position, has_key: false, - document, level: 1, mazes_completed: 0, time_remaining: 300, - last_tick: js_sys::Date::now() / 1000.0, + last_tick: Date::now() / 1000.0, } } } - -impl Default for Perception { - fn default() -> Self { - Self { - size: 0, - walls: Vec::new(), - visited: HashSet::new(), - current_position: (0, 0), - start_position: (0, 0), - key_position: (0, 0), - door_position: (0, 0), - has_key: false, - document: web_sys::window() - .unwrap() - .document() - .unwrap(), - level: 1, - mazes_completed: 0, - time_remaining: 300, - last_tick: 0.0, - } - } -} \ No newline at end of file From 6147eba56833030be7cd2b5c769b9f2822899487 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 19:16:03 +0000 Subject: [PATCH 45/78] record number of moves --- src/games/perception/maze.rs | 1 + src/games/perception/mod.rs | 2 +- src/games/perception/movement.rs | 3 +++ src/games/perception/render.rs | 4 ++-- static/index.html | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index dd4f23c..8fe92d8 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -161,6 +161,7 @@ impl Perception { has_key: false, level: 1, mazes_completed: 0, + moves: 0, // <-- Initialize moves to 0 time_remaining: 300, last_tick: Date::now() / 1000.0, } diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index 522be4b..5e2144a 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -24,7 +24,7 @@ pub struct Perception { size: usize, level: usize, #[serde(default)] - mazes_completed: usize, + moves: usize, // <-- New field to record moves // Maze elements walls: Vec, diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index 998ce49..af956bf 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -59,6 +59,9 @@ impl Perception { return -1; } + // Record the move before updating the position + self.moves += 1; // <-- Increment move counter + self.current_position = (x, y); self.visited.insert((x, y)); diff --git a/src/games/perception/render.rs b/src/games/perception/render.rs index bdabcbe..5b8578f 100644 --- a/src/games/perception/render.rs +++ b/src/games/perception/render.rs @@ -44,8 +44,8 @@ impl Perception { if let Some(level_el) = self.document.get_element_by_id("level") { level_el.set_text_content(Some(&self.level.to_string())); } - if let Some(completed_el) = self.document.get_element_by_id("completed") { - completed_el.set_text_content(Some(&self.mazes_completed.to_string())); + if let Some(completed_el) = self.document.get_element_by_id("moves") { + completed_el.set_text_content(Some(&self.moves.to_string())); } if let Some(timer_el) = self.document.get_element_by_id("timer") { let minutes = self.time_remaining / 60; diff --git a/static/index.html b/static/index.html index 84fc91b..50106eb 100644 --- a/static/index.html +++ b/static/index.html @@ -110,7 +110,7 @@
Level: 1 | - Completed: 0 | + Moves: 0 | Time: 5:00
From 540b0259e78051ecb3488763892846ef25f93926 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 20:52:23 +0000 Subject: [PATCH 46/78] fix init --- src/games/perception/maze.rs | 3 +-- src/games/perception/mod.rs | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index 8fe92d8..0539e6e 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -160,8 +160,7 @@ impl Perception { visited, has_key: false, level: 1, - mazes_completed: 0, - moves: 0, // <-- Initialize moves to 0 + moves: 0, time_remaining: 300, last_tick: Date::now() / 1000.0, } diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index 5e2144a..661919d 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -127,10 +127,9 @@ impl Perception { #[wasm_bindgen] pub fn reset_to_level_one(&mut self) -> Result<(), JsValue> { // Only reset if above level 1 - if self.size > 1 { + if self.size > 2 { self.size = 2; // Level 1 starts with size 2 self.level = 1; - self.mazes_completed = 0; // Create new level 1 maze let new_game = Self::create_maze(self.size, self.document.clone()); @@ -147,8 +146,8 @@ impl Perception { if let Some(level_el) = self.document.get_element_by_id("level") { level_el.set_text_content(Some("1")); } - if let Some(completed_el) = self.document.get_element_by_id("completed") { - completed_el.set_text_content(Some("0")); + if let Some(el) = self.document.get_element_by_id("moves") { + el.set_text_content(Some("0")); } if let Some(timer_el) = self.document.get_element_by_id("timer") { timer_el.set_text_content(Some("5:00")); From b3052bd6e5df5ad6a0c9ccaa46d02f5c3ca6217c Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 21:28:47 +0000 Subject: [PATCH 47/78] continue timer after leaving tab --- src/games/perception/mod.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index 661919d..54f0d88 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -53,17 +53,32 @@ impl Perception { .local_storage()? .expect("no local storage"); - let mut game = if let Some(state) = storage.get_item("maze_state")? { + let now = js_sys::Date::now(); + let game = if let Some(state) = storage.get_item("maze_state")? { let last_save = storage .get_item("maze_time")? .unwrap_or_else(|| "0".to_string()) .parse::() .unwrap_or(0.0); - if js_sys::Date::now() - last_save > 300000.0 { + // If more than 5 minutes (300000 ms) have passed since last save, create a new maze. + if now - last_save > 300000.0 { Self::create_maze(2, document) } else { - serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)? + // Deserialize the saved state. + let mut game: Self = + serde_wasm_bindgen::from_value(js_sys::JSON::parse(&state)?)?; + let now_secs = now / 1000.0; + let elapsed = now_secs - game.last_tick; + if elapsed as i32 >= game.time_remaining { + // Timer expired: start a new maze. + Self::create_maze(2, document) + } else { + // Otherwise adjust the time remaining based on elapsed time. + game.time_remaining -= elapsed as i32; + game.last_tick = now_secs; + game + } } } else { Self::create_maze(2, document) From 78e6c7d33bc62db6837eb871439340c6793cc121 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 21:30:51 +0000 Subject: [PATCH 48/78] fix --- src/games/perception/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index 54f0d88..edfbd64 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -54,7 +54,7 @@ impl Perception { .expect("no local storage"); let now = js_sys::Date::now(); - let game = if let Some(state) = storage.get_item("maze_state")? { + let mut game = if let Some(state) = storage.get_item("maze_state")? { let last_save = storage .get_item("maze_time")? .unwrap_or_else(|| "0".to_string()) From cfb0ea514157dc53cc13cdcd2ad0ea64da5905a4 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 22:02:47 +0000 Subject: [PATCH 49/78] fix(perception): grid foreground now similar to mindgage --- static/index.html | 16 +++------------- static/styles.css | 6 ------ 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/static/index.html b/static/index.html index 50106eb..b21fba8 100644 --- a/static/index.html +++ b/static/index.html @@ -12,21 +12,19 @@ --cell-size: min(5vw, 3.75rem); --visited-color: #999999; --pointer-color: #000000; - --key-color: #000000; - --door-color: #000000; + --grid-fg: #0a0310; } @media (prefers-color-scheme: dark) { :root { --visited-color: #333333; - --pointer-color: #959595; - --key-color: #959595; - --door-color: #959595; + --grid-fg: #5e1d92; } } .grid { display: grid; + color: var(--grid-fg); gap: 0; margin: 1.25rem auto; position: relative; @@ -90,14 +88,6 @@ transform: translateY(-50%) rotate(90deg); } - .key { - color: var(--key-color); - } - - .door { - color: var(--door-color); - } - #stats { color: var(--text-color); margin: 1rem; diff --git a/static/styles.css b/static/styles.css index 94a5dd3..9ba2d1e 100644 --- a/static/styles.css +++ b/static/styles.css @@ -3,12 +3,6 @@ --bg-color: #666666; --text-color: #000000; --border-color: #000000; - --visited-color: #4B0082; - --pointer-color: #000000; - --key-color: #000000; - --door-color: #000000; - --bubble-color: #737373; - --bubble-selected: #999999; /* Cursor variables */ --cursor-stroke: #000000; From 38c2d99e12a919353778537bc9863b2bfd4f4f8a Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 22:24:27 +0000 Subject: [PATCH 50/78] fix(perception): improve contrast --- static/index.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/static/index.html b/static/index.html index b21fba8..b701512 100644 --- a/static/index.html +++ b/static/index.html @@ -13,12 +13,16 @@ --visited-color: #999999; --pointer-color: #000000; --grid-fg: #0a0310; + /* Darkest shade of #a532ff that yields β‰₯ 3:1 contrast on a #999999 background */ + --visited-fg: #491672; } @media (prefers-color-scheme: dark) { :root { --visited-color: #333333; --grid-fg: #5e1d92; + /* Darkest shade of #a532ff that yields β‰₯ 3:1 contrast on a #333333 background */ + --visited-fg: #be3aff; } } @@ -44,8 +48,10 @@ position: relative; } + /* For visited or current cells, you can now apply the new fg colour as needed */ .visited, .current { background-color: var(--visited-color); + color: var(--visited-fg); } .current::before, From cdc684625af7d006fb21a6e64dfc10a71773d781 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 22:30:42 +0000 Subject: [PATCH 51/78] fix(perception): restore dark mode pointer colour --- static/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/static/index.html b/static/index.html index b701512..e47fef1 100644 --- a/static/index.html +++ b/static/index.html @@ -21,6 +21,7 @@ :root { --visited-color: #333333; --grid-fg: #5e1d92; + --pointer-color: #959595; /* Darkest shade of #a532ff that yields β‰₯ 3:1 contrast on a #333333 background */ --visited-fg: #be3aff; } From 5e80e9ea9da2cf59b5aa59f2ef489b8d0fa0bc47 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 23:12:29 +0000 Subject: [PATCH 52/78] fix contrast --- static/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/index.html b/static/index.html index e47fef1..bde96f0 100644 --- a/static/index.html +++ b/static/index.html @@ -20,7 +20,7 @@ @media (prefers-color-scheme: dark) { :root { --visited-color: #333333; - --grid-fg: #5e1d92; + --grid-fg: #8200E6; --pointer-color: #959595; /* Darkest shade of #a532ff that yields β‰₯ 3:1 contrast on a #333333 background */ --visited-fg: #be3aff; From b37339edc08f28dc67a510e2ce6d9fc531351c87 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 23:18:24 +0000 Subject: [PATCH 53/78] fix(perception): reset moves properly --- src/games/perception/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index edfbd64..8c2ccec 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -121,6 +121,7 @@ impl Perception { self.reset_position(); // Reset timer state completely + self.moves = 0; self.time_remaining = 300; self.last_tick = js_sys::Date::now() / 1000.0; From 72af365fc9db4067f84776f77cd43be7821d818c Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 23:51:01 +0000 Subject: [PATCH 54/78] center maze and timer --- static/index.html | 20 +++++++++++++------- static/styles.css | 6 ------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/static/index.html b/static/index.html index bde96f0..fca1eaf 100644 --- a/static/index.html +++ b/static/index.html @@ -29,11 +29,11 @@ .grid { display: grid; - color: var(--grid-fg); - gap: 0; margin: 1.25rem auto; + gap: 0; position: relative; - clip-path: inset(0); + clip-path: inset(0 -0.125rem 0 0); + width: max-content; } .cell { @@ -49,7 +49,6 @@ position: relative; } - /* For visited or current cells, you can now apply the new fg colour as needed */ .visited, .current { background-color: var(--visited-color); color: var(--visited-fg); @@ -99,17 +98,24 @@ color: var(--text-color); margin: 1rem; text-align: center; + font-size: 1.5rem; /* 24px - qualifies as large text */ + } + + #timer-display { + font-size: 1.5rem; + text-align: center; + margin-bottom: 1rem; + font-weight: bold; } -
Level: 1 | - Moves: 0 | - Time: 5:00 + Moves: 0
+
5:00
diff --git a/static/styles.css b/static/styles.css index 9ba2d1e..21fbcef 100644 --- a/static/styles.css +++ b/static/styles.css @@ -34,12 +34,6 @@ body { line-height: 1.5; } -#stats { - color: var(--text-color); - margin: 1rem; - text-align: center; - font-size: 1.5rem; /* 24px - qualifies as large text */ -} .cell:hover { --cursor-stroke: #4A90E2; --cursor-fill: rgba(74, 144, 226, 0.1); From e4d6531f256053c0038e4cde9692dc38f96d4e03 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 23:58:46 +0000 Subject: [PATCH 55/78] fix syntax error --- static/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/index.html b/static/index.html index fca1eaf..0518f9d 100644 --- a/static/index.html +++ b/static/index.html @@ -115,7 +115,7 @@ Level: 1 | Moves: 0
-
5:00 +
5:00
From 225879309ce3629527d9a8b4501c75b50ffc2d01 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Thu, 13 Feb 2025 23:59:48 +0000 Subject: [PATCH 56/78] yeahh maybe i need to sleep --- static/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/index.html b/static/index.html index 0518f9d..c19e9f9 100644 --- a/static/index.html +++ b/static/index.html @@ -101,7 +101,7 @@ font-size: 1.5rem; /* 24px - qualifies as large text */ } - #timer-display { + #timer { font-size: 1.5rem; text-align: center; margin-bottom: 1rem; From 25931dee32ac98cf122f050e6529bf2965a04e3c Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:26:04 +0000 Subject: [PATCH 57/78] fix 2x2 maze gen --- src/games/perception/maze.rs | 97 +++++++++++++++++------------------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index 0539e6e..a924249 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -6,46 +6,46 @@ use web_sys::Document; impl Perception { pub(super) fn create_maze(size: usize, document: Document) -> Self { - // Number of cells and walls per cell (top, right, bottom, left) + // Total cells and walls per cell (top, right, bottom, left) let total_cells = size * size; let wall_per_cell = 4; - // Initialize all walls to true (wall exists) let mut walls = vec![true; total_cells * wall_per_cell]; - // Create a visited grid for DFS maze generation + // DFS setup for spanning tree generation let mut visited_cells = vec![false; total_cells]; + let idx = |r: usize, c: usize| r * size + c; // utility: (row, col) -> index - // Utility: convert (r, c) into index - let idx = |r: usize, c: usize| r * size + c; - - // Random start cell for DFS + // Pick a random starting cell (row, col) let start_row = (Math::random() * size as f64).floor() as usize; let start_col = (Math::random() * size as f64).floor() as usize; - // Stack for DFS; push starting cell. let mut stack = vec![(start_row, start_col)]; visited_cells[idx(start_row, start_col)] = true; // Directions: (dr, dc, current wall index, neighbor wall index) + // Here we assume cell coordinates are (row, col): + // Up: (r-1, c) uses wall 0 in current and 2 in neighbor. + // Right: (r, c+1) uses wall 1 in current and 3 in neighbor. + // Down: (r+1, c) uses wall 2 in current and 0 in neighbor. + // Left: (r, c-1) uses wall 3 in current and 1 in neighbor. let directions = [ - (-1, 0, 0, 2), // Up: remove top for current, bottom for neighbor - (0, 1, 1, 3), // Right: remove right for current, left for neighbor - (1, 0, 2, 0), // Down: remove bottom for current, top for neighbor - (0, -1, 3, 1), // Left: remove left for current, right for neighbor + (-1, 0, 0, 2), + (0, 1, 1, 3), + (1, 0, 2, 0), + (0, -1, 3, 1), ]; - // Iterative DFS to generate spanning tree maze + // Iterative DFS: remove walls to create a spanning tree while let Some((r, c)) = stack.last().copied() { - // Collect unvisited valid neighbors in a vector let mut neighbors = Vec::new(); for &(dr, dc, cur_wall, nb_wall) in &directions { - let new_r = r as isize + dr; - let new_c = c as isize + dc; - if new_r >= 0 && new_r < size as isize && new_c >= 0 && new_c < size as isize { - let new_r = new_r as usize; - let new_c = new_c as usize; - if !visited_cells[idx(new_r, new_c)] { - neighbors.push((new_r, new_c, cur_wall, nb_wall)); + let nr = r as isize + dr; + let nc = c as isize + dc; + if nr >= 0 && nr < size as isize && nc >= 0 && nc < size as isize { + let nr = nr as usize; + let nc = nc as usize; + if !visited_cells[idx(nr, nc)] { + neighbors.push((nr, nc, cur_wall, nb_wall)); } } } @@ -60,12 +60,11 @@ impl Perception { } }); let (nr, nc, cur_wall, nb_wall) = neighbors[0]; - // Remove wall between current and neighbor + // Remove walls between current and neighbor let cell_base = idx(r, c) * wall_per_cell; walls[cell_base + cur_wall] = false; let nb_base = idx(nr, nc) * wall_per_cell; walls[nb_base + nb_wall] = false; - // Mark neighbor as visited and push it to the stack visited_cells[idx(nr, nc)] = true; stack.push((nr, nc)); } else { @@ -73,53 +72,51 @@ impl Perception { } } - // --- BFS helper to find furthest cell and parents --- - // Returns: (furthest_cell, parent mapping vector) + // --- BFS helper: find furthest cell (and parent pointers) --- let bfs = |start_cell: (usize, usize)| { let mut dist = vec![None; total_cells]; let mut parent = vec![None; total_cells]; - let start_index = idx(start_cell.0, start_cell.1); + let start_idx = idx(start_cell.0, start_cell.1); let mut queue = VecDeque::new(); - dist[start_index] = Some(0); - queue.push_back(start_index); + dist[start_idx] = Some(0); + queue.push_back(start_idx); while let Some(current) = queue.pop_front() { let r = current / size; let c = current % size; - // Check all four directions for &(dr, dc, cur_wall, _) in &directions { let nr = r as isize + dr; let nc = c as isize + dc; if nr >= 0 && nr < size as isize && nc >= 0 && nc < size as isize { let nr = nr as usize; let nc = nc as usize; - let neighbor_index = idx(nr, nc); - // If the wall between current and neighbor is open then move there. + let neighbor_idx = idx(nr, nc); + // Only move if there is no wall between current and neighbor. let cell_base = current * wall_per_cell; - if !walls[cell_base + cur_wall] && dist[neighbor_index].is_none() { - dist[neighbor_index] = Some(dist[current].unwrap() + 1); - parent[neighbor_index] = Some(current); - queue.push_back(neighbor_index); + if !walls[cell_base + cur_wall] && dist[neighbor_idx].is_none() { + dist[neighbor_idx] = Some(dist[current].unwrap() + 1); + parent[neighbor_idx] = Some(current); + queue.push_back(neighbor_idx); } } } } - // Find furthest cell from start_cell - let (furthest_index, _) = dist + + // Find the furthest cell from the start + let (furthest_idx, _) = dist .iter() .enumerate() - .filter_map(|(i, d)| d.map(|distance| (i, distance))) - .max_by_key(|&(_, distance)| distance) + .filter_map(|(i, d)| d.map(|d| (i, d))) + .max_by_key(|&(_, d)| d) .unwrap(); - ((furthest_index / size, furthest_index % size), parent) + ((furthest_idx / size, furthest_idx % size), parent) }; - // --- Find longest path (maze diameter) --- - // First BFS from the random start chosen in DFS: + // --- Determine maze endpoints using the diameter --- let (cell_a, _) = bfs((start_row, start_col)); - // Second BFS starting from cell_a to find the furthest cell_b and record parents let (cell_b, parent_map) = bfs(cell_a); - // Reconstruct the path from cell_b back to cell_a + + // Reconstruct the unique path (from cell_a to cell_b) let mut path = Vec::new(); let mut current = idx(cell_b.0, cell_b.1); path.push(current); @@ -127,25 +124,23 @@ impl Perception { path.push(p); current = p; } - path.reverse(); // Now path is from cell_a to cell_b + path.reverse(); - // --- Set start, door, key --- - // start is one end and door is at the other. + // Assign start and door as the two endpoints. let start_cell = cell_a; let door_cell = cell_b; - // Choose key from an intermediate cell in the path. + + // Place the key on an intermediate cell on the path. let key_cell = if path.len() >= 3 { - // valid random index in (0,len-1) excluding endpoints: indices 1..path.len()-1 + // Pick a random intermediate index (excluding endpoints) let key_idx = 1 + ((Math::random() * ((path.len() - 2) as f64)).floor() as usize); let key = path[key_idx]; (key / size, key % size) } else { - // if path too short, use start_cell (this rarely happens except in tiny labyrinths) start_cell }; // --- Initialize remaining fields --- - // Current position starts at start_cell and visited is initialized with it. let mut visited = HashSet::new(); visited.insert(start_cell); From 399be01f1578d6eb352531eca6a3f00082799d27 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:34:11 +0000 Subject: [PATCH 58/78] fix 2x2 maze gen --- src/games/perception/maze.rs | 23 +++++++++++++---------- src/games/perception/movement.rs | 2 ++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/games/perception/maze.rs b/src/games/perception/maze.rs index a924249..a17aec4 100644 --- a/src/games/perception/maze.rs +++ b/src/games/perception/maze.rs @@ -23,7 +23,6 @@ impl Perception { visited_cells[idx(start_row, start_col)] = true; // Directions: (dr, dc, current wall index, neighbor wall index) - // Here we assume cell coordinates are (row, col): // Up: (r-1, c) uses wall 0 in current and 2 in neighbor. // Right: (r, c+1) uses wall 1 in current and 3 in neighbor. // Down: (r+1, c) uses wall 2 in current and 0 in neighbor. @@ -126,20 +125,24 @@ impl Perception { } path.reverse(); - // Assign start and door as the two endpoints. - let start_cell = cell_a; - let door_cell = cell_b; - - // Place the key on an intermediate cell on the path. - let key_cell = if path.len() >= 3 { + // In the DFS/BFS we used (row, col) order. + // Convert to (x, y) where x = col and y = row to match movement.rs. + let convert = |(r, c): (usize, usize)| (c, r); + let start_rc = cell_a; + let door_rc = cell_b; + let key_rc = if path.len() >= 3 { // Pick a random intermediate index (excluding endpoints) let key_idx = 1 + ((Math::random() * ((path.len() - 2) as f64)).floor() as usize); - let key = path[key_idx]; - (key / size, key % size) + let cell = path[key_idx]; + (cell / size, cell % size) } else { - start_cell + start_rc }; + let start_cell = convert(start_rc); + let door_cell = convert(door_rc); + let key_cell = convert(key_rc); + // --- Initialize remaining fields --- let mut visited = HashSet::new(); visited.insert(start_cell); diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index af956bf..aeec805 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -89,6 +89,8 @@ impl Perception { self.visited.clear(); self.visited.insert(new_game.start_position); // Insert correct start position self.has_key = false; + self.moves = 0; + self.time_remaining = 300; self.last_tick = js_sys::Date::now() / 1000.0; return 2; From a62e96aa74ae26eb5eacf3f59c0dcded50bbe179 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:49:07 +0000 Subject: [PATCH 59/78] feat(perception): After a wall hit, before resetting the position, animate the wall that has been hit. --- src/games/perception/movement.rs | 29 ++++++++++++++++++++++++++++- static/index.html | 1 + 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index aeec805..0bcc618 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -42,6 +42,32 @@ impl Perception { } } + fn animate_wall_hit(&self, target_x: usize, target_y: usize) -> Result<(), JsValue> { + let maze = self.document.get_element_by_id("maze").unwrap(); + let index = self.current_position.1 * self.size + self.current_position.0; + if let Some(cell) = maze.children().item(index as u32) { + let dir = if target_x > self.current_position.0 { + "right" + } else if target_x < self.current_position.0 { + "left" + } else if target_y > self.current_position.1 { + "bottom" + } else { + "top" + }; + let class = format!("hit-{}", dir); + cell.class_list().add_1(&class)?; + let cell_clone = cell.clone(); + let class_clone = class.clone(); + let closure = Closure::wrap(Box::new(move || { + let _ = cell_clone.class_list().remove_1(&class_clone); + }) as Box); + window().unwrap().set_timeout_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), 300)?; + closure.forget(); + } + Ok(()) + } + pub(super) fn try_move(&mut self, x: usize, y: usize) -> i32 { if !self.is_adjacent(x, y) { return 0; @@ -55,6 +81,8 @@ impl Perception { } if self.walls[wall_idx] { + // Animate the wall hit before resetting position. + let _ = self.animate_wall_hit(x, y); self.reset_position(); return -1; } @@ -90,7 +118,6 @@ impl Perception { self.visited.insert(new_game.start_position); // Insert correct start position self.has_key = false; self.moves = 0; - self.time_remaining = 300; self.last_tick = js_sys::Date::now() / 1000.0; return 2; diff --git a/static/index.html b/static/index.html index c19e9f9..7db1fc8 100644 --- a/static/index.html +++ b/static/index.html @@ -47,6 +47,7 @@ justify-content: center; align-items: center; position: relative; + user-select: none; } .visited, .current { From 8ce7f1d8568755840590db8e00b09488bf37cda1 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:49:34 +0000 Subject: [PATCH 60/78] add imports --- src/games/perception/movement.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index 0bcc618..748f0bb 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -1,4 +1,8 @@ use super::Perception; +use wasm_bindgen::prelude::*; +use wasm_bindgen::closure::Closure; +use wasm_bindgen::JsCast; +use web_sys::window; impl Perception { pub(super) fn is_adjacent(&self, x: usize, y: usize) -> bool { From d796ec923d9675f1c950d72e8779ad92eca08a5e Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:50:04 +0000 Subject: [PATCH 61/78] foobar --- static/index.html | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/static/index.html b/static/index.html index 7db1fc8..6a3d865 100644 --- a/static/index.html +++ b/static/index.html @@ -108,6 +108,23 @@ margin-bottom: 1rem; font-weight: bold; } + + .cell.hit-top { + border-top: 2px solid red; + transition: border-top 300ms ease; + } + .cell.hit-bottom { + border-bottom: 2px solid red; + transition: border-bottom 300ms ease; + } + .cell.hit-left { + border-left: 2px solid red; + transition: border-left 300ms ease; + } + .cell.hit-right { + border-right: 2px solid red; + transition: border-right 300ms ease; + } From 2485dd39a4d3ecb85f34fbf954478b8b0efa5187 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 08:07:27 +0000 Subject: [PATCH 62/78] set door colour --- static/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/static/index.html b/static/index.html index 6a3d865..be53925 100644 --- a/static/index.html +++ b/static/index.html @@ -29,6 +29,7 @@ .grid { display: grid; + color: var(--grid-fg); margin: 1.25rem auto; gap: 0; position: relative; From fd6e3762ad7ca5fc52c4084567a0560680fbb855 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 09:29:40 +0000 Subject: [PATCH 63/78] make animation last longer --- static/index.html | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/static/index.html b/static/index.html index be53925..6ed89ab 100644 --- a/static/index.html +++ b/static/index.html @@ -109,22 +109,21 @@ margin-bottom: 1rem; font-weight: bold; } - + .hit { + transition: border 1000ms ease-out; + --border: var(--cell-size) solid red; + } .cell.hit-top { - border-top: 2px solid red; - transition: border-top 300ms ease; + border-top: var(--border); } .cell.hit-bottom { - border-bottom: 2px solid red; - transition: border-bottom 300ms ease; + border-bottom: var(--border); } .cell.hit-left { - border-left: 2px solid red; - transition: border-left 300ms ease; + border-left: var(--border); } .cell.hit-right { - border-right: 2px solid red; - transition: border-right 300ms ease; + border-right: var(--border); } From 073d8dadfc357c46ca98fd69414557b8fb075ca8 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 09:35:49 +0000 Subject: [PATCH 64/78] oops --- static/index.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/static/index.html b/static/index.html index 6ed89ab..404edda 100644 --- a/static/index.html +++ b/static/index.html @@ -49,6 +49,8 @@ align-items: center; position: relative; user-select: none; + transition: border 1000ms ease-out; + --border: var(--cell-size) solid red; } .visited, .current { @@ -109,10 +111,7 @@ margin-bottom: 1rem; font-weight: bold; } - .hit { - transition: border 1000ms ease-out; - --border: var(--cell-size) solid red; - } + .cell.hit-top { border-top: var(--border); } From 73e4c5652c9d8aef50a97028232551dfa09bdd1d Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:56:14 +0000 Subject: [PATCH 65/78] try to make animation last after position reset --- src/games/perception/mod.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index 8c2ccec..6398f94 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -134,11 +134,32 @@ impl Perception { self.render().expect("Failed to render reset"); } fn reset_position(&mut self) { + let old_pos = self.current_position; self.current_position = self.start_position; self.visited.clear(); self.visited.insert(self.start_position); self.has_key = false; - self.render().expect("Failed to render position reset"); + + // Update only the changed cells rather than the entire grid. + let maze = self + .document + .get_element_by_id("maze") + .expect("Maze element not found"); + + // Helper closure to update a specific cell. + let update_cell = |x: usize, y: usize, game: &Self| -> Result<(), JsValue> { + let index = (y * game.size + x) as u32; + if let Some(cell) = maze.children().item(index) { + game.update_cell_state(&cell, x, y)?; + } + Ok(()) + }; + + // Update the old cell if it differs from start_position. + if old_pos != self.start_position { + let _ = update_cell(old_pos.0, old_pos.1, self); + } + let _ = update_cell(self.start_position.0, self.start_position.1, self); } #[wasm_bindgen] pub fn reset_to_level_one(&mut self) -> Result<(), JsValue> { From 3cf08a1caef7b81db386f855bc66bc743e6e8c94 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:05:17 +0000 Subject: [PATCH 66/78] fix? --- src/games/perception/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index 6398f94..7d96a47 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -147,10 +147,10 @@ impl Perception { .expect("Maze element not found"); // Helper closure to update a specific cell. - let update_cell = |x: usize, y: usize, game: &Self| -> Result<(), JsValue> { + let update_cell = |x: usize, y: usize| -> Result<(), JsValue> { let index = (y * game.size + x) as u32; if let Some(cell) = maze.children().item(index) { - game.update_cell_state(&cell, x, y)?; + self.update_cell_state(&cell, x, y)?; } Ok(()) }; From d7df01e65ae5de42be062ba1611a345d6e0e7682 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:19:41 +0000 Subject: [PATCH 67/78] pub --- src/games/perception/mod.rs | 4 ++-- src/games/perception/render.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index 7d96a47..bc9ead5 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -157,9 +157,9 @@ impl Perception { // Update the old cell if it differs from start_position. if old_pos != self.start_position { - let _ = update_cell(old_pos.0, old_pos.1, self); + let _ = update_cell(old_pos.0, old_pos.1); } - let _ = update_cell(self.start_position.0, self.start_position.1, self); + let _ = update_cell(self.start_position.0, self.start_position.1); } #[wasm_bindgen] pub fn reset_to_level_one(&mut self) -> Result<(), JsValue> { diff --git a/src/games/perception/render.rs b/src/games/perception/render.rs index 5b8578f..03ca051 100644 --- a/src/games/perception/render.rs +++ b/src/games/perception/render.rs @@ -55,7 +55,7 @@ impl Perception { Ok(()) } - fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { + pub fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { // Reset base class cell.set_class_name("cell"); From 6f50d4f6bd23c3a90c4e2769068e9c95660185d4 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:19:49 +0000 Subject: [PATCH 68/78] ? --- src/games/perception/render.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/games/perception/render.rs b/src/games/perception/render.rs index 03ca051..cb559d5 100644 --- a/src/games/perception/render.rs +++ b/src/games/perception/render.rs @@ -55,7 +55,7 @@ impl Perception { Ok(()) } - pub fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { + pub(super) fn update_cell_state(&self, cell: &Element, x: usize, y: usize) -> Result<(), JsValue> { // Reset base class cell.set_class_name("cell"); From 6dc908bc34eea884d046105b813b352c6da9e0d7 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:27:18 +0000 Subject: [PATCH 69/78] ?? --- src/games/perception/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/games/perception/mod.rs b/src/games/perception/mod.rs index bc9ead5..978423a 100644 --- a/src/games/perception/mod.rs +++ b/src/games/perception/mod.rs @@ -148,7 +148,7 @@ impl Perception { // Helper closure to update a specific cell. let update_cell = |x: usize, y: usize| -> Result<(), JsValue> { - let index = (y * game.size + x) as u32; + let index = (y * self.size + x) as u32; if let Some(cell) = maze.children().item(index) { self.update_cell_state(&cell, x, y)?; } From a8e164481f79a64435f83fea2c3ac28e95fabbdb Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 19:01:53 +0000 Subject: [PATCH 70/78] fix(perception): ensure that animation displays use web animations API --- src/games/perception/movement.rs | 45 ++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index 748f0bb..7db16c5 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -5,8 +5,8 @@ use wasm_bindgen::JsCast; use web_sys::window; impl Perception { - pub(super) fn is_adjacent(&self, x: usize, y: usize) -> bool { - let current_x = self.current_position.0; + // Changed visibility from `pub(super)` to restrict to perception. + pub(in crate::games::perception) fn is_adjacent(&self, x: usize, y: usize) -> bool { let current_y = self.current_position.1; // Check if target position is adjacent (up, down, left, right) @@ -50,24 +50,37 @@ impl Perception { let maze = self.document.get_element_by_id("maze").unwrap(); let index = self.current_position.1 * self.size + self.current_position.0; if let Some(cell) = maze.children().item(index as u32) { - let dir = if target_x > self.current_position.0 { - "right" + // Determine which border to animate. + let border_prop = if target_x > self.current_position.0 { + "border-right" } else if target_x < self.current_position.0 { - "left" + "border-left" } else if target_y > self.current_position.1 { - "bottom" + "border-bottom" } else { - "top" + "border-top" }; - let class = format!("hit-{}", dir); - cell.class_list().add_1(&class)?; - let cell_clone = cell.clone(); - let class_clone = class.clone(); - let closure = Closure::wrap(Box::new(move || { - let _ = cell_clone.class_list().remove_1(&class_clone); - }) as Box); - window().unwrap().set_timeout_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), 300)?; - closure.forget(); + + // Build keyframes: start with a strong colored border, then transition to none. + let keyframes = js_sys::Array::new(); + + let start_frame = js_sys::Object::new(); + js_sys::Reflect::set(&start_frame, &JsValue::from_str("offset"), &JsValue::from_f64(0.0))?; + js_sys::Reflect::set(&start_frame, &JsValue::from_str(border_prop), &JsValue::from_str("2px solid red"))?; + keyframes.push(&start_frame); + + let end_frame = js_sys::Object::new(); + js_sys::Reflect::set(&end_frame, &JsValue::from_str("offset"), &JsValue::from_f64(1.0))?; + js_sys::Reflect::set(&end_frame, &JsValue::from_str(border_prop), &JsValue::from_str("0px solid transparent"))?; + keyframes.push(&end_frame); + + // Set animation options: duration and persistence on finish. + let options = js_sys::Object::new(); + js_sys::Reflect::set(&options, &JsValue::from_str("duration"), &JsValue::from_f64(300.0))?; + js_sys::Reflect::set(&options, &JsValue::from_str("fill"), &JsValue::from_str("forwards"))?; + + // Start the animation. The returned Animation isn’t used here. + let _anim = cell.animate(&keyframes.into(), &options.into())?; } Ok(()) } From 233016354e8bed91b40145fca5a6815841f91adf Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 20:13:53 +0000 Subject: [PATCH 71/78] what happened there --- src/games/perception/movement.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index 7db16c5..cba40db 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -5,8 +5,8 @@ use wasm_bindgen::JsCast; use web_sys::window; impl Perception { - // Changed visibility from `pub(super)` to restrict to perception. - pub(in crate::games::perception) fn is_adjacent(&self, x: usize, y: usize) -> bool { + pub(super) fn is_adjacent(&self, x: usize, y: usize) -> bool { + let current_x = self.current_position.0; let current_y = self.current_position.1; // Check if target position is adjacent (up, down, left, right) From e4d9d4067b595a764b388a110b0c9ad4489a91b2 Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 20:28:01 +0000 Subject: [PATCH 72/78] add missing Cargo.toml feature --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 4e7fbf0..d8f3467 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ features = [ "Location", "Performance", "Text", + "Animation", ] [dev-dependencies] From 9c98522833b030972e8b33e395b9edd8601b59ea Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 21:28:05 +0000 Subject: [PATCH 73/78] fix missing animation import? --- Cargo.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d8f3467..f042ebc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ serde-wasm-bindgen = "0.5" console_error_panic_hook = { version = "0.1.5", optional = true } [dependencies.web-sys] -version = "0.3.22" +version = "0.3.77" features = [ "console", "Document", @@ -54,4 +54,6 @@ features = [ [dev-dependencies] wasm-bindgen-test = "0.2.45" futures = "0.1.27" -wasm-bindgen-futures = "0.3.22" \ No newline at end of file +wasm-bindgen-futures = "0.3.22" +[build] +rustflags = ["--cfg=web_sys_unstable_apis"] From 7a0c69c153f9b32dbc64e69554dd8d2265e4895e Mon Sep 17 00:00:00 2001 From: Umar Sharief <80610051+noneofyourbusiness1415252@users.noreply.github.com> Date: Fri, 14 Feb 2025 22:19:43 +0000 Subject: [PATCH 74/78] why can't i see the animation --- src/games/perception/movement.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index cba40db..62af131 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -100,7 +100,25 @@ impl Perception { if self.walls[wall_idx] { // Animate the wall hit before resetting position. let _ = self.animate_wall_hit(x, y); - self.reset_position(); + + // Delay resetting position to let the animation play. + let window = web_sys::window().unwrap(); + let game_ptr = self as *mut Self; // SAFETY: used within a controlled closure + let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move || { + unsafe { + (*game_ptr).reset_position(); + // (Optionally, update only the affected cells instead of full re-render.) + } + }) as Box); + + window + .set_timeout_with_callback_and_timeout_and_arguments_0( + closure.as_ref().unchecked_ref(), + 2000, + ) + .unwrap(); + closure.forget(); + return -1; } From 59c56fef5d408a73434c6764e59c0f31cc0bbe99 Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Fri, 14 Feb 2025 22:23:10 +0000 Subject: [PATCH 75/78] support animation --- .cargo/config.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..8467175 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["--cfg=web_sys_unstable_apis"] From 51ea5156ab75a96500648b7c511102ed0142d238 Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sat, 15 Feb 2025 00:35:16 +0000 Subject: [PATCH 76/78] cba --- Cargo.toml | 1 + src/games/perception/movement.rs | 51 +++++++++++++++++++++----------- static/index.html | 35 +++++++++++++++++----- 3 files changed, 62 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f042ebc..7e0c176 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ features = [ "Performance", "Text", "Animation", + "KeyframeAnimationOptions", ] [dev-dependencies] diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index 62af131..c30ee01 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -1,6 +1,6 @@ use super::Perception; -use wasm_bindgen::prelude::*; use wasm_bindgen::closure::Closure; +use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use web_sys::window; @@ -61,26 +61,43 @@ impl Perception { "border-top" }; - // Build keyframes: start with a strong colored border, then transition to none. + // Build keyframes: from red border to no border. let keyframes = js_sys::Array::new(); let start_frame = js_sys::Object::new(); - js_sys::Reflect::set(&start_frame, &JsValue::from_str("offset"), &JsValue::from_f64(0.0))?; - js_sys::Reflect::set(&start_frame, &JsValue::from_str(border_prop), &JsValue::from_str("2px solid red"))?; + js_sys::Reflect::set( + &start_frame, + &JsValue::from_str("offset"), + &JsValue::from_f64(0.0), + )?; + js_sys::Reflect::set( + &start_frame, + &JsValue::from_str(border_prop), + &JsValue::from_str("var(--cell-size) solid red"), + )?; keyframes.push(&start_frame); let end_frame = js_sys::Object::new(); - js_sys::Reflect::set(&end_frame, &JsValue::from_str("offset"), &JsValue::from_f64(1.0))?; - js_sys::Reflect::set(&end_frame, &JsValue::from_str(border_prop), &JsValue::from_str("0px solid transparent"))?; + js_sys::Reflect::set( + &end_frame, + &JsValue::from_str("offset"), + &JsValue::from_f64(1.0), + )?; + js_sys::Reflect::set( + &end_frame, + &JsValue::from_str(border_prop), + &JsValue::from_str("0px solid transparent"), + )?; keyframes.push(&end_frame); - // Set animation options: duration and persistence on finish. - let options = js_sys::Object::new(); - js_sys::Reflect::set(&options, &JsValue::from_str("duration"), &JsValue::from_f64(300.0))?; - js_sys::Reflect::set(&options, &JsValue::from_str("fill"), &JsValue::from_str("forwards"))?; + // Create KeyframeAnimationOptions and set the options. + let options = web_sys::KeyframeAnimationOptions::new(); + options.set_duration(&10000.0.into()); + + // Use animate_with_keyframe_animation_options. + let anim = cell.animate_with_f64(Some(keyframes.as_ref()), 10000.0); - // Start the animation. The returned Animation isn’t used here. - let _anim = cell.animate(&keyframes.into(), &options.into())?; + web_sys::console::log_3(&anim, &keyframes, &options); } Ok(()) } @@ -100,7 +117,7 @@ impl Perception { if self.walls[wall_idx] { // Animate the wall hit before resetting position. let _ = self.animate_wall_hit(x, y); - + // Delay resetting position to let the animation play. let window = web_sys::window().unwrap(); let game_ptr = self as *mut Self; // SAFETY: used within a controlled closure @@ -110,20 +127,20 @@ impl Perception { // (Optionally, update only the affected cells instead of full re-render.) } }) as Box); - + window .set_timeout_with_callback_and_timeout_and_arguments_0( closure.as_ref().unchecked_ref(), - 2000, + 1000, ) .unwrap(); closure.forget(); - + return -1; } // Record the move before updating the position - self.moves += 1; // <-- Increment move counter + self.moves += 1; // <-- Increment move counter self.current_position = (x, y); self.visited.insert((x, y)); diff --git a/static/index.html b/static/index.html index 404edda..bb27f72 100644 --- a/static/index.html +++ b/static/index.html @@ -49,8 +49,8 @@ align-items: center; position: relative; user-select: none; - transition: border 1000ms ease-out; - --border: var(--cell-size) solid red; + /* transition: border 1000ms ease-out; + --border: var(--cell-size) solid red; */ } .visited, .current { @@ -112,18 +112,37 @@ font-weight: bold; } - .cell.hit-top { - border-top: var(--border); + /* Define keyframe animations for each hitting direction */ + @keyframes hitTop { + from { border-top: var(--cell-size) solid red; } + to { border-top: 0px solid transparent; } + } + @keyframes hitBottom { + from { border-bottom: var(--cell-size) solid red; } + to { border-bottom: 0px solid transparent; } + } + @keyframes hitLeft { + from { border-left: var(--cell-size) solid red; } + to { border-left: 0px solid transparent; } + } + @keyframes hitRight { + from { border-right: var(--cell-size) solid red; } + to { border-right: 0px solid transparent; } + } + + /* Apply the animations via classes */ + /* .cell.hit-top { + animation: hitTop 1000ms ease-out forwards; } .cell.hit-bottom { - border-bottom: var(--border); + animation: hitBottom 1000ms ease-out forwards; } .cell.hit-left { - border-left: var(--border); + animation: hitLeft 1000ms ease-out forwards; } .cell.hit-right { - border-right: var(--border); - } + animation: hitRight 1000ms ease-out forwards; + } */ From dc7e0c384d39298293268f8f0b8160adee55a142 Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sun, 16 Feb 2025 14:31:59 +0000 Subject: [PATCH 77/78] fix animation property names --- src/games/perception/movement.rs | 42 +++++++------------------------- static/index.html | 4 +++ 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/src/games/perception/movement.rs b/src/games/perception/movement.rs index c30ee01..8605d1a 100644 --- a/src/games/perception/movement.rs +++ b/src/games/perception/movement.rs @@ -52,13 +52,13 @@ impl Perception { if let Some(cell) = maze.children().item(index as u32) { // Determine which border to animate. let border_prop = if target_x > self.current_position.0 { - "border-right" + "borderRight" } else if target_x < self.current_position.0 { - "border-left" + "borderLeft" } else if target_y > self.current_position.1 { - "border-bottom" + "borderBottom" } else { - "border-top" + "borderTop" }; // Build keyframes: from red border to no border. @@ -73,7 +73,7 @@ impl Perception { js_sys::Reflect::set( &start_frame, &JsValue::from_str(border_prop), - &JsValue::from_str("var(--cell-size) solid red"), + &JsValue::from_str("1ch solid var(--magma-color)"), )?; keyframes.push(&start_frame); @@ -89,20 +89,14 @@ impl Perception { &JsValue::from_str("0px solid transparent"), )?; keyframes.push(&end_frame); - - // Create KeyframeAnimationOptions and set the options. - let options = web_sys::KeyframeAnimationOptions::new(); - options.set_duration(&10000.0.into()); - - // Use animate_with_keyframe_animation_options. - let anim = cell.animate_with_f64(Some(keyframes.as_ref()), 10000.0); - - web_sys::console::log_3(&anim, &keyframes, &options); + let anim = cell.animate_with_f64(Some(&keyframes), 1000.0); + web_sys::console::log_2(&anim, &keyframes); } Ok(()) } pub(super) fn try_move(&mut self, x: usize, y: usize) -> i32 { + #[cfg(debug_assertions)] if !self.is_adjacent(x, y) { return 0; } @@ -117,25 +111,7 @@ impl Perception { if self.walls[wall_idx] { // Animate the wall hit before resetting position. let _ = self.animate_wall_hit(x, y); - - // Delay resetting position to let the animation play. - let window = web_sys::window().unwrap(); - let game_ptr = self as *mut Self; // SAFETY: used within a controlled closure - let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move || { - unsafe { - (*game_ptr).reset_position(); - // (Optionally, update only the affected cells instead of full re-render.) - } - }) as Box); - - window - .set_timeout_with_callback_and_timeout_and_arguments_0( - closure.as_ref().unchecked_ref(), - 1000, - ) - .unwrap(); - closure.forget(); - + self.reset_position(); return -1; } diff --git a/static/index.html b/static/index.html index bb27f72..264ea17 100644 --- a/static/index.html +++ b/static/index.html @@ -15,6 +15,8 @@ --grid-fg: #0a0310; /* Darkest shade of #a532ff that yields β‰₯ 3:1 contrast on a #999999 background */ --visited-fg: #491672; + /* Darkest shade of red that yields β‰₯ 3:1 contrast on --bg-color (#666666) */ + --magma-color: #400000; } @media (prefers-color-scheme: dark) { @@ -24,6 +26,8 @@ --pointer-color: #959595; /* Darkest shade of #a532ff that yields β‰₯ 3:1 contrast on a #333333 background */ --visited-fg: #be3aff; + /* Darkest shade of red that yields β‰₯ 3:1 contrast on --bg-color (#000000) */ + --magma-color: #B60000; } } From f575fbc6db937dd5fd2d69a4ab1a8774b8b187ae Mon Sep 17 00:00:00 2001 From: Umar Sharief Date: Sun, 16 Feb 2025 20:16:35 +0000 Subject: [PATCH 78/78] update README with game descriptions, installation instructions, and project structure --- README.md | 98 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 680f386..0cb8ad5 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,96 @@ -## How to install +# Cognitive Games -```sh +A collection of brain training games built with Rust and WebAssembly, focusing on numeracy and perception skills. + +## Games + +### Numeracy Game +A mathematical puzzle game that challenges your ability to compare numerical expressions. + +**Features:** +- Select expressions in ascending order +- Dynamic difficulty scaling +- Time-based scoring with a 15-second round timer +- Level progression based on performance +- Supports basic arithmetic operations (+, -, Γ—, Γ·) +- Decimal numbers in higher levels +- Progress auto-saving + +### Perception Game (Maze) +A procedurally generated maze game testing spatial awareness and planning. + +**Features:** +- Procedurally generated mazes using depth-first search +- Key-and-door mechanics +- Progressive difficulty with increasing maze size +- Move tracking +- 5-minute time limit per level +- Visual feedback for wall collisions +- Automatic progress saving +- Dark mode support + +## Prerequisites + +- Rust (nightly toolchain) +- Node.js +- npm + +## Installation + +```bash +# Clone the repository +git clone https://github.com/noneofyourbusiness1415252/cognitive-games.git +cd cognitive-games + +# Install dependencies and build the project npm install +npm run prepare ``` -## How to run in debug mode +## Development -```sh -# Builds the project and opens it in a new browser tab. Auto-reloads when the project changes. +Start the development server: + +```bash npm start ``` -## How to build in release mode +The games will be accessible at: +- Numeracy Game: http://localhost:80/numeracy.html +- Maze Game: http://localhost:80/ + +## Building for Production -```sh -# Builds the project and places it into the `dist` folder. +```bash npm run build ``` -## How to run unit tests +The output will be in the `dist` directory. -```sh -# Runs tests in Firefox -npm test -- --firefox +## Testing -# Runs tests in Chrome -npm test -- --chrome +Run the test suite: -# Runs tests in Safari -npm test -- --safari +```bash +npm test ``` -## What does each file do? - -* `Cargo.toml` contains the standard Rust metadata. You put your Rust dependencies in here. You must change this file with your details (name, description, version, authors, categories) +This will run both Rust unit tests and WebAssembly integration tests. -* `package.json` contains the standard npm metadata. You put your JavaScript dependencies in here. You must change this file with your details (author, name, version) +## Project Structure -* `webpack.config.js` contains the Webpack configuration. You shouldn't need to change this, unless you have very special needs. +- `src/games/numeracy/` - Numeracy game implementation +- `src/games/perception/` - Maze game implementation +- `static/` - HTML, CSS, and other static assets +- `js/` - JavaScript entry point +- `Cargo.toml` - Rust dependencies and configuration +- `package.json` - Node.js dependencies and scripts +- `webpack.config.js` - Webpack configuration -* The `js` folder contains your JavaScript code (`index.js` is used to hook everything into Webpack, you don't need to change it). +## License -* The `src` folder contains your Rust code. +This project is licensed under the MIT License - see the LICENSE file for details. -* The `static` folder contains any files that you want copied as-is into the final build. It contains an `index.html` file which loads the `index.js` file. +## Author -* The `tests` folder contains your Rust unit tests. +Umar Sharief \ No newline at end of file