diff --git a/README.md b/README.md index 9fe2d5f2cbe..c7f6ff4ac5f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@
# ๐Ÿฅ‹ Black Trigram (ํ‘๊ด˜) -### *๋ฌด์˜ˆ๋Š” ๋ชธ๊ณผ ๋งˆ์Œ, ๊ทธ๋ฆฌ๊ณ  ์˜ํ˜ผ์˜ ์กฐํ™”์ด๋‹ค* -*"Martial arts are the harmony of body, mind, and spirit"* -![Dark Trigram](src/assets/black-trigram.png) +### _๋ฌด์˜ˆ๋Š” ๋ชธ๊ณผ ๋งˆ์Œ, ๊ทธ๋ฆฌ๊ณ  ์˜ํ˜ผ์˜ ์กฐํ™”์ด๋‹ค_ + +_"Martial arts are the harmony of body, mind, and spirit"_ + +![Dark Trigram Logo](public/dark-trigram-256.png) [![License](https://img.shields.io/github/license/Hack23/blacktrigram.svg)](https://github.com/Hack23/blacktrigram/raw/master/LICENSE.md) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Hack23/blacktrigram/badge)](https://scorecard.dev/viewer/?uri=github.com/Hack23/blacktrigram) -*A 2D precision combat game inspired by Korean martial arts philosophy and the I Ching* +_A 2D precision combat game inspired by Korean martial arts philosophy and the I Ching_ + +**[๐ŸŽฎ Play Now](https://hack23.github.io/blacktrigram/)** | **[๐Ÿ“– Game Guide](#-game-features)** | **[๐Ÿ”ง Development](#-technical-excellence)**
@@ -26,41 +30,41 @@ **๐ŸŽฏ ์ •๊ฒฉ์ž** -*Jeonggyeokja* +_Jeonggyeokja_ **Precision Attacker** -*Every strike calculated -and deliberate* +_Every strike calculated +and deliberate_ **โš”๏ธ ๋น„์ˆ˜** -*Bisu* +_Bisu_ **Lethal Blade** -*Metaphorical lethality -in unarmed combat* +_Metaphorical lethality +in unarmed combat_ **๐Ÿฅท ์•”์‚ด์ž** -*Amsalja* +_Amsalja_ **Assassin** -*Focus on finishing -techniques* +_Focus on finishing +techniques_ **๐Ÿ’€ ๊ธ‰์†Œ๊ฒฉ** -*Geupsogyeok* +_Geupsogyeok_ **Vital Point Strike** -*Core combat -mechanic* +_Core combat +mechanic_ @@ -68,40 +72,42 @@ mechanic* --- -## ๐ŸŒŸ Game Features +## ๐ŸŒŸ Enhanced Game Features ### ๐Ÿ”ฎ Trigram-Based Combat System (ํŒ”๊ด˜ ๋ฌด์ˆ ์ฒด๊ณ„) -Master **8 unique fighting philosophies** based on the I Ching trigrams: +Master **8 unique fighting philosophies** based on the I Ching trigrams with enhanced visual effects:
-| Trigram | Name | Philosophy | Combat Style | -|:-------:|:----:|:----------:|:------------:| -| โ˜ฐ | **๊ฑด (Geon)** - *Heaven* | Pure yang energy | ๐Ÿ”ฅ Power strikes, guard breaking | -| โ˜ฑ | **ํƒœ (Tae)** - *Lake* | Joy and flexibility | ๐ŸŒŠ Flowing combinations | -| โ˜ฒ | **๋ฆฌ (Li)** - *Fire* | Clarity and intensity | โšก Fast, precise attacks | -| โ˜ณ | **์ง„ (Jin)** - *Thunder* | Sudden movement | ๐Ÿ’ฅ Explosive bursts | -| โ˜ด | **์† (Son)** - *Wind* | Gentle persistence | ๐ŸŒช๏ธ Continuous pressure | -| โ˜ต | **๊ฐ (Gam)** - *Water* | Adaptation and flow | ๐Ÿ›ก๏ธ Evasion and counters | -| โ˜ถ | **๊ฐ„ (Gan)** - *Mountain* | Stillness and endurance | ๐Ÿ—ฟ Immovable defense | -| โ˜ท | **๊ณค (Gon)** - *Earth* | Receptive yin energy | ๐Ÿคœ Throws and takedowns | +| Trigram | Name | Philosophy | Combat Style | Visual Effects | +| :-----: | :-----------------------: | :---------------------: | :----------------------: | :----------------------------: | +| โ˜ฐ | **๊ฑด (Geon)** - _Heaven_ | Pure yang energy | ๐Ÿ”ฅ Power strikes | Lightning bursts, screen shake | +| โ˜ฑ | **ํƒœ (Tae)** - _Lake_ | Joy and flexibility | ๐ŸŒŠ Flowing combinations | Water ripple effects | +| โ˜ฒ | **๋ฆฌ (Li)** - _Fire_ | Clarity and intensity | โšก Fast, precise attacks | Fire particle trails | +| โ˜ณ | **์ง„ (Jin)** - _Thunder_ | Sudden movement | ๐Ÿ’ฅ Explosive bursts | Thunder shock waves | +| โ˜ด | **์† (Son)** - _Wind_ | Gentle persistence | ๐ŸŒช๏ธ Continuous pressure | Wind spiral effects | +| โ˜ต | **๊ฐ (Gam)** - _Water_ | Adaptation and flow | ๐Ÿ›ก๏ธ Evasion and counters | Water shield barriers | +| โ˜ถ | **๊ฐ„ (Gan)** - _Mountain_ | Stillness and endurance | ๐Ÿ—ฟ Immovable defense | Stone impact effects | +| โ˜ท | **๊ณค (Gon)** - _Earth_ | Receptive yin energy | ๐Ÿคœ Throws and takedowns | Earth shake tremors |
-### ๐ŸŽฏ Vital Point Targeting System +### ๐Ÿ’ฅ Enhanced Combat Effects -- **70 Anatomical Targets** across 5 body regions -- **Precision-based damage** - Perfect hits deal 150% damage -- **Real martial arts knowledge** - Learn actual vital points -- **Anatomical mastery** - Unlock new techniques through study +- **๐ŸŒŸ Particle Systems** - Explosive hit effects with 25+ particles for vital strikes +- **๐Ÿ“บ Screen Shake** - Dynamic camera shake based on impact force +- **โšก Lightning Effects** - Electric arcs for critical hits +- **๐ŸŽญ Dark Trigram Logo** - Appears on devastating vital point strikes +- **๐Ÿ”Š Audio-Visual Sync** - Sound effects perfectly timed with visual impacts +- **๐ŸŒ€ Element-Specific Particles** - Each trigram has unique visual effects -### ๐ŸŽจ Authentic Korean Aesthetics +### ๐ŸŽฏ Advanced Vital Point System -- **Traditional Korean art style** with modern 2D animation -- **Ink brush UI elements** and transitional effects -- **Korean language integration** with proper martial arts terminology -- **Cultural authenticity** in philosophy and combat principles +- **๐ŸŽช 70 Anatomical Targets** with enhanced visual feedback +- **๐Ÿ’ฏ Precision-based damage** - Perfect hits trigger spectacular effects +- **๐Ÿง  Real martial arts knowledge** - Learn actual pressure points +- **๐ŸŽจ Korean Typography** - Beautiful Korean text integration --- @@ -111,617 +117,198 @@ Built with modern web technologies for **maximum performance** and **security**:
-### ๐ŸŽฎ Game Engine +### ๐ŸŽฎ Enhanced Game Engine + ![PixiJS](https://img.shields.io/badge/PixiJS-8.x-ff6b6b?style=for-the-badge&logo=javascript) ![React](https://img.shields.io/badge/React-19-61dafb?style=for-the-badge&logo=react) ![TypeScript](https://img.shields.io/badge/TypeScript-Strict-3178c6?style=for-the-badge&logo=typescript) -### โšก Performance +### โšก Performance Optimized + ![Vite](https://img.shields.io/badge/Vite-Fast-646cff?style=for-the-badge&logo=vite) ![WebGL](https://img.shields.io/badge/WebGL-Hardware_Accelerated-990000?style=for-the-badge) ![Howler.js](https://img.shields.io/badge/Audio-Howler.js-ff9500?style=for-the-badge) -### ๐Ÿ”’ Security First -![OSSF](https://img.shields.io/badge/OSSF-Scorecard-4c1?style=for-the-badge) -![SLSA](https://img.shields.io/badge/SLSA-Compliant-2ea44f?style=for-the-badge) -![SBOM](https://img.shields.io/badge/SBOM-Generated-orange?style=for-the-badge) +### ๐ŸŽจ Visual Effects -
+![Particles](https://img.shields.io/badge/Particles-Custom_Engine-9c27b0?style=for-the-badge) +![Shaders](https://img.shields.io/badge/Shaders-WebGL-ff5722?style=for-the-badge) +![Animation](https://img.shields.io/badge/Animation-60fps-4caf50?style=for-the-badge) ---- + -## ๐ŸŽญ Game Modes +### ๐ŸŽจ New UI Components -### ๐Ÿฏ ์ˆ˜๋ จ ๋ชจ๋“œ (Suryeon Mode) - Training -- **๐Ÿ“š Vital Point Study** - Learn anatomical targets -- **โšก Trigram Practice** - Master individual styles -- **๐ŸŽฏ Precision Training** - Improve targeting accuracy -- **๐Ÿง˜ Breathing Exercises** - Develop rhythm and timing - -### โš”๏ธ ๋Œ€๋ จ ๋ชจ๋“œ (Daeryeon Mode) - Sparring -- **๐ŸฅŠ Single Combat** - One-on-one precision duels -- **๐Ÿ† Tournament** - Progressive difficulty challenges -- **๐Ÿ‘‘ Master Challenges** - Face legendary martial artists - -### ๐Ÿ“– ์ฒ ํ•™ ๋ชจ๋“œ (Cheolhak Mode) - Philosophy -- **๐Ÿ“œ I Ching Study** - Learn trigram meanings -- **๐Ÿ›๏ธ Historical Lessons** - Korean martial arts heritage -- **๐Ÿง˜โ€โ™‚๏ธ Meditation Practice** - Improve focus and precision - -### ๐ŸŒ™ ์•”์‚ด ๋ชจ๋“œ (Amsal Mode) - Assassination -- **๐Ÿคซ Stealth Missions** - Eliminate without detection -- **๐Ÿ’ฏ One-Strike Challenges** - Perfect precision required -- **๐Ÿž๏ธ Environmental Kills** - Use surroundings tactically +- **CyberpunkPanel** - Futuristic panels with Korean design elements +- **CyberpunkButton** - Interactive buttons with hover effects +- **Enhanced HitEffects** - Spectacular visual feedback system +- **Texture Loading** - Optimized asset management with caching --- -## ๐Ÿ”’ Security Features - -This template implements comprehensive security measures: - -- **๐Ÿ›ก๏ธ Supply Chain Security** - OSSF Scorecard analysis and dependency review -- **๐Ÿ” Static Analysis** - CodeQL scanning for vulnerabilities -- **๐Ÿ“ฆ Dependency Protection** - Automated dependency vulnerability checks -- **๐Ÿ” Runner Hardening** - All CI/CD runners are hardened with audit logging -- **๐Ÿ“‹ Security Policies** - GitHub security advisories and vulnerability reporting -- **๐Ÿท๏ธ Pinned Dependencies** - All GitHub Actions pinned to specific SHA hashes -- **๐Ÿ“„ SBOM Generation** - Software Bill of Materials for transparency -- **๐Ÿ” Build Attestations** - Cryptographic proof of build integrity -- **๐Ÿ† Artifact Verification** - SLSA-compliant build provenance -- **๐Ÿ•ท๏ธ ZAP Security Scanning** - OWASP ZAP dynamic application security testing -- **โšก Lighthouse Performance** - Automated performance and accessibility audits - -## Features - -- โšก **Vite** - Fast build tool and dev server -- โš›๏ธ **React 19** - Modern React with hooks -- ๐Ÿ”ท **TypeScript** - Strict typing with latest standards -- ๐Ÿงช **Vitest** - Fast unit testing with coverage -- ๐ŸŒฒ **Cypress** - Reliable E2E testing -- ๐Ÿ“ฆ **ESLint** - Code linting with TypeScript rules -- ๐Ÿ”„ **GitHub Actions** - Automated testing and reporting -- ๐ŸŽฎ **PixiJS 8.x** - High-performance WebGL renderer for 2D games -- ๐ŸŽต **Howler.js** - Audio library for games - -## Development Environment - -This template includes a fully configured development environment: - -- **๐Ÿš€ GitHub Codespaces** - Zero-configuration development environment -- **๐Ÿค– GitHub Copilot** - AI-assisted development with code suggestions -- **๐Ÿ’ฌ Copilot Chat** - In-editor AI assistance for debugging and explanations -- **๐Ÿ”ง VS Code Extensions** - Pre-configured extensions for game development -- **๐Ÿ”’ Secure Container** - Hardened development container with security features - -### ๐Ÿš€ Codespaces Setup - -This repository is fully configured for GitHub Codespaces, providing: - -- **One-click setup** - Start coding immediately with zero configuration -- **Pre-installed dependencies** - All tools and libraries ready to use -- **Configured test environment** - Cypress and Vitest ready to run -- **GitHub Copilot integration** - AI-powered code assistance -- **Optimized performance** - Container configured for game development - -```mermaid -graph LR - A[Developer] -->|Opens in Codespace| B[Container Setup] - B -->|Auto-configures| C[Development Environment] - C -->|Provides| D[VS Code + Extensions] - C -->|Initializes| E[Node.js Environment] - C -->|Configures| F[Testing Tools] - - D -->|Includes| G[GitHub Copilot] - D -->|Includes| H[ESLint Integration] - D -->|Includes| I[Debug Tools] - - E -->|Installs| J[PixiJS 8.x] - E -->|Installs| K[React 19] - E -->|Installs| L[TypeScript] - - F -->|Prepares| M[Cypress E2E] - F -->|Prepares| N[Vitest Unit Tests] - - G -->|Assists with| O[Game Logic] - G -->|Suggests| P[Game Components] - - classDef primary fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#000 - classDef tools fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px,color:#000 - classDef ai fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000 - classDef testing fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#000 - - class A,B,C primary - class D,E,F tools - class G,O,P ai - class M,N testing - class J,K,L tools - class H,I tools -``` +## ๐ŸŽฎ Game Controls -## Security Workflows - -```mermaid -graph TD - A[๐Ÿ”’ Code Push/PR] --> B{๐Ÿ›ก๏ธ Security Gates} - - B --> |๐Ÿ” Code Analysis| C[CodeQL Scanning] - B --> |๐Ÿ“ฆ Dependencies| D[Dependency Review] - B --> |๐Ÿ—๏ธ Supply Chain| E[OSSF Scorecard] - - C --> |๐Ÿšจ Vulnerabilities| F[Security Alerts] - D --> |โš ๏ธ Known CVEs| F - E --> |๐Ÿ“Š Security Score| G[Security Dashboard] - - F --> H[๐Ÿšซ Block Merge] - G --> I[โœ… Security Badge] - - subgraph "๐Ÿ” Protection Layers" - J[Runner Hardening] - K[Pinned Actions] - L[Audit Logging] - end - - subgraph "๐Ÿงช Runtime Security Testing" - M[๐Ÿ•ท๏ธ ZAP DAST Scan] - N[โšก Lighthouse Audit] - O[๐ŸŒ Live Site Testing] - end - - I --> M - M --> |๐Ÿ” Dynamic Scan| N - N --> |๐Ÿ“Š Performance Report| O - - %% Styling - classDef security fill:#ffebee,stroke:#c62828,stroke-width:2px,color:#000 - classDef analysis fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px,color:#000 - classDef protection fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#000 - classDef alert fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,color:#000 - classDef runtime fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000 - - class A,B,H security - class C,D,E analysis - class J,K,L protection - class F,G,I alert - class M,N,O runtime -``` +### โŒจ๏ธ Keyboard Controls -## Test & Report Workflow +- **๐Ÿƒ Movement**: `WASD` or `Arrow Keys` +- **โš”๏ธ Trigram Techniques**: `1-8` (Number keys) +- **๐Ÿ›ก๏ธ Block**: `Spacebar` +- **๐ŸŽฏ Quick Attack**: `Mouse Click` -```mermaid -graph TD - A[๐Ÿš€ Code Push/PR] --> B{๐Ÿ” Prepare Environment} +### ๐ŸŽจ Visual Feedback - B --> |โœ… Dependencies| C[๐Ÿ—๏ธ Build Validation] - B --> |โœ… Cypress Cache| D[๐Ÿงช Unit Tests] - B --> |โœ… Display Setup| E[๐ŸŒ E2E Tests] +- **๐Ÿ’ฅ Hit Effects**: Dynamic particle explosions +- **๐Ÿ“บ Screen Shake**: Impact-based camera movement +- **โšก Lightning**: Critical hit visual effects +- **๐Ÿฎ Logo Appearance**: Dark Trigram logo on vital strikes - C --> |โœ… Build Success| F{๐Ÿ“Š Parallel Testing} +--- - F --> D - F --> E +## ๐ŸŽญ Enhanced Game Modes - D --> |๐Ÿ“ˆ Coverage Report| G[๐Ÿ“‹ Test Reports] - E --> |๐ŸŽฌ Videos & Screenshots| G +### ๐Ÿฏ ์ˆ˜๋ จ ๋ชจ๋“œ (Suryeon Mode) - Training - G --> H[๐Ÿ“ค Artifact Upload] - H --> I[โœจ Combined Reports] +- **๐Ÿ“š Vital Point Study** - Interactive anatomy lessons +- **โšก Trigram Practice** - Master visual effect combos +- **๐ŸŽฏ Precision Training** - Enhanced targeting system +- **๐Ÿง˜ Philosophy Study** - Learn I Ching principles - %% Styling - classDef startEnd fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#000 - classDef process fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#000 - classDef test fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px,color:#000 - classDef report fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000 - classDef artifact fill:#fce4ec,stroke:#880e4f,stroke-width:2px,color:#000 +### โš”๏ธ ๋Œ€๋ จ ๋ชจ๋“œ (Daeryeon Mode) - Enhanced Sparring - class A,I startEnd - class B,C,F process - class D,E test - class G,H report - class H artifact -``` +- **๐Ÿ’ฅ Visual Combat** - Spectacular effect-driven fights +- **๐Ÿ† Tournament** - Progressive visual complexity +- **๐Ÿ‘‘ Master Challenges** - Epic boss encounters with unique effects -## Quick Start +### ๐ŸŒ™ ๊ธ‰์†Œ๊ฒฉ ๋ชจ๋“œ (Geupsogyeok Mode) - Vital Strike Training -```bash -# Using GitHub Codespaces -# Click "Code" button on repository and select "Open with Codespaces" +- **๐ŸŽฏ Precision Targeting** - Learn exact vital point locations +- **๐Ÿ’€ One-Shot Challenges** - Perfect accuracy required +- **๐ŸŽจ Effect Mastery** - Unlock new visual effects -# Or local development: -# Install dependencies -npm install +--- -# Start development server -npm run dev +## ๐Ÿ”ง Development Features -# Build for production -npm run build +### ๐ŸŽจ Enhanced Asset Pipeline -# Run unit tests -npm run test +```typescript +// Auto-loading textures with caching +const { texture, loading, error } = useTexture("/dark-trigram-256.png"); -# Run E2E tests -npm run test:e2e +// Preload game assets +await preloadGameTextures(); ``` -## PixiJS 8.x Integration - -This template uses PixiJS 8.x for high-performance 2D game rendering: - -- Modern WebGL-based rendering -- Optimized sprite batching -- Integrated with React via @pixi/react -- Sound support via @pixi/sound and Howler.js -- Responsive game canvas -- Touch and mouse input handling - -Example game component: - -```tsx -import { Stage, Sprite, useTick } from "@pixi/react"; -import { useState } from "react"; - -export function Game() { - const [position, setPosition] = useState({ x: 100, y: 100 }); - - useTick((delta) => { - // Game logic here - setPosition((prev) => ({ - x: prev.x + delta, - y: prev.y, - })); - }); - - return ( - - - - ); -} -``` +### ๐Ÿ’ฅ Particle Effect System -## Testing - -### Unit Tests - -- Uses Vitest with jsdom environment -- Configured for React Testing Library -- Coverage reports generated automatically -- Run with: `npm run test` - -### E2E Tests - -- Uses Cypress for end-to-end testing -- Starts dev server automatically -- Screenshots and videos on failure -- Run with: `npm run test:e2e` - -### CI/CD Pipeline - -```mermaid -flowchart LR - subgraph "๐Ÿ”ง CI Pipeline" - A1[๐Ÿ“ Code Changes] --> A2[๐Ÿ” Lint & Type Check] - A2 --> A3[๐Ÿ—๏ธ Build] - A3 --> A4[๐Ÿงช Test] - A4 --> A5[๐Ÿ“Š Report] - end - - subgraph "๐Ÿ”’ Security Pipeline" - S1[๐Ÿ›ก๏ธ CodeQL Analysis] - S2[๐Ÿ“ฆ Dependency Review] - S3[๐Ÿ† OSSF Scorecard] - S4[๐Ÿ” Runner Hardening] - end - - subgraph "๐Ÿ“ˆ Test Coverage" - B1[Unit Tests
80%+ Coverage] - B2[E2E Tests
Critical Flows] - B3[Type Safety
Strict Mode] - end - - subgraph "๐ŸŽฏ Outputs" - C1[๐Ÿ“„ Coverage Reports] - C2[๐ŸŽฌ Test Videos] - C3[๐Ÿ“ธ Screenshots] - C4[๐Ÿ“‹ JUnit XML] - C5[๐Ÿ›ก๏ธ Security Reports] - end - - A4 --> B1 - A4 --> B2 - A4 --> B3 - - A1 --> S1 - A1 --> S2 - A1 --> S3 - A1 --> S4 - - A5 --> C1 - A5 --> C2 - A5 --> C3 - A5 --> C4 - S1 --> C5 - S2 --> C5 - S3 --> C5 - - %% Styling - classDef pipeline fill:#e3f2fd,stroke:#1565c0,stroke-width:2px - classDef security fill:#ffebee,stroke:#c62828,stroke-width:2px - classDef testing fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px - classDef output fill:#fff8e1,stroke:#f57c00,stroke-width:2px - - class A1,A2,A3,A4,A5 pipeline - class S1,S2,S3,S4 security - class B1,B2,B3 testing - class C1,C2,C3,C4,C5 output +```typescript +// Generate hit particles with Korean aesthetics +const particles = generateHitParticles({ + count: isVitalHit ? 25 : 12, + colors: [CYAN, ORANGE, LIGHTNING_YELLOW], + trigram: effect.trigram, +}); ``` -### Security Workflows - -- **CodeQL Analysis**: Automated vulnerability scanning on push/PR -- **Dependency Review**: Checks for known vulnerabilities in dependencies -- **OSSF Scorecard**: Supply chain security assessment with public scoring -- **Runner Hardening**: All CI/CD runners use hardened security policies - -## ๐Ÿš€ Release Management - -This template includes a comprehensive, security-first release workflow with automated versioning, security attestations, and deployment. - -### Release Flow - -```mermaid -flowchart TD - A[๐Ÿš€ Release Trigger] --> B{๐Ÿ“‹ Release Type} - - B -->|๐Ÿท๏ธ Tag Push| C[๐Ÿ”„ Automatic Release] - B -->|โšก Manual Dispatch| D[๐Ÿ“ Manual Release] - - C --> E[๐Ÿ“ฆ Prepare Phase] - D --> E - - E --> F[๐Ÿ—๏ธ Build & Test] - F --> G[๐Ÿ”’ Security Validation] - - G --> H[๐Ÿ“„ Generate SBOM] - H --> I[๐Ÿ” Create Attestations] - I --> J[๐Ÿ“‹ Draft Release Notes] - - J --> K[๐ŸŒ Deploy to Pages] - K --> L[๐Ÿ“ข Publish Release] - - subgraph "๐Ÿ”’ Security Layers" - M[SLSA Build Provenance] - N[SBOM Attestation] - O[Artifact Signing] - P[Supply Chain Verification] - end +### ๐Ÿ“บ Screen Effects - I --> M - I --> N - I --> O - G --> P - - %% Styling - classDef trigger fill:#e1f5fe,stroke:#01579b,stroke-width:2px - classDef process fill:#f3e5f5,stroke:#4a148c,stroke-width:2px - classDef security fill:#ffebee,stroke:#c62828,stroke-width:2px - classDef deploy fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px - - class A,B,C,D trigger - class E,F,J,K,L process - class G,H,I,M,N,O,P security +```typescript +// Dynamic screen shake based on impact +generateScreenShake({ + intensity: isVitalHit ? 15 : damage / 2, + duration: 300, +}); ``` -### ๐Ÿท๏ธ Release Types - -#### Automatic Releases (Tag-based) - -```bash -# Create and push a tag to trigger automatic release -git tag v1.0.0 -git push origin v1.0.0 -``` - -#### Manual Releases (Workflow Dispatch) - -- Navigate to **Actions** โ†’ **Build, Attest and Release** -- Click **Run workflow** -- Specify version (e.g., `v1.0.1`) and pre-release status -- The workflow handles version bumping and tagging automatically - -### ๐Ÿ“‹ Automated Release Notes - -Release notes are automatically generated using semantic labeling: - -```mermaid -graph LR - A[๐Ÿ”„ PR Labels] --> B[๐Ÿ“ Release Drafter] - B --> C[๐Ÿ“Š Categorized Notes] +--- - subgraph "๐Ÿท๏ธ Label Categories" - D[๐Ÿš€ New Features] - E[๐ŸŽฎ Game Development] - F[๐Ÿ”’ Security & Compliance] - G[๐Ÿ› Bug Fixes] - H[๐Ÿ“ฆ Dependencies] - I[๐Ÿงช Test Coverage] - end +## ๐Ÿš€ Quick Start - A --> D - A --> E - A --> F - A --> G - A --> H - A --> I +### ๐ŸŒ Play Online - C --> J[๐Ÿ“ข GitHub Release] +**[๐ŸŽฎ Play Black Trigram Now](https://hack23.github.io/blacktrigram/)** - classDef labels fill:#fff3e0,stroke:#e65100,stroke-width:2px - classDef process fill:#e3f2fd,stroke:#1565c0,stroke-width:2px - classDef output fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px +### ๐Ÿ”ง Local Development - class D,E,F,G,H,I labels - class A,B,C process - class J output -``` +```bash +# Clone repository +git clone https://github.com/Hack23/blacktrigram.git +cd blacktrigram -#### Release Note Categories - -- **๐Ÿš€ New Features** - Major feature additions -- **๐ŸŽฎ Game Development** - Game logic, graphics, audio improvements -- **๐ŸŽจ UI/UX Improvements** - Interface and design updates -- **๐Ÿ—๏ธ Infrastructure & Performance** - Build and performance optimizations -- **๐Ÿ”„ Code Quality & Refactoring** - Code improvements and testing -- **๐Ÿ”’ Security & Compliance** - Security updates and fixes -- **๐Ÿ“ Documentation** - Documentation improvements -- **๐Ÿ“ฆ Dependencies** - Dependency updates -- **๐Ÿ› Bug Fixes** - Bug fixes and patches - -### ๐Ÿ”’ Security Attestations & SBOM - -#### Software Bill of Materials (SBOM) - -Every release includes a comprehensive SBOM in SPDX format: - -```json -{ - "SPDXID": "SPDXRef-DOCUMENT", - "name": "game-v1.0.0", - "packages": [ - { - "SPDXID": "SPDXRef-Package-react", - "name": "react", - "versionInfo": "19.1.0", - "licenseConcluded": "MIT" - } - ] -} -``` +# Install dependencies +npm install -#### Build Provenance Attestations - -SLSA-compliant build attestations provide cryptographic proof: - -```json -{ - "_type": "https://in-toto.io/Statement/v0.1", - "predicateType": "https://slsa.dev/provenance/v0.2", - "subject": [ - { - "name": "game-v1.0.0.zip", - "digest": { - "sha256": "abc123..." - } - } - ], - "predicate": { - "builder": { - "id": "https://github.com/actions/runner" - }, - "buildType": "https://github.com/actions/workflow@v1" - } -} -``` +# Start development server with hot reload +npm run dev -#### Verification Commands +# Build for production with optimizations +npm run build -```bash -# Verify build provenance -gh attestation verify game-v1.0.0.zip \ - --owner Hack23 --repo game - -# Verify SBOM attestation -gh attestation verify game-v1.0.0.zip \ - --owner Hack23 --repo game \ - --predicate-type https://spdx.dev/Document +# Run comprehensive test suite +npm run test +npm run test:e2e ``` -### ๐Ÿ“ฆ Release Artifacts +### ๐Ÿณ GitHub Codespaces -Each release includes multiple artifacts with full traceability: - -``` -๐Ÿ“ฆ Release v1.0.0 -โ”œโ”€โ”€ ๐ŸŽฎ game-v1.0.0.zip # Built application -โ”œโ”€โ”€ ๐Ÿ“„ game-v1.0.0.spdx.json # Software Bill of Materials -โ”œโ”€โ”€ ๐Ÿ” game-v1.0.0.zip.intoto.jsonl # Build provenance attestation -โ””โ”€โ”€ ๐Ÿ“‹ game-v1.0.0.spdx.json.intoto.jsonl # SBOM attestation -``` +1. Click **"Use this template"** or fork the repository +2. Click **"Code"** โ†’ **"Create codespace on main"** +3. Wait for automatic setup (2-3 minutes) +4. Run `npm run dev` and start coding! -### ๐ŸŒ Deployment Pipeline +--- -```mermaid -sequenceDiagram - participant Dev as ๐Ÿ‘จโ€๐Ÿ’ป Developer - participant GH as ๐Ÿ™ GitHub - participant CI as ๐Ÿ”„ CI/CD - participant Sec as ๐Ÿ”’ Security - participant Pages as ๐ŸŒ GitHub Pages +## ๐ŸŽฏ Game Philosophy - Dev->>GH: ๐Ÿท๏ธ Push Tag/Manual Trigger - GH->>CI: ๐Ÿš€ Start Release Workflow +> **"ํ‘๊ด˜์—์„œ ๋ฌด์˜ˆ๋Š” ๋‹จ์ˆœํ•œ ์‹ธ์›€์ด ์•„๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ์šฐ์ฃผ์˜ ์กฐํ™”๋ฅผ ๋ชธ์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ์˜ˆ์ˆ ์ด๋‹ค."** +> +> _"In Black Trigram, martial arts are not mere fighting. They are the art of expressing cosmic harmony through the body."_ - CI->>CI: ๐Ÿงช Run Tests & Build - CI->>Sec: ๐Ÿ” Security Scans - Sec-->>CI: โœ… Security Validated +### ๐Ÿ”ฎ The Eight Paths (ํŒ”๊ด˜๋„) - CI->>Sec: ๐Ÿ“„ Generate SBOM - CI->>Sec: ๐Ÿ” Create Attestations - Sec-->>CI: ๐Ÿ“‹ Security Artifacts Ready +Each trigram represents not just a fighting style, but a complete philosophy: - CI->>GH: ๐Ÿ“ Draft Release Notes - CI->>GH: ๐Ÿ“ฆ Upload Artifacts +- **โ˜ฐ Heaven (๊ฑด)** - Leadership through overwhelming force +- **โ˜ฑ Lake (ํƒœ)** - Joy in fluid, adaptive combat +- **โ˜ฒ Fire (๋ฆฌ)** - Clarity of purpose in every strike +- **โ˜ณ Thunder (์ง„)** - Decisive action without hesitation +- **โ˜ด Wind (์†)** - Persistent pressure that wears down mountains +- **โ˜ต Water (๊ฐ)** - Flowing around obstacles to find weakness +- **โ˜ถ Mountain (๊ฐ„)** - Immovable defense, perfect timing +- **โ˜ท Earth (๊ณค)** - Receptive power that grounds opponents - CI->>Pages: ๐ŸŒ Deploy Application - Pages-->>CI: โœ… Deployment Success +--- - CI->>GH: ๐Ÿ“ข Publish Release - GH-->>Dev: ๐ŸŽ‰ Release Complete -``` +## ๐Ÿ† Achievements & Mastery -### ๐Ÿ” Security Compliance +### ๐ŸŽจ Visual Effect Mastery -#### OSSF Scorecard Integration +- **๐Ÿ’ซ Particle Master** - Execute 100 perfect vital strikes +- **โšก Lightning Bringer** - Land 50 critical hits in a row +- **๐ŸŒŸ Trigram Sage** - Master all 8 visual effect styles +- **๐Ÿ‘‘ Dark Trigram Legend** - Achieve flawless victory with logo appearance -- **Automated scoring** of supply chain security practices -- **Public transparency** with security badge -- **Continuous monitoring** of security posture +### ๐ŸŽฏ Combat Excellence -#### Supply Chain Protection +- **๐ŸŽช Vital Point Scholar** - Identify all 70 pressure points +- **๐Ÿฅท Shadow Strike** - Win without taking damage +- **๐Ÿ”ฅ Combo Master** - Chain 20+ techniques seamlessly +- **๐Ÿฎ Philosophy Student** - Complete I Ching study mode -- **Pinned dependencies** - All GitHub Actions pinned to SHA hashes -- **Dependency scanning** - Automated vulnerability detection -- **SLSA compliance** - Build integrity and provenance -- **Signed artifacts** - Cryptographic verification of releases +--- -### ๐Ÿ“Š Release Metrics +
-Track release quality and security with built-in metrics: +## ๐ŸŒŸ Ready to Master the Dark Arts? -- **๐Ÿ”’ Security Score** - OSSF Scorecard rating -- **๐Ÿ“ˆ Test Coverage** - Unit and E2E test coverage -- **๐Ÿท๏ธ Vulnerability Count** - Known security issues -- **๐Ÿ“ฆ Dependency Health** - Outdated/vulnerable dependencies -- **๐Ÿš€ Build Success Rate** - CI/CD pipeline reliability +**[๐ŸŽฎ Begin Your Journey](https://hack23.github.io/blacktrigram/)** -## Building Your Game +_Experience the harmony of body, mind, and spectacular visual effects_ -This template provides a **secure foundation** for game development: +--- -1. Replace the counter example with your game logic -2. Add game-specific components in `src/components/` -3. Create game state management (Context API, Zustand, etc.) -4. Add unit tests for game logic -5. Create E2E tests for game flows -6. **Create releases** using the automated workflow -7. **Monitor security** through OSSF Scorecard and attestations -8. Deploy using the included **security-hardened** GitHub Actions +### Built with โค๏ธ and โšก by the Dark Trigram Team -All security workflows will automatically protect your game from common vulnerabilities and supply chain attacks, while providing full transparency through SBOM and attestations. +**๐Ÿฅ‹ ๋ฌด์˜ˆ์˜ ๊ธธ์„ ๊ฑธ์–ด๋ผ (Walk the Path of Martial Arts) ๐Ÿฅ‹** -Happy gaming! ๐ŸŽฎ๐Ÿ”’ +
diff --git a/cypress/screenshots/game-flow.cy.ts/Black Trigram - Complete Game Flow E2E -- Error Recovery -- should recover from rapid key combinations (failed).png b/cypress/screenshots/game-flow.cy.ts/Black Trigram - Complete Game Flow E2E -- Error Recovery -- should recover from rapid key combinations (failed).png new file mode 100644 index 00000000000..afb4b358dbe Binary files /dev/null and b/cypress/screenshots/game-flow.cy.ts/Black Trigram - Complete Game Flow E2E -- Error Recovery -- should recover from rapid key combinations (failed).png differ diff --git a/cypress/screenshots/game-flow.cy.ts/Black Trigram - Complete Game Flow E2E -- should recover from rapid key combinations -- before each hook (failed) (attempt 2).png b/cypress/screenshots/game-flow.cy.ts/Black Trigram - Complete Game Flow E2E -- should recover from rapid key combinations -- before each hook (failed) (attempt 2).png new file mode 100644 index 00000000000..904802a7b1e Binary files /dev/null and b/cypress/screenshots/game-flow.cy.ts/Black Trigram - Complete Game Flow E2E -- should recover from rapid key combinations -- before each hook (failed) (attempt 2).png differ diff --git a/cypress/videos/app.cy.ts.mp4 b/cypress/videos/app.cy.ts.mp4 new file mode 100644 index 00000000000..74c41f80382 Binary files /dev/null and b/cypress/videos/app.cy.ts.mp4 differ diff --git a/cypress/videos/game-flow.cy.ts.mp4 b/cypress/videos/game-flow.cy.ts.mp4 new file mode 100644 index 00000000000..d2f461e9cee Binary files /dev/null and b/cypress/videos/game-flow.cy.ts.mp4 differ diff --git a/index.html b/index.html index ea50d2bfe67..38c83c1c68f 100644 --- a/index.html +++ b/index.html @@ -10,17 +10,17 @@ content="Master the dark arts of vital point striking through eight distinct trigram fighting styles in this precision combat experience." /> - + - + { Container: expect.any(Function), Graphics: expect.any(Function), Text: expect.any(Function), + Sprite: expect.any(Function), }); }); }); diff --git a/src/App.tsx b/src/App.tsx index 0a257323760..5b564750a06 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,18 @@ import { Application, extend } from "@pixi/react"; -import { Container, Graphics, Text } from "pixi.js"; +import { Container, Graphics, Text, Sprite } from "pixi.js"; import { useState, useEffect, useCallback } from "react"; import type { JSX } from "react"; import "./App.css"; import { GameEngine } from "./components/game/GameEngine"; import { useAudio } from "./audio/AudioManager"; +import { useTexture } from "./hooks/useTexture"; // Extend @pixi/react with the Pixi components we want to use extend({ Container, Graphics, Text, + Sprite, }); type GameMode = "intro" | "game" | "training"; @@ -91,6 +93,8 @@ function IntroScreen({ "sparring" ); const audio = useAudio(); + const [initialized, setInitialized] = useState(false); + const { texture: logoTexture } = useTexture("/dark-trigram-256.png"); // Enhanced trigram positioning with combat style icons const trigrams: TrigramSymbol[] = [ @@ -188,12 +192,23 @@ function IntroScreen({ const interval = setInterval(() => { setTime((prev) => prev + 0.016); // ~60fps }, 16); + + // Initialize controls state to prevent first-time issues + if (!initialized) { + setInitialized(true); + } + return () => clearInterval(interval); - }, []); + }, [initialized]); - // Keyboard controls for intro screen + // Keyboard controls for intro screen with improved initialization useEffect(() => { + if (!initialized) return; + const handleKeyDown = (event: KeyboardEvent): void => { + // Ensure control signals are properly triggered + event.preventDefault(); + // Navigation controls if (event.code === "ArrowLeft" || event.code === "KeyA") { audio.playSFX("menu_hover"); @@ -206,7 +221,7 @@ function IntroScreen({ // Action controls if (event.code === "Space" || event.code === "Enter") { - event.preventDefault(); + audio.playSFX("menu_select"); if (selectedOption === "sparring") { onStartGame(); } else { @@ -216,31 +231,31 @@ function IntroScreen({ // Alternative keys if (event.code === "AltLeft" || event.code === "AltRight") { - event.preventDefault(); + audio.playSFX("menu_select"); onStartTraining(); } // Quick start with number keys if (event.code === "Digit1") { + audio.playSFX("menu_select"); onStartGame(); } if (event.code === "Digit2") { + audio.playSFX("menu_select"); onStartTraining(); } }; - const handleKeyUp = (): void => { - // Key up handler - no state tracking needed for intro screen - }; - window.addEventListener("keydown", handleKeyDown); - window.addEventListener("keyup", handleKeyUp); + + // Ensure app has focus to receive key events + window.focus(); + document.body.click(); return () => { window.removeEventListener("keydown", handleKeyDown); - window.removeEventListener("keyup", handleKeyUp); }; - }, [selectedOption, onStartGame, onStartTraining, audio]); + }, [selectedOption, onStartGame, onStartTraining, audio, initialized]); const drawBackground = useCallback((graphics: Graphics) => { graphics.clear(); @@ -249,8 +264,8 @@ function IntroScreen({ graphics.rect(0, 0, window.innerWidth, window.innerHeight); graphics.fill(); - // Subtle grid pattern for traditional Korean aesthetic - graphics.setStrokeStyle({ color: 0x111111, width: 1, alpha: 0.3 }); + // Cyan grid pattern + graphics.setStrokeStyle({ color: 0x003333, width: 1, alpha: 0.3 }); const gridSize = 50; for (let x = 0; x < window.innerWidth; x += gridSize) { graphics.moveTo(x, 0); @@ -268,31 +283,21 @@ function IntroScreen({ (graphics: Graphics) => { graphics.clear(); - // Outer ring - traditional Korean red (๋‹จ์ƒ‰) - graphics.setStrokeStyle({ color: 0x8b0000, width: 4 }); + // Cyan outer ring + graphics.setStrokeStyle({ color: 0x00ffd0, width: 4 }); graphics.circle(0, 0, 140); graphics.stroke(); - // Middle ring - white for balance - graphics.setStrokeStyle({ color: 0xffffff, width: 2 }); + // Inner cyan ring + graphics.setStrokeStyle({ color: 0x00ffd0, width: 2, alpha: 0.7 }); graphics.circle(0, 0, 120); graphics.stroke(); - // Inner ring - darker red - graphics.setStrokeStyle({ color: 0x660000, width: 2 }); - graphics.circle(0, 0, 100); - graphics.stroke(); - - // Center yin-yang inspired circle - pulsing + // Pulsing cyan center const pulse = Math.sin(time * 1.5) * 0.4 + 0.6; - graphics.setFillStyle({ color: 0x8b0000, alpha: pulse }); + graphics.setFillStyle({ color: 0x00ffd0, alpha: pulse }); graphics.circle(0, 0, 12); graphics.fill(); - - // Small white dot for balance - graphics.setFillStyle({ color: 0xffffff, alpha: pulse }); - graphics.circle(3, 0, 3); - graphics.fill(); }, [time] ); @@ -342,45 +347,61 @@ function IntroScreen({ - {/* Enhanced main title with iconic elements */} + {/* Dark Trigram Logo */} { g.clear(); - // Title background with traditional design - g.setFillStyle({ color: 0x000000, alpha: 0.8 }); - g.roundRect(-250, -50, 500, 100, 20); + // Dark background with deeper panel + g.setFillStyle({ color: 0x000a12, alpha: 0.95 }); + g.roundRect(-270, -70, 540, 140, 20); g.fill(); - // Golden border - g.setStrokeStyle({ color: 0xffd700, width: 3 }); - g.roundRect(-250, -50, 500, 100, 20); + // Cyan glowing border with double-line effect + g.setStrokeStyle({ color: 0x00ffd0, width: 3 }); + g.roundRect(-270, -70, 540, 140, 20); g.stroke(); - }} - /> - + {logoTexture && ( + + )} + @@ -575,7 +596,7 @@ function IntroScreen({ {/* Enhanced game mode selection with better visual feedback */} @@ -766,6 +830,7 @@ function TrainingMode(): JSX.Element { } function BackButton({ onBack }: { onBack: () => void }): JSX.Element { + const [isHovered, setIsHovered] = useState(false); const audio = useAudio(); return ( @@ -778,23 +843,52 @@ function BackButton({ onBack }: { onBack: () => void }): JSX.Element { audio.playSFX("menu_back"); onBack(); }} - onPointerEnter={() => audio.playSFX("menu_hover")} + onPointerEnter={() => { + audio.playSFX("menu_hover"); + setIsHovered(true); + }} + onPointerLeave={() => setIsHovered(false)} > { g.clear(); - g.setFillStyle({ color: 0x666666 }); - g.roundRect(-30, -15, 60, 30, 5); + // Cyberpunk back button + g.setFillStyle({ color: 0x000a12, alpha: 0.9 }); + g.roundRect(-40, -20, 80, 40, 5); g.fill(); + + // Border + g.setStrokeStyle({ + color: isHovered ? 0x00ffd0 : 0x004455, + width: isHovered ? 2 : 1, + alpha: isHovered ? 0.9 : 0.7, + }); + g.roundRect(-40, -20, 80, 40, 5); + g.stroke(); + + // Tech accent lines + g.setStrokeStyle({ color: 0x00ffd0, width: 1, alpha: 0.4 }); + g.moveTo(-40, -10); + g.lineTo(-30, -20); + g.moveTo(40, -10); + g.lineTo(30, -20); + g.stroke(); }} /> diff --git a/src/Game.css b/src/Game.css new file mode 100644 index 00000000000..9c42fe50b27 --- /dev/null +++ b/src/Game.css @@ -0,0 +1,121 @@ +/* Game-specific styling for Dark Trigram */ + +/* Focus outline removal for better game aesthetics */ +canvas:focus { + outline: none !important; +} + +/* Loading message styling - cyberpunk theme */ +.loading-screen { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #000; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 1000; + color: #00ffd0; + font-family: "Orbitron", "Noto Sans KR", monospace; +} + +.loading-logo { + width: 150px; + height: 150px; + margin-bottom: 20px; + animation: pulse 2s infinite; +} + +.loading-text { + font-size: 18px; + letter-spacing: 2px; + margin-top: 20px; + text-transform: uppercase; +} + +.loading-spinner { + width: 40px; + height: 40px; + border: 4px solid rgba(0, 255, 208, 0.3); + border-top: 4px solid #00ffd0; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes pulse { + 0% { + opacity: 0.6; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.6; + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* Vital point targeting effect */ +.vital-point { + position: absolute; + width: 20px; + height: 20px; + border: 1px solid #00ffd0; + transform: translate(-50%, -50%); + opacity: 0.7; + animation: target-pulse 2s infinite; +} + +.vital-point::before, +.vital-point::after { + content: ""; + position: absolute; + background-color: #00ffd0; +} + +.vital-point::before { + top: 50%; + left: -5px; + right: -5px; + height: 1px; + transform: translateY(-50%); +} + +.vital-point::after { + left: 50%; + top: -5px; + bottom: -5px; + width: 1px; + transform: translateX(-50%); +} + +@keyframes target-pulse { + 0% { + box-shadow: 0 0 0 0 rgba(0, 255, 208, 0.4); + } + 70% { + box-shadow: 0 0 0 10px rgba(0, 255, 208, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(0, 255, 208, 0); + } +} + +/* Neon text effect for Korean text */ +.neon-text { + color: #fff; + text-shadow: 0 0 5px #00ffd0, 0 0 10px #00ffd0, 0 0 20px #00ffd0, + 0 0 40px #00ffd0; + font-weight: bold; +} diff --git a/src/components/game/DojangBackground.tsx b/src/components/game/DojangBackground.tsx index 97a81d3a1b0..a54d748516e 100644 --- a/src/components/game/DojangBackground.tsx +++ b/src/components/game/DojangBackground.tsx @@ -1,269 +1,279 @@ +import { useState, useEffect, useCallback } from "react"; import type { JSX } from "react"; import type { Graphics as PixiGraphics } from "pixi.js"; +import { useTexture } from "../../hooks/useTexture"; + +// Dark Trigram theme constants with strict typing +const DARK_TRIGRAM_THEME = { + PRIMARY_CYAN: 0x00ffd0, + DARK_BG: 0x0a0e12, + DARKER_BG: 0x181c20, + MEDIUM_BG: 0x23272b, + VITAL_ORANGE: 0xff4400, + CRITICAL_RED: 0xff3030, + WHITE: 0xffffff, + GRID_CYAN: 0x003333, +} as const; interface DojangBackgroundProps { readonly gameTime: number; } +interface VitalPoint { + readonly id: number; + readonly x: number; + readonly y: number; + readonly damage: number; + readonly type: string; +} + +interface TrigramPosition { + readonly x: number; + readonly y: number; + readonly symbol: string; + readonly name: string; +} + export function DojangBackground({ gameTime, }: DojangBackgroundProps): JSX.Element { - const drawDojang = (graphics: PixiGraphics): void => { - graphics.clear(); - - // Enhanced traditional Korean dojang with better visual hierarchy - graphics.setFillStyle({ color: 0x000000 }); - graphics.rect(0, 0, window.innerWidth, window.innerHeight); - graphics.fill(); - - // Improved animated background particles with icon-like shapes - for (let i = 0; i < 15; i++) { - const x = - (Math.sin(gameTime * 0.01 + i) * 150 + window.innerWidth / 2) % - window.innerWidth; - const y = - (Math.cos(gameTime * 0.008 + i * 0.5) * 150 + window.innerHeight / 2) % - window.innerHeight; - const alpha = Math.sin(gameTime * 0.02 + i) * 0.08 + 0.08; - - // Create star-like particles representing martial energy - graphics.setFillStyle({ color: 0x8b0000, alpha }); - const size = 3 + Math.sin(gameTime * 0.03 + i) * 2; - graphics.circle(x, y, size); - graphics.fill(); - - // Add cross-shaped energy markers - graphics.setStrokeStyle({ - color: 0xffd700, - width: 1, - alpha: alpha * 0.6, + const [vitalPoints, setVitalPoints] = useState([]); + const { texture: logoTexture } = useTexture("/dark-trigram-256.png"); + + // Initialize vital points for Korean martial arts atmosphere + useEffect(() => { + const vitalPointTypes: readonly string[] = [ + "sternum", + "solar_plexus", + "throat", + "temples", + "pressure_points", + "nerve_clusters", + "circulation_points", + "balance_points", + ] as const; + + const newVitalPoints: VitalPoint[] = []; + + for (let i = 0; i < 12; i++) { + const randomIndex = Math.floor(Math.random() * vitalPointTypes.length); + const vitalPointType = vitalPointTypes[randomIndex] ?? "pressure_points"; + + newVitalPoints.push({ + id: i, + x: Math.random() * window.innerWidth, + y: Math.random() * window.innerHeight, + damage: Math.random() * 50 + 10, + type: vitalPointType, }); - graphics.moveTo(x - size, y); - graphics.lineTo(x + size, y); - graphics.moveTo(x, y - size); - graphics.lineTo(x, y + size); - graphics.stroke(); } - - // Enhanced traditional Korean flooring with geometric patterns - const floorHeight = window.innerHeight * 0.35; - graphics.setFillStyle({ color: 0x4a3728 }); // Darker wood tone - graphics.rect( - 0, - window.innerHeight - floorHeight, - window.innerWidth, - floorHeight - ); - graphics.fill(); - - // Enhanced wood grain pattern with Korean aesthetic and subtle animation - graphics.setStrokeStyle({ color: 0x654321, width: 1, alpha: 0.8 }); - for (let x = 0; x < window.innerWidth; x += 60) { - const wobble = Math.sin(gameTime * 0.01 + x * 0.01) * 2; - graphics.moveTo(x + wobble, window.innerHeight - floorHeight); - graphics.lineTo(x + wobble, window.innerHeight); - graphics.stroke(); - - // Add traditional Korean wood joining details with subtle glow - graphics.setFillStyle({ - color: 0x2a1f1a, - alpha: 0.6 + Math.sin(gameTime * 0.005 + x) * 0.1, - }); - graphics.rect( - x - 2 + wobble, - window.innerHeight - floorHeight, - 4, - floorHeight - ); + + setVitalPoints(newVitalPoints); + }, []); + + // Enhanced dojang drawing with Korean martial arts elements + const drawDojang = useCallback( + (graphics: PixiGraphics): void => { + graphics.clear(); + + // Deep black background + graphics.setFillStyle({ color: DARK_TRIGRAM_THEME.DARK_BG }); + graphics.rect(0, 0, window.innerWidth, window.innerHeight); graphics.fill(); - } - - // Enhanced fighting arena with iconic Korean border patterns - const arenaWidth = Math.min(700, window.innerWidth * 0.8); - const arenaHeight = Math.min(400, window.innerHeight * 0.5); - const arenaX = (window.innerWidth - arenaWidth) / 2; - const arenaY = (window.innerHeight - arenaHeight) / 2; - - // Traditional Korean taeguk-inspired border design - const borderPulse = Math.sin(gameTime * 0.02) * 0.2 + 0.8; - - // Outer ceremonial border with traditional patterns - graphics.setStrokeStyle({ color: 0x8b0000, width: 8, alpha: borderPulse }); - graphics.rect(arenaX - 50, arenaY - 40, arenaWidth + 100, arenaHeight + 80); - graphics.stroke(); - // Traditional Korean decorative corners (icon-like elements) - const cornerPositions: Array<[number, number]> = [ - [arenaX - 50, arenaY - 40], - [arenaX + arenaWidth + 50, arenaY - 40], - [arenaX - 50, arenaY + arenaHeight + 40], - [arenaX + arenaWidth + 50, arenaY + arenaHeight + 40], - ]; + // Subtle animated grid pattern + graphics.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.GRID_CYAN, + width: 1, + alpha: 0.03 + }); + + const gridSize = 40; + for (let y = 0; y < window.innerHeight; y += gridSize) { + graphics.moveTo(0, y); + graphics.lineTo(window.innerWidth, y); + graphics.stroke(); + } + + for (let x = 0; x < window.innerWidth; x += gridSize) { + graphics.moveTo(x, 0); + graphics.lineTo(x, window.innerHeight); + graphics.stroke(); + } - cornerPositions.forEach(([x, y]) => { - // Traditional Korean corner decoration (resembling ํƒœ๊ทน symbol elements) - graphics.setFillStyle({ color: 0xffd700, alpha: borderPulse * 0.8 }); - graphics.circle(x, y, 12); - graphics.fill(); + // Central octagonal dojang platform + const centerX = window.innerWidth / 2; + const centerY = window.innerHeight / 2; + const radius = Math.min(window.innerWidth, window.innerHeight) * 0.25; - graphics.setFillStyle({ color: 0x8b0000, alpha: borderPulse }); - graphics.circle(x + 3, y, 6); + // Platform base + graphics.setFillStyle({ color: DARK_TRIGRAM_THEME.DARKER_BG }); + graphics.circle(centerX, centerY, radius); graphics.fill(); - }); - - // Enhanced center yin-yang inspired design with iconic elements - const centerX = window.innerWidth / 2; - const centerY = window.innerHeight / 2; - - // Main ceremonial circle with traditional Korean colors - graphics.setStrokeStyle({ color: 0x8b0000, width: 6, alpha: borderPulse }); - graphics.circle(centerX, centerY, 80 + Math.sin(gameTime * 0.015) * 8); - graphics.stroke(); - // Secondary ring with decorative elements - graphics.setStrokeStyle({ - color: 0xffd700, - width: 3, - alpha: borderPulse * 0.9, - }); - graphics.circle(centerX, centerY, 60 + Math.cos(gameTime * 0.018) * 5); - graphics.stroke(); - - // Central energy core with pulsing effect - const corePulse = Math.sin(gameTime * 0.025) * 0.4 + 0.7; - graphics.setFillStyle({ color: 0x8b0000, alpha: corePulse }); - graphics.circle(centerX, centerY, 12 + Math.sin(gameTime * 0.03) * 3); - graphics.fill(); + // Traditional Korean octagonal platform outline + graphics.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + width: 2, + alpha: 0.6 + }); + + const octagonPoints: Array<{ x: number; y: number }> = []; + for (let i = 0; i < 8; i++) { + const angle = (i * Math.PI) / 4 - Math.PI / 8; + octagonPoints.push({ + x: centerX + Math.cos(angle) * radius, + y: centerY + Math.sin(angle) * radius, + }); + } - // Traditional Korean trigram-inspired orbital elements - for (let i = 0; i < 8; i++) { - const angle = gameTime * 0.008 + (i * Math.PI) / 4; - const orbitRadius = 100 + Math.sin(gameTime * 0.01 + i) * 15; - const orbX = centerX + Math.cos(angle) * orbitRadius; - const orbY = centerY + Math.sin(angle) * orbitRadius; + // Draw octagon outline + if (octagonPoints.length > 0) { + const firstPoint = octagonPoints[0]; + if (firstPoint) { + graphics.moveTo(firstPoint.x, firstPoint.y); + for (let i = 1; i < 8; i++) { + const point = octagonPoints[i]; + if (point) { + graphics.lineTo(point.x, point.y); + } + } + graphics.closePath(); + graphics.stroke(); + } + } - // Create trigram-line inspired orbital markers + // Pulsing center core with yin-yang inspiration + const corePulse = Math.sin(gameTime * 0.05) * 0.3 + 0.7; + + // Outer glow graphics.setStrokeStyle({ - color: 0xffd700, - width: 4, - alpha: corePulse * 0.7, + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + width: 3, + alpha: corePulse * 0.4, }); - const lineLength = 8; - graphics.moveTo(orbX - lineLength, orbY); - graphics.lineTo(orbX + lineLength, orbY); + graphics.circle(centerX, centerY, 30); graphics.stroke(); - // Add small decorative dots - graphics.setFillStyle({ color: 0xffffff, alpha: corePulse * 0.5 }); - graphics.circle(orbX, orbY - 3, 2); - graphics.circle(orbX, orbY + 3, 2); + // Inner core + graphics.setFillStyle({ + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + alpha: corePulse * 0.6 + }); + graphics.circle(centerX, centerY, 12); graphics.fill(); - } - - // Enhanced trigram symbol corners with better iconic representation - const enhancedTrigramPositions = [ - { x: 150, y: 150, symbol: "โ˜ฐ", meaning: "Heaven", color: 0xffd700 }, - { - x: window.innerWidth - 150, - y: 150, - symbol: "โ˜ฒ", - meaning: "Fire", - color: 0xff4500, - }, - { - x: 150, - y: window.innerHeight - 150, - symbol: "โ˜ท", - meaning: "Earth", - color: 0x654321, - }, - { - x: window.innerWidth - 150, - y: window.innerHeight - 150, - symbol: "โ˜ต", - meaning: "Water", - color: 0x4169e1, - }, - ]; - enhancedTrigramPositions.forEach(({ x, y, color }, index) => { - const symbolPulse = - Math.sin(gameTime * 0.02 + (index * Math.PI) / 2) * 0.3 + 0.7; - - // Enhanced trigram symbol background with iconic design - graphics.setFillStyle({ color: 0x000000, alpha: 0.8 }); - graphics.circle(x, y, 35); + // Central dot (meditation point) + graphics.setFillStyle({ + color: DARK_TRIGRAM_THEME.WHITE, + alpha: corePulse * 0.8, + }); + graphics.circle(centerX, centerY, 4); graphics.fill(); - graphics.setStrokeStyle({ color, width: 3, alpha: symbolPulse }); - graphics.circle(x, y, 35); - graphics.stroke(); + // Dynamic vital point indicators + vitalPoints.forEach((point, index) => { + const pointPulse = Math.sin(gameTime * 0.01 + index * 0.5) * 0.3 + 0.7; + const distFromCenter = Math.sqrt( + Math.pow(point.x - centerX, 2) + Math.pow(point.y - centerY, 2) + ); + + // Only show points outside the main combat area + if (distFromCenter < radius - 50) return; + + // Vital point marker + graphics.setFillStyle({ + color: DARK_TRIGRAM_THEME.VITAL_ORANGE, + alpha: pointPulse * 0.4, + }); + graphics.circle(point.x, point.y, 3 + pointPulse * 2); + graphics.fill(); - // Inner decorative ring - graphics.setStrokeStyle({ - color: 0xffd700, - width: 1, - alpha: symbolPulse * 0.6, + // Vital point ring + graphics.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.VITAL_ORANGE, + width: 1, + alpha: pointPulse * 0.3, + }); + graphics.circle(point.x, point.y, 8 + pointPulse * 3); + graphics.stroke(); }); - graphics.circle(x, y, 25); - graphics.stroke(); - // Enhanced energy wisps with more iconic movement - for (let j = 0; j < 4; j++) { - const wispAngle = gameTime * 0.01 + j * ((Math.PI * 2) / 4); - const wispRadius = 45 + Math.sin(gameTime * 0.015 + j) * 8; - const wispX = x + Math.cos(wispAngle) * wispRadius; - const wispY = y + Math.sin(wispAngle) * wispRadius; - - graphics.setFillStyle({ color, alpha: symbolPulse * 0.4 }); - graphics.circle(wispX, wispY, 3); + // Traditional Korean dojang boundary markers + const trigramPositions: readonly TrigramPosition[] = [ + { x: window.innerWidth * 0.15, y: window.innerHeight * 0.2, symbol: "โ˜ฐ", name: "Heaven" }, + { x: window.innerWidth * 0.85, y: window.innerHeight * 0.2, symbol: "โ˜ฑ", name: "Lake" }, + { x: window.innerWidth * 0.15, y: window.innerHeight * 0.8, symbol: "โ˜ฒ", name: "Fire" }, + { x: window.innerWidth * 0.85, y: window.innerHeight * 0.8, symbol: "โ˜ณ", name: "Thunder" }, + ] as const; + + trigramPositions.forEach((position, index) => { + const trigramPulse = Math.sin(gameTime * 0.008 + index * 1.5) * 0.2 + 0.8; + + // Trigram symbol background + graphics.setFillStyle({ + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + alpha: trigramPulse * 0.1, + }); + graphics.circle(position.x, position.y, 25); graphics.fill(); - } - }); - - // Enhanced atmospheric grid with better visual hierarchy - graphics.setStrokeStyle({ - color: 0x333333, - width: 1, - alpha: 0.1 + Math.sin(gameTime * 0.005) * 0.05, - }); - const gridSize = 60; - for (let x = 0; x < window.innerWidth; x += gridSize) { - const lineAlpha = 0.15 + Math.sin(gameTime * 0.003 + x * 0.01) * 0.08; - graphics.setStrokeStyle({ color: 0x444444, width: 1, alpha: lineAlpha }); - graphics.moveTo(x, 0); - graphics.lineTo(x, window.innerHeight); - graphics.stroke(); - } - for (let y = 0; y < window.innerHeight; y += gridSize) { - const lineAlpha = 0.15 + Math.sin(gameTime * 0.003 + y * 0.01) * 0.08; - graphics.setStrokeStyle({ color: 0x444444, width: 1, alpha: lineAlpha }); - graphics.moveTo(0, y); - graphics.lineTo(window.innerWidth, y); - graphics.stroke(); - } - // Enhanced floating energy motes with iconic shapes - for (let i = 0; i < 20; i++) { - const moteX = - (Math.sin(gameTime * 0.002 + i * 0.8) * window.innerWidth) / 2.5 + - window.innerWidth / 2; - const moteY = - (Math.cos(gameTime * 0.003 + i * 1.2) * window.innerHeight) / 2.5 + - window.innerHeight / 2; - const moteAlpha = Math.sin(gameTime * 0.02 + i) * 0.2 + 0.2; - const moteSize = 2 + Math.sin(gameTime * 0.025 + i * 0.4) * 1; - - // Create diamond-shaped energy motes - graphics.setFillStyle({ color: 0xffd700, alpha: moteAlpha }); - graphics.moveTo(moteX, moteY - moteSize); - graphics.lineTo(moteX + moteSize, moteY); - graphics.lineTo(moteX, moteY + moteSize); - graphics.lineTo(moteX - moteSize, moteY); - graphics.lineTo(moteX, moteY - moteSize); - graphics.fill(); - } - }; + // Trigram symbol border + graphics.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + width: 1, + alpha: trigramPulse * 0.3, + }); + graphics.circle(position.x, position.y, 25); + graphics.stroke(); + }); - return ; + // Corner accent lines for dojang atmosphere + graphics.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + width: 1, + alpha: 0.3 + }); + + // Top corners + graphics.moveTo(0, 0); + graphics.lineTo(50, 0); + graphics.moveTo(0, 0); + graphics.lineTo(0, 50); + graphics.moveTo(window.innerWidth, 0); + graphics.lineTo(window.innerWidth - 50, 0); + graphics.moveTo(window.innerWidth, 0); + graphics.lineTo(window.innerWidth, 50); + + // Bottom corners + graphics.moveTo(0, window.innerHeight); + graphics.lineTo(50, window.innerHeight); + graphics.moveTo(0, window.innerHeight); + graphics.lineTo(0, window.innerHeight - 50); + graphics.moveTo(window.innerWidth, window.innerHeight); + graphics.lineTo(window.innerWidth - 50, window.innerHeight); + graphics.moveTo(window.innerWidth, window.innerHeight); + graphics.lineTo(window.innerWidth, window.innerHeight - 50); + graphics.stroke(); + }, + [gameTime, vitalPoints] + ); + + return ( + + + + {/* Dark Trigram logo in bottom right corner */} + {logoTexture && ( + + )} + + ); } diff --git a/src/components/game/GameUI.tsx b/src/components/game/GameUI.tsx index df66187c66d..42a9e998d25 100644 --- a/src/components/game/GameUI.tsx +++ b/src/components/game/GameUI.tsx @@ -1,5 +1,7 @@ +import { useState, useEffect } from "react"; import type { JSX } from "react"; import type { Graphics as PixiGraphics } from "pixi.js"; +import { useTexture } from "../../hooks/useTexture"; interface GameState { readonly player1Health: number; @@ -25,16 +27,16 @@ const GAME_CONFIG = { ARENA_HEIGHT: 600, } as const; -const UI_THEME = { - TRADITIONAL_RED: 0x8b0000, - TRADITIONAL_GOLD: 0xffd700, - TRADITIONAL_BLACK: 0x000000, - TRADITIONAL_WHITE: 0xffffff, - KOREAN_GRAY: 0x666666, - ACCENT_BLUE: 0x4a90e2, - ENERGY_PURPLE: 0x9370db, - HEALTH_GREEN: 0x4caf50, - DANGER_RED: 0xff4444, +const DARK_TRIGRAM_UI_THEME = { + PRIMARY_CYAN: 0x00ffd0, + DARK_BG: 0x0a0e12, + DARKER_BG: 0x181c20, + MEDIUM_BG: 0x23272b, + VITAL_ORANGE: 0xff4400, + CRITICAL_RED: 0xff3030, + WHITE: 0xffffff, + GRAY: 0x666666, + HEALTH_CYAN: 0x00ffd0, WARNING_ORANGE: 0xff9800, } as const; @@ -106,13 +108,13 @@ function BackgroundOverlay(): JSX.Element { draw={(g: PixiGraphics) => { g.clear(); // Traditional Korean pattern background - g.setFillStyle({ color: UI_THEME.TRADITIONAL_BLACK, alpha: 0.7 }); + g.setFillStyle({ color: DARK_TRIGRAM_UI_THEME.DARKER_BG, alpha: 0.7 }); g.rect(0, 0, GAME_CONFIG.ARENA_WIDTH, GAME_CONFIG.ARENA_HEIGHT); g.fill(); // Subtle Korean-inspired border pattern g.setStrokeStyle({ - color: UI_THEME.TRADITIONAL_RED, + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, width: 2, alpha: 0.3, }); @@ -129,63 +131,75 @@ function BackgroundOverlay(): JSX.Element { } function GameTitle(): JSX.Element { + const { texture: logoTexture } = useTexture("/dark-trigram-256.png"); + return ( - {/* Enhanced background with iconic elements */} { g.clear(); - // Traditional Korean title background - g.setFillStyle({ color: UI_THEME.TRADITIONAL_BLACK, alpha: 0.9 }); - g.roundRect(-200, -25, 400, 80, 15); + + // Dark background with deeper panel + g.setFillStyle({ color: 0x000a12, alpha: 0.95 }); + g.roundRect(-220, -30, 440, 85, 15); g.fill(); - // Decorative border with traditional patterns - g.setStrokeStyle({ color: UI_THEME.TRADITIONAL_RED, width: 3 }); - g.roundRect(-200, -25, 400, 80, 15); + // Cyan glowing border with futuristic style + g.setStrokeStyle({ + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, + width: 3, + }); + g.roundRect(-220, -30, 440, 85, 15); g.stroke(); - // Inner golden accent - g.setStrokeStyle({ color: UI_THEME.TRADITIONAL_GOLD, width: 1 }); - g.roundRect(-195, -20, 390, 70, 12); + // Inner glow with tech accent + g.setStrokeStyle({ + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, + width: 1, + alpha: 0.7, + }); + g.roundRect(-210, -20, 420, 65, 12); g.stroke(); - // Corner decorative elements (icon-like) - const corners: Array<[number, number]> = [ - [-190, -15], - [190, -15], - [-190, 45], - [190, 45], - ]; - corners.forEach(([x, y]) => { - g.setFillStyle({ color: UI_THEME.TRADITIONAL_GOLD, alpha: 0.8 }); - g.circle(x, y, 4); - g.fill(); + // Add tech-looking diagonal slashes + g.setStrokeStyle({ + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, + width: 1, + alpha: 0.4, }); + g.moveTo(-220, 0); + g.lineTo(-190, -30); + g.moveTo(220, 0); + g.lineTo(190, -30); + g.stroke(); }} /> - {/* Enhanced title with better typography */} - + {/* Use the game logo sprite instead of text */} + {logoTexture && ( + + )} + @@ -208,90 +222,98 @@ function HealthBar({ return ( - {/* Enhanced health bar frame with icons */} { g.clear(); - // Main frame with gradient-like effect - g.setFillStyle({ color: UI_THEME.TRADITIONAL_BLACK, alpha: 0.95 }); + // Dark frame + g.setFillStyle({ color: DARK_TRIGRAM_UI_THEME.DARK_BG, alpha: 0.95 }); g.roundRect(-10, -10, 240, 80, 12); g.fill(); - // Traditional border - g.setStrokeStyle({ color: UI_THEME.TRADITIONAL_RED, width: 3 }); + // Cyan border + g.setStrokeStyle({ + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, + width: 3, + }); g.roundRect(-10, -10, 240, 80, 12); g.stroke(); - // Inner accent border - g.setStrokeStyle({ color: UI_THEME.TRADITIONAL_GOLD, width: 1 }); + // Inner glow + g.setStrokeStyle({ + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, + width: 1, + alpha: 0.7, + }); g.roundRect(-5, -5, 230, 70, 8); g.stroke(); }} /> - {/* Player label with icon */} - {/* Enhanced health bar with better visual feedback */} { g.clear(); - // Background with subtle texture - g.setFillStyle({ color: 0x2a2a2a }); + // Dark background + g.setFillStyle({ color: DARK_TRIGRAM_UI_THEME.MEDIUM_BG }); g.roundRect(10, 0, 200, 22, 11); g.fill(); // Health fill with dynamic color const healthColor = health > 60 - ? UI_THEME.HEALTH_GREEN + ? DARK_TRIGRAM_UI_THEME.HEALTH_CYAN : health > 30 - ? UI_THEME.WARNING_ORANGE - : UI_THEME.DANGER_RED; + ? DARK_TRIGRAM_UI_THEME.WARNING_ORANGE + : DARK_TRIGRAM_UI_THEME.CRITICAL_RED; g.setFillStyle({ color: healthColor }); g.roundRect(12, 2, health * 1.96, 18, 9); g.fill(); - // Health bar shine effect - g.setFillStyle({ color: UI_THEME.TRADITIONAL_WHITE, alpha: 0.3 }); - g.roundRect(12, 2, health * 1.96, 8, 4); - g.fill(); - - // Border with player-specific color - const borderColor = isPlayerOne - ? UI_THEME.ACCENT_BLUE - : UI_THEME.TRADITIONAL_RED; - g.setStrokeStyle({ color: borderColor, width: 2 }); + // Cyan glow border + g.setStrokeStyle({ + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, + width: 2, + }); g.roundRect(10, 0, 200, 22, 11); g.stroke(); }} /> - {/* Health percentage text */} @@ -309,19 +331,21 @@ function Timer({ gameState, gameTime }: TimerProps): JSX.Element { return ( - {/* Enhanced timer background */} { g.clear(); const bgColor = isUrgent - ? UI_THEME.DANGER_RED - : UI_THEME.TRADITIONAL_BLACK; + ? DARK_TRIGRAM_UI_THEME.CRITICAL_RED + : DARK_TRIGRAM_UI_THEME.DARK_BG; g.setFillStyle({ color: bgColor, alpha: 0.9 }); g.roundRect(-80, -20, 160, 40, 12); g.fill(); - g.setStrokeStyle({ color: UI_THEME.TRADITIONAL_GOLD, width: 2 }); + g.setStrokeStyle({ + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, + width: 2, + }); g.roundRect(-80, -20, 160, 40, 12); g.stroke(); }} @@ -332,13 +356,17 @@ function Timer({ gameState, gameTime }: TimerProps): JSX.Element { anchor={{ x: 0.5, y: 0.5 }} alpha={pulse} style={{ - fontFamily: "Noto Sans KR", + fontFamily: "Orbitron, Noto Sans KR", fontSize: isUrgent ? 22 : 20, fill: isUrgent - ? UI_THEME.TRADITIONAL_WHITE - : UI_THEME.TRADITIONAL_GOLD, + ? DARK_TRIGRAM_UI_THEME.WHITE + : DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, fontWeight: "bold", - stroke: { color: UI_THEME.TRADITIONAL_BLACK, width: 2 }, + dropShadow: { + color: 0x00ffd0, + blur: 6, + distance: 0, + }, }} /> @@ -374,48 +402,48 @@ function ControlLegend({ gameStarted }: { gameStarted: boolean }): JSX.Element { return ( - {/* Enhanced control panel */} { g.clear(); - // Main panel background - g.setFillStyle({ color: UI_THEME.TRADITIONAL_BLACK, alpha: 0.95 }); + // Dark panel background + g.setFillStyle({ color: DARK_TRIGRAM_UI_THEME.DARK_BG, alpha: 0.95 }); g.roundRect(0, 0, 380, 140, 15); g.fill(); - // Traditional Korean border - g.setStrokeStyle({ color: UI_THEME.TRADITIONAL_RED, width: 3 }); + // Cyan border + g.setStrokeStyle({ + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, + width: 3, + }); g.roundRect(0, 0, 380, 140, 15); g.stroke(); - // Inner decorative border - g.setStrokeStyle({ color: UI_THEME.TRADITIONAL_GOLD, width: 1 }); - g.roundRect(5, 5, 370, 130, 12); - g.stroke(); - - // Section dividers + // Inner glow g.setStrokeStyle({ - color: UI_THEME.KOREAN_GRAY, + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, width: 1, - alpha: 0.5, + alpha: 0.7, }); - g.moveTo(20, 35); - g.lineTo(360, 35); + g.roundRect(5, 5, 370, 130, 12); g.stroke(); }} /> - {/* Enhanced control labels with icons */} @@ -424,9 +452,14 @@ function ControlLegend({ gameStarted }: { gameStarted: boolean }): JSX.Element { x={20} y={45} style={{ - fontFamily: "Noto Sans KR", + fontFamily: "Orbitron, Noto Sans KR", fontSize: 13, - fill: UI_THEME.TRADITIONAL_WHITE, + fill: DARK_TRIGRAM_UI_THEME.WHITE, + dropShadow: { + color: 0x00ffd0, + blur: 2, + distance: 0, + }, }} /> @@ -435,9 +468,14 @@ function ControlLegend({ gameStarted }: { gameStarted: boolean }): JSX.Element { x={20} y={65} style={{ - fontFamily: "Noto Sans KR", + fontFamily: "Orbitron, Noto Sans KR", fontSize: 13, - fill: UI_THEME.TRADITIONAL_WHITE, + fill: DARK_TRIGRAM_UI_THEME.WHITE, + dropShadow: { + color: 0x00ffd0, + blur: 2, + distance: 0, + }, }} /> @@ -446,9 +484,14 @@ function ControlLegend({ gameStarted }: { gameStarted: boolean }): JSX.Element { x={20} y={85} style={{ - fontFamily: "Noto Sans KR", + fontFamily: "Orbitron, Noto Sans KR", fontSize: 13, - fill: UI_THEME.TRADITIONAL_WHITE, + fill: DARK_TRIGRAM_UI_THEME.WHITE, + dropShadow: { + color: 0x00ffd0, + blur: 2, + distance: 0, + }, }} /> @@ -459,7 +502,7 @@ function ControlLegend({ gameStarted }: { gameStarted: boolean }): JSX.Element { style={{ fontFamily: "Noto Sans KR", fontSize: 12, - fill: UI_THEME.WARNING_ORANGE, + fill: DARK_TRIGRAM_UI_THEME.WARNING_ORANGE, fontStyle: "italic", }} /> @@ -485,12 +528,15 @@ function TrigramDecorations(): JSX.Element { { g.clear(); - g.setFillStyle({ color: UI_THEME.TRADITIONAL_BLACK, alpha: 0.8 }); + g.setFillStyle({ + color: DARK_TRIGRAM_UI_THEME.DARKER_BG, + alpha: 0.8, + }); g.rect(0, 0, window.innerWidth, 50); g.fill(); g.setStrokeStyle({ - color: UI_THEME.TRADITIONAL_RED, + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, width: 2, alpha: 0.6, }); @@ -506,11 +552,17 @@ function TrigramDecorations(): JSX.Element { { g.clear(); - g.setFillStyle({ color: UI_THEME.TRADITIONAL_RED, alpha: 0.3 }); + g.setFillStyle({ + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, + alpha: 0.3, + }); g.circle(0, 0, 20); g.fill(); - g.setStrokeStyle({ color: UI_THEME.TRADITIONAL_GOLD, width: 1 }); + g.setStrokeStyle({ + color: DARK_TRIGRAM_UI_THEME.PRIMARY_CYAN, + width: 1, + }); g.circle(0, 0, 20); g.stroke(); }} @@ -522,7 +574,7 @@ function TrigramDecorations(): JSX.Element { style={{ fontFamily: "serif", fontSize: 14, - fill: UI_THEME.TRADITIONAL_WHITE, + fill: DARK_TRIGRAM_UI_THEME.WHITE, }} /> @@ -540,6 +592,8 @@ function StartButton({ onStartMatch, gameTime, }: StartButtonProps): JSX.Element { + const [isHovered, setIsHovered] = useState(false); + return ( setIsHovered(true)} + onPointerLeave={() => setIsHovered(false)} > { g.clear(); const pulse = Math.sin(gameTime * 0.1) * 0.3 + 0.7; - g.setFillStyle({ color: 0x8b0000, alpha: pulse }); - g.roundRect(-120, -40, 240, 80, 15); + + // Cyberpunk-style button with deeper background + g.setFillStyle({ color: 0x000a12, alpha: 0.95 }); + g.roundRect(-140, -50, 280, 100, 15); g.fill(); - g.setStrokeStyle({ color: 0xffffff, width: 3 }); - g.roundRect(-120, -40, 240, 80, 15); + + // Dynamic border + g.setStrokeStyle({ + color: isHovered ? 0x00ffd0 : 0x005566, + width: isHovered ? 3 : 2, + alpha: pulse, + }); + g.roundRect(-140, -50, 280, 100, 15); g.stroke(); + + // Inner tech accent + g.setStrokeStyle({ + color: 0x00ffd0, + width: 1, + alpha: isHovered ? 0.7 : 0.4, + }); + g.roundRect(-130, -40, 260, 80, 12); + g.stroke(); + + // Tech accent triangles on corners + g.setFillStyle({ color: 0x00ffd0, alpha: isHovered ? 0.7 : 0.4 }); + g.moveTo(-140, -30); + g.lineTo(-120, -50); + g.lineTo(-140, -50); + g.lineTo(-140, -30); + g.fill(); + + g.moveTo(140, -30); + g.lineTo(120, -50); + g.lineTo(140, -50); + g.lineTo(140, -30); + g.fill(); + + g.moveTo(-140, 30); + g.lineTo(-120, 50); + g.lineTo(-140, 50); + g.lineTo(-140, 30); + g.fill(); + + g.moveTo(140, 30); + g.lineTo(120, 50); + g.lineTo(140, 50); + g.lineTo(140, 30); + g.fill(); + + // Add pulsing glow effect when hovered + if (isHovered) { + g.setStrokeStyle({ + color: 0x00ffd0, + width: 2, + alpha: pulse * 0.5, + }); + g.roundRect(-150, -60, 300, 120, 20); + g.stroke(); + } }} /> @@ -593,6 +712,18 @@ function VictoryScreen({ winner, onResetMatch, }: VictoryScreenProps): JSX.Element { + const [isHovered, setIsHovered] = useState(false); + const [time, setTime] = useState(0); + const { texture: logoTexture } = useTexture("/dark-trigram-256.png"); + + // Add animation timer + useEffect(() => { + const interval = setInterval(() => { + setTime((t: number) => t + 0.016); + }, 16); + return () => clearInterval(interval); + }, []); + return ( + {/* Victory logo */} + {logoTexture && ( + + )} + setIsHovered(true)} + onPointerLeave={() => setIsHovered(false)} > { g.clear(); - g.setFillStyle({ color: 0x666666 }); - g.roundRect(-60, -20, 120, 40, 8); + const pulse = Math.sin(time * 0.1) * 0.3 + 0.7; + + // Cyberpunk button + g.setFillStyle({ color: 0x000a12, alpha: 0.95 }); + g.roundRect(-80, -25, 160, 50, 10); g.fill(); - g.setStrokeStyle({ color: 0xffffff, width: 2 }); - g.roundRect(-60, -20, 120, 40, 8); + + g.setStrokeStyle({ + color: isHovered ? 0x00ffd0 : 0x005566, + width: isHovered ? 3 : 2, + alpha: pulse, + }); + g.roundRect(-80, -25, 160, 50, 10); g.stroke(); + + // Tech accent corners + g.setFillStyle({ color: 0x00ffd0, alpha: isHovered ? 0.6 : 0.3 }); + g.moveTo(-80, -15); + g.lineTo(-70, -25); + g.lineTo(-80, -25); + g.lineTo(-80, -15); + g.fill(); + + g.moveTo(80, -15); + g.lineTo(70, -25); + g.lineTo(80, -25); + g.lineTo(80, -15); + g.fill(); + + if (isHovered) { + g.setStrokeStyle({ + color: 0x00ffd0, + width: 2, + alpha: pulse * 0.5, + }); + g.roundRect(-90, -35, 180, 70, 15); + g.stroke(); + } }} /> diff --git a/src/components/game/HitEffectsLayer.tsx b/src/components/game/HitEffectsLayer.tsx index 950c44f3618..a18fa750731 100644 --- a/src/components/game/HitEffectsLayer.tsx +++ b/src/components/game/HitEffectsLayer.tsx @@ -1,4 +1,18 @@ +import { useState, useEffect, useCallback } from "react"; import type { JSX } from "react"; +import type { Graphics as PixiGraphics } from "pixi.js"; +import { useTexture } from "../../hooks/useTexture"; + +// Dark Trigram theme constants +const DARK_TRIGRAM_THEME = { + PRIMARY_CYAN: 0x00ffd0, + VITAL_ORANGE: 0xff4400, + CRITICAL_RED: 0xff3030, + WHITE: 0xffffff, + DARK_BG: 0x0a0e12, + ENERGY_BLUE: 0x4169e1, + LIGHTNING_YELLOW: 0xffd700, +} as const; export interface HitEffect { readonly id: string; @@ -8,51 +22,382 @@ export interface HitEffect { readonly technique: string; readonly life: number; readonly maxLife: number; + readonly isVitalHit?: boolean; + readonly trigram?: string; } interface HitEffectsLayerProps { readonly hitEffects: readonly HitEffect[]; } +interface Particle { + readonly id: string; + readonly x: number; + readonly y: number; + readonly vx: number; + readonly vy: number; + readonly life: number; + readonly maxLife: number; + readonly color: number; + readonly size: number; +} + export function HitEffectsLayer({ hitEffects, }: HitEffectsLayerProps): JSX.Element { + const [particles, setParticles] = useState([]); + const [screenShake, setScreenShake] = useState({ x: 0, y: 0, intensity: 0 }); + const { texture: logoTexture } = useTexture("/dark-trigram-256.png"); + + // Generate explosive particles for vital hits + const generateHitParticles = useCallback((effect: HitEffect): Particle[] => { + const particleCount = effect.isVitalHit ? 25 : 12; + const particles: Particle[] = []; + + for (let i = 0; i < particleCount; i++) { + const angle = (Math.PI * 2 * i) / particleCount; + const speed = effect.isVitalHit + ? 3 + Math.random() * 2 + : 1.5 + Math.random(); + const life = 60 + Math.random() * 30; + + particles.push({ + id: `particle_${effect.id}_${i}`, + x: effect.x, + y: effect.y, + vx: Math.cos(angle) * speed, + vy: Math.sin(angle) * speed, + life, + maxLife: life, + color: effect.isVitalHit + ? DARK_TRIGRAM_THEME.VITAL_ORANGE + : DARK_TRIGRAM_THEME.PRIMARY_CYAN, + size: effect.isVitalHit ? 4 : 2, + }); + } + + return particles; + }, []); + + // Generate screen shake for powerful hits + const generateScreenShake = useCallback((effect: HitEffect): void => { + const intensity = effect.isVitalHit ? 15 : Math.min(effect.damage / 2, 8); + setScreenShake({ + x: (Math.random() - 0.5) * intensity, + y: (Math.random() - 0.5) * intensity, + intensity, + }); + }, []); + + // Handle new hit effects + useEffect(() => { + hitEffects.forEach((effect) => { + // Generate particles for new effects + if (effect.life === effect.maxLife) { + const newParticles = generateHitParticles(effect); + setParticles((prev) => [...prev, ...newParticles]); + generateScreenShake(effect); + } + }); + }, [hitEffects, generateHitParticles, generateScreenShake]); + + // Update particles and screen shake + useEffect(() => { + const interval = setInterval(() => { + // Update particles + setParticles((prev) => + prev + .map((particle) => ({ + ...particle, + x: particle.x + particle.vx, + y: particle.y + particle.vy, + vx: particle.vx * 0.98, // Air resistance + vy: particle.vy * 0.98 + 0.1, // Gravity + life: particle.life - 1, + })) + .filter((particle) => particle.life > 0) + ); + + // Decay screen shake + setScreenShake((prev) => ({ + x: prev.x * 0.9, + y: prev.y * 0.9, + intensity: prev.intensity * 0.9, + })); + }, 16); // ~60fps + + return () => clearInterval(interval); + }, []); + + const drawParticles = useCallback( + (g: PixiGraphics) => { + g.clear(); + + particles.forEach((particle) => { + const alpha = particle.life / particle.maxLife; + const size = particle.size * alpha; + + // Energy particles with glow effect + g.setFillStyle({ color: particle.color, alpha: alpha * 0.8 }); + g.circle(particle.x, particle.y, size); + g.fill(); + + // Outer glow + g.setFillStyle({ color: particle.color, alpha: alpha * 0.3 }); + g.circle(particle.x, particle.y, size * 2); + g.fill(); + }); + }, + [particles] + ); + return ( - <> + + {/* Particle effects */} + + + {/* Enhanced hit effects with Korean aesthetics */} {hitEffects.map((effect) => { const alpha = effect.life / effect.maxLife; - const scale = 1 + (1 - alpha) * 0.5; + const scale = 1 + (1 - alpha) * 0.8; + const isVitalHit = effect.damage > 25; + return ( - - + {/* Background explosion ring for vital hits */} + {isVitalHit && ( + { + g.clear(); + const ringSize = (1 - alpha) * 100; + + // Outer explosion ring + g.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.VITAL_ORANGE, + width: 3, + alpha: alpha * 0.6, + }); + g.circle(0, 0, ringSize); + g.stroke(); + + // Inner energy ring + g.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.LIGHTNING_YELLOW, + width: 1, + alpha: alpha * 0.8, + }); + g.circle(0, 0, ringSize * 0.7); + g.stroke(); + + // Lightning effect for vital hits + if (alpha > 0.7) { + for (let i = 0; i < 8; i++) { + const angle = (Math.PI * 2 * i) / 8; + const length = 30 + Math.random() * 20; + g.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.LIGHTNING_YELLOW, + width: 2, + alpha: alpha * 0.5, + }); + g.moveTo(0, 0); + g.lineTo( + Math.cos(angle) * length + (Math.random() - 0.5) * 10, + Math.sin(angle) * length + (Math.random() - 0.5) * 10 + ); + g.stroke(); + } + } + }} + /> + )} + + {/* Dark Trigram logo for critical hits */} + {isVitalHit && logoTexture && alpha > 0.5 && ( + + )} + + {/* Enhanced damage number with Korean style */} + + { + g.clear(); + // Background panel for damage number + g.setFillStyle({ + color: DARK_TRIGRAM_THEME.DARK_BG, + alpha: alpha * 0.8, + }); + g.roundRect(-40, -15, 80, 30, 8); + g.fill(); + + // Border with trigram colors + g.setStrokeStyle({ + color: isVitalHit + ? DARK_TRIGRAM_THEME.VITAL_ORANGE + : DARK_TRIGRAM_THEME.PRIMARY_CYAN, + width: 2, + alpha: alpha, + }); + g.roundRect(-40, -15, 80, 30, 8); + g.stroke(); + }} + /> + + + + + {/* Vital hit indicator with Korean text and effects */} + {isVitalHit && ( + + { + g.clear(); + // Energy burst background + g.setFillStyle({ + color: DARK_TRIGRAM_THEME.VITAL_ORANGE, + alpha: alpha * 0.3, + }); + g.roundRect(-60, -20, 120, 40, 12); + g.fill(); + + // Pulsing border + g.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.WHITE, + width: 2, + alpha: alpha * 0.9, + }); + g.roundRect(-60, -20, 120, 40, 12); + g.stroke(); + }} + /> + + + + )} + + {/* Technique name with enhanced Korean typography */} + + { + g.clear(); + // Traditional Korean panel background + g.setFillStyle({ color: 0x000000, alpha: alpha * 0.7 }); + g.roundRect(-80, -12, 160, 24, 6); + g.fill(); + + // Traditional red border + g.setStrokeStyle({ + color: 0x8b0000, + width: 1, + alpha: alpha * 0.8, + }); + g.roundRect(-80, -12, 160, 24, 6); + g.stroke(); + }} + /> + + + + + {/* Trigram symbol for technique classification */} + {effect.trigram && ( + + { + g.clear(); + // Circular background for trigram + g.setFillStyle({ + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + alpha: alpha * 0.2, + }); + g.circle(0, 0, 25); + g.fill(); + + g.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + width: 2, + alpha: alpha * 0.6, + }); + g.circle(0, 0, 25); + g.stroke(); + }} + /> + + + + )} ); })} - + ); } diff --git a/src/components/game/Player.tsx b/src/components/game/Player.tsx index 98a1a552515..0f1bb45d066 100644 --- a/src/components/game/Player.tsx +++ b/src/components/game/Player.tsx @@ -338,7 +338,7 @@ export function PlayerContainer({ // Enhanced attack execution with audio feedback const executeAttack = useCallback( - (stance: TrigramStance) => { + (stance: TrigramStance): void => { if (!gameStarted || attackCooldown > 0) return; const technique = TRIGRAM_TECHNIQUES[stance]; @@ -346,9 +346,14 @@ export function PlayerContainer({ setPlayerState((prev) => PlayerStateManager.executeAttack(prev, stance)); - // Play attack sound based on damage + // Play attack sound based on damage with vital point detection + const isVitalPoint = technique.damage > 30; audio.playAttackSound(technique.damage); + if (isVitalPoint) { + audio.playHitSound(technique.damage, true); + } + const attackX = playerState.facing === "right" ? playerState.x + technique.range @@ -377,7 +382,7 @@ export function PlayerContainer({ playerState.x, playerState.y, onAttack, - audio, // Added audio dependency + audio, ] ); @@ -420,7 +425,7 @@ export function PlayerContainer({ // Enhanced AI behavior with better decision making const handleAI = useCallback( - (delta: number) => { + (delta: number): void => { if (!opponentPosition || !gameStarted || isPlayerOne) return; const distance = AISystem.calculateDistance( @@ -463,7 +468,7 @@ export function PlayerContainer({ // Enhanced player input handling const handlePlayerInput = useCallback( - (delta: number) => { + (delta: number): void => { if (!isPlayerOne) { handleAI(delta); return; @@ -480,7 +485,7 @@ export function PlayerContainer({ ? GAME_BALANCE.BASE_MOVE_SPEED * GAME_BALANCE.BLOCKING_SPEED_MULTIPLIER : GAME_BALANCE.BASE_MOVE_SPEED; - // Movement controls + // Movement controls using delta for frame-rate independence if (keys.has("KeyA") || keys.has("ArrowLeft")) { newX -= moveSpeed * delta; newFacing = "left"; @@ -546,19 +551,19 @@ export function PlayerContainer({ // Enhanced game loop with better performance useTick( useCallback( - (ticker: { deltaTime: number }) => { + (ticker: { deltaTime: number }): void => { const delta = ticker.deltaTime; setAnimationTime((prev) => prev + delta); if (!gameStarted) return; - // Update cooldowns + // Update cooldowns using delta time setAttackCooldown((prev) => Math.max(0, prev - delta)); - // Handle input + // Handle input with delta timing handlePlayerInput(delta); - // Update stamina + // Update stamina with delta timing setPlayerState((prev) => PlayerStateManager.updateStamina(prev, delta)); }, [gameStarted, handlePlayerInput] diff --git a/src/components/game/PlayerVisuals.tsx b/src/components/game/PlayerVisuals.tsx index ed27bce4773..21b38c8680a 100644 --- a/src/components/game/PlayerVisuals.tsx +++ b/src/components/game/PlayerVisuals.tsx @@ -1,47 +1,41 @@ import type { JSX } from "react"; import type { Graphics as PixiGraphics } from "pixi.js"; -import type { PlayerState, TrigramStance } from "./Player"; - -// Enhanced visual theme with more iconic elements -const VISUAL_THEME = { - PLAYER_1_COLOR: 0x4a90e2, - PLAYER_2_COLOR: 0x8b0000, - TRADITIONAL_GOLD: 0xffd700, - KOREAN_WHITE: 0xffffff, - COMBAT_ICONS: { - HEALTH: "โค๏ธ", - STAMINA: "โšก", - ATTACK: "โš”๏ธ", - DEFENSE: "๐Ÿ›ก๏ธ", - PRECISION: "๐ŸŽฏ", - }, - ENERGY_COLORS: { - geon: 0xffd700, // Gold - Heaven (Lightning energy) ๐Ÿ”ฅ - tae: 0x87ceeb, // Sky Blue - Lake (Water energy) ๐ŸŒŠ - li: 0xff4500, // Red Orange - Fire (Flame energy) โšก - jin: 0x9370db, // Purple - Thunder (Electric energy) ๐Ÿ’ฅ - son: 0x98fb98, // Pale Green - Wind (Air energy) ๐ŸŒช๏ธ - gam: 0x4169e1, // Royal Blue - Water (Liquid energy) ๐Ÿ›ก๏ธ - gan: 0x8b4513, // Saddle Brown - Mountain (Earth energy) ๐Ÿ—ฟ - gon: 0x654321, // Dark Brown - Earth (Stone energy) ๐Ÿคœ - }, +import { PlayerState, TrigramStance } from "./Player"; + +// Dark Trigram theme constants +const DARK_TRIGRAM_THEME = { + PRIMARY_CYAN: 0x00ffd0, + DARK_BG: 0x0a0e12, + DARKER_BG: 0x181c20, + MEDIUM_BG: 0x23272b, + VITAL_ORANGE: 0xff4400, + CRITICAL_RED: 0xff3030, + WHITE: 0xffffff, + HEALTH_CYAN: 0x00ffd0, + WARNING_ORANGE: 0xff9800, STANCE_ICONS: { - geon: "๐Ÿ”ฅ", // Heaven - Power strikes - tae: "๐ŸŒŠ", // Lake - Flowing combos - li: "โšก", // Fire - Fast attacks - jin: "๐Ÿ’ฅ", // Thunder - Explosive bursts - son: "๐ŸŒช๏ธ", // Wind - Continuous pressure - gam: "๐Ÿ›ก๏ธ", // Water - Evasion and counters - gan: "๐Ÿ—ฟ", // Mountain - Immovable defense - gon: "๐Ÿคœ", // Earth - Throws and takedowns - }, + geon: "๐Ÿ”ฅ", // Heaven + tae: "๐ŸŒŠ", // Lake + li: "โšก", // Fire + jin: "๐Ÿ’ฅ", // Thunder + son: "๐ŸŒช๏ธ", // Wind + gam: "๐Ÿ›ก๏ธ", // Water + gan: "๐Ÿ—ฟ", // Mountain + gon: "๐Ÿคœ", // Earth + } as Record, } as const; -// Helper functions for stance color and symbol -export function getStanceColor(stance: TrigramStance): number { - return VISUAL_THEME.ENERGY_COLORS[stance]; +// Trigram technique interface +interface TrigramTechnique { + readonly name: string; + readonly damage: number; + readonly stamina: number; + readonly speed: number; + readonly range: number; + readonly vitalPoints: readonly string[]; } +// Export the helper function for testing export function getTrigramSymbol(stance: TrigramStance): string { const symbols: Record = { geon: "โ˜ฐ", // Heaven @@ -56,20 +50,33 @@ export function getTrigramSymbol(stance: TrigramStance): string { return symbols[stance]; } -interface TrigramTechnique { - readonly name: string; - readonly damage: number; - readonly stamina: number; - readonly speed: number; - readonly range: number; - readonly vitalPoints: readonly string[]; +// Export stance color function for testing +export function getStanceColor(stance: TrigramStance): number { + const stanceColors: Record = { + geon: 0xffd700, // Gold - Heaven + tae: 0x87ceeb, // Sky Blue - Lake + li: 0xff4500, // Red Orange - Fire + jin: 0x9370db, // Purple - Thunder + son: 0x98fb98, // Pale Green - Wind + gam: 0x4169e1, // Royal Blue - Water + gan: 0x8b4513, // Saddle Brown - Mountain + gon: 0x654321, // Dark Brown - Earth + }; + return stanceColors[stance]; +} + +// Helper function to get trigram symbols +function getTrigramSymbolInternal(stance: TrigramStance): string { + return getTrigramSymbol(stance); } +// Main PlayerVisuals component interface PlayerVisualsProps { readonly playerState: PlayerState; readonly currentTechnique: TrigramTechnique; readonly isPlayerOne: boolean; readonly animationTime: number; + readonly showImpactEffect?: boolean; } export function PlayerVisuals({ @@ -77,16 +84,20 @@ export function PlayerVisuals({ currentTechnique, isPlayerOne, animationTime, + showImpactEffect = false, }: PlayerVisualsProps): JSX.Element { return ( - <> + + + + {playerState.isAttacking && ( )} + + {playerState.isAttacking && showImpactEffect && ( + 30} + /> + )} + {playerState.isMoving && ( )} - + ); } @@ -110,202 +131,143 @@ function PlayerBody({ isPlayerOne, animationTime, }: { - playerState: PlayerState; - isPlayerOne: boolean; - animationTime: number; + readonly playerState: PlayerState; + readonly isPlayerOne: boolean; + readonly animationTime: number; }): JSX.Element { + const auraPulse = Math.sin(animationTime * 0.1) * 0.3 + 0.7; + + // Use isPlayerOne for player-specific styling + const playerColor = isPlayerOne + ? DARK_TRIGRAM_THEME.PRIMARY_CYAN + : DARK_TRIGRAM_THEME.VITAL_ORANGE; + return ( - <> - {/* Enhanced fighter body with martial arts uniform */} - { - g.clear(); + { + g.clear(); - const bodyColor = isPlayerOne - ? VISUAL_THEME.PLAYER_1_COLOR - : VISUAL_THEME.PLAYER_2_COLOR; + // Dark silhouette body with player-specific accent + g.setFillStyle({ color: DARK_TRIGRAM_THEME.DARKER_BG, alpha: 0.95 }); + g.rect(-25, -90, 50, 90); + g.fill(); - // Traditional Korean martial arts uniform (dobok) with better design - g.setFillStyle({ color: 0xffffff, alpha: 0.9 }); // White dobok - g.rect(-25, -90, 50, 90); - g.fill(); + // Player-specific glowing outline + g.setStrokeStyle({ color: playerColor, width: 4, alpha: auraPulse }); + g.rect(-25, -90, 50, 90); + g.stroke(); - // Traditional belt with rank colors - const beltColor = isPlayerOne ? 0x000080 : 0x8b0000; // Blue vs Red belt - g.setFillStyle({ color: beltColor }); - g.rect(-27, -25, 54, 10); - g.fill(); + // Enhanced head with player-specific glow + g.setFillStyle({ color: DARK_TRIGRAM_THEME.MEDIUM_BG }); + g.circle(0, -75, 15); + g.fill(); + + g.setStrokeStyle({ + color: playerColor, + width: 3, + alpha: auraPulse * 0.8, + }); + g.circle(0, -75, 15); + g.stroke(); - // Arm positioning for martial arts stance - g.setStrokeStyle({ color: bodyColor, width: 8 }); - if (playerState.facing === "right") { - // Right guard position - g.moveTo(-15, -60); - g.lineTo(-35, -45); - g.moveTo(15, -60); - g.lineTo(40, -50); - } else { - // Left guard position - g.moveTo(15, -60); - g.lineTo(35, -45); - g.moveTo(-15, -60); - g.lineTo(-40, -50); - } + // Arm positioning with player-specific glow + g.setStrokeStyle({ color: playerColor, width: 6, alpha: 0.8 }); + if (playerState.facing === "right") { + // Right-facing stance + g.moveTo(25, -60); + g.lineTo(45, -40); + g.moveTo(-25, -60); + g.lineTo(-15, -50); + } else { + // Left-facing stance + g.moveTo(-25, -60); + g.lineTo(-45, -40); + g.moveTo(25, -60); + g.lineTo(15, -50); + } + g.stroke(); + + // Pulsing aura when attacking or high stamina + if (playerState.isAttacking || playerState.stamina > 95) { + const attackAlpha = playerState.isAttacking ? 0.8 : 0.4; + g.setStrokeStyle({ + color: playerColor, + width: 8, + alpha: attackAlpha * auraPulse, + }); + g.circle(0, -45, 55 + Math.sin(animationTime * 0.3) * 10); g.stroke(); + } - // Head with martial arts headband - g.setFillStyle({ color: 0xffdbac }); // Skin tone - g.circle(0, -75, 15); - g.fill(); + // Enhanced blocking effect with player distinction + if (playerState.isBlocking) { + g.setStrokeStyle({ + color: 0xffffff, + width: 4, + alpha: 0.9, + }); + g.circle(0, -45, 35); + g.stroke(); - // Traditional headband - g.setFillStyle({ color: beltColor }); - g.rect(-18, -85, 36, 6); + g.setFillStyle({ color: 0xffffff, alpha: 0.3 }); + g.circle(0, -45, 30); g.fill(); - - // Enhanced stance aura with particle-like effects - if (playerState.isAttacking) { - const attackPulse = Math.sin(animationTime * 0.3) * 0.4 + 0.6; - const stanceColor = VISUAL_THEME.ENERGY_COLORS[playerState.stance]; - - // Energy aura around fighter - g.setStrokeStyle({ - color: stanceColor, - width: 8, - alpha: attackPulse * 0.8, - }); - g.circle(0, -45, 45 + Math.sin(animationTime * 0.5) * 5); - g.stroke(); - - // Inner energy core - g.setFillStyle({ - color: stanceColor, - alpha: attackPulse * 0.3, - }); - g.circle(0, -45, 35); - g.fill(); - - // Energy sparks - for (let i = 0; i < 8; i++) { - const angle = (i / 8) * Math.PI * 2 + animationTime * 0.1; - const sparkX = - Math.cos(angle) * (50 + Math.sin(animationTime + i) * 10); - const sparkY = - -45 + Math.sin(angle) * (50 + Math.sin(animationTime + i) * 10); - - g.setFillStyle({ - color: VISUAL_THEME.KOREAN_WHITE, - alpha: attackPulse * 0.8, - }); - g.circle(sparkX, sparkY, 2 + Math.sin(animationTime * 2 + i) * 1); - g.fill(); - } - } - - // Enhanced blocking effect with traditional patterns - if (playerState.isBlocking) { - // Main blocking barrier - g.setStrokeStyle({ - color: VISUAL_THEME.KOREAN_WHITE, - width: 6, - alpha: 0.9, - }); - g.rect(-30, -95, 60, 100); - g.stroke(); - - // Traditional Korean geometric patterns - g.setStrokeStyle({ - color: VISUAL_THEME.TRADITIONAL_GOLD, - width: 2, - alpha: 0.7, - }); - - // Cross pattern - g.moveTo(-30, -45); - g.lineTo(30, -45); - g.moveTo(0, -95); - g.lineTo(0, 5); - g.stroke(); - - // Corner decorations with proper destructuring - const corners: readonly [number, number][] = [ - [-25, -90], - [25, -90], - [-25, 0], - [25, 0], - ]; - corners.forEach(([x, y]) => { - g.setFillStyle({ - color: VISUAL_THEME.TRADITIONAL_GOLD, - alpha: 0.6, - }); - g.rect(x - 3, y - 3, 6, 6); - g.fill(); - }); - } - }} - /> - + } + }} + /> ); } -function StanceIndicator({ stance }: { stance: TrigramStance }): JSX.Element { +// Update StanceIndicator to use the internal function +function StanceIndicator({ + stance, +}: { + readonly stance: TrigramStance; +}): JSX.Element { + const pulse = Math.sin(Date.now() * 0.002) * 0.2 + 0.8; + return ( - - {/* Enhanced stance display with combat icons */} + + {/* Stance symbol background */} { g.clear(); - - const stanceColor = VISUAL_THEME.ENERGY_COLORS[stance]; - - // Main background with enhanced design - g.setFillStyle({ color: 0x000000, alpha: 0.95 }); - g.roundRect(-55, -125, 110, 35, 15); + g.setFillStyle({ + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + alpha: pulse * 0.3, + }); + g.circle(0, 0, 25); g.fill(); - // Glowing border with stance-specific color - g.setStrokeStyle({ color: stanceColor, width: 3, alpha: 0.9 }); - g.roundRect(-55, -125, 110, 35, 15); - g.stroke(); - - // Inner glow effect g.setStrokeStyle({ - color: VISUAL_THEME.KOREAN_WHITE, - width: 1, - alpha: 0.7, + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + width: 2, + alpha: pulse, }); - g.roundRect(-50, -120, 100, 25, 12); + g.circle(0, 0, 25); g.stroke(); - - // Corner energy markers - const corners: readonly [number, number][] = [ - [-45, -115], - [45, -115], - [-45, -95], - [45, -95], - ]; - corners.forEach(([x, y]) => { - g.setFillStyle({ color: stanceColor, alpha: 0.8 }); - g.circle(x, y, 3); - g.fill(); - }); }} /> - {/* Combat style icon with trigram symbol */} + {/* Trigram symbol */} + + {/* Stance icon */} + @@ -315,82 +277,72 @@ function StanceIndicator({ stance }: { stance: TrigramStance }): JSX.Element { function HealthStaminaBars({ playerState, }: { - playerState: PlayerState; + readonly playerState: PlayerState; }): JSX.Element { + const healthColor = + playerState.health > 50 + ? DARK_TRIGRAM_THEME.PRIMARY_CYAN + : playerState.health > 25 + ? DARK_TRIGRAM_THEME.WARNING_ORANGE + : DARK_TRIGRAM_THEME.CRITICAL_RED; + return ( - <> - {/* Enhanced health bar with icon */} - + + {/* Health bar */} { g.clear(); // Background - g.setFillStyle({ color: 0x2a2a2a }); - g.roundRect(-35, -135, 70, 10, 5); + g.setFillStyle({ color: DARK_TRIGRAM_THEME.DARKER_BG, alpha: 0.8 }); + g.rect(-30, 0, 60, 8); g.fill(); - // Health fill with dynamic color - const healthColor = - playerState.health > 50 - ? 0x4caf50 - : playerState.health > 25 - ? 0xff9800 - : 0xff4444; + // Health fill g.setFillStyle({ color: healthColor }); - g.roundRect(-33, -133, playerState.health * 0.66, 6, 3); + g.rect(-30, 0, (playerState.health / 100) * 60, 8); g.fill(); // Border - g.setStrokeStyle({ color: 0xffffff, width: 1 }); - g.roundRect(-35, -135, 70, 10, 5); + g.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + width: 1, + }); + g.rect(-30, 0, 60, 8); g.stroke(); }} /> - {/* Enhanced stamina bar with icon */} - + {/* Stamina bar */} { g.clear(); - // Health bar background - g.setFillStyle({ color: 0x2a2a2a }); - g.roundRect(-35, -142, 70, 8, 4); + // Background + g.setFillStyle({ color: DARK_TRIGRAM_THEME.DARKER_BG, alpha: 0.8 }); + g.rect(-30, 0, 60, 6); g.fill(); // Stamina fill - g.setFillStyle({ color: 0xffc107 }); - g.roundRect(-33, -118, playerState.stamina * 0.66, 4, 2); + g.setFillStyle({ + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + alpha: 0.8, + }); + g.rect(-30, 0, (playerState.stamina / 100) * 60, 6); g.fill(); // Border - g.setStrokeStyle({ color: 0xffffff, width: 1 }); - g.roundRect(-35, -120, 70, 8, 4); + g.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.PRIMARY_CYAN, + width: 1, + alpha: 0.6, + }); + g.rect(-30, 0, 60, 6); g.stroke(); }} /> - + ); } @@ -399,94 +351,100 @@ function AttackEffect({ technique, animationTime, }: { - playerState: PlayerState; - technique: TrigramTechnique; - animationTime: number; + readonly playerState: PlayerState; + readonly technique: TrigramTechnique; + readonly animationTime: number; }): JSX.Element { + const isVital = technique.damage > 30; + const burstColor = isVital + ? DARK_TRIGRAM_THEME.VITAL_ORANGE + : DARK_TRIGRAM_THEME.PRIMARY_CYAN; + const burstAlpha = Math.sin(animationTime * 0.3) * 0.4 + 0.6; + return ( - <> - {/* Main attack effect */} - { - g.clear(); - const attackAlpha = Math.sin(animationTime * 0.3) * 0.4 + 0.6; - const attackWidth = technique.range; - const attackHeight = 30 + technique.damage * 0.8; - const stanceColor = VISUAL_THEME.ENERGY_COLORS[playerState.stance]; - - // Enhanced attack effect with energy burst - g.setFillStyle({ color: stanceColor, alpha: attackAlpha * 0.6 }); - g.ellipse(attackWidth / 2, 0, attackWidth, attackHeight); - g.fill(); + { + g.clear(); - // Attack core with pulsing effect - g.setFillStyle({ color: 0xffffff, alpha: attackAlpha }); - g.ellipse(attackWidth / 2, 0, attackWidth * 0.3, attackHeight * 0.5); - g.fill(); - }} - /> + const attackX = playerState.facing === "right" ? 35 : -35; - {/* Attack impact indicator */} - - + // Main attack burst + g.setFillStyle({ color: burstColor, alpha: burstAlpha * 0.6 }); + g.circle(attackX, -45, 20 + Math.sin(animationTime * 0.4) * 8); + g.fill(); + + // Attack ring + g.setStrokeStyle({ color: burstColor, width: 3, alpha: burstAlpha }); + g.circle(attackX, -45, 25 + Math.sin(animationTime * 0.5) * 5); + g.stroke(); + + // Technique-specific effect + if (isVital) { + // Vital point targeting crosshair + g.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.VITAL_ORANGE, + width: 2, + alpha: burstAlpha, + }); + g.moveTo(attackX - 15, -45); + g.lineTo(attackX + 15, -45); + g.moveTo(attackX, -60); + g.lineTo(attackX, -30); + g.stroke(); + } + }} + /> ); } -function AttackImpactEffect({ +// Export AttackImpactEffect for use in other components +export function AttackImpactEffect({ technique, playerState, animationTime, + isVital = false, }: { - technique: TrigramTechnique; - playerState: PlayerState; - animationTime: number; + readonly technique: TrigramTechnique; + readonly playerState: PlayerState; + readonly animationTime: number; + readonly isVital?: boolean; }): JSX.Element { const impactX = playerState.facing === "right" ? 35 + technique.range : -35 - technique.range; + const impactColor = isVital + ? DARK_TRIGRAM_THEME.VITAL_ORANGE + : DARK_TRIGRAM_THEME.PRIMARY_CYAN; + const impactAlpha = Math.sin(animationTime * 0.4) * 0.5 + 0.5; return ( { g.clear(); - const impactAlpha = Math.sin(animationTime * 0.4) * 0.5 + 0.5; - const stanceColor = VISUAL_THEME.ENERGY_COLORS[playerState.stance]; - // Impact explosion effect - g.setFillStyle({ - color: stanceColor, - alpha: impactAlpha * 0.7, - }); - g.circle(impactX, 0, 15 + Math.sin(animationTime * 0.6) * 5); + // Impact burst + g.setFillStyle({ color: impactColor, alpha: impactAlpha * 0.8 }); + g.circle(impactX, -45, 15 + Math.sin(animationTime * 0.6) * 5); g.fill(); - // Impact ring - g.setStrokeStyle({ - color: VISUAL_THEME.KOREAN_WHITE, - width: 3, - alpha: impactAlpha, - }); - g.circle(impactX, 0, 20 + Math.sin(animationTime * 0.8) * 8); + // Impact shockwave + g.setStrokeStyle({ color: impactColor, width: 2, alpha: impactAlpha }); + g.circle(impactX, -45, 20 + Math.sin(animationTime * 0.4) * 10); g.stroke(); - // Impact sparks - for (let i = 0; i < 8; i++) { - const angle = (i / 8) * Math.PI * 2; - const sparkDistance = 25 + Math.sin(animationTime * 0.7 + i) * 10; - const sparkX = impactX + Math.cos(angle) * sparkDistance; - const sparkY = Math.sin(angle) * sparkDistance; - - g.setFillStyle({ - color: VISUAL_THEME.KOREAN_WHITE, - alpha: impactAlpha * 0.8, + if (isVital) { + // Vital hit indicator + g.setStrokeStyle({ + color: DARK_TRIGRAM_THEME.VITAL_ORANGE, + width: 3, + alpha: impactAlpha, }); - g.circle(sparkX, sparkY, 2); - g.fill(); + g.moveTo(impactX - 20, -45); + g.lineTo(impactX + 20, -45); + g.moveTo(impactX, -65); + g.lineTo(impactX, -25); + g.stroke(); } }} /> @@ -495,48 +453,35 @@ function AttackImpactEffect({ function MovementTrail({ isPlayerOne, + animationTime, facing, }: { - isPlayerOne: boolean; - animationTime: number; - facing: "left" | "right"; + readonly isPlayerOne: boolean; + readonly animationTime: number; + readonly facing: "left" | "right"; }): JSX.Element { + // Use isPlayerOne for different trail colors + const trailColor = isPlayerOne + ? DARK_TRIGRAM_THEME.PRIMARY_CYAN + : DARK_TRIGRAM_THEME.VITAL_ORANGE; + return ( { g.clear(); - const trailAlpha = 0.4; - const trailColor = isPlayerOne - ? VISUAL_THEME.PLAYER_1_COLOR - : VISUAL_THEME.PLAYER_2_COLOR; - // Enhanced movement trail with speed lines + const trailX = facing === "right" ? -20 : 20; + const trailAlpha = Math.sin(animationTime * 0.2) * 0.3 + 0.4; + + // Movement trail effect for (let i = 0; i < 5; i++) { - const offset = facing === "right" ? -(i * 8) : i * 8; - const alpha = trailAlpha * (1 - i * 0.15); + const offsetX = trailX + i * (facing === "right" ? -8 : 8); + const alpha = trailAlpha * (1 - i * 0.2); - g.setFillStyle({ - color: trailColor, - alpha: alpha, - }); - g.rect(offset - 20, -75, 40, 70); + g.setFillStyle({ color: trailColor, alpha }); + g.circle(offsetX, -45, 8 - i * 1.5); g.fill(); } - - // Speed lines for dynamic movement - g.setStrokeStyle({ - color: VISUAL_THEME.KOREAN_WHITE, - width: 2, - alpha: trailAlpha * 0.6, - }); - - for (let i = 0; i < 6; i++) { - const lineY = -60 + i * 15; - const lineOffset = facing === "right" ? -30 : 30; - g.moveTo(lineOffset, lineY); - g.lineTo(lineOffset + (facing === "right" ? -15 : 15), lineY); - g.stroke(); - } }} /> ); diff --git a/src/hooks/useTexture.ts b/src/hooks/useTexture.ts new file mode 100644 index 00000000000..78e159cec61 --- /dev/null +++ b/src/hooks/useTexture.ts @@ -0,0 +1,90 @@ +import { useState, useEffect } from "react"; +import { Texture, Assets } from "pixi.js"; + +interface UseTextureResult { + readonly texture: Texture | null; + readonly loading: boolean; + readonly error: string | null; +} + +// Texture cache to prevent reloading +const textureCache = new Map(); + +export function useTexture(url: string): UseTextureResult { + const [texture, setTexture] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + // Check cache first + if (textureCache.has(url)) { + const cachedTexture = textureCache.get(url); + if (cachedTexture) { + setTexture(cachedTexture); + setLoading(false); + return; + } + } + + let cancelled = false; + + const loadTexture = async (): Promise => { + try { + setLoading(true); + setError(null); + + const loadedTexture = await Assets.load(url); + + if (!cancelled) { + // Cache the texture + textureCache.set(url, loadedTexture); + setTexture(loadedTexture); + setLoading(false); + } + } catch (err) { + if (!cancelled) { + const errorMessage = + err instanceof Error + ? err.message + : `Failed to load texture: ${url}`; + setError(errorMessage); + setLoading(false); + console.warn(`[useTexture] ${errorMessage}`); + } + } + }; + + loadTexture(); + + return () => { + cancelled = true; + }; + }, [url]); + + return { texture, loading, error }; +} + +// Preload commonly used textures +export async function preloadGameTextures(): Promise { + const commonTextures = [ + "/dark-trigram-256.png", + "/trigram-effects.png", + "/vital-points.png", + ]; + + try { + await Promise.all( + commonTextures.map(async (url) => { + if (!textureCache.has(url)) { + const texture = await Assets.load(url); + textureCache.set(url, texture); + } + }) + ); + } catch (error) { + console.warn( + "[preloadGameTextures] Some textures failed to preload:", + error + ); + } +} diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo index df828227e6c..2afe5e43994 100644 --- a/tsconfig.tsbuildinfo +++ b/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/App.test.tsx","./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/audio/AudioManager.ts","./src/audio/AudioUtils.ts","./src/audio/DefaultSoundGenerator.ts","./src/audio/placeholder-sounds.ts","./src/audio/__tests__/AudioManager.integration.test.ts","./src/audio/__tests__/AudioManager.test.ts","./src/audio/__tests__/AudioUtils.test.ts","./src/audio/__tests__/integration.test.tsx","./src/components/__tests__/AudioIntegration.test.tsx","./src/components/__tests__/GameAudio.test.tsx","./src/components/game/DojangBackground.test.tsx","./src/components/game/DojangBackground.tsx","./src/components/game/GameEngine.test.tsx","./src/components/game/GameEngine.tsx","./src/components/game/GameUI.test.tsx","./src/components/game/GameUI.tsx","./src/components/game/HitEffectsLayer.test.tsx","./src/components/game/HitEffectsLayer.tsx","./src/components/game/Player.test.tsx","./src/components/game/Player.tsx","./src/components/game/PlayerVisuals.test.tsx","./src/components/game/PlayerVisuals.tsx","./src/test/setup.ts","./src/types/pixi-react.d.ts"],"version":"5.8.3"} \ No newline at end of file +{"root":["./src/App.test.tsx","./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/audio/AudioManager.ts","./src/audio/AudioUtils.ts","./src/audio/DefaultSoundGenerator.ts","./src/audio/placeholder-sounds.ts","./src/audio/__tests__/AudioManager.integration.test.ts","./src/audio/__tests__/AudioManager.test.ts","./src/audio/__tests__/AudioUtils.test.ts","./src/audio/__tests__/integration.test.tsx","./src/components/__tests__/AudioIntegration.test.tsx","./src/components/__tests__/GameAudio.test.tsx","./src/components/game/DojangBackground.test.tsx","./src/components/game/DojangBackground.tsx","./src/components/game/GameEngine.test.tsx","./src/components/game/GameEngine.tsx","./src/components/game/GameUI.test.tsx","./src/components/game/GameUI.tsx","./src/components/game/HitEffectsLayer.test.tsx","./src/components/game/HitEffectsLayer.tsx","./src/components/game/InputSystem.tsx","./src/components/game/Player.test.tsx","./src/components/game/Player.tsx","./src/components/game/PlayerVisuals.test.tsx","./src/components/game/PlayerVisuals.tsx","./src/components/ui/CyberpunkButton.tsx","./src/components/ui/CyberpunkPanel.tsx","./src/hooks/useTexture.ts","./src/test/setup.ts","./src/types/pixi-react.d.ts"],"version":"5.8.3"} \ No newline at end of file