diff --git a/grand-gardenias/.github/workflows/lint.yaml b/grand-gardenias/.github/workflows/lint.yaml new file mode 100644 index 00000000..7f67e803 --- /dev/null +++ b/grand-gardenias/.github/workflows/lint.yaml @@ -0,0 +1,35 @@ +# GitHub Action workflow enforcing our code style. + +name: Lint + +# Trigger the workflow on both push (to the main repository, on the main branch) +# and pull requests (against the main repository, but from any repo, from any branch). +on: + push: + branches: + - main + pull_request: + +# Brand new concurrency setting! This ensures that not more than one run can be triggered for the same commit. +# It is useful for pull requests coming from the main repository since both triggers will match. +concurrency: lint-${{ github.sha }} + +jobs: + lint: + runs-on: ubuntu-latest + + env: + # The Python version your project uses. Feel free to change this if required. + PYTHON_VERSION: "3.12" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Run pre-commit hooks + uses: pre-commit/action@v3.0.1 diff --git a/grand-gardenias/.gitignore b/grand-gardenias/.gitignore new file mode 100644 index 00000000..61f27918 --- /dev/null +++ b/grand-gardenias/.gitignore @@ -0,0 +1,38 @@ +# Files generated by the interpreter +__pycache__/ +*.py[cod] + +# Environment specific +.venv +venv +.env +env + +# Unittest reports +.coverage* + +# Logs +*.log + +# PyEnv version selector +.python-version + +# Built objects +*.so +dist/ +build/ + +# IDEs +# PyCharm +.idea/ +# VSCode +.vscode/ +# MacOS +.DS_Store + +# npm and tailwindcss +# Dependency directory +node_modules/ + +# Generated files +dist/ diff --git a/grand-gardenias/.pre-commit-config.yaml b/grand-gardenias/.pre-commit-config.yaml new file mode 100644 index 00000000..c0a8de23 --- /dev/null +++ b/grand-gardenias/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +# Pre-commit configuration. +# See https://github.com/python-discord/code-jam-template/tree/main#pre-commit-run-linting-before-committing + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.2 + hooks: + - id: ruff-check + - id: ruff-format diff --git a/grand-gardenias/LICENSE.txt b/grand-gardenias/LICENSE.txt new file mode 100644 index 00000000..5a04926b --- /dev/null +++ b/grand-gardenias/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2021 Python Discord + +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/grand-gardenias/README.md b/grand-gardenias/README.md new file mode 100644 index 00000000..6126baa7 --- /dev/null +++ b/grand-gardenias/README.md @@ -0,0 +1,142 @@ +

+ Project Logo +

+ +# 👾 Tetris Bugs: The Code Editor You Never Asked For + +Built by **Grand Gardenias** for Python Discord's Summer CodeJam 2025. Challenge theme: **Wrong Tool for the Job** using **Python in the Browser** via [Pyscript](https://pyscript.net/). + +--- + +## 🎯 The Concept + +Instead of typing code like a normal human, you now have to **catch falling code blocks** and arrange them into working programs. + +## 🎮 Game Modes (Choose Your Suffering) + +### 1. 🏗️ Tetris Code Editor + +- New File +- Export/Save +- Undo/Redo +- Run Code +- Terminal Output +- Code Falls from Sky + +### 2. ⚡ CodeRush Mode + +- 5-minute timer counting down +- Questions show up on the left +- Solve as many as you can before time’s up +- Your score = how many problems you solved before the end + +### 3. 🗡️ Roguelike Mode + +- We provide the CORRECT solution +- Arrange the blocks properly to "clear" lines like Tetris +- Wrong arrangements stack up +- Reach the top = GAME OVER + +### 4. We could not finish the multiplayer mode, but it is documented in [docs/goals.md](docs/goals.md) + +## 🛠️ Tech Stack + +- **PyScript** +- **TailwindCSS** + +## 📦 Requirements + +- Python 3.13+ +- Node.js & npm + +## ⚙️ Setup Instructions + +1. Clone the repository: + ```shell + git clone https://github.com/zishankadri/tetris-bugs.git + cd tetris-bugs + ``` +3. Start a local Server: + ```shell + cd frontend + npm install + npm run build + python -m http.server + ``` + +3. **Open Your Browser** to `http://localhost:8000` +4. 💡 For Contributors: Run `npm run dev` to auto-update Tailwind while you code. + +## 🎉 Achievement Unlocked +**Congratulations! You now have the most unique answer to "What IDE do you use?"** + +*"Oh, I use Tetris."* + +## Video Presentation +[code-editor.webm](https://github.com/user-attachments/assets/cf059496-4f29-4d98-a95a-8969f91b333e) + +
+ ⚡ CodeRush Video Presentation + https://drive.google.com/file/d/1t6_92z2R_ntOok2yyzjklHpKyxoKZl_g/view +
+ +
+ 🗡️ Roguelike (BETA) Video Presentation + +[roguelike.webm](https://github.com/user-attachments/assets/ffc35054-e3de-4e12-ac63-f1e3520f2ee8) +
+ +## 📸 Screenshots +
+ Preview Images 📸 + code-editor + code-editor + code-rush + roguelike + menu + +
+ +## 🎯 Wrong Tool for the Job + +✅ Using Tetris as an IDE +✅ Spatial reasoning for coding problems + +With some tweaks and backend, this could be a useful tool as a coding qualifier, as we doubt any LLM alone could pass this test. + +## 🤝 Credits + +(in order of contributed LOC): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameContributions
LuffyLuffyLead developer, Project architecture, Ideation, Frontend
rxdiationxrxdiationxBase of CodeRush, sound effects/music, loading screen, Sourcing of problems
Sapient44Sapient44Terminal and code execution, Modifications in roguelike mode, Sourcing of audio
Shivk123Shivk123Meeting facilitation, Initial file structure, Set up Flask backend
mhasanali2010mhasanali2010timer logic, pause screen
diff --git a/grand-gardenias/backend/__init__.py b/grand-gardenias/backend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/backend/app.py b/grand-gardenias/backend/app.py new file mode 100644 index 00000000..ab00dd81 --- /dev/null +++ b/grand-gardenias/backend/app.py @@ -0,0 +1,21 @@ +# Define paths +from pathlib import Path + +from flask import Flask, render_template + +template_dir = Path(__file__).resolve().parent.parent / "frontend" +static_dir = template_dir # if your CSS/JS/config are in frontend/ + +app = Flask( + __name__, + template_folder=template_dir, + static_folder=static_dir, # serve static files from frontend/ + static_url_path="", # serve at root so /config.json works +) + + +@app.route("/") +def home() -> None: + """Render the main page.""" + # Render the index.html file from the frontend directory + return render_template("index.html") diff --git a/grand-gardenias/backend/run.py b/grand-gardenias/backend/run.py new file mode 100644 index 00000000..c9997e69 --- /dev/null +++ b/grand-gardenias/backend/run.py @@ -0,0 +1,40 @@ +import shutil +import subprocess +import sys +from pathlib import Path + +from flask.cli import main as flask_main + +DEFAULT_ARG_COUNT = 2 # Avoid magic number + + +def main() -> None: + """Run the Flask application and start the frontend development server.""" + frontend_dir = Path(__file__).parent.parent / "frontend" + + # Locate npm safely + npm_path = shutil.which("npm") + if npm_path is None: + print("npm not found in PATH.") + sys.exit(1) + + # Start npm dev server + try: + subprocess.Popen( # noqa: S603 + [npm_path, "run", "dev"], + cwd=frontend_dir, + ) + print("Frontend dev server started.") + except OSError as err: + print(f"Failed to start frontend: {err}") + + sys.argv.insert(1, "--app=backend.app") + + if len(sys.argv) == DEFAULT_ARG_COUNT: + sys.argv.append("run") + + flask_main() + + +if __name__ == "__main__": + main() diff --git a/grand-gardenias/docs/architecture.md b/grand-gardenias/docs/architecture.md new file mode 100644 index 00000000..56cfcf05 --- /dev/null +++ b/grand-gardenias/docs/architecture.md @@ -0,0 +1,23 @@ +# Project Architecture + +*(See ![Architecture Diagram](img/architecture-diagram.svg) for visual reference.)* + +**Goal:** Avoid circular imports, enable reuse across modes, separate concerns. + +## Layers + +1. **Objects Layer** — self-contained game entities (no external knowledge). +2. **Core Game Management** — orchestrates state, no DOM access. +3. **Support Modules** — UI Manager, controls, block generators. + +**Entry Point:** `main` wires components together and selects the mode. + +## Dependency Rules + +- Objects → no outward dependencies. +- Game Manager → can talk to objects & support modules, but no cycles. +- UI Manager → DOM Manipulation. +- Controls → interact with Game Manager and objects only. +- `main` → composition root, no logic. + +Modes can add their own files, but they must follow the same rules. diff --git a/grand-gardenias/docs/goals.md b/grand-gardenias/docs/goals.md new file mode 100644 index 00000000..b601fd07 --- /dev/null +++ b/grand-gardenias/docs/goals.md @@ -0,0 +1,13 @@ +# MULTIPLAYER + +![multiplayer screen](img/multiplayer.svg) + +## THE CONCEPT OF 👾 Bugs + +Occasionally, instead of the actual code blocks, the player will receive bugs from a number of pre-defined weapons in a class named 'Bugs' e.g. Bugs.beetle(). I originally thought of Bugs.bomb (), which on placing will immediately disappear from the player's own screen, but a bomb will appear at the same spot on the enemy's screen touching which will require the enemy to do over that one block, giving us a 'time advantage'. This will make so much sense if we create a 'time-based winner' game. (Open to suggestions this is just an example I have in mind for now.) + +![bugs](img/special-block.svg) + +## Bugs.beetle() + +![bugs](img/bugs.svg) diff --git a/grand-gardenias/docs/img/architecture-diagram.svg b/grand-gardenias/docs/img/architecture-diagram.svg new file mode 100644 index 00000000..9be508f0 --- /dev/null +++ b/grand-gardenias/docs/img/architecture-diagram.svg @@ -0,0 +1,3 @@ +UIManagerother mode-specific modulesMainDOMmanipulationControlsGame MangaerBlockAll Objects must beIsolatedObjects«constructor injection»«constructor injection»«setter injection»«constructor injection»«import»«import»«import»«import» diff --git a/grand-gardenias/docs/img/bugs.svg b/grand-gardenias/docs/img/bugs.svg new file mode 100644 index 00000000..849b6264 --- /dev/null +++ b/grand-gardenias/docs/img/bugs.svg @@ -0,0 +1,3 @@ +for i in ran_____ 6): if i % 2 == 0: print(f"{i} is even") else: print(f"{i} is odd")rripfoWorks the sameas Bugs.bomb(),just fancier. diff --git a/grand-gardenias/docs/img/logo.svg b/grand-gardenias/docs/img/logo.svg new file mode 100644 index 00000000..175d3c9d --- /dev/null +++ b/grand-gardenias/docs/img/logo.svg @@ -0,0 +1,2 @@ +T{TR!SGUBS diff --git a/grand-gardenias/docs/img/multiplayer.svg b/grand-gardenias/docs/img/multiplayer.svg new file mode 100644 index 00000000..66006f16 --- /dev/null +++ b/grand-gardenias/docs/img/multiplayer.svg @@ -0,0 +1,2 @@ +Enemy ScreeenYour screen diff --git a/grand-gardenias/docs/img/special-block.svg b/grand-gardenias/docs/img/special-block.svg new file mode 100644 index 00000000..bc0a5b85 --- /dev/null +++ b/grand-gardenias/docs/img/special-block.svg @@ -0,0 +1,2 @@ +BUGS.BOMBB() diff --git a/grand-gardenias/docs/refactoring/2025-08-15-singleplayer-architecture-update.md b/grand-gardenias/docs/refactoring/2025-08-15-singleplayer-architecture-update.md new file mode 100644 index 00000000..41743e4b --- /dev/null +++ b/grand-gardenias/docs/refactoring/2025-08-15-singleplayer-architecture-update.md @@ -0,0 +1,24 @@ +# Refactor: Singleplayer mode architecture update + +**Date:** 2025-08-15 + +**Reason:** +Modules in singleplayer mode were tightly coupled, making reuse in other modes impossible. +New features were causing circular imports, blocking further development. + +**Changes:** + +- Redesigned singleplayer architecture to decouple modules. +- Introduced clear separation of shared utilities and mode-specific logic. +- Updated imports across affected files to match new structure. + +**Impact:** + +- No more circular import issues. +- Modules can now be reused across different modes without modification. +- Easier to extend features without breaking unrelated components. + +**Follow-up:** + +- Apply the same architecture to all modes. +- Document the new module dependency flow in `docs/architecture.md`. diff --git a/grand-gardenias/frontend/__init__.py b/grand-gardenias/frontend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/engine/__init__.py b/grand-gardenias/frontend/engine/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/engine/constants.py b/grand-gardenias/frontend/engine/constants.py new file mode 100644 index 00000000..ad3eca62 --- /dev/null +++ b/grand-gardenias/frontend/engine/constants.py @@ -0,0 +1,2 @@ +MAX_BLOCK_LENGTH = 5 +TIMER_MINUTES = 5 diff --git a/grand-gardenias/frontend/engine/controls.py b/grand-gardenias/frontend/engine/controls.py new file mode 100644 index 00000000..83392a9b --- /dev/null +++ b/grand-gardenias/frontend/engine/controls.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from engine.game import GameManager + from engine.ui_manager import UIManager + +from js import KeyboardEvent, document + + +class BaseController: + """Handles user input and mediates interactions between the game state and the UI. + + Attributes: + game_manager (GameManager): The game state manager instance. + ui_manager (UIManager): The ui_manager responsible for updating the visual grid. + + """ + + def __init__(self, game_manager: GameManager, ui_manager: UIManager) -> None: + self.game_manager = game_manager + self.ui_manager = ui_manager + + def handle_key(self, evt: KeyboardEvent) -> None: + """Handle arrow keys and spacebar.""" + active = document.activeElement + if active and active.id == "text-input": + return + + if not self.game_manager.current_block: + return + + moved = False + if evt.key == "ArrowLeft": + moved = self.game_manager.current_block.move(-1, 0, self.game_manager.grid) + elif evt.key == "ArrowRight": + moved = self.game_manager.current_block.move(1, 0, self.game_manager.grid) + elif evt.key == "ArrowDown": + moved = self.game_manager.current_block.move(0, 1, self.game_manager.grid) + elif evt.key == " ": + evt.preventDefault() + self.game_manager.lock_current_block() + moved = True + + if moved: + self.ui_manager.render() diff --git a/grand-gardenias/frontend/engine/game.py b/grand-gardenias/frontend/engine/game.py new file mode 100644 index 00000000..bd388d7c --- /dev/null +++ b/grand-gardenias/frontend/engine/game.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from engine.ui_manager import BaseUIManager + +from engine.objects.block import Block +from engine.patterns import SingletonMeta + + +class BaseGameManager(metaclass=SingletonMeta): + """Game logic manager.""" + + def __init__(self, cols: int, rows: int) -> None: + self.cols, self.rows = cols, rows + self.grid: list[list[str | None]] = [[None for _ in range(self.cols)] for _ in range(self.rows)] + self.current_block: Block | None = None + self.ui_manager: BaseUIManager = None + + def tick(self) -> None: + """Advance game state by one step.""" + if not self.current_block or not self.current_block.falling: + return + + if not self.current_block.move(0, 1, self.grid): + self.lock_current_block() + + def spawn_block(self, text: str) -> None: + """Create and spawn a new block.""" + new_block = Block(text, self.cols, self.rows) + self.current_block = new_block + + self.ui_manager.render() + + def lock_current_block(self) -> None: + """Lock current block into grid.""" + self.current_block.lock(self.grid) + self.ui_manager.lock_visual_cells() + + self.current_block = None + + def format_grid_as_text(self) -> str: + """Return grid contents as plain text.""" + lines = [ + "".join(cell if cell else " " for cell in row) + for row in self.grid + if any(cell is not None for cell in row) + ] + return "\n".join(lines) if lines else "" + + def format_grid_line_as_text(self, i: int) -> str: + """Return the i-th line of the grid as text.""" + row = self.grid[i] + return "".join(cell if cell else " " for cell in row).rstrip() + + def clear_grid(self) -> None: + """Clear the grid by resetting all cells to None. + + Note: + This only updates the game state; visual updates are be handled + separately by the ui_manager. + + """ + self.current_block = None + self.grid = [[None for _ in range(self.cols)] for _ in range(self.rows)] + + def clear_row(self, row_index: int) -> None: + """Clear all cells in the specified row in the internal grid. + + Note: + This only updates the game state; visual updates are be handled + separately by the ui_manager. + + """ + self.grid[row_index] = [None for _ in range(self.cols)] diff --git a/grand-gardenias/frontend/engine/objects/__init__.py b/grand-gardenias/frontend/engine/objects/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/engine/objects/block.py b/grand-gardenias/frontend/engine/objects/block.py new file mode 100644 index 00000000..00c9c3c9 --- /dev/null +++ b/grand-gardenias/frontend/engine/objects/block.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from random import randint + +from engine.constants import MAX_BLOCK_LENGTH + + +class Block: + """Represents a horizontal text block moving on a grid.""" + + def __init__(self, text: str, grid_cols: int, grid_rows: int) -> None: + self.text = text[: min(MAX_BLOCK_LENGTH, grid_cols)] + self.cols = grid_cols + self.rows = grid_rows + # Spawns blocks randomly along the x-axis. + self.x = randint(0, self.cols - len(self.text)) # noqa: S311 + self.y = 0 + self.falling = True + + def can_move(self, dx: int, dy: int, grid: list[list[str | None]]) -> bool: + """Check if move is valid.""" + new_x = self.x + dx + new_y = self.y + dy + for i in range(len(self.text)): + tx, ty = new_x + i, new_y + if tx < 0 or tx >= self.cols or ty >= self.rows: + return False + if ty >= 0 and grid[ty][tx] is not None: + return False + return True + + def move(self, dx: int, dy: int, grid: list[list[str | None]]) -> bool: + """Try moving the block, return True if moved.""" + if self.can_move(dx, dy, grid): + # Update position if move is valid + self.x += dx + self.y += dy + return True + return False + + def lock(self, grid: list[list[str | None]]) -> None: + """Place the block into the grid and stop it from falling.""" + for i, ch in enumerate(self.text): + tx = self.x + i + ty = self.y + if 0 <= tx < self.cols and 0 <= ty < self.rows: + grid[ty][tx] = ch + self.falling = False + + def get_cells_coords(self) -> list[tuple[int, int]]: + """Return the coordinates of the current block on the grid.""" + return [(self.x + i, self.y) for i in range(len(self.text))] diff --git a/grand-gardenias/frontend/engine/objects/bugs.py b/grand-gardenias/frontend/engine/objects/bugs.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/engine/patterns.py b/grand-gardenias/frontend/engine/patterns.py new file mode 100644 index 00000000..b76a5e3d --- /dev/null +++ b/grand-gardenias/frontend/engine/patterns.py @@ -0,0 +1,8 @@ +class SingletonMeta(type): # noqa: D101 + _instance = None # type: object | None + + def __call__(cls: type["SingletonMeta"], *args: object, **kwargs: object) -> object: + """Return the singleton instance, creating it if necessary.""" + if cls._instance is None: + cls._instance = super().__call__(*args, **kwargs) + return cls._instance diff --git a/grand-gardenias/frontend/engine/ui_manager.py b/grand-gardenias/frontend/engine/ui_manager.py new file mode 100644 index 00000000..093a12d8 --- /dev/null +++ b/grand-gardenias/frontend/engine/ui_manager.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +from datetime import UTC, datetime +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from engine.game import GameManager +from js import URL, Blob, document + + +class BaseUIManager: + """Handles rendering and UI interactions for the game.""" + + def __init__(self, game: GameManager) -> None: + self.game = game + self.cells: list[list] = [] + self._last_rendered_grid: list[list[str | None]] = [[None for _ in range(game.cols)] for _ in range(game.rows)] + + def create_visual_grid(self) -> None: + """Create the visual grid in DOM.""" + game_div = document.getElementById("game") + fragment = document.createDocumentFragment() + + for _y in range(self.game.rows): + row = [] + for _x in range(self.game.cols): + cell = document.createElement("div") + cell.classList.add("cell") + fragment.appendChild(cell) + row.append(cell) + self.cells.append(row) + + game_div.appendChild(fragment) + + def render(self) -> None: + """Update only cells that have changed to match the current game state.""" + combined_grid = [row.copy() for row in self.game.grid] + + if self.game.current_block and self.game.current_block.falling: + for i, ch in enumerate(self.game.current_block.text): + tx = self.game.current_block.x + i + ty = self.game.current_block.y + if 0 <= tx < self.game.cols and 0 <= ty < self.game.rows: + combined_grid[ty][tx] = ch + + for y in range(self.game.rows): + for x in range(self.game.cols): + cell = self.cells[y][x] + current_char = combined_grid[y][x] + last_char = self._last_rendered_grid[y][x] + + if current_char != last_char: + if current_char is None: + cell.className = "cell" + cell.style.background = "" + cell.style.color = "" + cell.textContent = "" + else: + cell.className = "block" + cell.textContent = current_char + + self._last_rendered_grid[y][x] = current_char + + def save_grid_code_to_file(self) -> None: + """Prompt user to download current grid code.""" + saved_code = self.game.format_grid_as_text() + blob = Blob.new([saved_code], {"type": "text/x-python"}) + url = URL.createObjectURL(blob) + + download_link = document.createElement("a") + download_link.href = url + timestamp = datetime.now(UTC).strftime("%Y-%m-%d_%H-%M-%S") + download_link.download = f"tetris_code_{timestamp}.py" + download_link.style.display = "none" + + document.body.appendChild(download_link) + download_link.click() + document.body.removeChild(download_link) + URL.revokeObjectURL(url) + + def lock_visual_cells(self) -> None: + """Lock the cells occupied by the current block visually.""" + block_cords = self.game.current_block.get_cells_coords() + + block_cells = [self.cells[cord[1]][cord[0]] for cord in block_cords] + + for cell in block_cells: + cell.className = "locked-cell" + + def clear_grid(self) -> None: + """Clear the grid by resetting all cells to None.""" + self.game.clear_grid() + self._last_rendered_grid = [[None for _ in range(self.game.cols)] for _ in range(self.game.rows)] + + # Force clear all visual cells to ensure locked cells are reset + for y in range(self.game.rows): + for x in range(self.game.cols): + cell = self.cells[y][x] + cell.className = "cell" + cell.style.background = "" + cell.style.color = "" + cell.textContent = "" + self.render() + + def clear_row(self, row_index: int) -> None: + """Clear all cells in a specific row of the game grid. + + This will set all elements in the row to None in the game's internal + grid representation and update the visual representation on the DOM. + + Args: + row_index (int): The index of the row to clear (0-based). + + """ + # Clear the internal grid data + self.game.clear_row(row_index) + + # Clear the visual cells + for x in range(self.game.cols): + cell = self.cells[row_index][x] + + cell.className = "cell" + cell.style.background = "" + cell.style.color = "" + cell.textContent = "" + + # Update last rendered grid to reflect cleared state + self._last_rendered_grid[row_index] = [None for _ in range(self.game.cols)] + self.render() diff --git a/grand-gardenias/frontend/index.html b/grand-gardenias/frontend/index.html new file mode 100644 index 00000000..21a4e75b --- /dev/null +++ b/grand-gardenias/frontend/index.html @@ -0,0 +1,65 @@ + + + + + + + Game Menu + + + + + + + + + + + + + + + +

Tetris Bugs

+ + +
+ + Code Editor + Code Rush + Roguelike (beta) + Multiplayer (coming soon) +
+ + + + + + + + diff --git a/grand-gardenias/frontend/modes/code_editor/__init__.py b/grand-gardenias/frontend/modes/code_editor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/code_editor/config.json b/grand-gardenias/frontend/modes/code_editor/config.json new file mode 100644 index 00000000..356560a2 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/config.json @@ -0,0 +1,18 @@ +{ + "files": { + "../../engine/game.py": "engine/game.py", + "../../engine/patterns.py": "engine/patterns.py", + "../../engine/controls.py": "engine/controls.py", + "../../engine/ui_manager.py": "engine/ui_manager.py", + "../../engine/constants.py": "engine/constants.py", + "../../engine/objects/block.py": "engine/objects/block.py", + "../../shared/audio_utils.py": "shared/audio_utils.py", + + "./src/controls.py": "", + "./src/game.py": "", + "./src/modal.py": "", + "./src/ui_manager.py": "", + "./src/timer.py": "", + "./src/execute_code.py": "" + } +} diff --git a/grand-gardenias/frontend/modes/code_editor/index.html b/grand-gardenias/frontend/modes/code_editor/index.html new file mode 100644 index 00000000..75e556bd --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/index.html @@ -0,0 +1,154 @@ + + + + + + + Code Editor - Tetris + + + + + + + + + + + + + + + +
+
+

Tetris Bugs

+
+ +

Loading...

+
+
+ + + + + +
+ +
+ + +
+ + +
+ + +
+
+ + +
+ +
+ + +
+ + +
+ + +
+
+ + +
+ +
+ +
+ + +
+

Terminal

+
+
+
+ +
+ + + + + + + + + diff --git a/grand-gardenias/frontend/modes/code_editor/main.py b/grand-gardenias/frontend/modes/code_editor/main.py new file mode 100644 index 00000000..8020aa61 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/main.py @@ -0,0 +1,84 @@ +from typing import Tuple # noqa: UP035 + +from controls import Controller +from execute_code import run_python_code +from game import GameManager +from js import document, setInterval, window +from modal import continue_modal + +# pyright: reportMissingImports=false +from pyodide.ffi import create_proxy +from ui_manager import UIManager + +game_manager = GameManager(40, 20) + + +def main() -> None: + """Initialize the game.""" + ui_manager = UIManager(game_manager) + game_manager.ui_manager = ui_manager # Inject ui_manager instance (dependency injection) + controller = Controller(game_manager, ui_manager) # Inject game_manager and ui_manager instance + + ui_manager.create_visual_grid() # Create display grid + + # Bind events + input_box = document.getElementById("text-input") + input_proxy = create_proxy(lambda evt: controller.handle_input(evt, input_box)) + input_box.addEventListener("keydown", input_proxy) + + # Bind save button + run_btn = document.getElementById("runCodeButton") + run_proxy = create_proxy(lambda *_: run_python_code(game_manager)) + run_btn.addEventListener("click", run_proxy) + + # Bind save button + save_btn = document.getElementById("save-btn") + save_btn2 = document.getElementById("save-btn2") + save_proxy = create_proxy(lambda *_: ui_manager.save_grid_code_to_file()) + save_btn.addEventListener("click", save_proxy) + save_btn2.addEventListener("click", save_proxy) + + # MENU BUTTONS + # Bind new-file button + new_file = document.getElementById("new-file") + new_file_proxy = create_proxy(lambda *_: ui_manager.clear_grid()) + new_file.addEventListener("click", new_file_proxy) + + # Bind undo button + undo = document.getElementById("undo") + undo_proxy = create_proxy(lambda *_: game_manager.undo()) + undo.addEventListener("click", undo_proxy) + + # Bind redo button + redo = document.getElementById("redo") + redo_proxy = create_proxy(lambda *_: game_manager.redo()) + redo.addEventListener("click", redo_proxy) + + # Bind continue modal button + continue_btn = document.getElementById("continue-btn") + if continue_btn: + + def on_continue(*_args: Tuple) -> None: # noqa: UP006 + continue_modal("modal-bg") # hide modal + + continue_proxy = create_proxy(on_continue) + continue_btn.addEventListener("click", continue_proxy) + + # Bind keyboard event inside the game manager + handle_key_proxy = create_proxy(lambda evt: controller.handle_key(evt)) + window.addEventListener("keydown", handle_key_proxy) + + tick_proxy = create_proxy(lambda *_: (game_manager.tick(), ui_manager.render())) + setInterval(tick_proxy, 500) + + ui_manager.render() + + # Hide loading screen once game is ready + loading_screen = document.getElementById("loading-screen") + if loading_screen: + loading_screen.classList.add("hidden") + + return ui_manager + + +main() diff --git a/grand-gardenias/frontend/modes/code_editor/src/__init__.py b/grand-gardenias/frontend/modes/code_editor/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/code_editor/src/controls.py b/grand-gardenias/frontend/modes/code_editor/src/controls.py new file mode 100644 index 00000000..428c0a39 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/controls.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from js import HTMLInputElement, KeyboardEvent + +from engine.constants import MAX_BLOCK_LENGTH +from engine.controls import BaseController + + +class Controller(BaseController): + """Handles user input and mediates interactions between the game state and the UI. + + Attributes: + game_manager (GameManager): The game state manager instance. + ui_manager (UIManager): The ui_manager responsible for updating the visual grid. + + """ + + def handle_input(self, evt: KeyboardEvent, input_box: HTMLInputElement) -> None: + """Spawn a new block when Enter is pressed.""" + # Only allow new block if none is falling + if self.game_manager.current_block and self.game_manager.current_block.falling: + return + try: + if evt.key == "Enter": + text = input_box.value.strip() + if 1 <= len(text) <= MAX_BLOCK_LENGTH: + self.game_manager.spawn_block(text) + input_box.value = "" + input_box.blur() + self.ui_manager.render() + except AttributeError: + pass diff --git a/grand-gardenias/frontend/modes/code_editor/src/execute_code.py b/grand-gardenias/frontend/modes/code_editor/src/execute_code.py new file mode 100644 index 00000000..06baa260 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/execute_code.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from engine.game import BaseGameManager + +import io +import sys + +from js import document + + +def run_python_code(game_manager: BaseGameManager) -> str: + """Run the python code in the grid.""" + code = game_manager.format_grid_as_text() + buffer = io.StringIO() + sys_stdout = sys.stdout # Save the current stdout + try: + sys.stdout = buffer # Redirect stdout to buffer + exec(code) # noqa: S102 + output = buffer.getvalue() + except Exception as e: # noqa: BLE001 + # We don't know what exception may arise so this is a catch all solution + output = f"Error: {e}" + finally: + sys.stdout = sys_stdout # Restore original stdout + document.getElementById("CodeOutput").textContent = output diff --git a/grand-gardenias/frontend/modes/code_editor/src/game.py b/grand-gardenias/frontend/modes/code_editor/src/game.py new file mode 100644 index 00000000..9a0e3993 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/game.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import copy + +from engine.game import BaseGameManager + +Grid = list[list[str | None]] + + +class GameManager(BaseGameManager): + """Game logic manager.""" + + def __init__(self, *args: int, **kwargs: int) -> None: + super().__init__(*args, **kwargs) + self.undo_stack: list[Grid] = [] + self.redo_stack: list[Grid] = [] + + def lock_current_block(self) -> None: + """Lock current block into grid and add last state to undo history.""" + self.undo_stack.append(copy.deepcopy(self.grid)) + + super().lock_current_block() + + def undo(self) -> None: + """Undo block placement in the grid.""" + if not self.undo_stack: + return + self.redo_stack.append(copy.deepcopy(self.grid)) + self.grid = self.undo_stack.pop() + self.ui_manager.render() + + def redo(self) -> None: + """Redo block placement in the grid.""" + if not self.redo_stack: + return + self.undo_stack.append(copy.deepcopy(self.grid)) + self.grid = self.redo_stack.pop() + self.ui_manager.render() + + +game_manager = GameManager(40, 20) diff --git a/grand-gardenias/frontend/modes/code_editor/src/modal.py b/grand-gardenias/frontend/modes/code_editor/src/modal.py new file mode 100644 index 00000000..441f891d --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/modal.py @@ -0,0 +1,16 @@ +from js import document + + +def close_modal(id: str) -> None: + """Close a modal dialog by its DOM element ID.""" + modal_bg = document.getElementById(id) + if modal_bg: + modal_bg.remove() + + +def continue_modal(id: str) -> None: + """Close a modal dialog by its DOM element ID.""" + close_modal(id) + # Focus the input field + input_box = document.getElementById("text-input") + input_box.focus() diff --git a/grand-gardenias/frontend/modes/code_editor/src/timer.py b/grand-gardenias/frontend/modes/code_editor/src/timer.py new file mode 100644 index 00000000..10036e32 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/timer.py @@ -0,0 +1,32 @@ +from constants import TIMER_MINUTES +from js import clearInterval, document, setInterval +from pyodide.ffi import create_proxy + +time_left = TIMER_MINUTES * 60 +interval_id = None + + +def run_timer() -> None: + """Run the timer.""" + global time_left, interval_id # noqa: PLW0603 + timer_element = document.getElementById("timer") + if not timer_element: + return + + minutes = time_left // 60 + seconds = time_left % 60 + timer_element.textContent = f"{minutes:02d}:{seconds:02d}" + + time_left -= 1 + if time_left < 0: + clearInterval(interval_id) + interval_id = None + + +def start_timer() -> None: + """Start the timer.""" + global interval_id # noqa: PLW0603 + if interval_id is None: + run_timer() + timer_proxy = create_proxy(run_timer) + interval_id = setInterval(timer_proxy, 1000) diff --git a/grand-gardenias/frontend/modes/code_editor/src/ui_manager.py b/grand-gardenias/frontend/modes/code_editor/src/ui_manager.py new file mode 100644 index 00000000..8e5ab187 --- /dev/null +++ b/grand-gardenias/frontend/modes/code_editor/src/ui_manager.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from engine.ui_manager import BaseUIManager +from js import document +from shared.audio_utils import play_place_sound + + +class UIManager(BaseUIManager): + """Handles rendering and UI interactions for the game.""" + + def lock_visual_cells(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + """Lock the cells occupied by the current block visually.""" + super().lock_visual_cells(*args, **kwargs) + + # Play place sound + play_place_sound() + + # Focus the input field + input_box = document.getElementById("text-input") + input_box.focus() diff --git a/grand-gardenias/frontend/modes/coderush/__init__.py b/grand-gardenias/frontend/modes/coderush/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/coderush/config.json b/grand-gardenias/frontend/modes/coderush/config.json new file mode 100644 index 00000000..dfc0d42e --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/config.json @@ -0,0 +1,24 @@ +{ + "files": { + "../../engine/game.py": "engine/game.py", + "../../engine/patterns.py": "engine/patterns.py", + "../../engine/controls.py": "engine/controls.py", + "../../engine/ui_manager.py": "engine/ui_manager.py", + "../../engine/constants.py": "engine/constants.py", + "../../engine/objects/block.py": "engine/objects/block.py", + "../../shared/audio_utils.py": "shared/audio_utils.py", + + + + "./src/controls.py": "", + "./src/ui_manager.py": "", + "./src/modal.py": "", + + "./src/problem.py": "", + "./src/problem_helper.py": "", + "./src/timer.py":"", + + "./src/problems.json":"" + + } +} diff --git a/grand-gardenias/frontend/modes/coderush/index.html b/grand-gardenias/frontend/modes/coderush/index.html new file mode 100644 index 00000000..a98c734e --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/index.html @@ -0,0 +1,292 @@ + + + + + + + Code Rush + + + + + + + + + + + + + + + + + +
+
+

Tetris Bugs

+
+ +

Loading...

+
+
+ + + + + + + + + +
+ +
+ +
+

+

+
+ +
+ +

+ Current Score: 0
+ High Score: 0 +

+
+ +
+
+ + +
+
+
+
+ + +
+ +
+ + + + +

+

+
+ + + + +
+
+ + +
+ + + +
+ +
+
+ + + + + + + + + + + + + + + + diff --git a/grand-gardenias/frontend/modes/coderush/main.py b/grand-gardenias/frontend/modes/coderush/main.py new file mode 100644 index 00000000..c82cdaaf --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/main.py @@ -0,0 +1,72 @@ +import timer +from controls import Controller +from engine.game import BaseGameManager +from js import document, setInterval, window +from modal import continue_modal +from pyodide.ffi import create_proxy +from ui_manager import UIManager + +game_manager = BaseGameManager(40, 25) + + +def main() -> None: + """Initialize the game.""" + ui_manager = UIManager(game_manager) + game_manager.ui_manager = ui_manager # Inject ui_manager instance (dependency injection) + controller = Controller(game_manager, ui_manager) # Inject game_manager and ui_manager instance + + ui_manager.create_visual_grid() # Create display grid + + # Set timer callback for game over + timer.on_time_up = ui_manager.show_game_over + + # Bind text-input + input_box = document.getElementById("text-input") + input_proxy = create_proxy(lambda evt: controller.handle_input(evt, input_box)) + input_box.addEventListener("keydown", input_proxy) + + # Bind save button + save_btn = document.getElementById("save-btn") + save_proxy = create_proxy(lambda *_: ui_manager.save_grid_code_to_file()) + save_btn.addEventListener("click", save_proxy) + + # Bind run button + run_btn = document.getElementById("run-btn") + run_proxy = create_proxy(lambda *_: ui_manager.problem_switch()) + run_btn.addEventListener("click", run_proxy) + + # Bind retry button + retry_btn = document.getElementById("retry-btn") + retry_proxy = create_proxy(lambda *_: ui_manager.clear_grid()) + retry_btn.addEventListener("click", retry_proxy) + + # Bind restart button + restart_btn = document.getElementById("restart-btn") + restart_proxy = create_proxy(lambda *_: ui_manager.restart_game()) + restart_btn.addEventListener("click", restart_proxy) + + # Bind continue modal button and start timer + + continue_btn = document.getElementById("continue-btn") + continue_proxy = create_proxy(lambda _evt: continue_modal("modal-bg")) + continue_btn.addEventListener("click", continue_proxy) + + # Bind keyboard event inside the game manager + handle_key_proxy = create_proxy(lambda evt: controller.handle_key(evt)) + window.addEventListener("keydown", handle_key_proxy) + + tick_proxy = create_proxy(lambda *_: (game_manager.tick(), ui_manager.render())) + setInterval(tick_proxy, 500) + + # Kick start + ui_manager.render() + ui_manager.show_problem() + ui_manager.update_score_display() + + # Hide loading screen once game is ready + loading_screen = document.getElementById("loading-screen") + if loading_screen: + loading_screen.classList.add("hidden") + + +main() diff --git a/grand-gardenias/frontend/modes/coderush/src/__init__.py b/grand-gardenias/frontend/modes/coderush/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/coderush/src/controls.py b/grand-gardenias/frontend/modes/coderush/src/controls.py new file mode 100644 index 00000000..428c0a39 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/controls.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from js import HTMLInputElement, KeyboardEvent + +from engine.constants import MAX_BLOCK_LENGTH +from engine.controls import BaseController + + +class Controller(BaseController): + """Handles user input and mediates interactions between the game state and the UI. + + Attributes: + game_manager (GameManager): The game state manager instance. + ui_manager (UIManager): The ui_manager responsible for updating the visual grid. + + """ + + def handle_input(self, evt: KeyboardEvent, input_box: HTMLInputElement) -> None: + """Spawn a new block when Enter is pressed.""" + # Only allow new block if none is falling + if self.game_manager.current_block and self.game_manager.current_block.falling: + return + try: + if evt.key == "Enter": + text = input_box.value.strip() + if 1 <= len(text) <= MAX_BLOCK_LENGTH: + self.game_manager.spawn_block(text) + input_box.value = "" + input_box.blur() + self.ui_manager.render() + except AttributeError: + pass diff --git a/grand-gardenias/frontend/modes/coderush/src/modal.py b/grand-gardenias/frontend/modes/coderush/src/modal.py new file mode 100644 index 00000000..92a621f0 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/modal.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from js import document +from timer import start_timer + + +def close_modal(id: str) -> None: + """Close a modal dialog by its DOM element ID.""" + modal_bg = document.getElementById(id) + if modal_bg: + modal_bg.remove() + + +def continue_modal(id: str) -> None: + """Close a modal and spawn the first block.""" + close_modal(id) + + # Focus the input field + input_box = document.getElementById("text-input") + input_box.focus() + start_timer() # start the timer diff --git a/grand-gardenias/frontend/modes/coderush/src/problem.py b/grand-gardenias/frontend/modes/coderush/src/problem.py new file mode 100644 index 00000000..d881f53c --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/problem.py @@ -0,0 +1,33 @@ +from engine.patterns import SingletonMeta +from js import document +from problem_helper import get_ques + + +class Problem(metaclass=SingletonMeta): # noqa: D101 + def __init__(self) -> None: + self.problem_id = 1 + self.problems_solved = 0 + self.problem_title = get_ques(self.problem_id)["title"] + self.problem_desc = get_ques(self.problem_id)["description"] + self.title_elem = document.getElementById("problem-title") + self.desc_elem = document.getElementById("problem-desc") + + def render(self) -> None: + """Render question.""" + self.title_elem.innerText = self.problem_title + self.desc_elem.innerText = self.problem_desc + + def switch_problem(self) -> None: + """Switch_problem.""" + self.problems_solved += 1 + if get_ques(self.problem_id) != {}: + self.problem_title = get_ques(self.problem_id)["title"] + self.problem_desc = get_ques(self.problem_id)["description"] + self.render() + else: + self.problem_title = "Winner!" + self.problem_desc = "You beat the game!" + self.render() + + +problem_manager = Problem() diff --git a/grand-gardenias/frontend/modes/coderush/src/problem_helper.py b/grand-gardenias/frontend/modes/coderush/src/problem_helper.py new file mode 100644 index 00000000..13a8e3c1 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/problem_helper.py @@ -0,0 +1,48 @@ +import json + + +def check_code(code: str, id: int) -> str: # noqa: C901 + """Check code and test case.""" + if not code: + return "Write some code, dumbo." + ques = get_ques(id) + namespace = {} + try: + exec(code, namespace) # noqa: S102 + try: + func = namespace[ques["func_name"]] + validity_list = [] + + for i in ques["test_cases"]: + if isinstance(i["input"], list) and len(i["input"]) == 2: # noqa: PLR2004 + if func(*tuple(i["input"])) == i["expected_output"]: + validity_list.append((True, i["input"])) + else: + validity_list.append((False, i["input"])) + elif str(func(i["input"])) == str(i["expected_output"]): + validity_list.append((True, i["input"])) + else: + validity_list.append((False, i["input"])) + failed_case = [] + for i, test_case in validity_list: + if not i: + failed_case.append((i, test_case)) + if failed_case: + return f"Incorrect: {failed_case} {len(failed_case)} test case(s) failed" + return "correct" # noqa: TRY300 + + except (KeyError, TypeError): + return "incorrect function" + + except (NameError, IndentationError, SyntaxError): + return "wrong code" + + +# Get questions from json file +def get_ques(id: int) -> dict: + """Get ques from json file.""" + try: + with open("problems.json") as f: # noqa: PTH123 + return json.loads(f.read())[id - 1] + except IndexError: + return {} diff --git a/grand-gardenias/frontend/modes/coderush/src/problems.json b/grand-gardenias/frontend/modes/coderush/src/problems.json new file mode 100644 index 00000000..24f30538 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/problems.json @@ -0,0 +1,606 @@ +[ + { + "problem_id": 1, + "title": "Add Two Numbers", + "func_name":"add_numbers", + "description": "Write a function named 'add_numbers' that takes two numbers as input and returns their sum.", + "solution_code": "def add_numbers(a, b):\n return a + b", + "test_cases": [ + { + "input": [5, 3], + "expected_output": 8 + }, + { + "input": [0, 0], + "expected_output": 0 + }, + { + "input": [-10, 5], + "expected_output": -5 + } + ] + }, + { + "problem_id": 2, + "title": "Reverse a String", + "func_name":"reverse_string", + "description": "Write a function named 'reverse_string' that takes a string as input and returns the string reversed.", + "solution_code": "def reverse_string(s):\n return s[::-1]", + "test_cases": [ + { + "input": "hello", + "expected_output": "olleh" + }, + { + "input": "Python", + "expected_output": "nohtyP" + }, + { + "input": "a", + "expected_output": "a" + } + ] + }, + { + "problem_id": 3, + "title": "Check if a Number is Even", + "func_name":"is_even", + "description": "Write a function named 'is_even' that takes an integer and returns True if it's even, otherwise False.", + "solution_code": "def is_even(n):\n return n % 2 == 0", + "test_cases": [ + { + "input": 4, + "expected_output": "True" + }, + { + "input": 7, + "expected_output": "False" + }, + { + "input": 0, + "expected_output": "True" + } + ] + }, + { + "problem_id": 4, + "title": "Find the Maximum Number in a List", + "func_name":"find_max", + "description": "Write a function named 'find_max' that takes a list of numbers and returns the largest number in the list.", + "solution_code": "def find_max(numbers):\n return max(numbers)", + "test_cases": [ + { + "input": [1, 5, 2, 8, 3], + "expected_output": 8 + }, + { + "input": [-10, -2, -5], + "expected_output": -2 + }, + { + "input": [100], + "expected_output": 100 + } + ] + }, + { + "problem_id": 5, + "title": "Calculate Factorial", + "func_name":"factorial", + "description": "Write a function named 'factorial' to calculate the factorial of a non-negative integer.", + "solution_code": "def factorial(n):\n if n == 0:\n return 1\n else:\n return n * factorial(n - 1)", + "test_cases": [ + { + "input": 5, + "expected_output": 120 + }, + { + "input": 0, + "expected_output": 1 + }, + { + "input": 3, + "expected_output": 6 + } + ] + }, + { + "problem_id": 6, + "title": "Count Vowels in a String", + "func_name":"count_vowels", + "description": "Write a function named 'count_vowels' that takes a string and returns the number of vowels (a, e, i, o, u) in it.", + "solution_code": "def count_vowels(s):\n vowels = \"aeiou\"\n count = 0\n for char in s.lower():\n if char in vowels:\n count += 1\n return count", + "test_cases": [ + { + "input": "hello", + "expected_output": 2 + }, + { + "input": "AEIOU", + "expected_output": 5 + }, + { + "input": "rhythm", + "expected_output": 0 + } + ] + }, + { + "problem_id": 7, + "title": "Check for Palindrome", + "func_name":"is_palindrome", + "description": "Write a function named 'is_palindrome' that checks if a string is a palindrome (reads the same forwards and backwards).", + "solution_code": "def is_palindrome(s):\n return s.lower() == s.lower()[::-1]", + "test_cases": [ + { + "input": "racecar", + "expected_output": "True" + }, + { + "input": "hello", + "expected_output": "False" + }, + { + "input": "Madam", + "expected_output": "True" + } + ] + }, + { + "problem_id": 8, + "title": "Find the Smallest Number in a List", + "func_name":"find_min", + "description": "Write a function named 'find_min' that takes a list of numbers and returns the smallest number.", + "solution_code": "def find_min(numbers):\n return min(numbers)", + "test_cases": [ + { + "input": [10, 5, 2, 8, 3], + "expected_output": 2 + }, + { + "input": [-10, -2, -5], + "expected_output": -10 + }, + { + "input": [100], + "expected_output": 100 + } + ] + }, + { + "problem_id": 9, + "title": "Sum all Numbers in a List", + "func_name":"sum_list", + "description": "Write a function named 'sum_list' that takes a list of numbers and returns their sum.", + "solution_code": "def sum_list(numbers):\n return sum(numbers)", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": 10 + }, + { + "input": [0, 0, 0], + "expected_output": 0 + }, + { + "input": [10, -5, 2], + "expected_output": 7 + } + ] + }, + { + "problem_id": 10, + "title": "Remove Duplicates from a List", + "func_name":"remove_duplicates", + "description": "Write a function named 'remove_duplicates' that takes a list and returns a new list with all duplicates removed.", + "solution_code": "def remove_duplicates(l):\n return list(set(l))", + "test_cases": [ + { + "input": [1, 2, 2, 3, 4, 4, 5], + "expected_output": [1, 2, 3, 4, 5] + }, + { + "input": ["a", "b", "c", "a"], + "expected_output": ["a", "b", "c"] + }, + { + "input": [1, 1, 1], + "expected_output": [1] + } + ] + }, + { + "problem_id": 11, + "title": "Calculate Average", + "func_name":"calculate_average", + "description": "Write a function named 'calculate_average' that takes a list of numbers and returns their average.", + "solution_code": "def calculate_average(numbers):\n if not numbers:\n return 0\n return sum(numbers) / len(numbers)", + "test_cases": [ + { + "input": [1, 2, 3, 4, 5], + "expected_output": 3.0 + }, + { + "input": [10, 20, 30], + "expected_output": 20.0 + }, + { + "input": [], + "expected_output": 0 + } + ] + }, + { + "problem_id": 12, + "title": "Find the Longest Word", + "func_name":"find_longest_word", + "description": "Write a function named 'find_longest_word' that takes a list of words and returns the longest word.", + "solution_code": "def find_longest_word(words):\n return max(words, key=len)", + "test_cases": [ + { + "input": ["apple", "banana", "kiwi"], + "expected_output": "banana" + }, + { + "input": ["a", "bb", "ccc"], + "expected_output": "ccc" + }, + { + "input": ["one"], + "expected_output": "one" + } + ] + }, + { + "problem_id": 13, + "title": "Is String a Substring?", + "func_name":"is_substring", + "description": "Write a function named 'is_substring' that checks if a string is a substring of another string.", + "solution_code": "def is_substring(sub, main):\n return sub in main", + "test_cases": [ + { + "input": ["world", "hello world"], + "expected_output": "True" + }, + { + "input": ["moon", "sun"], + "expected_output": "False" + }, + { + "input": ["", "any string"], + "expected_output": "True" + } + ] + }, + { + "problem_id": 14, + "title": "Calculate Circle Area", + "func_name":"circle_area", + + "description": "Write a function named 'circle_area' that takes the radius of a circle and returns its area.", + "solution_code": "import math\ndef circle_area(r):\n return math.pi * r**2", + "test_cases": [ + { + "input": 1, + "expected_output": 3.141592653589793 + }, + { + "input": 0, + "expected_output": 0.0 + }, + { + "input": 2.5, + "expected_output": 19.634954084936208 + } + ] + }, + { + "problem_id": 15, + "title": "Convert Celsius to Fahrenheit", + "func_name":"celsius_to_farenheit", + "description": "Write a function named 'celsius_to_fahrenheit' that converts a temperature from Celsius to Fahrenheit.", + "solution_code": "def celsius_to_fahrenheit(c):\n return (c * 9/5) + 32", + "test_cases": [ + { + "input": 0, + "expected_output": 32.0 + }, + { + "input": 100, + "expected_output": 212.0 + }, + { + "input": -40, + "expected_output": -40.0 + } + ] + }, + { + "problem_id": 16, + "title": "Count Words in a String", + "func_name":"count_words", + "description": "Write a function named 'count_words' that counts the number of words in a given string.", + "solution_code": "def count_words(s):\n return len(s.split())", + "test_cases": [ + { + "input": "Hello world", + "expected_output": 2 + }, + { + "input": "This is a test sentence.", + "expected_output": 5 + }, + { + "input": "oneword", + "expected_output": 1 + } + ] + }, + { + "problem_id": 17, + "title": "Find First Uppercase Letter", + "func_name":"find_first_uppercase", + "description": "Write a function named 'find_first_uppercase' that finds the first uppercase letter in a string.", + "solution_code": "def find_first_uppercase(s):\n for char in s:\n if 'A' <= char <= 'Z':\n return char\n return None", + "test_cases": [ + { + "input": "helloWorld", + "expected_output": "W" + }, + { + "input": "alllowercase", + "expected_output": null + }, + { + "input": "UPPERCASE", + "expected_output": "U" + } + ] + }, + { + "problem_id": 18, + "title": "Generate a Fibonacci Sequence", + "func_name":"fibonacci_sequence", + + "description": "Write a function named 'fibonacci_sequence' that generates a Fibonacci sequence up to a given number of terms.", + "solution_code": "def fibonacci_sequence(n):\n sequence = []\n a, b = 0, 1\n while len(sequence) < n:\n sequence.append(a)\n a, b = b, a + b\n return sequence", + "test_cases": [ + { + "input": 5, + "expected_output": [0, 1, 1, 2, 3] + }, + { + "input": 1, + "expected_output": [0] + }, + { + "input": 0, + "expected_output": [] + } + ] + }, + { + "problem_id": 19, + "title": "Check if a Number is Prime", + "func_name":"is_prime", + "description": "Write a function named 'is_prime' that checks if a number is a prime number.", + "solution_code": "def is_prime(n):\n if n <= 1:\n return False\n for i in range(2, int(n**0.5) + 1):\n if n % i == 0:\n return False\n return True", + "test_cases": [ + { + "input": 7, + "expected_output": "True" + }, + { + "input": 10, + "expected_output": "False" + }, + { + "input": 1, + "expected_output": "False" + } + ] + }, + { + "problem_id": 20, + "title": "FizzBuzz", + "func_name":"fizzbuzz", + "description": "Write a function named 'fizzbuzz' that prints numbers from 1 to 15. For multiples of 3 print 'Fizz', for multiples of 5 print 'Buzz', and for multiples of both, print 'FizzBuzz'.", + "solution_code": "def fizzbuzz():\n results = []\n for i in range(1, 16):\n if i % 3 == 0 and i % 5 == 0:\n results.append('FizzBuzz')\n elif i % 3 == 0:\n results.append('Fizz')\n elif i % 5 == 0:\n results.append('Buzz')\n else:\n results.append(str(i))\n return results", + "test_cases": [ + { + "input": null, + "expected_output": ["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz"] + } + ] + }, + { + "problem_id": 21, + "title": "Merge Two Lists", + "func_name":"merge_lists", + "description": "Write a function named 'merge_lists' that takes two lists and returns a new list containing all elements from both.", + "solution_code": "def merge_lists(list1, list2):\n return list1 + list2", + "test_cases": [ + { + "input": [[1, 2], [3, 4]], + "expected_output": [1, 2, 3, 4] + }, + { + "input": [["a"], ["b", "c"]], + "expected_output": ["a", "b", "c"] + }, + { + "input": [[], [1, 2, 3]], + "expected_output": [1, 2, 3] + } + ] + }, + { + "problem_id": 22, + "title": "Check if List is Sorted", + "func_name":"is_sorted", + "description": "Write a function named 'is_sorted' that returns True if a list is sorted in ascending order, otherwise False.", + "solution_code": "def is_sorted(l):\n return l == sorted(l)", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": "True" + }, + { + "input": [4, 3, 2, 1], + "expected_output": "False" + }, + { + "input": [1, 3, 2], + "expected_output": "False" + } + ] + }, + { + "problem_id": 23, + "title": "Reverse a List", + "func_name":"reverse_list", + "description": "Write a function named 'reverse_list' that reverses the order of elements in a list.", + "solution_code": "def reverse_list(l):\n return l[::-1]", + "test_cases": [ + { + "input": [1, 2, 3], + "expected_output": [3, 2, 1] + }, + { + "input": ["a", "b", "c"], + "expected_output": ["c", "b", "a"] + }, + { + "input": [10], + "expected_output": [10] + } + ] + }, + { + "problem_id": 24, + "title": "Sum of Digits", + "func_name":"sum_digits", + "description": "Write a function named 'sum_digits' that calculates the sum of the digits of an integer.", + "solution_code": "def sum_digits(n):\n return sum(int(digit) for digit in str(n))", + "test_cases": [ + { + "input": 123, + "expected_output": 6 + }, + { + "input": 999, + "expected_output": 27 + }, + { + "input": 0, + "expected_output": 0 + } + ] + }, + + { + "problem_id": 25, + "func_name":"is_digit_string", + "title": "Check if a String Contains Only Digits", + "description": "Write a function named 'is_digit_string' that returns True if a string contains only digits, otherwise False.", + "solution_code": "def is_digit_string(s):\n return s.isdigit()", + "test_cases": [ + { + "input": "12345", + "expected_output": "True" + }, + { + "input": "123a45", + "expected_output": "False" + }, + { + "input": "", + "expected_output": "False" + } + ] + }, + { + "problem_id": 26, + "func_name":"swap_first_last", + "title": "Swap First and Last Elements of a List", + "description": "Write a function named 'swap_first_last' that swaps the first and last elements of a list and returns the modified list.", + "solution_code": "def swap_first_last(l):\n if len(l) > 1:\n l[0], l[-1] = l[-1], l[0]\n return l", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": [4, 2, 3, 1] + }, + { + "input": ["a", "b", "c"], + "expected_output": ["c", "b", "a"] + }, + { + "input": [10], + "expected_output": [10] + } + ] + }, + { + "problem_id": 27, + "title": "Convert a String to a List of Characters", + "func_name":"string_to_list", + "description": "Write a function named 'string_to_list' that takes a string and converts it into a list of its characters.", + "solution_code": "def string_to_list(s):\n return list(s)", + "test_cases": [ + { + "input": "hello", + "expected_output": ["h", "e", "l", "l", "o"] + }, + { + "input": "Python", + "expected_output": ["P", "y", "t", "h", "o", "n"] + }, + { + "input": "", + "expected_output": [] + } + ] + }, + { + "problem_id": 28, + "func_name":"all_are_unique", + "title": "Check if all Elements in a List are Unique", + "description": "Write a function named 'are_all_unique' that returns True if all elements in a list are unique, otherwise False.", + "solution_code": "def are_all_unique(l):\n return len(l) == len(set(l))", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": "True" + }, + { + "input": [1, 2, 2, 3], + "expected_output": "False" + }, + { + "input": [], + "expected_output": "True" + } + ] + }, + { + "problem_id": 29, + "title": "Find the Intersection of Two Lists", + "func_name":"list_intersection", + "description": "Write a function named 'list_intersection' that takes two lists and returns a new list containing only the elements that are common to both.", + "solution_code": "def list_intersection(list1, list2):\n return list(set(list1) & set(list2))", + "test_cases": [ + { + "input": [[1, 2, 3], [3, 4, 5]], + "expected_output": [3] + }, + { + "input": [[1, 2], [3, 4]], + "expected_output": [] + }, + { + "input": [["a", "b"], ["b", "c"]], + "expected_output": ["b"] + } + ] + } + ] diff --git a/grand-gardenias/frontend/modes/coderush/src/timer.py b/grand-gardenias/frontend/modes/coderush/src/timer.py new file mode 100644 index 00000000..f7303002 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/timer.py @@ -0,0 +1,46 @@ +from engine.constants import TIMER_MINUTES +from js import clearInterval, document, setInterval +from pyodide.ffi import create_proxy + +time_left = TIMER_MINUTES * 60 +interval_id = None + +# Callback to be called when timer runs out +on_time_up = None + + +def run_timer() -> None: + """Run the timer.""" + global time_left, interval_id # noqa: PLW0603 + timer_element = document.getElementById("timer") + if not timer_element: + return + + minutes = time_left // 60 + seconds = time_left % 60 + timer_element.textContent = f"{minutes:02d}:{seconds:02d}" + + time_left -= 1 + if time_left < 0: + clearInterval(interval_id) + interval_id = None + if on_time_up is not None: + on_time_up() + + +def start_timer() -> None: + """Start the timer.""" + global interval_id # noqa: PLW0603 + if interval_id is None: + run_timer() + timer_proxy = create_proxy(run_timer) + interval_id = setInterval(timer_proxy, 1000) + + +def reset_timer() -> None: + """Reset the timer to full duration.""" + global time_left, interval_id # noqa: PLW0603 + time_left = TIMER_MINUTES * 60 + if interval_id is not None: + clearInterval(interval_id) + interval_id = None diff --git a/grand-gardenias/frontend/modes/coderush/src/ui_manager.py b/grand-gardenias/frontend/modes/coderush/src/ui_manager.py new file mode 100644 index 00000000..21eeb2c9 --- /dev/null +++ b/grand-gardenias/frontend/modes/coderush/src/ui_manager.py @@ -0,0 +1,150 @@ +from __future__ import annotations + +from engine.ui_manager import BaseUIManager +from js import document, localStorage, window +from problem import problem_manager +from problem_helper import check_code, get_ques +from shared.audio_utils import play_place_sound, win_sound + + +class UIManager(BaseUIManager): + """Handles rendering and UI interactions for the game.""" + + def clear_grid(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + """Clear the grid by resetting all cells to None.""" + super().clear_grid(*args, **kwargs) + + # Clear the code output + output = document.getElementById("code-output") + if output: + output.innerText = "" + # Focus the input field + input_box = document.getElementById("text-input") + if input_box: + input_box.focus() + self.update_score_display() + + def lock_visual_cells(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + """Lock the cells occupied by the current block visually.""" + super().lock_visual_cells(*args, **kwargs) + + # Play place sound + play_place_sound() + + # Focus the input field + input_box = document.getElementById("text-input") + input_box.focus() + + # Mode-specific methods + + def show_problem(self) -> None: + """Show and update problem.""" + problem_manager.render() + + def update_score_display(self) -> None: + """Update both current score and high score displays above the grid.""" + # Update current score + current_score = problem_manager.problems_solved + current_score_elem = document.getElementById("current-score") + if current_score_elem: + current_score_elem.innerText = str(current_score) + + # Update high score + current_high_score = self.get_current_high_score() + high_score_elem = document.getElementById("current-high-score") + if high_score_elem: + high_score_elem.innerText = str(current_high_score) + + def update_high_score_display(self) -> None: + """Update the high score display above the grid.""" + current_high_score = self.get_current_high_score() + high_score_elem = document.getElementById("current-high-score") + if high_score_elem: + high_score_elem.innerText = str(current_high_score) + + def get_current_high_score(self) -> int: + """Get the current high score from localStorage.""" + current_high_score = localStorage.getItem("tetris_high_score") + if current_high_score is None or str(current_high_score).lower() in ["null", "jsnull", "undefined"]: + return 0 + try: + return int(str(current_high_score)) + except ValueError: + return 0 + + def problem_switch(self) -> None: + """Move to next problem.""" + output = document.getElementById("code-output") + code = self.game.format_grid_as_text() + if check_code(code, problem_manager.problem_id) == "correct": + problem_manager.problem_id += 1 + problem_manager.switch_problem() + current_high_score = self.get_current_high_score() + current_score = problem_manager.problems_solved + if current_score > current_high_score: + localStorage.setItem("tetris_high_score", str(current_score)) + self.update_score_display() + self.clear_grid() + win_sound() + output.innerText = "Correct!" + elif check_code(code, problem_manager.problem_id) == "wrong code": + # Play wrong answer sound + if hasattr(window, "playWrongAnswerSound"): + window.playWrongAnswerSound() + output.innerText = "Incorrect Code, try again" + + elif check_code(code, problem_manager.problem_id) == "incorrect function": + # Play wrong answer sound + if hasattr(window, "playWrongAnswerSound"): + window.playWrongAnswerSound() + output.innerText = "Check your function name" + + else: + # Play wrong answer sound for any other incorrect result + if hasattr(window, "playWrongAnswerSound"): + window.playWrongAnswerSound() + output.innerText = check_code(code, problem_manager.problem_id) + + def show_game_over(self) -> None: + """Show game over screen with high score.""" + # Get current high score from localStorage + current_high_score = self.get_current_high_score() + # Check if current score is higher + current_score = problem_manager.problems_solved + if current_score > current_high_score: + localStorage.setItem("tetris_high_score", str(current_score)) + current_high_score = current_score + # Update the game over modal + problems_solved_elem = document.getElementById("problems-solved") + high_score_elem = document.getElementById("high-score") + if problems_solved_elem: + problems_solved_elem.innerText = str(current_score) + if high_score_elem: + high_score_elem.innerText = str(current_high_score) + # Show the game over modal + game_over_modal = document.getElementById("game-over-modal") + if game_over_modal: + game_over_modal.classList.remove("hidden") + # Update the score display + self.update_score_display() + # Pause the game + self.game.paused = True + + def restart_game(self) -> None: + """Restart the game.""" + problem_manager.problem_id = 1 + problem_manager.problems_solved = 0 + problem_manager.problem_title = get_ques(problem_manager.problem_id)["title"] + problem_manager.problem_desc = get_ques(problem_manager.problem_id)["description"] + problem_manager.render() + self.update_score_display() + self.clear_grid() + game_over_modal = document.getElementById("game-over-modal") + if game_over_modal: + game_over_modal.classList.add("hidden") + self.game.paused = False + from timer import reset_timer, start_timer # noqa: PLC0415 + + reset_timer() + start_timer() + self.update_score_display() diff --git a/grand-gardenias/frontend/modes/roguelike/__init__.py b/grand-gardenias/frontend/modes/roguelike/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/roguelike/config.json b/grand-gardenias/frontend/modes/roguelike/config.json new file mode 100644 index 00000000..bf29072f --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/config.json @@ -0,0 +1,20 @@ +{ + "files": { + "../../engine/game.py": "engine/game.py", + "../../engine/patterns.py": "engine/patterns.py", + "../../engine/controls.py": "engine/controls.py", + "../../engine/ui_manager.py": "engine/ui_manager.py", + "../../engine/constants.py": "engine/constants.py", + "../../engine/objects/block.py": "engine/objects/block.py", + + "./src/game.py": "", + "./src/modal.py": "", + + "./src/block_generator.py": "", + "./src/question_manager.py": "", + "./src/game_state.py": "", + "./src/controls.py": "", + "./src/problem_helper.py": "", + "./src/problems.json": "" + } +} diff --git a/grand-gardenias/frontend/modes/roguelike/index.html b/grand-gardenias/frontend/modes/roguelike/index.html new file mode 100644 index 00000000..3dff3e16 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/index.html @@ -0,0 +1,98 @@ + + + + + + + Roguelike + + + + + + + + + + + + + + + + + +
+
+

Tetris Bugs

+
+ +

Loading...

+
+
+ + + + + +
+
+ + +
+ + +
+ +

+ +
+ + +
+
+ +
+ +
+ + +
+ + + import sys + sys.modules.pop("main_v2", None) + + + + + + + + + + diff --git a/grand-gardenias/frontend/modes/roguelike/main.py b/grand-gardenias/frontend/modes/roguelike/main.py new file mode 100644 index 00000000..9e10662d --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/main.py @@ -0,0 +1,45 @@ +from block_generator import block_generator +from controls import Controller +from engine.ui_manager import BaseUIManager +from game import game_manager +from js import document, setInterval, window +from modal import continue_modal +from pyodide.ffi import create_proxy + + +def main() -> None: + """Initialize the game.""" + ui_manager = BaseUIManager(game_manager) + game_manager.ui_manager = ui_manager # Inject ui_manager instance (dependency injection) + controller = Controller(game_manager, ui_manager) # Inject game_manager and ui_manager instance + game_manager.block_gen = block_generator(ui_manager) + + ui_manager.create_visual_grid() # Create display grid + + # Bind continue modal button and start timer + continue_btn = document.getElementById("continue-btn") + + continue_proxy = create_proxy(lambda _evt: continue_modal("modal-bg", ui_manager)) + continue_btn.addEventListener("click", continue_proxy) + + # Bind the retry button to refresh the page + retry_button = document.getElementById("retry-btn") + retry_proxy = create_proxy(lambda _evt: window.location.reload()) + retry_button.addEventListener("click", retry_proxy) + + # Bind keyboard event inside the game manager + handle_key_proxy = create_proxy(lambda evt: controller.handle_key(evt)) + window.addEventListener("keydown", handle_key_proxy) + + tick_proxy = create_proxy(lambda *_: (game_manager.tick(), ui_manager.render())) + setInterval(tick_proxy, 500) + + ui_manager.render() + + # Hide loading screen once game is ready + loading_screen = document.getElementById("loading-screen") + if loading_screen: + loading_screen.classList.add("hidden") + + +main() diff --git a/grand-gardenias/frontend/modes/roguelike/src/__init__.py b/grand-gardenias/frontend/modes/roguelike/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/modes/roguelike/src/block_generator.py b/grand-gardenias/frontend/modes/roguelike/src/block_generator.py new file mode 100644 index 00000000..398b0119 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/block_generator.py @@ -0,0 +1,60 @@ +import random +from collections.abc import Generator + +from game import game_manager +from question_manager import question_manager + + +def split_into_blocks(s: str, block_size: int = 5) -> list[str]: + """Split a string into blocks of given size, last block may be shorter.""" + return [s[i : i + block_size] for i in range(0, len(s), block_size)] + + +def block_generator(renderer: str) -> Generator[str]: + """Yield blocks of a program string from bottom to top for gameplay. + + Each line of the program is split into mostly 5-character blocks, + and blocks are yielded in random order. After yielding a line's + blocks, the generator checks the corresponding player's line: + if it matches, the row is cleared from both the game state and renderer. + + Args: + renderer (str): The GridRenderer instance used to update the visual grid. + + Yields: + str: Individual blocks of code from the program string, in random order. + + """ + + def first_empty_row_from_bottom() -> int: + """Return the 1-based index of the first completely empty row from the bottom.""" + n = len(game_manager.grid) + for i in range(n - 1, -1, -1): + if all(not cell for cell in game_manager.grid[i]): + # Distance from bottom + return n - i + return 1 # fallback if no empty row + + question_details = question_manager.get_question() + lines = question_details["solution_code"].splitlines() + bottom_pointer = 1 + + for line in reversed(lines): + blocks = split_into_blocks(line.strip()) + + while len(blocks): + rand_j = random.randint(0, len(blocks) - 1) # noqa: S311 + yield blocks[rand_j] + blocks.pop(rand_j) + + player_line = game_manager.format_grid_line_as_text(-(bottom_pointer)) + + if player_line.strip() == line.strip(): + # Clear the row if correctly answered + game_manager.clear_row(-(bottom_pointer)) + renderer.clear_row(-(bottom_pointer)) + else: + # Incorrect answer + # Increment the pointer, as the current row will stay stuck + bottom_pointer = first_empty_row_from_bottom() + game_manager.bottom_pointer = bottom_pointer diff --git a/grand-gardenias/frontend/modes/roguelike/src/controls.py b/grand-gardenias/frontend/modes/roguelike/src/controls.py new file mode 100644 index 00000000..8f519b88 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/controls.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from engine.controls import BaseController +from js import KeyboardEvent, document + + +class Controller(BaseController): + """Handles user input and mediates interactions between the game state and the UI. + + Attributes: + game_manager (GameManager): The game state manager instance. + ui_manager (UIManager): The ui_manager responsible for updating the visual grid. + + """ + + def handle_key(self, evt: KeyboardEvent) -> None: + """Handle arrow keys and spacebar.""" + active = document.activeElement + if active and active.id == "text-input": + return + + if not self.game_manager.current_block: + return + + moved = False + if evt.key == "ArrowLeft": + moved = self.game_manager.current_block.move(-1, 0, self.game_manager.grid) + elif evt.key == "ArrowRight": + moved = self.game_manager.current_block.move(1, 0, self.game_manager.grid) + elif evt.key == "ArrowDown": + moved = self.game_manager.current_block.move(0, 1, self.game_manager.grid) + + if moved: + self.ui_manager.render() diff --git a/grand-gardenias/frontend/modes/roguelike/src/game.py b/grand-gardenias/frontend/modes/roguelike/src/game.py new file mode 100644 index 00000000..fb8406c7 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/game.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterator + + from engine.objects.block import Block + from engine.ui_manager import BaseUIManager + +from engine.game import BaseGameManager +from game_state import update_result + + +class GameManager(BaseGameManager): + # class GameManager(metaclass=SingletonMeta): + """Game logic manager.""" + + def __init__(self, *args: int, **kwargs: int) -> None: + super().__init__(*args, **kwargs) + self.block_gen: Iterator[Block] | None = None + self.ui_manager: BaseUIManager = None + self.bottom_pointer = 1 + + def tick(self) -> None: + """Advance game state by one step.""" + if not self.current_block or not self.current_block.falling: + return + + if self.current_block.y > self.rows - self.bottom_pointer: + self.lock_current_block() + + if not self.current_block.move(0, 1, self.grid): + self.lock_current_block() + + def spawn_next_block(self) -> None: + """Generate and spawn the next block.""" + try: + self.spawn_block(next(self.block_gen)) + except StopIteration: + update_result(self.format_grid_as_text()) + + def lock_current_block(self) -> None: + """Lock current block into grid.""" + super().lock_current_block() + self.spawn_next_block() + + +game_manager = GameManager(40, 20) diff --git a/grand-gardenias/frontend/modes/roguelike/src/game_state.py b/grand-gardenias/frontend/modes/roguelike/src/game_state.py new file mode 100644 index 00000000..aa905548 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/game_state.py @@ -0,0 +1,24 @@ +from js import document +from problem_helper import check_code +from question_manager import question_manager + + +def check_win(code: str) -> bool: # noqa: RET503 + """Check Whether the player has won or lost.""" + result = check_code(code, question_manager.ques_id) + if result in {"wrong code", "incorrect function"}: + return False + if result == "correct": + return True + + +def update_result(code: str) -> None: + """Update the UI after checking of the game status.""" + winning_status = check_win(code) + game_state = document.getElementById("game-state") + game_end_box = document.getElementById("game-end") + game_end_box.classList.remove("hidden") + if winning_status: + game_state.textContent = "Wow you got the code right" + else: + game_state.textContent = "No.. That is not right" diff --git a/grand-gardenias/frontend/modes/roguelike/src/modal.py b/grand-gardenias/frontend/modes/roguelike/src/modal.py new file mode 100644 index 00000000..4f078c0b --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/modal.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from engine.ui_manager import BaseUIManager + +from game import game_manager +from js import document + + +def close_modal(id: str) -> None: + """Close a modal dialog by its DOM element ID.""" + modal_bg = document.getElementById(id) + if modal_bg: + modal_bg.remove() + + +def continue_modal(id: str, ui_manager: BaseUIManager) -> None: + """Close a modal and spawn the first block.""" + close_modal(id) + + # Spawn the first block + game_manager.spawn_next_block() + ui_manager.render() diff --git a/grand-gardenias/frontend/modes/roguelike/src/problem_helper.py b/grand-gardenias/frontend/modes/roguelike/src/problem_helper.py new file mode 100644 index 00000000..446a1f39 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/problem_helper.py @@ -0,0 +1,48 @@ +import json + + +def check_code(code: str, id: int) -> str: # noqa: C901 + """Check code and test case.""" + if not code: + return "Write some code, dumbo." + ques = get_ques(id) + namespace = {} + try: + exec(code, namespace) # noqa: S102 + try: + func = namespace[ques["func_name"]] + validity_list = [] + + for i in ques["test_cases"]: + if isinstance(i["input"], list) and len(i["input"]) == 2: # noqa: PLR2004 + if func(*tuple(i["input"])) == i["expected_output"]: + validity_list.append((True, i["input"])) + else: + validity_list.append((False, i["input"])) + elif str(func(i["input"])) == str(i["expected_output"]): + validity_list.append((True, i["input"])) + else: + validity_list.append((False, i["input"])) + failed_case = [] + for i, test_case in validity_list: + if not i: + failed_case.append((i, test_case)) + if failed_case: + return f"Incorrect: {failed_case} {len(failed_case)} test case(s) failed" + return "correct" # noqa: TRY300 + + except (KeyError, TypeError): + return "incorrect function" + + except (NameError, IndentationError, SyntaxError): + return "wrong code" + + +# Get questions from json file +def get_ques(id: int) -> dict: + """Get ques from json file.""" + try: + with open(r"problems.json") as f: # noqa: PTH123 + return json.loads(f.read())[id - 1] + except IndexError: + return {} diff --git a/grand-gardenias/frontend/modes/roguelike/src/problems.json b/grand-gardenias/frontend/modes/roguelike/src/problems.json new file mode 100644 index 00000000..24f30538 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/problems.json @@ -0,0 +1,606 @@ +[ + { + "problem_id": 1, + "title": "Add Two Numbers", + "func_name":"add_numbers", + "description": "Write a function named 'add_numbers' that takes two numbers as input and returns their sum.", + "solution_code": "def add_numbers(a, b):\n return a + b", + "test_cases": [ + { + "input": [5, 3], + "expected_output": 8 + }, + { + "input": [0, 0], + "expected_output": 0 + }, + { + "input": [-10, 5], + "expected_output": -5 + } + ] + }, + { + "problem_id": 2, + "title": "Reverse a String", + "func_name":"reverse_string", + "description": "Write a function named 'reverse_string' that takes a string as input and returns the string reversed.", + "solution_code": "def reverse_string(s):\n return s[::-1]", + "test_cases": [ + { + "input": "hello", + "expected_output": "olleh" + }, + { + "input": "Python", + "expected_output": "nohtyP" + }, + { + "input": "a", + "expected_output": "a" + } + ] + }, + { + "problem_id": 3, + "title": "Check if a Number is Even", + "func_name":"is_even", + "description": "Write a function named 'is_even' that takes an integer and returns True if it's even, otherwise False.", + "solution_code": "def is_even(n):\n return n % 2 == 0", + "test_cases": [ + { + "input": 4, + "expected_output": "True" + }, + { + "input": 7, + "expected_output": "False" + }, + { + "input": 0, + "expected_output": "True" + } + ] + }, + { + "problem_id": 4, + "title": "Find the Maximum Number in a List", + "func_name":"find_max", + "description": "Write a function named 'find_max' that takes a list of numbers and returns the largest number in the list.", + "solution_code": "def find_max(numbers):\n return max(numbers)", + "test_cases": [ + { + "input": [1, 5, 2, 8, 3], + "expected_output": 8 + }, + { + "input": [-10, -2, -5], + "expected_output": -2 + }, + { + "input": [100], + "expected_output": 100 + } + ] + }, + { + "problem_id": 5, + "title": "Calculate Factorial", + "func_name":"factorial", + "description": "Write a function named 'factorial' to calculate the factorial of a non-negative integer.", + "solution_code": "def factorial(n):\n if n == 0:\n return 1\n else:\n return n * factorial(n - 1)", + "test_cases": [ + { + "input": 5, + "expected_output": 120 + }, + { + "input": 0, + "expected_output": 1 + }, + { + "input": 3, + "expected_output": 6 + } + ] + }, + { + "problem_id": 6, + "title": "Count Vowels in a String", + "func_name":"count_vowels", + "description": "Write a function named 'count_vowels' that takes a string and returns the number of vowels (a, e, i, o, u) in it.", + "solution_code": "def count_vowels(s):\n vowels = \"aeiou\"\n count = 0\n for char in s.lower():\n if char in vowels:\n count += 1\n return count", + "test_cases": [ + { + "input": "hello", + "expected_output": 2 + }, + { + "input": "AEIOU", + "expected_output": 5 + }, + { + "input": "rhythm", + "expected_output": 0 + } + ] + }, + { + "problem_id": 7, + "title": "Check for Palindrome", + "func_name":"is_palindrome", + "description": "Write a function named 'is_palindrome' that checks if a string is a palindrome (reads the same forwards and backwards).", + "solution_code": "def is_palindrome(s):\n return s.lower() == s.lower()[::-1]", + "test_cases": [ + { + "input": "racecar", + "expected_output": "True" + }, + { + "input": "hello", + "expected_output": "False" + }, + { + "input": "Madam", + "expected_output": "True" + } + ] + }, + { + "problem_id": 8, + "title": "Find the Smallest Number in a List", + "func_name":"find_min", + "description": "Write a function named 'find_min' that takes a list of numbers and returns the smallest number.", + "solution_code": "def find_min(numbers):\n return min(numbers)", + "test_cases": [ + { + "input": [10, 5, 2, 8, 3], + "expected_output": 2 + }, + { + "input": [-10, -2, -5], + "expected_output": -10 + }, + { + "input": [100], + "expected_output": 100 + } + ] + }, + { + "problem_id": 9, + "title": "Sum all Numbers in a List", + "func_name":"sum_list", + "description": "Write a function named 'sum_list' that takes a list of numbers and returns their sum.", + "solution_code": "def sum_list(numbers):\n return sum(numbers)", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": 10 + }, + { + "input": [0, 0, 0], + "expected_output": 0 + }, + { + "input": [10, -5, 2], + "expected_output": 7 + } + ] + }, + { + "problem_id": 10, + "title": "Remove Duplicates from a List", + "func_name":"remove_duplicates", + "description": "Write a function named 'remove_duplicates' that takes a list and returns a new list with all duplicates removed.", + "solution_code": "def remove_duplicates(l):\n return list(set(l))", + "test_cases": [ + { + "input": [1, 2, 2, 3, 4, 4, 5], + "expected_output": [1, 2, 3, 4, 5] + }, + { + "input": ["a", "b", "c", "a"], + "expected_output": ["a", "b", "c"] + }, + { + "input": [1, 1, 1], + "expected_output": [1] + } + ] + }, + { + "problem_id": 11, + "title": "Calculate Average", + "func_name":"calculate_average", + "description": "Write a function named 'calculate_average' that takes a list of numbers and returns their average.", + "solution_code": "def calculate_average(numbers):\n if not numbers:\n return 0\n return sum(numbers) / len(numbers)", + "test_cases": [ + { + "input": [1, 2, 3, 4, 5], + "expected_output": 3.0 + }, + { + "input": [10, 20, 30], + "expected_output": 20.0 + }, + { + "input": [], + "expected_output": 0 + } + ] + }, + { + "problem_id": 12, + "title": "Find the Longest Word", + "func_name":"find_longest_word", + "description": "Write a function named 'find_longest_word' that takes a list of words and returns the longest word.", + "solution_code": "def find_longest_word(words):\n return max(words, key=len)", + "test_cases": [ + { + "input": ["apple", "banana", "kiwi"], + "expected_output": "banana" + }, + { + "input": ["a", "bb", "ccc"], + "expected_output": "ccc" + }, + { + "input": ["one"], + "expected_output": "one" + } + ] + }, + { + "problem_id": 13, + "title": "Is String a Substring?", + "func_name":"is_substring", + "description": "Write a function named 'is_substring' that checks if a string is a substring of another string.", + "solution_code": "def is_substring(sub, main):\n return sub in main", + "test_cases": [ + { + "input": ["world", "hello world"], + "expected_output": "True" + }, + { + "input": ["moon", "sun"], + "expected_output": "False" + }, + { + "input": ["", "any string"], + "expected_output": "True" + } + ] + }, + { + "problem_id": 14, + "title": "Calculate Circle Area", + "func_name":"circle_area", + + "description": "Write a function named 'circle_area' that takes the radius of a circle and returns its area.", + "solution_code": "import math\ndef circle_area(r):\n return math.pi * r**2", + "test_cases": [ + { + "input": 1, + "expected_output": 3.141592653589793 + }, + { + "input": 0, + "expected_output": 0.0 + }, + { + "input": 2.5, + "expected_output": 19.634954084936208 + } + ] + }, + { + "problem_id": 15, + "title": "Convert Celsius to Fahrenheit", + "func_name":"celsius_to_farenheit", + "description": "Write a function named 'celsius_to_fahrenheit' that converts a temperature from Celsius to Fahrenheit.", + "solution_code": "def celsius_to_fahrenheit(c):\n return (c * 9/5) + 32", + "test_cases": [ + { + "input": 0, + "expected_output": 32.0 + }, + { + "input": 100, + "expected_output": 212.0 + }, + { + "input": -40, + "expected_output": -40.0 + } + ] + }, + { + "problem_id": 16, + "title": "Count Words in a String", + "func_name":"count_words", + "description": "Write a function named 'count_words' that counts the number of words in a given string.", + "solution_code": "def count_words(s):\n return len(s.split())", + "test_cases": [ + { + "input": "Hello world", + "expected_output": 2 + }, + { + "input": "This is a test sentence.", + "expected_output": 5 + }, + { + "input": "oneword", + "expected_output": 1 + } + ] + }, + { + "problem_id": 17, + "title": "Find First Uppercase Letter", + "func_name":"find_first_uppercase", + "description": "Write a function named 'find_first_uppercase' that finds the first uppercase letter in a string.", + "solution_code": "def find_first_uppercase(s):\n for char in s:\n if 'A' <= char <= 'Z':\n return char\n return None", + "test_cases": [ + { + "input": "helloWorld", + "expected_output": "W" + }, + { + "input": "alllowercase", + "expected_output": null + }, + { + "input": "UPPERCASE", + "expected_output": "U" + } + ] + }, + { + "problem_id": 18, + "title": "Generate a Fibonacci Sequence", + "func_name":"fibonacci_sequence", + + "description": "Write a function named 'fibonacci_sequence' that generates a Fibonacci sequence up to a given number of terms.", + "solution_code": "def fibonacci_sequence(n):\n sequence = []\n a, b = 0, 1\n while len(sequence) < n:\n sequence.append(a)\n a, b = b, a + b\n return sequence", + "test_cases": [ + { + "input": 5, + "expected_output": [0, 1, 1, 2, 3] + }, + { + "input": 1, + "expected_output": [0] + }, + { + "input": 0, + "expected_output": [] + } + ] + }, + { + "problem_id": 19, + "title": "Check if a Number is Prime", + "func_name":"is_prime", + "description": "Write a function named 'is_prime' that checks if a number is a prime number.", + "solution_code": "def is_prime(n):\n if n <= 1:\n return False\n for i in range(2, int(n**0.5) + 1):\n if n % i == 0:\n return False\n return True", + "test_cases": [ + { + "input": 7, + "expected_output": "True" + }, + { + "input": 10, + "expected_output": "False" + }, + { + "input": 1, + "expected_output": "False" + } + ] + }, + { + "problem_id": 20, + "title": "FizzBuzz", + "func_name":"fizzbuzz", + "description": "Write a function named 'fizzbuzz' that prints numbers from 1 to 15. For multiples of 3 print 'Fizz', for multiples of 5 print 'Buzz', and for multiples of both, print 'FizzBuzz'.", + "solution_code": "def fizzbuzz():\n results = []\n for i in range(1, 16):\n if i % 3 == 0 and i % 5 == 0:\n results.append('FizzBuzz')\n elif i % 3 == 0:\n results.append('Fizz')\n elif i % 5 == 0:\n results.append('Buzz')\n else:\n results.append(str(i))\n return results", + "test_cases": [ + { + "input": null, + "expected_output": ["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz"] + } + ] + }, + { + "problem_id": 21, + "title": "Merge Two Lists", + "func_name":"merge_lists", + "description": "Write a function named 'merge_lists' that takes two lists and returns a new list containing all elements from both.", + "solution_code": "def merge_lists(list1, list2):\n return list1 + list2", + "test_cases": [ + { + "input": [[1, 2], [3, 4]], + "expected_output": [1, 2, 3, 4] + }, + { + "input": [["a"], ["b", "c"]], + "expected_output": ["a", "b", "c"] + }, + { + "input": [[], [1, 2, 3]], + "expected_output": [1, 2, 3] + } + ] + }, + { + "problem_id": 22, + "title": "Check if List is Sorted", + "func_name":"is_sorted", + "description": "Write a function named 'is_sorted' that returns True if a list is sorted in ascending order, otherwise False.", + "solution_code": "def is_sorted(l):\n return l == sorted(l)", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": "True" + }, + { + "input": [4, 3, 2, 1], + "expected_output": "False" + }, + { + "input": [1, 3, 2], + "expected_output": "False" + } + ] + }, + { + "problem_id": 23, + "title": "Reverse a List", + "func_name":"reverse_list", + "description": "Write a function named 'reverse_list' that reverses the order of elements in a list.", + "solution_code": "def reverse_list(l):\n return l[::-1]", + "test_cases": [ + { + "input": [1, 2, 3], + "expected_output": [3, 2, 1] + }, + { + "input": ["a", "b", "c"], + "expected_output": ["c", "b", "a"] + }, + { + "input": [10], + "expected_output": [10] + } + ] + }, + { + "problem_id": 24, + "title": "Sum of Digits", + "func_name":"sum_digits", + "description": "Write a function named 'sum_digits' that calculates the sum of the digits of an integer.", + "solution_code": "def sum_digits(n):\n return sum(int(digit) for digit in str(n))", + "test_cases": [ + { + "input": 123, + "expected_output": 6 + }, + { + "input": 999, + "expected_output": 27 + }, + { + "input": 0, + "expected_output": 0 + } + ] + }, + + { + "problem_id": 25, + "func_name":"is_digit_string", + "title": "Check if a String Contains Only Digits", + "description": "Write a function named 'is_digit_string' that returns True if a string contains only digits, otherwise False.", + "solution_code": "def is_digit_string(s):\n return s.isdigit()", + "test_cases": [ + { + "input": "12345", + "expected_output": "True" + }, + { + "input": "123a45", + "expected_output": "False" + }, + { + "input": "", + "expected_output": "False" + } + ] + }, + { + "problem_id": 26, + "func_name":"swap_first_last", + "title": "Swap First and Last Elements of a List", + "description": "Write a function named 'swap_first_last' that swaps the first and last elements of a list and returns the modified list.", + "solution_code": "def swap_first_last(l):\n if len(l) > 1:\n l[0], l[-1] = l[-1], l[0]\n return l", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": [4, 2, 3, 1] + }, + { + "input": ["a", "b", "c"], + "expected_output": ["c", "b", "a"] + }, + { + "input": [10], + "expected_output": [10] + } + ] + }, + { + "problem_id": 27, + "title": "Convert a String to a List of Characters", + "func_name":"string_to_list", + "description": "Write a function named 'string_to_list' that takes a string and converts it into a list of its characters.", + "solution_code": "def string_to_list(s):\n return list(s)", + "test_cases": [ + { + "input": "hello", + "expected_output": ["h", "e", "l", "l", "o"] + }, + { + "input": "Python", + "expected_output": ["P", "y", "t", "h", "o", "n"] + }, + { + "input": "", + "expected_output": [] + } + ] + }, + { + "problem_id": 28, + "func_name":"all_are_unique", + "title": "Check if all Elements in a List are Unique", + "description": "Write a function named 'are_all_unique' that returns True if all elements in a list are unique, otherwise False.", + "solution_code": "def are_all_unique(l):\n return len(l) == len(set(l))", + "test_cases": [ + { + "input": [1, 2, 3, 4], + "expected_output": "True" + }, + { + "input": [1, 2, 2, 3], + "expected_output": "False" + }, + { + "input": [], + "expected_output": "True" + } + ] + }, + { + "problem_id": 29, + "title": "Find the Intersection of Two Lists", + "func_name":"list_intersection", + "description": "Write a function named 'list_intersection' that takes two lists and returns a new list containing only the elements that are common to both.", + "solution_code": "def list_intersection(list1, list2):\n return list(set(list1) & set(list2))", + "test_cases": [ + { + "input": [[1, 2, 3], [3, 4, 5]], + "expected_output": [3] + }, + { + "input": [[1, 2], [3, 4]], + "expected_output": [] + }, + { + "input": [["a", "b"], ["b", "c"]], + "expected_output": ["b"] + } + ] + } + ] diff --git a/grand-gardenias/frontend/modes/roguelike/src/question_manager.py b/grand-gardenias/frontend/modes/roguelike/src/question_manager.py new file mode 100644 index 00000000..7593ece3 --- /dev/null +++ b/grand-gardenias/frontend/modes/roguelike/src/question_manager.py @@ -0,0 +1,37 @@ +import random +from typing import ClassVar + +from engine.patterns import SingletonMeta +from js import document +from problem_helper import get_ques + + +class QuestionManager(metaclass=SingletonMeta): + """Manages question selection and updates the displayed question.""" + + TOTAL_QUESTIONS: ClassVar[int] = 29 + # Questions excluded because they cause issues in this game mode + DEFAULT_EXCLUDED: ClassVar[set[int]] = {0, 6, 17, 18, 19, 20, 26, 29} + + def __init__(self) -> None: + self.ques_id: int | None = None + # Keep track of excluded questions and avoid duplicates + self.excluded_questions = set(self.DEFAULT_EXCLUDED) + + def get_question(self) -> dict: + """Select a new question while avoiding excluded questions.""" + remaining_questions = set(range(1, self.TOTAL_QUESTIONS + 1)) - self.excluded_questions + + if not remaining_questions: + # TODO: Add end game logic + pass + + self.ques_id = random.choice(list(remaining_questions)) # noqa: S311 + self.excluded_questions.add(self.ques_id) + + ques = get_ques(self.ques_id) + document.getElementById("question").textContent = ques["description"] + return ques + + +question_manager = QuestionManager() diff --git a/grand-gardenias/frontend/package-lock.json b/grand-gardenias/frontend/package-lock.json new file mode 100644 index 00000000..ac50c023 --- /dev/null +++ b/grand-gardenias/frontend/package-lock.json @@ -0,0 +1,1140 @@ +{ + "name": "frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@tailwindcss/cli": "^4.1.11", + "tailwindcss": "^4.1.11" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/cli": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.11.tgz", + "integrity": "sha512-7RAFOrVaXCFz5ooEG36Kbh+sMJiI2j4+Ozp71smgjnLfBRu7DTfoq8DsTvzse2/6nDeo2M3vS/FGaxfDgr3rtQ==", + "license": "MIT", + "dependencies": { + "@parcel/watcher": "^2.5.1", + "@tailwindcss/node": "4.1.11", + "@tailwindcss/oxide": "4.1.11", + "enhanced-resolve": "^5.18.1", + "mri": "^1.2.0", + "picocolors": "^1.1.1", + "tailwindcss": "4.1.11" + }, + "bin": { + "tailwindcss": "dist/index.mjs" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", + "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", + "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-arm64": "4.1.11", + "@tailwindcss/oxide-darwin-x64": "4.1.11", + "@tailwindcss/oxide-freebsd-x64": "4.1.11", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", + "@tailwindcss/oxide-linux-x64-musl": "4.1.11", + "@tailwindcss/oxide-wasm32-wasi": "4.1.11", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", + "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", + "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", + "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", + "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", + "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", + "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", + "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", + "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", + "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", + "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.11", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", + "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", + "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide/node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "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==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss/node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/grand-gardenias/frontend/package.json b/grand-gardenias/frontend/package.json new file mode 100644 index 00000000..4a6f2524 --- /dev/null +++ b/grand-gardenias/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "npx @tailwindcss/cli -i ./styles/input.css -o ./dist/output.css --watch", + "build": "npx @tailwindcss/cli -i ./styles/input.css -o ./dist/output.css --minify" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "@tailwindcss/cli": "^4.1.11", + "tailwindcss": "^4.1.11" + } +} diff --git a/grand-gardenias/frontend/shared/__init__.py b/grand-gardenias/frontend/shared/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/grand-gardenias/frontend/shared/audio/button.wav b/grand-gardenias/frontend/shared/audio/button.wav new file mode 100644 index 00000000..f4519317 Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/button.wav differ diff --git a/grand-gardenias/frontend/shared/audio/game-over-wolarado.wav b/grand-gardenias/frontend/shared/audio/game-over-wolarado.wav new file mode 100644 index 00000000..4b83faac Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/game-over-wolarado.wav differ diff --git a/grand-gardenias/frontend/shared/audio/place.wav b/grand-gardenias/frontend/shared/audio/place.wav new file mode 100644 index 00000000..1e06fb03 Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/place.wav differ diff --git a/grand-gardenias/frontend/shared/audio/retro.mp3 b/grand-gardenias/frontend/shared/audio/retro.mp3 new file mode 100644 index 00000000..0f227df5 Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/retro.mp3 differ diff --git a/grand-gardenias/frontend/shared/audio/win.wav b/grand-gardenias/frontend/shared/audio/win.wav new file mode 100644 index 00000000..80d96ed0 Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/win.wav differ diff --git a/grand-gardenias/frontend/shared/audio/wrong-ans.wav b/grand-gardenias/frontend/shared/audio/wrong-ans.wav new file mode 100644 index 00000000..16d679e2 Binary files /dev/null and b/grand-gardenias/frontend/shared/audio/wrong-ans.wav differ diff --git a/grand-gardenias/frontend/shared/audio_utils.py b/grand-gardenias/frontend/shared/audio_utils.py new file mode 100644 index 00000000..9b11dd1b --- /dev/null +++ b/grand-gardenias/frontend/shared/audio_utils.py @@ -0,0 +1,13 @@ +from js import Audio + + +def play_place_sound() -> None: + """Play the block place sound effect.""" + audio = Audio.new("/shared/audio/place.wav") + audio.play() + + +def win_sound() -> None: + """Play win sound.""" + audio = Audio.new("/shared/audio/win.wav") + audio.play() diff --git a/grand-gardenias/frontend/styles/input.css b/grand-gardenias/frontend/styles/input.css new file mode 100644 index 00000000..5aa05b28 --- /dev/null +++ b/grand-gardenias/frontend/styles/input.css @@ -0,0 +1,82 @@ +@import "tailwindcss"; + +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Press+Start+2P&display=swap'); + +/* Import custom CSS files */ +/* @import "./base.css"; */ +/* @import "./components.css"; */ + + +/* When input is focused, remove border & shadow from grid */ +#wrapper:focus-within #game { + @apply border-transparent shadow-none; +} + +/* When input is focused, give it brand border */ +#wrapper:focus-within #text-input { + @apply border border-brand outline-none; + border-style: solid; +} + + +@theme { + --animate-spin: spin 1s linear infinite; + --color-brand: #11ce60; /* lime-500 by default */ + --font-mono: "JetBrains Mono", monospace; + --font-retro: "Press Start 2P", system-ui; + --shadow-retro: 4px 4px 0 #ff00ff, 8px 8px 0 #00ffff; + --shadow-retro-lg: 6px 6px 0 #ff00ff, 12px 12px 0 #00ffff; + + /* shadow-retro Animations */ + --shadow-retro-small: 1px 1px 0 #ff00ff, 2px 2px 0 #00ffff; + --shadow-retro-large: 4px 4px 0 #ff00ff, 8px 8px 0 #00ffff; + --animate-retro-shadow: retro-shadow-grow 0.3s forwards; +} + +@keyframes retro-shadow-grow { + from { + box-shadow: var(--shadow-retro-small); + } + to { + box-shadow: var(--shadow-retro-large); + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.block { + @apply font-mono bg-brand text-black flex items-center justify-center; +} + +.cell { + @apply w-[20px] h-[20px] bg-[#111] border border-black; + @apply text-white text-[12px] font-mono flex items-center justify-center; +} + +.locked-cell { + @apply font-mono bg-[#222] flex items-center justify-center; +} + +.retro-shadow { + box-shadow: 4px 4px 0 #ff00ff, 8px 8px 0 #00ffff; +} + + +.retro-btn { + @apply py-4 px-8 bg-brand text-black uppercase font-retro; + @apply cursor-pointer transition-all; + @apply hover:animate-retro-shadow +} + +.retro-btn-secondary { + @apply py-4 px-8 border-brand text-brand uppercase font-retro; + @apply cursor-pointer transition-all hover:bg-brand border hover:text-black; + @apply hover:animate-retro-shadow +} diff --git a/grand-gardenias/poetry.lock b/grand-gardenias/poetry.lock new file mode 100644 index 00000000..20d79058 --- /dev/null +++ b/grand-gardenias/poetry.lock @@ -0,0 +1,719 @@ +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. + +[[package]] +name = "blinker" +version = "1.9.0" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, + {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, +] + +[[package]] +name = "click" +version = "8.2.1" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "distlib" +version = "0.4.0" +description = "Distribution utilities" +optional = false +python-versions = "*" +groups = ["main", "dev"] +files = [ + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, +] + +[[package]] +name = "filelock" +version = "3.18.0" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, + {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] + +[[package]] +name = "flask" +version = "3.1.1" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c"}, + {file = "flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e"}, +] + +[package.dependencies] +blinker = ">=1.9.0" +click = ">=8.1.3" +itsdangerous = ">=2.2.0" +jinja2 = ">=3.1.2" +markupsafe = ">=2.1.1" +werkzeug = ">=3.1.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "identify" +version = "2.6.13" +description = "File identification library for Python" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, + {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev"] +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "4.3.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8"}, + {file = "pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyscript" +version = "0.3.3" +description = "Command Line Interface for PyScript" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyscript-0.3.3-py3-none-any.whl", hash = "sha256:320383f38e9eec6515dbe0c184d4ad9d9c58e2c98fb82ec09e8d8b2e93c9e62f"}, + {file = "pyscript-0.3.3.tar.gz", hash = "sha256:11fc64a3f187d8645c601ae6a80e3f0142e0dd9e0c5d3244b0ec508ca0d373f9"}, +] + +[package.dependencies] +Jinja2 = "<3.2" +platformdirs = "<4.3" +pluggy = "1.5.0" +requests = "<=2.31.0" +rich = "<=13.7.1" +toml = "<0.11" +typer = "<=0.9.0" + +[package.extras] +dev = ["coverage (<7.3)", "mypy (<=1.4.1)", "pytest (<7.5)", "types-requests", "types-toml (<0.11)"] +docs = ["Sphinx (<5.2)", "myst-parser (<0.19.3)", "pydata-sphinx-theme (<0.13.4)", "sphinx-autobuild (<2021.4.0)", "sphinx-autodoc-typehints (<1.20)"] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "13.7.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +groups = ["main"] +files = [ + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "ruff" +version = "0.12.8" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513"}, + {file = "ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc"}, + {file = "ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb"}, + {file = "ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161"}, + {file = "ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46"}, + {file = "ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3"}, + {file = "ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e"}, + {file = "ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749"}, + {file = "ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033"}, +] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "typer" +version = "0.9.0" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, + {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, +] + +[package.dependencies] +click = ">=7.1.1,<9.0.0" +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] +doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] +test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.33.1" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67"}, + {file = "virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] + +[[package]] +name = "werkzeug" +version = "3.1.3" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, + {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[metadata] +lock-version = "2.1" +python-versions = "^3.13" +content-hash = "73ff5e7216ad449dbd9b9ddb15d661cbe6b0f52bd9ded2003b2bd98c1fc75894" diff --git a/grand-gardenias/pyproject.toml b/grand-gardenias/pyproject.toml new file mode 100644 index 00000000..e166ff52 --- /dev/null +++ b/grand-gardenias/pyproject.toml @@ -0,0 +1,44 @@ +[tool.poetry] +name = "code-jam-template" +version = "0.1.0" +description = "Add your description here" +authors = [ + "Zishan Kadri", + "Shivam Khetan" +] +readme = "README.md" +packages = [ + { include = "backend" }, + { include = "frontend" } +] + +[tool.poetry.scripts] +start = "backend.run:main" + +[tool.poetry.dependencies] +python = "^3.13" +pyscript = "^0.3.3" +flask = "^3.1.1" +pre-commit = "^4.3.0" + +[tool.poetry.group.dev.dependencies] +pre-commit = "^4.3.0" +ruff = "~0.12.2" + +[tool.ruff] +line-length = 119 +target-version = "py313" +fix = true +src = ["src"] + +[tool.ruff.lint] +select = ["ALL"] +ignore = [ + "D100", "D104", "D105", "D106", "D107", + "D203", "D213", + "D415", + "D301", + "A", + "T20", + "TD002", "TD003", "FIX" +]