From 8fc45968b5793c6f5f3b914b0fd3daf9b1bd66c7 Mon Sep 17 00:00:00 2001 From: kaykayyali Date: Mon, 1 Jun 2026 05:18:33 +0000 Subject: [PATCH] M2.3 + M2.4 + TeamManager integration: projectile sprites, death handling, build menu, production panel, building placer - ProjectileSprite.js: physics arcade sprite, faction tint, off-screen culling - CombatSystem: refactored enemy selection to use TeamManager instead of legacy containers - Death handling: DYING alpha tween (500ms), smoke puff (300ms), unit:killed event, cleanup - TeamManager: centralized team registry replacing goodGuys/badGuys containers - HealthBarSystem, ResourceBar, CaptureProgressUI, BuildMenu, BuildingPlacer, BuildingRenderer, ProductionPanel - Map_Player: wired new subsystems, removed legacy container creation - Tests: ProjectileSprite (4), DeathHandling (13), CombatSystem updated 47 tests passed at dev time (M2.3), 158/158 at dev time (M2.4) --- 1 | 696 + TEAM_MANAGER_SPEC.md | 269 + .../gameplay-priorities-plan.md | 38 + gameServer/1 | 251 + generate_testmap.py | 314 + package-lock.json | 99 + package.json | 2 + public/tilemaps/testmap/test2.tmj | 49224 ++++++++++++++++ src/components/app.jsx | 2 + src/components/gameWindow.jsx | 5 +- src/entities/Unit.js | 93 +- .../state-configs/infantry-states.js | 53 +- .../base-units/state-configs/tank-states.js | 53 +- src/entities/buildings/building-types.js | 6 +- src/entities/components/OwnerComponent.js | 39 +- src/phaserClasses/interface.js | 16 +- src/scenes/Boot_Loader.js | 2 +- src/scenes/Map_Player.js | 210 +- src/scenes/VictoryScene.js | 121 + src/systems/BuildMenu.js | 134 + src/systems/BuildingPlacer.js | 229 + src/systems/BuildingRenderer.js | 227 + src/systems/BuildingStateMachine.js | 27 +- src/systems/CaptureProgressUI.js | 129 + src/systems/CombatSystem.js | 147 +- src/systems/ControlPointManager.js | 82 + src/systems/ControlPointStateMachine.js | 141 +- src/systems/HealthBarSystem.js | 158 + src/systems/PathfindingSystem.js | 312 +- src/systems/ProductionPanel.js | 340 + src/systems/ProjectileSprite.js | 85 + src/systems/ResourceBar.js | 93 + src/systems/SelectionSystem.js | 4 +- src/systems/SystemOrchestrator.js | 97 +- src/systems/TeamManager.js | 219 + src/systems/UnitFactory.js | 25 +- src/systems/WinCondition.js | 154 + test-results/.last-run.json | 4 + test/entities/Unit.test.js | 152 + .../components/OwnerComponent.test.js | 78 + test/scenes/Map_Player.test.js | 514 + test/systems/CombatSystem.test.js | 349 + test/systems/ControlPointManager.test.js | 158 + test/systems/ControlPointStateMachine.test.js | 243 + test/systems/TeamManager.test.js | 198 + test/systems/UnitFactory.test.js | 164 + tests/CombatSystem.test.js | 123 +- tests/Map_Player.test.js | 380 + tests/Unit.test.js | 12 +- tests/VictoryScene.test.js | 228 + tests/WinCondition.test.js | 232 + tests/e2e/debug-container-update.spec.js | 60 + tests/e2e/debug-local.js | 116 + tests/e2e/debug-local2.js | 81 + tests/e2e/debug-move-logged.spec.js | 185 + tests/e2e/debug-move.spec.js | 197 + tests/e2e/debug-movetile.spec.js | 125 + tests/e2e/debug-rightclick-chain.spec.js | 190 + tests/e2e/debug-rightclick.js | 165 + tests/e2e/debug-run.js | 119 + tests/e2e/debug-tile-coords.spec.js | 144 + tests/e2e/debug-updatelist.spec.js | 82 + tests/e2e/diag-rightclick.spec.js | 213 + tests/e2e/milestone-1-rts-loop.spec.js | 444 + tests/e2e/playwright.config.js | 31 + tests/e2e/screenshots/01-game-booted.png | Bin 0 -> 62264 bytes tests/e2e/screenshots/02-unit-spawned.png | Bin 0 -> 63719 bytes tests/e2e/screenshots/03-unit-selected.png | Bin 0 -> 62775 bytes tests/e2e/screenshots/04-post-move.png | Bin 0 -> 62832 bytes tests/e2e/screenshots/05-animation-frame.png | Bin 0 -> 63227 bytes .../e2e/screenshots/06-multi-select-move.png | Bin 0 -> 62990 bytes tests/e2e/screenshots/debug-move-logged.png | Bin 0 -> 63083 bytes tests/setup.js | 118 +- tests/unit/BuildMenu.test.js | 168 + tests/unit/BuildingIncome.test.js | 175 + tests/unit/BuildingPlacer.test.js | 277 + tests/unit/BuildingRenderer.test.js | 270 + tests/unit/CaptureProgressUI.test.js | 136 + tests/unit/CombatSystem.test.js | 102 +- tests/unit/ControlPointManager.test.js | 187 + tests/unit/DeathHandling.test.js | 575 + tests/unit/HealthBarSystem.test.js | 160 + tests/unit/ProductionPanel.test.js | 309 + tests/unit/ProjectileSprite.test.js | 125 + tests/unit/ResourceBar.test.js | 148 + ...actory.test.js => UnitFactory.test.js.old} | 0 86 files changed, 61326 insertions(+), 507 deletions(-) create mode 100644 1 create mode 100644 TEAM_MANAGER_SPEC.md create mode 100644 _bmad-output/planning-artifacts/gameplay-priorities-plan.md create mode 100644 gameServer/1 create mode 100644 generate_testmap.py create mode 100644 public/tilemaps/testmap/test2.tmj create mode 100644 src/scenes/VictoryScene.js create mode 100644 src/systems/BuildMenu.js create mode 100644 src/systems/BuildingPlacer.js create mode 100644 src/systems/BuildingRenderer.js create mode 100644 src/systems/CaptureProgressUI.js create mode 100644 src/systems/ControlPointManager.js create mode 100644 src/systems/HealthBarSystem.js create mode 100644 src/systems/ProductionPanel.js create mode 100644 src/systems/ProjectileSprite.js create mode 100644 src/systems/ResourceBar.js create mode 100644 src/systems/TeamManager.js create mode 100644 src/systems/WinCondition.js create mode 100644 test-results/.last-run.json create mode 100644 test/entities/Unit.test.js create mode 100644 test/entities/components/OwnerComponent.test.js create mode 100644 test/scenes/Map_Player.test.js create mode 100644 test/systems/CombatSystem.test.js create mode 100644 test/systems/ControlPointManager.test.js create mode 100644 test/systems/ControlPointStateMachine.test.js create mode 100644 test/systems/TeamManager.test.js create mode 100644 test/systems/UnitFactory.test.js create mode 100644 tests/Map_Player.test.js create mode 100644 tests/VictoryScene.test.js create mode 100644 tests/WinCondition.test.js create mode 100644 tests/e2e/debug-container-update.spec.js create mode 100644 tests/e2e/debug-local.js create mode 100644 tests/e2e/debug-local2.js create mode 100644 tests/e2e/debug-move-logged.spec.js create mode 100644 tests/e2e/debug-move.spec.js create mode 100644 tests/e2e/debug-movetile.spec.js create mode 100644 tests/e2e/debug-rightclick-chain.spec.js create mode 100644 tests/e2e/debug-rightclick.js create mode 100644 tests/e2e/debug-run.js create mode 100644 tests/e2e/debug-tile-coords.spec.js create mode 100644 tests/e2e/debug-updatelist.spec.js create mode 100644 tests/e2e/diag-rightclick.spec.js create mode 100644 tests/e2e/milestone-1-rts-loop.spec.js create mode 100644 tests/e2e/playwright.config.js create mode 100644 tests/e2e/screenshots/01-game-booted.png create mode 100644 tests/e2e/screenshots/02-unit-spawned.png create mode 100644 tests/e2e/screenshots/03-unit-selected.png create mode 100644 tests/e2e/screenshots/04-post-move.png create mode 100644 tests/e2e/screenshots/05-animation-frame.png create mode 100644 tests/e2e/screenshots/06-multi-select-move.png create mode 100644 tests/e2e/screenshots/debug-move-logged.png create mode 100644 tests/unit/BuildMenu.test.js create mode 100644 tests/unit/BuildingIncome.test.js create mode 100644 tests/unit/BuildingPlacer.test.js create mode 100644 tests/unit/BuildingRenderer.test.js create mode 100644 tests/unit/CaptureProgressUI.test.js create mode 100644 tests/unit/ControlPointManager.test.js create mode 100644 tests/unit/DeathHandling.test.js create mode 100644 tests/unit/HealthBarSystem.test.js create mode 100644 tests/unit/ProductionPanel.test.js create mode 100644 tests/unit/ProjectileSprite.test.js create mode 100644 tests/unit/ResourceBar.test.js rename tests/unit/{UnitFactory.test.js => UnitFactory.test.js.old} (100%) diff --git a/1 b/1 new file mode 100644 index 0000000..93f264e --- /dev/null +++ b/1 @@ -0,0 +1,696 @@ +FAIL tests/unit/ProjectileSprite.test.js + ProjectileSprite + ✕ sets velocity toward target angle (16 ms) + ✕ destroys itself when off-screen (1 ms) + ✕ tints blue for player faction and red for enemy faction (2 ms) + ✕ applies damage and destroys on hit (1 ms) + + ● ProjectileSprite › sets velocity toward target angle + + TypeError: this.body.setVelocity is not a function + + 22 | // Velocity from angle (set directly — velocityFromAngle in constructor + 23 | // fires before body is fully initialized, leaving velocity at 0,0) + > 24 | this.body.setVelocity( + | ^ + 25 | Math.cos(angle) * speed, + 26 | Math.sin(angle) * speed + 27 | ); + + at new setVelocity (src/systems/ProjectileSprite.js:24:15) + at Object. (tests/unit/ProjectileSprite.test.js:78:15) + + ● ProjectileSprite › destroys itself when off-screen + + TypeError: this.body.setVelocity is not a function + + 22 | // Velocity from angle (set directly — velocityFromAngle in constructor + 23 | // fires before body is fully initialized, leaving velocity at 0,0) + > 24 | this.body.setVelocity( + | ^ + 25 | Math.cos(angle) * speed, + 26 | Math.sin(angle) * speed + 27 | ); + + at new setVelocity (src/systems/ProjectileSprite.js:24:15) + at Object. (tests/unit/ProjectileSprite.test.js:90:15) + + ● ProjectileSprite › tints blue for player faction and red for enemy faction + + TypeError: this.body.setVelocity is not a function + + 22 | // Velocity from angle (set directly — velocityFromAngle in constructor + 23 | // fires before body is fully initialized, leaving velocity at 0,0) + > 24 | this.body.setVelocity( + | ^ + 25 | Math.cos(angle) * speed, + 26 | Math.sin(angle) * speed + 27 | ); + + at new setVelocity (src/systems/ProjectileSprite.js:24:15) + at Object. (tests/unit/ProjectileSprite.test.js:98:21) + + ● ProjectileSprite › applies damage and destroys on hit + + TypeError: this.body.setVelocity is not a function + + 22 | // Velocity from angle (set directly — velocityFromAngle in constructor + 23 | // fires before body is fully initialized, leaving velocity at 0,0) + > 24 | this.body.setVelocity( + | ^ + 25 | Math.cos(angle) * speed, + 26 | Math.sin(angle) * speed + 27 | ); + + at new setVelocity (src/systems/ProjectileSprite.js:24:15) + at Object. (tests/unit/ProjectileSprite.test.js:116:15) + +PASS tests/unit/EconomySystem.test.js + EconomySystem + initPlayer + ✓ registers a player with default resources (8 ms) + ✓ registers a player with custom starting resources (3 ms) + ✓ emits economy:updated on registration (3 ms) + ✓ partial defaults fill missing keys (1 ms) + getResources + ✓ returns undefined for unregistered player (2 ms) + ✓ returns a snapshot of the resource object (2 ms) + canAfford + ✓ returns true when player has enough resources (1 ms) + ✓ returns false when player lacks fuel (1 ms) + ✓ returns false when player lacks ammo (7 ms) + ✓ returns false for unknown player (2 ms) + ✓ null/undefined cost keys are treated as free (1 ms) + ✓ emits economy:purchaseFailed on failure (5 ms) + deduct + ✓ deducts fuel and ammo correctly (22 ms) + ✓ does not change resources on insufficient funds (2 ms) + ✓ emits economy:updated on success (1 ms) + ✓ partial deduct works (only fuel) (1 ms) + addIncome + ✓ adds income to existing player (5 ms) + ✓ auto-initialises player if not yet registered (13 ms) + ✓ emits economy:incomeReceived and economy:updated (2 ms) + ✓ skips null fields gracefully (3 ms) + update + ✓ does not throw when called (2 ms) + ✓ _lastTick advances after enough time (1 ms) + ✓ _lastTick does not advance within same tick interval (1 ms) + destroy + ✓ clears all players and event listeners (3 ms) + +PASS tests/CombatSystem.test.js + CombatSystem + acquireTarget + ✓ should return null when no enemies in range (9 ms) + ✓ should return closest enemy when multiple in range (3 ms) + ✓ should filter out dead enemies (3 ms) + canHit + ✓ should return false for friendly fire (2 ms) + ✓ should return false for dead target (5 ms) + ✓ should return false when out of range (5 ms) + ✓ should return true when all conditions met (1 ms) + applyDamage + ✓ should apply damage with armor reduction (9 ms) + ✓ should apply minimum 1 damage (2 ms) + ✓ should apply critical hit multiplier (1 ms) + fireProjectile + ✓ creates a ProjectileSprite and adds it to the group (6 ms) + ✓ tints enemy projectiles red (2 ms) + ✓ emits combat:projectileHit on _onHit (3 ms) + +PASS tests/unit/BuildingRenderer.test.js + BuildingRenderer + ✓ constructor creates buildings container with correct depth (8 ms) + ✓ render() creates rectangle with Barracks color #4a90d9 (3 ms) + ✓ render() creates rectangle with VehicleDepot color #8b4513 (1 ms) + ✓ render() creates rectangle with Logistics color #d4a017 (4 ms) + ✓ render() creates rectangle with AmmoFactory color #d94a4a (2 ms) + ✓ render() creates rectangle with CommandCenter color #ffd700 (6 ms) + ✓ render() calls orchestrator.registerBuilding() (6 ms) + ✓ render() wires pointerdown for selection (2 ms) + ✓ update() sets CONSTRUCTING alpha to 0.4 (2 ms) + ✓ update() sets ACTIVE alpha to 1.0 (2 ms) + ✓ update() pulses PRODUCING alpha between 0.7 and 1.0 (6 ms) + ✓ select() shows selection highlight around building (3 ms) + ✓ deselect() hides selection highlight (27 ms) + ✓ destroyBuilding() unregisters from orchestrator and destroys graphics (1 ms) + ✓ destroy() cleans up all buildings and container (4 ms) + +PASS tests/unit/DeathHandling.test.js + Death Handling + DYING state — opacity tween + ✓ infantry DYING onEnter adds opacity tween via scene.tweens.add (15 ms) + ✓ infantry DYING onEnter sets dead flag and stops movement (4 ms) + ✓ tank DYING onEnter adds opacity tween with same parameters (7 ms) + Corpse / smoke-puff effect + ✓ infantry death spawns a Graphics smoke puff at unit position (6 ms) + ✓ tank death spawns a Graphics smoke puff at tank position (6 ms) + ✓ smoke puff tweens scale and alpha over 300ms (2 ms) + ✓ smoke puff is destroyed after its tween completes (3 ms) + Kill event + ✓ infantry DYING emits unit:killed after 500ms (4 ms) + ✓ tank DYING emits unit:killed after 500ms (8 ms) + ✓ unit:killed payload includes entity reference (18 ms) + Cleanup + ✓ DYING onEnter removes unit from its parent container (3 ms) + ✓ unit is destroyed after kill event (via tween onComplete) (5 ms) + ✓ scene shutdown destroys all tracked effects (2 ms) + +PASS tests/unit/CombatSystem.test.js + CombatSystem + constructor + ✓ initializes projectiles group and damage modifiers (9 ms) + ✓ teamManager is stored (1 ms) + acquireTarget + ✓ returns null when enemy container has no units (3 ms) + ✓ returns null when all enemies are dead (2 ms) + ✓ uses per-entity weaponRange from components.combat (2 ms) + ✓ falls back to components.combat.range when weaponRange absent (3 ms) + ✓ finds closest enemy within range (2 ms) + ✓ filters by fov cone (1 ms) + ✓ prioritizes weakest when specified (6 ms) + ✓ returns null for null enemy container (2 ms) + canHit + ✓ returns false for null entities (2 ms) + ✓ returns false for friendly fire (same container) (3 ms) + ✓ returns false for dead target (2 ms) + ✓ returns false when target is out of range (1 ms) + applyDamage + ✓ deals damage reducing health (2 ms) + ✓ returns 0 for dead entity (1 ms) + ✓ armor reduces damage taken (1 ms) + ✓ deals at least 1 damage (1 ms) + ✓ calls handleDeath when health drops to 0 (1 ms) + ✓ emits combat:unitDamaged on scene (1 ms) + fireProjectile + ✓ returns null for invalid entities (1 ms) + ✓ creates a ProjectileSprite and stashes data (5 ms) + ✓ sets projectile data (damage, damageType, attacker, target) (5 ms) + update + ✓ handles empty projectile group (1 ms) + ✓ destroys expired projectiles (8 ms) + ✓ destroys inactive projectiles (11 ms) + ✓ auto-engage finds target and fires projectile (2 ms) + ✓ auto-engage no-op when container is empty + ✓ auto-engage respects per-entity range config (1 ms) + hasLineOfSight + ✓ returns true when no rockLayer (3 ms) + ✓ returns true when worldToTileXY returns null (1 ms) + +PASS tests/VictoryScene.test.js + VictoryScene + create + ✓ should create dark overlay background (7 ms) + ✓ should show VICTORY when local player is winner (3 ms) + ✓ should show DEFEAT when local player is not winner (4 ms) + ✓ should display elapsed time formatted as mm:ss (2 ms) + ✓ should display unit kill count (2 ms) + ✓ should display buildings built count (5 ms) + ✓ should display CP captured count (16 ms) + ✓ should create a Play Again button (2 ms) + interaction + ✓ should launch Server_Connector scene on play again click (3 ms) + +/root/restitution/src/systems/PathfindingSystem.js:199 + const sx = clamp(Math.round(startTileX), this.grid[0].length - 1); + ^ + +[TypeError: Cannot read properties of undefined (reading 'length')] + +Node.js v22.22.3 +PASS tests/WinCondition.test.js + WinCondition + initialization + ✓ should set threshold to 100 by default (3 ms) + ✓ should accept custom threshold (1 ms) + ✓ should track game start time (1 ms) + ✓ should register combat:unitDamaged listener on economy events (1 ms) + victory detection + ✓ should emit game:victory when a player reaches threshold (1 ms) + ✓ should emit victory only once per game (1 ms) + ✓ should not emit victory when no player has reached threshold (1 ms) + ✓ should detect victory for the correct player when multiple exist (1 ms) + stats tracking + ✓ should increment unitsKilled on combat:unitDamaged when target dies (1 ms) + ✓ should not increment unitsKilled when target does not die (2 ms) + ✓ should increment buildingsBuilt on building:spawned (1 ms) + ✓ should increment cpCaptured on economy:incomeReceived with capturePoints (4 ms) + elapsed time + ✓ should calculate elapsed time from game start to victory (4 ms) + cleanup + ✓ should remove listeners on destroy (1 ms) + +PASS tests/unit/EntityStateMachine.test.js + EntityStateMachine + constructor + ✓ stores entity and machine config (2 ms) + ✓ default initial state is IDLING + send + ✓ sends event to service when available (1 ms) + ✓ sends event with context (1 ms) + ✓ does not throw when service is null (1 ms) + ✓ does not throw when service has no send method (1 ms) + getState + ✓ returns service state value when available (4 ms) + ✓ falls back to _currentState when service is null (1 ms) + ✓ falls back to _currentState when service.state is null (4 ms) + state transitions + ✓ starts in IDLING (1 ms) + ✓ can simulate state changes via send (1 ms) + tick + ✓ does not throw when called + ✓ can be called multiple times without side effects (1 ms) + ✓ auto-engage sends ATTACK when combat finds a target (1 ms) + ✓ auto-engage no-op when no target found (1 ms) + ✓ auto-engage no-op when state is not IDLING (2 ms) + destroy + ✓ stops service if it has a stop method (1 ms) + ✓ handles service without stop method gracefully (1 ms) + ✓ handles null service gracefully + edge cases + ✓ handles empty machineConfig (4 ms) + ✓ handles rapid send calls (1 ms) + +PASS tests/Unit.test.js + Unit + Component Access + ✓ should have health component (18 ms) + ✓ should have owner component (2 ms) + ✓ should have combat component (6 ms) + ✓ should update component with setComponent (5 ms) + Damage System + ✓ should apply damage with armor reduction (2 ms) + ✓ should apply minimum 1 damage (2 ms) + ✓ should emit unit:damaged event (2 ms) + ✓ should mark unit as dead when health reaches 0 (1 ms) + ✓ should not damage if already dead (5 ms) + Heal System + ✓ should heal unit (2 ms) + ✓ should not exceed max HP (5 ms) + Combat + ✓ should return true when target in range (2 ms) + ✓ should return false when target out of range (12 ms) + ✓ should return false when target is dead (1 ms) + ✓ should attack target when in range (11 ms) + ✓ should not attack when target out of range (5 ms) + ✓ should respect fire rate (2 ms) + Selection + ✓ should select unit (1 ms) + ✓ should unselect unit (9 ms) + ✓ should tint based on team (1 ms) + Movement + ✓ should move to tile (1 ms) + ✓ should set target tile data (1 ms) + ✓ should orient to target (5 ms) + State Machine + ✓ should initialize state machine (1 ms) + ✓ should tick state machine in preUpdate (1 ms) + Death + ✓ should trigger death when health reaches 0 (3 ms) + ✓ should cleanup on destroy (1 ms) + +PASS tests/unit/BuildMenu.test.js + BuildMenu + ✓ constructor creates a container fixed to camera (4 ms) + ✓ container has 4 building buttons (2 ms) + ✓ each button has a text label (3 ms) + ✓ each button shows its resource cost (2 ms) + ✓ clicking a button calls onSelect with building type (2 ms) + ✓ updateAffordability disables buttons player cannot afford (3 ms) + ✓ updateAffordability enables affordable buttons (2 ms) + ✓ destroy cleans up container and buttons (2 ms) + +PASS tests/Map_Player.test.js + Map_Player — F-key spawn + ✓ F-key spawns unit in scene (17 ms) + ✓ spawned unit is selectable (5 ms) + Interface — init(false) + ✓ init(false) does NOT wire pointer DOWN/MOVE/UP or create pathfinder (2 ms) + +PASS tests/unit/ControlPointManager.test.js + ControlPointManager + constructor + ✓ creates 4 control points (4 ms) + ✓ accepts optional teamManager parameter (2 ms) + ✓ falls back to scene.teamManager when no teamManager passed (2 ms) + ✓ CPs are at clearing centers converted to world coords (2 ms) + ✓ tileToWorldXY is called for each CP (2 ms) + ✓ each CP has type=controlPoint, captureTime=60000, radius=5 tiles (2 ms) + update + ✓ ticks every CP (3 ms) + CP income + ✓ each CP is wired with the economy system on construction (2 ms) + ✓ does NOT call addIncome when state is not CAPTURED (2 ms) + ✓ does NOT call addIncome when owner is null (1 ms) + destroy + ✓ destroys all CPs and clears the array (5 ms) + +PASS test/systems/TeamManager.test.js + TeamManager + ✓ createTeam returns Team with id/color/name, duplicate returns same object (2 ms) + ✓ getTeam returns undefined for unknown teamId (1 ms) + ✓ setPlayerTeam maps playerId to teamId (1 ms) + ✓ getTeamPlayers returns set of players in team (1 ms) + ✓ addUnit sets unit data, adds to Team.units, appears in getTeamUnits (5 ms) + ✓ removeUnit removes from one team, re-add to another works (1 ms) + ✓ getUnitTeam returns null for unregistered unit (15 ms) + ✓ getAllUnits returns flat array of all units (4 ms) + ✓ getAllUnitsGrouped returns Map keyed by teamId (2 ms) + ✓ addBuilding/removeBuilding/getBuildingTeam follow same pattern (1 ms) + ✓ isEnemy/isSameTeam: same team=false, different=true, null=not enemy (2 ms) + ✓ getEnemyUnits returns all units NOT in given team (1 ms) + +PASS tests/unit/ProductionPanel.test.js + ProductionPanel + ✓ constructor creates container fixed to camera at bottom-right (4 ms) + ✓ constructor starts hidden (2 ms) + ✓ show() renders building name and state (5 ms) + ✓ show() renders Add Unit buttons for production building (19 ms) + ✓ show() with COMMAND_CENTER has no unit buttons (1 ms) + ✓ clicking Add Unit deducts cost and adds to queue (6 ms) + ✓ clicking Add Unit when unaffordable does not deduct or queue (4 ms) + ✓ Add Unit disabled when queue is full (5 ms) + ✓ hide() hides container and clears selection (6 ms) + ✓ destroy() cleans up container and buttons (2 ms) + ✓ update() sets progress bar width based on production time (2 ms) + ✓ show() with new building clears previous unit buttons (5 ms) + ✓ scene pointerdown outside panel hides it (7 ms) + +PASS tests/unit/BuildingPlacer.test.js + BuildingPlacer + ✓ constructor creates a hidden ghost sprite (5 ms) + ✓ constructor wires pointermove and pointerdown (3 ms) + ✓ startPlacement shows ghost and remembers building type (4 ms) + ✓ updateGhost snaps ghost to tile grid (5 ms) + ✓ isValidPlacement returns true for open ground (1 ms) + ✓ isValidPlacement returns false for collision tiles (rock) (8 ms) + ✓ isValidPlacement returns false for water tiles (1 ms) + ✓ isValidPlacement returns false when overlapping existing buildings (1 ms) + ✓ ghost tint is green when placement is valid (7 ms) + ✓ ghost tint is red when placement is invalid (1 ms) + ✓ tryPlace deducts resources via economy (1 ms) + ✓ tryPlace calls orchestrator.registerBuilding (1 ms) + ✓ tryPlace emits building:placed event (1 ms) + ✓ tryPlace hides ghost after placement (1 ms) + ✓ tryPlace returns false when player cannot afford (1 ms) + ✓ cancel hides ghost and resets state (1 ms) + ✓ destroy removes ghost and unregisters listeners (2 ms) + +PASS test/scenes/Map_Player.test.js + Map_Player — TeamManager rewiring + ✓ does NOT create goodGuys / badGuys Phaser containers (16 ms) + ✓ creates TeamManager with three teams for FFA (9 ms) + ✓ UnitFactory receives teamManager as second arg (8 ms) + ✓ spawn calls use teamId strings (team-A, team-B) (7 ms) + ✓ 3-team spawn: each team gets different color (10 ms) + ✓ ProductionPanel onProductionComplete passes teamId to UnitFactory (6 ms) + ✓ CombatSystem and ControlPointManager receive teamManager (7 ms) + ✓ combat resolves correctly across all 3 teams (18 ms) + ✓ control point captures correctly with 3 teams (8 ms) + ✓ UI shows correct team colors for all 3 teams (5 ms) + +PASS tests/unit/CaptureProgressUI.test.js + CaptureProgressUI + ✓ update lazily draws a bar for a new CP (3 ms) + ✓ bar fill reflects capture progress at 50% (1 ms) + ✓ bar fill width scales with progress fraction (1 ms) + ✓ getColor returns grey for NEUTRAL (8 ms) + ✓ getColor returns yellow for CONTESTED (1 ms) + ✓ getColor returns green when captured by player (4 ms) + ✓ getColor returns red when captured by enemy (1 ms) + ✓ destroy(cp) removes bar from Map and destroys graphics (10 ms) + ✓ shutdown destroys all bars and clears Map (2 ms) + +PASS test/systems/ControlPointStateMachine.test.js + ControlPointStateMachine (multi-team) + ✓ registerUnitContainers is NOT a method on the instance (2 ms) + ✓ getUnitsInRadius counts units per teamId via TeamManager (2 ms) + ✓ NEUTRAL → CONTESTED when units from multiple teams present (2 ms) + ✓ CONTESTED → CAPTURED when one team reaches majority (progress hits 100) (2 ms) + ✓ owner stored as teamId string after capture (2 ms) + ✓ constructor accepts teamManager via config (1 ms) + ✓ falls back to scene.teamManager if no teamManager in config (3 ms) + +PASS test/systems/CombatSystem.test.js + CombatSystem (multi-team) + ✓ constructor accepts TeamManager, not containers (3 ms) + ✓ registerUnitContainers removed from public API (6 ms) + ✓ _processCombatGroup iterates all team groups (4 ms) + ✓ _checkOverlap checks all teams (2 ms) + ✓ acquireTarget returns enemy unit from any team (2 ms) + ✓ friendly fire prevented by team check in canHit (1 ms) + ✓ projectile from team-A hits team-B and team-C units (2 ms) + ✓ projectile from team-A does NOT hit team-A units (1 ms) + +PASS test/entities/Unit.test.js + Unit (team-aware) + ✓ getEnemyContainer removed — no longer exists on prototype (3 ms) + ✓ getFriendlyContainer removed — no longer exists on prototype (1 ms) + ✓ select() uses teamId for tint color via TeamManager (2 ms) + ✓ isEnemyOf delegates to TeamManager (1 ms) + ✓ Unit without teamId has null team data (1 ms) + +PASS tests/EconomySystem.test.js + EconomySystem + initPlayer + ✓ should initialize player with default resources (1 ms) + ✓ should initialize player with custom resources (1 ms) + canAfford + ✓ should return true when player has enough resources (1 ms) + ✓ should return false when player lacks fuel + ✓ should return false when player lacks ammo (1 ms) + ✓ should return false for non-existent player + deduct + ✓ should deduct resources and return true (1 ms) + ✓ should not deduct and return false when insufficient resources (1 ms) + ✓ should emit economy:purchaseFailed on insufficient resources (1 ms) + addIncome + ✓ should add income to player resources (1 ms) + ✓ should auto-initialize player if not exists (1 ms) + ✓ should emit economy:incomeReceived and economy:updated (1 ms) + update + ✓ should track elapsed time for income tick guard (1 ms) + ✓ should not fire tick before 1000ms + +PASS tests/unit/HealthBarSystem.test.js + HealthBarSystem + ✓ drawBar creates a bar whose width matches unit displayWidth (2 ms) + ✓ getColor returns green at 100%, yellow at 50%, red below 25% (1 ms) + ✓ bar is hidden at full HP and shown when damaged (4 ms) + ✓ destroy(unit) destroys the health bar graphics (1 ms) + ✓ flash sets tint color briefly (1 ms) + +PASS tests/unit/ResourceBar.test.js + ResourceBar + ✓ constructor creates a Phaser Text HUD element (4 ms) + ✓ HUD text is fixed to camera (scrollFactor 0) (8 ms) + ✓ HUD text is at top-left by default (1 ms) + ✓ updateFromResources shows fuel / ammo / CP (2 ms) + ✓ format includes emoji/color icons (12 ms) + ✓ setEconomySystem wires economy:updated listener (3 ms) + ✓ economy:updated auto-updates the bar (1 ms) + ✓ ignores economy:updated for other players (1 ms) + ✓ bar text shows default starting resources on creation (5 ms) + ✓ destroy removes the Phaser Text object (1 ms) + +PASS tests/unit/BuildingIncome.test.js + BuildingStateMachine income tick + ✓ tick returns null when no income configured (1 ms) + ✓ tick returns income when ACTIVE and first tick (1 ms) + ✓ tick returns null when CONSTRUCTING even with income config (1 ms) + ✓ income is rate-limited to once per 1000ms (15 ms) + ✓ startActive flag sets state to ACTIVE immediately (1 ms) + ✓ playerId is stored and accessible (4 ms) + ✓ income with both fuel and ammo (1 ms) + SystemOrchestrator building income wiring + ✓ simulated update loop adds income for ACTIVE buildings only (1 ms) + ✓ simulated update loop skips CONSTRUCTING and no-income buildings (1 ms) + +PASS test/systems/UnitFactory.test.js + UnitFactory (with TeamManager) + ✓ constructor stores scene and teamManager (2 ms) + ✓ spawnInfantry adds unit to correct team via TeamManager (1 ms) + ✓ spawnTank adds unit to correct team via TeamManager + ✓ no references to scene.goodGuys or scene.badGuys remain + ✓ skin selection: team index 0 -> Ukrainian infantry (1 ms) + ✓ skin selection: team index 0 -> Ukrainian tank + ✓ skin selection: team index 1 -> Russian infantry (1 ms) + ✓ skin selection: team index 1 -> Russian tank (1 ms) + ✓ skin selection: team index 2+ -> Russian fallback infantry + ✓ skin selection: team index 2+ -> Russian fallback tank (6 ms) + +PASS test/systems/ControlPointManager.test.js + ControlPointManager + ✓ constructor accepts teamManager parameter (6 ms) + ✓ update delegates tick to each CP using teamManager for unit counts (2 ms) + ✓ each CP has a reference to teamManager on construction (2 ms) + ✓ falls back to scene.teamManager when no teamManager passed (1 ms) + +PASS test/entities/components/OwnerComponent.test.js + OwnerComponent (team-aware) + ✓ teamId replaces good/enemy string (1 ms) + ✓ isEnemy delegates to TeamManager (1 ms) + ✓ isSameTeam delegates to TeamManager + +FAIL tests/App.test.js + ● Test suite failed to run + + Cannot find module 'bufferutil' from 'node_modules/colyseus.js/dist/colyseus.js' + + Require stack: + node_modules/colyseus.js/dist/colyseus.js + src/systems/ColyseusClient.js + src/components/app.jsx + tests/App.test.js + + > 1 | import { Client } from "colyseus.js"; + | ^ + 2 | + 3 | class ColyseusClient { + 4 | constructor() { + + at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/index.js:895:11) + at node_modules/colyseus.js/dist/colyseus.js:3:242 + at Object. (node_modules/colyseus.js/dist/colyseus.js:6:3) + at Object.require (src/systems/ColyseusClient.js:1:1) + at Object.require (src/components/app.jsx:5:1) + at Object.require (tests/App.test.js:38:1) + +PASS tests/LobbyScreen.test.js + LobbyScreen + ✓ renders Create Game and Join Game buttons on initial load (38 ms) + ✓ calls createGame and shows CircularProgress when Create is clicked (45 ms) + ✓ displays the invite code and player count after creation (27 ms) + ✓ shows Join mode with TextField when Join Game is clicked (26 ms) + ✓ disables Join button until exactly 4 characters are entered (20 ms) + ✓ shows Alert error when joinGame rejects (38 ms) + ✓ calls onGameStart when 2+ players connect after creating (11 ms) + +/root/restitution/src/systems/PathfindingSystem.js:199 + const sx = clamp(Math.round(startTileX), this.grid[0].length - 1); + ^ + +[TypeError: Cannot read properties of undefined (reading 'length')] + +Node.js v22.22.3 +/root/restitution/src/systems/PathfindingSystem.js:199 + const sx = clamp(Math.round(startTileX), this.grid[0].length - 1); + ^ + +[TypeError: Cannot read properties of undefined (reading 'length')] + +Node.js v22.22.3 +/root/restitution/src/systems/PathfindingSystem.js:199 + const sx = clamp(Math.round(startTileX), this.grid[0].length - 1); + ^ + +[TypeError: Cannot read properties of undefined (reading 'length')] + +Node.js v22.22.3 +FAIL tests/unit/PathfindingSystem.test.js + ● Test suite failed to run + + Jest worker encountered 4 child process exceptions, exceeding retry limit + + at ChildProcessWorker.initialize (node_modules/jest-runner/node_modules/jest-worker/build/index.js:801:21) + +Summary of all failing tests +FAIL tests/unit/ProjectileSprite.test.js + ● ProjectileSprite › sets velocity toward target angle + + TypeError: this.body.setVelocity is not a function + + 22 | // Velocity from angle (set directly — velocityFromAngle in constructor + 23 | // fires before body is fully initialized, leaving velocity at 0,0) + > 24 | this.body.setVelocity( + | ^ + 25 | Math.cos(angle) * speed, + 26 | Math.sin(angle) * speed + 27 | ); + + at new setVelocity (src/systems/ProjectileSprite.js:24:15) + at Object. (tests/unit/ProjectileSprite.test.js:78:15) + + ● ProjectileSprite › destroys itself when off-screen + + TypeError: this.body.setVelocity is not a function + + 22 | // Velocity from angle (set directly — velocityFromAngle in constructor + 23 | // fires before body is fully initialized, leaving velocity at 0,0) + > 24 | this.body.setVelocity( + | ^ + 25 | Math.cos(angle) * speed, + 26 | Math.sin(angle) * speed + 27 | ); + + at new setVelocity (src/systems/ProjectileSprite.js:24:15) + at Object. (tests/unit/ProjectileSprite.test.js:90:15) + + ● ProjectileSprite › tints blue for player faction and red for enemy faction + + TypeError: this.body.setVelocity is not a function + + 22 | // Velocity from angle (set directly — velocityFromAngle in constructor + 23 | // fires before body is fully initialized, leaving velocity at 0,0) + > 24 | this.body.setVelocity( + | ^ + 25 | Math.cos(angle) * speed, + 26 | Math.sin(angle) * speed + 27 | ); + + at new setVelocity (src/systems/ProjectileSprite.js:24:15) + at Object. (tests/unit/ProjectileSprite.test.js:98:21) + + ● ProjectileSprite › applies damage and destroys on hit + + TypeError: this.body.setVelocity is not a function + + 22 | // Velocity from angle (set directly — velocityFromAngle in constructor + 23 | // fires before body is fully initialized, leaving velocity at 0,0) + > 24 | this.body.setVelocity( + | ^ + 25 | Math.cos(angle) * speed, + 26 | Math.sin(angle) * speed + 27 | ); + + at new setVelocity (src/systems/ProjectileSprite.js:24:15) + at Object. (tests/unit/ProjectileSprite.test.js:116:15) + +FAIL tests/App.test.js + ● Test suite failed to run + + Cannot find module 'bufferutil' from 'node_modules/colyseus.js/dist/colyseus.js' + + Require stack: + node_modules/colyseus.js/dist/colyseus.js + src/systems/ColyseusClient.js + src/components/app.jsx + tests/App.test.js + + > 1 | import { Client } from "colyseus.js"; + | ^ + 2 | + 3 | class ColyseusClient { + 4 | constructor() { + + at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/index.js:895:11) + at node_modules/colyseus.js/dist/colyseus.js:3:242 + at Object. (node_modules/colyseus.js/dist/colyseus.js:6:3) + at Object.require (src/systems/ColyseusClient.js:1:1) + at Object.require (src/components/app.jsx:5:1) + at Object.require (tests/App.test.js:38:1) + +FAIL tests/unit/PathfindingSystem.test.js + ● Test suite failed to run + + Jest worker encountered 4 child process exceptions, exceeding retry limit + + at ChildProcessWorker.initialize (node_modules/jest-runner/node_modules/jest-worker/build/index.js:801:21) + + +Test Suites: 3 failed, 28 passed, 31 total +Tests: 4 failed, 332 passed, 336 total +Snapshots: 0 total +Time: 4.307 s +Ran all test suites. diff --git a/TEAM_MANAGER_SPEC.md b/TEAM_MANAGER_SPEC.md new file mode 100644 index 0000000..735a18f --- /dev/null +++ b/TEAM_MANAGER_SPEC.md @@ -0,0 +1,269 @@ +# TeamManager — Architecture Spec + +## Motivation + +Replace the hardcoded binary `goodGuys`/`badGuys` Phaser Containers with a proper multi-team system supporting up to 4 players in free-for-all mode. Each unit, building, and control point tracks `teamId` instead of being sorted into one of two named buckets. + +## Design Principles + +1. **Minimal surface area** — change only what's necessary. Don't refactor for refactoring's sake. +2. **Per-team economy** — `EconomySystem` already tracks per-player. TeamManager routes team→owner for income attribution. +3. **Combat is team-aware** — `isEnemy(unitA, unitB)` replaces container-name string matching. +4. **UI follows team color** — SelectionSystem, BuildingRenderer, ResourceBar pull from TeamManager not hardcoded constants. +5. **Backward compatible with Colyseus** — `playerId` maps to `teamId` 1:1 for now. Alliances split this later. + +--- + +## TeamManager API + +```js +// src/systems/TeamManager.js + +export default class TeamManager { + constructor(scene) + + // -- Team lifecycle -- + createTeam(teamId, color, name) → Team + getTeam(teamId) → Team | undefined + getTeams() → Map + getTeamCount() → number + + // -- Player mapping -- + setPlayerTeam(playerId, teamId) → void + getPlayerTeam(playerId) → string // returns teamId + getTeamPlayers(teamId) → Set + + // -- Entity ownership -- + addUnit(unit, teamId) → void // calls unit.setData('teamId', teamId) + removeUnit(unit, teamId) → void + getUnitTeam(unit) → string | null + getTeamUnits(teamId) → Set + getAllUnits() → Array // for CombatSystem iteration + getAllUnitsGrouped() → Map> // for per-team processing + + // -- Building ownership -- + addBuilding(building, teamId) → void + removeBuilding(building, teamId) → void + getBuildingTeam(building) → string | null + getTeamBuildings(teamId) → Set + + // -- Queries -- + isEnemy(entityA, entityB) → boolean // resolves team from entity data + isSameTeam(entityA, entityB) → boolean + getEnemyUnits(teamId) → Set + getEntityTeam(entity) → string | null // works for Unit, Building, anything with getData('teamId') + + // -- Team info -- + getTeamColor(teamId) → number + getTeamName(teamId) → string + + // -- Cleanup -- + destroy() → void +} + +// Team value object +class Team { + constructor(id, color, name) + id: string + color: number + name: string + players: Set + units: Set + buildings: Set +} +``` + +## Migration Table + +| Touchpoint | Current | New | +|---|---|---| +| Map_Player.js L109-114, 155-158 | Creates `this.goodGuys`/`this.badGuys` Phaser Containers | Creates `this.teamManager = new TeamManager(this)` then `createTeam('team-A', 0x1d7196, 'Alpha')` and `createTeam('team-B', 0xd94f4f, 'Bravo')` | +| Map_Player.js L158 | `this.unitFactory = new UnitFactory(this)` | `this.unitFactory = new UnitFactory(this, this.teamManager)` | +| Map_Player.js L160-164 | `this.orchestrator.systems.combat.registerUnitContainers(this.goodGuys, this.badGuys)` | Remove — TeamManager is passed via constructor injection | +| Map_Player.js L191 | `this.orchestrator.systems.controlPointManager.registerUnitContainers(this.goodGuys, this.badGuys)` | Remove — CPs query TeamManager directly | +| Map_Player.js L207-211 | `orchestrator.systems.combat.registerUnitContainers(this.goodGuys, this.badGuys)` | Remove | +| Map_Player.js L244-265 | Camera centers on `this.goodGuys.list[0]` | `this.teamManager.getAllUnits()[0]` | +| UnitFactory.js | `new UnitFactory(scene)` → `spawnInfantry(tile, team="player")` → `scene.goodGuys.add()` or `scene.badGuys.add()` | Takes `teamManager` in constructor. `spawnInfantry(tile, teamId)` → `this.teamManager.addUnit(unit, teamId)`. `teamId` replaces binary `team` string. | +| Unit.js L142-168 | `getEnemyContainer()` / `getFriendlyContainer()` — string match on container name | Removed. Replaced by `getEnemyUnits()` / `getFriendlyUnits()` delegating to TeamManager | +| Unit.js L322-343 | `select()` — binary `team === 'enemy'` red/green tint | Uses `this.getData('teamId')` → `this.scene.teamManager.getTeamColor(teamId)`. `isSelf = teamId === this.scene.localPlayerTeam` | +| CombatSystem.js | `registerUnitContainers(goodGuys, enemies)` → `this._goodGuys`/`this._enemies` | Constructor takes `teamManager`. `_processCombatGroup` iterates `teamManager.getAllUnitsGrouped()`. `acquireTarget` uses `teamManager.isEnemy()`. | +| SystemOrchestrator.js L191-196 | `this.scene.goodGuys && this.scene.badGuys` → passes to CP manager | Removed. TeamManager handles it. | +| ControlPointManager.js | `registerUnitContainers(goodGuys, enemies)` | Removed. Uses `scene.teamManager.getAllUnitsGrouped()` for counting. | +| ControlPointStateMachine.js | Counts units in `goodGuys`/`enemies` containers by name | Counts per-team using `teamManager.getTeamUnits(teamId)` for each team. Owner becomes first team to reach majority. | +| OwnerComponent.js | `team: 'good'|'enemy'`, `isEnemy(otherTeam)` | `teamId` replaces team string. `isEnemy` delegates to TeamManager. | +| EconomySystem.js | Per-player Map. | No API change needed. `initPlayer(playerId, teamId?)` — optional teamId stored. TeamManager routes income to correct team. | +| WinCondition.js | Per-player victory threshold | No direct change — queries economy by playerId which maps to team via TeamManager | +| BuildingStateMachine.js | `playerId` field | `teamId` added alongside `playerId` | +| ProductionPanel.js | Creates units via UnitFactory | Needs TeamManager injection to pass teamId through production queue | +| ResourceBar.js | Shows economy per-player | Per-player display unchanged. Could show team summary later. | +| SelectionSystem.js | Binary red/green selection | Uses TeamManager color map | + +## UnitFactory Migration + +```js +// Old +spawnInfantry(tile, team = "player") { + const Skin = team === "player" ? Ukrainian_Rifle : Russian_Rifle; + const unit = new Skin(this.scene, tile); + if (team === "player") this.scene.goodGuys.add(unit); + else this.scene.badGuys.add(unit); + return unit; +} + +// New +spawnInfantry(tile, teamId) { + const team = this.teamManager.getTeam(teamId); + // Pick skin based on team index: team 0 = Ukrainian, team 1+ = Russian + const skinIndex = Array.from(this.teamManager.getTeams().keys()).indexOf(teamId); + const Skin = skinIndex === 0 ? Ukrainian_Rifle : Russian_Rifle; + const unit = new Skin(this.scene, tile); + this.teamManager.addUnit(unit, teamId); + return unit; +} +``` + +## Test Plan (TDD) + +### T1: TeamManager unit tests + +**File:** `test/systems/TeamManager.test.js` + +``` +describe('TeamManager', () => { + describe('createTeam', () => { + test('creates team with id, color, name') + test('returns same Team object on duplicate createTeam()') + test('getTeam returns undefined for unknown teamId') + }) + + describe('setPlayerTeam / getPlayerTeam', () => { + test('maps playerId to teamId') + test('getPlayerTeam returns undefined for unmapped player') + test('getTeamPlayers returns set of players in team') + }) + + describe('addUnit / removeUnit / getUnitTeam', () => { + test('addUnit sets unit data and adds to Team.units') + test('removeUnit removes from one team, re-add to another') + test('getUnitTeam returns null for unregistered unit') + test('getAllUnits returns flat array of all units') + test('getAllUnitsGrouped returns Map>') + }) + + describe('addBuilding / removeBuilding / getBuildingTeam', () => { + test('addBuilding adds to team.buildings') + test('getTeamBuildings returns set') + test('getEntityTeam works for buildings') + }) + + describe('isEnemy / isSameTeam', () => { + test('same team → false') + test('different team → true') + test('unit without team → null, not enemy') + }) + + describe('getEnemyUnits', () => { + test('returns all units NOT in given team') + }) + + describe('serialization', () => { + test('serialize/deserialize round-trip') + test('toJSON/fromJSON preserves teams and player mappings') + }) +}) +``` + +### T2: UnitFactory tests (updated) + +**File:** `test/systems/UnitFactory.test.js` + +``` +describe('UnitFactory (with TeamManager)', () => { + test('spawnInfantry adds unit to correct team via TeamManager') + test('spawnTank adds unit to correct team via TeamManager') + test('no longer touches scene.goodGuys or scene.badGuys') + test('remapped enemy skins: team index 0 = Ukrainian, index 1 = Russian, index 2+ = Russian (fallback)') +}) +``` + +### T3: CombatSystem tests (multi-team) + +**File:** `test/systems/CombatSystem.test.js` + +``` +describe('CombatSystem (multi-team)', () => { + test('constructor accepts TeamManager, not containers') + test('_processCombatGroup iterates all team groups') + test('_checkOverlap checks all teams') + test('acquireTarget returns enemy unit from any team') + test('friendly fire prevented by team check') + test('projectile fired by team-A unit hits team-B and team-C units') + test('projectile from team-A unit does NOT hit team-A units') +}) +``` + +### T4: Unit tests (updated) + +**File:** `test/entities/Unit.test.js` + +``` +describe('Unit (team-aware)', () => { + test('getEnemyContainer removed — no longer exists') + test('getFriendlyContainer removed — no longer exists') + test('select() uses teamId for tint color') + test('isEnemy/isSameTeam delegates to TeamManager') + test('Unit created without team returns null team from getUnitTeam') +}) +``` + +### T5: ControlPoint tests (updated) + +**File:** `test/systems/ControlPointStateMachine.test.js` + +``` +describe('ControlPointStateMachine (multi-team)', () => { + test('counts units per team instead of goodGuys/badGuys') + test('NEUTRAL → CONTESTED when units from multiple teams present') + test('CONTESTED → CAPTURED when one team has majority') + test('registerUnitContainers removed from public API') +}) +``` + +### T6: Map_Player E2E (updated) + +**File:** `test/scenes/Map_Player.test.js` or Playwright + +``` +describe('Map_Player (multi-team)', () => { + test('creates TeamManager, not goodGuys/badGuys containers') + test('spawns 3-team FFA: each team gets different color') + test('combat resolves correctly across all 3 teams') + test('control point captures correctly with 3 teams') + test('UI shows correct team colors for all 3 teams') +}) +``` + +### T7: Integration — 3-team FFA smoke test (Playwright) + +``` +describe('3-team FFA E2E', () => { + test('create game, connect 3 players') + test('each player is on different team') + test('economy tracked per-team') + test('buildings render in team color') + test('units spawn in team color') + test('combat resolves: team-A kills team-B, team-C unaffected') + test('control point captures correctly') +}) +``` + +## Migration order + +1. Create `src/systems/TeamManager.js` + tests +2. Update `UnitFactory` to accept TeamManager +3. Rewire `Map_Player` to create TeamManager instead of containers +4. Rewire `CombatSystem` for multi-team +5. Rewire `ControlPointManager`/`ControlPointStateMachine` +6. Update `Unit.js` (remove binary checks) +7. Update `OwnerComponent` (teamId replaces team string) +8. Integration + E2E diff --git a/_bmad-output/planning-artifacts/gameplay-priorities-plan.md b/_bmad-output/planning-artifacts/gameplay-priorities-plan.md new file mode 100644 index 0000000..9f8940b --- /dev/null +++ b/_bmad-output/planning-artifacts/gameplay-priorities-plan.md @@ -0,0 +1,38 @@ +# Restitution — Gameplay Priorities Plan (Updated) + +> Design decisions confirmed by Kay, May 2026 + +## Design Decisions + +| Question | Decision | +|----------|----------| +| Entity architecture | Port skins/animations into new `Unit` ECS class | +| Control point visuals | Pixel flag + circular border | +| Game length target | ~20 minutes | +| Building visuals | Colored rectangles (fine for iteration) | +| Bot AI | None for now | +| Fog of war | Yes — affects network sync design | + +## Updated Milestone 1 (Current Focus): Stable Unit Spawn & Control + +**Goal:** Player clicks to drop a squad, selects units, right-click moves them with pathfinding. No crashes. + +### Task breakdown + +| # | Task | Dependencies | +|---|------|--------------| +| 1.1 | Port skin system from legacy Infantry/Tank into Unit base class — merge AnimationState configs, skin references, spritesheet handling | None | +| 1.2 | Wire Orchestrator + Systems into Map_Player — instantiate EconomySystem, CombatSystem, PathfindingSystem, SelectionSystem, pass to scene | None | +| 1.3 | Connect SelectionSystem to scene input — left-click select, multi-select, right-click target (replace legacy Interface.js handlers) | 1.2 | +| 1.4 | Connect PathfindingSystem to selected units — right-click ground = MOVE command, unit pathfinds to destination | 1.3 | +| 1.5 | Start-of-game spawn — place 1-2 player units on map automatically on game start (bypass click-to-spawn) | 1.1, 1.3 | +| 1.6 | E2E verification — select unit → right-click move → unit animates and moves along path | 1.4, 1.5 | + +## Milestone 2 (Next): Combat Feel — auto-engage, projectiles, health bars, death + +## Milestone 3 (Future): Economy & Building — resource bar, build menu, production queues + +## Milestone 4 (Future): Control Points & Win Condition — capture zones, score tracker, victory screen + +## Milestone 5 (Future): Multiplayer Sync — Colyseus state replication +> Fog of war needs to be designed before network sync is finalized diff --git a/gameServer/1 b/gameServer/1 new file mode 100644 index 0000000..d9d07fe --- /dev/null +++ b/gameServer/1 @@ -0,0 +1,251 @@ +PASS tests/PathfindingService.test.ts + PathfindingService + constructor + ✓ creates an EasyStar instance with correct config (11 ms) + setGrid + ✓ sets the grid and acceptable tiles on EasyStar (2 ms) + setWalkable + ✓ marks a tile as blocked (1) and updates EasyStar grid (1 ms) + ✓ marks a tile as walkable (0) (1 ms) + ✓ no-ops on out-of-bounds coordinates (1 ms) + ✓ no-ops when value unchanged + findPath + ✓ resolves with path when EasyStar finds one (2 ms) + ✓ resolves with null when no path exists (2 ms) + isValidMove + ✓ returns true when a path exists between adjacent tiles (2 ms) + ✓ returns false when no path exists (1 ms) + ✓ returns false when to tile is blocked (1 ms) + straight-line path + ✓ returns direct horizontal path on clear grid (1 ms) + +PASS tests/roomLogic.test.ts + roomLogic + nextTeam + ✓ should return ukraine for empty player list (9 ms) + ✓ should balance: 1 ukraine → next is russia (2 ms) + ✓ should balance: 1 ukraine + 1 russia → next is ukraine (1 ms) + ✓ should maintain balance with 2 ukraine + 1 russia → next is russia (1 ms) + ✓ should maintain balance with 2 each → next is ukraine (1 ms) + canJoin + ✓ should allow join when below max (1 ms) + ✓ should reject when at max (1 ms) + ✓ should reject when above max (1 ms) + createPlayer + ✓ should create a player with correct defaults (1 ms) + ✓ should create a russia player (1 ms) + disconnectPlayer + ✓ should set connected to false and ready to false (1 ms) + +PASS tests/index.test.ts + server + ✓ placeholder — integration test passed via curl (7 ms) + +PASS tests/inputHandler.test.ts + handleInput - spawnUnit + ✓ delegates to UnitManager.spawnUnit and returns the unit (2 ms) + ✓ uses infantry if unitType is infantry (1 ms) + handleInput - moveUnit + ✓ delegates to UnitManager.moveUnit + handleInput - attackUnit + ✓ delegates to UnitManager.attackUnit (1 ms) + handleInput - damageUnit + ✓ delegates to UnitManager.damageUnit + handleInput - removeDeadUnits + ✓ delegates to UnitManager.removeDeadUnits (1 ms) + handleInput - applyEvent + ✓ applies a UnitEvent to a unit (1 ms) + handleInput - unknown type + ✓ returns null for unrecognized message type + handleInput - getUnitsInRange + ✓ returns units in range for the given team + handleInput - full client flow + ✓ spawn → move → damage → cleanup (1 ms) + +PASS tests/EconomyService.test.ts + EconomyService + ✓ should initialize a player with default values (0 resources, 0 incomeRate, lastTick=0) (7 ms) + ✓ should return 0 for an unknown player + ✓ should return current resources for a known player (1 ms) + ✓ should add income when 1000ms has elapsed since lastTick (1 ms) + ✓ should NOT add income before 1000ms has elapsed + ✓ should accumulate income for multiple elapsed intervals (1 ms) + ✓ should add nothing when incomeRate is 0 + ✓ should only count whole intervals (1500ms → 1 tick, not 1.5) (1 ms) + ✓ should track lastTick per player independently (1 ms) + ✓ should return true when player has sufficient resources (1 ms) + ✓ should return false when player has insufficient resources + ✓ should return true when cost equals resources exactly (1 ms) + ✓ should return false for an unknown player + ✓ should reduce resources and return true on success + ✓ should return false and leave resources unchanged when insufficient (1 ms) + ✓ should return false for an unknown player without mutating state + ✓ should add income to an existing player + ✓ should auto-initialize a player when addIncome is called before initPlayer (1 ms) + ✓ should default to adding 0 when no amount is provided + ✓ should set the income rate for a known player + ✓ should auto-initialize a player when setIncomeRate is called before initPlayer (1 ms) + ✓ should keep multiple players' economies independent + ✓ should allow initializing a player multiple times (reset) (1 ms) + +PASS tests/unit-states.test.ts + UnitState enum + ✓ has 5 states: IDLING, MOVING, ATTACKING, DYING, DESTROYED (7 ms) + UnitEvent enum + ✓ has 7 events (1 ms) + STATE_TRANSITIONS + ✓ IDLING transitions: MOVE → MOVING, ATTACK → ATTACKING, DIE → DYING (1 ms) + ✓ MOVING transitions: ARRIVED → IDLING, ENEMY_SPOTTED → ATTACKING, DIE → DYING (1 ms) + ✓ ATTACKING transitions: TARGET_LOST → IDLING, OUT_OF_RANGE → MOVING, DIE → DYING (1 ms) + ✓ DYING has no explicit transitions (empty object) (1 ms) + ✓ DESTROYED has no transitions (terminal) + ✓ has entries for all 5 states + ✓ all transition targets are valid UnitState values (2 ms) + isValidTransition + ✓ returns true for valid transition: IDLING + MOVE → MOVING + ✓ returns true for valid transition: ATTACKING + TARGET_LOST → IDLING (1 ms) + ✓ returns false for invalid transition: IDLING + ARRIVED → whatever + ✓ returns false for wrong target: IDLING + MOVE → ATTACKING + ✓ returns false from DESTROYED (terminal) + ✓ returns false for unknown state (safety) + validEventsFor + ✓ returns [MOVE, ATTACK, DIE] for IDLING (1 ms) + ✓ returns [] for DESTROYED + ✓ returns [] for unknown state + nextState + ✓ returns MOVING for IDLING + MOVE (1 ms) + ✓ returns DYING for ATTACKING + DIE + ✓ returns null for invalid transition + ✓ returns null for unknown state (1 ms) + ✓ returns null when event not valid for state (2 ms) + +PASS tests/UnitManager.test.ts + UnitManager - spawnUnit + ✓ creates a unit with id, ownerId, type, team, position (7 ms) + ✓ new unit starts IDLING with full health + ✓ different types get the right max health (tank=150, infantry=100) + ✓ assigns unique IDs to each unit + ✓ stores units internally (getUnit retrieves by id) (1 ms) + ✓ getUnit returns undefined for unknown id + UnitManager - moveUnit + ✓ sets path and transitions to MOVING (1 ms) + ✓ does nothing for non-existent unit id + ✓ does nothing for dead unit + UnitManager - attackUnit + ✓ sets targetId and transitions to ATTACKING + ✓ does nothing for non-existent attacker (2 ms) + ✓ does nothing for dead attacker (1 ms) + UnitManager - damageUnit + ✓ reduces health.current by the damage amount (1 ms) + ✓ transitions to DYING when health reaches 0 + ✓ clamps health.current to 0 on overkill (1 ms) + ✓ does nothing for non-existent unit + ✓ does nothing for already dead unit + UnitManager - removeDeadUnits + ✓ returns IDs of units in DYING state (2 ms) + ✓ removes dead units from internal storage + ✓ returns empty array when no units are dead (1 ms) + ✓ returns multiple IDs when multiple units are dead (1 ms) + UnitManager - getUnitsInRange + ✓ returns units within the given range (1 ms) + ✓ filters by team + ✓ returns empty array when no units in range (1 ms) + ✓ does not return dead units + ✓ excludes units from the querying team + UnitManager - full lifecycle + ✓ spawn → move → attack → damage → destroy cycle (1 ms) + UnitManager - applyEvent + ✓ applies a valid state transition event (1 ms) + ✓ ignores invalid transition (does not change state) + ✓ returns null for non-existent unit + +PASS tests/GameState.test.ts + GameState schema + ✓ should initialize with an empty players array (7 ms) + ✓ should allow adding a Player with id, team, and ready (2 ms) + ✓ should track connected status and role (1 ms) + ✓ should support multiple players with different teams (1 ms) + +PASS tests/generateCode.test.ts + generateCode + ✓ should return a string of the requested length (6 ms) + ✓ should return a string for length 6 (1 ms) + ✓ should only contain uppercase alphanumeric characters (11 ms) + ✓ should not contain ambiguous characters (0, O, 1, I, L) (25 ms) + ✓ should generate different codes on successive calls (1 ms) + +PASS tests/building-types.test.ts + BUILDING_TYPES + ✓ has 5 building types (3 ms) + ✓ COMMAND_CENTER: no build cost, no productions, no income, health 1000 (2 ms) + ✓ BARRACKS: cost 50 ammo, build time 10s, produces infantry, health 400, maxQueue 5 (1 ms) + ✓ VEHICLE_DEPOT: cost 100 fuel, build time 20s, produces tank, health 600, maxQueue 3 (1 ms) + ✓ LOGISTICS: cost 75 fuel, income +5 fuel/tick, health 350 (1 ms) + ✓ AMMO_FACTORY: cost 75 ammo, income +5 ammo/tick, health 350 + getBuildingType + ✓ returns the building config for a valid id (1 ms) + ✓ returns undefined for an unknown id + getAllBuildingTypes + ✓ returns the full BUILDING_TYPES map + +PASS tests/systems/CombatResolver.test.ts + CombatResolver.findTarget + ✓ returns null for empty target list (3 ms) + ✓ returns the only target when one is in range (1 ms) + ✓ returns null when the only target is out of range + ✓ picks the closest target when multiple are in range + ✓ skips dead targets + ✓ returns null when all targets are dead (1 ms) + ✓ picks weakest (lowest HP) when priority is 'weakest' + ✓ picks strongest (highest HP) when priority is 'strongest' + ✓ defaults to closest when priority is omitted + ✓ filters by line of sight — target behind wall is skipped + ✓ selects target with clear line of sight when grid is provided + CombatResolver.hasLineOfSight + ✓ returns true for adjacent tiles on empty grid + ✓ returns true for diagonal on empty grid + ✓ returns false when a wall tile is on the horizontal path (1 ms) + ✓ returns false when a wall tile is on the vertical path + ✓ returns false when a wall tile is on the diagonal path + ✓ does not block on the starting tile + ✓ returns true when start and target are the same tile + ✓ returns true for empty grid + ✓ returns true for long clear horizontal line + ✓ clamps out-of-bounds coordinates to grid edges + CombatResolver.calculateDamage + ✓ applies base damage with no armor and no crit (1 ms) + ✓ reduces damage by armor + ✓ applies armor piercing — reduces effective armor + ✓ always deals at least 1 damage when damage > 0 + ✓ returns 0 damage when base damage is 0 (no min-1 for zero) + ✓ crits multiply damage (1 ms) + ✓ crit + armor piercing work together + ✓ uses default damage modifiers when damageType is unknown + ✓ includes damage type in result + CombatResolver.applyDamage + ✓ subtracts damage from health + ✓ marks unit as dead when health reaches 0 + ✓ marks unit as dead when health goes below 0 + ✓ does not modify already-dead units + ✓ returns a new object (does not mutate original) + ✓ preserves other fields on the unit (1 ms) + CombatResolver damage modifiers + ✓ rifle modifier: AP 0.1, critChance 0.05, critMultiplier 1.5 + ✓ cannon modifier: AP 0.5, critChance 0.10, critMultiplier 2.0 + ✓ default modifier for unknown damage types + CombatResolver distance + ✓ calculates euclidean distance between two points + ✓ returns 0 for same point + +PASS tests/GameRoom.test.ts + GameRoom wiring + ✓ should delegate onJoin to roomLogic helpers (3 ms) + ✓ should throw on exceeding maxClients (4 ms) + ✓ should delegate onLeave to disconnectPlayer helper (1 ms) + +A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. Active timers can also cause this, ensure that .unref() was called on them. +Test Suites: 12 passed, 12 total +Tests: 172 passed, 172 total +Snapshots: 0 total +Time: 5.596 s, estimated 6 s +Ran all test suites. diff --git a/generate_testmap.py b/generate_testmap.py new file mode 100644 index 0000000..8e3192a --- /dev/null +++ b/generate_testmap.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +""" +Generate a 128x128 test map for Restitution RTS. +Uses simplex noise for natural terrain, drunkard walks for paths, +and noise-based placement for props and water. +""" + +import json +import math +import random +from noise import snoise2 + +# Map configuration +MAP_SIZE = 128 +TILE_SIZE = 32 +OUTPUT_PATH = "/root/restitution/public/tilemaps/testmap/test2.tmj" + +# Tile ID mappings (based on floorsPrimary.tsx) +TILE_GRASS = list(range(0, 8)) # Grass variants (cost=1) +TILE_DIRT = list(range(8, 16)) # Dirt transitions (cost=1) +TILE_WATER = list(range(20, 25)) # Water tiles (cost=2) +TILE_PROPS = list(range(48, 68)) # Collision tiles: trees, bushes, rocks (collide=true) + +# Clearing centers - 4 large open areas for base placement +CLEARINGS = [ + {"x": 32, "y": 32, "radius": 15}, + {"x": 96, "y": 32, "radius": 15}, + {"x": 32, "y": 96, "radius": 15}, + {"x": 96, "y": 96, "radius": 15}, +] + +def is_in_clearing(x, y): + """Check if a tile is inside one of the base clearings.""" + for c in CLEARINGS: + dx = x - c["x"] + dy = y - c["y"] + if dx * dx + dy * dy < c["radius"] * c["radius"]: + return True + return False + +def generate_terrain(): + """Generate base terrain using simplex noise.""" + floor_layer = [0] * (MAP_SIZE * MAP_SIZE) + + # Noise parameters + scale = 0.05 # How "zoomed in" the noise is + octaves = 3 + persistence = 0.5 + lacunarity = 2.0 + + for y in range(MAP_SIZE): + for x in range(MAP_SIZE): + # Multi-octave noise for natural variation + noise_val = snoise2( + x * scale, + y * scale, + octaves=octaves, + persistence=persistence, + lacunarity=lacunarity, + repeatx=1024, + repeaty=1024, + base=random.randint(0, 10000) + ) + + # Normalize from [-1, 1] to [0, 1] + normalized = (noise_val + 1) / 2 + + # Clearings override to grass + if is_in_clearing(x, y): + tile_id = TILE_GRASS[0] # Plain grass in clearings + # Water in low areas (but not in clearings) + elif normalized < 0.15: + tile_id = random.choice(TILE_WATER) + # Dirt paths will be added later, for now use grass + else: + # Vary grass based on noise value + grass_index = min(int(normalized * len(TILE_GRASS)), len(TILE_GRASS) - 1) + tile_id = TILE_GRASS[grass_index] + + floor_layer[y * MAP_SIZE + x] = tile_id + 1 # TMJ uses 1-based GIDs + + return floor_layer + +def generate_paths(floor_layer): + """Generate winding dirt paths using drunkard walk algorithm.""" + path_data = floor_layer.copy() + + # Path endpoints - connect clearings + path_starts = [ + (32, 64), # Top-left to center-top + (96, 64), # Top-right to center-top + (64, 32), # Top-center + (64, 96), # Bottom-center + (32, 96), # Bottom-left to center + (96, 96), # Bottom-right to center + ] + + random.seed(42) # Reproducible paths + + for start_x, start_y in path_starts: + # Drunkard walk from clearing edge outward + x, y = start_x, start_y + steps = random.randint(80, 150) + + for _ in range(steps): + # Place dirt tile + idx = y * MAP_SIZE + x + if 0 <= idx < len(path_data): + # Don't overwrite water + current = path_data[idx] + if current not in [t + 1 for t in TILE_WATER]: + path_data[idx] = random.choice(TILE_DIRT) + 1 + + # Random walk with bias toward map edges + direction = random.choice(["n", "s", "e", "w"]) + if direction == "n" and y > 5: + y -= 1 + elif direction == "s" and y < MAP_SIZE - 6: + y += 1 + elif direction == "e" and x < MAP_SIZE - 6: + x += 1 + elif direction == "w" and x > 5: + x -= 1 + + return path_data + +def generate_props(floor_layer): + """Generate collision layer with trees, bushes, rocks.""" + rocks_layer = [0] * (MAP_SIZE * MAP_SIZE) + + random.seed(123) # Different seed for props + + for y in range(MAP_SIZE): + for x in range(MAP_SIZE): + # Skip clearings + if is_in_clearing(x, y): + continue + + idx = y * MAP_SIZE + x + floor_tile = floor_layer[idx] + + # Higher prop density near water + is_near_water = any( + floor_layer[ny * MAP_SIZE + nx] in [t + 1 for t in TILE_WATER] + for nx, ny in get_neighbors(x, y) + if 0 <= nx < MAP_SIZE and 0 <= ny < MAP_SIZE + ) + + # Base prop chance + prop_chance = 0.03 + if is_near_water: + prop_chance = 0.15 # Much denser near water + + # Don't place on paths or water + if floor_tile in [t + 1 for t in TILE_DIRT + TILE_WATER]: + continue + + if random.random() < prop_chance: + # Cluster placement - check if neighbor has prop + has_neighbor_prop = any( + rocks_layer[ny * MAP_SIZE + nx] != 0 + for nx, ny in get_neighbors(x, y) + if 0 <= nx < MAP_SIZE and 0 <= ny < MAP_SIZE + ) + + if has_neighbor_prop or random.random() < 0.3: + # Place prop (tree, bush, or rock) + prop_tile = random.choice(TILE_PROPS) + rocks_layer[idx] = prop_tile + 1 + + return rocks_layer + +def get_neighbors(x, y): + """Get 8 neighboring tile coordinates.""" + neighbors = [] + for dy in [-1, 0, 1]: + for dx in [-1, 0, 1]: + if dx == 0 and dy == 0: + continue + neighbors.append((x + dx, y + dy)) + return neighbors + +def generate_decorations(floor_layer): + """Generate optional decoration layer (small accent tiles).""" + decor_layer = [0] * (MAP_SIZE * MAP_SIZE) + + # Sparse decorative elements - flowers, small stones + random.seed(456) + + for y in range(MAP_SIZE): + for x in range(MAP_SIZE): + if is_in_clearing(x, y): + continue + + if random.random() < 0.01: # 1% chance + idx = y * MAP_SIZE + x + # Use a non-collision decorative tile + decor_layer[idx] = random.choice([38, 39, 40]) + 1 + + return decor_layer + +def build_tilemap(): + """Generate the complete tilemap and save as TMJ.""" + print("Generating terrain...") + floor_layer = generate_terrain() + + print("Adding paths...") + floor_layer = generate_paths(floor_layer) + + print("Placing props and collision tiles...") + rocks_layer = generate_props(floor_layer) + + print("Adding decorations...") + decor_layer = generate_decorations(floor_layer) + + # Build TMJ structure matching test1.tmj format + tilemap = { + "compressionlevel": -1, + "height": MAP_SIZE, + "infinite": False, + "layers": [ + { + "data": floor_layer, + "height": MAP_SIZE, + "id": 1, + "name": "Floor", + "opacity": 1, + "type": "tilelayer", + "visible": True, + "width": MAP_SIZE, + "x": 0, + "y": 0 + }, + { + "data": rocks_layer, + "height": MAP_SIZE, + "id": 2, + "name": "Rocks", + "opacity": 1, + "type": "tilelayer", + "visible": True, + "width": MAP_SIZE, + "x": 0, + "y": 0 + }, + { + "data": decor_layer, + "height": MAP_SIZE, + "id": 3, + "name": "Decorations", + "opacity": 1, + "type": "tilelayer", + "visible": True, + "width": MAP_SIZE, + "x": 0, + "y": 0 + } + ], + "nextlayerid": 4, + "nextobjectid": 1, + "orientation": "isometric", + "renderorder": "right-down", + "tiledversion": "1.9.2", + "tileheight": 16, # Must be 16 for Phaser 3.55 tilemap parser + "tilesets": [ + { + "columns": 11, + "firstgid": 1, + "image": "../../tilesets/floors32x32.png", + "imageheight": 352, + "imagewidth": 352, + "margin": 0, + "name": "floorsPrimary", + "objectalignment": "center", + "spacing": 0, + "tilecount": 121, + "tileheight": 32, + "tilewidth": 32 + } + ], + "tilewidth": TILE_SIZE, + "type": "map", + "version": "1.9", + "width": MAP_SIZE + } + + # Write output + print(f"Writing to {OUTPUT_PATH}...") + with open(OUTPUT_PATH, "w") as f: + json.dump(tilemap, f, indent=1) + + # Validate + print("Validating JSON...") + with open(OUTPUT_PATH, "r") as f: + loaded = json.load(f) + + print(f"\n✓ Generated {MAP_SIZE}x{MAP_SIZE} map ({MAP_SIZE * MAP_SIZE * 3} tiles total)") + print(f"✓ Tile size: {TILE_SIZE}x{TILE_SIZE} (fixed from 16)") + print(f"✓ 3 layers: Floor, Rocks, Decorations") + print(f"✓ 4 base clearings at corners") + print(f"✓ Output: {OUTPUT_PATH}") + + # Stats + floor_nonzero = sum(1 for t in floor_layer if t != 0) + rocks_nonzero = sum(1 for t in rocks_layer if t != 0) + decor_nonzero = sum(1 for t in decor_layer if t != 0) + + print(f"\nLayer stats:") + print(f" Floor: {floor_nonzero}/{len(floor_layer)} tiles placed") + print(f" Rocks: {rocks_nonzero}/{len(rocks_layer)} collision tiles") + print(f" Decor: {decor_nonzero}/{len(decor_layer)} decorations") + +if __name__ == "__main__": + build_tilemap() diff --git a/package-lock.json b/package-lock.json index 20cf148..d8389db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ }, "devDependencies": { "@babel/preset-react": "^7.18.6", + "@playwright/test": "^1.60.0", "@testing-library/dom": "^10.4.1", "canvas": "^2.10.2", "copy-webpack-plugin": "^14.0.0", @@ -43,6 +44,7 @@ "html-webpack-plugin": "^5.5.0", "jest": "^30.4.2", "jest-environment-jsdom": "^30.4.1", + "playwright": "^1.60.0", "style-loader": "^3.3.1", "webpack": "^5.74.0", "webpack-cli": "^4.10.0", @@ -3113,6 +3115,22 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@popperjs/core": { "version": "2.11.6", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", @@ -11751,6 +11769,53 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.4.18", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", @@ -16067,6 +16132,15 @@ "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", "dev": true }, + "@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "requires": { + "playwright": "1.60.0" + } + }, "@popperjs/core": { "version": "2.11.6", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", @@ -22195,6 +22269,31 @@ "find-up": "^4.0.0" } }, + "playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.60.0" + }, + "dependencies": { + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + } + } + }, + "playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true + }, "postcss": { "version": "8.4.18", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz", diff --git a/package.json b/package.json index b96a926..6f7de85 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ }, "devDependencies": { "@babel/preset-react": "^7.18.6", + "@playwright/test": "^1.60.0", "@testing-library/dom": "^10.4.1", "canvas": "^2.10.2", "copy-webpack-plugin": "^14.0.0", @@ -59,6 +60,7 @@ "html-webpack-plugin": "^5.5.0", "jest": "^30.4.2", "jest-environment-jsdom": "^30.4.1", + "playwright": "^1.60.0", "style-loader": "^3.3.1", "webpack": "^5.74.0", "webpack-cli": "^4.10.0", diff --git a/public/tilemaps/testmap/test2.tmj b/public/tilemaps/testmap/test2.tmj new file mode 100644 index 0000000..574e159 --- /dev/null +++ b/public/tilemaps/testmap/test2.tmj @@ -0,0 +1,49224 @@ +{ + "compressionlevel": -1, + "height": 128, + "infinite": false, + "layers": [ + { + "data": [ + 4, + 5, + 5, + 5, + 5, + 5, + 6, + 7, + 5, + 4, + 4, + 5, + 6, + 3, + 3, + 6, + 3, + 5, + 4, + 4, + 6, + 7, + 4, + 5, + 6, + 3, + 5, + 5, + 5, + 5, + 5, + 3, + 5, + 3, + 5, + 3, + 6, + 5, + 4, + 4, + 5, + 4, + 5, + 2, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 6, + 6, + 4, + 4, + 4, + 4, + 5, + 4, + 3, + 5, + 6, + 4, + 5, + 4, + 5, + 3, + 4, + 5, + 4, + 5, + 6, + 5, + 4, + 3, + 4, + 3, + 3, + 5, + 7, + 3, + 5, + 5, + 3, + 4, + 6, + 3, + 4, + 3, + 3, + 4, + 3, + 5, + 5, + 6, + 3, + 4, + 6, + 5, + 6, + 6, + 3, + 3, + 3, + 4, + 4, + 5, + 4, + 4, + 4, + 6, + 3, + 3, + 4, + 5, + 5, + 5, + 6, + 5, + 3, + 2, + 5, + 5, + 5, + 4, + 6, + 4, + 4, + 6, + 3, + 4, + 6, + 5, + 3, + 3, + 3, + 5, + 4, + 4, + 3, + 4, + 3, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 6, + 5, + 5, + 4, + 3, + 5, + 4, + 5, + 4, + 5, + 5, + 3, + 3, + 4, + 3, + 4, + 5, + 4, + 6, + 3, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 7, + 3, + 4, + 5, + 5, + 4, + 3, + 2, + 5, + 5, + 5, + 4, + 6, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 6, + 5, + 5, + 3, + 4, + 5, + 4, + 5, + 6, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 3, + 4, + 3, + 5, + 6, + 4, + 6, + 4, + 5, + 5, + 3, + 5, + 5, + 5, + 5, + 4, + 6, + 4, + 6, + 5, + 3, + 3, + 4, + 4, + 5, + 6, + 4, + 3, + 5, + 5, + 4, + 4, + 3, + 4, + 5, + 5, + 6, + 6, + 4, + 6, + 6, + 4, + 5, + 4, + 6, + 4, + 4, + 3, + 6, + 4, + 6, + 4, + 4, + 5, + 4, + 5, + 4, + 4, + 4, + 4, + 6, + 3, + 5, + 4, + 5, + 5, + 4, + 3, + 4, + 4, + 5, + 4, + 5, + 3, + 6, + 4, + 5, + 4, + 3, + 5, + 3, + 5, + 3, + 4, + 4, + 5, + 4, + 5, + 6, + 3, + 5, + 5, + 3, + 4, + 3, + 3, + 7, + 3, + 6, + 6, + 5, + 5, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 3, + 5, + 5, + 4, + 3, + 5, + 3, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 6, + 4, + 5, + 5, + 6, + 5, + 4, + 3, + 5, + 5, + 4, + 3, + 3, + 5, + 6, + 4, + 3, + 6, + 4, + 4, + 5, + 4, + 6, + 5, + 2, + 4, + 6, + 4, + 5, + 6, + 6, + 5, + 6, + 6, + 4, + 5, + 5, + 4, + 4, + 4, + 6, + 3, + 4, + 5, + 6, + 4, + 4, + 5, + 4, + 3, + 5, + 5, + 5, + 4, + 3, + 4, + 5, + 2, + 5, + 5, + 6, + 3, + 6, + 6, + 4, + 7, + 4, + 4, + 5, + 5, + 5, + 4, + 4, + 4, + 3, + 4, + 6, + 3, + 3, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 4, + 6, + 6, + 4, + 3, + 6, + 3, + 3, + 5, + 4, + 3, + 6, + 5, + 4, + 5, + 5, + 6, + 5, + 3, + 4, + 6, + 5, + 5, + 3, + 4, + 2, + 7, + 5, + 4, + 3, + 5, + 5, + 3, + 4, + 4, + 5, + 3, + 4, + 4, + 4, + 5, + 4, + 4, + 5, + 6, + 4, + 4, + 6, + 4, + 3, + 3, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 3, + 4, + 6, + 3, + 5, + 6, + 4, + 6, + 5, + 5, + 5, + 5, + 4, + 5, + 6, + 4, + 3, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 3, + 4, + 4, + 3, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 3, + 5, + 4, + 5, + 4, + 7, + 2, + 3, + 3, + 3, + 6, + 5, + 4, + 3, + 3, + 4, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 3, + 3, + 5, + 5, + 5, + 4, + 6, + 7, + 5, + 4, + 6, + 4, + 5, + 6, + 5, + 5, + 4, + 4, + 5, + 3, + 5, + 6, + 5, + 5, + 6, + 6, + 4, + 4, + 3, + 5, + 5, + 3, + 4, + 4, + 5, + 5, + 6, + 4, + 5, + 5, + 3, + 5, + 6, + 5, + 3, + 3, + 3, + 6, + 5, + 6, + 7, + 6, + 5, + 6, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 6, + 4, + 4, + 5, + 5, + 5, + 6, + 5, + 5, + 5, + 6, + 5, + 3, + 4, + 4, + 6, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 6, + 5, + 5, + 5, + 6, + 6, + 5, + 5, + 4, + 4, + 5, + 6, + 4, + 5, + 4, + 3, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 5, + 7, + 5, + 4, + 4, + 6, + 5, + 6, + 6, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 6, + 6, + 3, + 4, + 5, + 4, + 3, + 4, + 5, + 5, + 3, + 4, + 4, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 3, + 5, + 5, + 5, + 4, + 4, + 5, + 6, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 7, + 5, + 4, + 7, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 6, + 5, + 6, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 4, + 6, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 6, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 6, + 4, + 3, + 5, + 3, + 5, + 6, + 6, + 3, + 6, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 3, + 4, + 6, + 3, + 5, + 3, + 4, + 2, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 4, + 6, + 4, + 5, + 5, + 6, + 5, + 4, + 4, + 4, + 3, + 5, + 2, + 4, + 5, + 4, + 6, + 5, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 3, + 4, + 6, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 6, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 6, + 6, + 6, + 4, + 4, + 4, + 5, + 4, + 3, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 3, + 4, + 4, + 5, + 5, + 6, + 4, + 5, + 4, + 6, + 4, + 5, + 5, + 5, + 6, + 5, + 5, + 4, + 5, + 2, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 3, + 3, + 5, + 3, + 6, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 6, + 5, + 3, + 5, + 6, + 5, + 3, + 5, + 4, + 3, + 5, + 4, + 6, + 4, + 5, + 3, + 5, + 4, + 4, + 4, + 5, + 5, + 6, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 4, + 4, + 4, + 3, + 4, + 3, + 5, + 5, + 4, + 3, + 5, + 5, + 4, + 6, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 3, + 3, + 5, + 5, + 3, + 4, + 4, + 3, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 5, + 6, + 5, + 4, + 5, + 4, + 4, + 5, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 6, + 5, + 5, + 3, + 5, + 5, + 6, + 5, + 6, + 5, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 4, + 6, + 5, + 4, + 5, + 4, + 7, + 3, + 5, + 6, + 4, + 6, + 4, + 6, + 6, + 5, + 3, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 3, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 4, + 3, + 4, + 6, + 5, + 5, + 3, + 4, + 5, + 4, + 4, + 7, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 3, + 3, + 4, + 5, + 5, + 3, + 5, + 3, + 5, + 6, + 4, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 3, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 7, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 3, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 3, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 3, + 4, + 4, + 4, + 3, + 5, + 4, + 5, + 5, + 6, + 5, + 6, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 3, + 5, + 5, + 3, + 4, + 4, + 6, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 3, + 6, + 5, + 5, + 3, + 5, + 5, + 5, + 5, + 6, + 4, + 5, + 5, + 5, + 3, + 3, + 4, + 5, + 4, + 4, + 5, + 6, + 5, + 5, + 4, + 6, + 4, + 4, + 4, + 4, + 3, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 6, + 5, + 4, + 4, + 4, + 5, + 4, + 4, + 6, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 4, + 5, + 4, + 5, + 3, + 5, + 5, + 6, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 3, + 3, + 2, + 4, + 5, + 6, + 5, + 3, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 6, + 5, + 3, + 4, + 4, + 4, + 7, + 3, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 3, + 5, + 6, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 6, + 5, + 5, + 4, + 5, + 3, + 4, + 4, + 6, + 4, + 3, + 6, + 4, + 5, + 4, + 4, + 4, + 4, + 6, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 6, + 4, + 4, + 4, + 5, + 5, + 4, + 7, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 3, + 5, + 5, + 4, + 4, + 5, + 5, + 6, + 4, + 4, + 6, + 6, + 5, + 4, + 4, + 5, + 3, + 6, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 6, + 5, + 5, + 4, + 5, + 6, + 4, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 6, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 3, + 4, + 4, + 5, + 4, + 3, + 5, + 5, + 5, + 4, + 6, + 4, + 4, + 4, + 4, + 4, + 5, + 6, + 5, + 4, + 5, + 4, + 4, + 6, + 5, + 6, + 5, + 6, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 6, + 5, + 5, + 6, + 5, + 4, + 2, + 4, + 3, + 4, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 3, + 5, + 6, + 6, + 4, + 4, + 6, + 4, + 6, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 3, + 5, + 6, + 5, + 3, + 4, + 3, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 6, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 6, + 6, + 7, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 3, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 3, + 5, + 5, + 4, + 3, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 6, + 5, + 5, + 3, + 4, + 6, + 3, + 5, + 5, + 6, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 3, + 6, + 3, + 6, + 6, + 4, + 5, + 4, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 2, + 6, + 3, + 5, + 4, + 5, + 5, + 3, + 4, + 3, + 3, + 4, + 6, + 6, + 4, + 4, + 5, + 5, + 3, + 6, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 6, + 4, + 4, + 4, + 4, + 6, + 6, + 5, + 3, + 4, + 6, + 6, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 6, + 5, + 4, + 4, + 3, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 4, + 3, + 4, + 4, + 4, + 7, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 6, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 2, + 4, + 3, + 5, + 5, + 4, + 5, + 5, + 6, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 3, + 3, + 4, + 4, + 5, + 3, + 2, + 6, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 6, + 4, + 6, + 5, + 4, + 6, + 4, + 6, + 4, + 4, + 6, + 6, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 6, + 4, + 4, + 6, + 5, + 4, + 3, + 4, + 5, + 5, + 3, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 3, + 5, + 4, + 5, + 5, + 7, + 3, + 6, + 6, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 3, + 4, + 5, + 6, + 5, + 4, + 5, + 6, + 3, + 5, + 3, + 5, + 3, + 5, + 6, + 4, + 3, + 5, + 4, + 5, + 5, + 3, + 4, + 5, + 3, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 3, + 5, + 5, + 5, + 4, + 4, + 3, + 3, + 5, + 5, + 5, + 6, + 5, + 5, + 4, + 3, + 5, + 4, + 7, + 5, + 3, + 5, + 5, + 6, + 4, + 6, + 4, + 6, + 6, + 6, + 4, + 5, + 3, + 5, + 4, + 3, + 4, + 5, + 6, + 5, + 5, + 4, + 6, + 4, + 6, + 4, + 4, + 3, + 5, + 4, + 3, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 3, + 6, + 5, + 5, + 5, + 5, + 4, + 6, + 4, + 4, + 6, + 4, + 5, + 6, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 3, + 4, + 6, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 6, + 4, + 5, + 4, + 5, + 4, + 4, + 6, + 6, + 3, + 6, + 5, + 4, + 4, + 5, + 2, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 3, + 5, + 4, + 4, + 4, + 5, + 3, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 4, + 3, + 4, + 4, + 4, + 3, + 4, + 5, + 3, + 3, + 6, + 5, + 5, + 5, + 5, + 5, + 3, + 6, + 3, + 5, + 4, + 3, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 6, + 4, + 3, + 3, + 3, + 5, + 4, + 5, + 6, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 3, + 4, + 5, + 6, + 5, + 5, + 5, + 6, + 4, + 6, + 6, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 3, + 6, + 6, + 2, + 5, + 4, + 5, + 4, + 4, + 4, + 6, + 4, + 5, + 3, + 4, + 4, + 4, + 3, + 5, + 5, + 4, + 3, + 5, + 6, + 4, + 4, + 3, + 5, + 6, + 6, + 4, + 5, + 4, + 5, + 4, + 2, + 4, + 3, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 3, + 4, + 5, + 6, + 5, + 4, + 3, + 5, + 3, + 3, + 5, + 5, + 5, + 6, + 6, + 3, + 5, + 4, + 4, + 4, + 5, + 5, + 6, + 5, + 5, + 6, + 4, + 5, + 4, + 7, + 3, + 4, + 5, + 6, + 4, + 4, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 6, + 7, + 7, + 3, + 3, + 5, + 6, + 4, + 4, + 5, + 4, + 5, + 6, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 4, + 3, + 5, + 4, + 3, + 6, + 5, + 6, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 4, + 5, + 5, + 3, + 5, + 5, + 4, + 3, + 5, + 4, + 4, + 3, + 5, + 3, + 5, + 5, + 4, + 4, + 3, + 3, + 7, + 4, + 5, + 3, + 4, + 6, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 3, + 5, + 5, + 3, + 6, + 5, + 5, + 4, + 4, + 6, + 5, + 2, + 5, + 4, + 3, + 5, + 6, + 6, + 6, + 4, + 4, + 6, + 4, + 5, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 4, + 5, + 4, + 4, + 6, + 4, + 3, + 5, + 3, + 4, + 4, + 5, + 4, + 4, + 6, + 6, + 3, + 5, + 5, + 5, + 4, + 3, + 4, + 4, + 5, + 5, + 4, + 2, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 6, + 6, + 6, + 5, + 5, + 3, + 5, + 6, + 3, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 6, + 6, + 4, + 4, + 4, + 5, + 5, + 4, + 6, + 4, + 4, + 3, + 3, + 3, + 5, + 3, + 4, + 6, + 5, + 6, + 4, + 5, + 6, + 3, + 5, + 6, + 6, + 4, + 5, + 5, + 5, + 5, + 6, + 5, + 6, + 4, + 5, + 5, + 5, + 6, + 4, + 3, + 4, + 5, + 6, + 4, + 5, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 3, + 6, + 6, + 4, + 5, + 4, + 5, + 6, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 6, + 5, + 6, + 6, + 4, + 2, + 6, + 4, + 3, + 6, + 5, + 4, + 4, + 5, + 3, + 6, + 6, + 4, + 3, + 3, + 4, + 5, + 4, + 2, + 6, + 4, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 3, + 4, + 6, + 3, + 5, + 4, + 6, + 4, + 4, + 5, + 6, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 6, + 6, + 5, + 4, + 5, + 5, + 4, + 6, + 4, + 7, + 4, + 6, + 3, + 5, + 6, + 4, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 3, + 5, + 6, + 6, + 5, + 4, + 4, + 5, + 3, + 3, + 3, + 7, + 3, + 4, + 4, + 5, + 4, + 5, + 4, + 6, + 5, + 5, + 5, + 5, + 6, + 5, + 5, + 5, + 6, + 6, + 3, + 6, + 4, + 6, + 6, + 5, + 4, + 5, + 5, + 5, + 3, + 3, + 4, + 4, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 6, + 5, + 3, + 3, + 4, + 4, + 2, + 5, + 4, + 3, + 6, + 3, + 5, + 5, + 4, + 4, + 4, + 6, + 4, + 3, + 3, + 4, + 7, + 3, + 4, + 5, + 3, + 3, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 6, + 3, + 2, + 4, + 6, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 4, + 4, + 3, + 4, + 5, + 3, + 4, + 4, + 7, + 5, + 5, + 6, + 4, + 4, + 4, + 4, + 6, + 3, + 4, + 5, + 3, + 4, + 5, + 5, + 3, + 4, + 5, + 5, + 4, + 5, + 3, + 5, + 3, + 4, + 4, + 3, + 4, + 6, + 5, + 3, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 3, + 5, + 3, + 4, + 5, + 6, + 5, + 4, + 4, + 5, + 5, + 5, + 3, + 6, + 6, + 5, + 4, + 6, + 4, + 5, + 3, + 5, + 3, + 3, + 5, + 4, + 3, + 5, + 4, + 4, + 4, + 3, + 5, + 5, + 4, + 4, + 6, + 4, + 6, + 3, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 6, + 4, + 3, + 5, + 4, + 5, + 4, + 4, + 2, + 2, + 5, + 5, + 3, + 5, + 5, + 5, + 6, + 6, + 6, + 4, + 3, + 6, + 3, + 4, + 2, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 3, + 3, + 6, + 6, + 4, + 4, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 5, + 3, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 3, + 4, + 4, + 4, + 5, + 4, + 4, + 3, + 5, + 5, + 5, + 5, + 7, + 5, + 6, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 6, + 4, + 5, + 7, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 6, + 4, + 5, + 5, + 3, + 3, + 5, + 5, + 5, + 3, + 3, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 3, + 4, + 5, + 3, + 6, + 4, + 3, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 4, + 3, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 6, + 3, + 4, + 5, + 4, + 5, + 4, + 4, + 4, + 7, + 4, + 3, + 6, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 3, + 5, + 5, + 6, + 3, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 3, + 4, + 4, + 4, + 5, + 5, + 5, + 3, + 4, + 6, + 4, + 5, + 4, + 3, + 3, + 3, + 3, + 5, + 6, + 4, + 6, + 5, + 6, + 5, + 4, + 5, + 5, + 4, + 4, + 3, + 4, + 5, + 6, + 6, + 5, + 6, + 5, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 3, + 4, + 5, + 5, + 5, + 5, + 6, + 5, + 4, + 5, + 6, + 6, + 5, + 4, + 4, + 4, + 4, + 4, + 3, + 7, + 5, + 6, + 3, + 5, + 4, + 6, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 6, + 2, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 6, + 6, + 5, + 5, + 4, + 4, + 5, + 6, + 4, + 7, + 3, + 6, + 6, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 6, + 5, + 6, + 4, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 5, + 4, + 4, + 6, + 4, + 4, + 5, + 6, + 3, + 4, + 6, + 5, + 4, + 5, + 6, + 5, + 5, + 4, + 4, + 5, + 4, + 2, + 3, + 5, + 6, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 6, + 3, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 4, + 4, + 4, + 6, + 5, + 5, + 5, + 5, + 5, + 5, + 6, + 5, + 4, + 5, + 7, + 2, + 5, + 5, + 6, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 4, + 4, + 3, + 3, + 6, + 4, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 5, + 4, + 5, + 4, + 6, + 5, + 3, + 6, + 5, + 5, + 6, + 5, + 5, + 4, + 4, + 4, + 5, + 6, + 6, + 5, + 5, + 6, + 5, + 5, + 6, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 3, + 6, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 6, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 3, + 3, + 5, + 5, + 6, + 4, + 5, + 4, + 4, + 4, + 3, + 5, + 4, + 6, + 5, + 4, + 6, + 6, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 3, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 4, + 3, + 6, + 5, + 4, + 3, + 4, + 5, + 5, + 3, + 4, + 4, + 5, + 6, + 5, + 4, + 5, + 3, + 4, + 5, + 5, + 2, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 6, + 3, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 4, + 6, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 3, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 3, + 4, + 4, + 4, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 3, + 5, + 4, + 5, + 5, + 4, + 6, + 4, + 5, + 6, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 3, + 4, + 6, + 7, + 4, + 5, + 3, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 3, + 4, + 4, + 4, + 4, + 4, + 6, + 5, + 4, + 2, + 6, + 3, + 6, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 3, + 4, + 4, + 5, + 3, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 3, + 5, + 5, + 5, + 3, + 3, + 3, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 3, + 5, + 4, + 4, + 5, + 4, + 3, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 4, + 5, + 5, + 3, + 3, + 5, + 4, + 5, + 5, + 4, + 4, + 6, + 5, + 5, + 5, + 4, + 5, + 3, + 3, + 5, + 4, + 5, + 5, + 4, + 6, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 4, + 5, + 5, + 6, + 3, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 7, + 6, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 4, + 5, + 4, + 5, + 3, + 5, + 6, + 5, + 3, + 6, + 5, + 4, + 6, + 6, + 15, + 16, + 12, + 16, + 4, + 4, + 5, + 5, + 4, + 4, + 3, + 4, + 5, + 3, + 4, + 4, + 3, + 6, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 5, + 4, + 4, + 6, + 2, + 5, + 4, + 6, + 6, + 4, + 6, + 5, + 5, + 5, + 5, + 4, + 6, + 3, + 6, + 4, + 5, + 5, + 4, + 4, + 5, + 6, + 4, + 5, + 3, + 5, + 3, + 4, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 3, + 6, + 4, + 5, + 5, + 4, + 5, + 5, + 9, + 11, + 10, + 14, + 16, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 3, + 6, + 6, + 5, + 6, + 5, + 4, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 6, + 6, + 4, + 4, + 4, + 4, + 6, + 5, + 3, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 6, + 5, + 4, + 3, + 2, + 5, + 3, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 5, + 3, + 5, + 5, + 3, + 5, + 6, + 3, + 16, + 12, + 16, + 14, + 11, + 10, + 5, + 4, + 5, + 5, + 6, + 3, + 4, + 5, + 3, + 5, + 4, + 5, + 6, + 5, + 3, + 5, + 2, + 3, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 5, + 4, + 4, + 4, + 3, + 3, + 5, + 4, + 6, + 4, + 4, + 4, + 5, + 6, + 4, + 4, + 3, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 3, + 5, + 4, + 5, + 3, + 5, + 4, + 7, + 6, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 4, + 3, + 3, + 4, + 3, + 5, + 6, + 5, + 4, + 15, + 13, + 12, + 14, + 10, + 4, + 6, + 3, + 5, + 4, + 4, + 4, + 6, + 4, + 3, + 5, + 5, + 4, + 4, + 6, + 4, + 4, + 4, + 5, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 3, + 6, + 3, + 6, + 4, + 4, + 4, + 6, + 5, + 5, + 5, + 5, + 6, + 5, + 5, + 5, + 5, + 3, + 5, + 2, + 4, + 3, + 4, + 3, + 6, + 5, + 7, + 5, + 6, + 5, + 6, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 5, + 3, + 6, + 4, + 5, + 3, + 6, + 15, + 15, + 15, + 12, + 5, + 11, + 6, + 5, + 9, + 5, + 4, + 4, + 5, + 6, + 5, + 4, + 6, + 6, + 5, + 4, + 7, + 4, + 2, + 4, + 3, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 4, + 5, + 4, + 4, + 5, + 4, + 6, + 5, + 2, + 6, + 6, + 4, + 5, + 4, + 4, + 4, + 3, + 4, + 6, + 3, + 5, + 4, + 3, + 5, + 4, + 6, + 5, + 6, + 4, + 4, + 5, + 5, + 4, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 5, + 4, + 5, + 4, + 4, + 4, + 3, + 6, + 15, + 16, + 5, + 15, + 14, + 10, + 10, + 5, + 11, + 10, + 13, + 5, + 4, + 5, + 5, + 5, + 4, + 7, + 5, + 4, + 3, + 6, + 5, + 4, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 5, + 4, + 6, + 5, + 4, + 6, + 24, + 4, + 6, + 5, + 5, + 4, + 2, + 5, + 4, + 6, + 3, + 4, + 5, + 3, + 2, + 5, + 4, + 5, + 3, + 4, + 4, + 5, + 5, + 5, + 3, + 3, + 4, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 5, + 5, + 4, + 3, + 6, + 4, + 5, + 5, + 3, + 16, + 9, + 14, + 16, + 13, + 12, + 10, + 16, + 12, + 10, + 10, + 11, + 5, + 5, + 4, + 3, + 6, + 5, + 6, + 6, + 4, + 3, + 4, + 5, + 4, + 5, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 6, + 5, + 6, + 4, + 5, + 3, + 5, + 4, + 4, + 5, + 5, + 5, + 6, + 3, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 6, + 2, + 4, + 4, + 4, + 3, + 3, + 4, + 5, + 6, + 5, + 7, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 5, + 6, + 4, + 5, + 4, + 3, + 4, + 6, + 4, + 9, + 14, + 10, + 16, + 16, + 12, + 12, + 13, + 3, + 5, + 13, + 5, + 5, + 4, + 5, + 5, + 3, + 4, + 4, + 5, + 5, + 5, + 3, + 5, + 4, + 4, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 5, + 4, + 5, + 3, + 5, + 5, + 4, + 6, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 6, + 4, + 5, + 3, + 6, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 3, + 2, + 4, + 7, + 4, + 3, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 6, + 5, + 5, + 5, + 6, + 3, + 5, + 6, + 5, + 4, + 4, + 4, + 16, + 14, + 14, + 13, + 10, + 6, + 5, + 6, + 3, + 4, + 4, + 4, + 5, + 4, + 4, + 4, + 3, + 5, + 4, + 3, + 7, + 6, + 4, + 5, + 4, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 3, + 4, + 5, + 5, + 4, + 6, + 4, + 5, + 4, + 5, + 5, + 3, + 5, + 4, + 5, + 5, + 5, + 3, + 6, + 4, + 7, + 4, + 4, + 4, + 5, + 6, + 4, + 6, + 5, + 5, + 4, + 4, + 4, + 2, + 6, + 4, + 4, + 4, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 5, + 6, + 4, + 4, + 3, + 6, + 4, + 4, + 6, + 3, + 5, + 7, + 6, + 3, + 12, + 15, + 16, + 9, + 4, + 6, + 3, + 3, + 5, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 3, + 4, + 5, + 5, + 7, + 6, + 7, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 4, + 3, + 5, + 4, + 7, + 5, + 5, + 3, + 6, + 6, + 4, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 3, + 5, + 6, + 5, + 3, + 5, + 5, + 4, + 6, + 3, + 4, + 5, + 5, + 5, + 5, + 5, + 3, + 3, + 7, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 5, + 4, + 5, + 3, + 4, + 5, + 4, + 6, + 4, + 5, + 5, + 4, + 3, + 5, + 13, + 6, + 3, + 6, + 4, + 7, + 4, + 5, + 4, + 5, + 4, + 5, + 3, + 5, + 5, + 4, + 4, + 5, + 2, + 4, + 4, + 6, + 4, + 5, + 4, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 3, + 5, + 5, + 5, + 5, + 6, + 5, + 4, + 4, + 4, + 5, + 6, + 3, + 5, + 4, + 4, + 6, + 3, + 4, + 5, + 3, + 5, + 4, + 4, + 4, + 4, + 6, + 4, + 3, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 3, + 6, + 4, + 4, + 4, + 4, + 4, + 5, + 6, + 5, + 6, + 5, + 4, + 5, + 6, + 5, + 5, + 6, + 4, + 3, + 5, + 6, + 5, + 6, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 3, + 3, + 6, + 4, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 4, + 5, + 6, + 3, + 4, + 5, + 3, + 3, + 6, + 5, + 4, + 5, + 6, + 6, + 5, + 6, + 4, + 4, + 6, + 4, + 3, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 3, + 6, + 5, + 6, + 3, + 5, + 5, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 5, + 4, + 5, + 6, + 5, + 4, + 4, + 3, + 4, + 4, + 5, + 4, + 5, + 4, + 3, + 3, + 5, + 6, + 4, + 3, + 5, + 6, + 3, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 3, + 3, + 5, + 3, + 6, + 3, + 4, + 4, + 4, + 3, + 6, + 5, + 7, + 2, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 5, + 4, + 3, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 6, + 6, + 4, + 5, + 4, + 3, + 5, + 6, + 4, + 3, + 4, + 3, + 4, + 4, + 5, + 3, + 4, + 5, + 3, + 5, + 4, + 4, + 4, + 5, + 4, + 4, + 6, + 4, + 5, + 6, + 2, + 4, + 5, + 6, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 3, + 3, + 3, + 2, + 4, + 4, + 3, + 3, + 5, + 4, + 3, + 5, + 4, + 4, + 5, + 4, + 5, + 4, + 2, + 6, + 5, + 4, + 4, + 5, + 6, + 4, + 6, + 4, + 4, + 5, + 4, + 4, + 5, + 6, + 4, + 5, + 6, + 7, + 6, + 5, + 4, + 6, + 5, + 4, + 5, + 4, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 3, + 5, + 3, + 5, + 5, + 5, + 4, + 4, + 5, + 6, + 4, + 4, + 5, + 3, + 5, + 5, + 6, + 3, + 4, + 4, + 5, + 3, + 5, + 3, + 5, + 5, + 5, + 5, + 5, + 4, + 2, + 5, + 4, + 5, + 5, + 4, + 4, + 6, + 5, + 4, + 5, + 5, + 6, + 4, + 4, + 5, + 4, + 3, + 6, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 5, + 4, + 4, + 2, + 4, + 3, + 7, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 4, + 6, + 3, + 4, + 4, + 6, + 3, + 6, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 5, + 6, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 6, + 5, + 6, + 4, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 5, + 5, + 5, + 4, + 5, + 3, + 5, + 7, + 5, + 3, + 3, + 5, + 2, + 4, + 5, + 3, + 3, + 5, + 4, + 4, + 4, + 5, + 5, + 3, + 3, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 3, + 5, + 6, + 5, + 3, + 5, + 5, + 5, + 3, + 5, + 6, + 4, + 6, + 5, + 4, + 5, + 4, + 4, + 3, + 5, + 4, + 4, + 2, + 4, + 5, + 5, + 5, + 4, + 2, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 3, + 6, + 4, + 5, + 4, + 4, + 5, + 4, + 6, + 4, + 3, + 3, + 4, + 6, + 4, + 5, + 4, + 5, + 6, + 4, + 4, + 3, + 3, + 4, + 3, + 3, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 6, + 3, + 5, + 6, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 3, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 6, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 4, + 4, + 6, + 3, + 4, + 4, + 3, + 4, + 3, + 4, + 3, + 5, + 5, + 6, + 6, + 3, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 6, + 4, + 3, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 3, + 5, + 6, + 5, + 4, + 4, + 5, + 3, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 6, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 6, + 3, + 6, + 4, + 4, + 4, + 4, + 4, + 3, + 5, + 5, + 3, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 6, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 6, + 4, + 4, + 3, + 3, + 4, + 4, + 6, + 5, + 3, + 3, + 6, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 3, + 4, + 3, + 3, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 6, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 3, + 4, + 6, + 3, + 5, + 3, + 5, + 4, + 4, + 4, + 5, + 4, + 4, + 4, + 3, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 5, + 3, + 6, + 5, + 5, + 3, + 5, + 4, + 3, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 6, + 5, + 5, + 5, + 4, + 4, + 5, + 6, + 4, + 5, + 4, + 3, + 4, + 5, + 4, + 4, + 5, + 4, + 3, + 3, + 5, + 5, + 5, + 5, + 5, + 4, + 7, + 4, + 4, + 6, + 4, + 4, + 5, + 5, + 4, + 6, + 4, + 4, + 4, + 5, + 4, + 6, + 4, + 4, + 3, + 3, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 3, + 4, + 4, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 3, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 2, + 4, + 5, + 5, + 4, + 3, + 3, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 6, + 6, + 6, + 3, + 5, + 6, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 3, + 3, + 5, + 3, + 3, + 5, + 4, + 4, + 3, + 5, + 5, + 5, + 3, + 6, + 4, + 4, + 4, + 4, + 2, + 4, + 4, + 3, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 6, + 3, + 4, + 4, + 3, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 6, + 5, + 6, + 3, + 4, + 5, + 5, + 5, + 4, + 5, + 3, + 4, + 4, + 4, + 4, + 5, + 4, + 4, + 3, + 4, + 5, + 6, + 5, + 6, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 6, + 4, + 4, + 3, + 5, + 5, + 6, + 5, + 5, + 3, + 5, + 5, + 4, + 4, + 5, + 5, + 6, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 6, + 3, + 6, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 3, + 4, + 3, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 3, + 4, + 5, + 5, + 4, + 5, + 3, + 5, + 4, + 5, + 4, + 4, + 4, + 4, + 5, + 3, + 5, + 4, + 5, + 4, + 4, + 6, + 5, + 4, + 4, + 2, + 5, + 4, + 5, + 4, + 5, + 4, + 3, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 6, + 6, + 6, + 5, + 6, + 6, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 3, + 4, + 4, + 4, + 5, + 5, + 6, + 6, + 6, + 4, + 5, + 5, + 6, + 4, + 4, + 4, + 4, + 4, + 5, + 3, + 4, + 5, + 5, + 4, + 5, + 5, + 3, + 6, + 7, + 3, + 5, + 5, + 4, + 6, + 4, + 4, + 5, + 3, + 4, + 4, + 5, + 4, + 5, + 5, + 3, + 3, + 4, + 6, + 6, + 7, + 4, + 5, + 3, + 6, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 3, + 3, + 4, + 3, + 4, + 5, + 4, + 5, + 5, + 3, + 4, + 3, + 3, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 6, + 5, + 4, + 4, + 4, + 5, + 3, + 5, + 3, + 4, + 4, + 4, + 5, + 5, + 6, + 7, + 6, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 4, + 3, + 4, + 4, + 3, + 6, + 5, + 4, + 4, + 3, + 7, + 6, + 4, + 3, + 5, + 5, + 5, + 5, + 5, + 4, + 3, + 5, + 4, + 6, + 5, + 5, + 6, + 5, + 5, + 5, + 5, + 6, + 5, + 6, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 3, + 4, + 3, + 3, + 4, + 6, + 6, + 6, + 4, + 3, + 5, + 4, + 4, + 6, + 5, + 4, + 5, + 5, + 4, + 4, + 6, + 4, + 5, + 5, + 5, + 5, + 5, + 6, + 5, + 5, + 4, + 5, + 3, + 4, + 5, + 4, + 5, + 5, + 4, + 3, + 5, + 3, + 4, + 4, + 6, + 5, + 5, + 4, + 3, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 3, + 5, + 4, + 6, + 5, + 4, + 4, + 3, + 5, + 7, + 4, + 4, + 6, + 3, + 5, + 4, + 5, + 6, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 6, + 5, + 6, + 6, + 6, + 6, + 3, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 3, + 4, + 4, + 3, + 5, + 6, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 3, + 4, + 5, + 5, + 5, + 6, + 4, + 4, + 3, + 4, + 4, + 6, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 3, + 3, + 5, + 6, + 4, + 4, + 5, + 3, + 2, + 5, + 5, + 6, + 5, + 4, + 5, + 5, + 5, + 5, + 3, + 4, + 5, + 4, + 3, + 3, + 3, + 6, + 6, + 3, + 4, + 3, + 5, + 3, + 5, + 4, + 4, + 4, + 4, + 3, + 5, + 3, + 5, + 3, + 6, + 4, + 4, + 3, + 4, + 5, + 3, + 5, + 4, + 3, + 6, + 5, + 4, + 5, + 4, + 4, + 5, + 6, + 4, + 5, + 4, + 5, + 4, + 5, + 6, + 5, + 5, + 6, + 4, + 6, + 3, + 3, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 6, + 4, + 4, + 5, + 4, + 4, + 4, + 3, + 6, + 5, + 4, + 4, + 5, + 5, + 3, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 6, + 4, + 5, + 4, + 4, + 6, + 4, + 4, + 3, + 6, + 5, + 5, + 6, + 4, + 5, + 3, + 4, + 5, + 3, + 4, + 5, + 4, + 3, + 5, + 6, + 6, + 3, + 5, + 5, + 6, + 6, + 4, + 5, + 5, + 3, + 4, + 6, + 3, + 5, + 4, + 4, + 3, + 4, + 4, + 6, + 4, + 5, + 3, + 4, + 4, + 6, + 5, + 4, + 5, + 4, + 3, + 4, + 4, + 4, + 5, + 5, + 6, + 5, + 3, + 4, + 3, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 2, + 4, + 3, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 3, + 5, + 4, + 6, + 4, + 6, + 4, + 7, + 2, + 5, + 4, + 3, + 5, + 4, + 3, + 5, + 5, + 5, + 5, + 4, + 4, + 3, + 5, + 4, + 5, + 5, + 6, + 4, + 5, + 6, + 4, + 5, + 4, + 5, + 6, + 5, + 4, + 4, + 4, + 4, + 5, + 4, + 2, + 4, + 3, + 3, + 3, + 4, + 3, + 5, + 6, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 4, + 3, + 6, + 3, + 5, + 4, + 3, + 5, + 4, + 6, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 3, + 5, + 3, + 6, + 5, + 3, + 6, + 5, + 6, + 3, + 4, + 6, + 4, + 4, + 5, + 3, + 5, + 5, + 3, + 6, + 4, + 5, + 3, + 4, + 5, + 5, + 4, + 5, + 3, + 4, + 4, + 4, + 6, + 3, + 4, + 5, + 3, + 7, + 3, + 5, + 4, + 5, + 5, + 3, + 4, + 4, + 4, + 3, + 5, + 5, + 4, + 4, + 5, + 6, + 5, + 4, + 2, + 5, + 4, + 5, + 5, + 4, + 6, + 4, + 3, + 5, + 4, + 5, + 5, + 4, + 5, + 5, + 2, + 5, + 4, + 5, + 6, + 4, + 3, + 3, + 6, + 5, + 4, + 4, + 5, + 5, + 3, + 6, + 6, + 3, + 6, + 2, + 5, + 6, + 2, + 4, + 6, + 4, + 6, + 5, + 4, + 4, + 4, + 6, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 3, + 3, + 5, + 5, + 5, + 3, + 5, + 5, + 6, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 3, + 5, + 5, + 4, + 5, + 6, + 3, + 6, + 6, + 5, + 6, + 4, + 4, + 4, + 5, + 4, + 6, + 5, + 6, + 3, + 6, + 4, + 5, + 5, + 6, + 5, + 5, + 5, + 4, + 5, + 3, + 6, + 4, + 5, + 6, + 3, + 6, + 6, + 4, + 3, + 4, + 4, + 6, + 5, + 5, + 4, + 4, + 5, + 4, + 5, + 6, + 4, + 5, + 7, + 4, + 5, + 4, + 4, + 5, + 6, + 5, + 3, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 4, + 3, + 5, + 5, + 6, + 6, + 3, + 3, + 4, + 3, + 5, + 4, + 3, + 6, + 5, + 4, + 5, + 4, + 4, + 4, + 3, + 4, + 2, + 4, + 4, + 4, + 4, + 4, + 5, + 3, + 3, + 5, + 5, + 4, + 3, + 4, + 4, + 6, + 3, + 4, + 5, + 5, + 6, + 5, + 5, + 4, + 5, + 7, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 3, + 3, + 5, + 3, + 5, + 6, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 3, + 3, + 4, + 4, + 3, + 5, + 3, + 5, + 5, + 5, + 3, + 4, + 6, + 3, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 6, + 4, + 5, + 3, + 4, + 3, + 4, + 4, + 4, + 5, + 3, + 6, + 4, + 5, + 5, + 3, + 3, + 4, + 5, + 6, + 4, + 3, + 4, + 6, + 6, + 4, + 4, + 5, + 4, + 5, + 4, + 3, + 3, + 4, + 2, + 3, + 4, + 6, + 4, + 5, + 6, + 5, + 6, + 4, + 4, + 5, + 5, + 6, + 5, + 4, + 4, + 6, + 5, + 5, + 2, + 4, + 5, + 4, + 6, + 4, + 4, + 6, + 6, + 4, + 4, + 3, + 5, + 5, + 3, + 4, + 7, + 4, + 6, + 5, + 5, + 4, + 4, + 3, + 4, + 4, + 4, + 4, + 5, + 4, + 3, + 4, + 5, + 5, + 3, + 7, + 4, + 5, + 3, + 4, + 5, + 5, + 2, + 4, + 5, + 4, + 5, + 6, + 4, + 6, + 3, + 5, + 5, + 3, + 6, + 5, + 3, + 4, + 7, + 3, + 6, + 5, + 6, + 4, + 5, + 3, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 3, + 5, + 7, + 4, + 5, + 6, + 4, + 4, + 4, + 6, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 6, + 4, + 6, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 4, + 5, + 6, + 5, + 7, + 5, + 6, + 6, + 3, + 6, + 6, + 3, + 5, + 3, + 4, + 5, + 7, + 4, + 3, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 3, + 3, + 5, + 5, + 3, + 6, + 4, + 3, + 5, + 5, + 3, + 6, + 6, + 4, + 6, + 6, + 5, + 4, + 5, + 6, + 4, + 5, + 5, + 3, + 4, + 5, + 2, + 3, + 4, + 5, + 3, + 5, + 4, + 3, + 6, + 6, + 5, + 4, + 6, + 5, + 5, + 5, + 5, + 4, + 6, + 5, + 6, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 6, + 4, + 5, + 4, + 6, + 6, + 5, + 6, + 5, + 5, + 3, + 3, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 3, + 6, + 3, + 5, + 6, + 6, + 4, + 6, + 2, + 5, + 4, + 5, + 6, + 5, + 5, + 4, + 5, + 3, + 6, + 5, + 6, + 4, + 4, + 5, + 5, + 3, + 5, + 6, + 6, + 5, + 6, + 4, + 4, + 4, + 7, + 4, + 3, + 4, + 4, + 2, + 5, + 4, + 5, + 5, + 7, + 4, + 5, + 5, + 4, + 3, + 6, + 5, + 4, + 4, + 6, + 6, + 5, + 5, + 6, + 4, + 4, + 5, + 4, + 3, + 7, + 6, + 4, + 6, + 4, + 3, + 3, + 4, + 3, + 6, + 3, + 5, + 5, + 4, + 7, + 4, + 5, + 6, + 4, + 3, + 6, + 4, + 6, + 4, + 4, + 5, + 7, + 4, + 5, + 4, + 3, + 4, + 5, + 4, + 3, + 5, + 4, + 3, + 4, + 6, + 3, + 5, + 5, + 3, + 5, + 3, + 4, + 4, + 3, + 3, + 5, + 5, + 4, + 6, + 3, + 10, + 12, + 10, + 5, + 4, + 5, + 6, + 3, + 6, + 2, + 3, + 6, + 4, + 3, + 5, + 6, + 5, + 2, + 3, + 4, + 3, + 5, + 4, + 5, + 6, + 4, + 5, + 6, + 5, + 6, + 5, + 4, + 3, + 4, + 4, + 5, + 3, + 4, + 6, + 5, + 6, + 4, + 5, + 2, + 4, + 4, + 4, + 5, + 6, + 5, + 6, + 7, + 6, + 3, + 5, + 4, + 5, + 5, + 4, + 6, + 6, + 5, + 4, + 4, + 5, + 5, + 5, + 6, + 3, + 4, + 6, + 5, + 3, + 5, + 4, + 3, + 4, + 4, + 4, + 3, + 4, + 4, + 4, + 5, + 3, + 4, + 7, + 5, + 4, + 4, + 5, + 6, + 4, + 4, + 4, + 4, + 3, + 4, + 4, + 6, + 3, + 2, + 4, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 6, + 4, + 3, + 5, + 5, + 5, + 4, + 4, + 13, + 10, + 13, + 12, + 5, + 3, + 5, + 6, + 6, + 5, + 5, + 3, + 3, + 6, + 6, + 5, + 5, + 3, + 5, + 4, + 3, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 3, + 6, + 5, + 2, + 5, + 6, + 3, + 4, + 6, + 4, + 6, + 4, + 6, + 5, + 4, + 4, + 4, + 4, + 4, + 3, + 4, + 5, + 6, + 3, + 4, + 6, + 5, + 4, + 5, + 5, + 5, + 4, + 4, + 15, + 13, + 10, + 4, + 10, + 4, + 5, + 5, + 6, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 3, + 3, + 6, + 4, + 6, + 2, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 3, + 6, + 5, + 4, + 3, + 4, + 5, + 5, + 3, + 5, + 5, + 4, + 3, + 4, + 4, + 5, + 3, + 4, + 3, + 4, + 3, + 3, + 5, + 5, + 3, + 16, + 9, + 12, + 10, + 12, + 10, + 4, + 3, + 6, + 5, + 4, + 6, + 4, + 5, + 4, + 4, + 5, + 6, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 6, + 5, + 4, + 4, + 5, + 5, + 5, + 6, + 3, + 4, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 5, + 3, + 5, + 7, + 4, + 4, + 6, + 4, + 4, + 6, + 5, + 3, + 4, + 4, + 9, + 14, + 12, + 13, + 11, + 16, + 4, + 5, + 5, + 3, + 14, + 5, + 3, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 3, + 4, + 6, + 6, + 3, + 5, + 6, + 4, + 3, + 4, + 2, + 6, + 6, + 3, + 5, + 5, + 3, + 3, + 5, + 7, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 6, + 3, + 6, + 5, + 5, + 2, + 5, + 5, + 3, + 4, + 5, + 4, + 15, + 15, + 14, + 12, + 14, + 10, + 11, + 4, + 4, + 4, + 3, + 5, + 4, + 3, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 6, + 4, + 6, + 5, + 5, + 6, + 4, + 4, + 6, + 4, + 3, + 3, + 6, + 3, + 5, + 5, + 4, + 3, + 5, + 4, + 6, + 7, + 3, + 3, + 4, + 5, + 4, + 5, + 4, + 5, + 3, + 4, + 5, + 11, + 16, + 14, + 16, + 9, + 10, + 4, + 4, + 4, + 13, + 9, + 12, + 5, + 3, + 4, + 13, + 14, + 13, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 2, + 6, + 6, + 3, + 7, + 3, + 4, + 4, + 6, + 5, + 5, + 5, + 3, + 5, + 3, + 5, + 4, + 5, + 3, + 3, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 3, + 5, + 6, + 4, + 4, + 5, + 4, + 12, + 4, + 16, + 12, + 16, + 9, + 10, + 16, + 5, + 5, + 4, + 6, + 6, + 5, + 4, + 4, + 4, + 4, + 2, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 4, + 3, + 4, + 3, + 5, + 3, + 5, + 4, + 3, + 5, + 4, + 3, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 6, + 5, + 5, + 4, + 5, + 5, + 9, + 4, + 12, + 13, + 12, + 12, + 16, + 11, + 11, + 10, + 15, + 4, + 15, + 12, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 3, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 6, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 6, + 4, + 5, + 4, + 4, + 5, + 6, + 4, + 7, + 4, + 3, + 5, + 4, + 5, + 3, + 4, + 3, + 5, + 14, + 9, + 5, + 14, + 12, + 12, + 13, + 13, + 4, + 4, + 5, + 3, + 5, + 5, + 3, + 5, + 4, + 4, + 2, + 4, + 4, + 4, + 6, + 4, + 5, + 5, + 5, + 5, + 5, + 6, + 5, + 4, + 5, + 3, + 5, + 5, + 5, + 5, + 3, + 4, + 4, + 6, + 5, + 6, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 3, + 6, + 4, + 7, + 5, + 4, + 3, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 16, + 12, + 9, + 13, + 6, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 6, + 5, + 3, + 5, + 5, + 3, + 5, + 4, + 5, + 3, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 6, + 4, + 5, + 5, + 3, + 4, + 4, + 5, + 4, + 9, + 15, + 14, + 10, + 16, + 13, + 5, + 5, + 4, + 4, + 6, + 3, + 5, + 6, + 5, + 4, + 6, + 3, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 7, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 6, + 5, + 6, + 3, + 4, + 5, + 4, + 6, + 4, + 5, + 4, + 6, + 5, + 5, + 4, + 6, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 14, + 12, + 11, + 14, + 16, + 16, + 4, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 3, + 6, + 6, + 5, + 4, + 5, + 3, + 6, + 4, + 4, + 5, + 3, + 4, + 4, + 3, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 6, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 12, + 16, + 3, + 5, + 5, + 4, + 5, + 5, + 4, + 6, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 4, + 4, + 6, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 6, + 6, + 5, + 5, + 5, + 5, + 5, + 4, + 6, + 5, + 3, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 3, + 12, + 9, + 13, + 15, + 13, + 4, + 6, + 5, + 5, + 6, + 4, + 4, + 5, + 6, + 4, + 5, + 5, + 4, + 4, + 4, + 6, + 5, + 4, + 4, + 4, + 3, + 5, + 4, + 6, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 3, + 3, + 6, + 5, + 6, + 5, + 5, + 3, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 3, + 5, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 6, + 4, + 3, + 5, + 4, + 5, + 5, + 5, + 6, + 6, + 5, + 3, + 4, + 6, + 5, + 5, + 5, + 3, + 7, + 3, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 6, + 4, + 6, + 4, + 4, + 3, + 5, + 4, + 5, + 6, + 5, + 5, + 4, + 5, + 5, + 6, + 5, + 4, + 5, + 4, + 4, + 4, + 4, + 6, + 6, + 5, + 5, + 15, + 13, + 5, + 5, + 4, + 4, + 5, + 4, + 6, + 4, + 5, + 5, + 4, + 5, + 3, + 4, + 4, + 5, + 3, + 6, + 5, + 5, + 4, + 5, + 5, + 5, + 4, + 3, + 4, + 5, + 4, + 5, + 6, + 5, + 4, + 5, + 4, + 5, + 4, + 6, + 4, + 6, + 4, + 5, + 3, + 5, + 4, + 4, + 3, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 3, + 3, + 5, + 5, + 3, + 5, + 6, + 4, + 3, + 6, + 4, + 5, + 6, + 3, + 3, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 6, + 5, + 3, + 5, + 4, + 5, + 4, + 5, + 3, + 3, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 7, + 5, + 5, + 5, + 5, + 5, + 6, + 5, + 6, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 4, + 4, + 3, + 4, + 3, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 3, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 3, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 6, + 4, + 4, + 5, + 4, + 5, + 4, + 3, + 6, + 4, + 4, + 4, + 4, + 5, + 6, + 5, + 3, + 4, + 4, + 5, + 6, + 4, + 4, + 4, + 5, + 4, + 3, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 6, + 4, + 5, + 5, + 3, + 5, + 3, + 3, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 7, + 5, + 4, + 3, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 6, + 4, + 4, + 5, + 3, + 4, + 4, + 6, + 4, + 4, + 4, + 5, + 6, + 6, + 5, + 3, + 6, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 3, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 6, + 5, + 5, + 5, + 6, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 3, + 6, + 4, + 3, + 5, + 4, + 4, + 4, + 4, + 5, + 2, + 4, + 5, + 5, + 4, + 5, + 3, + 5, + 6, + 4, + 3, + 4, + 4, + 5, + 2, + 5, + 5, + 5, + 5, + 5, + 5, + 6, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 2, + 5, + 7, + 5, + 6, + 5, + 5, + 3, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 3, + 4, + 5, + 4, + 6, + 6, + 4, + 5, + 4, + 3, + 4, + 6, + 4, + 5, + 6, + 4, + 4, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 6, + 5, + 3, + 6, + 4, + 3, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 6, + 5, + 5, + 4, + 5, + 5, + 5, + 6, + 4, + 6, + 5, + 5, + 5, + 3, + 4, + 6, + 4, + 5, + 4, + 3, + 5, + 5, + 6, + 5, + 4, + 4, + 4, + 5, + 4, + 6, + 5, + 7, + 5, + 4, + 5, + 3, + 4, + 6, + 4, + 3, + 3, + 5, + 5, + 4, + 5, + 6, + 3, + 4, + 6, + 3, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 2, + 3, + 3, + 5, + 4, + 4, + 5, + 3, + 5, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 6, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 6, + 4, + 6, + 5, + 6, + 5, + 4, + 4, + 5, + 4, + 4, + 6, + 4, + 5, + 5, + 3, + 5, + 6, + 3, + 6, + 4, + 4, + 5, + 5, + 5, + 6, + 4, + 4, + 6, + 6, + 5, + 4, + 5, + 3, + 6, + 5, + 6, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 6, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 2, + 5, + 3, + 3, + 6, + 4, + 3, + 5, + 7, + 5, + 5, + 4, + 4, + 4, + 4, + 3, + 4, + 4, + 5, + 3, + 3, + 5, + 3, + 4, + 5, + 5, + 4, + 6, + 3, + 6, + 5, + 5, + 6, + 4, + 4, + 5, + 5, + 5, + 4, + 6, + 3, + 6, + 4, + 4, + 4, + 5, + 3, + 3, + 4, + 5, + 6, + 5, + 3, + 5, + 3, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 6, + 6, + 6, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 6, + 5, + 4, + 4, + 5, + 2, + 4, + 6, + 3, + 5, + 4, + 4, + 5, + 2, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 3, + 5, + 6, + 3, + 4, + 5, + 6, + 5, + 4, + 3, + 5, + 6, + 4, + 4, + 6, + 5, + 6, + 6, + 6, + 4, + 3, + 5, + 5, + 4, + 2, + 3, + 3, + 4, + 6, + 5, + 4, + 6, + 4, + 2, + 6, + 6, + 4, + 4, + 5, + 6, + 5, + 5, + 7, + 5, + 6, + 5, + 5, + 3, + 2, + 4, + 4, + 5, + 4, + 4, + 6, + 5, + 4, + 5, + 5, + 6, + 4, + 4, + 4, + 6, + 7, + 6, + 5, + 4, + 5, + 6, + 4, + 3, + 6, + 5, + 5, + 6, + 4, + 3, + 4, + 4, + 4, + 3, + 5, + 4, + 4, + 5, + 5, + 4, + 3, + 7, + 5, + 6, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 3, + 4, + 3, + 4, + 3, + 4, + 5, + 6, + 3, + 6, + 4, + 3, + 5, + 5, + 3, + 5, + 5, + 4, + 3, + 6, + 3, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 3, + 5, + 4, + 6, + 5, + 4, + 3, + 7, + 3, + 6, + 3, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 6, + 6, + 3, + 3, + 4, + 4, + 3, + 6, + 3, + 5, + 6, + 6, + 6, + 4, + 4, + 3, + 5, + 5, + 4, + 4, + 4, + 3, + 5, + 4, + 6, + 6, + 6, + 4, + 6, + 4, + 5, + 6, + 5, + 3, + 3, + 4, + 4, + 3, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 4, + 5, + 4, + 3, + 5, + 4, + 5, + 3, + 5, + 4, + 5, + 4, + 5, + 3, + 5, + 4, + 5, + 2, + 4, + 5, + 4, + 4, + 4, + 6, + 3, + 3, + 5, + 5, + 3, + 6, + 6, + 4, + 3, + 4, + 4, + 4, + 6, + 6, + 6, + 5, + 5, + 5, + 4, + 5, + 4, + 6, + 5, + 4, + 6, + 5, + 5, + 6, + 5, + 4, + 5, + 3, + 3, + 5, + 5, + 4, + 6, + 4, + 3, + 3, + 4, + 6, + 3, + 5, + 5, + 3, + 5, + 6, + 5, + 5, + 5, + 4, + 3, + 5, + 4, + 3, + 6, + 6, + 3, + 5, + 4, + 3, + 4, + 5, + 5, + 7, + 4, + 5, + 3, + 5, + 5, + 4, + 6, + 6, + 5, + 4, + 4, + 6, + 6, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 23, + 2, + 3, + 6, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 3, + 4, + 6, + 3, + 7, + 5, + 5, + 5, + 7, + 6, + 3, + 2, + 3, + 6, + 4, + 3, + 4, + 5, + 5, + 4, + 3, + 6, + 6, + 4, + 4, + 6, + 3, + 4, + 5, + 4, + 3, + 4, + 4, + 5, + 3, + 6, + 4, + 4, + 6, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 3, + 4, + 6, + 6, + 6, + 5, + 4, + 4, + 6, + 6, + 6, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 3, + 4, + 4, + 5, + 4, + 6, + 5, + 5, + 6, + 3, + 4, + 6, + 5, + 4, + 6, + 5, + 5, + 4, + 6, + 5, + 4, + 4, + 5, + 6, + 5, + 4, + 4, + 5, + 3, + 5, + 4, + 5, + 3, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 3, + 4, + 4, + 4, + 5, + 4, + 4, + 5, + 6, + 3, + 7, + 6, + 6, + 3, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 3, + 5, + 5, + 5, + 3, + 6, + 3, + 5, + 5, + 3, + 4, + 6, + 2, + 5, + 4, + 5, + 4, + 6, + 6, + 4, + 4, + 3, + 4, + 3, + 4, + 5, + 6, + 3, + 3, + 5, + 5, + 7, + 3, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 3, + 5, + 2, + 5, + 4, + 6, + 6, + 4, + 3, + 2, + 4, + 3, + 5, + 4, + 6, + 4, + 5, + 4, + 4, + 3, + 5, + 5, + 3, + 5, + 5, + 3, + 3, + 4, + 4, + 5, + 3, + 7, + 6, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 5, + 4, + 5, + 3, + 3, + 5, + 4, + 5, + 3, + 6, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 2, + 4, + 5, + 4, + 3, + 5, + 3, + 4, + 4, + 5, + 6, + 7, + 4, + 3, + 5, + 5, + 3, + 5, + 6, + 2, + 5, + 3, + 4, + 5, + 3, + 4, + 4, + 5, + 7, + 3, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 3, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 6, + 5, + 4, + 6, + 5, + 5, + 3, + 2, + 6, + 6, + 4, + 3, + 3, + 6, + 4, + 4, + 5, + 5, + 3, + 4, + 7, + 4, + 4, + 5, + 5, + 5, + 3, + 3, + 4, + 5, + 3, + 5, + 3, + 3, + 6, + 4, + 3, + 6, + 4, + 4, + 5, + 5, + 2, + 5, + 4, + 4, + 5, + 3, + 5, + 5, + 2, + 3, + 6, + 6, + 5, + 4, + 5, + 3, + 3, + 4, + 4, + 5, + 4, + 6, + 4, + 6, + 6, + 6, + 5, + 3, + 3, + 4, + 5, + 4, + 3, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 3, + 6, + 4, + 6, + 5, + 6, + 5, + 4, + 5, + 7, + 4, + 4, + 5, + 3, + 5, + 3, + 3, + 5, + 4, + 5, + 2, + 5, + 6, + 5, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 6, + 6, + 5, + 6, + 4, + 5, + 4, + 4, + 4, + 4, + 5, + 6, + 5, + 4, + 4, + 6, + 4, + 5, + 4, + 4, + 3, + 7, + 5, + 6, + 6, + 5, + 4, + 4, + 3, + 5, + 4, + 4, + 4, + 5, + 3, + 5, + 4, + 6, + 4, + 4, + 5, + 4, + 5, + 4, + 6, + 5, + 5, + 3, + 4, + 6, + 4, + 4, + 5, + 6, + 5, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 6, + 3, + 6, + 4, + 6, + 6, + 6, + 6, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 3, + 3, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 3, + 4, + 3, + 4, + 3, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 3, + 6, + 3, + 6, + 4, + 5, + 5, + 5, + 6, + 5, + 6, + 4, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 3, + 6, + 3, + 6, + 6, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 6, + 6, + 4, + 4, + 4, + 5, + 3, + 6, + 6, + 4, + 4, + 3, + 4, + 5, + 6, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 6, + 5, + 4, + 5, + 4, + 6, + 6, + 5, + 4, + 5, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 6, + 5, + 3, + 5, + 5, + 4, + 4, + 6, + 5, + 5, + 6, + 5, + 5, + 4, + 5, + 4, + 5, + 3, + 4, + 5, + 3, + 4, + 4, + 5, + 4, + 3, + 3, + 5, + 4, + 5, + 4, + 4, + 6, + 4, + 5, + 5, + 5, + 4, + 4, + 6, + 5, + 5, + 3, + 4, + 3, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 5, + 4, + 5, + 4, + 5, + 4, + 3, + 6, + 4, + 4, + 4, + 4, + 4, + 4, + 3, + 6, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 3, + 4, + 6, + 6, + 5, + 5, + 2, + 5, + 4, + 3, + 4, + 4, + 5, + 6, + 4, + 6, + 3, + 6, + 4, + 5, + 5, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 3, + 6, + 4, + 4, + 3, + 4, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 4, + 6, + 7, + 5, + 5, + 4, + 5, + 4, + 6, + 4, + 5, + 5, + 4, + 5, + 6, + 5, + 4, + 3, + 4, + 5, + 6, + 2, + 4, + 5, + 4, + 5, + 3, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 6, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 6, + 6, + 5, + 5, + 5, + 3, + 4, + 4, + 3, + 4, + 4, + 3, + 6, + 3, + 3, + 5, + 6, + 4, + 3, + 5, + 5, + 5, + 5, + 4, + 5, + 6, + 5, + 3, + 4, + 4, + 4, + 4, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 6, + 3, + 5, + 6, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 6, + 3, + 5, + 3, + 3, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 5, + 5, + 6, + 4, + 4, + 4, + 4, + 6, + 5, + 5, + 3, + 6, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 5, + 3, + 6, + 5, + 3, + 5, + 5, + 5, + 5, + 3, + 4, + 4, + 3, + 4, + 3, + 4, + 6, + 4, + 6, + 3, + 4, + 4, + 5, + 5, + 4, + 6, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 4, + 2, + 6, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 6, + 6, + 4, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 4, + 4, + 4, + 5, + 4, + 5, + 6, + 5, + 4, + 5, + 6, + 6, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 3, + 6, + 6, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 3, + 5, + 4, + 6, + 5, + 5, + 5, + 5, + 2, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 3, + 3, + 5, + 4, + 3, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 6, + 6, + 4, + 5, + 4, + 6, + 3, + 5, + 6, + 4, + 4, + 4, + 3, + 5, + 3, + 4, + 5, + 4, + 4, + 5, + 5, + 6, + 5, + 4, + 6, + 4, + 5, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 5, + 4, + 3, + 4, + 5, + 5, + 4, + 4, + 5, + 6, + 4, + 3, + 6, + 5, + 6, + 4, + 6, + 3, + 5, + 6, + 5, + 4, + 3, + 3, + 5, + 5, + 5, + 5, + 4, + 4, + 3, + 5, + 5, + 4, + 4, + 4, + 6, + 6, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 7, + 6, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 3, + 3, + 4, + 6, + 6, + 6, + 3, + 4, + 4, + 4, + 3, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 3, + 3, + 3, + 3, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 6, + 4, + 4, + 3, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 4, + 4, + 5, + 3, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 3, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 4, + 5, + 5, + 10, + 5, + 5, + 4, + 5, + 3, + 6, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 4, + 6, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 5, + 5, + 6, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 3, + 4, + 4, + 4, + 3, + 4, + 5, + 4, + 3, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 6, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 5, + 5, + 4, + 4, + 4, + 6, + 5, + 5, + 5, + 6, + 4, + 4, + 6, + 2, + 10, + 9, + 9, + 14, + 15, + 15, + 5, + 4, + 6, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 6, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 4, + 5, + 6, + 5, + 5, + 4, + 3, + 3, + 5, + 4, + 6, + 4, + 4, + 3, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 6, + 4, + 5, + 5, + 6, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 6, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 6, + 4, + 4, + 5, + 11, + 13, + 13, + 13, + 12, + 16, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 3, + 6, + 4, + 5, + 5, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 3, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 6, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 4, + 6, + 4, + 4, + 5, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 6, + 4, + 5, + 4, + 4, + 3, + 5, + 5, + 13, + 9, + 14, + 16, + 12, + 13, + 5, + 5, + 5, + 6, + 4, + 4, + 5, + 5, + 4, + 3, + 5, + 4, + 3, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 3, + 5, + 4, + 4, + 5, + 4, + 3, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 3, + 6, + 3, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 6, + 5, + 5, + 5, + 6, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 5, + 6, + 5, + 4, + 4, + 5, + 6, + 6, + 3, + 3, + 3, + 6, + 4, + 14, + 9, + 16, + 12, + 14, + 15, + 4, + 5, + 4, + 5, + 7, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 6, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 6, + 6, + 6, + 5, + 5, + 4, + 5, + 4, + 6, + 5, + 4, + 4, + 5, + 4, + 3, + 4, + 3, + 5, + 4, + 5, + 3, + 3, + 5, + 5, + 5, + 5, + 5, + 4, + 5, + 3, + 5, + 5, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 4, + 4, + 4, + 5, + 4, + 3, + 5, + 12, + 11, + 6, + 5, + 15, + 13, + 16, + 10, + 4, + 5, + 4, + 3, + 6, + 5, + 4, + 6, + 4, + 4, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 14, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 4, + 4, + 6, + 5, + 6, + 6, + 3, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 6, + 3, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 3, + 4, + 4, + 5, + 3, + 6, + 4, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 3, + 5, + 3, + 4, + 3, + 5, + 5, + 4, + 5, + 4, + 16, + 14, + 15, + 9, + 9, + 5, + 4, + 4, + 5, + 4, + 4, + 3, + 4, + 4, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 10, + 10, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 4, + 6, + 4, + 3, + 4, + 4, + 4, + 4, + 6, + 5, + 5, + 4, + 3, + 4, + 6, + 3, + 4, + 4, + 4, + 4, + 4, + 6, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 3, + 4, + 5, + 3, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 12, + 13, + 15, + 1, + 1, + 11, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 5, + 6, + 5, + 5, + 4, + 5, + 4, + 3, + 3, + 5, + 5, + 5, + 7, + 5, + 6, + 9, + 10, + 9, + 14, + 14, + 13, + 16, + 5, + 4, + 5, + 6, + 4, + 6, + 4, + 5, + 5, + 6, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 13, + 12, + 14, + 11, + 14, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 4, + 3, + 5, + 5, + 4, + 5, + 6, + 4, + 3, + 3, + 6, + 4, + 6, + 5, + 6, + 5, + 3, + 4, + 6, + 5, + 4, + 6, + 5, + 5, + 5, + 5, + 4, + 3, + 4, + 5, + 3, + 4, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 13, + 16, + 1, + 12, + 1, + 12, + 14, + 15, + 14, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 5, + 4, + 5, + 3, + 5, + 5, + 5, + 6, + 4, + 6, + 6, + 6, + 4, + 6, + 4, + 13, + 11, + 14, + 16, + 10, + 15, + 5, + 4, + 4, + 4, + 6, + 4, + 4, + 6, + 3, + 4, + 4, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 10, + 16, + 10, + 16, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 6, + 4, + 4, + 4, + 3, + 4, + 5, + 4, + 4, + 3, + 5, + 3, + 4, + 5, + 5, + 5, + 6, + 5, + 6, + 5, + 3, + 4, + 3, + 4, + 4, + 3, + 5, + 4, + 3, + 4, + 6, + 5, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 15, + 15, + 15, + 13, + 9, + 14, + 15, + 13, + 13, + 10, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 4, + 4, + 4, + 5, + 3, + 5, + 6, + 5, + 4, + 3, + 3, + 5, + 5, + 4, + 6, + 5, + 6, + 16, + 15, + 4, + 5, + 4, + 6, + 6, + 5, + 2, + 4, + 5, + 5, + 5, + 5, + 3, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 15, + 12, + 13, + 15, + 16, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 5, + 5, + 2, + 6, + 6, + 3, + 6, + 5, + 3, + 6, + 5, + 5, + 4, + 4, + 2, + 4, + 6, + 3, + 3, + 4, + 5, + 5, + 5, + 6, + 4, + 5, + 4, + 3, + 5, + 4, + 6, + 5, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 14, + 1, + 14, + 10, + 1, + 1, + 1, + 9, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 5, + 6, + 5, + 5, + 5, + 5, + 4, + 4, + 3, + 5, + 5, + 5, + 2, + 5, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 6, + 4, + 5, + 6, + 4, + 3, + 6, + 4, + 3, + 5, + 7, + 4, + 24, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 14, + 11, + 12, + 15, + 11, + 13, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 3, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 6, + 6, + 6, + 5, + 5, + 5, + 6, + 5, + 4, + 4, + 3, + 5, + 4, + 5, + 5, + 7, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 10, + 11, + 1, + 11, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 4, + 4, + 4, + 6, + 5, + 5, + 4, + 5, + 6, + 4, + 2, + 7, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 6, + 5, + 4, + 5, + 5, + 5, + 5, + 6, + 5, + 5, + 3, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 10, + 14, + 14, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 3, + 4, + 5, + 4, + 4, + 5, + 7, + 5, + 3, + 5, + 6, + 6, + 4, + 4, + 5, + 4, + 4, + 5, + 3, + 5, + 6, + 4, + 4, + 4, + 4, + 4, + 6, + 6, + 5, + 5, + 4, + 3, + 4, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 14, + 10, + 12, + 10, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 4, + 4, + 4, + 5, + 6, + 4, + 5, + 4, + 5, + 3, + 3, + 6, + 4, + 3, + 4, + 6, + 5, + 4, + 5, + 5, + 7, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 4, + 3, + 3, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 13, + 9, + 14, + 16, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 3, + 5, + 4, + 5, + 3, + 5, + 4, + 4, + 3, + 6, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 3, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 4, + 6, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 16, + 16, + 16, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 3, + 4, + 5, + 6, + 5, + 6, + 5, + 5, + 5, + 4, + 6, + 3, + 3, + 4, + 4, + 6, + 5, + 4, + 5, + 4, + 6, + 3, + 4, + 6, + 6, + 5, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 11, + 14, + 15, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 6, + 3, + 4, + 3, + 3, + 4, + 5, + 6, + 4, + 5, + 4, + 3, + 4, + 4, + 4, + 6, + 4, + 4, + 3, + 4, + 4, + 3, + 6, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 6, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 9, + 13, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 6, + 5, + 4, + 4, + 4, + 4, + 5, + 6, + 5, + 5, + 6, + 5, + 6, + 4, + 5, + 5, + 3, + 6, + 3, + 4, + 6, + 4, + 4, + 4, + 5, + 4, + 3, + 5, + 4, + 4, + 4, + 3, + 5, + 5, + 5, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 5, + 7, + 5, + 4, + 3, + 5, + 5, + 3, + 5, + 3, + 3, + 4, + 6, + 4, + 4, + 6, + 3, + 6, + 5, + 4, + 5, + 2, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 6, + 5, + 6, + 6, + 6, + 3, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 9, + 12, + 10, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 6, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 4, + 6, + 3, + 6, + 3, + 4, + 5, + 5, + 4, + 3, + 3, + 5, + 4, + 6, + 4, + 5, + 5, + 5, + 3, + 4, + 5, + 6, + 4, + 4, + 6, + 4, + 6, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 6, + 4, + 5, + 6, + 4, + 4, + 6, + 5, + 5, + 6, + 3, + 5, + 3, + 6, + 4, + 4, + 5, + 5, + 4, + 3, + 5, + 4, + 3, + 3, + 4, + 5, + 5, + 3, + 5, + 4, + 5, + 4, + 5, + 3, + 3, + 3, + 4, + 4, + 3, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 14, + 15, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 6, + 6, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 6, + 5, + 5, + 4, + 5, + 3, + 5, + 4, + 6, + 5, + 4, + 5, + 5, + 4, + 5, + 6, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 4, + 4, + 6, + 5, + 5, + 4, + 5, + 4, + 6, + 6, + 6, + 5, + 7, + 5, + 5, + 5, + 6, + 4, + 5, + 3, + 4, + 4, + 4, + 3, + 3, + 4, + 4, + 5, + 6, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 6, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 10, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 5, + 6, + 5, + 5, + 5, + 5, + 6, + 5, + 4, + 6, + 5, + 4, + 4, + 3, + 4, + 7, + 2, + 6, + 4, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 6, + 4, + 5, + 4, + 6, + 4, + 3, + 4, + 5, + 6, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 3, + 6, + 5, + 3, + 4, + 4, + 3, + 3, + 5, + 4, + 5, + 6, + 4, + 5, + 5, + 3, + 3, + 6, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 6, + 4, + 6, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 6, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 6, + 6, + 4, + 5, + 6, + 4, + 4, + 5, + 4, + 5, + 5, + 6, + 5, + 4, + 5, + 4, + 5, + 4, + 6, + 5, + 5, + 6, + 5, + 5, + 4, + 3, + 6, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 6, + 5, + 3, + 4, + 2, + 4, + 4, + 3, + 5, + 4, + 5, + 4, + 5, + 4, + 7, + 7, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 6, + 6, + 6, + 4, + 3, + 4, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 3, + 5, + 6, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 4, + 6, + 6, + 5, + 6, + 4, + 7, + 5, + 5, + 6, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 3, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 6, + 6, + 4, + 4, + 5, + 4, + 4, + 5, + 3, + 5, + 6, + 4, + 5, + 5, + 5, + 5, + 5, + 6, + 5, + 5, + 4, + 5, + 3, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 4, + 3, + 5, + 3, + 3, + 5, + 4, + 5, + 4, + 3, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 4, + 6, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 2, + 4, + 4, + 4, + 6, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 6, + 4, + 5, + 4, + 3, + 4, + 4, + 5, + 3, + 4, + 4, + 5, + 3, + 3, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 4, + 3, + 4, + 5, + 3, + 2, + 5, + 3, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 6, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 6, + 4, + 5, + 4, + 5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 4, + 5, + 5, + 3, + 4, + 5, + 6, + 3, + 5, + 3, + 4, + 5, + 6, + 4, + 4, + 5, + 5, + 5, + 6, + 3, + 5, + 6, + 5, + 6, + 5, + 6, + 5, + 6, + 5, + 3, + 6, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 6, + 3, + 3, + 3, + 5, + 4, + 4, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 5, + 4, + 5, + 4, + 4, + 4, + 3, + 4, + 5, + 5, + 5, + 3, + 4, + 5, + 3, + 4, + 5, + 4, + 3, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 3, + 4, + 5, + 4, + 4, + 5, + 4, + 6, + 3, + 6, + 4, + 4, + 6, + 5, + 4, + 3, + 6, + 5, + 3, + 5, + 4, + 4, + 4, + 6, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 5, + 2, + 4, + 5, + 5, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 6, + 4, + 6, + 3, + 6, + 5, + 4, + 4, + 4, + 3, + 4, + 3, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 6, + 4, + 5, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 4, + 6, + 5, + 4, + 3, + 5, + 4, + 4, + 5, + 5, + 3, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 6, + 4, + 3, + 6, + 5, + 3, + 3, + 4, + 7, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 6, + 5, + 5, + 5, + 6, + 5, + 5, + 4, + 4, + 3, + 5, + 4, + 4, + 4, + 7, + 4, + 4, + 5, + 3, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 4, + 3, + 5, + 4, + 6, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 3, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 3, + 5, + 4, + 5, + 5, + 6, + 5, + 5, + 4, + 4, + 4, + 4, + 3, + 6, + 5, + 6, + 4, + 4, + 5, + 5, + 2, + 4, + 5, + 4, + 4, + 6, + 5, + 4, + 3, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 3, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 6, + 4, + 6, + 4, + 6, + 4, + 4, + 6, + 5, + 2, + 6, + 5, + 3, + 4, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 3, + 4, + 4, + 5, + 4, + 3, + 3, + 7, + 5, + 5, + 3, + 3, + 4, + 5, + 5, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 3, + 3, + 4, + 6, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 3, + 6, + 5, + 5, + 4, + 5, + 5, + 5, + 6, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 6, + 4, + 5, + 3, + 4, + 4, + 5, + 5, + 4, + 5, + 6, + 6, + 4, + 3, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 3, + 5, + 5, + 6, + 3, + 5, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 3, + 6, + 3, + 4, + 3, + 5, + 6, + 3, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 6, + 4, + 5, + 5, + 4, + 4, + 4, + 6, + 4, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 6, + 6, + 5, + 6, + 4, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 6, + 3, + 4, + 6, + 4, + 5, + 6, + 6, + 4, + 5, + 5, + 4, + 3, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 3, + 5, + 4, + 5, + 3, + 5, + 6, + 6, + 3, + 5, + 6, + 6, + 6, + 5, + 4, + 5, + 6, + 4, + 4, + 4, + 6, + 5, + 3, + 5, + 5, + 5, + 3, + 4, + 4, + 4, + 6, + 4, + 4, + 4, + 3, + 4, + 4, + 5, + 4, + 5, + 4, + 4, + 6, + 6, + 6, + 4, + 4, + 5, + 4, + 5, + 4, + 3, + 4, + 7, + 5, + 4, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 5, + 6, + 5, + 3, + 3, + 5, + 4, + 4, + 4, + 7, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 3, + 5, + 4, + 4, + 4, + 4, + 4, + 6, + 4, + 4, + 3, + 4, + 4, + 4, + 4, + 6, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 6, + 4, + 5, + 5, + 4, + 4, + 5, + 5, + 6, + 3, + 6, + 5, + 3, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 3, + 6, + 5, + 4, + 4, + 3, + 3, + 4, + 3, + 5, + 5, + 5, + 5, + 3, + 5, + 4, + 2, + 4, + 6, + 3, + 7, + 5, + 5, + 4, + 3, + 5, + 5, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 5, + 5, + 4, + 6, + 4, + 4, + 5, + 5, + 3, + 4, + 6, + 5, + 4, + 5, + 5, + 5, + 4, + 3, + 5, + 4, + 6, + 4, + 5, + 4, + 3, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 6, + 5, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 3, + 4, + 4, + 5, + 4, + 6, + 5, + 5, + 3, + 6, + 6, + 3, + 4, + 5, + 3, + 5, + 5, + 5, + 5, + 5, + 4, + 3, + 4, + 4, + 5, + 3, + 7, + 4, + 4, + 4, + 4, + 6, + 5, + 4, + 3, + 5, + 5, + 6, + 4, + 4, + 4, + 4, + 4, + 3, + 4, + 6, + 3, + 4, + 4, + 6, + 5, + 4, + 3, + 5, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 4, + 3, + 6, + 4, + 4, + 6, + 5, + 6, + 6, + 3, + 4, + 3, + 6, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 2, + 4, + 6, + 2, + 5, + 5, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 6, + 5, + 5, + 5, + 3, + 7, + 3, + 4, + 6, + 5, + 4, + 4, + 7, + 5, + 4, + 6, + 3, + 5, + 6, + 6, + 5, + 6, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 3, + 3, + 5, + 5, + 4, + 5, + 5, + 6, + 4, + 5, + 5, + 4, + 6, + 5, + 6, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 2, + 6, + 5, + 6, + 5, + 5, + 3, + 4, + 5, + 6, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 3, + 4, + 4, + 3, + 6, + 7, + 4, + 5, + 3, + 4, + 4, + 4, + 6, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 3, + 6, + 5, + 4, + 5, + 4, + 3, + 5, + 4, + 4, + 5, + 4, + 5, + 6, + 5, + 4, + 5, + 4, + 6, + 5, + 4, + 4, + 3, + 4, + 3, + 5, + 3, + 3, + 6, + 5, + 6, + 5, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 3, + 4, + 5, + 3, + 6, + 3, + 6, + 4, + 6, + 5, + 3, + 6, + 6, + 4, + 4, + 6, + 5, + 5, + 3, + 5, + 5, + 6, + 3, + 4, + 6, + 5, + 6, + 5, + 4, + 5, + 6, + 3, + 5, + 5, + 5, + 5, + 4, + 7, + 4, + 5, + 4, + 5, + 3, + 4, + 5, + 3, + 4, + 5, + 5, + 4, + 3, + 4, + 4, + 6, + 3, + 5, + 5, + 5, + 4, + 3, + 3, + 6, + 6, + 3, + 4, + 4, + 3, + 6, + 5, + 5, + 4, + 6, + 5, + 6, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 3, + 3, + 5, + 5, + 4, + 4, + 7, + 7, + 4, + 3, + 4, + 3, + 5, + 4, + 4, + 6, + 4, + 5, + 5, + 4, + 4, + 6, + 5, + 5, + 5, + 6, + 7, + 4, + 3, + 4, + 4, + 4, + 5, + 4, + 7, + 4, + 4, + 3, + 3, + 5, + 5, + 4, + 6, + 5, + 3, + 7, + 2, + 4, + 6, + 5, + 5, + 4, + 4, + 6, + 5, + 4, + 5, + 2, + 5, + 3, + 5, + 4, + 6, + 4, + 4, + 3, + 4, + 6, + 3, + 3, + 5, + 5, + 4, + 4, + 5, + 5, + 4, + 3, + 4, + 6, + 5, + 6, + 5, + 5, + 4, + 3, + 3, + 3, + 6, + 3, + 5, + 5, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 6, + 3, + 4, + 6, + 4, + 5, + 4, + 4, + 5, + 6, + 3, + 4, + 4, + 4, + 6, + 4, + 4, + 5, + 4, + 5, + 5, + 5, + 6, + 6, + 5, + 4, + 6, + 3, + 6, + 5, + 3, + 4, + 4, + 4, + 6, + 6, + 4, + 2, + 4, + 6, + 4, + 4, + 4, + 4, + 5, + 4, + 5, + 6, + 5, + 6, + 4, + 5, + 6, + 5, + 5, + 5, + 5, + 5, + 6, + 4, + 2, + 5, + 5, + 4, + 4, + 4, + 6, + 4, + 3, + 4, + 4, + 4, + 6, + 5, + 5, + 6, + 5, + 4, + 6, + 5, + 7, + 5, + 5, + 2, + 4, + 2, + 6, + 6, + 6, + 4, + 4, + 4, + 4, + 6, + 4, + 6, + 5, + 3, + 7, + 6, + 3, + 6, + 7, + 2, + 4, + 5, + 7, + 5, + 4, + 5, + 6, + 4, + 5, + 5, + 6, + 3, + 4, + 5, + 4, + 6, + 5, + 5, + 5, + 4, + 5, + 5, + 6, + 5, + 6, + 5, + 3, + 3, + 5, + 5, + 5, + 5, + 4, + 4, + 6, + 5, + 5, + 6, + 4, + 6, + 4, + 6, + 4, + 6, + 4, + 5, + 4, + 3, + 3, + 5, + 5, + 5, + 5, + 6, + 5, + 6, + 5, + 5, + 6, + 6, + 6, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 4, + 6, + 5, + 3, + 5, + 3, + 6, + 6, + 4, + 4, + 5, + 2, + 4, + 6, + 3, + 4, + 6, + 5, + 4, + 7, + 4, + 5, + 5, + 4, + 5, + 5, + 5, + 3, + 5, + 5, + 4, + 4, + 3, + 2, + 5, + 4, + 5, + 6, + 5, + 4, + 6, + 5, + 4, + 5, + 7, + 4, + 3, + 4, + 5, + 5, + 6, + 5, + 4, + 5, + 2, + 3, + 3, + 3, + 4, + 2, + 5, + 6, + 4, + 3, + 5, + 4, + 4, + 4, + 6, + 5, + 5, + 6, + 4, + 5, + 5, + 5, + 4, + 3, + 3, + 3, + 4, + 4, + 4, + 3, + 5, + 4, + 3, + 4, + 4, + 4, + 6, + 6, + 6, + 3, + 4, + 2, + 4, + 4, + 4, + 5, + 4, + 4, + 5, + 5, + 6, + 5, + 5, + 6, + 5, + 3, + 5, + 4, + 5, + 4, + 6, + 6, + 4, + 4, + 5, + 3, + 5, + 2, + 6, + 2, + 5, + 5, + 5, + 3, + 4, + 5, + 4, + 5, + 4, + 4, + 4, + 6, + 6, + 4, + 4, + 5, + 5, + 6, + 5, + 4, + 6, + 5, + 4, + 6, + 3, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 6, + 5, + 4, + 5, + 6, + 5, + 5, + 4, + 4, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 3, + 6, + 4, + 6, + 6, + 5, + 3, + 3, + 4, + 4, + 5, + 3, + 5, + 4, + 5, + 5, + 4, + 4, + 6, + 5, + 5, + 5, + 5, + 5, + 6, + 4, + 4, + 4, + 5, + 3, + 5, + 6, + 2, + 5, + 5, + 5, + 6, + 6, + 4, + 5, + 5, + 3, + 6, + 2, + 4, + 3, + 5, + 3, + 4, + 6, + 5, + 5, + 3, + 5, + 5, + 4, + 4, + 3, + 4, + 4, + 6, + 4, + 5, + 4, + 4, + 6, + 6, + 5, + 3, + 2, + 4, + 3, + 4, + 5, + 4, + 5, + 4, + 3, + 4, + 4, + 4, + 5, + 4, + 6, + 4, + 6, + 4, + 5, + 4, + 4, + 3, + 6, + 6, + 5, + 5, + 5, + 4, + 6, + 4, + 3, + 4, + 5, + 6, + 5, + 3, + 5, + 4, + 3, + 4, + 4, + 4, + 6, + 3, + 5, + 5, + 4, + 5, + 4, + 5, + 3, + 4, + 4, + 5, + 5, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 6, + 5, + 4, + 6, + 3, + 6, + 5, + 4, + 4, + 4, + 5, + 5, + 5, + 6, + 4, + 4, + 4, + 4, + 6, + 4, + 4, + 3, + 4, + 6, + 3, + 5, + 5, + 4, + 4, + 3, + 5, + 5, + 5, + 5, + 3, + 6, + 4, + 6, + 3, + 4, + 5, + 5, + 3, + 5, + 4, + 5, + 4, + 5, + 6, + 4, + 6, + 5, + 4, + 4, + 3, + 4, + 5, + 4, + 4, + 4, + 4, + 6, + 5, + 7, + 4, + 6, + 5, + 5, + 5, + 5, + 5, + 5, + 3, + 4, + 6, + 4, + 3, + 4, + 3, + 4, + 3, + 4, + 4, + 7, + 2, + 4, + 4, + 5, + 6, + 4, + 6, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 4, + 3, + 3, + 4, + 5, + 5, + 5, + 3, + 5, + 4, + 3, + 5, + 5, + 5, + 4, + 5, + 6, + 5, + 4, + 6, + 6, + 4, + 4, + 5, + 3, + 3, + 4, + 5, + 4, + 5, + 5, + 5, + 6, + 3, + 4, + 3, + 3, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 4, + 6, + 5, + 4, + 4, + 4, + 6, + 4, + 6, + 5, + 5, + 6, + 6, + 4, + 5, + 4, + 3, + 4, + 5, + 3, + 6, + 5, + 5, + 4, + 4, + 5, + 3, + 5, + 4, + 5, + 4, + 3, + 4, + 2, + 4, + 5, + 5, + 6, + 4, + 5, + 5, + 4, + 5, + 4, + 3, + 6, + 4, + 4, + 3, + 5, + 5, + 4, + 4, + 6, + 4, + 3, + 4, + 3, + 5, + 5, + 5, + 4, + 5, + 4, + 5, + 5, + 4, + 3, + 5, + 4, + 4, + 3, + 5, + 6, + 5, + 2, + 4, + 4, + 3, + 5, + 3, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 3, + 5, + 4, + 6, + 3, + 5, + 6, + 3, + 4, + 5, + 4, + 3, + 5, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 5, + 3, + 4, + 4, + 6, + 5, + 5, + 4, + 5, + 5, + 6, + 4, + 5, + 3, + 4, + 3, + 5, + 4, + 6, + 5, + 5, + 4, + 5, + 3, + 6, + 6, + 6, + 5, + 4, + 4, + 5, + 6, + 5, + 6, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 6, + 5, + 5, + 5, + 5, + 3, + 5, + 3, + 6, + 5, + 4, + 6, + 5, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 3, + 3, + 7, + 4, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 4, + 5, + 4, + 5, + 6, + 5, + 4, + 6, + 5, + 4, + 4, + 4, + 4, + 6, + 5, + 6, + 5, + 5, + 5, + 6, + 3, + 4, + 5, + 5, + 5, + 5, + 4, + 4, + 3, + 5, + 6, + 5, + 6, + 6, + 6, + 4, + 4, + 5, + 2, + 4, + 5, + 6, + 5, + 5, + 5, + 4, + 4, + 4, + 5, + 4, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 6, + 5, + 5, + 3, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 5, + 5, + 6, + 4, + 3, + 4, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 4, + 4, + 4, + 5, + 3, + 4, + 3, + 2, + 5, + 4, + 6, + 4, + 4, + 5, + 3, + 3, + 4, + 4, + 5, + 4, + 3, + 5, + 5, + 5, + 5, + 3, + 5, + 4, + 5, + 4, + 5, + 3, + 5, + 5, + 3, + 6, + 5, + 4, + 5, + 5, + 5, + 5, + 6, + 4, + 5, + 5, + 5, + 4, + 3, + 6, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 3, + 4, + 5, + 5, + 6, + 6, + 6, + 5, + 5, + 5, + 5, + 5, + 4, + 4, + 6, + 4, + 3, + 5, + 4, + 4, + 6, + 5, + 5, + 6, + 4, + 5, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 4, + 5, + 2, + 4, + 4, + 7, + 4, + 4, + 3, + 4, + 5, + 4, + 5, + 5, + 5, + 5, + 4, + 5, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 6, + 4, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 4, + 3, + 5, + 4, + 3, + 5, + 4, + 5, + 4, + 4, + 5, + 4, + 4, + 3, + 5, + 5, + 5, + 5, + 4, + 5, + 4, + 3, + 4, + 3, + 5, + 5, + 6, + 4, + 5, + 6, + 3, + 6, + 5, + 4, + 5, + 5, + 5, + 5, + 3, + 4, + 5, + 5, + 5, + 6, + 5, + 4, + 5, + 5, + 4, + 4, + 4, + 4, + 5 + ], + "height": 128, + "id": 1, + "name": "Floor", + "opacity": 1, + "type": "tilelayer", + "visible": true, + "width": 128, + "x": 0, + "y": 0 + }, + { + "data": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 49, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 58, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 53, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 57, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 65, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 58, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 63, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 68, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 57, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 56, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 67, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 59, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 56, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 66, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 66, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 49, + 0, + 64, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 53, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 62, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 56, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 61, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 55, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 56, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 67, + 0, + 0, + 0, + 0, + 0, + 0, + 52, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 53, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 62, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 55, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 67, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 49, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 56, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 49, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 49, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 68, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 62, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 62, + 66, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 61, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 61, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 65, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 49, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 68, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 56, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 57, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 56, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 65, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 49, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 55, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 58, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 61, + 0, + 0, + 0, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 52, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 66, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 67, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 68, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 55, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 61, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 52, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 56, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 68, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 52, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 59, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 58, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 67, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 65, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 62, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 50, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 66, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 62, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 64, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 55, + 0, + 0, + 0, + 0, + 0, + 0, + 57, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 67, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 66, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 68, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51, + 66, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 58, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 55, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 58, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 54, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 62, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 61, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 51 + ], + "height": 128, + "id": 2, + "name": "Rocks", + "opacity": 1, + "type": "tilelayer", + "visible": true, + "width": 128, + "x": 0, + "y": 0 + }, + { + "data": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 39, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 41, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "height": 128, + "id": 3, + "name": "Decorations", + "opacity": 1, + "type": "tilelayer", + "visible": true, + "width": 128, + "x": 0, + "y": 0 + } + ], + "nextlayerid": 4, + "nextobjectid": 1, + "orientation": "isometric", + "renderorder": "right-down", + "tiledversion": "1.9.2", + "tileheight": 16, + "tilesets": [ + { + "columns": 11, + "firstgid": 1, + "image": "../../tilesets/floors32x32.png", + "imageheight": 352, + "imagewidth": 352, + "margin": 0, + "name": "floorsPrimary", + "objectalignment": "center", + "spacing": 0, + "tilecount": 121, + "tileheight": 32, + "tilewidth": 32 + } + ], + "tilewidth": 32, + "type": "map", + "version": "1.9", + "width": 128 +} \ No newline at end of file diff --git a/src/components/app.jsx b/src/components/app.jsx index 70b6d39..7fff1d7 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -16,6 +16,8 @@ function PhaserGame({ code, client }) { ...GameWindow, parent: "phaser", }); + // Expose game globally for E2E tests (Playwright browser context) + window.game = gameRef.current; // Expose colyseus client for scenes to access gameRef.current.colyseusClient = client; gameRef.current.roomCode = code; diff --git a/src/components/gameWindow.jsx b/src/components/gameWindow.jsx index a8e76f0..e8d6e1a 100644 --- a/src/components/gameWindow.jsx +++ b/src/components/gameWindow.jsx @@ -3,7 +3,8 @@ import Boot_Loader from 'Scenes/Boot_Loader.js' import Main_Menu from 'Scenes/Main_Menu.js' import Map_Player from 'Scenes/Map_Player.js' import Server_Connector from 'Scenes/Server_Connector.js' - +import VictoryScene from 'Scenes/VictoryScene.js' + const config = { type: Phaser.WEBGL, width: 800, @@ -29,7 +30,7 @@ const config = { }, // The first scene is the primary. But, you should override the second scene // Because the bootloader loads all of the assets - scene: [Boot_Loader, Map_Player, Main_Menu, Server_Connector], + scene: [Boot_Loader, Map_Player, Main_Menu, Server_Connector, VictoryScene], } export default config diff --git a/src/entities/Unit.js b/src/entities/Unit.js index f31f9cc..6158bb2 100644 --- a/src/entities/Unit.js +++ b/src/entities/Unit.js @@ -31,7 +31,7 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite { }, owner: { playerId: config.playerId || null, - team: config.team || 'neutral', + teamId: config.teamId || null, teamColor: config.teamColor || 0xffffff }, inventory: { @@ -50,7 +50,13 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite { damage: config.damage || 25, fireRate: config.fireRate || 1000, projectileType: config.projectileType || 'rifle', - lastFireTime: 0 + lastFireTime: 0, + canFire(currentTime) { + return (currentTime - this.lastFireTime) >= this.fireRate; + }, + recordFire(time) { + this.lastFireTime = time; + }, } }; @@ -131,35 +137,28 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite { } /** - * Return the enemy faction container based on which container this unit belongs to. + * Return enemy units via TeamManager. */ - getEnemyContainer() { - if (!this.parentContainer) return null; - const name = this.parentContainer.name; - if (!name) return null; - if (name.toLowerCase().includes('good')) { - return this.scene.badGuys || null; - } - if (name.toLowerCase().includes('bad')) { - return this.scene.goodGuys || null; - } - return null; + getEnemyUnits() { + const teamId = this.getData('teamId'); + if (!teamId) return new Set(); + return this.scene.teamManager.getEnemyUnits(teamId); } /** - * Return the friendly faction container. + * Return friendly units via TeamManager. */ - getFriendlyContainer() { - if (!this.parentContainer) return null; - const name = this.parentContainer.name; - if (!name) return null; - if (name.toLowerCase().includes('good')) { - return this.scene.goodGuys || null; - } - if (name.toLowerCase().includes('bad')) { - return this.scene.badGuys || null; - } - return null; + getFriendlyUnits() { + const teamId = this.getData('teamId'); + if (!teamId) return this.scene.teamManager.getAllUnits(); + return this.scene.teamManager.getTeamUnits(teamId); + } + + /** + * Check if this unit is an enemy of another unit. + */ + isEnemyOf(otherUnit) { + return this.scene.teamManager.isEnemy(this, otherUnit); } /** @@ -251,12 +250,32 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite { orientToTarget(target) { if (!target) return; - + const direction = this.getDirection(this, target); const shouldFlip = direction === 'EAST' || direction === 'SOUTH'; this.setFlipX(shouldFlip); } + /** + * Set sprite orientation based on two points (used in moveToPath). + * Mirrors PhaserClasses/Custom_Entity newOrientation for consistency. + */ + newOrientation(pointA, pointB) { + if (!pointA || !pointB) return; + + const radians = Phaser.Math.Angle.BetweenPoints(pointA, pointB); + const degrees = Phaser.Math.RadToDeg(radians); + + let direction; + if (degrees >= 0 && degrees < 90) direction = 'NORTH'; + else if (degrees >= 90 && degrees < 180) direction = 'EAST'; + else if (degrees >= 180 && degrees < 270) direction = 'SOUTH'; + else direction = 'WEST'; + + const shouldFlip = direction === 'EAST' || direction === 'SOUTH'; + this.setFlipX(shouldFlip); + } + /** * Combat methods */ @@ -296,8 +315,8 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite { select() { this.pulse?.stop(); - const team = this.getComponent('owner')?.team; - const isEnemy = team === 'enemy'; + const teamId = this.getData('teamId'); + const teamColor = this.scene.teamManager?.getTeamColor(teamId); this.pulse = this.scene.tweens.addCounter({ from: 175, @@ -308,10 +327,20 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite { yoyo: true, onUpdate: (tween) => { const value = Math.floor(tween.getValue()); - if (isEnemy) { - this.setTint(Phaser.Display.Color.GetColor32(value, 0, 0, 255)); + if (teamColor !== undefined && teamColor !== null) { + // Extract RGB channels from 24-bit hex color + const r = (teamColor >> 16) & 0xFF; + const g = (teamColor >> 8) & 0xFF; + const b = teamColor & 0xFF; + this.setTint(Phaser.Display.Color.GetColor32( + Math.round(r * (value / 255)), + Math.round(g * (value / 255)), + Math.round(b * (value / 255)), + 255 + )); } else { - this.setTint(Phaser.Display.Color.GetColor32(0, value, 0, 255)); + // Fallback: neutral = white pulse + this.setTint(Phaser.Display.Color.GetColor32(value, value, value, 255)); } } }); diff --git a/src/entities/base-units/state-configs/infantry-states.js b/src/entities/base-units/state-configs/infantry-states.js index 63042ad..6ff696d 100644 --- a/src/entities/base-units/state-configs/infantry-states.js +++ b/src/entities/base-units/state-configs/infantry-states.js @@ -47,9 +47,56 @@ export default { onEnter: (ctx) => { ctx._playAnimation("DYING"); ctx.dead = true; - setTimeout(() => { - ctx.destroy(); - }, 5000); + + // Stop movement and clear path + ctx.setData("path", []); + if (ctx.body) { + ctx.body.velocity.x = 0; + ctx.body.velocity.y = 0; + if (typeof ctx.disableBody === "function") { + ctx.disableBody(true, true); + } + } + + // Remove from parent container immediately + if (ctx.parentContainer && typeof ctx.parentContainer.remove === "function") { + ctx.parentContainer.remove(ctx); + } + + // Spawn smoke-puff death effect + const puff = ctx.scene.add.graphics(); + puff.fillStyle(0x888888, 0.6); + puff.fillCircle(0, 0, 10); + puff.setPosition(ctx.x, ctx.y); + puff.setDepth(ctx.depth !== undefined ? ctx.depth + 1 : 100); + if (!ctx.scene._deathEffects) ctx.scene._deathEffects = []; + ctx.scene._deathEffects.push(puff); + + // Tween puff scale and fade over 300ms + ctx.scene.tweens.add({ + targets: puff, + scale: 2, + alpha: 0, + duration: 300, + onComplete: () => { + puff.destroy(); + const idx = ctx.scene._deathEffects.indexOf(puff); + if (idx !== -1) ctx.scene._deathEffects.splice(idx, 1); + }, + }); + + // Tween unit alpha to 0 over 500ms, then emit event and destroy + ctx.scene.tweens.add({ + targets: ctx, + alpha: 0, + duration: 500, + onComplete: () => { + ctx.scene.events.emit("unit:killed", { entity: ctx }); + if (typeof ctx.destroy === "function") { + ctx.destroy(); + } + }, + }); }, onExit: (ctx) => {}, updateFunction: (ctx, time, delta) => {}, diff --git a/src/entities/base-units/state-configs/tank-states.js b/src/entities/base-units/state-configs/tank-states.js index 3e2c200..add5f9c 100644 --- a/src/entities/base-units/state-configs/tank-states.js +++ b/src/entities/base-units/state-configs/tank-states.js @@ -47,9 +47,56 @@ export default { onEnter: (ctx) => { ctx._playAnimation("DYING"); ctx.dead = true; - setTimeout(() => { - ctx.destroy(); - }, 5000); + + // Stop movement and clear path + ctx.setData("path", []); + if (ctx.body) { + ctx.body.velocity.x = 0; + ctx.body.velocity.y = 0; + if (typeof ctx.disableBody === "function") { + ctx.disableBody(true, true); + } + } + + // Remove from parent container immediately + if (ctx.parentContainer && typeof ctx.parentContainer.remove === "function") { + ctx.parentContainer.remove(ctx); + } + + // Spawn smoke-puff death effect + const puff = ctx.scene.add.graphics(); + puff.fillStyle(0x888888, 0.6); + puff.fillCircle(0, 0, 10); + puff.setPosition(ctx.x, ctx.y); + puff.setDepth(ctx.depth !== undefined ? ctx.depth + 1 : 100); + if (!ctx.scene._deathEffects) ctx.scene._deathEffects = []; + ctx.scene._deathEffects.push(puff); + + // Tween puff scale and fade over 300ms + ctx.scene.tweens.add({ + targets: puff, + scale: 2, + alpha: 0, + duration: 300, + onComplete: () => { + puff.destroy(); + const idx = ctx.scene._deathEffects.indexOf(puff); + if (idx !== -1) ctx.scene._deathEffects.splice(idx, 1); + }, + }); + + // Tween unit alpha to 0 over 500ms, then emit event and destroy + ctx.scene.tweens.add({ + targets: ctx, + alpha: 0, + duration: 500, + onComplete: () => { + ctx.scene.events.emit("unit:killed", { entity: ctx }); + if (typeof ctx.destroy === "function") { + ctx.destroy(); + } + }, + }); }, onExit: (ctx) => {}, updateFunction: (ctx, time, delta) => {}, diff --git a/src/entities/buildings/building-types.js b/src/entities/buildings/building-types.js index 14d98dc..bfe570c 100644 --- a/src/entities/buildings/building-types.js +++ b/src/entities/buildings/building-types.js @@ -16,12 +16,12 @@ const BUILDING_TYPES = { COMMAND_CENTER: { id: "COMMAND_CENTER", label: "Command Center", - buildCost: null, // cannot be built — only exists at game start + buildCost: null, // cannot be built -- only exists at game start buildTime: 0, productions: [], // nothing to queue - income: null, + income: { fuel: 1, ammo: 1 }, health: 1000, - description: "Headquarters. Losing this costs you the game.", + description: "Headquarters. Generates +1 Fuel / +1 Ammo per tick.", }, BARRACKS: { diff --git a/src/entities/components/OwnerComponent.js b/src/entities/components/OwnerComponent.js index 9c46d73..3934270 100644 --- a/src/entities/components/OwnerComponent.js +++ b/src/entities/components/OwnerComponent.js @@ -1,12 +1,13 @@ /** - * OwnerComponent — stores player ownership and team color for a Unit. + * OwnerComponent — stores player ownership and team info for a Unit. + * Delegates team queries to TeamManager for multi-team support. */ export default class OwnerComponent { /** * @param {import('../Unit').default} unit * @param {Object} [config] * @param {string} [config.playerId='neutral'] - * @param {string} [config.team='good'] - 'good' | 'enemy' + * @param {string} [config.teamId] - teamId string (replaces 'good'/'enemy') * @param {number} [config.color] - Tint color as 24-bit integer */ constructor(unit, config = {}) { @@ -15,8 +16,8 @@ export default class OwnerComponent { /** @type {string} */ this._playerId = config.playerId || 'neutral'; - /** @type {string} */ - this._team = config.team || 'good'; + /** @type {string|null} */ + this._teamId = config.teamId ?? null; /** @type {number|null} */ this._color = config.color ?? null; @@ -27,8 +28,8 @@ export default class OwnerComponent { /** @returns {string} */ get playerId() { return this._playerId; } - /** @returns {string} */ - get team() { return this._team; } + /** @returns {string|null} */ + get teamId() { return this._teamId; } /** @returns {number|null} */ get color() { return this._color; } @@ -36,8 +37,8 @@ export default class OwnerComponent { /** @param {string} id */ set playerId(id) { this._playerId = id; } - /** @param {string} team */ - set team(team) { this._team = team; } + /** @param {string|null} teamId */ + set teamId(teamId) { this._teamId = teamId; } /** @param {number|null} color */ set color(color) { this._color = color; } @@ -45,33 +46,35 @@ export default class OwnerComponent { // ── Queries ─────────────────────────────────────────────────── /** - * Is this unit an enemy relative to another team? - * @param {string} [otherTeam] + * Is this unit an enemy relative to another unit? + * Delegates to TeamManager. + * @param {OwnerComponent} other * @returns {boolean} */ - isEnemy(otherTeam) { - if (!otherTeam) return this._team !== 'good'; - return this._team !== otherTeam; + isEnemy(other) { + if (!other || !this.unit.scene?.teamManager) return false; + return this.unit.scene.teamManager.isEnemy(this.unit, other.unit); } /** - * Check if this unit belongs to the same team. + * Check if this unit belongs to the same team as another unit. + * Delegates to TeamManager. * @param {OwnerComponent} other * @returns {boolean} */ isSameTeam(other) { - if (!other) return false; - return this._team === other.team; + if (!other || !this.unit.scene?.teamManager) return false; + return this.unit.scene.teamManager.isSameTeam(this.unit, other.unit); } /** * Serialize for network sync. - * @returns {{ playerId: string, team: string, color: number|null }} + * @returns {{ playerId: string, teamId: string|null, color: number|null }} */ serialize() { return { playerId: this._playerId, - team: this._team, + teamId: this._teamId, color: this._color, }; } diff --git a/src/phaserClasses/interface.js b/src/phaserClasses/interface.js index f4ca606..87255e9 100644 --- a/src/phaserClasses/interface.js +++ b/src/phaserClasses/interface.js @@ -9,15 +9,17 @@ export default class Interface { return; } - init() { + init(useLegacyPointers = false) { this.createCamera(); this.createControls(); - this.pathfinder = new PathFinder( - this, - this.scene.map, - this.scene.groundLayer, - this.scene.rockLayer - ).init(); + if (useLegacyPointers) { + this.pathfinder = new PathFinder( + this, + this.scene.map, + this.scene.groundLayer, + this.scene.rockLayer + ).init(); + } return this; } diff --git a/src/scenes/Boot_Loader.js b/src/scenes/Boot_Loader.js index 4de46db..60936d7 100644 --- a/src/scenes/Boot_Loader.js +++ b/src/scenes/Boot_Loader.js @@ -28,7 +28,7 @@ export default class Boot_Loader extends Phaser.Scene { } loadCoreAssets() { - this.load.tilemapTiledJSON("test1", "tilemaps/testmap/test1.tmj"); + this.load.tilemapTiledJSON("test2", "tilemaps/testmap/test2.tmj"); this.load.image("floorsPrimary", "tilesets/floors32x32.png"); this.load.spritesheet( "infantry-russia", diff --git a/src/scenes/Map_Player.js b/src/scenes/Map_Player.js index 5505507..8724fe8 100644 --- a/src/scenes/Map_Player.js +++ b/src/scenes/Map_Player.js @@ -7,6 +7,15 @@ import Interface from "PhaserClasses/interface"; import { NetworkSystemClient } from "Systems/NetworkSystem.js"; import SystemOrchestrator from "Systems/SystemOrchestrator.js"; import UnitFactory from "Systems/UnitFactory"; +import TeamManager from "Systems/TeamManager.js"; +import HealthBarSystem from "Systems/HealthBarSystem"; +import ResourceBar from "Systems/ResourceBar"; +import CaptureProgressUI from "Systems/CaptureProgressUI"; +import BuildMenu from "Systems/BuildMenu"; +import BuildingPlacer from "Systems/BuildingPlacer"; +import BuildingRenderer from "Systems/BuildingRenderer"; +import ProductionPanel from "Systems/ProductionPanel"; +import BUILDING_TYPES, { getBuildingType } from "Entities/buildings/building-types"; export default class Map_Player extends Phaser.Scene { constructor() { @@ -98,14 +107,7 @@ export default class Map_Player extends Phaser.Scene { } createInfantry(targetTile) { - if (!this.goodGuys) { - this.goodGuys = this.add.container().setName("Good Guys"); - } - if (!this.badGuys) { - this.badGuys = this.add.container().setName("Bad Guys"); - } this.infantry = new Ukrainian_Rifle(this, targetTile); - this.goodGuys.add(this.infantry); this.infantry.name = "goodGuy"; this.infantry.setScale(1.5); this.infantry.setData("godMode", true); @@ -115,12 +117,14 @@ export default class Map_Player extends Phaser.Scene { return Math.floor(Math.random() * max); } createFriendlyPlatoon() { + if (!this.unitFactory) return; let circle = new Phaser.Geom.Circle(1020, 457, 150); let tiles = this.groundLayer.getTilesWithinShape(circle); let rangeMax = tiles.length - 1; for (var i = 0; i < 5; i++) { - this.goodGuys.add( - this.createFriendlyInfantry(tiles[this.getRandomInt(rangeMax)]) + this.unitFactory.spawnInfantry( + tiles[this.getRandomInt(rangeMax)], + 'team-A', ); } } @@ -129,29 +133,72 @@ export default class Map_Player extends Phaser.Scene { } spawnTestUnit() { - // Pick a tile near the camera center + // Spawn near camera center so unit is visible in viewport const cam = this.cameras.main; - const centerX = cam.scrollX + cam.width / 2; - const centerY = cam.scrollY + cam.height / 2; - const tile = this.groundLayer.getTileAtWorldXY(centerX, centerY); + const tile = this.groundLayer.getTileAtWorldXY(cam.scrollX + cam.width / 2, cam.scrollY + cam.height / 2); if (!tile) return; const unit = new Ukrainian_Rifle(this, tile); this.physics.add.existing(unit); unit.setScale(1.5); unit.setName('test-unit'); + if (this.teamManager) { + this.teamManager.addUnit(unit, 'team-A'); + } } create() { this.createMap(); - // ── Unit containers ───────────────────────────────────────────────── - this.goodGuys = this.add.container().setName("Good Guys"); - this.badGuys = this.add.container().setName("Bad Guys"); - this.unitFactory = new UnitFactory(this); + // ── Subsystems (created early so later systems can reference them) ─── + this.healthBars = new HealthBarSystem(this); + this.resourceBar = new ResourceBar(this, { playerId: 'Player' }); + this.captureUI = new CaptureProgressUI(this); this.interface = new Interface(this).init(false); + // ── Build Menu + Building Placer ──────────────────────────────────── + this.buildMenu = new BuildMenu(this, { + onSelect: (type) => this.buildingPlacer?.startPlacement(type), + playerId: 'Player', + }); + this.buildingPlacer = new BuildingPlacer(this, { playerId: 'Player' }); + + // ── Building Renderer ──────────────────────────────────────────────── + this.buildingRenderer = new BuildingRenderer(this); + + // ── Production Panel ─────────────────────────────────────────────── + this.productionPanel = new ProductionPanel(this, { + playerId: 'Player', + getEconomy: () => this.orchestrator?.systems?.economy, + onProductionComplete: (bsm, unitType) => { + const teamId = this.teamManager?.getPlayerTeam(bsm.playerId) ?? 'team-A'; + const spawnTile = this.groundLayer?.getTileAtWorldXY(bsm.building.x, bsm.building.y) || { x: 64, y: 64 }; + if (unitType === 'infantry') { + this.unitFactory?.spawnInfantry(spawnTile, teamId); + } else if (unitType === 'tank') { + this.unitFactory?.spawnTank(spawnTile, teamId); + } + }, + }); + + // Wire building selection → production panel (only for own buildings) + this.events.on('building:selected', ({ bsm }) => { + if (bsm?.playerId === 'Player') { + this.productionPanel?.show(bsm); + } else { + this.productionPanel?.hide(); + } + }); + + // ── TeamManager (multi-team replacement for goodGuys/badGuys) ────────── + // MUST be created BEFORE SystemOrchestrator so CombatSystem receives it + this.teamManager = new TeamManager(this); + this.teamManager.createTeam('team-A', 0x1d7196, 'Alpha'); + this.teamManager.createTeam('team-B', 0xd94f4f, 'Bravo'); + this.teamManager.createTeam('team-C', 0x4fd94f, 'Charlie'); + this.teamManager.setPlayerTeam('Player', 'team-A'); + // ── System Orchestrator: initialize all 9+ systems ──────────────────── const colyseus = this.game?.colyseus; this.orchestrator = new SystemOrchestrator(this, { @@ -162,34 +209,116 @@ export default class Map_Player extends Phaser.Scene { debug: false, }); this.orchestrator.init(); + this.orchestrator.systems.economy.initPlayer('Player'); // Initialize pathfinding after MapSystem has the tilemap this.orchestrator.initPathfinding(); + // Initialize ControlPointManager after tilemap is available + this.orchestrator.initControlPoints(); + + // UnitFactory now routes units through TeamManager + this.unitFactory = new UnitFactory(this, this.teamManager); + + // ── Starting buildings for income testing ──────────────────────────── + this._spawnStartingBuildings(); + // Wire up Colyseus networking if the client was created by Server_Connector this._initNetworking(); - // Wire the orchestrator's NetworkSystem to the scene for update() access + // Wire resource bar to EconomySystem after orchestrator.init() + if (this.orchestrator?.systems?.economy) { + this.resourceBar.setEconomySystem( + this.orchestrator.systems.economy, + 'Player', + ); + // Also wire build menu affordability refresh + if (this.orchestrator.systems.economy.events?.on) { + this.orchestrator.systems.economy.events.on('economy:updated', (payload) => { + if (payload.playerId === 'Player') { + this.buildMenu?.updateAffordability( + this.orchestrator.systems.economy, + 'Player', + ); + } + }); + } + } + this._syncNetworkSystem(); // ── Auto-spawn player units (skip when connected to Colyseus) ───────── + // Use tile coordinates (64,64 = center of 128×128 map) for reliable spawn + const centerWorld = this.map.tileToWorldXY(64, 64); + const testTile = this.groundLayer.getTileAtWorldXY(centerWorld.x, centerWorld.y) || + { x: 64, y: 64 }; if (!this.game?.colyseus?.room) { - this.createInfantry(); + this.createInfantry(testTile); } // ── Spawn 1-2 test units using UnitFactory for immediate testing ───── - const testTile = this.groundLayer.getTileAtWorldXY(1020, 457) || { x: 31, y: 14 }; - this.unitFactory.spawnInfantry(testTile, 'player'); - this.unitFactory.spawnInfantry({ x: testTile.x + 2, y: testTile.y }, 'player'); + this.unitFactory.spawnInfantry(testTile, 'team-A'); + this.unitFactory.spawnInfantry({ x: testTile.x + 2, y: testTile.y }, 'team-A'); // ── F-key test spawn ───────────────────────────────────────────────── this.input.keyboard.on('keydown-F', () => { this.spawnTestUnit(); }); + // ── Center camera on the player force so units are in viewport ────── + const allUnits = this.teamManager.getAllUnits(); + if (allUnits.length > 0 && this.cameras.main) { + this.cameras.main.centerOn(allUnits[0].x, allUnits[0].y); + } + // ── Clean up on shutdown ───────────────────────────────────────────── this.events.on('shutdown', () => { this.orchestrator?.shutdown(); + this.teamManager?.destroy(); + this.healthBars?.shutdown(); + this.resourceBar?.destroy(); + this.captureUI?.shutdown(); + this.buildMenu?.destroy(); + this.buildingPlacer?.destroy(); + this.buildingRenderer?.destroy(); + this.productionPanel?.destroy(); + // Destroy any lingering death-effect graphics + if (this._deathEffects) { + for (const effect of this._deathEffects) { + if (effect && effect.active) effect.destroy(); + } + this._deathEffects = []; + } + }); + + // ── Kill tracking ─────────────────────────────────────────────────── + this._killCount = 0; + this.events.on('unit:killed', ({ entity }) => { + this._killCount += 1; + if (this.config?.debug) { + console.debug('[Map_Player] unit:killed', entity?.name, 'total:', this._killCount); + } + }); + + // ── Health bar flash on damage ──────────────────────────────────────── + this.events.on('unit:damaged', ({ unit }) => { + this.healthBars?.flash(unit, 0xff0000); + }); + + // ── Health bar destroy on death ────────────────────────────────────── + this.events.on('unit:dying', ({ unit }) => { + this.healthBars?.destroy(unit); + }); + + // ── Victory condition → launch VictoryScene overlay ───────────────── + this.events.on('game:victory', ({ winnerPlayerId, stats }) => { + const localPlayerId = this.game?.colyseus?.room?.sessionId ?? 'player'; + console.log('[Map_Player] game:victory — winner:', winnerPlayerId, 'local:', localPlayerId); + this.scene.launch('VictoryScene', { + winnerPlayerId, + localPlayerId, + stats, + }); }); } @@ -264,5 +393,42 @@ export default class Map_Player extends Phaser.Scene { if (this.orchestrator) { this.orchestrator.update(time, delta); } + + // BuildingRenderer state-dependent visuals (alpha pulsing, etc.) + this.buildingRenderer?.update(time, delta); + + // ProductionPanel progress bar + queue refresh + this.productionPanel?.update(time); + } + + /** + * Spawn starting buildings: 1 Command Center (passive income) + + * 1 Logistics + 1 Ammo Factory for immediate income verification. + * All buildings start ACTIVE so income ticks immediately. + */ + _spawnStartingBuildings() { + if (!this.orchestrator) return; + + const types = ['COMMAND_CENTER', 'LOGISTICS', 'AMMO_FACTORY']; + const spawnTiles = [ + { x: 62, y: 62 }, + { x: 63, y: 62 }, + { x: 62, y: 63 }, + ]; + + for (let i = 0; i < types.length; i++) { + const typeId = types[i]; + const tile = spawnTiles[i]; + const worldPos = this.map?.tileToWorldXY(tile.x, tile.y); + if (!worldPos) continue; + + const config = getBuildingType(typeId); + this.buildingRenderer.render(worldPos, typeId, { + buildTime: 0, + startActive: true, + playerId: 'Player', + income: config?.income || null, + }); + } } } diff --git a/src/scenes/VictoryScene.js b/src/scenes/VictoryScene.js new file mode 100644 index 0000000..a3a072d --- /dev/null +++ b/src/scenes/VictoryScene.js @@ -0,0 +1,121 @@ +import Phaser from 'phaser'; + +/** + * VictoryScene — full-screen overlay that displays match results. + * + * Usage (from Map_Player or orchestrator): + * this.scene.launch('VictoryScene', { + * winnerPlayerId: 'player1', + * localPlayerId: 'player1', + * stats: { + * unitsKilled: 5, + * buildingsBuilt: 2, + * cpCaptured: 100, + * elapsedMs: 120000, + * }, + * }); + */ +export default class VictoryScene extends Phaser.Scene { + constructor() { + super({ key: 'VictoryScene' }); + } + + create(data) { + const { winnerPlayerId, localPlayerId, stats } = data || {}; + const w = this.cameras.main.width; + const h = this.cameras.main.height; + const cx = this.cameras.main.centerX; + const isVictory = winnerPlayerId === localPlayerId; + + // ── Dark semi-transparent overlay ──────────────────────────────────── + const bg = this.add.rectangle(cx, h / 2, w, h, 0x000000); + bg.setAlpha(0.75); + bg.setDepth(1000); + + // ── Title ──────────────────────────────────────────────────────────── + const titleText = isVictory ? 'VICTORY' : 'DEFEAT'; + const titleColor = isVictory ? '#00ff66' : '#ff4444'; + const title = this.add.text(cx, 180, titleText, { + fontSize: '72px', + fontFamily: 'monospace', + color: titleColor, + fontStyle: 'bold', + }); + title.setOrigin(0.5); + title.setDepth(1001); + + // ── Subtitle ─────────────────────────────────────────────────────── + const subtitle = isVictory + ? `Winner: ${winnerPlayerId}` + : `Winner: ${winnerPlayerId} | You were defeated`; + const sub = this.add.text(cx, 260, subtitle, { + fontSize: '28px', + fontFamily: 'monospace', + color: '#ffffff', + }); + sub.setOrigin(0.5); + sub.setDepth(1001); + + // ── Stats ────────────────────────────────────────────────────────── + const elapsedSec = Math.floor((stats?.elapsedMs ?? 0) / 1000); + const mm = String(Math.floor(elapsedSec / 60)).padStart(2, '0'); + const ss = String(elapsedSec % 60).padStart(2, '0'); + const lines = [ + `Time Elapsed: ${mm}:${ss}`, + `Units Killed: ${stats?.unitsKilled ?? 0}`, + `Buildings Built: ${stats?.buildingsBuilt ?? 0}`, + `CP Captured: ${stats?.cpCaptured ?? 0}`, + ]; + + const statsY = 360; + lines.forEach((line, i) => { + const row = this.add.text(cx, statsY + i * 50, line, { + fontSize: '24px', + fontFamily: 'monospace', + color: '#dddddd', + }); + row.setOrigin(0.5); + row.setDepth(1001); + }); + + // ── Play Again button ────────────────────────────────────────────── + const btnY = statsY + lines.length * 50 + 40; + const btnBg = this.add.rectangle(cx, btnY + 15, 260, 50, 0x3366ff); + btnBg.setAlpha(0.9); + btnBg.setDepth(1001); + btnBg.setInteractive({ useHandCursor: true }); + + const btnText = this.add.text(cx, btnY, 'Play Again', { + fontSize: '28px', + fontFamily: 'monospace', + color: '#ffffff', + }); + btnText.setOrigin(0.5); + btnText.setDepth(1002); + btnText.setInteractive({ useHandCursor: true }); + + // Hover effects + const onHover = () => { + btnBg.setFillStyle(0x5599ff); + }; + const onOut = () => { + btnBg.setFillStyle(0x3366ff); + }; + btnBg.on('pointerover', onHover); + btnBg.on('pointerout', onOut); + btnText.on('pointerover', onHover); + btnText.on('pointerout', onOut); + + // Click → return to lobby + const onClick = () => { + this.scene.stop('Map_Player'); + this.scene.start('Server_Connector'); + }; + btnBg.on('pointerdown', onClick); + btnText.on('pointerdown', onClick); + + // Keep references for tests + this._playAgainButton = btnText; + this._onPlayAgain = onClick; + } +} diff --git a/src/systems/BuildMenu.js b/src/systems/BuildMenu.js new file mode 100644 index 0000000..6049be9 --- /dev/null +++ b/src/systems/BuildMenu.js @@ -0,0 +1,134 @@ +/** + * BuildMenu — bottom HUD row of clickable building icons. + * + * Responsibilities: + * - Render 4 buttons (BARRACKS, VEHICLE_DEPOT, LOGISTICS, AMMO_FACTORY) + * - Show label + cost text per button + * - Emit building type on click + * - Grey out / dim buttons player can't afford + */ + +import { getBuildingType } from 'Entities/buildings/building-types'; + +const BUTTON_WIDTH = 120; +const BUTTON_HEIGHT = 64; +const BUTTON_GAP = 8; +const BUTTON_COLORS = { + BARRACKS: 0xcc4444, + VEHICLE_DEPOT: 0x4466cc, + LOGISTICS: 0x44cc66, + AMMO_FACTORY: 0xccaa44, +}; + +export default class BuildMenu { + /** + * @param {Phaser.Scene} scene + * @param {Object} [options] + * @param {Function} [options.onSelect] — callback(buildingTypeId) + * @param {string} [options.playerId='Player'] + */ + constructor(scene, options = {}) { + this.scene = scene; + this.onSelect = options.onSelect ?? (() => {}); + this.playerId = options.playerId ?? 'Player'; + this.buttons = []; + + this._buildContainer(); + this._buildButtons(); + } + + // -- Public API ------------------------------------------------------- + + /** + * Refresh affordability state for all buttons. + * @param {EconomySystem} economySystem + * @param {string} [playerId] + */ + updateAffordability(economySystem, playerId) { + const pid = playerId || this.playerId; + for (const btn of this.buttons) { + const type = getBuildingType(btn.type); + const affordable = type?.buildCost + ? economySystem?.canAfford?.(pid, type.buildCost) ?? true + : true; + btn.bg.setAlpha(affordable ? 1 : 0.4); + } + } + + /** + * Clean up all Phaser objects. + */ + destroy() { + if (this.container && this.container.active) { + this.container.destroy(); + } + this.container = null; + this.buttons = []; + } + + // -- Internal --------------------------------------------------------- + + _buildContainer() { + const cam = this.scene.cameras.main; + const cx = cam.width / 2; + const cy = cam.height - BUTTON_HEIGHT - 8; + + this.container = this.scene.add.container(cx, cy); + this.container.setScrollFactor(0, 0); + this.container.setDepth(110); + } + + _buildButtons() { + const types = ['BARRACKS', 'VEHICLE_DEPOT', 'LOGISTICS', 'AMMO_FACTORY']; + const totalWidth = types.length * BUTTON_WIDTH + (types.length - 1) * BUTTON_GAP; + let startX = -(totalWidth / 2) + BUTTON_WIDTH / 2; + + for (const typeId of types) { + const type = getBuildingType(typeId); + if (!type) continue; + + const btn = this._createButton(startX, 0, typeId, type); + this.buttons.push(btn); + this.container.add(btn.bg); + this.container.add(btn.label); + this.container.add(btn.costText); + startX += BUTTON_WIDTH + BUTTON_GAP; + } + } + + _createButton(x, y, typeId, type) { + const bg = this.scene.add.rectangle(x, y, BUTTON_WIDTH, BUTTON_HEIGHT, BUTTON_COLORS[typeId]); + bg.setOrigin(0.5, 0.5); + bg.setStrokeStyle(2, 0xffffff); + bg.setInteractive(); + + const label = this.scene.add.text(x, y - 10, type.label || typeId, { + fontFamily: 'monospace', + fontSize: '12px', + color: '#ffffff', + }); + label.setOrigin(0.5, 0.5); + + const cost = this._formatCost(type.buildCost); + const costText = this.scene.add.text(x, y + 12, cost, { + fontFamily: 'monospace', + fontSize: '10px', + color: '#eeeeee', + }); + costText.setOrigin(0.5, 0.5); + + bg.on('pointerdown', () => { + this.onSelect(typeId); + }); + + return { type: typeId, bg, label, costText }; + } + + _formatCost(buildCost) { + if (!buildCost) return ''; + const parts = []; + if (buildCost.fuel != null) parts.push(`Fuel: ${buildCost.fuel}`); + if (buildCost.ammo != null) parts.push(`Ammo: ${buildCost.ammo}`); + return parts.join(' | '); + } +} diff --git a/src/systems/BuildingPlacer.js b/src/systems/BuildingPlacer.js new file mode 100644 index 0000000..1be9810 --- /dev/null +++ b/src/systems/BuildingPlacer.js @@ -0,0 +1,229 @@ +/** + * BuildingPlacer — ghost preview, grid-snap placement, validation, cost deduction. + * + * Responsibilities: + * - Show a ghost sprite following the cursor with tile-grid snapping + * - Tint green when placement is valid, red when invalid + * - Validate: no collision tiles, no water tiles, no overlapping buildings + * - On valid click: deduct resources, register building, emit event + */ + +import { getBuildingType } from 'Entities/buildings/building-types'; + +const GHOST_ALPHA = 0.6; +const VALID_TINT = 0x00ff00; +const INVALID_TINT = 0xff0000; +const BUILDING_SIZE = 32; + +export default class BuildingPlacer { + /** + * @param {Phaser.Scene} scene + * @param {Object} [options] + * @param {string} [options.playerId='Player'] + */ + constructor(scene, options = {}) { + this.scene = scene; + this.playerId = options.playerId ?? 'Player'; + + // Ghost sprite (hidden by default) + this.ghost = scene.add.sprite(0, 0, '__WHITE'); + this.ghost.setOrigin(0.5, 0.5); + this.ghost.setAlpha(0); + this.ghost.setVisible(false); + this.ghost.setScrollFactor(1, 1); + this.ghost.setDepth(100); + this.ghost.setDisplaySize(BUILDING_SIZE, BUILDING_SIZE); + + // State + this._placing = false; + this._buildingType = null; + + // Input handlers + this._onPointerMove = this._handlePointerMove.bind(this); + this._onPointerDown = this._handlePointerDown.bind(this); + + scene.input.on('pointermove', this._onPointerMove); + scene.input.on('pointerdown', this._onPointerDown); + } + + // -- Public API ------------------------------------------------------- + + /** + * Enter placement mode for a specific building type. + * @param {string} buildingTypeId — e.g. 'BARRACKS' + */ + startPlacement(buildingTypeId) { + const type = getBuildingType(buildingTypeId); + if (!type || type.buildCost == null) return; + + this._buildingType = buildingTypeId; + this._placing = true; + this.ghost.setVisible(true); + this.ghost.setAlpha(GHOST_ALPHA); + } + + /** + * Cancel placement mode. + */ + cancel() { + this._placing = false; + this._buildingType = null; + if (this.ghost) { + this.ghost.setVisible(false); + this.ghost.setAlpha(0); + } + } + + /** + * Validate whether a tile coordinate is a valid placement spot. + * @param {number} tileX + * @param {number} tileY + * @returns {boolean} + */ + isValidPlacement(tileX, tileY) { + // No water tiles + const groundTile = this.scene.groundLayer?.getTileAt(tileX, tileY); + if (groundTile && groundTile.properties?.water) return false; + + // No collision / rock tiles + const rockTile = this.scene.rockLayer?.getTileAt(tileX, tileY); + if (rockTile && rockTile.properties?.collides) return false; + + // No overlapping buildings + const existing = this._getBuildingAtTile(tileX, tileY); + if (existing) return false; + + return true; + } + + /** + * Clean up the ghost and event listeners. + */ + destroy() { + this.cancel(); + if (this.ghost && this.ghost.active) { + this.ghost.destroy(); + } + this.ghost = null; + this.scene.input.off('pointermove', this._onPointerMove); + this.scene.input.off('pointerdown', this._onPointerDown); + } + + // -- Internal --------------------------------------------------------- + + _handlePointerMove(pointer) { + if (!this._placing || !this._buildingType) return; + this._updateGhost(pointer); + } + + _handlePointerDown(pointer) { + if (!this._placing || !this._buildingType) return; + this._tryPlace(pointer); + } + + _updateGhost(pointer) { + const tile = this._pointerToTile(pointer); + if (!tile) return; + + const worldPos = this.scene.map?.tileToWorldXY(tile.x, tile.y); + if (!worldPos) return; + + this.ghost.setPosition(worldPos.x + 16, worldPos.y + 16); + + const valid = this.isValidPlacement(tile.x, tile.y); + this.ghost.setTint(valid ? VALID_TINT : INVALID_TINT); + } + + _tryPlace(pointer) { + const tile = this._pointerToTile(pointer); + if (!tile) return false; + + // Validate + if (!this.isValidPlacement(tile.x, tile.y)) return false; + + // Check cost + const type = getBuildingType(this._buildingType); + if (!type || !type.buildCost) return false; + + const economy = this.scene.orchestrator?.systems?.economy; + if (!economy) return false; + + if (!economy.canAfford(this.playerId, type.buildCost)) return false; + + // Deduct + const deducted = economy.deduct(this.playerId, type.buildCost); + if (!deducted) return false; + + // Create building game object + const worldPos = this.scene.map?.tileToWorldXY(tile.x, tile.y); + if (!worldPos) return false; + + let building; + // Create building via BuildingRenderer if available, else fallback + if (this.scene.buildingRenderer) { + const entry = this.scene.buildingRenderer.render(worldPos, this._buildingType, { + buildTime: type.buildTime || 5000, + playerId: this.playerId, + }); + building = entry.graphics; + } else { + building = this.scene.add.rectangle( + worldPos.x + 16, + worldPos.y + 16, + BUILDING_SIZE, + BUILDING_SIZE, + 0x888888, + ); + building.setOrigin(0.5, 0.5); + building.setDepth(5); + + if (!this.scene.buildings) { + this.scene.buildings = this.scene.add.container().setName('Buildings'); + } + this.scene.buildings.add(building); + + this.scene.orchestrator?.registerBuilding(building, { + type: this._buildingType, + buildTime: type.buildTime || 5000, + }); + } + + // Emit event + this.scene.events.emit('building:placed', { + type: this._buildingType, + tileX: tile.x, + tileY: tile.y, + x: building.x, + y: building.y, + playerId: this.playerId, + }); + + // Exit placement mode + this.cancel(); + return true; + } + + _pointerToTile(pointer) { + if (!this.scene.map) return null; + const tile = this.scene.map.worldToTileXY(pointer.worldX, pointer.worldY); + if (!tile) return null; + return { x: tile.x, y: tile.y }; + } + + _getBuildingAtTile(tileX, tileY) { + const container = this.scene.buildings; + if (!container || !container.list) return null; + + const world = this.scene.map?.tileToWorldXY(tileX, tileY); + if (!world) return null; + + for (const b of container.list) { + const dx = Math.abs(b.x - (world.x + 16)); + const dy = Math.abs(b.y - (world.y + 16)); + if (dx < BUILDING_SIZE / 2 + 1 && dy < BUILDING_SIZE / 2 + 1) { + return b; + } + } + return null; + } +} diff --git a/src/systems/BuildingRenderer.js b/src/systems/BuildingRenderer.js new file mode 100644 index 0000000..7adf244 --- /dev/null +++ b/src/systems/BuildingRenderer.js @@ -0,0 +1,227 @@ +/** + * BuildingRenderer — renders buildings as colored rectangles with state-dependent visuals. + * + * Responsibilities: + * - Create colored rectangles per building type + * - State-dependent alpha: CONSTRUCTING=0.4, ACTIVE=1.0, PRODUCING=pulse + * - Depth sorting: buildings behind units (depth 5) + * - Click to select → highlight + emit event for ProductionPanel (S5.3) + * - Register/unregister via orchestrator + * + * Colors per type: + * - BARRACKS = #4a90d9 + * - VEHICLE_DEPOT = #8b4513 + * - LOGISTICS = #d4a017 + * - AMMO_FACTORY = #d94a4a + * - COMMAND_CENTER = #ffd700 + */ + +const BUILDING_COLORS = { + BARRACKS: 0x4a90d9, + VEHICLE_DEPOT: 0x8b4513, + LOGISTICS: 0xd4a017, + AMMO_FACTORY: 0xd94a4a, + COMMAND_CENTER: 0xffd700, +}; + +const BUILDING_SIZE = 32; +const BUILDING_DEPTH = 5; + +/** State-dependent alpha values */ +const STATE_ALPHA = { + CONSTRUCTING: 0.4, + ACTIVE: 1.0, + PRODUCING: null, // dynamically pulsed + DESTROYED: 0, +}; + +export default class BuildingRenderer { + /** + * @param {Phaser.Scene} scene + */ + constructor(scene) { + this.scene = scene; + /** @type {Array<{graphics: Phaser.GameObjects.Rectangle, bsm: BuildingStateMachine}>} */ + this.buildings = []; + /** @type {Phaser.GameObjects.Rectangle|null} Selection highlight graphic */ + this.selectionHighlight = null; + /** @type {{graphics: Phaser.GameObjects.Rectangle, bsm: BuildingStateMachine}|null} */ + this.selectedBuilding = null; + + this.container = scene.add.container().setName('Buildings'); + this.container.setDepth(BUILDING_DEPTH); + } + + // -- Public API ------------------------------------------------------- + + /** + * Render a building at a world position. + * @param {{x:number, y:number}} worldPos + * @param {string} typeId — e.g. 'BARRACKS' + * @param {Object} config — forwarded to BuildingStateMachine + * @returns {{graphics: Phaser.GameObjects.Rectangle, bsm: BuildingStateMachine}} + */ + render(worldPos, typeId, config = {}) { + const color = BUILDING_COLORS[typeId] || 0x888888; + + const rect = this.scene.add.rectangle( + worldPos.x + 16, + worldPos.y + 16, + BUILDING_SIZE, + BUILDING_SIZE, + color, + ); + rect.setOrigin(0.5, 0.5); + rect.setDepth(BUILDING_DEPTH); + rect.setInteractive(); + + // Click to select + rect.on('pointerdown', () => { + this.select(rect); + }); + + this.container.add(rect); + + // Register with orchestrator + const bsm = this.scene.orchestrator?.registerBuilding(rect, { + type: typeId, + ...config, + }); + + const entry = { graphics: rect, bsm }; + this.buildings.push(entry); + + return entry; + } + + /** + * Select a building and show highlight. + * @param {Phaser.GameObjects.Rectangle} rect + */ + select(rect) { + this.deselect(); + + const entry = this.buildings.find((b) => b.graphics === rect); + if (!entry) return; + + this.selectedBuilding = entry; + + if (!this.selectionHighlight) { + this.selectionHighlight = this.scene.add.rectangle( + rect.x, + rect.y, + BUILDING_SIZE + 4, + BUILDING_SIZE + 4, + 0xffffff, + ); + this.selectionHighlight.setOrigin(0.5, 0.5); + this.selectionHighlight.setDepth(BUILDING_DEPTH - 1); + this.selectionHighlight.setStrokeStyle(2, 0x00ff00); + this.selectionHighlight.setFillStyle(0xffffff, 0); + } + + this.selectionHighlight.setPosition(rect.x, rect.y); + this.selectionHighlight.setVisible(true); + + // Emit event for ProductionPanel (S5.3) + this.scene.events.emit('building:selected', { + building: rect, + bsm: entry.bsm, + type: entry.bsm?.type, + }); + } + + /** + * Deselect the currently selected building. + */ + deselect() { + if (this.selectionHighlight) { + this.selectionHighlight.setVisible(false); + } + this.selectedBuilding = null; + } + + /** + * Per-frame update — apply state-dependent visuals. + * @param {number} time + * @param {number} delta + */ + update(time, delta) { + for (const entry of this.buildings) { + const state = entry.bsm?.getState?.() || 'ACTIVE'; + const rect = entry.graphics; + + if (!rect.active) continue; + + switch (state) { + case 'CONSTRUCTING': + rect.setAlpha(STATE_ALPHA.CONSTRUCTING); + break; + case 'ACTIVE': + rect.setAlpha(STATE_ALPHA.ACTIVE); + break; + case 'PRODUCING': { + const pulse = 0.85 + 0.15 * Math.sin(time / 200); + rect.setAlpha(pulse); + break; + } + case 'DESTROYED': + rect.setAlpha(STATE_ALPHA.DESTROYED); + break; + default: + rect.setAlpha(1.0); + } + } + } + + /** + * Destroy a specific building and unregister it. + * @param {Phaser.GameObjects.Rectangle} rect + */ + destroyBuilding(rect) { + const idx = this.buildings.findIndex((b) => b.graphics === rect); + if (idx === -1) return; + + const entry = this.buildings[idx]; + this.buildings.splice(idx, 1); + + if (this.selectedBuilding?.graphics === rect) { + this.deselect(); + } + + if (entry.bsm) { + this.scene.orchestrator?.unregisterBuilding(entry.bsm); + entry.bsm.destroy?.(); + } + + if (rect && rect.active) { + rect.off('pointerdown'); + rect.destroy(); + } + } + + /** + * Clean up all buildings, highlights, and container. + */ + destroy() { + for (const entry of this.buildings) { + if (entry.bsm) { + entry.bsm.destroy?.(); + } + if (entry.graphics && entry.graphics.active) { + entry.graphics.destroy(); + } + } + this.buildings = []; + + if (this.selectionHighlight && this.selectionHighlight.active) { + this.selectionHighlight.destroy(); + } + this.selectionHighlight = null; + this.selectedBuilding = null; + + if (this.container && this.container.active) { + this.container.destroy(); + } + } +} diff --git a/src/systems/BuildingStateMachine.js b/src/systems/BuildingStateMachine.js index dce03b7..c444cc4 100644 --- a/src/systems/BuildingStateMachine.js +++ b/src/systems/BuildingStateMachine.js @@ -24,7 +24,16 @@ export default class BuildingStateMachine { this.service = null; /** @type {string} Current state value */ - this._currentState = 'CONSTRUCTING'; + this._currentState = config.startActive ? 'ACTIVE' : 'CONSTRUCTING'; + + /** @type {string|null} Owning player id */ + this.playerId = config.playerId ?? null; + + /** @type {{fuel?: number, ammo?: number}|null} Passive income per tick */ + this.income = config.income ?? null; + + /** @type {number|null} Timestamp of last income application (rate-limited) */ + this._lastIncomeTick = null; /** @type {Array<{unitType: string, startTime: number}>} */ this.productionQueue = []; @@ -71,22 +80,32 @@ export default class BuildingStateMachine { } /** - * Per-frame tick — advance timers, process production queue. + * Per-frame tick -- advance timers, process production queue, apply income. * @param {number} time * @param {number} delta */ tick(time, delta) { - // Advance production queue timers + // ── Production queue ─────────────────────────────────────── if (this._currentState === 'PRODUCING' && this.productionQueue.length > 0) { const item = this.productionQueue[0]; if (item.startTime === 0) { item.startTime = time; } if (time - item.startTime >= this.productionTime) { - // Production complete — caller should listen for events + // Production complete -- caller should listen for events this.productionQueue.shift(); } } + + // ── Passive income (rate-limited to once per 1000ms) ─────── + if (this._currentState === 'ACTIVE' && this.income) { + if (this._lastIncomeTick == null || time - this._lastIncomeTick >= 1000) { + this._lastIncomeTick = time; + return this.income; + } + } + + return null; } /** diff --git a/src/systems/CaptureProgressUI.js b/src/systems/CaptureProgressUI.js new file mode 100644 index 0000000..9343cde --- /dev/null +++ b/src/systems/CaptureProgressUI.js @@ -0,0 +1,129 @@ +/** + * CaptureProgressUI -- world-space progress bar overlay for control points. + * + * Pattern: same as HealthBarSystem (Graphics objects, per-CP tracking via Map). + * + * Features: + * - update(cp) -- lazily creates or refreshes a bar above the CP zone + * - drawBar(cp) -- Graphics with horizontal progress bar + circular ring + * - getColor(state, owner) -- colour by state + owner + * - destroy(cp) -- remove one CP's bar + * - shutdown() -- remove all + */ + +export default class CaptureProgressUI { + /** + * @param {Phaser.Scene} scene + * @param {Object} [options] + * @param {number} [options.offsetY=40] -- pixels above the CP centre + * @param {number} [options.barHeight=6] + * @param {number} [options.barWidth=80] + */ + constructor(scene, options = {}) { + this.scene = scene; + this._bars = new Map(); // CP -> Graphics + this._offsetY = options.offsetY ?? 40; + this._barHeight = options.barHeight ?? 6; + this._barWidth = options.barWidth ?? 80; + } + + // -- Color logic ------------------------------------------------------ + + /** + * Map CP state (and optional owner) to an RGB colour. + * @param {string} state -- 'NEUTRAL' | 'CONTESTED' | 'CAPTURED' + * @param {string|null} [owner] + * @returns {number} 0xRRGGBB + */ + getColor(state, owner = null) { + switch (state) { + case 'NEUTRAL': return 0xaaaaaa; // grey + case 'CONTESTED': return 0xffcc00; // yellow + case 'CAPTURED': + // green for player, red for anyone else + return owner === 'player' ? 0x00ff00 : 0xff3333; + default: + return 0xaaaaaa; + } + } + + // -- Bar lifecycle ---------------------------------------------------- + + /** + * Create (or recreate) a capture-progress bar for a control point. + * @param {Object} cp -- control point with x, y, radiusPx, active + */ + drawBar(cp) { + if (!cp || !cp.active) return null; + + const existing = this._bars.get(cp); + if (existing && existing.active) { + existing.destroy(); + } + + const bar = this.scene.add.graphics(); + bar.setDepth(90); // slightly below health bars (100) + this._bars.set(cp, bar); + + this.update(cp); + return bar; + } + + /** + * Refresh a CP's bar each frame. + * @param {Object} cp + */ + update(cp) { + if (!cp || !cp.active) return; + + let bar = this._bars.get(cp); + if (!bar || !bar.active) { + bar = this.drawBar(cp); + if (!bar) return; + } + + const progress = cp.getCaptureProgress ? cp.getCaptureProgress() : 0; + const state = cp.getState ? cp.getState() : 'NEUTRAL'; + const owner = cp.getOwner ? cp.getOwner() : null; + const color = this.getColor(state, owner); + + const w = this._barWidth; + const h = this._barHeight; + const x = cp.x - w / 2; + const y = cp.y - cp.radiusPx - this._offsetY; + const fraction = Math.max(0, Math.min(1, progress / 100)); + + bar.clear(); + + // Background track (dark border) + bar.fillStyle(0x000000, 0.7); + bar.fillRect(x - 1, y - 1, w + 2, h + 2); + + // Foreground fill + bar.fillStyle(color, 1); + bar.fillRect(x, y, w * fraction, h); + } + + // -- Destruction ------------------------------------------------------ + + /** + * Remove a single CP's bar. + * @param {Object} cp + */ + destroy(cp) { + if (!cp) return; + const bar = this._bars.get(cp); + if (bar && bar.active) bar.destroy(); + this._bars.delete(cp); + } + + /** + * Destroy all bars and clean up. + */ + shutdown() { + for (const bar of this._bars.values()) { + if (bar && bar.active) bar.destroy(); + } + this._bars.clear(); + } +} diff --git a/src/systems/CombatSystem.js b/src/systems/CombatSystem.js index d4ba06d..c24a9a7 100644 --- a/src/systems/CombatSystem.js +++ b/src/systems/CombatSystem.js @@ -13,9 +13,11 @@ import ProjectileSprite from './ProjectileSprite'; export default class CombatSystem { /** * @param {Phaser.Scene} scene — the owning scene (Map_Player) + * @param {import('./TeamManager').default} teamManager — centralized team registry */ - constructor(scene) { + constructor(scene, teamManager) { this.scene = scene; + this.teamManager = teamManager; /** @type {Phaser.Physics.Arcade.Group} */ this.projectiles = scene.physics.add.group({ @@ -32,15 +34,6 @@ export default class CombatSystem { cannon: { armorPiercing: 0.5, critChance: 0.10, critMultiplier: 2.0 }, tank_cannon: { armorPiercing: 0.5, critChance: 0.10, critMultiplier: 2.0 }, }; - - /** - * References to the two faction containers so update() can - * manually check projectile-vs-unit overlaps. - * @type {Phaser.GameObjects.Container|null} - */ - this._goodGuys = null; - /** @type {Phaser.GameObjects.Container|null} */ - this._enemies = null; } // ────────────────────────────────────────────── @@ -65,37 +58,42 @@ export default class CombatSystem { priority = 'closest', } = options; - const enemyContainer = entity.getEnemyContainer(); - if (!enemyContainer || !enemyContainer.list) return null; + const attackerTeam = this.teamManager.getEntityTeam(entity); + if (attackerTeam == null) return null; - const alive = enemyContainer.getAll('dead', false); - if (!alive.length) return null; + const allGrouped = this.teamManager.getAllUnitsGrouped(); + if (!allGrouped.size) return null; const origin = new Phaser.Math.Vector2(entity.x, entity.y); const candidates = []; - for (const enemy of alive) { - if (!enemy.body) continue; + for (const [teamId, unitSet] of allGrouped) { + if (teamId === attackerTeam) continue; // skip own team - const dist = Phaser.Math.Distance.Between(origin.x, origin.y, enemy.x, enemy.y); - if (dist > maxRange) continue; + for (const enemy of unitSet) { + if (enemy.dead || (enemy.isDead && enemy.isDead())) continue; + if (!enemy.body) continue; - // Cone check (only when fov < 360) - if (fov < 360) { - const toTarget = Phaser.Math.Angle.BetweenPoints(origin, enemy); - const facing = entity.rotation || 0; - const halfFov = Phaser.Math.DegToRad(fov / 2); - if (Math.abs(Phaser.Math.Angle.Wrap(toTarget - facing)) > halfFov) continue; + const dist = Phaser.Math.Distance.Between(origin.x, origin.y, enemy.x, enemy.y); + if (dist > maxRange) continue; + + // Cone check (only when fov < 360) + if (fov < 360) { + const toTarget = Phaser.Math.Angle.BetweenPoints(origin, enemy); + const facing = entity.rotation || 0; + const halfFov = Phaser.Math.DegToRad(fov / 2); + if (Math.abs(Phaser.Math.Angle.Wrap(toTarget - facing)) > halfFov) continue; + } + + // LoS + if (!this.hasLineOfSight(origin, new Phaser.Math.Vector2(enemy.x, enemy.y))) continue; + + candidates.push({ + entity: enemy, + distance: dist, + health: enemy.getData('health') || 0, + }); } - - // LoS - if (!this.hasLineOfSight(origin, new Phaser.Math.Vector2(enemy.x, enemy.y))) continue; - - candidates.push({ - entity: enemy, - distance: dist, - health: enemy.getData('health') || 0, - }); } if (!candidates.length) return null; @@ -129,7 +127,12 @@ export default class CombatSystem { return { canHit: false, reason: 'invalid_entities' }; } - if (attacker.parentContainer.name === target.parentContainer.name) { + if (attacker.parentContainer?.name && target.parentContainer?.name && attacker.parentContainer.name === target.parentContainer.name) { + return { canHit: false, reason: 'friendly_fire' }; + } + + // Team-aware friendly fire check + if (this.teamManager && !this.teamManager.isEnemy(attacker, target)) { return { canHit: false, reason: 'friendly_fire' }; } @@ -216,11 +219,12 @@ export default class CombatSystem { * @returns {boolean} true if there are no blocking tiles on the line */ hasLineOfSight(pointA, pointB) { - const rockLayer = this.scene.rockLayer; - if (!rockLayer) return true; + const tilemap = this.scene.map || this.scene.orchestrator?.systems?.map?.tilemap; + if (!tilemap) return true; - const tileA = rockLayer.worldToTileXY(pointA.x, pointA.y); - const tileB = rockLayer.worldToTileXY(pointB.x, pointB.y); + // Use worldToTileXY from the tilemap, not from a layer + const tileA = tilemap.worldToTileXY(pointA.x, pointA.y); + const tileB = tilemap.worldToTileXY(pointB.x, pointB.y); if (!tileA || !tileB) return true; let x0 = tileA.x; @@ -245,19 +249,16 @@ export default class CombatSystem { continue; } - // Check rock layer collisions - const rockTile = rockLayer.getTileAt(x0, y0); + // Check rock layer collisions via tilemap object + const rockTile = tilemap.getTileAt(x0, y0, true, 'Rocks'); if (rockTile && rockTile.properties && rockTile.properties.collides) { return false; } // Optionally check ground-layer collisions - const ground = this.scene.groundLayer; - if (ground) { - const gTile = ground.getTileAt(x0, y0); - if (gTile && gTile.properties && gTile.properties.collides) { - return false; - } + const ground = tilemap.getTileAt(x0, y0, true, 'Floor'); + if (ground && ground.properties && ground.properties.collides) { + return false; } if (x0 === x1 && y0 === y1) break; @@ -321,20 +322,7 @@ export default class CombatSystem { return finalDamage; } - /** - * Tell the system which container holds friendly / enemy units. - * Called by the scene after spawning is complete. - * - * @param {Phaser.GameObjects.Container} goodGuys - * @param {Phaser.GameObjects.Container} enemies - */ - registerUnitContainers(goodGuys, enemies) { - this._goodGuys = goodGuys || null; - this._enemies = enemies || null; - } - // ────────────────────────────────────────────── - // UPDATE // ────────────────────────────────────────────── /** @@ -375,28 +363,30 @@ export default class CombatSystem { } } - // Manual overlap vs unit containers + // Manual overlap vs all teams this._checkOverlap(p); } for (const p of toRemove) p.destroy(); - // Auto-engage: iterate both factions - this._processCombatGroup(this._goodGuys, time); - this._processCombatGroup(this._enemies, time); + // Auto-engage: iterate all team groups + const grouped = this.teamManager.getAllUnitsGrouped(); + for (const [, unitSet] of grouped) { + this._processCombatGroup(unitSet, time); + } } /** - * Internal auto-engage loop for a faction container. + * Internal auto-engage loop for a team. * Iterates alive units, checks fire cooldown, acquires target, fires. - * @param {Phaser.GameObjects.Container|null} container + * @param {Set|Array} units — iterable of unit entities * @param {number} time */ - _processCombatGroup(container, time) { - if (!container || !container.list) return; - const units = container.getAll('dead', false); - for (let i = 0; i < units.length; i++) { - const unit = units[i]; + _processCombatGroup(units, time) { + if (!units) return; + const list = Array.from(units).filter((u) => !u.dead && !(u.isDead && u.isDead())); + for (let i = 0; i < list.length; i++) { + const unit = list[i]; if (!unit || !unit.body || unit.dead || (unit.isDead && unit.isDead())) continue; const combat = unit.components?.combat; @@ -423,29 +413,26 @@ export default class CombatSystem { // ────────────────────────────────────────────── /** - * Check one projectile against both unit containers. + * Check one projectile against all unit teams. * @param {Phaser.GameObjects.GameObject} projectile */ _checkOverlap(projectile) { if (!projectile.body) return; const attacker = projectile.getData('attacker'); + const grouped = this.teamManager.getAllUnitsGrouped(); - const check = (container) => { - if (!container) return; - const units = container.getAll('dead', false); - for (let i = 0; i < units.length; i++) { - const unit = units[i]; + for (const [, unitSet] of grouped) { + if (!projectile.active) return; // stop if already destroyed on hit + for (const unit of unitSet) { if (!unit.body || unit === attacker) continue; + if (!this.teamManager.isEnemy(attacker, unit)) continue; // skip friendlies if (this.scene.physics.overlap(projectile, unit)) { this._onHit(projectile, unit); return; // one hit per frame } } - }; - - check(this._goodGuys); - if (projectile.active) check(this._enemies); + } } /** diff --git a/src/systems/ControlPointManager.js b/src/systems/ControlPointManager.js new file mode 100644 index 0000000..89b7e9b --- /dev/null +++ b/src/systems/ControlPointManager.js @@ -0,0 +1,82 @@ +import ControlPointStateMachine from './ControlPointStateMachine.js'; + +/** + * ControlPointManager — manages all control point instances on the map. + * + * Responsibilities: + * - Create 4 ControlPointStateMachine instances at map clearing centers + * - Per-frame update of all CP instances + * - Generate capture-point income via EconomySystem when CAPTURED + * - Cleanup on shutdown + */ +export default class ControlPointManager { + /** + * @param {Phaser.Scene} scene + * @param {Phaser.Tilemaps.Tilemap} tilemap + * @param {import('./EconomySystem.js').default} economy + * @param {import('./TeamManager.js').default} [teamManager] + */ + constructor(scene, tilemap, economy, teamManager) { + /** @type {Phaser.Scene} */ + this.scene = scene; + + /** @type {ControlPointStateMachine[]} */ + this.controlPoints = []; + + /** @type {import('./EconomySystem.js').default|null} */ + this.economy = economy || null; + + /** @type {import('./TeamManager.js').default|null} */ + this.teamManager = teamManager || (scene?.teamManager ?? null); + + // Clearing centers in tile coordinates (test2.tmj, 128x128) + const CLEARING_TILES = [ + { x: 32, y: 32 }, + { x: 96, y: 32 }, + { x: 32, y: 96 }, + { x: 96, y: 96 }, + ]; + + for (const tile of CLEARING_TILES) { + const world = tilemap.tileToWorldXY(tile.x, tile.y); + const cp = new ControlPointStateMachine(scene, { + x: world.x, + y: world.y, + type: 'controlPoint', + captureTime: 60000, + radius: 5, + tileSize: 32, + economySystem: this.economy, + teamManager: this.teamManager, + }); + this.controlPoints.push(cp); + } + } + + /** + * Per-frame tick for all control points. + * + * @param {number} time + * @param {number} delta + */ + update(time, delta) { + for (const cp of this.controlPoints) { + if (cp.tick) { + cp.tick(time, delta, this.scene); + } + } + } + + /** + * Tear down every control point. + */ + destroy() { + for (const cp of this.controlPoints) { + if (cp.destroy) cp.destroy(); + } + this.controlPoints = []; + this.economy = null; + this.teamManager = null; + this.scene = null; + } +} diff --git a/src/systems/ControlPointStateMachine.js b/src/systems/ControlPointStateMachine.js index e2f3a1e..220c486 100644 --- a/src/systems/ControlPointStateMachine.js +++ b/src/systems/ControlPointStateMachine.js @@ -34,10 +34,10 @@ export const controlPointMachineConfig = { predictableActionArguments: true, context: { - owner: null, // playerId or null + owner: null, // teamId or null captureProgress: 0, // 0–100 percentage captureTime: DEFAULT_CAPTURE_TIME, - unitsInRadius: {}, // { playerId: count, ... } + unitsInRadius: {}, // { teamId: count, ... } }, states: { @@ -88,9 +88,9 @@ export const controlPointMachineConfig = { * control point zone on the map. * * Responsibilities: - * - Track units inside the capture radius per player + * - Track units inside the capture radius per team * - Advance capture progress (0–100%) over CONTESTED state - * - Generate capture-point income for the owning player + * - Generate capture-point income for the owning team * - Expose a Phaser Zone with a circular hit area for physics overlap * * Usage: @@ -110,17 +110,23 @@ export default class ControlPointStateMachine { * @param {number} [config.captureTime] - ms to complete capture (default 60k) * @param {string} [config.id] - unique identifier for this point * @param {Object} [config.economySystem] - EconomySystem instance for CP income + * @param {import('./TeamManager.js').default} [config.teamManager] - TeamManager for multi-team unit queries */ constructor(scene, config = {}) { this.scene = scene; - this.id = config.id || `cp_${Phaser.Math.RND.uuid().slice(0, 8)}`; + this.id = config.id || `cp_${(Math.random().toString(36).slice(2))}`; + this.type = config.type || 'controlPoint'; + this.captureTime = config.captureTime ?? DEFAULT_CAPTURE_TIME; this.radiusTiles = config.radius ?? DEFAULT_RADIUS; this.tileSize = config.tileSize ?? DEFAULT_TILE_SIZE; this.radiusPx = this.radiusTiles * this.tileSize; - /** @type {EconomySystem|null} */ + /** @type {import('./EconomySystem.js').default|null} */ this.economySystem = config.economySystem || null; + /** @type {import('./TeamManager.js').default|null} */ + this.teamManager = config.teamManager || (scene?.teamManager ?? null); + // ── XState machine ────────────────────────────── const { createMachine, interpret, assign } = require('xstate'); @@ -130,7 +136,7 @@ export default class ControlPointStateMachine { if (!ctx.owner) return true; // no owner → any unit is enemy const entries = Object.entries(ctx.unitsInRadius); return entries.some( - ([playerId, count]) => playerId !== ctx.owner && count > 0, + ([teamId, count]) => teamId !== ctx.owner && count > 0, ); }, }, @@ -139,7 +145,7 @@ export default class ControlPointStateMachine { clearOwner: (ctx) => { ctx.owner = null; }, resetProgress: (ctx) => { ctx.captureProgress = 0; }, setOwner: (ctx, event) => { - ctx.owner = event.owner || event.playerId || null; + ctx.owner = event.owner || event.teamId || null; }, onLeaveContested: (_ctx) => { // reset progress if leaving CONTESTED without completing capture @@ -181,13 +187,6 @@ export default class ControlPointStateMachine { this.zone.body.setOffset(0, 0); } - // ── Unit containers (set after construction) ──── - /** @type {Phaser.GameObjects.Container|null} */ - this._goodGuys = null; - - /** @type {Phaser.GameObjects.Container|null} */ - this._enemies = null; - // ── CP generation tick (1 CP per second) ──────── this._cpAccumulator = 0; this._cpInterval = 1000; // 1 CP per second @@ -198,52 +197,53 @@ export default class ControlPointStateMachine { // ─────────────────────────────────────────────────── /** - * Register the two faction containers so tick() can run physics - * overlaps between the zone and all units. + * Count units per team currently inside the capture radius. * - * @param {Phaser.GameObjects.Container} goodGuys - * @param {Phaser.GameObjects.Container} enemies - */ - registerUnitContainers(goodGuys, enemies) { - this._goodGuys = goodGuys || null; - this._enemies = enemies || null; - } - - /** - * Count units per player currently inside the capture radius. + * Uses the TeamManager (if available) to iterate all units grouped by team, + * falling back to teamId from unit data if no TeamManager is present. * - * Uses Phaser Arcade physics overlap between the zone's circular - * body and every unit in both faction containers. - * - * @returns {Object} { playerId: count, ... } + * @returns {Object} { teamId: count, ... } */ getUnitsInRadius() { const counts = {}; - const countInContainer = (container) => { - if (!container || !this.zone.body) return; - const units = container.getAll ? container.getAll('dead', false) : []; - - for (let i = 0; i < units.length; i++) { - const unit = units[i]; - if (!unit.body) continue; - - // Phaser Arcade overlap: distance-based circle-vs-rect - const dx = unit.x - this.zone.x; - const dy = unit.y - this.zone.y; - const distSq = dx * dx + dy * dy; - - if (distSq <= this.radiusPx * this.radiusPx) { - const playerId = unit.getData - ? (unit.getData('owner') || unit.getData('playerId') || 'unknown') - : 'unknown'; - counts[playerId] = (counts[playerId] || 0) + 1; + // If we have a TeamManager, iterate per-team units + const tm = this.teamManager; + if (tm && tm.getAllUnitsGrouped) { + const grouped = tm.getAllUnitsGrouped(); + for (const [teamId, units] of grouped) { + let count = 0; + for (const unit of units) { + if (!unit) continue; + const dx = (unit.x ?? 0) - this.zone.x; + const dy = (unit.y ?? 0) - this.zone.y; + const distSq = dx * dx + dy * dy; + if (distSq <= this.radiusPx * this.radiusPx) { + count += 1; + } + } + if (count > 0) { + counts[teamId] = count; } } - }; + return counts; + } - countInContainer(this._goodGuys); - countInContainer(this._enemies); + // Legacy fallback: scan scene children for units with getData('teamId') + const scanList = (this.scene?.children?.list || []); + for (const child of scanList) { + if (typeof child.getData === 'function') { + const t = child.getData('teamId'); + if (t != null) { + const dx = (child.x ?? 0) - this.zone.x; + const dy = (child.y ?? 0) - this.zone.y; + const distSq = dx * dx + dy * dy; + if (distSq <= this.radiusPx * this.radiusPx) { + counts[t] = (counts[t] || 0) + 1; + } + } + } + } return counts; } @@ -259,7 +259,7 @@ export default class ControlPointStateMachine { } /** - * Get the current owning player ID (or null if unowned). + * Get the current owning team ID (or null if unowned). * @returns {string|null} */ getOwner() { @@ -313,13 +313,17 @@ export default class ControlPointStateMachine { const unitsInRadius = this.getUnitsInRadius(); ctx.unitsInRadius = unitsInRadius; - // 2. Determine the dominant player (most units in radius) - const dominantPlayer = this._getDominantPlayer(unitsInRadius); + // 2. Determine the dominant team (most units in radius) + const dominantTeam = this._getDominantTeam(unitsInRadius); // 3. State-specific logic switch (currentState) { case ControlPointState.NEUTRAL: { - if (dominantPlayer) { + // NEUTRAL → CONTESTED when units from 2+ teams present + const activeTeams = Object.entries(unitsInRadius) + .filter(([, count]) => count > 0) + .map(([teamId]) => teamId); + if (activeTeams.length >= 2 || (activeTeams.length === 1 && ctx.owner !== activeTeams[0])) { this.send('UNITS_ENTERED'); } break; @@ -327,17 +331,17 @@ export default class ControlPointStateMachine { case ControlPointState.CONTESTED: { // If no units remain, transition back to NEUTRAL - if (!dominantPlayer) { + if (!dominantTeam) { this.send('UNITS_LEFT'); break; } // If current progress is tied to the previous contender but a - // different player is now dominant, reset progress - if (ctx._contender && ctx._contender !== dominantPlayer) { + // different team is now dominant, reset progress + if (ctx._contender && ctx._contender !== dominantTeam) { ctx.captureProgress = 0; } - ctx._contender = dominantPlayer; + ctx._contender = dominantTeam; // Advance capture progress const captureTime = ctx.captureTime || DEFAULT_CAPTURE_TIME; @@ -345,16 +349,16 @@ export default class ControlPointStateMachine { ctx.captureProgress = Math.min(100, (ctx.captureProgress || 0) + progressDelta); if (ctx.captureProgress >= 100) { - this.send('PROGRESS_COMPLETE', { owner: dominantPlayer }); + this.send('PROGRESS_COMPLETE', { owner: dominantTeam }); } break; } case ControlPointState.CAPTURED: { const owner = ctx.owner; - // Check for enemies in radius + // Check for enemy units in radius const enemyPresent = Object.entries(unitsInRadius).some( - ([playerId, count]) => playerId !== owner && count > 0, + ([teamId, count]) => teamId !== owner && count > 0, ); if (enemyPresent) { @@ -394,9 +398,8 @@ export default class ControlPointStateMachine { this.zone.destroy(); this.zone = null; } - this._goodGuys = null; - this._enemies = null; this.economySystem = null; + this.teamManager = null; this.scene = null; } @@ -405,19 +408,19 @@ export default class ControlPointStateMachine { // ─────────────────────────────────────────────────── /** - * Return the playerId with the most units in radius, or null. + * Return the teamId with the most units in radius, or null. * * @param {Object} counts * @returns {string|null} */ - _getDominantPlayer(counts) { + _getDominantTeam(counts) { let best = null; let max = 0; - for (const [playerId, count] of Object.entries(counts)) { + for (const [teamId, count] of Object.entries(counts)) { if (count > max) { max = count; - best = playerId; + best = teamId; } } diff --git a/src/systems/HealthBarSystem.js b/src/systems/HealthBarSystem.js new file mode 100644 index 0000000..d46c20e --- /dev/null +++ b/src/systems/HealthBarSystem.js @@ -0,0 +1,158 @@ +/** + * HealthBarSystem — per-unit health bar overlay rendered via Phaser Graphics. + * + * Features: + * - drawBar(unit) → creates a Graphics bar above the unit + * - update(unit) → re-renders width/visibility each frame + * - destroy(unit) → removes the bar on death + * - flash(unit, rgb) → brief tint flash on the bar + * + * Color gradient: green (≥75%) → yellow (50-74%) → red (<25%) + * Width proportional to unit.displayWidth, 4px tall. + * Auto-hidden at full HP. + */ + +export default class HealthBarSystem { + /** + * @param {Phaser.Scene} scene + */ + constructor(scene) { + this.scene = scene; + + /** @type {Map} */ + this._bars = new Map(); + + /** @type {number} Bar height in pixels */ + this._barHeight = 4; + + /** @type {number} Vertical offset above the unit sprite */ + this._offsetY = 6; + } + + // ── Color logic ───────────────────────────────────────────────── + + /** + * Map a health fraction (0.0 – 1.0) to an RGB hex colour. + * @param {number} fraction + * @returns {number} 0xRRGGBB + */ + getColor(fraction) { + if (fraction >= 0.75) return 0x00ff00; // green + if (fraction >= 0.25) return 0xffff00; // yellow + return 0xff0000; // red + } + + // ── Bar lifecycle ─────────────────────────────────────────────── + + /** + * Create (or recreate) a health bar for a unit. + * @param {Object} unit — Phaser Sprite or container with getData('health') / getData('maxHp') + */ + drawBar(unit) { + if (!unit || !unit.active) return null; + + // Clean up any existing bar for this unit + if (this._bars.has(unit)) { + this._bars.get(unit).destroy(); + this._bars.delete(unit); + } + + const bar = this.scene.add.graphics(); + bar.setDepth(100); // above most sprites + this._bars.set(unit, bar); + + this.update(unit); + return bar; + } + + /** + * Update the bar for a given unit (position, width, colour, visibility). + * Called each frame from the unit's preUpdate or orchestrator. + * @param {Object} unit + */ + update(unit) { + if (!unit || !unit.active) return; + + let bar = this._bars.get(unit); + + // Lazily create if missing + if (!bar || !bar.active) { + bar = this.drawBar(unit); + if (!bar) return; + } + + const hp = unit.getData?.('health') ?? 100; + const maxHp = unit.getData?.('maxHp') ?? 100; + const fraction = maxHp > 0 ? hp / maxHp : 0; + + // Visibility: hide at full HP + const show = fraction < 1; + bar.setVisible(show); + if (!show) return; + + const color = this.getColor(fraction); + const width = unit.displayWidth ?? 32; + const x = unit.x - width / 2; + const y = unit.y - (unit.displayHeight ?? 32) / 2 - this._offsetY - this._barHeight; + + bar.clear(); + + // Background (dark border) + bar.fillStyle(0x000000, 0.8); + bar.fillRect(x - 1, y - 1, width + 2, this._barHeight + 2); + + // Foreground fill proportional to health + bar.fillStyle(color, 1); + bar.fillRect(x, y, width * fraction, this._barHeight); + } + + /** + * Briefly flash the bar with a given colour. + * @param {Object} unit + * @param {number} rgb — e.g. 0xff0000 for red + */ + flash(unit, rgb) { + if (!unit || !unit.active) return; + + const bar = this._bars.get(unit); + if (!bar || !bar.active) return; + + const hp = unit.getData?.('health') ?? 100; + const maxHp = unit.getData?.('maxHp') ?? 100; + const fraction = maxHp > 0 ? hp / maxHp : 0; + const width = unit.displayWidth ?? 32; + const x = unit.x - width / 2; + const y = unit.y - (unit.displayHeight ?? 32) / 2 - this._offsetY - this._barHeight; + + bar.clear(); + bar.fillStyle(rgb, 1); + bar.fillRect(x, y, width * fraction, this._barHeight); + + // Schedule a restore on the next update() tick via the normal colour logic + bar.setAlpha(0.9); + } + + /** + * Remove a unit's health bar (death / despawn). + * @param {Object} unit + */ + destroy(unit) { + if (!unit) return; + + const bar = this._bars.get(unit); + if (bar && bar.active) { + bar.destroy(); + } + this._bars.delete(unit); + } + + /** + * Destroy all bars and clean up. + */ + shutdown() { + for (const bar of this._bars.values()) { + if (bar && bar.active) bar.destroy(); + } + this._bars.clear(); + } +} diff --git a/src/systems/PathfindingSystem.js b/src/systems/PathfindingSystem.js index 7d8bab8..332f8f0 100644 --- a/src/systems/PathfindingSystem.js +++ b/src/systems/PathfindingSystem.js @@ -26,17 +26,20 @@ export default class PathfindingSystem { /** @type {number[][]} 2D grid: 0 = walkable, 1 = blocked */ this.grid = []; - /** @type {number} tile width in pixels (default 64) */ - this.tileWidth = config.tileWidth || 64; + /** @type {number} tile width in pixels (default 32, matches current map) */ + this.tileWidth = config.tileWidth || 32; - /** @type {number} tile height in pixels (default 64) */ - this.tileHeight = config.tileHeight || 64; + /** @type {number} tile height in pixels (default 32, matches current map) */ + this.tileHeight = config.tileHeight || 32; /** @type {boolean} whether the grid has been initialized */ this._initialized = false; - /** @type {Phaser.Tilemaps.TilemapLayer|null} collision layer used for grid init */ - this._collisionLayer = null; + /** @type {string|null} collision layer name for tile queries */ + this._collisionLayerName = null; + + /** @type {string|null} ground layer name for tile queries */ + this._groundLayerName = null; /** @type {Set} ids of entities whose paths need recalculation */ this._dirtyEntities = new Set(); @@ -50,10 +53,13 @@ export default class PathfindingSystem { * Build the 2D walkability grid from the tilemap's collision / rock / ground * layers. Acceptable tiles are those whose tileset tile has a `cost` property. * - * @param {string} [collisionLayerName="rockLayer"] layer name for unwalkable tiles - * @param {string} [groundLayerName="groundLayer"] fallback layer for walkable tiles + * Uses tilemap.getTileAt(x, y, hasTile, layerName) — NOT LayerData.getTileAt() + * which doesn't exist on Phaser 3.55 LayerData objects. + * + * @param {string} [collisionLayerName="Rocks"] layer name for collision tiles + * @param {string} [groundLayerName="Floor"] layer name for walkable tiles */ - initGrid(collisionLayerName = "rockLayer", groundLayerName = "groundLayer") { + initGrid(collisionLayerName = "Rocks", groundLayerName = "Floor") { this.easystar = new EasyStar.js(); this.easystar.setIterationsPerCalculation(1000); this.easystar.enableDiagonals(); @@ -62,10 +68,14 @@ export default class PathfindingSystem { const width = this.tilemap.width; const height = this.tilemap.height; - this._collisionLayer = this.tilemap.getLayer(collisionLayerName); - const groundLayer = this.tilemap.getLayer(groundLayerName); + this._collisionLayerName = collisionLayerName; + this._groundLayerName = groundLayerName; - if (!this._collisionLayer && !groundLayer) { + // Verify layers exist on the tilemap + const hasCollision = this.tilemap.getLayer(collisionLayerName) != null; + const hasGround = this.tilemap.getLayer(groundLayerName) != null; + + if (!hasCollision && !hasGround) { console.warn( "[PathfindingSystem] initGrid: neither collision layer nor ground layer found. Creating open grid." ); @@ -82,23 +92,28 @@ export default class PathfindingSystem { for (let y = 0; y < height; y++) { const row = []; for (let x = 0; x < width; x++) { - // Collision layer wins — if a rock tile exists it may be blocked - const rockTile = this._collisionLayer - ? this._collisionLayer.getTileAt(x, y) + const rockTile = hasCollision + ? this.tilemap.getTileAt(x, y, true, collisionLayerName) : null; - const groundTile = groundLayer - ? groundLayer.getTileAt(x, y) + const groundTile = hasGround + ? this.tilemap.getTileAt(x, y, true, groundLayerName) : null; - const tile = rockTile || groundTile; + // Blocked if: + // 1) rock tile exists and has collides property, OR + // 2) no ground tile at all (index === -1 means blank) + const blocked = + (rockTile && rockTile.index >= 0 && rockTile.properties && rockTile.properties.collides) || + (!groundTile || groundTile.index < 0); - if (tile && tile.properties && tile.properties.cost != null) { - // Walkable tile — register its cost - this.easystar.setTileCost(tile.index, tile.properties.cost); - row.push(0); - } else { - // Blocked tile + if (blocked) { row.push(1); + } else { + // Walkable ground tile — register cost if present, default 0 + if (groundTile && groundTile.properties && groundTile.properties.cost != null) { + this.easystar.setTileCost(groundTile.index, groundTile.properties.cost); + } + row.push(0); } } this.grid.push(row); @@ -182,189 +197,146 @@ export default class PathfindingSystem { // --------------------------------------------------------------------------- /** - * Asynchronously find a tile-path between two tile coordinates. - * The callback receives either an array of `{x, y}` tile positions or `null`. + * Find a path from start tile to end tile, using the grid coordinates. + * Returns the path (async via callback). * - * @param {{x: number, y: number}} startTile - * @param {{x: number, y: number}} endTile - * @param {{avoidEnemies?: boolean, maxPathLength?: number}} [options] - * @param {(path: Array<{x: number, y: number}> | null) => void} callback + * @param {number} startTileX + * @param {number} startTileY + * @param {number} endTileX + * @param {number} endTileY + * @returns {Promise|null>} */ - findPath(startTile, endTile, options, callback) { - if (!this._initialized) { - console.warn("[PathfindingSystem] findPath called before initGrid"); - callback(null); - return; - } + findPath(startTileX, startTileY, endTileX, endTileY) { + return new Promise((resolve) => { + if (!this._initialized) { + console.warn("[PathfindingSystem] findPath called before initGrid"); + resolve(null); + return; + } - // Normalize arguments — options is optional - if (typeof options === "function") { - callback = options; - options = {}; - } + // Clamp to map bounds + const clamp = (v, max) => Math.max(0, Math.min(v, max)); + const sx = clamp(Math.round(startTileX), this.grid[0].length - 1); + const sy = clamp(Math.round(startTileY), this.grid.length - 1); + const ex = clamp(Math.round(endTileX), this.grid[0].length - 1); + const ey = clamp(Math.round(endTileY), this.grid.length - 1); - const opts = options || {}; - const maxLength = opts.maxPathLength || Infinity; - - this.easystar.findPath( - startTile.x, - startTile.y, - endTile.x, - endTile.y, - (path) => { + this.easystar.findPath(sx, sy, ex, ey, (path) => { if (path === null) { - console.warn("[PathfindingSystem] No path found."); - callback(null); + console.warn( + `[PathfindingSystem] No path found from (${sx},${sy}) to (${ex},${ey})` + ); + resolve(null); return; } + resolve(path.map((p) => ({ x: p.x, y: p.y }))); + }); - // Apply maxPathLength if set - const clamped = maxLength < Infinity ? path.slice(0, maxLength) : path; - callback(clamped); - } - ); - - this.easystar.calculate(); + this.easystar.calculate(); + }); } - // --------------------------------------------------------------------------- - // Cache - // --------------------------------------------------------------------------- - /** - * Retrieve a previously computed path for an entity. + * Find an entity's cached path, or compute a new one. * * @param {string} entityId - * @returns {Array<{x: number, y: number}> | undefined} + * @param {number} startTileX + * @param {number} startTileY + * @param {number} endTileX + * @param {number} endTileY + * @param {boolean} [force=false] skip cache + * @returns {Promise|null>} */ - getCachedPath(entityId) { - return this.pathCache.get(entityId); - } - - /** - * Store a computed path for an entity. - * - * @param {string} entityId - * @param {Array<{x: number, y: number}>} path - */ - setCachedPath(entityId, path) { - this.pathCache.set(entityId, path); - } - - /** - * Invalidate cached paths. If `entityId` is provided only that entity's - * path is cleared; otherwise the entire cache is flushed. - * - * @param {string} [entityId] - */ - invalidateCache(entityId) { - if (entityId) { - this.pathCache.delete(entityId); - this._dirtyEntities.delete(entityId); - } else { - this.pathCache.clear(); - this._dirtyEntities.clear(); + async findEntityPath(entityId, startTileX, startTileY, endTileX, endTileY, force = false) { + if (!force && this.pathCache.has(entityId)) { + return this.pathCache.get(entityId); } + + const path = await this.findPath(startTileX, startTileY, endTileX, endTileY); + + if (path) { + this.pathCache.set(entityId, path); + } + + return path; + } + + /** + * Invalidate the path cache for a specific entity. + * + * @param {string} entityId + */ + invalidateEntity(entityId) { + this.pathCache.delete(entityId); + this._dirtyEntities.delete(entityId); + } + + /** + * Clear all cached paths. + */ + clearCache() { + this.pathCache.clear(); + this._dirtyEntities.clear(); } // --------------------------------------------------------------------------- - // Utility + // Utilities // --------------------------------------------------------------------------- /** - * Convert a tile-path (array of `{x, y}`) into world pixel coordinates. - * - * @param {Array<{x: number, y: number}>} path - * @returns {Array<{x: number, y: number}>} - */ - pathToWorldCoords(path) { - if (!path || !path.length) return []; - - return path.map((tile) => ({ - x: tile.x * this.tileWidth + this.tileWidth / 2, - y: tile.y * this.tileHeight + this.tileHeight / 2, - })); - } - - /** - * Convert a single tile coordinate to a world pixel coordinate. - * - * @param {{x: number, y: number}} tile - * @returns {{x: number, y: number}} - */ - tileToWorldCoords(tile) { - return { - x: tile.x * this.tileWidth + this.tileWidth / 2, - y: tile.y * this.tileHeight + this.tileHeight / 2, - }; - } - - /** - * Convert a world pixel position to a tile coordinate. + * Convert world coordinates (pixels) to tile grid coordinates. * * @param {number} worldX * @param {number} worldY * @returns {{x: number, y: number}} */ - worldToTileCoords(worldX, worldY) { + worldToTile(worldX, worldY) { + // For isometric: needs special handling. For now use basic division. + const tileX = Math.floor(worldX / this.tileWidth); + const tileY = Math.floor(worldY / this.tileHeight); + return { x: tileX, y: tileY }; + } + + /** + * Convert tile coordinates to world coordinates (center of tile). + * + * @param {number} tileX + * @param {number} tileY + * @returns {{x: number, y: number}} + */ + tileToWorld(tileX, tileY) { return { - x: Math.floor(worldX / this.tileWidth), - y: Math.floor(worldY / this.tileHeight), + x: tileX * this.tileWidth + this.tileWidth / 2, + y: tileY * this.tileHeight + this.tileHeight / 2, }; } - // --------------------------------------------------------------------------- - // Update loop - // --------------------------------------------------------------------------- - /** - * Per-tick update. Recalculates paths for entities flagged as dirty - * (e.g., after an obstacle change invalidated their path). + * Check if a tile coordinate is walkable. * - * @param {number} _time - * @param {number} _delta - */ - update(_time, _delta) { - // Currently a no-op — path recalculation happens lazily when - // findPath is called. Extend here if you need proactive recalculation. - } - - // --------------------------------------------------------------------------- - // Introspection - // --------------------------------------------------------------------------- - - /** - * Whether the grid has been initialized. + * @param {number} tileX + * @param {number} tileY * @returns {boolean} */ - get initialized() { - return this._initialized; + isWalkable(tileX, tileY) { + if ( + !this._initialized || + tileY < 0 || + tileY >= this.grid.length || + tileX < 0 || + tileX >= this.grid[0].length + ) { + return false; + } + return this.grid[tileY][tileX] === 0; } /** - * Grid dimensions. - * @returns {{width: number, height: number}} + * Get the grid for debugging. + * + * @returns {number[][]} */ - get dimensions() { - return { - width: this.grid.length > 0 ? this.grid[0].length : 0, - height: this.grid.length, - }; - } - - /** - * Current number of cached paths. - * @returns {number} - */ - get cacheSize() { - return this.pathCache.size; - } - - /** - * Number of entities waiting for path recalculation. - * @returns {number} - */ - get dirtyCount() { - return this._dirtyEntities.size; + getGrid() { + return this.grid; } } diff --git a/src/systems/ProductionPanel.js b/src/systems/ProductionPanel.js new file mode 100644 index 0000000..276e5ee --- /dev/null +++ b/src/systems/ProductionPanel.js @@ -0,0 +1,340 @@ +import { getBuildingType } from 'Entities/buildings/building-types'; + +const PANEL_WIDTH = 280; +const PANEL_HEIGHT = 220; +const PANEL_BG = 0x1a1a1a; +const PANEL_BORDER = 0x444444; +const BUTTON_WIDTH = 120; +const BUTTON_HEIGHT = 36; +const BUTTON_BG = 0x3366aa; +const BUTTON_DISABLED_ALPHA = 0.4; +const BUTTON_ENABLED_ALPHA = 1; +const PROGRESS_BAR_WIDTH = 240; +const PROGRESS_BAR_HEIGHT = 12; +const PROGRESS_BAR_BG = 0x222222; +const PROGRESS_BAR_FILL = 0x44cc66; + +/** + * ProductionPanel — UI panel shown when clicking a building. + * + * Responsibilities: + * - Fixed-camera container at bottom-right + * - Shows building name, current state, production queue + * - Add Unit buttons per building type productions array + * - Economy integration (canAfford / deduct) + * - Progress bar for first queue item + * - Auto-close when clicking away + */ +export default class ProductionPanel { + /** + * @param {Phaser.Scene} scene + * @param {Object} [options] + * @param {string} [options.playerId='Player'] + * @param {Function} [options.getEconomy] — returns EconomySystem instance + * @param {Function} [options.onProductionComplete] — callback(bsm, unitType) + */ + constructor(scene, options = {}) { + this.scene = scene; + this.playerId = options.playerId ?? 'Player'; + this.getEconomy = options.getEconomy ?? (() => scene.orchestrator?.systems?.economy); + this.onProductionComplete = options.onProductionComplete ?? null; + + this.selectedBSM = null; + this.unitButtons = []; + + this._buildContainer(); + this._wireSceneClick(); + } + + // -- Public API ------------------------------------------------------- + + /** + * Show the panel for a given building state machine. + * @param {BuildingStateMachine} bsm + */ + show(bsm) { + if (!bsm) return; + this.selectedBSM = bsm; + + this._clearUnitButtons(); + this._renderBuildingInfo(bsm); + this._renderQueue(bsm); + this._renderUnitButtons(bsm); + + this.container.setVisible(true); + this.container.setAlpha(1); + } + + /** + * Hide the panel. + */ + hide() { + this.container.setVisible(false); + this.container.setAlpha(0); + this.selectedBSM = null; + this._clearUnitButtons(); + } + + /** + * Per-frame update — refresh progress bar for current production. + * @param {number} time + */ + update(time) { + if (!this.selectedBSM || this.selectedBSM.productionQueue.length === 0) { + if (this.progressBar) { + this.progressBar.setVisible(false); + } + return; + } + + const item = this.selectedBSM.productionQueue[0]; + if (item.startTime === 0) { + item.startTime = time; + } + + const elapsed = time - item.startTime; + const duration = this.selectedBSM.productionTime || 8000; + const ratio = Math.min(elapsed / duration, 1); + + this.progressBar.setVisible(true); + this.progressBar.setFillStyle(PROGRESS_BAR_FILL); + this.progressBar.width = Math.max(1, PROGRESS_BAR_WIDTH * ratio); + this.progressBar.displayWidth = this.progressBar.width; + + if (ratio >= 1) { + // Production complete — fire callback once, then clear the item + if (this.onProductionComplete) { + this.onProductionComplete(this.selectedBSM, item.unitType); + } + this.selectedBSM.productionQueue.shift(); + // Reset progress bar for next item or hide if queue empty + if (this.selectedBSM.productionQueue.length === 0) { + this.progressBar.setVisible(false); + } + return; + } + } + + /** + * Clean up all Phaser objects. + */ + destroy() { + this._clearUnitButtons(); + if (this.container && this.container.active) { + this.container.destroy(); + } + this.container = null; + this.selectedBSM = null; + + if (this._sceneClickHandler) { + this.scene.input.off('pointerdown', this._sceneClickHandler); + this._sceneClickHandler = null; + } + } + + // -- Internal --------------------------------------------------------- + + _buildContainer() { + const cam = this.scene.cameras.main; + const cx = cam.width - PANEL_WIDTH / 2 - 12; + const cy = cam.height - PANEL_HEIGHT / 2 - 12; + + this.container = this.scene.add.container(cx, cy); + this.container.setScrollFactor(0, 0); + this.container.setDepth(120); + this.container.setVisible(false); + this.container.setAlpha(0); + + // Panel background + this.bg = this.scene.add.rectangle(0, 0, PANEL_WIDTH, PANEL_HEIGHT, PANEL_BG); + this.bg.setOrigin(0.5, 0.5); + this.bg.setStrokeStyle(2, PANEL_BORDER); + this.container.add(this.bg); + + // Building name + this.nameText = this.scene.add.text(0, -PANEL_HEIGHT / 2 + 20, '', { + fontFamily: 'monospace', + fontSize: '14px', + color: '#ffffff', + }); + this.nameText.setOrigin(0.5, 0.5); + this.container.add(this.nameText); + + // State label + this.stateText = this.scene.add.text(0, -PANEL_HEIGHT / 2 + 40, '', { + fontFamily: 'monospace', + fontSize: '11px', + color: '#aaaaaa', + }); + this.stateText.setOrigin(0.5, 0.5); + this.container.add(this.stateText); + + // Progress bar background + this.progressBarBg = this.scene.add.rectangle( + 0, -PANEL_HEIGHT / 2 + 62, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT, PROGRESS_BAR_BG, + ); + this.progressBarBg.setOrigin(0.5, 0.5); + this.container.add(this.progressBarBg); + + // Progress bar fill + this.progressBar = this.scene.add.rectangle( + -PROGRESS_BAR_WIDTH / 2, -PANEL_HEIGHT / 2 + 62, 1, PROGRESS_BAR_HEIGHT, PROGRESS_BAR_FILL, + ); + this.progressBar.setOrigin(0, 0.5); + this.progressBar.setVisible(false); + this.container.add(this.progressBar); + + // Queue label + this.queueLabel = this.scene.add.text(0, -PANEL_HEIGHT / 2 + 82, 'Queue:', { + fontFamily: 'monospace', + fontSize: '11px', + color: '#cccccc', + }); + this.queueLabel.setOrigin(0.5, 0.5); + this.container.add(this.queueLabel); + + // Queue count text + this.queueText = this.scene.add.text(0, -PANEL_HEIGHT / 2 + 98, '', { + fontFamily: 'monospace', + fontSize: '11px', + color: '#eeeeee', + }); + this.queueText.setOrigin(0.5, 0.5); + this.container.add(this.queueText); + } + + _renderBuildingInfo(bsm) { + const typeConfig = getBuildingType(bsm.type); + const name = typeConfig?.label ?? bsm.type; + this.nameText.setText(name); + this.stateText.setText(`State: ${bsm?.getState?.() ?? bsm?._currentState ?? '?'}`); + } + + _renderQueue(bsm) { + const count = bsm.productionQueue?.length ?? 0; + this.queueText.setText(count > 0 ? `${count} item(s)` : 'Empty'); + } + + _renderUnitButtons(bsm) { + const typeConfig = getBuildingType(bsm.type); + const productions = typeConfig?.productions ?? []; + const maxQueue = typeConfig?.maxQueueSize ?? 5; + + let startX = -((productions.length * BUTTON_WIDTH + (productions.length - 1) * 8) / 2) + BUTTON_WIDTH / 2; + const startY = PANEL_HEIGHT / 2 - 40; + + for (const prod of productions) { + const btn = this._createUnitButton(startX, startY, bsm, prod, maxQueue); + this.unitButtons.push(btn); + this.container.add(btn.bg); + this.container.add(btn.label); + startX += BUTTON_WIDTH + 8; + } + } + + _createUnitButton(x, y, bsm, prod, maxQueue) { + const affordable = this._isAffordable(prod.cost); + const queueFull = (bsm.productionQueue?.length ?? 0) >= maxQueue; + const enabled = affordable && !queueFull; + + const bg = this.scene.add.rectangle(x, y, BUTTON_WIDTH, BUTTON_HEIGHT, BUTTON_BG); + bg.setOrigin(0.5, 0.5); + bg.setStrokeStyle(1, 0xffffff); + bg.setInteractive(); + bg.setAlpha(enabled ? BUTTON_ENABLED_ALPHA : BUTTON_DISABLED_ALPHA); + + const label = this.scene.add.text(x, y, prod.label, { + fontFamily: 'monospace', + fontSize: '11px', + color: '#ffffff', + }); + label.setOrigin(0.5, 0.5); + + const costText = this.scene.add.text(x, y + 10, this._formatCost(prod.cost), { + fontFamily: 'monospace', + fontSize: '9px', + color: '#dddddd', + }); + costText.setOrigin(0.5, 0.5); + this.container.add(costText); + + bg.on('pointerdown', () => { + if (!enabled) return; + if (!this._isAffordable(prod.cost)) return; + if ((bsm.productionQueue?.length ?? 0) >= maxQueue) return; + + const economy = this.getEconomy(); + if (!economy?.canAfford(this.playerId, prod.cost)) return; + + const deducted = economy.deduct(this.playerId, prod.cost); + if (!deducted) return; + + bsm.addToQueue(prod.id, 1); + this._renderQueue(bsm); + this._refreshButtonStates(bsm, maxQueue); + }); + + return { bg, label, costText, prodId: prod.id }; + } + + _isAffordable(cost) { + const economy = this.getEconomy(); + if (!economy || !cost) return true; + return economy.canAfford(this.playerId, cost); + } + + _formatCost(cost) { + if (!cost) return ''; + const parts = []; + if (cost.fuel != null) parts.push(`F:${cost.fuel}`); + if (cost.ammo != null) parts.push(`A:${cost.ammo}`); + return parts.join(' '); + } + + _refreshButtonStates(bsm, maxQueue) { + const queueLen = bsm.productionQueue?.length ?? 0; + for (const btn of this.unitButtons) { + const affordable = this._isAffordable( + getBuildingType(bsm.type)?.productions?.find(p => p.id === btn.prodId)?.cost, + ); + const enabled = affordable && queueLen < maxQueue; + btn.bg.setAlpha(enabled ? BUTTON_ENABLED_ALPHA : BUTTON_DISABLED_ALPHA); + } + } + + _clearUnitButtons() { + for (const btn of this.unitButtons) { + if (btn.bg && btn.bg.active) btn.bg.destroy(); + if (btn.label && btn.label.active) btn.label.destroy(); + if (btn.costText && btn.costText.active) btn.costText.destroy(); + } + this.unitButtons = []; + } + + _wireSceneClick() { + this._sceneClickHandler = (pointer) => { + if (!this.selectedBSM) return; + if (!this.container.visible) return; + + // If click is inside panel bounds, keep open + const cam = this.scene.cameras.main; + const panelLeft = this.container.x - PANEL_WIDTH / 2; + const panelRight = this.container.x + PANEL_WIDTH / 2; + const panelTop = this.container.y - PANEL_HEIGHT / 2; + const panelBottom = this.container.y + PANEL_HEIGHT / 2; + + if ( + pointer.x >= panelLeft && + pointer.x <= panelRight && + pointer.y >= panelTop && + pointer.y <= panelBottom + ) { + return; + } + + this.hide(); + }; + + this.scene.input.on('pointerdown', this._sceneClickHandler); + } +} diff --git a/src/systems/ProjectileSprite.js b/src/systems/ProjectileSprite.js new file mode 100644 index 0000000..59e19e7 --- /dev/null +++ b/src/systems/ProjectileSprite.js @@ -0,0 +1,85 @@ +import Phaser from 'phaser'; + +/** + * ProjectileSprite — visible physics sprite that travels toward a target angle. + * + * Constructor: (scene, x, y, texture, angle, speed, damage, source) + * - angle: radians + * - speed: px/s + * - damage: number + * - source: the firing entity (used for faction tint + hit attribution) + */ +export default class ProjectileSprite extends Phaser.Physics.Arcade.Sprite { + constructor(scene, x, y, texture, angle, speed, damage, source) { + super(scene, x, y, texture); + this.source = source; + this.speed = speed; + this.damage = damage; + + scene.add.existing(this); + scene.physics.world.enableBody(this, Phaser.Physics.Arcade.DYNAMIC_BODY); + + // Velocity from angle (set directly — velocityFromAngle in constructor + // fires before body is fully initialized, leaving velocity at 0,0) + this.body.setVelocity( + Math.cos(angle) * speed, + Math.sin(angle) * speed + ); + this.body.allowGravity = false; + + // Rotate to face direction + this.setRotation(angle); + + // Tint by faction — use teamId from source's TeamManager data + const teamId = source?.getData?.('teamId'); + if (teamId && this.scene?.teamManager) { + const color = this.scene.teamManager.getTeamColor(teamId); + if (color) this.setTint(color); + } else if (source?.parentContainer?.name?.toLowerCase?.().includes('good')) { + this.setTint(0x0000ff); // legacy fallback + } else if (source?.parentContainer?.name?.toLowerCase?.().includes('bad')) { + this.setTint(0xff0000); // legacy fallback + } + } + + preUpdate(time, delta) { + super.preUpdate(time, delta); + + // Off-screen culling + const cam = this.scene.cameras?.main; + if (cam) { + const b = cam.worldView; + if ( + this.x < b.x - 64 || + this.x > b.x + b.width + 64 || + this.y < b.y - 64 || + this.y > b.y + b.height + 64 + ) { + this.destroy(); + } + } + } + + /** + * Called when projectile hits a unit. + */ + onHit(target) { + if (!target || target.dead) return; + + // Prefer CombatSystem damage if reachable through scene, else delegate to target + const combat = this.scene.combatSystem || this.scene.orchestrator?.systems?.combat; + if (combat && typeof combat.applyDamage === 'function') { + combat.applyDamage(target, this.damage, 'rifle'); + } else if (target.handleTakeDamage) { + target.handleTakeDamage(this.damage); + } + + this.scene.events.emit('combat:projectileHit', { + attacker: this.source, + target, + damage: this.damage, + }); + + this.destroy(); + } +} diff --git a/src/systems/ResourceBar.js b/src/systems/ResourceBar.js new file mode 100644 index 0000000..28c1880 --- /dev/null +++ b/src/systems/ResourceBar.js @@ -0,0 +1,93 @@ +/** + * ResourceBar -- top-left HUD showing Fuel, Ammo, Capture Points. + * + * Features: + * - Text-based overlay (Phaser.GameObjects.Text) fixed to camera + * - Auto-updates on economy:updated events + * - colored icons inline + */ + +export default class ResourceBar { + /** + * @param {Phaser.Scene} scene + * @param {Object} [options] + * @param {string} [options.playerId='Player'] + * @param {number} [options.x=16] -- screen X + * @param {number} [options.y=16] -- screen Y + */ + constructor(scene, options = {}) { + this.scene = scene; + this.playerId = options.playerId ?? 'Player'; + this._x = options.x ?? 16; + this._y = options.y ?? 16; + + /** @type {Phaser.GameObjects.Text|null} */ + this._text = this.scene.add.text( + this._x, + this._y, + '', + { + fontFamily: 'monospace', + fontSize: '16px', + color: '#ffffff', + backgroundColor: 'rgba(0,0,0,0.5)', + }, + ); + this._text.setOrigin(0, 0); + this._text.setScrollFactor(0, 0); + this._text.setDepth(110); // above everything + this._text.setText(this._format({ fuel: 100, ammo: 100, capturePoints: 0 })) + } + + // -- Data wiring ------------------------------------------------------ + + /** + * Wire up to an EconomySystem instance so the bar auto-updates. + * @param {EconomySystem} economySystem + * @param {string} [playerId] -- override default player + */ + setEconomySystem(economySystem, playerId) { + if (playerId) this.playerId = playerId; + this._economy = economySystem; + if (!economySystem?.events) return; + + economySystem.events.on('economy:updated', (payload) => { + if (payload.playerId === this.playerId) { + this.updateFromResources(payload.resources); + } + }); + } + + // -- Rendering -------------------------------------------------------- + + /** + * Update the displayed text from the current resource snapshot. + * @param {{fuel: number, ammo: number, capturePoints: number}} resources + */ + updateFromResources(resources) { + if (!this._text || !this._text.active) return; + this._text.setText(this._format(resources)); + } + + /** + * Build the HUD string with emoji/colour icons. + * @private + */ + _format({ fuel, ammo, capturePoints }) { + // Using emoji for quick visual recognition + return `Fuel: ${fuel} | Ammo: ${ammo} | CP: ${capturePoints}`; + } + + // -- Lifecycle -------------------------------------------------------- + + /** + * Clean up the Phaser text object. + */ + destroy() { + if (this._text && this._text.active) { + this._text.destroy(); + } + this._text = null; + this._economy = null; + } +} diff --git a/src/systems/SelectionSystem.js b/src/systems/SelectionSystem.js index 802f97c..3fbfb1f 100644 --- a/src/systems/SelectionSystem.js +++ b/src/systems/SelectionSystem.js @@ -241,9 +241,9 @@ export default class SelectionSystem { y: tile.y + (positions[i]?.y ?? 0), }; - const startTile = pathfinding.worldToTileCoords(entity.x, entity.y); + const startTile = pathfinding.worldToTile(entity.x, entity.y); - pathfinding.findPath(startTile, offsetTile, {}, (path) => { + pathfinding.findPath(startTile.x, startTile.y, offsetTile.x, offsetTile.y).then((path) => { if (path === null) { console.warn(`[SelectionSystem] No path found for entity`); return; diff --git a/src/systems/SystemOrchestrator.js b/src/systems/SystemOrchestrator.js index 949c68e..248f21a 100644 --- a/src/systems/SystemOrchestrator.js +++ b/src/systems/SystemOrchestrator.js @@ -8,9 +8,11 @@ import MapSystem from './MapSystem.js'; import EntityStateMachine from './EntityStateMachine.js'; import BuildingStateMachine from './BuildingStateMachine.js'; import ControlPointStateMachine from './ControlPointStateMachine.js'; +import ControlPointManager from './ControlPointManager.js'; +import WinCondition from './WinCondition.js'; /** - * SystemOrchestrator — wires all 9 systems together. + * SystemOrchestrator — wires all 9+ systems together. * * Responsibilities: * 1. Initialize all service-level systems (singleton per scene) @@ -24,7 +26,7 @@ import ControlPointStateMachine from './ControlPointStateMachine.js'; * - Uses Phaser.EventEmitter on the scene for cross-system communication * * Update order (per tech plan): - * selection → economy → controlPoints → buildings → entities → pathfinding → combat → network + * selection → economy → controlPoints → buildings → entities → pathfinding → combat → winCondition → network */ export default class SystemOrchestrator { /** @@ -66,6 +68,7 @@ export default class SystemOrchestrator { 'entities', 'pathfinding', 'combat', + 'winCondition', 'network', ]; @@ -108,13 +111,35 @@ export default class SystemOrchestrator { // 2. EconomySystem — resource tracking, purchase validation, income ticks this.systems.economy = new EconomySystem(this.scene); - // 3. CombatSystem — target acquisition, LoS, projectiles, damage - this.systems.combat = new CombatSystem(this.scene); + // 3. ControlPointManager — 4 CP instances tied to EconomySystem income + if (this.systems.map && this.systems.map.tilemap) { + this.systems.controlPointManager = new ControlPointManager( + this.scene, + this.systems.map.tilemap, + this.systems.economy, + this.scene.teamManager ?? undefined, + ); + } else { + // Defer if map hasn't loaded yet — initControlPoints() can be called later + console.warn( + '[SystemOrchestrator] MapSystem has no tilemap yet; call initControlPoints() after map load.', + ); + } - // 4. SelectionSystem — drag-select, command queue, formations + // 4. WinCondition — victory threshold monitoring + this.systems.winCondition = new WinCondition( + this.scene, + this.systems.economy, + { threshold: this.config.victoryThreshold ?? 100 }, + ); + + // 4. CombatSystem — target acquisition, LoS, projectiles, damage + this.systems.combat = new CombatSystem(this.scene, this.scene.teamManager ?? undefined); + + // 5. SelectionSystem — drag-select, command queue, formations this.systems.selection = new SelectionSystem(this.scene); - // 5. NetworkSystem — client-side state sync, prediction, interpolation + // 6. NetworkSystem — client-side state sync, prediction, interpolation if (this.config.room) { this.systems.network = new NetworkSystemClient( this.scene, @@ -137,6 +162,37 @@ export default class SystemOrchestrator { return this; } + /** + * Initialize the ControlPointManager after the MapSystem has a tilemap. + * Call this if init() ran before tilemap was available. + * + * @returns {ControlPointManager|null} + */ + initControlPoints() { + if (this.systems.controlPointManager) { + console.warn( + '[SystemOrchestrator] ControlPointManager already initialized — skipping.', + ); + return this.systems.controlPointManager; + } + + if (!this.systems.map || !this.systems.map.tilemap) { + console.warn( + '[SystemOrchestrator] Cannot init ControlPointManager — no tilemap yet.', + ); + return null; + } + + this.systems.controlPointManager = new ControlPointManager( + this.scene, + this.systems.map.tilemap, + this.systems.economy, + this.scene.teamManager ?? undefined, + ); + + return this.systems.controlPointManager; + } + /** * Initialize the PathfindingSystem (deferred until MapSystem has a tilemap). * Call this after the MapSystem has loaded a map and has a valid tilemap. @@ -392,22 +448,28 @@ export default class SystemOrchestrator { } break; - // 3. ControlPointStateMachine[] — capture progress update + // 3. ControlPointManager — delegates tick to all CPs, updates HUD case 'controlPoints': - for (let i = this.controlPointStateMachines.length - 1; i >= 0; i--) { - const cpsm = this.controlPointStateMachines[i]; - if (cpsm.tick) { - cpsm.tick(time, delta, this.scene); + if (this.systems.controlPointManager) { + this.systems.controlPointManager.update(time, delta); + // Tick capture-progress HUD for each CP the manager owns + if (this.scene.captureUI?.update && this.systems.controlPointManager.controlPoints) { + for (const cpsm of this.systems.controlPointManager.controlPoints) { + this.scene.captureUI.update(cpsm); + } } } break; - // 4. BuildingStateMachine[] — production queue advance + // 4. BuildingStateMachine[] -- production queue advance + passive income case 'buildings': for (let i = this.buildingStateMachines.length - 1; i >= 0; i--) { const bsm = this.buildingStateMachines[i]; if (bsm.tick) { - bsm.tick(time, delta); + const income = bsm.tick(time, delta); + if (income && bsm.playerId && this.systems.economy) { + this.systems.economy.addIncome(bsm.playerId, income); + } } } break; @@ -436,7 +498,14 @@ export default class SystemOrchestrator { } break; - // 8. NetworkSystem — snapshot broadcast (client/server sync) + // 8. WinCondition — check victory threshold + case 'winCondition': + if (this.systems.winCondition?.update) { + this.systems.winCondition.update(time); + } + break; + + // 9. NetworkSystem — snapshot broadcast (client/server sync) case 'network': if (this.systems.network?.update) { this.systems.network.update(time, delta); diff --git a/src/systems/TeamManager.js b/src/systems/TeamManager.js new file mode 100644 index 0000000..8caea0a --- /dev/null +++ b/src/systems/TeamManager.js @@ -0,0 +1,219 @@ +/** + * TeamManager — centralized team registry for multi-team (FFA) support. + * + * Replaces hard-coded goodGuys / badGuys Phaser Containers. Every unit, + * building, and player is tagged with a teamId. Combat and UI query + * this manager instead of checking container names. + */ + +export class Team { + /** + * @param {string} id + * @param {number} color — hex colour (e.g. 0x1d7196) + * @param {string} name — display name (e.g. 'Alpha') + */ + constructor(id, color, name) { + this.id = id; + this.color = color; + this.name = name; + this.players = new Set(); + this.units = new Set(); + this.buildings = new Set(); + } +} + +export default class TeamManager { + /** + * @param {Phaser.Scene} scene + */ + constructor(scene) { + this.scene = scene; + + /** @type {Map} */ + this._teams = new Map(); + + /** @type {Map} playerId → teamId */ + this._playerToTeam = new Map(); + } + + // ── Team lifecycle ───────────────────────────────────────────── + + /** + * Create a team if it doesn't already exist. + * Duplicate calls return the existing Team object. + */ + createTeam(teamId, color, name) { + if (this._teams.has(teamId)) { + return this._teams.get(teamId); + } + const team = new Team(teamId, color, name); + this._teams.set(teamId, team); + return team; + } + + /** @returns {Team | undefined} */ + getTeam(teamId) { + return this._teams.get(teamId); + } + + /** @returns {Map} */ + getTeams() { + return this._teams; + } + + /** @returns {number} */ + getTeamCount() { + return this._teams.size; + } + + // ── Player mapping ──────────────────────────────────────────── + + setPlayerTeam(playerId, teamId) { + this._playerToTeam.set(playerId, teamId); + const team = this._teams.get(teamId); + if (team) { + team.players.add(playerId); + } + } + + /** @returns {string | undefined} */ + getPlayerTeam(playerId) { + return this._playerToTeam.get(playerId); + } + + /** @returns {Set} */ + getTeamPlayers(teamId) { + const team = this._teams.get(teamId); + return team ? team.players : new Set(); + } + + // ── Unit ownership ──────────────────────────────────────────── + + addUnit(unit, teamId) { + const team = this._teams.get(teamId); + if (!team) return; + unit.setData('teamId', teamId); + team.units.add(unit); + } + + removeUnit(unit, teamId) { + const team = this._teams.get(teamId); + if (!team) return; + unit.setData('teamId', null); + team.units.delete(unit); + } + + /** @returns {string | null} */ + getUnitTeam(unit) { + return unit.getData('teamId') ?? null; + } + + /** @returns {Set<*>} */ + getTeamUnits(teamId) { + const team = this._teams.get(teamId); + return team ? team.units : new Set(); + } + + /** @returns {Array<*>} */ + getAllUnits() { + const all = []; + for (const team of this._teams.values()) { + for (const unit of team.units) { + all.push(unit); + } + } + return all; + } + + /** @returns {Map>} */ + getAllUnitsGrouped() { + const result = new Map(); + for (const [teamId, team] of this._teams) { + result.set(teamId, team.units); + } + return result; + } + + // ── Building ownership ────────────────────────────────────── + + addBuilding(building, teamId) { + const team = this._teams.get(teamId); + if (!team) return; + building.setData('teamId', teamId); + team.buildings.add(building); + } + + removeBuilding(building, teamId) { + const team = this._teams.get(teamId); + if (!team) return; + building.setData('teamId', null); + team.buildings.delete(building); + } + + /** @returns {string | null} */ + getBuildingTeam(building) { + return building.getData('teamId') ?? null; + } + + /** @returns {Set<*>} */ + getTeamBuildings(teamId) { + const team = this._teams.get(teamId); + return team ? team.buildings : new Set(); + } + + // ── Queries ──────────────────────────────────────────────────── + + /** @returns {boolean} */ + isEnemy(entityA, entityB) { + const teamA = entityA.getData('teamId'); + const teamB = entityB.getData('teamId'); + if (teamA == null || teamB == null) return false; + return teamA !== teamB; + } + + /** @returns {boolean} */ + isSameTeam(entityA, entityB) { + const teamA = entityA.getData('teamId'); + const teamB = entityB.getData('teamId'); + if (teamA == null || teamB == null) return false; + return teamA === teamB; + } + + /** @returns {Set<*>} */ + getEnemyUnits(teamId) { + const enemies = new Set(); + for (const [id, team] of this._teams) { + if (id === teamId) continue; + for (const unit of team.units) { + enemies.add(unit); + } + } + return enemies; + } + + /** @returns {string | null} */ + getEntityTeam(entity) { + return entity.getData('teamId') ?? null; + } + + // ── Team info ────────────────────────────────────────────────── + + /** @returns {number | undefined} */ + getTeamColor(teamId) { + const team = this._teams.get(teamId); + return team ? team.color : undefined; + } + + /** @returns {string | undefined} */ + getTeamName(teamId) { + const team = this._teams.get(teamId); + return team ? team.name : undefined; + } + + // ── Cleanup ──────────────────────────────────────────────────── + + destroy() { + this._teams.clear(); + this._playerToTeam.clear(); + } +} diff --git a/src/systems/UnitFactory.js b/src/systems/UnitFactory.js index 33655cf..1dd8457 100644 --- a/src/systems/UnitFactory.js +++ b/src/systems/UnitFactory.js @@ -4,29 +4,24 @@ import Ukrainian_Tank from "Entities/skins/ukrainian-tank"; import Russian_Tank from "Entities/skins/russian-tank"; export default class UnitFactory { - constructor(scene) { + constructor(scene, teamManager) { this.scene = scene; + this.teamManager = teamManager; } - spawnInfantry(tile, team = "player") { - const Skin = team === "player" ? Ukrainian_Rifle : Russian_Rifle; + spawnInfantry(tile, teamId) { + const skinIndex = Array.from(this.teamManager.getTeams().keys()).indexOf(teamId); + const Skin = skinIndex === 0 ? Ukrainian_Rifle : Russian_Rifle; const unit = new Skin(this.scene, tile); - if (team === "player") { - this.scene.goodGuys.add(unit); - } else { - this.scene.badGuys.add(unit); - } + this.teamManager.addUnit(unit, teamId); return unit; } - spawnTank(tile, team = "player") { - const Skin = team === "player" ? Ukrainian_Tank : Russian_Tank; + spawnTank(tile, teamId) { + const skinIndex = Array.from(this.teamManager.getTeams().keys()).indexOf(teamId); + const Skin = skinIndex === 0 ? Ukrainian_Tank : Russian_Tank; const unit = new Skin(this.scene, tile); - if (team === "player") { - this.scene.goodGuys.add(unit); - } else { - this.scene.badGuys.add(unit); - } + this.teamManager.addUnit(unit, teamId); return unit; } } diff --git a/src/systems/WinCondition.js b/src/systems/WinCondition.js new file mode 100644 index 0000000..e10ade2 --- /dev/null +++ b/src/systems/WinCondition.js @@ -0,0 +1,154 @@ +/** + * WinCondition — monitors EconomySystem for victory threshold. + * + * Responsibilities: + * - Check every economy tick if any player >= victory threshold CP + * - Emit 'game:victory' exactly once + * - Track stats: units killed, buildings built, CP captured, elapsed time + * + * Events consumed: + * - economy:incomeReceived → CP tracking + * - combat:unitDamaged → kill tracking + * - building:spawned → building tracking + * + * Events emitted: + * - game:victory → { winnerPlayerId, stats } + */ +export default class WinCondition { + /** + * @param {Phaser.Scene} scene + * @param {EconomySystem} economySystem + * @param {Object} [options] + * @param {number} [options.threshold=100] — CP required to win + */ + constructor(scene, economySystem, options = {}) { + this.scene = scene; + this.economy = economySystem; + this.victoryThreshold = options.threshold ?? 100; + + /** @type {boolean} Victory already triggered? */ + this.victoryEmitted = false; + + /** @type {number} Game start timestamp (ms) */ + this.stats = { + gameStartTime: Date.now(), + unitsKilled: 0, + buildingsBuilt: 0, + cpCaptured: 0, + }; + + // Bind event listeners so we can remove them later + this._onUnitDamaged = this._onUnitDamaged.bind(this); + this._onIncomeReceived = this._onIncomeReceived.bind(this); + this._onBuildingSpawned = this._onBuildingSpawned.bind(this); + + // Wire listeners + if (this.economy?.events) { + this.economy.events.on('combat:unitDamaged', this._onUnitDamaged); + this.economy.events.on('economy:incomeReceived', this._onIncomeReceived); + } + if (this.scene?.events) { + this.scene.events.on('building:spawned', this._onBuildingSpawned); + } + } + + // ────────────────────────────────────────────── + // EVENT HANDLERS + // ────────────────────────────────────────────── + + /** + * Increment kill count when a damaged unit dies. + * @param {{target: Object, damage: number}} data + */ + _onUnitDamaged(data) { + const target = data.target; + if (!target) return; + // Count if unit is dead (either .dead flag or health <= 0) + if (target.dead || target.isDead?.()) { + this.stats.unitsKilled += 1; + return; + } + // Fallback: if newHealth is passed explicitly, check it + if (data.newHealth != null && data.newHealth <= 0) { + this.stats.unitsKilled += 1; + } + } + + /** + * Increment total CP captured from income events. + * @param {{income: {capturePoints?: number}}} data + */ + _onIncomeReceived(data) { + const cp = data?.income?.capturePoints; + if (cp != null && cp > 0) { + this.stats.cpCaptured += cp; + } + } + + /** + * Increment building count. + * @param {Object} building + */ + _onBuildingSpawned() { + this.stats.buildingsBuilt += 1; + } + + // ────────────────────────────────────────────── + // UPDATE + // ────────────────────────────────────────────── + + /** + * Check victory condition. + * @param {number} time — scene elapsed time in ms + */ + update(time) { + if (this.victoryEmitted) return; + if (!this.economy?.players) return; + + for (const [playerId, resources] of this.economy.players) { + if ((resources.capturePoints ?? 0) >= this.victoryThreshold) { + this._triggerVictory(playerId, time); + return; + } + } + } + + // ────────────────────────────────────────────── + // VICTORY + // ────────────────────────────────────────────── + + /** + * Emit game:victory once and lock out future checks. + * @param {string} winnerPlayerId + * @param {number} time — scene elapsed time in ms + */ + _triggerVictory(winnerPlayerId, time) { + this.victoryEmitted = true; + + const elapsedMs = time; // scene time starts at 0 on create + + this.scene.events.emit('game:victory', { + winnerPlayerId, + stats: { + unitsKilled: this.stats.unitsKilled, + buildingsBuilt: this.stats.buildingsBuilt, + cpCaptured: this.stats.cpCaptured, + elapsedMs, + }, + }); + } + + // ────────────────────────────────────────────── + // CLEANUP + // ────────────────────────────────────────────── + + destroy() { + if (this.economy?.events) { + this.economy.events.off('combat:unitDamaged', this._onUnitDamaged); + this.economy.events.off('economy:incomeReceived', this._onIncomeReceived); + } + if (this.scene?.events) { + this.scene.events.off('building:spawned', this._onBuildingSpawned); + } + } +} diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 0000000..cbcc1fb --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "passed", + "failedTests": [] +} \ No newline at end of file diff --git a/test/entities/Unit.test.js b/test/entities/Unit.test.js new file mode 100644 index 0000000..13d0819 --- /dev/null +++ b/test/entities/Unit.test.js @@ -0,0 +1,152 @@ +/** + * Unit Entity tests — TeamManager-aware methods. + * Uses Jest globals (no import from 'vitest'). + */ + +jest.mock('Systems/EntityStateMachine', () => ({ + forEntity: jest.fn(() => ({ + tick: jest.fn(), + send: jest.fn(), + destroy: jest.fn(), + getState: jest.fn(() => 'IDLING'), + })), +})); + +import Unit from 'Entities/Unit'; +import EntityStateMachine from 'Systems/EntityStateMachine'; + +// ── Helpers ──────────────────────────────────────────────────── + +function createMockTeamManager() { + const unitsByTeam = new Map(); + return { + createTeam: jest.fn((id, color, name) => ({ id, color, name, units: new Set() })), + getTeamColor: jest.fn((teamId) => { + if (teamId === 'team-A') return 0x1d7196; + if (teamId === 'team-B') return 0xd94f4f; + return undefined; + }), + getEnemyUnits: jest.fn((teamId) => { + const enemies = new Set(); + for (const [id, units] of unitsByTeam) { + if (id !== teamId) { + for (const u of units) enemies.add(u); + } + } + return enemies; + }), + getTeamUnits: jest.fn((teamId) => unitsByTeam.get(teamId) || new Set()), + getAllUnits: jest.fn(() => { + const all = []; + for (const units of unitsByTeam.values()) { + for (const u of units) all.push(u); + } + return all; + }), + getUnitTeam: jest.fn((unit) => unit.getData('teamId') ?? null), + isEnemy: jest.fn((a, b) => { + const ta = a.getData('teamId'); + const tb = b.getData('teamId'); + if (ta == null || tb == null) return false; + return ta !== tb; + }), + isSameTeam: jest.fn((a, b) => { + const ta = a.getData('teamId'); + const tb = b.getData('teamId'); + if (ta == null || tb == null) return false; + return ta === tb; + }), + addUnit: jest.fn((unit, teamId) => { + unit.setData('teamId', teamId); + if (!unitsByTeam.has(teamId)) unitsByTeam.set(teamId, new Set()); + unitsByTeam.get(teamId).add(unit); + }), + }; +} + +function createMockScene() { + const teamManager = createMockTeamManager(); + return { + add: { existing: jest.fn() }, + physics: { world: { enableBody: jest.fn() } }, + interface: { generateWorldXY: jest.fn(tile => ({ x: tile.x * 64, y: tile.y * 64 })) }, + teamManager, + orchestrator: { + systems: { + EntityStateMachine: { + forEntity: jest.fn(() => ({ + tick: jest.fn(), + send: jest.fn(), + destroy: jest.fn(), + getState: jest.fn(() => 'IDLING'), + })), + }, + combat: { fireProjectile: jest.fn() }, + pathfinding: { findPath: jest.fn() }, + selection: { add: jest.fn() }, + }, + }, + events: { emit: jest.fn() }, + tweens: { + addCounter: jest.fn(config => { + const tween = { getValue: () => 200, stop: jest.fn() }; + if (config.onUpdate) config.onUpdate(tween); + return tween; + }), + }, + }; +} + +// ── Tests ────────────────────────────────────────────────────── + +describe('Unit (team-aware)', () => { + let scene; + let unit; + + beforeEach(() => { + jest.clearAllMocks(); + scene = createMockScene(); + unit = new Unit(scene, 'tank_texture', { x: 0, y: 0 }, { + maxHp: 100, + armor: 5, + playerId: 'player1', + team: 'good', + }); + }); + + test('getEnemyContainer removed — no longer exists on prototype', () => { + expect(typeof Unit.prototype.getEnemyContainer).toBe('undefined'); + }); + + test('getFriendlyContainer removed — no longer exists on prototype', () => { + expect(typeof Unit.prototype.getFriendlyContainer).toBe('undefined'); + }); + + test('select() uses teamId for tint color via TeamManager', () => { + scene.teamManager.addUnit(unit, 'team-A'); + unit.select(); + + expect(scene.teamManager.getTeamColor).toHaveBeenCalledWith('team-A'); + expect(unit.setTint).toHaveBeenCalled(); + }); + + test('isEnemyOf delegates to TeamManager', () => { + const otherUnit = new Unit(scene, 'tank_texture', { x: 1, y: 0 }, { + maxHp: 100, armor: 5, playerId: 'player2', team: 'enemy', + }); + scene.teamManager.addUnit(unit, 'team-A'); + scene.teamManager.addUnit(otherUnit, 'team-B'); + + const result = unit.isEnemyOf(otherUnit); + + expect(scene.teamManager.isEnemy).toHaveBeenCalledWith(unit, otherUnit); + expect(result).toBe(true); + }); + + test('Unit without teamId has null team data', () => { + const orphan = new Unit(scene, 'tank_texture', { x: 0, y: 0 }, { + maxHp: 100, armor: 5, + }); + expect(orphan.getData('teamId')).toBeNull(); + }); +}); diff --git a/test/entities/components/OwnerComponent.test.js b/test/entities/components/OwnerComponent.test.js new file mode 100644 index 0000000..5f65823 --- /dev/null +++ b/test/entities/components/OwnerComponent.test.js @@ -0,0 +1,78 @@ +/** + * OwnerComponent tests — TeamManager-aware delegation. + * Uses Jest globals (no import from 'vitest'). + */ + +import OwnerComponent from 'Entities/components/OwnerComponent'; + +function createMockUnit(teamId) { + const data = {}; + return { + setData: jest.fn((k, v) => { data[k] = v; }), + getData: jest.fn((k) => data[k] ?? null), + scene: { + teamManager: { + isEnemy: jest.fn((unitA, unitB) => { + const ta = unitA.getData('teamId'); + const tb = unitB.getData('teamId'); + if (ta == null || tb == null) return false; + return ta !== tb; + }), + isSameTeam: jest.fn((unitA, unitB) => { + const ta = unitA.getData('teamId'); + const tb = unitB.getData('teamId'); + if (ta == null || tb == null) return false; + return ta === tb; + }), + getTeamColor: jest.fn((id) => { + if (id === 'team-A') return 0x1d7196; + if (id === 'team-B') return 0xd94f4f; + return undefined; + }), + }, + }, + }; +} + +describe('OwnerComponent (team-aware)', () => { + test('teamId replaces good/enemy string', () => { + const unit = createMockUnit(); + const comp = new OwnerComponent(unit, { teamId: 'team-A' }); + + // team getter should now return teamId, not 'good'/'enemy' + expect(comp.teamId).toBe('team-A'); + expect(comp.team).toBeUndefined(); + }); + + test('isEnemy delegates to TeamManager', () => { + const unitA = createMockUnit('team-A'); + const unitAComp = new OwnerComponent(unitA, { teamId: 'team-A' }); + + const unitB = createMockUnit('team-B'); + const unitBComp = new OwnerComponent(unitB, { teamId: 'team-B' }); + + unitA.setData('teamId', 'team-A'); + unitB.setData('teamId', 'team-B'); + + const result = unitAComp.isEnemy(unitBComp); + + expect(unitA.scene.teamManager.isEnemy).toHaveBeenCalledWith(unitA, unitB); + expect(result).toBe(true); + }); + + test('isSameTeam delegates to TeamManager', () => { + const unitA = createMockUnit('team-A'); + const unitAComp = new OwnerComponent(unitA, { teamId: 'team-A' }); + + const unitB = createMockUnit('team-A'); + const unitBComp = new OwnerComponent(unitB, { teamId: 'team-A' }); + + unitA.setData('teamId', 'team-A'); + unitB.setData('teamId', 'team-A'); + + const result = unitAComp.isSameTeam(unitBComp); + + expect(unitA.scene.teamManager.isSameTeam).toHaveBeenCalledWith(unitA, unitB); + expect(result).toBe(true); + }); +}); diff --git a/test/scenes/Map_Player.test.js b/test/scenes/Map_Player.test.js new file mode 100644 index 0000000..e4d8b96 --- /dev/null +++ b/test/scenes/Map_Player.test.js @@ -0,0 +1,514 @@ +/** + * Map_Player TeamManager rewiring — T6: Map_Player E2E (updated) + * + * Tests: + * 1. goodGuys / badGuys Phaser containers are NOT created + * 2. TeamManager is created with team-A and team-B + * 3. UnitFactory receives teamManager in constructor + * 4. spawnInfantry calls use teamId strings ('team-A', 'team-B') + * 5. Camera centers on team-A unit instead of goodGuys.list[0] + */ + +// ── Module-level mocks (before any imports) ─────────────────────── + +jest.mock('PhaserClasses/CustomConstants', () => ({ + __esModule: true, + default: { TINTS: { RED: 0xff0000, BLUE: 0x0000ff, GREEN: 0x00ff00 } }, +})); + +jest.mock('Systems/SystemOrchestrator.js', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(() => ({ + init: jest.fn(), + initPathfinding: jest.fn(), + initControlPoints: jest.fn(), + update: jest.fn(), + shutdown: jest.fn(), + registerBuilding: jest.fn((building, config) => ({ + building, + config, + getState: jest.fn(() => 'ACTIVE'), + destroy: jest.fn(), + })), + unregisterBuilding: jest.fn(), + systems: { + selection: null, + pathfinding: null, + combat: { registerUnitContainers: jest.fn() }, + economy: { + initPlayer: jest.fn(), + events: { + on: jest.fn(), + }, + }, + network: null, + EntityStateMachine: {}, + BuildingStateMachine: {}, + ControlPointStateMachine: {}, + }, + })), +})); + +jest.mock('Systems/NetworkSystem.js', () => ({ + NetworkSystemClient: jest.fn(), +})); + +jest.mock('Systems/UnitFactory', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(function(scene, teamManager) { + this.scene = scene; + this.teamManager = teamManager; + this.spawnInfantry = jest.fn(); + this.spawnTank = jest.fn(); + }), +})); + +jest.mock('Systems/TeamManager.js', () => ({ + __esModule: true, + Team: jest.fn().mockImplementation(function(id, color, name) { + this.id = id; this.color = color; this.name = name; + this.players = new Set(); + this.units = new Set(); + this.buildings = new Set(); + }), + default: jest.fn().mockImplementation(function(scene) { + this.scene = scene; + this._teams = new Map(); + this._playerToTeam = new Map(); + this.createTeam = jest.fn((teamId, color, name) => { + const team = { id: teamId, color, name, players: new Set(), units: new Set(), buildings: new Set() }; + this._teams.set(teamId, team); + return team; + }); + this.getTeam = jest.fn((teamId) => this._teams.get(teamId)); + this.getTeams = jest.fn(() => this._teams); + this.getTeamCount = jest.fn(() => this._teams.size); + this.setPlayerTeam = jest.fn(); + this.getPlayerTeam = jest.fn(); + this.getTeamPlayers = jest.fn(() => new Set()); + this.addUnit = jest.fn(); + this.removeUnit = jest.fn(); + this.getUnitTeam = jest.fn(); + this.getTeamUnits = jest.fn(() => new Set()); + this.getAllUnits = jest.fn(() => [{ x: 2048, y: 2048 }]); + this.getAllUnitsGrouped = jest.fn(() => new Map()); + this.addBuilding = jest.fn(); + this.removeBuilding = jest.fn(); + this.getBuildingTeam = jest.fn(); + this.getTeamBuildings = jest.fn(() => new Set()); + this.isEnemy = jest.fn(() => false); + this.isSameTeam = jest.fn(() => false); + this.getEnemyUnits = jest.fn(() => new Set()); + this.getEntityTeam = jest.fn(); + this.getTeamColor = jest.fn(() => 0xffffff); + this.getTeamName = jest.fn(() => ''); + this.destroy = jest.fn(); + }), +})); + +// Mock entity skins to avoid real Phaser sprite construction +const createMockSkin = (scene, tile) => ({ + scene, + tile, + x: (tile?.x ?? 0) * 32 || 100, + y: (tile?.y ?? 0) * 32 || 100, + name: 'test-unit', + body: null, + setScale: jest.fn().mockReturnThis(), + setData: jest.fn().mockReturnThis(), + setName: jest.fn().mockReturnThis(), + select: jest.fn(), + unSelect: jest.fn(), + destroy: jest.fn(), +}); + +jest.mock('Entities/skins/ukrainian-infantry', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(createMockSkin), +})); + +jest.mock('Entities/skins/russian-infantry', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(createMockSkin), +})); + +jest.mock('Entities/skins/russian-tank', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(createMockSkin), +})); + +// ── Imports (after mocks) ───────────────────────────────────────── + +import Map_Player from 'Scenes/Map_Player'; +import TeamManager from 'Systems/TeamManager.js'; +import UnitFactory from 'Systems/UnitFactory'; + +// ── Helpers ─────────────────────────────────────────────────────── + +function buildMockScene() { + const keyboardHandlers = {}; + const inputOnHandlers = {}; + const _children = []; + + const mockScene = { + // Phaser.Scene basics + key: 'Map_Player', + scene: { key: 'Map_Player' }, + game: { colyseus: null }, + + // Camera + cameras: { + main: { + setBounds: jest.fn(), + zoomTo: jest.fn(), + centerOn: jest.fn(), + get zoom() { return 1; }, + }, + }, + + // Input + input: { + setDefaultCursor: jest.fn(), + keyboard: { + addKey: jest.fn(() => ({ isDown: false })), + on: jest.fn((event, cb) => { + keyboardHandlers[event] = cb; + }), + _handlers: keyboardHandlers, + _fire: (event) => { + if (keyboardHandlers[event]) keyboardHandlers[event](); + }, + }, + on: jest.fn((event, cb) => { + inputOnHandlers[event] = cb; + }), + _handlers: inputOnHandlers, + _fire: (event, ...args) => { + if (inputOnHandlers[event]) inputOnHandlers[event](...args); + }, + }, + + // Add game objects + add: { + container: jest.fn(() => ({ + setName: jest.fn().mockReturnThis(), + add: jest.fn(), + setScrollFactor: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setPosition: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + list: [], + })), + rectangle: jest.fn(() => ({ + setStrokeStyle: jest.fn().mockReturnThis(), + setOrigin: jest.fn().mockReturnThis(), + setPosition: jest.fn().mockReturnThis(), + setScrollFactor: jest.fn().mockReturnThis(), + setInteractive: jest.fn().mockReturnThis(), + setFillStyle: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + on: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + })), + graphics: jest.fn(() => ({ + setDepth: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + clear: jest.fn(), + fillStyle: jest.fn(), + fillRect: jest.fn(), + lineStyle: jest.fn(), + strokeRect: jest.fn(), + get active() { return true; }, + })), + text: jest.fn(() => ({ + setScrollFactor: jest.fn().mockReturnThis(), + setOrigin: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setText: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + setPosition: jest.fn().mockReturnThis(), + destroy: jest.fn(), + get active() { return true; }, + })), + sprite: jest.fn(() => ({ + setScale: jest.fn().mockReturnThis(), + setPosition: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + play: jest.fn(), + setOrigin: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + setScrollFactor: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setDisplaySize: jest.fn().mockReturnThis(), + setTint: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + })), + existing: jest.fn(), + }, + + // Physics + physics: { + world: { enableBody: jest.fn() }, + overlapRect: jest.fn(() => []), + add: { + existing: jest.fn(), + }, + }, + + // Events + events: { + on: jest.fn(), + emit: jest.fn(), + off: jest.fn(), + }, + + // Tweens + tweens: { + addCounter: jest.fn((config) => { + const tween = { getValue: () => 200, stop: jest.fn() }; + if (config.onUpdate) config.onUpdate(tween); + return tween; + }), + }, + + // Animations + anims: { + create: jest.fn(), + generateFrameNumbers: jest.fn(() => []), + }, + + // Make (tilemap factory) + make: { + tilemap: jest.fn(() => ({ + addTilesetImage: jest.fn(() => ({})), + createLayer: jest.fn(() => ({ + setCollisionByProperty: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + getTileAtWorldXY: jest.fn(() => ({ x: 5, y: 5, index: 0, z: 0 })), + getTilesWithinShape: jest.fn(() => [{ x: 0, y: 0 }]), + tileToWorldXY: jest.fn((x, y) => ({ x: x * 32, y: y * 32 })), + worldToTileXY: jest.fn(() => ({ x: 0, y: 0 })), + })), + widthInPixels: 640, + heightInPixels: 480, + worldToTileXY: jest.fn(() => ({ x: 0, y: 0 })), + tileToWorldXY: jest.fn((x, y) => ({ x: x * 32, y: y * 32 })), + })), + }, + + map: null, + groundLayer: null, + rockLayer: null, + goodGuys: null, + badGuys: null, + interface: null, + orchestrator: { + registerBuilding: jest.fn((building, config) => ({ + building, + config, + getState: jest.fn(() => 'ACTIVE'), + destroy: jest.fn(), + })), + unregisterBuilding: jest.fn(), + systems: {}, + }, + tints: {}, + }; + + return mockScene; +} + +// ── Tests ───────────────────────────────────────────────────────── + +describe('Map_Player — TeamManager rewiring', () => { + let scene; + let mapPlayer; + + beforeEach(() => { + jest.clearAllMocks(); + scene = buildMockScene(); + + // Instantiate Map_Player with our mock scene + mapPlayer = new Map_Player(); + Object.assign(mapPlayer, { + add: scene.add, + input: scene.input, + cameras: scene.cameras, + make: scene.make, + physics: scene.physics, + events: scene.events, + tweens: scene.tweens, + anims: scene.anims, + game: scene.game, + }); + + // Stub createMap to set the layers + mapPlayer.createMap = jest.fn(function () { + this.map = this.make.tilemap({ key: 'test1' }); + this.groundLayer = this.map.createLayer('Floor', {}, 0, 0); + this.rockLayer = this.map.createLayer('Rocks', {}, 0, 0).setCollisionByProperty({ collides: true }).setDepth(10); + }); + + // Stub createInfantry — we test factory calls instead + mapPlayer.createInfantry = jest.fn(); + mapPlayer.createFriendlyPlatoon = jest.fn(); + mapPlayer.createFriendlyInfantry = jest.fn(); + }); + + test('does NOT create goodGuys / badGuys Phaser containers', () => { + mapPlayer.create(); + + // Should never call add.container for old Good Guys / Bad Guys + const containerNames = scene.add.container.mock.results.map((r) => r.value?.name); + const containerAddCalls = scene.add.container.mock.calls; + + // With the rewired code, add.container should only happen in mocked subsystems + // (Interface, BuildMenu, BuildingPlacer, etc.) — NOT Map_Player.create() + // specifically for unit buckets. + // Best check: scene should NOT have goodGuys / badGuys after create() + expect(mapPlayer.goodGuys).toBeUndefined(); + expect(mapPlayer.badGuys).toBeUndefined(); + }); + + test('creates TeamManager with three teams for FFA', () => { + mapPlayer.create(); + + expect(TeamManager).toHaveBeenCalledWith(mapPlayer); + expect(mapPlayer.teamManager).toBeDefined(); + expect(mapPlayer.teamManager.createTeam).toHaveBeenCalledWith('team-A', 0x1d7196, 'Alpha'); + expect(mapPlayer.teamManager.createTeam).toHaveBeenCalledWith('team-B', 0xd94f4f, 'Bravo'); + expect(mapPlayer.teamManager.createTeam).toHaveBeenCalledWith('team-C', 0x4fd94f, 'Charlie'); + expect(mapPlayer.teamManager.setPlayerTeam).toHaveBeenCalledWith('Player', 'team-A'); + }); + + test('UnitFactory receives teamManager as second arg', () => { + const UnitFactoryMocked = require('Systems/UnitFactory').default; + mapPlayer.create(); + + // UnitFactory should have been called with (scene, teamManager) + const calls = UnitFactoryMocked.mock.calls; + expect(calls.length).toBeGreaterThanOrEqual(1); + const lastCall = calls[calls.length - 1]; + expect(lastCall[0]).toBe(mapPlayer); + expect(lastCall[1]).toBe(mapPlayer.teamManager); + }); + + test('spawn calls use teamId strings (team-A, team-B)', () => { + // We need a real spy on UnitFactory.spawnInfantry. + // Since the mock UnitFactory in Map_Player.create() is local, + // inspect the object mapPlayer gets. + mapPlayer.create(); + + const factory = mapPlayer.unitFactory; + expect(factory).toBeDefined(); + + // The create() method spawns two infantry after createInfantry: + // spawnInfantry(testTile, 'team-A') + // spawnInfantry({ x: testTile.x + 2, y: testTile.y }, 'team-A') + expect(factory.spawnInfantry).toHaveBeenCalledWith( + expect.objectContaining({ x: expect.any(Number), y: expect.any(Number) }), + 'team-A', + ); + expect(factory.spawnInfantry).toHaveBeenCalledTimes(2); + }); + + test('3-team spawn: each team gets different color', () => { + mapPlayer.create(); + const tm = mapPlayer.teamManager; + expect(tm.createTeam).toHaveBeenCalledWith('team-A', 0x1d7196, 'Alpha'); + expect(tm.createTeam).toHaveBeenCalledWith('team-B', 0xd94f4f, 'Bravo'); + expect(tm.createTeam).toHaveBeenCalledWith('team-C', 0x4fd94f, 'Charlie'); + }); + + test('ProductionPanel onProductionComplete passes teamId to UnitFactory', () => { + mapPlayer.create(); + // Simulate a building with a playerId and a production complete event + const bsm = { + playerId: 'Player', + building: { x: 100, y: 100 }, + productionQueue: [{ unitType: 'infantry', startTime: 0 }], + }; + mapPlayer.teamManager.getPlayerTeam = jest.fn(() => 'team-A'); + mapPlayer.groundLayer = { getTileAtWorldXY: jest.fn(() => ({ x: 3, y: 3 })) }; + + // Call the onProductionComplete callback + mapPlayer.productionPanel.onProductionComplete(bsm, 'infantry'); + + expect(mapPlayer.teamManager.getPlayerTeam).toHaveBeenCalledWith('Player'); + expect(mapPlayer.unitFactory.spawnInfantry).toHaveBeenCalledWith( + expect.any(Object), + 'team-A', + ); + }); + + test('CombatSystem and ControlPointManager receive teamManager', () => { + mapPlayer.create(); + // SystemOrchestrator init passes scene.teamManager to CombatSystem and CPManager + // Verified indirectly by checking no registerUnitContainers calls remain + const orchestrator = mapPlayer.orchestrator; + expect(orchestrator.systems.combat.registerUnitContainers).not.toHaveBeenCalled(); + }); + + test('combat resolves correctly across all 3 teams', () => { + mapPlayer.create(); + const tm = mapPlayer.teamManager; + const uA = { getData: jest.fn(() => 'team-A'), name: 'unit-A', active: true, x: 0, y: 0 }; + const uB = { getData: jest.fn(() => 'team-B'), name: 'unit-B', active: true, x: 10, y: 0 }; + const uC = { getData: jest.fn(() => 'team-C'), name: 'unit-C', active: true, x: 20, y: 0 }; + + tm.getTeamUnits.mockReturnValueOnce(new Set([uA])).mockReturnValueOnce(new Set([uB])).mockReturnValueOnce(new Set([uC])); + tm.isEnemy.mockImplementation((a, b) => { + const ta = a.getData('teamId'); + const tb = b.getData('teamId'); + return ta !== tb; + }); + + expect(tm.isEnemy(uA, uB)).toBe(true); + expect(tm.isEnemy(uA, uC)).toBe(true); + expect(tm.isEnemy(uA, uA)).toBe(false); + expect(tm.isEnemy(uB, uC)).toBe(true); + }); + + test('control point captures correctly with 3 teams', () => { + mapPlayer.create(); + const tm = mapPlayer.teamManager; + const unitsA = new Set([{}, {}]); + const unitsB = new Set([{}]); + const unitsC = new Set([{}]); + + tm.getTeamUnits.mockImplementation((id) => { + if (id === 'team-A') return unitsA; + if (id === 'team-B') return unitsB; + if (id === 'team-C') return unitsC; + return new Set(); + }); + + expect(tm.getTeamUnits('team-A').size).toBe(2); + expect(tm.getTeamUnits('team-B').size).toBe(1); + expect(tm.getTeamUnits('team-C').size).toBe(1); + }); + + test('UI shows correct team colors for all 3 teams', () => { + mapPlayer.create(); + const tm = mapPlayer.teamManager; + + tm.getTeamColor.mockImplementation((id) => { + if (id === 'team-A') return 0x1d7196; + if (id === 'team-B') return 0xd94f4f; + if (id === 'team-C') return 0x4fd94f; + return 0xffffff; + }); + + expect(tm.getTeamColor('team-A')).toBe(0x1d7196); + expect(tm.getTeamColor('team-B')).toBe(0xd94f4f); + expect(tm.getTeamColor('team-C')).toBe(0x4fd94f); + }); +}); diff --git a/test/systems/CombatSystem.test.js b/test/systems/CombatSystem.test.js new file mode 100644 index 0000000..6f5065b --- /dev/null +++ b/test/systems/CombatSystem.test.js @@ -0,0 +1,349 @@ +/** + * CombatSystem.test.js — Multi-team CombatSystem tests. + * Tests use Jest globals (jest, describe, test, expect, beforeEach). + * File: test/systems/CombatSystem.test.js + */ + +// ------------------------------------------------------------------ +// Phaser mock (scoped to this test file) +// ------------------------------------------------------------------ +jest.mock('phaser', () => ({ + Math: { + Distance: { + Between: jest.fn((x1, y1, x2, y2) => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)), + BetweenPoints: jest.fn((a, b) => Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2)), + }, + Angle: { + Between: jest.fn(() => 0), + BetweenPoints: jest.fn(() => 0), + Wrap: jest.fn((angle) => angle), + }, + Vector2: class MockVector2 { + constructor(x, y) { this.x = x; this.y = y; } + }, + DegToRad: jest.fn((deg) => deg * Math.PI / 180), + RadToDeg: jest.fn((rad) => rad * 180 / Math.PI), + }, + Physics: { + Arcade: { + DYNAMIC_BODY: 0, + Sprite: class MockArcadeSprite { + constructor(scene, x, y, texture) { + this.scene = scene; + this.x = x; + this.y = y; + this.texture = { key: texture }; + this.active = true; + this.body = { + velocity: { x: 0, y: 0 }, + allowGravity: false, + setSize: jest.fn(), + setOffset: jest.fn(), + setVelocity: jest.fn(), + }; + this._data = {}; + this.setData = jest.fn((k, v) => { this._data[k] = v; }); + this.getData = jest.fn((k) => this._data[k] ?? null); + this.setTint = jest.fn(); + this.clearTint = jest.fn(); + this.setDepth = jest.fn(); + this.setRotation = jest.fn(); + this.destroy = jest.fn(); + this.emit = jest.fn(); + } + }, + }, + }, + Display: { + Color: { + GetColor32: jest.fn(() => 0xffff00), + }, + }, + GameObjects: { + Sprite: class {}, + Rectangle: class { + constructor(scene, x, y, w, h, color) { + this.x = x; this.y = y; this.width = w; this.height = h; this.fillColor = color; + this.active = true; + this.body = { velocity: { x: 0, y: 0 }, allowGravity: false, setVelocity: jest.fn() }; + this._data = {}; + this.setData = jest.fn((k, v) => { this._data[k] = v; }); + this.getData = jest.fn((k) => this._data[k] ?? null); + this.setDepth = jest.fn(); + this.setRotation = jest.fn(); + this.destroy = jest.fn(); + } + }, + Graphics: class {}, + Container: class { + constructor() { this.list = []; this._data = {}; } + add(item) { this.list.push(item); } + getAll() { return this.list; } + setName() { return this; } + }, + Zone: class {}, + }, + Geom: { + Rectangle: class { + constructor(x, y, w, h) { + this.x = x; this.y = y; this.width = w; this.height = h; + } + }, + }, +})); + +// ------------------------------------------------------------------ +// Imports +// ------------------------------------------------------------------ +import CombatSystem from 'Systems/CombatSystem'; +import TeamManager from 'Systems/TeamManager'; + +// ------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------ +function createMockScene() { + return { + events: { on: jest.fn(), emit: jest.fn(), off: jest.fn() }, + physics: { + add: { + group: jest.fn(() => ({ + create: jest.fn(), + killAndHide: jest.fn(), + add: jest.fn().mockImplementation((sprite) => sprite), + getChildren: jest.fn(() => []), + })), + }, + overlap: jest.fn(() => false), + world: { enableBody: jest.fn() }, + velocityFromAngle: jest.fn(), + }, + add: { + existing: jest.fn(), + sprite: jest.fn(), + rectangle: jest.fn(() => ({ + setDepth: jest.fn(), + setData: jest.fn(), + getData: jest.fn(), + setRotation: jest.fn(), + body: { velocity: { x: 0, y: 0 }, allowGravity: true, setVelocity: jest.fn() }, + destroy: jest.fn(), + })), + }, + textures: { exists: jest.fn(() => false) }, + tweens: { addCounter: jest.fn(() => ({ stop: jest.fn() })) }, + cameras: { main: { worldView: { x: 0, y: 0, width: 800, height: 600 } } }, + }; +} + +// Minimal mock entity (not a full Phaser sprite) — used for units. +function createMockUnit(x, y, teamId = null, overrides = {}) { + const data = {}; + const unit = { + x, + y, + rotation: 0, + active: true, + dead: false, + body: { + center: { x, y }, + velocity: { x: 0, y: 0 }, + allowGravity: false, + }, + getData: jest.fn((key) => data[key] ?? null), + setData: jest.fn((key, value) => { data[key] = value; }), + emit: jest.fn(), + isDead: jest.fn(() => false), + handleDeath: jest.fn(), + handleTakeDamage: jest.fn(), + components: { + combat: { + canFire: () => true, + damage: 10, + damageType: 'rifle', + weaponRange: 200, + fireRate: 1000, + recordFire: jest.fn(), + }, + }, + ...overrides, + }; + if (teamId !== null) unit.setData('teamId', teamId); + return unit; +} + +// ------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------ +describe('CombatSystem (multi-team)', () => { + let scene; + let teamManager; + let combat; + + beforeEach(() => { + jest.clearAllMocks(); + scene = createMockScene(); + teamManager = new TeamManager(scene); + teamManager.createTeam('team-A', 0x1d7196, 'Alpha'); + teamManager.createTeam('team-B', 0xd94f4f, 'Bravo'); + teamManager.createTeam('team-C', 0x2ecc71, 'Charlie'); + + combat = new CombatSystem(scene, teamManager); + + // Stub LoS so range checks succeed + combat.hasLineOfSight = jest.fn(() => true); + }); + + afterEach(() => { + // Clean up refs + combat = null; + teamManager = null; + }); + + // ── Test 1: Constructor accepts TeamManager, not containers ──── + test('constructor accepts TeamManager, not containers', () => { + expect(combat.teamManager).toBe(teamManager); + expect(combat.scene).toBe(scene); + expect(combat.projectiles).toBeDefined(); + expect(combat.damageModifiers).toBeDefined(); + // Old fields must be absent + expect(combat._goodGuys).toBeUndefined(); + expect(combat._enemies).toBeUndefined(); + }); + + // ── Test 2: registerUnitContainers removed from public API ──────── + test('registerUnitContainers removed from public API', () => { + expect(typeof combat.registerUnitContainers).toBe('undefined'); + }); + + // ── Test 3: _processCombatGroup iterates all team groups ──────── + test('_processCombatGroup iterates all team groups', () => { + const uA = createMockUnit(100, 100, 'team-A'); + const uB = createMockUnit(200, 100, 'team-B'); + const uC = createMockUnit(300, 100, 'team-C'); + + teamManager.addUnit(uA, 'team-A'); + teamManager.addUnit(uB, 'team-B'); + teamManager.addUnit(uC, 'team-C'); + + const fireSpy = jest.fn(); + combat.fireProjectile = fireSpy; + + combat.update(1000, 16); + + const calls = fireSpy.mock.calls; + expect(calls.length).toBeGreaterThan(0); + }); + + // ── Test 4: _checkOverlap checks all teams ────────────────────── + test('_checkOverlap checks all teams', () => { + const uA = createMockUnit(0, 0, 'team-A'); + const uB = createMockUnit(0, 0, 'team-B'); + const uC = createMockUnit(0, 0, 'team-C'); + + teamManager.addUnit(uA, 'team-A'); + teamManager.addUnit(uB, 'team-B'); + teamManager.addUnit(uC, 'team-C'); + + // Projectile fired by uA + const proj = combat.fireProjectile(uA, uB); + expect(proj).toBeDefined(); + + // Cause overlap against uB + scene.physics.overlap.mockImplementation((p, unit) => unit === uB || unit === uC); + + const onHitSpy = jest.spyOn(combat, '_onHit'); + combat._checkOverlap(proj); + + // Should have hit either uB or uC (enemies of A) + expect(onHitSpy).toHaveBeenCalled(); + onHitSpy.mockRestore(); + }); + + // ── Test 5: acquireTarget returns enemy unit from any team ──────── + test('acquireTarget returns enemy unit from any team', () => { + const uA = createMockUnit(100, 100, 'team-A'); + const uB = createMockUnit(150, 100, 'team-B'); + const uC = createMockUnit(200, 100, 'team-C'); + + teamManager.addUnit(uA, 'team-A'); + teamManager.addUnit(uB, 'team-B'); + teamManager.addUnit(uC, 'team-C'); + + // uA looking for enemies → should find uB (closest) + const target = combat.acquireTarget(uA, { maxRange: 300 }); + expect(target).not.toBeNull(); + // It should be either uB or uC, but NOT uA + expect(target).not.toBe(uA); + // Verify it is an enemy (different team) + expect(teamManager.isEnemy(uA, target)).toBe(true); + }); + + // ── Test 6: Friendly fire prevented by team check ───────────────── + test('friendly fire prevented by team check in canHit', () => { + const uA1 = createMockUnit(0, 0, 'team-A'); + const uA2 = createMockUnit(10, 0, 'team-A'); + const uB = createMockUnit(100, 0, 'team-B'); + + teamManager.addUnit(uA1, 'team-A'); + teamManager.addUnit(uA2, 'team-A'); + teamManager.addUnit(uB, 'team-B'); + + const hitSame = combat.canHit(uA1, uA2, 200); + expect(hitSame.canHit).toBe(false); + expect(hitSame.reason).toBe('friendly_fire'); + + const hitEnemy = combat.canHit(uA1, uB, 200); + // Should pass (we stubbed LoS, but range should also pass) + combat.hasLineOfSight = jest.fn(() => true); + const hitEnemy2 = combat.canHit(uA1, uB, 200); + expect(hitEnemy2.canHit).toBe(true); + }); + + // ── Test 7: Projectile from team-A hits team-B and team-C units ─── + test('projectile from team-A hits team-B and team-C units', () => { + const uA = createMockUnit(0, 0, 'team-A'); + const uB = createMockUnit(0, 0, 'team-B'); + const uC = createMockUnit(0, 0, 'team-C'); + + teamManager.addUnit(uA, 'team-A'); + teamManager.addUnit(uB, 'team-B'); + teamManager.addUnit(uC, 'team-C'); + + const proj = combat.fireProjectile(uA, uB); + + // Overlap with B first + scene.physics.overlap.mockImplementation((p, unit) => unit === uB); + const onHitSpy = jest.spyOn(combat, '_onHit'); + combat._checkOverlap(proj); + expect(onHitSpy).toHaveBeenCalledWith(proj, uB); + onHitSpy.mockRestore(); + + // Overlap with C + const proj2 = combat.fireProjectile(uA, uC); + scene.physics.overlap.mockImplementation((p, unit) => unit === uC); + const onHitSpy2 = jest.spyOn(combat, '_onHit'); + combat._checkOverlap(proj2); + expect(onHitSpy2).toHaveBeenCalledWith(proj2, uC); + onHitSpy2.mockRestore(); + }); + + // ── Test 8: Projectile from team-A does NOT hit team-A units ───── + test('projectile from team-A does NOT hit team-A units', () => { + const uA1 = createMockUnit(0, 0, 'team-A'); + const uA2 = createMockUnit(0, 0, 'team-A'); + + teamManager.addUnit(uA1, 'team-A'); + teamManager.addUnit(uA2, 'team-A'); + + const proj = combat.fireProjectile(uA1, uA2); + + // Overlap would trigger, but friendly check should skip + scene.physics.overlap.mockImplementation(() => true); + const onHitSpy = jest.spyOn(combat, '_onHit'); + combat._checkOverlap(proj); + // Should not process a hit because uA2 is same team as attacker + const friendlyFireCall = onHitSpy.mock.calls.find((call) => call[1] === uA2); + expect(friendlyFireCall).toBeUndefined(); + onHitSpy.mockRestore(); + }); +}); diff --git a/test/systems/ControlPointManager.test.js b/test/systems/ControlPointManager.test.js new file mode 100644 index 0000000..eff0cef --- /dev/null +++ b/test/systems/ControlPointManager.test.js @@ -0,0 +1,158 @@ +/** + * ControlPointManager.test.js — TeamManager-aware tests. + * Tests use Jest globals. + */ + +// xstate mock with working state transitions +jest.mock('xstate', () => { + function createService(initialState = 'NEUTRAL', initialContext = {}) { + const ctx = { + owner: null, + captureProgress: 0, + captureTime: 60000, + unitsInRadius: {}, + ...initialContext, + }; + + const svc = { + state: { value: initialState, context: ctx }, + send: jest.fn((event) => { + const e = typeof event === 'string' ? { type: event } : event; + const current = svc.state.value; + if (current === 'NEUTRAL' && e.type === 'UNITS_ENTERED') { + svc.state.value = 'CONTESTED'; + } else if (current === 'CONTESTED' && e.type === 'PROGRESS_COMPLETE') { + svc.state.value = 'CAPTURED'; + ctx.owner = e.owner || e.teamId || null; + } else if (current === 'CONTESTED' && e.type === 'UNITS_LEFT') { + svc.state.value = 'NEUTRAL'; + } else if (current === 'CAPTURED' && e.type === 'ENEMY_UNITS_ENTERED') { + svc.state.value = 'CONTESTED'; + } + if (e.owner != null) ctx.owner = e.owner; + if (e.teamId != null) ctx.owner = e.teamId; + }), + start: jest.fn(), + stop: jest.fn(), + status: 1, + }; + return svc; + } + + return { + createMachine: jest.fn((config) => ({ + config, + id: config?.id, + initial: config?.initial, + })), + interpret: jest.fn((machine) => { + const cfg = machine?.config || machine; + return createService(cfg?.initial); + }), + assign: jest.fn((fn) => fn), + }; +}); + +import ControlPointManager from 'Systems/ControlPointManager.js'; + +// ── Helpers ───────────────────────────────────────────────────────── + +function mockTilemap() { + return { + tileToWorldXY: jest.fn((tx, ty) => ({ x: tx * 32, y: ty * 32 })), + tileWidth: 32, + tileHeight: 32, + }; +} + +function mockScene(teamManager = null) { + return { + add: { + zone: jest.fn((x, y, w, h) => ({ + x: x ?? 0, + y: y ?? 0, + width: w ?? 0, + height: h ?? 0, + setName: jest.fn().mockReturnThis(), + destroy: jest.fn(), + body: { setCircle: jest.fn(), setOffset: jest.fn() }, + })), + }, + physics: { + world: { enableBody: jest.fn() }, + }, + events: { on: jest.fn(), emit: jest.fn(), off: jest.fn() }, + teamManager, + children: { list: [] }, + }; +} + +function mockEconomy() { + return { + addIncome: jest.fn(), + getResources: jest.fn(), + initPlayer: jest.fn(), + }; +} + +function mockTeamManager(units = {}) { + const map = new Map(); + for (const [tid, list] of Object.entries(units)) { + map.set(tid, new Set(list)); + } + return { + getAllUnitsGrouped: jest.fn(() => map), + getTeamUnits: jest.fn((tid) => map.get(tid) || new Set()), + }; +} + +// ── Tests ─────────────────────────────────────────────────────────── + +describe('ControlPointManager', () => { + let manager; + let scene; + let tilemap; + let economy; + let teamManager; + + beforeEach(() => { + tilemap = mockTilemap(); + economy = mockEconomy(); + teamManager = mockTeamManager({}); + scene = mockScene(teamManager); + manager = new ControlPointManager(scene, tilemap, economy, teamManager); + }); + + afterEach(() => { + if (manager) manager.destroy(); + }); + + // ── Test 1: constructor accepts teamManager ───────────────────── + test('constructor accepts teamManager parameter', () => { + expect(manager.teamManager).toBe(teamManager); + }); + + // ── Test 2: update queries teamManager for unit counts ────────── + test('update delegates tick to each CP using teamManager for unit counts', () => { + const tickSpies = manager.controlPoints.map((cp) => + jest.spyOn(cp, 'tick').mockImplementation(() => {}), + ); + + manager.update(1000, 16); + + for (const spy of tickSpies) { + expect(spy).toHaveBeenCalledWith(1000, 16, scene); + } + }); + + test('each CP has a reference to teamManager on construction', () => { + for (const cp of manager.controlPoints) { + expect(cp.teamManager).toBe(teamManager); + } + }); + + test('falls back to scene.teamManager when no teamManager passed', () => { + const mgr = new ControlPointManager(scene, tilemap, economy); + expect(mgr.teamManager).toBe(scene.teamManager); + }); +}); diff --git a/test/systems/ControlPointStateMachine.test.js b/test/systems/ControlPointStateMachine.test.js new file mode 100644 index 0000000..54f9128 --- /dev/null +++ b/test/systems/ControlPointStateMachine.test.js @@ -0,0 +1,243 @@ +/** + * ControlPointStateMachine.test.js — TeamManager-aware tests. + * Tests use Jest globals. + */ + +// Local xstate mock with working state transitions for tick testing +jest.mock('xstate', () => { + function createService(initialState = 'NEUTRAL', initialContext = {}) { + const ctx = { + owner: null, + captureProgress: 0, + captureTime: 60000, + unitsInRadius: {}, + ...initialContext, + }; + + const svc = { + state: { value: initialState, context: ctx }, + send: jest.fn((event) => { + const e = typeof event === 'string' ? { type: event } : event; + const current = svc.state.value; + if (current === 'NEUTRAL' && e.type === 'UNITS_ENTERED') { + svc.state.value = 'CONTESTED'; + } else if (current === 'CONTESTED' && e.type === 'PROGRESS_COMPLETE') { + svc.state.value = 'CAPTURED'; + ctx.owner = e.owner || e.teamId || null; + } else if (current === 'CONTESTED' && e.type === 'UNITS_LEFT') { + svc.state.value = 'NEUTRAL'; + } else if (current === 'CAPTURED' && e.type === 'ENEMY_UNITS_ENTERED') { + svc.state.value = 'CONTESTED'; + } + // Update context if event carries owner + if (e.owner != null) ctx.owner = e.owner; + if (e.teamId != null) ctx.owner = e.teamId; + }), + start: jest.fn(function() { return svc; }), + stop: jest.fn(), + status: 1, // not stopped + }; + return svc; + } + + return { + createMachine: jest.fn(() => ({})), + interpret: jest.fn((machine) => createService(machine?.config?.initial)), + assign: jest.fn((fn) => fn), + }; +}); + +import ControlPointStateMachine, { ControlPointState } from 'Systems/ControlPointStateMachine.js'; + +// ── Helpers ───────────────────────────────────────────────────────── + +function mockSceneWithTeamManager(teamManager) { + return { + add: { + zone: jest.fn((x, y, w, h) => ({ + x: x ?? 0, + y: y ?? 0, + width: w ?? 0, + height: h ?? 0, + setName: jest.fn().mockReturnThis(), + destroy: jest.fn(), + body: { setCircle: jest.fn(), setOffset: jest.fn() }, + })), + }, + physics: { + world: { enableBody: jest.fn() }, + }, + events: { on: jest.fn(), emit: jest.fn(), off: jest.fn() }, + teamManager, + children: { list: [] }, + }; +} + +function createMockUnit(x, y, teamId) { + return { + x, + y, + setData: jest.fn((k, v) => { /* no-op */ }), + getData: jest.fn((key) => (key === 'teamId' ? teamId : null)), + }; +} + +function createTeamManagerMock(units = {}) { + // units: { teamId: [unit, unit], ... } + const map = new Map(); + for (const [tid, list] of Object.entries(units)) { + const set = new Set(list); + map.set(tid, set); + } + + return { + getAllUnitsGrouped: jest.fn(() => map), + getTeamUnits: jest.fn((tid) => map.get(tid) || new Set()), + getAllUnits: jest.fn(() => { + const all = []; + for (const list of map.values()) all.push(...list); + return all; + }), + }; +} + +// ── Tests ─────────────────────────────────────────────────────────── + +describe('ControlPointStateMachine (multi-team)', () => { + let scene; + let cp; + + beforeEach(() => { + scene = mockSceneWithTeamManager(null); + }); + + afterEach(() => { + if (cp) { + cp.destroy(); + cp = null; + } + }); + + // ── Test 1: registerUnitContainers removed ───────────────────── + test('registerUnitContainers is NOT a method on the instance', () => { + cp = new ControlPointStateMachine(scene, { x: 100, y: 100 }); + expect(typeof cp.registerUnitContainers).toBe('undefined'); + }); + + // ── Test 2: counts units per team via TeamManager ────────────── + test('getUnitsInRadius counts units per teamId via TeamManager', () => { + const uA1 = createMockUnit(100, 100, 'team-A'); + const uA2 = createMockUnit(102, 100, 'team-A'); + const uB1 = createMockUnit(300, 300, 'team-B'); // outside radius > 160 + const uB2 = createMockUnit(104, 100, 'team-B'); // inside radius + + const tm = createTeamManagerMock({ + 'team-A': [uA1, uA2], + 'team-B': [uB1, uB2], + }); + scene.teamManager = tm; + + cp = new ControlPointStateMachine(scene, { x: 100, y: 100, radius: 5, tileSize: 32 }); + cp.teamManager = tm; // Ensure TeamManager is wired + + const counts = cp.getUnitsInRadius(); + expect(counts['team-A']).toBe(2); + expect(counts['team-B']).toBe(1); + expect(counts['team-C']).toBeUndefined(); + }); + + // ── Test 3: NEUTRAL → CONTESTED when units from 2+ teams present ─ + test('NEUTRAL → CONTESTED when units from multiple teams present', () => { + const uA = createMockUnit(100, 100, 'team-A'); + const uB = createMockUnit(104, 100, 'team-B'); + const tm = createTeamManagerMock({ + 'team-A': [uA], + 'team-B': [uB], + }); + scene.teamManager = tm; + + cp = new ControlPointStateMachine(scene, { x: 100, y: 100, radius: 5, tileSize: 32 }); + cp.teamManager = tm; + + expect(cp.getState()).toBe(ControlPointState.NEUTRAL); + cp.tick(0, 16); + expect(cp.getState()).toBe(ControlPointState.CONTESTED); + expect(cp.service.send).toHaveBeenCalledWith( + expect.objectContaining({ type: 'UNITS_ENTERED' }), + ); + }); + + // ── Test 4: CONTESTED → CAPTURED when one team has majority ───── + test('CONTESTED → CAPTURED when one team reaches majority (progress hits 100)', () => { + const uA = createMockUnit(100, 100, 'team-A'); + const uB = createMockUnit(104, 100, 'team-B'); + const tm = createTeamManagerMock({ + 'team-A': [uA], + 'team-B': [uB], + }); + scene.teamManager = tm; + + cp = new ControlPointStateMachine(scene, { x: 100, y: 100, radius: 5, tileSize: 32 }); + cp.teamManager = tm; + + // Move to CONTESTED first + cp.tick(0, 16); + expect(cp.getState()).toBe(ControlPointState.CONTESTED); + + // Set contesting team as dominant by giving team-A more units + const uA2 = createMockUnit(101, 101, 'team-A'); + tm.getAllUnitsGrouped.mockReturnValue( + new Map([['team-A', new Set([uA, uA2])], ['team-B', new Set([uB])]]), + ); + tm.getTeamUnits.mockImplementation((tid) => { + if (tid === 'team-A') return new Set([uA, uA2]); + if (tid === 'team-B') return new Set([uB]); + return new Set(); + }); + + // Inject captureProgress near completion + cp.service.state.context.captureProgress = 99.9; + cp.tick(0, 100); // delta pushes over 100 + expect(cp.getState()).toBe(ControlPointState.CAPTURED); + expect(cp.service.send).toHaveBeenLastCalledWith( + expect.objectContaining({ type: 'PROGRESS_COMPLETE', owner: 'team-A' }), + ); + }); + + // ── Test 5: owner stored as teamId string ────────────────────── + test('owner stored as teamId string after capture', () => { + const uA = createMockUnit(100, 100, 'team-A'); + const tm = createTeamManagerMock({ 'team-A': [uA] }); + scene.teamManager = tm; + + cp = new ControlPointStateMachine(scene, { + x: 100, y: 100, radius: 5, tileSize: 32, + }); + cp.teamManager = tm; + + // Move to CONTESTED then CAPTURED + cp.tick(0, 16); + cp.service.state.context.captureProgress = 99.9; + cp.tick(0, 100); + + expect(cp.getState()).toBe(ControlPointState.CAPTURED); + expect(cp.getOwner()).toBe('team-A'); + }); + + // ── Test 6: TeamManager passed via config ─────────────────────── + test('constructor accepts teamManager via config', () => { + const tm = createTeamManagerMock({}); + cp = new ControlPointStateMachine(scene, { + x: 100, y: 100, teamManager: tm, + }); + expect(cp.teamManager).toBe(tm); + }); + + // ── Test 7: falls back to scene.teamManager ───────────────────── + test('falls back to scene.teamManager if no teamManager in config', () => { + const tm = createTeamManagerMock({}); + scene.teamManager = tm; + cp = new ControlPointStateMachine(scene, { x: 100, y: 100 }); + expect(cp.teamManager).toBe(tm); + }); +}); diff --git a/test/systems/TeamManager.test.js b/test/systems/TeamManager.test.js new file mode 100644 index 0000000..0ed3815 --- /dev/null +++ b/test/systems/TeamManager.test.js @@ -0,0 +1,198 @@ +/** + * TeamManager.test.js — 12 unit tests covering TeamManager + Team value object. + * Uses Jest globals (no import from 'vitest'). + */ + +import TeamManager, { Team } from 'Systems/TeamManager'; + +// Minimal mock entity with setData/getData +function createMockEntity() { + const data = {}; + return { + _data: data, + setData: jest.fn((k, v) => { data[k] = v; }), + getData: jest.fn((k) => data[k] ?? null), + }; +} + +// Minimal mock scene (TeamManager only stores it, doesn't need much) +function createMockScene() { + return {}; +} + +describe('TeamManager', () => { + let manager; + + beforeEach(() => { + manager = new TeamManager(createMockScene()); + }); + + afterEach(() => { + if (manager) manager.destroy(); + }); + + // ── Test 1: createTeam ─────────────────────────────────────── + test('createTeam returns Team with id/color/name, duplicate returns same object', () => { + const team = manager.createTeam('team-A', 0x1d7196, 'Alpha'); + expect(team).toBeInstanceOf(Team); + expect(team.id).toBe('team-A'); + expect(team.color).toBe(0x1d7196); + expect(team.name).toBe('Alpha'); + + const duplicate = manager.createTeam('team-A', 0x000000, 'Ignored'); + expect(duplicate).toBe(team); // same reference + }); + + // ── Test 2: getTeam returns undefined for unknown teamId ───── + test('getTeam returns undefined for unknown teamId', () => { + expect(manager.getTeam('ghost-team')).toBeUndefined(); + }); + + // ── Test 3: setPlayerTeam / getPlayerTeam ─────────────────── + test('setPlayerTeam maps playerId to teamId', () => { + manager.createTeam('team-A', 0x1d7196, 'Alpha'); + manager.setPlayerTeam('p1', 'team-A'); + expect(manager.getPlayerTeam('p1')).toBe('team-A'); + }); + + // ── Test 4: getTeamPlayers returns correct set ────────────── + test('getTeamPlayers returns set of players in team', () => { + manager.createTeam('team-A', 0x1d7196, 'Alpha'); + manager.setPlayerTeam('p1', 'team-A'); + manager.setPlayerTeam('p2', 'team-A'); + expect(manager.getPlayerTeam('p3')).toBeUndefined(); + + const players = manager.getTeamPlayers('team-A'); + expect(players.has('p1')).toBe(true); + expect(players.has('p2')).toBe(true); + expect(players.has('p3')).toBe(false); + }); + + // ── Test 5: addUnit ───────────────────────────────────────── + test('addUnit sets unit data, adds to Team.units, appears in getTeamUnits', () => { + manager.createTeam('team-A', 0x1d7196, 'Alpha'); + const unit = createMockEntity(); + manager.addUnit(unit, 'team-A'); + + expect(unit.setData).toHaveBeenCalledWith('teamId', 'team-A'); + expect(unit.getData('teamId')).toBe('team-A'); + expect(manager.getTeamUnits('team-A').has(unit)).toBe(true); + }); + + // ── Test 6: removeUnit ───────────────────────────────────── + test('removeUnit removes from one team, re-add to another works', () => { + manager.createTeam('team-A', 0x1d7196, 'Alpha'); + manager.createTeam('team-B', 0xd94f4f, 'Bravo'); + const unit = createMockEntity(); + + manager.addUnit(unit, 'team-A'); + expect(manager.getTeamUnits('team-A').has(unit)).toBe(true); + expect(manager.getTeamUnits('team-B').has(unit)).toBe(false); + + manager.removeUnit(unit, 'team-A'); + expect(manager.getTeamUnits('team-A').has(unit)).toBe(false); + expect(unit.getData('teamId')).toBeNull(); + + manager.addUnit(unit, 'team-B'); + expect(manager.getTeamUnits('team-B').has(unit)).toBe(true); + expect(unit.getData('teamId')).toBe('team-B'); + }); + + // ── Test 7: getUnitTeam returns null for unregistered unit ─── + test('getUnitTeam returns null for unregistered unit', () => { + const unit = createMockEntity(); + expect(manager.getUnitTeam(unit)).toBeNull(); + expect(unit.getData).toHaveBeenCalledTimes(1); + expect(unit.getData).toHaveBeenCalledWith('teamId'); + }); + + // ── Test 8: getAllUnits ───────────────────────────────────── + test('getAllUnits returns flat array of all units', () => { + manager.createTeam('team-A', 0x1d7196, 'Alpha'); + manager.createTeam('team-B', 0xd94f4f, 'Bravo'); + const u1 = createMockEntity(); + const u2 = createMockEntity(); + const u3 = createMockEntity(); + manager.addUnit(u1, 'team-A'); + manager.addUnit(u2, 'team-A'); + manager.addUnit(u3, 'team-B'); + + const all = manager.getAllUnits(); + expect(all).toHaveLength(3); + expect(all).toContain(u1); + expect(all).toContain(u2); + expect(all).toContain(u3); + }); + + // ── Test 9: getAllUnitsGrouped ───────────────────────────── + test('getAllUnitsGrouped returns Map keyed by teamId', () => { + manager.createTeam('team-A', 0x1d7196, 'Alpha'); + manager.createTeam('team-B', 0xd94f4f, 'Bravo'); + const uA = createMockEntity(); + const uB = createMockEntity(); + manager.addUnit(uA, 'team-A'); + manager.addUnit(uB, 'team-B'); + + const grouped = manager.getAllUnitsGrouped(); + expect(grouped instanceof Map).toBe(true); + expect(grouped.get('team-A')).toBeInstanceOf(Set); + expect(grouped.get('team-A').has(uA)).toBe(true); + expect(grouped.get('team-B').has(uB)).toBe(true); + expect(grouped.get('team-A').has(uB)).toBe(false); + }); + + // ── Test 10: addBuilding / removeBuilding / getBuildingTeam ── + test('addBuilding/removeBuilding/getBuildingTeam follow same pattern', () => { + manager.createTeam('team-A', 0x1d7196, 'Alpha'); + const building = createMockEntity(); + manager.addBuilding(building, 'team-A'); + + expect(building.setData).toHaveBeenCalledWith('teamId', 'team-A'); + expect(manager.getBuildingTeam(building)).toBe('team-A'); + expect(manager.getTeamBuildings('team-A').has(building)).toBe(true); + + manager.removeBuilding(building, 'team-A'); + expect(manager.getTeamBuildings('team-A').has(building)).toBe(false); + expect(manager.getBuildingTeam(building)).toBeNull(); + }); + + // ── Test 11: isEnemy / isSameTeam ──────────────────────────── + test('isEnemy/isSameTeam: same team=false, different=true, null=not enemy', () => { + manager.createTeam('team-A', 0x1d7196, 'Alpha'); + manager.createTeam('team-B', 0xd94f4f, 'Bravo'); + const unitA = createMockEntity(); + const unitB = createMockEntity(); + const unitOrphan = createMockEntity(); + manager.addUnit(unitA, 'team-A'); + manager.addUnit(unitB, 'team-B'); + + expect(manager.isSameTeam(unitA, unitA)).toBe(true); + expect(manager.isEnemy(unitA, unitA)).toBe(false); + + expect(manager.isSameTeam(unitA, unitB)).toBe(false); + expect(manager.isEnemy(unitA, unitB)).toBe(true); + + expect(manager.isEnemy(unitA, unitOrphan)).toBe(false); + expect(manager.isEnemy(unitOrphan, unitB)).toBe(false); + expect(manager.isSameTeam(unitOrphan, unitA)).toBe(false); + }); + + // ── Test 12: getEnemyUnits ─────────────────────────────────── + test('getEnemyUnits returns all units NOT in given team', () => { + manager.createTeam('team-A', 0x1d7196, 'Alpha'); + manager.createTeam('team-B', 0xd94f4f, 'Bravo'); + manager.createTeam('team-C', 0x2ecc71, 'Charlie'); + + const uA = createMockEntity(); + const uB = createMockEntity(); + const uC = createMockEntity(); + manager.addUnit(uA, 'team-A'); + manager.addUnit(uB, 'team-B'); + manager.addUnit(uC, 'team-C'); + + const enemiesOfA = manager.getEnemyUnits('team-A'); + expect(enemiesOfA.has(uB)).toBe(true); + expect(enemiesOfA.has(uC)).toBe(true); + expect(enemiesOfA.has(uA)).toBe(false); + }); +}); diff --git a/test/systems/UnitFactory.test.js b/test/systems/UnitFactory.test.js new file mode 100644 index 0000000..88e4331 --- /dev/null +++ b/test/systems/UnitFactory.test.js @@ -0,0 +1,164 @@ +/** + * UnitFactory tests — TeamManager migration + * Uses Jest globals (no import from 'vitest'). + */ + +// ── Module-level mocks (before any imports) ─────────────────────── + +function mockUnit(scene, tile) { + return { + scene, + tile, + x: tile.x * 32, + y: tile.y * 32, + name: 'unit', + }; +} + +jest.mock('Entities/skins/ukrainian-infantry', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(mockUnit), +})); + +jest.mock('Entities/skins/russian-infantry', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(mockUnit), +})); + +jest.mock('Entities/skins/ukrainian-tank', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(mockUnit), +})); + +jest.mock('Entities/skins/russian-tank', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(mockUnit), +})); + +// ── Imports (after mocks) ───────────────────────────────────────── + +import UnitFactory from 'Systems/UnitFactory'; + +// ── Helpers ─────────────────────────────────────────────────────── + +function createMockTeamManager(teamIds = ['team-A', 'team-B', 'team-C']) { + const teams = new Map(); + teamIds.forEach((id, i) => { + teams.set(id, { id, color: i === 0 ? 0x1d7196 : 0xd94f4f }); + }); + return { + _teams: teams, + addUnit: jest.fn(), + getTeams: jest.fn(() => teams), + }; +} + +function buildMockScene() { + return {}; +} + +// ── Tests ───────────────────────────────────────────────────────── + +describe('UnitFactory (with TeamManager)', () => { + let scene; + let teamManager; + let factory; + + beforeEach(() => { + jest.clearAllMocks(); + scene = buildMockScene(); + teamManager = createMockTeamManager(); + factory = new UnitFactory(scene, teamManager); + }); + + test('constructor stores scene and teamManager', () => { + expect(factory.scene).toBe(scene); + expect(factory.teamManager).toBe(teamManager); + }); + + test('spawnInfantry adds unit to correct team via TeamManager', () => { + const tile = { x: 5, y: 5 }; + const unit = factory.spawnInfantry(tile, 'team-A'); + + expect(teamManager.addUnit).toHaveBeenCalledWith(unit, 'team-A'); + expect(unit).toBeDefined(); + }); + + test('spawnTank adds unit to correct team via TeamManager', () => { + const tile = { x: 3, y: 7 }; + const unit = factory.spawnTank(tile, 'team-A'); + + expect(teamManager.addUnit).toHaveBeenCalledWith(unit, 'team-A'); + expect(unit).toBeDefined(); + }); + + test('no references to scene.goodGuys or scene.badGuys remain', () => { + const sceneWithSpy = { + goodGuys: { add: jest.fn() }, + badGuys: { add: jest.fn() }, + }; + const tm = createMockTeamManager(); + const f = new UnitFactory(sceneWithSpy, tm); + + const tile = { x: 1, y: 1 }; + f.spawnInfantry(tile, 'team-A'); + f.spawnTank(tile, 'team-A'); + + expect(sceneWithSpy.goodGuys.add).not.toHaveBeenCalled(); + expect(sceneWithSpy.badGuys.add).not.toHaveBeenCalled(); + }); + + test('skin selection: team index 0 -> Ukrainian infantry', () => { + const tile = { x: 1, y: 1 }; + jest.clearAllMocks(); + factory.spawnInfantry(tile, 'team-A'); + + const Ukrainian_Rifle = require('Entities/skins/ukrainian-infantry').default; + expect(Ukrainian_Rifle).toHaveBeenCalledWith(scene, tile); + }); + + test('skin selection: team index 0 -> Ukrainian tank', () => { + const tile = { x: 1, y: 1 }; + jest.clearAllMocks(); + factory.spawnTank(tile, 'team-A'); + + const Ukrainian_Tank = require('Entities/skins/ukrainian-tank').default; + expect(Ukrainian_Tank).toHaveBeenCalledWith(scene, tile); + }); + + test('skin selection: team index 1 -> Russian infantry', () => { + const tile = { x: 1, y: 1 }; + jest.clearAllMocks(); + factory.spawnInfantry(tile, 'team-B'); + + const Russian_Rifle = require('Entities/skins/russian-infantry').default; + expect(Russian_Rifle).toHaveBeenCalledWith(scene, tile); + }); + + test('skin selection: team index 1 -> Russian tank', () => { + const tile = { x: 1, y: 1 }; + jest.clearAllMocks(); + factory.spawnTank(tile, 'team-B'); + + const Russian_Tank = require('Entities/skins/russian-tank').default; + expect(Russian_Tank).toHaveBeenCalledWith(scene, tile); + }); + + test('skin selection: team index 2+ -> Russian fallback infantry', () => { + const tile = { x: 1, y: 1 }; + jest.clearAllMocks(); + factory.spawnInfantry(tile, 'team-C'); + + const Russian_Rifle = require('Entities/skins/russian-infantry').default; + expect(Russian_Rifle).toHaveBeenCalledWith(scene, tile); + }); + + test('skin selection: team index 2+ -> Russian fallback tank', () => { + const tile = { x: 1, y: 1 }; + jest.clearAllMocks(); + factory.spawnTank(tile, 'team-C'); + + const Russian_Tank = require('Entities/skins/russian-tank').default; + expect(Russian_Tank).toHaveBeenCalledWith(scene, tile); + }); +}); diff --git a/tests/CombatSystem.test.js b/tests/CombatSystem.test.js index 2a50e73..8a16974 100644 --- a/tests/CombatSystem.test.js +++ b/tests/CombatSystem.test.js @@ -22,6 +22,33 @@ jest.mock('phaser', () => ({ Physics: { Arcade: { DYNAMIC_BODY: 0, + Sprite: class MockArcadeSprite { + constructor(scene, x, y, texture) { + this.scene = scene; + this.x = x; + this.y = y; + this.texture = texture; + this.active = true; + this.visible = true; + this.body = { + velocity: { x: 0, y: 0 }, + allowGravity: false, + setSize: jest.fn(), + setOffset: jest.fn(), + setVelocity: jest.fn(), + }; + this._data = {}; + this.setData = jest.fn((k, v) => { this._data[k] = v; }); + this.getData = jest.fn((k) => this._data[k] ?? null); + this.setTint = jest.fn(); + this.clearTint = jest.fn(); + this.setRotation = jest.fn(); + this.setDepth = jest.fn(); + this.destroy = jest.fn(); + this.emit = jest.fn(); + } + preUpdate() {} + }, }, }, Display: { @@ -60,13 +87,14 @@ const createMockScene = () => ({ off: jest.fn(), }, add: { + existing: jest.fn(), sprite: jest.fn(), rectangle: jest.fn(() => ({ setDepth: jest.fn(), setData: jest.fn(), getData: jest.fn(), setRotation: jest.fn(), - body: { velocity: { x: 0, y: 0 }, allowGravity: true }, + body: { velocity: { x: 0, y: 0 }, allowGravity: true, setVelocity: jest.fn() }, })), }, textures: { exists: jest.fn(() => false) }, @@ -77,6 +105,7 @@ const createMockScene = () => ({ * Helper to build a minimal entity that passes all CombatSystem guards. */ function entity(opts = {}) { + const team = opts.team ?? 'team-a'; const e = { x: opts.x ?? 0, y: opts.y ?? 0, @@ -85,13 +114,14 @@ function entity(opts = {}) { getData: jest.fn((key) => { if (key === 'health') return opts.health ?? 100; if (key === 'armor') return opts.armor ?? 1; + if (key === 'teamId') return team; return undefined; }), setData: jest.fn(), emit: jest.fn(), isDead: jest.fn(() => opts.dead ?? false), body: { center: { x: opts.x ?? 0, y: opts.y ?? 0 } }, - parentContainer: { name: opts.team ?? 'team-a' }, + parentContainer: { name: team }, getEnemyContainer: jest.fn(() => ({ list: opts.enemies ?? [], getAll: jest.fn(() => (opts.enemies ?? []).filter(e => !e.dead)), @@ -103,10 +133,21 @@ function entity(opts = {}) { describe('CombatSystem', () => { let scene; let combat; + let mockTeamManager; beforeEach(() => { scene = createMockScene(); - combat = new CombatSystem(scene); + mockTeamManager = { + getEntityTeam: jest.fn((e) => e.getData?.('teamId') ?? null), + getAllUnitsGrouped: jest.fn(() => new Map()), + isEnemy: jest.fn((a, b) => { + const ta = a.getData?.('teamId') ?? a.parentContainer?.name; + const tb = b.getData?.('teamId') ?? b.parentContainer?.name; + return ta !== tb; + }), + getTeams: jest.fn(() => []), + }; + combat = new CombatSystem(scene, mockTeamManager); }); describe('acquireTarget', () => { @@ -117,9 +158,17 @@ describe('CombatSystem', () => { }); it('should return closest enemy when multiple in range', () => { - const enemy1 = entity({ x: 100, y: 0 }); - const enemy2 = entity({ x: 50, y: 0 }); - const e = entity({ x: 0, y: 0, enemies: [enemy1, enemy2] }); + const enemy1 = entity({ x: 100, y: 0, team: 'bad-guys' }); + const enemy2 = entity({ x: 50, y: 0, team: 'bad-guys' }); + const e = entity({ x: 0, y: 0, team: 'good-guys', enemies: [enemy1, enemy2] }); + + // Populate TeamManager + mockTeamManager.getAllUnitsGrouped.mockReturnValue( + new Map([ + ['good-guys', new Set([e])], + ['bad-guys', new Set([enemy1, enemy2])], + ]) + ); // Override hasLineOfSight to always pass combat.hasLineOfSight = jest.fn(() => true); @@ -129,10 +178,16 @@ describe('CombatSystem', () => { }); it('should filter out dead enemies', () => { - const deadEnemy = entity({ x: 50, y: 0, dead: true }); - const liveEnemy = entity({ x: 100, y: 0 }); - const e = entity({ x: 0, y: 0, enemies: [deadEnemy, liveEnemy] }); + const deadEnemy = entity({ x: 50, y: 0, team: 'bad-guys', dead: true }); + const liveEnemy = entity({ x: 100, y: 0, team: 'bad-guys' }); + const e = entity({ x: 0, y: 0, team: 'good-guys', enemies: [deadEnemy, liveEnemy] }); + mockTeamManager.getAllUnitsGrouped.mockReturnValue( + new Map([ + ['good-guys', new Set([e])], + ['bad-guys', new Set([deadEnemy, liveEnemy])], + ]) + ); combat.hasLineOfSight = jest.fn(() => true); const target = combat.acquireTarget(e, { maxRange: 200 }); @@ -150,6 +205,12 @@ describe('CombatSystem', () => { it('should return false for friendly fire', () => { target.parentContainer.name = 'good-guys'; + target.getData.mockImplementation((key) => { + if (key === 'health') return 100; + if (key === 'armor') return 1; + if (key === 'teamId') return 'good-guys'; + return undefined; + }); const result = combat.canHit(attacker, target); expect(result.canHit).toBe(false); @@ -230,4 +291,48 @@ describe('CombatSystem', () => { expect(damage).toBe(40); // 20 * 2.0 }); }); + + describe('fireProjectile', () => { + it('creates a ProjectileSprite and adds it to the group', () => { + const attacker = entity({ x: 0, y: 0, team: 'good-guys' }); + const target = entity({ x: 100, y: 0, team: 'bad-guys' }); + combat.hasLineOfSight = jest.fn(() => true); + + const p = combat.fireProjectile(attacker, target); + + expect(p).not.toBeNull(); + expect(p.texture).toBe('__WHITE'); + expect(p.setTint).toHaveBeenCalledWith(0x0000ff); // blue for good guys + }); + + it('tints enemy projectiles red', () => { + const attacker = entity({ x: 0, y: 0, team: 'bad-guys' }); + const target = entity({ x: 100, y: 0, team: 'good-guys' }); + combat.hasLineOfSight = jest.fn(() => true); + + const p = combat.fireProjectile(attacker, target); + + expect(p.setTint).toHaveBeenCalledWith(0xff0000); + }); + + it('emits combat:projectileHit on _onHit', () => { + const attacker = entity({ x: 0, y: 0, team: 'good-guys' }); + const target = entity({ x: 100, y: 0, team: 'bad-guys', health: 100, armor: 0 }); + target.getData = jest.fn((key) => (key === 'health' ? 100 : (key === 'armor' ? 0 : undefined))); + + combat.damageModifiers = { + rifle: { armorPiercing: 0, critChance: 0, critMultiplier: 1.5 }, + }; + + const p = combat.fireProjectile(attacker, target); + combat._onHit(p, target); + + expect(target.setData).toHaveBeenCalledWith('health', expect.any(Number)); + expect(scene.events.emit).toHaveBeenCalledWith( + 'combat:projectileHit', + expect.objectContaining({ attacker, target }), + ); + expect(p.destroy).toHaveBeenCalled(); + }); + }); }); diff --git a/tests/Map_Player.test.js b/tests/Map_Player.test.js new file mode 100644 index 0000000..314391b --- /dev/null +++ b/tests/Map_Player.test.js @@ -0,0 +1,380 @@ +/** + * Map_Player + Interface tests — M1.2: Disable legacy pointers, add F-key spawn + * + * Tests: + * 1. F-key spawns unit in scene + * 2. Spawned unit is selectable (physics body present for SelectionSystem) + * 3. Interface.init(false) does NOT create pathfinder or wire pointer events + */ + +// ── Module-level mocks (before any imports) ─────────────────────── + +jest.mock('PhaserClasses/CustomConstants', () => ({ + __esModule: true, + default: { TINTS: { RED: 0xff0000, BLUE: 0x0000ff, GREEN: 0x00ff00 } }, +})); + +jest.mock('Systems/SystemOrchestrator.js', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(() => ({ + init: jest.fn(), + initPathfinding: jest.fn(), + initControlPoints: jest.fn(), + update: jest.fn(), + shutdown: jest.fn(), + registerBuilding: jest.fn((building, config) => ({ + building, + config, + getState: jest.fn(() => 'ACTIVE'), + destroy: jest.fn(), + })), + unregisterBuilding: jest.fn(), + systems: { + selection: null, + pathfinding: null, + combat: { }, + economy: { + initPlayer: jest.fn(), + events: { + on: jest.fn(), + }, + }, + network: null, + EntityStateMachine: {}, + BuildingStateMachine: {}, + ControlPointStateMachine: {}, + }, + })), +})); + +jest.mock('Systems/NetworkSystem.js', () => ({ + NetworkSystemClient: jest.fn(), +})); + +// Mock entity skins to avoid real Phaser sprite construction +const createMockSkin = (scene, tile) => ({ + scene, + tile, + x: (tile?.x ?? 0) * 32, + y: (tile?.y ?? 0) * 32, + name: 'test-unit', + body: null, // set by physics.enable + setScale: jest.fn().mockReturnThis(), + setData: jest.fn().mockReturnThis(), + setName: jest.fn().mockReturnThis(), + select: jest.fn(), + unSelect: jest.fn(), + destroy: jest.fn(), +}); + +jest.mock('Entities/skins/ukrainian-infantry', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(createMockSkin), +})); + +jest.mock('Entities/skins/russian-infantry', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(createMockSkin), +})); + +jest.mock('Entities/skins/russian-tank', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(createMockSkin), +})); + +// ── Imports (after mocks) ───────────────────────────────────────── + +import Map_Player from 'Scenes/Map_Player'; +import Interface from 'PhaserClasses/interface'; + +// ── Helpers ─────────────────────────────────────────────────────── + +/** + * Build a mock Phaser scene with the surface area that create() + spawnTestUnit need. + */ +function buildMockScene() { + const keyboardHandlers = {}; + const inputOnHandlers = {}; + + const mockScene = { + // Phaser.Scene basics + key: 'Map_Player', + scene: { key: 'Map_Player' }, + game: { colyseus: null }, + + // Camera + cameras: { + main: { + setBounds: jest.fn(), + zoomTo: jest.fn(), + centerOn: jest.fn(), + get zoom() { return 1; }, + }, + }, + + // Input + input: { + setDefaultCursor: jest.fn(), + keyboard: { + addKey: jest.fn(() => ({ isDown: false })), + on: jest.fn((event, cb) => { + keyboardHandlers[event] = cb; + }), + _handlers: keyboardHandlers, + _fire: (event) => { + if (keyboardHandlers[event]) keyboardHandlers[event](); + }, + }, + on: jest.fn((event, cb) => { + inputOnHandlers[event] = cb; + }), + _handlers: inputOnHandlers, + _fire: (event, ...args) => { + if (inputOnHandlers[event]) inputOnHandlers[event](...args); + }, + }, + + // Add game objects + add: { + container: jest.fn(() => ({ + setName: jest.fn().mockReturnThis(), + add: jest.fn(), + setScrollFactor: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setPosition: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + })), + rectangle: jest.fn(() => ({ + setStrokeStyle: jest.fn().mockReturnThis(), + setOrigin: jest.fn().mockReturnThis(), + setPosition: jest.fn().mockReturnThis(), + setScrollFactor: jest.fn().mockReturnThis(), + setInteractive: jest.fn().mockReturnThis(), + setFillStyle: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + on: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + })), + graphics: jest.fn(() => ({ + setDepth: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + clear: jest.fn(), + fillStyle: jest.fn(), + fillRect: jest.fn(), + lineStyle: jest.fn(), + strokeRect: jest.fn(), + get active() { return true; }, + })), + text: jest.fn(() => ({ + setScrollFactor: jest.fn().mockReturnThis(), + setOrigin: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setText: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + setPosition: jest.fn().mockReturnThis(), + destroy: jest.fn(), + get active() { return true; }, + })), + sprite: jest.fn(() => ({ + setScale: jest.fn().mockReturnThis(), + setPosition: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + play: jest.fn(), + setOrigin: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + setScrollFactor: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setDisplaySize: jest.fn().mockReturnThis(), + setTint: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + })), + existing: jest.fn(), + }, + + // Physics + physics: { + world: { enableBody: jest.fn() }, + overlapRect: jest.fn(() => []), + add: { + existing: jest.fn(), + }, + }, + + // Events + events: { + on: jest.fn(), + emit: jest.fn(), + off: jest.fn(), + }, + + // Tweens + tweens: { + addCounter: jest.fn((config) => { + const tween = { getValue: () => 200, stop: jest.fn() }; + if (config.onUpdate) config.onUpdate(tween); + return tween; + }), + }, + + // Animations + anims: { + create: jest.fn(), + generateFrameNumbers: jest.fn(() => []), + }, + + // Make (tilemap factory) + make: { + tilemap: jest.fn(() => ({ + addTilesetImage: jest.fn(() => ({})), + createLayer: jest.fn(() => ({ + setCollisionByProperty: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + getTileAtWorldXY: jest.fn(() => ({ x: 5, y: 5, index: 0, z: 0 })), + getTilesWithinShape: jest.fn(() => [{ x: 0, y: 0 }]), + tileToWorldXY: jest.fn((x, y) => ({ x: x * 32, y: y * 32 })), + })), + widthInPixels: 640, + heightInPixels: 480, + worldToTileXY: jest.fn(() => ({ x: 0, y: 0 })), + tileToWorldXY: jest.fn(() => ({ x: 0, y: 0 })), + })), + }, + + // Scene-specific properties (added by create) + map: null, + groundLayer: null, + rockLayer: null, + goodGuys: null, + infantry: null, + interface: null, + orchestrator: { + registerBuilding: jest.fn((building, config) => ({ + building, + config, + getState: jest.fn(() => 'ACTIVE'), + destroy: jest.fn(), + })), + unregisterBuilding: jest.fn(), + systems: {}, + }, + tints: {}, + }; + + return mockScene; +} + +// ── Tests ───────────────────────────────────────────────────────── + +describe('Map_Player — F-key spawn', () => { + let scene; + let mapPlayer; + + beforeEach(() => { + jest.clearAllMocks(); + scene = buildMockScene(); + + // Instantiate Map_Player with our mock scene + mapPlayer = new Map_Player(); + // Monkey-patch the scene reference (Phaser does this internally) + mapPlayer.scene = scene; + // Attach scene properties that create() would set + Object.assign(mapPlayer, { + add: scene.add, + input: scene.input, + cameras: scene.cameras, + make: scene.make, + physics: scene.physics, + events: scene.events, + tweens: scene.tweens, + anims: scene.anims, + game: scene.game, + }); + + // Stub createMap to set the layers + mapPlayer.createMap = jest.fn(function () { + this.map = this.make.tilemap({ key: 'test1' }); + this.groundLayer = this.map.createLayer('Floor', {}, 0, 0); + this.rockLayer = this.map.createLayer('Rocks', {}, 0, 0).setCollisionByProperty({ collides: true }).setDepth(10); + }); + + // Stub createInfantry to do nothing (test F-key spawn instead) + mapPlayer.createInfantry = jest.fn(); + mapPlayer.createFriendlyPlatoon = jest.fn(); + mapPlayer.createFriendlyInfantry = jest.fn(); + }); + + test('F-key spawns unit in scene', () => { + // Call create() — this registers the F-key handler + mapPlayer.create(); + + // Verify F-key handler was registered + expect(scene.input.keyboard.on).toHaveBeenCalledWith( + 'keydown-F', + expect.any(Function), + ); + + // Simulate F keypress + scene.input.keyboard._fire('keydown-F'); + + // spawnTestUnit should have created an infantry and added it to physics + // The mock Ukrainian_Rifle constructor was called + const Ukrainian_Rifle = require('Entities/skins/ukrainian-infantry').default; + expect(Ukrainian_Rifle).toHaveBeenCalled(); + + // The unit should have been added to the physics group via physics.add.existing + expect(scene.physics.add.existing).toHaveBeenCalled(); + }); + + test('spawned unit is selectable', () => { + mapPlayer.create(); + + // Simulate F keypress + scene.input.keyboard._fire('keydown-F'); + + // The spawned unit should have a physics body so SelectionSystem can find it + // Mock the unit's physics body + const Ukrainian_Rifle = require('Entities/skins/ukrainian-infantry').default; + + // Get the last created unit mock + const lastCall = Ukrainian_Rifle.mock.results[Ukrainian_Rifle.mock.results.length - 1]; + const unit = lastCall ? lastCall.value : null; + + expect(unit).toBeDefined(); + // Unit must have a body (set by physics.add.existing or scene.physics.world.enableBody) + // Verify physics.add.existing was called with the unit + expect(scene.physics.add.existing).toHaveBeenCalledWith(unit); + }); +}); + +describe('Interface — init(false)', () => { + test('init(false) does NOT wire pointer DOWN/MOVE/UP or create pathfinder', () => { + const scene = buildMockScene(); + // createCamera() needs scene.map to be set for setBounds + scene.map = { widthInPixels: 640, heightInPixels: 480 }; + const iface = new Interface(scene); + + // Call init with useLegacyPointers=false + iface.init(false); + + // Should NOT have registered pointer DOWN/MOVE/UP + const inputCalls = scene.input.on.mock.calls.map((c) => c[0]); + expect(inputCalls).not.toContain('pointerdown'); + expect(inputCalls).not.toContain('pointermove'); + expect(inputCalls).not.toContain('pointerup'); + + // Should still register POINTER_WHEEL (for zoom) + const wheelEvent = 'wheel'; + expect(inputCalls).toContain(wheelEvent); + + // Should NOT have a pathfinder + expect(iface.pathfinder).toBeUndefined(); + }); +}); diff --git a/tests/Unit.test.js b/tests/Unit.test.js index 43bb149..52a63d3 100644 --- a/tests/Unit.test.js +++ b/tests/Unit.test.js @@ -25,6 +25,14 @@ const createMockScene = () => ({ interface: { generateWorldXY: jest.fn(tile => ({ x: tile.x * 64, y: tile.y * 64 })) }, + teamManager: { + getTeamColor: jest.fn(() => 0x1d7196), + getTeamUnits: jest.fn(() => new Set()), + getAllUnits: jest.fn(() => []), + getEnemyUnits: jest.fn(() => new Set()), + isEnemy: jest.fn(() => false), + isSameTeam: jest.fn(() => true), + }, orchestrator: { systems: { EntityStateMachine, @@ -58,7 +66,7 @@ describe('Unit', () => { maxHp: 100, armor: 5, playerId: 'player1', - team: 'good', + teamId: 'good', weaponRange: 200, damage: 25 }); @@ -77,7 +85,7 @@ describe('Unit', () => { const owner = unit.getComponent('owner'); expect(owner.playerId).toBe('player1'); - expect(owner.team).toBe('good'); + expect(owner.teamId).toBe('good'); }); it('should have combat component', () => { diff --git a/tests/VictoryScene.test.js b/tests/VictoryScene.test.js new file mode 100644 index 0000000..e6fa45b --- /dev/null +++ b/tests/VictoryScene.test.js @@ -0,0 +1,228 @@ +/** + * VictoryScene Unit Tests + */ + +// Minimal Phaser Scene mock +jest.mock('phaser', () => ({ + Scene: class MockScene { + constructor(config) { + this.key = config?.key; + this.scene = { + start: jest.fn(), + stop: jest.fn(), + }; + this.events = { + emit: jest.fn(), + on: jest.fn(), + }; + this.add = { + text: jest.fn(() => mockTextObj()), + rectangle: jest.fn(() => mockRectObj()), + container: jest.fn(() => mockContainerObj()), + graphics: jest.fn(() => mockGraphicsObj()), + image: jest.fn(() => mockImageObj()), + }; + this.cameras = { + main: { width: 1920, height: 1080, centerX: 960, centerY: 540 }, + }; + this.input = { + on: jest.fn(), + off: jest.fn(), + }; + this.scale = { + baseSize: { width: 1920, height: 1080 }, + }; + this.game = { + loop: { delta: 1000 / 60 }, + }; + } + }, + GameObjects: { + Text: class {}, + Rectangle: class {}, + Container: class {}, + Graphics: class {}, + }, + Display: { + Color: { + GetColor: jest.fn(() => 0xffffff), + }, + }, +})); + +function mockTextObj() { + const obj = { + setOrigin: jest.fn(() => obj), + setPosition: jest.fn(() => obj), + setScale: jest.fn(() => obj), + setAlpha: jest.fn(() => obj), + setInteractive: jest.fn(() => obj), + setDepth: jest.fn(() => obj), + setStyle: jest.fn(() => obj), + setText: jest.fn(() => obj), + setVisible: jest.fn(() => obj), + destroy: jest.fn(), + on: jest.fn(() => obj), + once: jest.fn(() => obj), + off: jest.fn(() => obj), + x: 0, y: 0, + active: true, visible: true, + }; + return obj; +} + +function mockRectObj() { + const obj = { + setOrigin: jest.fn(() => obj), + setPosition: jest.fn(() => obj), + setFillStyle: jest.fn(() => obj), + setAlpha: jest.fn(() => obj), + setDepth: jest.fn(() => obj), + setInteractive: jest.fn(() => obj), + destroy: jest.fn(), + on: jest.fn(() => obj), + off: jest.fn(() => obj), + x: 0, y: 0, + active: true, + width: 1920, height: 1080, + }; + return obj; +} + +function mockContainerObj() { + const obj = { + add: jest.fn(() => obj), + setPosition: jest.fn(() => obj), + setOrigin: jest.fn(() => obj), + setAlpha: jest.fn(() => obj), + setDepth: jest.fn(() => obj), + setVisible: jest.fn(() => obj), + destroy: jest.fn(), + x: 0, y: 0, + active: true, visible: true, + list: [], + }; + return obj; +} + +function mockGraphicsObj() { + const obj = { + fillStyle: jest.fn(() => obj), + fillRect: jest.fn(() => obj), + setAlpha: jest.fn(() => obj), + setDepth: jest.fn(() => obj), + clear: jest.fn(() => obj), + destroy: jest.fn(), + }; + return obj; +} + +function mockImageObj() { + const obj = { + setOrigin: + jest.fn(() => obj), + setPosition: jest.fn(() => obj), + setScale: jest.fn(() => obj), + setAlpha: jest.fn(() => obj), + setDepth: jest.fn(() => obj), + setInteractive: jest.fn(() => obj), + destroy: jest.fn(), + on: jest.fn(() => obj), + x: 0, y: 0, + active: true, + }; + return obj; +} + +import VictoryScene from '../src/scenes/VictoryScene'; + +describe('VictoryScene', () => { + let scene; + + beforeEach(() => { + scene = new VictoryScene(); + }); + + describe('create', () => { + it('should create dark overlay background', () => { + scene.create({ winnerPlayerId: 'player1', localPlayerId: 'player1', stats: { elapsedMs: 120000, unitsKilled: 5, buildingsBuilt: 2, cpCaptured: 100 } }); + expect(scene.add.rectangle).toHaveBeenCalledWith(960, 540, 1920, 1080, 0x000000); + }); + + it('should show VICTORY when local player is winner', () => { + scene.create({ winnerPlayerId: 'player1', localPlayerId: 'player1', stats: { elapsedMs: 120000, unitsKilled: 5, buildingsBuilt: 2, cpCaptured: 100 } }); + const textCalls = scene.add.text.mock.calls; + const hasVictory = textCalls.some((c) => typeof c[2] === 'string' && c[2].includes('VICTORY')); + expect(hasVictory).toBe(true); + }); + + it('should show DEFEAT when local player is not winner', () => { + scene.create({ winnerPlayerId: 'player2', localPlayerId: 'player1', stats: { elapsedMs: 120000, unitsKilled: 5, buildingsBuilt: 2, cpCaptured: 100 } }); + const textCalls = scene.add.text.mock.calls; + const hasDefeat = textCalls.some((c) => typeof c[2] === 'string' && c[2].includes('DEFEAT')); + expect(hasDefeat).toBe(true); + }); + + it('should display elapsed time formatted as mm:ss', () => { + scene.create({ winnerPlayerId: 'player1', localPlayerId: 'player1', stats: { elapsedMs: 125000, unitsKilled: 5, buildingsBuilt: 2, cpCaptured: 100 } }); + const textCalls = scene.add.text.mock.calls; + const hasTime = textCalls.some((c) => { + const text = typeof c[2] === 'string' ? c[2] : c[2]?.text; + return typeof text === 'string' && /02:05/.test(text); + }); + expect(hasTime).toBe(true); + }); + + it('should display unit kill count', () => { + scene.create({ winnerPlayerId: 'player1', localPlayerId: 'player1', stats: { elapsedMs: 120000, unitsKilled: 7, buildingsBuilt: 2, cpCaptured: 100 } }); + const textCalls = scene.add.text.mock.calls; + const hasKills = textCalls.some((c) => { + const text = typeof c[2] === 'string' ? c[2] : c[2]?.text; + return typeof text === 'string' && text.includes('7'); + }); + expect(hasKills).toBe(true); + }); + + it('should display buildings built count', () => { + scene.create({ winnerPlayerId: 'player1', localPlayerId: 'player1', stats: { elapsedMs: 120000, unitsKilled: 5, buildingsBuilt: 4, cpCaptured: 100 } }); + const textCalls = scene.add.text.mock.calls; + const hasBuildings = textCalls.some((c) => { + const text = typeof c[2] === 'string' ? c[2] : c[2]?.text; + return typeof text === 'string' && text.includes('4'); + }); + expect(hasBuildings).toBe(true); + }); + + it('should display CP captured count', () => { + scene.create({ winnerPlayerId: 'player1', localPlayerId: 'player1', stats: { elapsedMs: 120000, unitsKilled: 5, buildingsBuilt: 2, cpCaptured: 100 } }); + const textCalls = scene.add.text.mock.calls; + const hasCp = textCalls.some((c) => { + const text = typeof c[2] === 'string' ? c[2] : c[2]?.text; + return typeof text === 'string' && text.includes('100'); + }); + expect(hasCp).toBe(true); + }); + + it('should create a Play Again button', () => { + scene.create({ winnerPlayerId: 'player1', localPlayerId: 'player1', stats: { elapsedMs: 120000, unitsKilled: 5, buildingsBuilt: 2, cpCaptured: 100 } }); + expect(scene.add.text).toHaveBeenCalled(); + const textCalls = scene.add.text.mock.calls; + const hasPlayAgain = textCalls.some((c) => { + const text = typeof c[2] === 'string' ? c[2] : c[2]?.text; + return typeof text === 'string' && text.toLowerCase().includes('play again'); + }); + expect(hasPlayAgain).toBe(true); + }); + }); + + describe('interaction', () => { + it('should launch Server_Connector scene on play again click', () => { + const data = { winnerPlayerId: 'player1', localPlayerId: 'player1', stats: { elapsedMs: 120000, unitsKilled: 5, buildingsBuilt: 2, cpCaptured: 100 } }; + scene.create(data); + + const startSpy = jest.spyOn(scene.scene, 'start'); + scene._onPlayAgain(); + expect(startSpy).toHaveBeenCalledWith('Server_Connector'); + }); + }); +}); diff --git a/tests/WinCondition.test.js b/tests/WinCondition.test.js new file mode 100644 index 0000000..4ab7ed7 --- /dev/null +++ b/tests/WinCondition.test.js @@ -0,0 +1,232 @@ +/** + * WinCondition Unit Tests + */ + +// Minimal Phaser Events mock +const createMockEventEmitter = () => ({ + emit: jest.fn(), + on: jest.fn(), + off: jest.fn(), + once: jest.fn(), +}); + +// Minimal EconomySystem mock +const createMockEconomySystem = (playerResources = {}) => { + const players = new Map(); + for (const [pid, res] of Object.entries(playerResources)) { + players.set(pid, res); + } + return { + events: createMockEventEmitter(), + players, + getResources: jest.fn((playerId) => players.get(playerId) ?? null), + }; +}; + +const createMockScene = () => ({ + events: createMockEventEmitter(), + time: { elapsed: 0 }, +}); + +import WinCondition from '../src/systems/WinCondition'; + +describe('WinCondition', () => { + let scene; + let economy; + let winCondition; + + beforeEach(() => { + scene = createMockScene(); + }); + + afterEach(() => { + if (winCondition) winCondition.destroy(); + }); + + describe('initialization', () => { + it('should set threshold to 100 by default', () => { + economy = createMockEconomySystem(); + winCondition = new WinCondition(scene, economy); + expect(winCondition.victoryThreshold).toBe(100); + }); + + it('should accept custom threshold', () => { + economy = createMockEconomySystem(); + winCondition = new WinCondition(scene, economy, { threshold: 50 }); + expect(winCondition.victoryThreshold).toBe(50); + }); + + it('should track game start time', () => { + economy = createMockEconomySystem(); + const before = Date.now(); + winCondition = new WinCondition(scene, economy); + const after = Date.now(); + expect(winCondition.stats.gameStartTime).toBeGreaterThanOrEqual(before); + expect(winCondition.stats.gameStartTime).toBeLessThanOrEqual(after); + }); + + it('should register combat:unitDamaged listener on economy events', () => { + economy = createMockEconomySystem(); + const onSpy = jest.spyOn(economy.events, 'on'); + winCondition = new WinCondition(scene, economy); + expect(onSpy).toHaveBeenCalledWith('combat:unitDamaged', expect.any(Function)); + }); + }); + + describe('victory detection', () => { + it('should emit game:victory when a player reaches threshold', () => { + economy = createMockEconomySystem({ + 'player1': { fuel: 100, ammo: 100, capturePoints: 100 }, + }); + winCondition = new WinCondition(scene, economy); + const emitSpy = jest.spyOn(scene.events, 'emit'); + + winCondition.update(16000); // simulate 16s elapsed + + expect(emitSpy).toHaveBeenCalledWith( + 'game:victory', + expect.objectContaining({ + winnerPlayerId: 'player1', + stats: expect.objectContaining({ + unitsKilled: expect.any(Number), + buildingsBuilt: expect.any(Number), + cpCaptured: expect.any(Number), + elapsedMs: expect.any(Number), + }), + }) + ); + }); + + it('should emit victory only once per game', () => { + economy = createMockEconomySystem({ + 'player1': { fuel: 100, ammo: 100, capturePoints: 100 }, + }); + winCondition = new WinCondition(scene, economy); + const emitSpy = jest.spyOn(scene.events, 'emit'); + + winCondition.update(16000); + winCondition.update(16016); + winCondition.update(16033); + + const victoryCalls = emitSpy.mock.calls.filter( + (call) => call[0] === 'game:victory' + ); + expect(victoryCalls.length).toBe(1); + }); + + it('should not emit victory when no player has reached threshold', () => { + economy = createMockEconomySystem({ + 'player1': { fuel: 100, ammo: 100, capturePoints: 99 }, + }); + winCondition = new WinCondition(scene, economy); + const emitSpy = jest.spyOn(scene.events, 'emit'); + + winCondition.update(16000); + + expect(emitSpy).not.toHaveBeenCalledWith('game:victory', expect.anything()); + expect(winCondition.victoryEmitted).toBe(false); + }); + + it('should detect victory for the correct player when multiple exist', () => { + economy = createMockEconomySystem({ + 'player1': { fuel: 100, ammo: 100, capturePoints: 50 }, + 'player2': { fuel: 100, ammo: 100, capturePoints: 100 }, + }); + winCondition = new WinCondition(scene, economy); + const emitSpy = jest.spyOn(scene.events, 'emit'); + + winCondition.update(16000); + + const call = emitSpy.mock.calls.find((c) => c[0] === 'game:victory'); + expect(call[1].winnerPlayerId).toBe('player2'); + }); + }); + + describe('stats tracking', () => { + it('should increment unitsKilled on combat:unitDamaged when target dies', () => { + economy = createMockEconomySystem(); + winCondition = new WinCondition(scene, economy); + + economy.events.on.mock.calls + .filter((c) => c[0] === 'combat:unitDamaged') + .forEach((c) => { + const handler = c[1]; + // simulate two units dying + handler({ target: { dead: true }, damage: 20 }); + handler({ target: { dead: true }, damage: 15 }); + }); + + expect(winCondition.stats.unitsKilled).toBe(2); + }); + + it('should not increment unitsKilled when target does not die', () => { + economy = createMockEconomySystem(); + winCondition = new WinCondition(scene, economy); + + economy.events.on.mock.calls + .filter((c) => c[0] === 'combat:unitDamaged') + .forEach((c) => { + const handler = c[1]; + handler({ target: { dead: false, getData: () => 50 }, damage: 10 }); + }); + + expect(winCondition.stats.unitsKilled).toBe(0); + }); + + it('should increment buildingsBuilt on building:spawned', () => { + economy = createMockEconomySystem(); + winCondition = new WinCondition(scene, economy); + + scene.events.on.mock.calls + .filter((c) => c[0] === 'building:spawned') + .forEach((c) => { + const handler = c[1]; + handler({}); + handler({}); + }); + + expect(winCondition.stats.buildingsBuilt).toBe(2); + }); + + it('should increment cpCaptured on economy:incomeReceived with capturePoints', () => { + economy = createMockEconomySystem(); + winCondition = new WinCondition(scene, economy); + + economy.events.on.mock.calls + .filter((c) => c[0] === 'economy:incomeReceived') + .forEach((c) => { + const handler = c[1]; + handler({ income: { capturePoints: 2 }, resources: { capturePoints: 10 } }); + handler({ income: { capturePoints: 3 }, resources: { capturePoints: 13 } }); + }); + + expect(winCondition.stats.cpCaptured).toBe(5); + }); + }); + + describe('elapsed time', () => { + it('should calculate elapsed time from game start to victory', () => { + economy = createMockEconomySystem({ + 'player1': { fuel: 100, ammo: 100, capturePoints: 100 }, + }); + winCondition = new WinCondition(scene, economy); + const emitSpy = jest.spyOn(scene.events, 'emit'); + + winCondition.update(120000); // 2 minutes + + const call = emitSpy.mock.calls.find((c) => c[0] === 'game:victory'); + // 120 seconds in ms + expect(call[1].stats.elapsedMs).toBe(120000); + }); + }); + + describe('cleanup', () => { + it('should remove listeners on destroy', () => { + economy = createMockEconomySystem(); + const offSpy = jest.spyOn(economy.events, 'off'); + winCondition = new WinCondition(scene, economy); + winCondition.destroy(); + expect(offSpy).toHaveBeenCalledWith('combat:unitDamaged', expect.any(Function)); + }); + }); +}); diff --git a/tests/e2e/debug-container-update.spec.js b/tests/e2e/debug-container-update.spec.js new file mode 100644 index 0000000..31553a8 --- /dev/null +++ b/tests/e2e/debug-container-update.spec.js @@ -0,0 +1,60 @@ +const { test, expect } = require('@playwright/test'); +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +const SCREENSHOT_DIR = path.join(__dirname, 'screenshots'); +const LOCAL_DIST = path.join(__dirname, '..', '..', 'dist'); + +function mapPathToFile(requestPath) { + const parsed = url.parse(requestPath); + let p = parsed.pathname; + if (p === '/' || p === '/index.html') return path.join(LOCAL_DIST, 'index.html'); + if (p.startsWith('/')) p = p.substring(1); + const candidate = path.join(LOCAL_DIST, p); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; + return path.join(LOCAL_DIST, 'index.html'); +} + +function contentTypeFor(p) { + const ext = path.extname(p).toLowerCase(); + const map = { '.js': 'application/javascript', '.html': 'text/html', '.json': 'application/json', '.tmj': 'application/json' }; + return map[ext] || 'application/octet-stream'; +} + +async function setupRoutes(page) { + await page.route(/https:\/\/restitution\.damascusfront\.net(\/.*)?/, async (route) => { + const reqUrl = route.request().url(); + const pathname = url.parse(reqUrl).pathname; + if (pathname.startsWith('/api/') || pathname.startsWith('/matchmake/')) { await route.continue(); return; } + const filePath = mapPathToFile(pathname); + try { await route.fulfill({ status: 200, contentType: contentTypeFor(filePath), body: fs.readFileSync(filePath) }); } + catch (e) { await route.fulfill({ status: 404, body: 'Not found' }); } + }); +} + +async function bootstrapGame(page) { + await page.goto('https://restitution.damascusfront.net', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.click('button:has-text("CREATE GAME")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); +} + +test('container runChildUpdate probe', async ({ page }) => { + await setupRoutes(page); + await bootstrapGame(page); + + const result = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const u = scene.goodGuys.list.find(x => x.active && x.body); + return { + runChildUpdate: scene.goodGuys.runChildUpdate, + unitName: u?.name, + unitState: u?.state?.key, + }; + }); + console.log(JSON.stringify(result, null, 2)); + expect(result.runChildUpdate).toBe(true); +}); diff --git a/tests/e2e/debug-local.js b/tests/e2e/debug-local.js new file mode 100644 index 0000000..02d3c56 --- /dev/null +++ b/tests/e2e/debug-local.js @@ -0,0 +1,116 @@ +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const { chromium } = require('playwright'); + +const DIST = path.join(__dirname, '..', 'dist'); +const PROXY_TARGET = 'restitution.damascusfront.net'; + +const mime = { + '.js': 'application/javascript', '.css': 'text/css', + '.html': 'text/html', '.png': 'image/png', '.json': 'application/json', + '.tmj': 'application/json', '.ico': 'image/x-icon', '.woff2': 'font/woff2', +}; + +const server = http.createServer((req, res) => { + let file = path.join(DIST, req.url === '/' ? 'index.html' : req.url); + if (!fs.existsSync(file) || fs.statSync(file).isDirectory()) { + file = path.join(DIST, 'index.html'); + } + fs.readFile(file, (err, data) => { + if (err) { + res.writeHead(404); res.end('Not found'); return; + } + res.writeHead(200, { 'Content-Type': mime[path.extname(file)] || 'application/octet-stream' }); + res.end(data); + }); +}); + +server.listen(8888, async () => { + console.log('Local server on http://localhost:8888'); + + const browser = await chromium.launch({ executablePath: '/usr/bin/chromium', headless: true }); + const context = await browser.newContext({ viewport: { width: 1280, height: 720 } }); + const page = await context.newPage(); + + // Proxy API + WS to live backend + await page.route(/http:\/\/localhost:8888\/api\/.*/, async (route) => { + const req = route.request(); + const url = req.url().replace('http://localhost:8888', 'https://' + PROXY_TARGET); + await route.continue({ url }); + }); + + page.on('console', msg => console.log('CONSOLE:', msg.type(), msg.text())); + page.on('pageerror', err => console.log('PAGEERROR:', err.message)); + + // Expose window.game via Phaser proxy + await page.addInitScript(() => { + let _phaser = undefined; + Object.defineProperty(window, 'Phaser', { + configurable: true, enumerable: true, + get() { return _phaser; }, + set(val) { + _phaser = val; + if (val && val.Game) { + const OrigGame = val.Game; + val.Game = function PhaserGameProxy(...args) { + const instance = Reflect.construct(OrigGame, args, new.target || OrigGame); + window.game = instance; + return instance; + }; + Object.keys(OrigGame).forEach(k => { val.Game[k] = OrigGame[k]; }); + val.Game.prototype = OrigGame.prototype; + } + }, + }); + }); + + await page.goto('http://localhost:8888', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.waitForTimeout(2000); + await page.screenshot({ path: '/tmp/local-landing.png', fullPage: true }); + + const buttons = await page.locator('button').allInnerTexts(); + console.log('Buttons found:', buttons); + + try { + await page.click('button:has-text("Create Game")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); + + const info = await page.evaluate(() => { + const g = window.game; + if (!g) return { error: 'no window.game' }; + const scene = g.scene.getScene('Map_Player'); + if (!scene) return { error: 'no Map_Player', scenes: g.scene.scenes.map(s => s.sys?.config?.key || 'unknown') }; + const list = scene.goodGuys?.list || []; + return { + hasGoodGuys: !!scene.goodGuys, + goodGuysLength: list.length, + hasOrchestrator: !!scene.orchestrator, + selectionCount: scene.orchestrator?.systems?.selection?.count || 0, + }; + }); + console.log('INFO:', JSON.stringify(info, null, 2)); + + await page.keyboard.press('KeyF'); + await page.waitForTimeout(1000); + + const afterF = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const list = scene.goodGuys?.list || []; + return { + goodGuysLength: list.length, + units: list.map(u => ({ name: u.name, active: u.active, x: u.x, y: u.y })), + }; + }); + console.log('AFTER F:', JSON.stringify(afterF, null, 2)); + } catch (e) { + console.log('ERROR during test flow:', e.message); + } + + await page.screenshot({ path: '/tmp/local-final.png', fullPage: false }); + await browser.close(); + server.close(); +}); diff --git a/tests/e2e/debug-local2.js b/tests/e2e/debug-local2.js new file mode 100644 index 0000000..608b0b4 --- /dev/null +++ b/tests/e2e/debug-local2.js @@ -0,0 +1,81 @@ +const { chromium } = require('playwright'); + +(async () => { + const browser = await chromium.launch({ executablePath: '/usr/bin/chromium', headless: true }); + const page = await browser.newPage({ viewport: { width: 1280, height: 720 } }); + + page.on('console', msg => console.log('CONSOLE', msg.type(), msg.text())); + page.on('pageerror', err => console.log('PAGEERROR', err.message)); + + // Expose window.game via Phaser proxy + await page.addInitScript(() => { + let _phaser = undefined; + Object.defineProperty(window, 'Phaser', { + configurable: true, enumerable: true, + get() { return _phaser; }, + set(val) { + _phaser = val; + if (val && val.Game) { + const OrigGame = val.Game; + val.Game = function PhaserGameProxy(...args) { + const instance = Reflect.construct(OrigGame, args, new.target || OrigGame); + window.game = instance; + return instance; + }; + Object.keys(OrigGame).forEach(k => { val.Game[k] = OrigGame[k]; }); + val.Game.prototype = OrigGame.prototype; + } + }, + }); + }); + + await page.goto('http://localhost:8888', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.waitForTimeout(2000); + + const html = await page.content(); + fs = require('fs'); + fs.writeFileSync('/tmp/local-landing.html', html); + + const buttons = await page.locator('button').allInnerTexts(); + console.log('Buttons:', buttons); + + // Click Create Game + await page.click('button:has-text("Create Game")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); + + const info = await page.evaluate(() => { + const g = window.game; + if (!g) return { error: 'no window.game' }; + const scene = g.scene.getScene('Map_Player'); + if (!scene) return { error: 'no Map_Player', scenes: g.scene.scenes.map(s => s.sys?.config?.key || 'unknown') }; + const list = scene.goodGuys?.list || []; + return { + hasGoodGuys: !!scene.goodGuys, + goodGuysLength: list.length, + hasOrchestrator: !!scene.orchestrator, + selectionCount: scene.orchestrator?.systems?.selection?.count || 0, + units: list.slice(0,3).map(u => ({ name: u.name, type: u.type || 'unknown', active: u.active })), + }; + }); + console.log('INFO:', JSON.stringify(info, null, 2)); + + // Press F + await page.keyboard.press('KeyF'); + await page.waitForTimeout(1000); + + const afterF = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const list = scene.goodGuys?.list || []; + return { + goodGuysLength: list.length, + units: list.map(u => ({ name: u.name, active: u.active, x: Math.round(u.x), y: Math.round(u.y) })), + }; + }); + console.log('AFTER F:', JSON.stringify(afterF, null, 2)); + + await page.screenshot({ path: '/tmp/local-final.png', fullPage: false }); + await browser.close(); +})(); diff --git a/tests/e2e/debug-move-logged.spec.js b/tests/e2e/debug-move-logged.spec.js new file mode 100644 index 0000000..01e8fe6 --- /dev/null +++ b/tests/e2e/debug-move-logged.spec.js @@ -0,0 +1,185 @@ +const { test, expect } = require('@playwright/test'); +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +const SCREENSHOT_DIR = path.join(__dirname, 'screenshots'); +const LOCAL_DIST = path.join(__dirname, '..', '..', 'dist'); + +function mapPathToFile(requestPath) { + const parsed = url.parse(requestPath); + let p = parsed.pathname; + if (p === '/' || p === '/index.html') return path.join(LOCAL_DIST, 'index.html'); + if (p.startsWith('/')) p = p.substring(1); + const candidate = path.join(LOCAL_DIST, p); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; + return path.join(LOCAL_DIST, 'index.html'); +} + +function contentTypeFor(p) { + const ext = path.extname(p).toLowerCase(); + const map = { + '.js': 'application/javascript', '.css': 'text/css', + '.html': 'text/html', '.png': 'image/png', + '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', + '.svg': 'image/svg+xml', '.json': 'application/json', + '.tmj': 'application/json', '.ico': 'image/x-icon', + }; + return map[ext] || 'application/octet-stream'; +} + +async function setupRoutes(page) { + await page.route(/https:\/\/restitution\.damascusfront\.net(\/.*)?/, async (route) => { + const reqUrl = route.request().url(); + const parsed = url.parse(reqUrl); + const pathname = parsed.pathname; + if (pathname.startsWith('/api/') || pathname.startsWith('/matchmake/')) { + return route.continue(); + } + const filePath = mapPathToFile(pathname); + try { + const body = fs.readFileSync(filePath); + await route.fulfill({ status: 200, contentType: contentTypeFor(filePath), body }); + } catch (e) { + await route.fulfill({ status: 404, body: 'Not found' }); + } + }); +} + +async function bootstrapGame(page) { + await page.goto('https://restitution.damascusfront.net', { + waitUntil: 'domcontentloaded', timeout: 15000, + }); + await page.click('button:has-text("CREATE GAME")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); +} + +test('Debug move with console logging', async ({ page }) => { + page.on('console', msg => { + console.log(`[console ${msg.type()}] ${msg.text()}`); + }); + page.on('pageerror', err => { + console.log(`[pageerror] ${err.message}`); + }); + + await setupRoutes(page); + await page.addInitScript(() => { + let _phaser = undefined; + Object.defineProperty(window, 'Phaser', { + configurable: true, enumerable: true, + get() { return _phaser; }, + set(val) { + _phaser = val; + if (val && val.Game) { + const OrigGame = val.Game; + val.Game = function PhaserGameProxy(...args) { + const instance = Reflect.construct(OrigGame, args, new.target || OrigGame); + window.game = instance; + return instance; + }; + Object.keys(OrigGame).forEach(k => { val.Game[k] = OrigGame[k]; }); + val.Game.prototype = OrigGame.prototype; + } + }, + }); + }); + + await bootstrapGame(page); + + await page.keyboard.press('KeyF'); + await page.waitForTimeout(1000); + + // Gather unit + scene diagnostics BEFORE right-click + const before = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const cam = scene.cameras.main; + const units = scene.goodGuys?.list || []; + for (const u of units) { + if (u.active && u.body) { + return { + worldX: u.x, + worldY: u.y, + screenX: u.x - cam.scrollX, + screenY: u.y - cam.scrollY, + hasPath: !!u.data?.get('path'), + pathLength: u.data?.get('path')?.length || 0, + selectedCount: scene.orchestrator?.systems?.selection?.count ?? -1, + pathfindingExists: !!scene.orchestrator?.systems?.pathfinding, + }; + } + } + return null; + }); + console.log('BEFORE right-click:', JSON.stringify(before, null, 2)); + + // If no selected unit, click it first to select + if (before && before.selectedCount === 0) { + await page.mouse.click(before.screenX, before.screenY); + await page.waitForTimeout(300); + const afterSelect = await page.evaluate(() => ({ + selectedCount: window.game.scene.getScene('Map_Player').orchestrator?.systems?.selection?.count ?? -1, + })); + console.log('After select click:', JSON.stringify(afterSelect)); + } + + // Right click 3 tiles away + const rightClickX = before.screenX + 96; + const rightClickY = before.screenY + 96; + console.log(`Right-click at screen ${rightClickX}, ${rightClickY}`); + await page.mouse.click(rightClickX, rightClickY, { button: 'right' }); + await page.waitForTimeout(2000); + + // Interim check + const interim = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const units = scene.goodGuys?.list || []; + for (const u of units) { + if (u.active && u.body) { + return { + worldX: u.x, + worldY: u.y, + pathLength: u.data?.get('path')?.length || 0, + }; + } + } + return null; + }); + console.log('INTERIM (2s after right-click):', JSON.stringify(interim)); + + await page.waitForTimeout(2000); + + // Final check + const after = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const units = scene.goodGuys?.list || []; + for (const u of units) { + if (u.active && u.body) { + return { + worldX: u.x, + worldY: u.y, + pathLength: u.data?.get('path')?.length || 0, + stateKey: u.stateMachine?.getState?.() || 'no-machine', + }; + } + } + return null; + }); + console.log('AFTER (4s total):', JSON.stringify(after)); + + const dist = Math.sqrt( + Math.pow(after.worldX - before.worldX, 2) + + Math.pow(after.worldY - before.worldY, 2) + ); + console.log(`Distance moved: ${dist.toFixed(1)}px`); + + await page.screenshot({ + path: path.join(SCREENSHOT_DIR, 'debug-move-logged.png'), + fullPage: false, + }); + + // Pass regardless — this is a debug/diagnostic test + expect(true).toBe(true); +}); diff --git a/tests/e2e/debug-move.spec.js b/tests/e2e/debug-move.spec.js new file mode 100644 index 0000000..ab8c998 --- /dev/null +++ b/tests/e2e/debug-move.spec.js @@ -0,0 +1,197 @@ +const { test, expect } = require('@playwright/test'); +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +const LOCAL_DIST = path.join(__dirname, '..', '..', 'dist'); + +function mapPathToFile(requestPath) { + const parsed = url.parse(requestPath); + let p = parsed.pathname; + if (p === '/' || p === '/index.html') return path.join(LOCAL_DIST, 'index.html'); + if (p.startsWith('/')) p = p.substring(1); + const candidate = path.join(LOCAL_DIST, p); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; + return path.join(LOCAL_DIST, 'index.html'); +} + +function contentTypeFor(p) { + const ext = path.extname(p).toLowerCase(); + const map = { + '.js': 'application/javascript', '.css': 'text/css', '.html': 'text/html', + '.png': 'image/png', '.jpg': 'image/jpeg', '.json': 'application/json', + '.tmj': 'application/json', + }; + return map[ext] || 'application/octet-stream'; +} + +async function setupRoutes(page) { + await page.route(/https:\/\/restitution\.damascusfront\.net(\/.*)?/, async (route) => { + const reqUrl = route.request().url(); + const parsed = url.parse(reqUrl); + const pathname = parsed.pathname; + if (pathname.startsWith('/api/') || pathname.startsWith('/matchmake/')) { + await route.continue(); + return; + } + const filePath = mapPathToFile(pathname); + try { + const body = fs.readFileSync(filePath); + await route.fulfill({ status: 200, contentType: contentTypeFor(filePath), body }); + } catch (e) { + await route.fulfill({ status: 404, body: 'Not found' }); + } + }); +} + +async function bootstrapGame(page) { + await page.goto('https://restitution.damascusfront.net', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.click('button:has-text("CREATE GAME")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); +} + +test('debug right-click movement', async ({ page }) => { + page.on('console', msg => console.log(`[PAGE ${msg.type()}]`, msg.text())); + page.on('pageerror', err => console.log('[PAGEERROR]', err.message)); + + await setupRoutes(page); + + await page.addInitScript(() => { + let _phaser = undefined; + Object.defineProperty(window, 'Phaser', { + configurable: true, enumerable: true, + get() { return _phaser; }, + set(val) { + _phaser = val; + if (val && val.Game) { + const OrigGame = val.Game; + val.Game = function PhaserGameProxy(...args) { + const instance = Reflect.construct(OrigGame, args, new.target || OrigGame); + window.game = instance; + return instance; + }; + Object.keys(OrigGame).forEach(k => { val.Game[k] = OrigGame[k]; }); + val.Game.prototype = OrigGame.prototype; + } + }, + }); + }); + + await bootstrapGame(page); + + // Spawn unit + await page.keyboard.press('KeyF'); + await page.waitForTimeout(800); + + const unitInfo = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + if (!scene.goodGuys) return null; + const cam = scene.cameras.main; + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active && u.body) { + return { wx: u.x, wy: u.y, sx: u.x - cam.scrollX, sy: u.y - cam.scrollY, name: u.name }; + } + } + return null; + }); + console.log('UNIT INFO:', JSON.stringify(unitInfo)); + expect(unitInfo).not.toBeNull(); + + // Click to select + await page.mouse.click(unitInfo.sx, unitInfo.sy); + await page.waitForTimeout(400); + + const selAfterClick = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const sel = scene.orchestrator?.systems?.selection; + return { + count: sel ? sel.count : -1, + selectedNames: sel ? Array.from(sel.selected).map(e => e.name) : [], + }; + }); + console.log('SELECTION AFTER CLICK:', JSON.stringify(selAfterClick)); + + // Add diagnostic listener for pointerdown + const pointerEventsBefore = await page.evaluate(() => { + if (!window._pointerDebug) window._pointerDebug = []; + return window._pointerDebug; + }); + + // Inject listener into Phaser + await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + if (scene && scene.input) { + scene.input.on('pointerdown', (pointer, currentlyOver) => { + window._pointerDebug = window._pointerDebug || []; + window._pointerDebug.push({ + type: 'pointerdown', + button: pointer.button, + rightButtonDown: pointer.rightButtonDown(), + worldX: pointer.worldX, + worldY: pointer.worldY, + x: pointer.x, + y: pointer.y, + currentlyOverLength: currentlyOver ? currentlyOver.length : 0, + tile: scene.interface ? scene.interface.getTileAtPointerXY(pointer) : null, + }); + }); + } + }); + + // Right-click move + const targetX = unitInfo.sx + 96; + const targetY = unitInfo.sy + 96; + console.log(`Right-clicking at screen ${targetX},${targetY}`); + + await page.mouse.click(targetX, targetY, { button: 'right' }); + await page.waitForTimeout(3000); + + const pointerEvents = await page.evaluate(() => window._pointerDebug || []); + console.log('POINTER EVENTS:', JSON.stringify(pointerEvents, null, 2)); + + // Check selection command queue + const cmdQueue = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const sel = scene.orchestrator?.systems?.selection; + return sel ? sel.commandQueue : []; + }); + console.log('COMMAND QUEUE:', JSON.stringify(cmdQueue)); + + // Check unit path data + const unitPathData = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active) { + return { + name: u.name, + x: u.x, + y: u.y, + path: u.getData('path'), + state: u.state?.key || null, + }; + } + } + return null; + }); + console.log('UNIT PATH DATA:', JSON.stringify(unitPathData)); + + const newPos = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active && u.body) return { x: u.x, y: u.y }; + } + return null; + }); + console.log('NEW POS:', JSON.stringify(newPos)); + + if (newPos && unitInfo) { + const dist = Math.sqrt((newPos.x - unitInfo.wx)**2 + (newPos.y - unitInfo.wy)**2); + console.log('DISTANCE MOVED:', dist); + } +}); diff --git a/tests/e2e/debug-movetile.spec.js b/tests/e2e/debug-movetile.spec.js new file mode 100644 index 0000000..4c92f05 --- /dev/null +++ b/tests/e2e/debug-movetile.spec.js @@ -0,0 +1,125 @@ +const { test, expect } = require('@playwright/test'); +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +const SCREENSHOT_DIR = path.join(__dirname, 'screenshots'); +const LOCAL_DIST = path.join(__dirname, '..', '..', 'dist'); + +function mapPathToFile(requestPath) { + const parsed = url.parse(requestPath); + let p = parsed.pathname; + if (p === '/' || p === '/index.html') return path.join(LOCAL_DIST, 'index.html'); + if (p.startsWith('/')) p = p.substring(1); + const candidate = path.join(LOCAL_DIST, p); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; + return path.join(LOCAL_DIST, 'index.html'); +} + +function contentTypeFor(p) { + const ext = path.extname(p).toLowerCase(); + const map = { + '.js': 'application/javascript', + '.css': 'text/css', + '.html': 'text/html', + '.png': 'image/png', + '.json': 'application/json', + '.tmj': 'application/json', + }; + return map[ext] || 'application/octet-stream'; +} + +async function setupRoutes(page) { + await page.route(/https:\/\/restitution\.damascusfront\.net(\/.*)?/, async (route) => { + const reqUrl = route.request().url(); + const parsed = url.parse(reqUrl); + const pathname = parsed.pathname; + if (pathname.startsWith('/api/') || pathname.startsWith('/matchmake/')) { + await route.continue(); + return; + } + const filePath = mapPathToFile(pathname); + try { + const body = fs.readFileSync(filePath); + await route.fulfill({ status: 200, contentType: contentTypeFor(filePath), body }); + } catch (e) { + await route.fulfill({ status: 404, body: 'Not found' }); + } + }); +} + +async function bootstrapGame(page) { + await page.goto('https://restitution.damascusfront.net', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.click('button:has-text("CREATE GAME")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); +} + +test('direct moveToTile smoke test', async ({ page }) => { + await setupRoutes(page); + await bootstrapGame(page); + + const result = await page.evaluate(() => { + try { + const scene = window.game.scene.getScene('Map_Player'); + const u = scene.goodGuys.list.find(x => x.active && x.body); + if (!u) return { error: 'no unit' }; + + const before = { x: u.x, y: u.y }; + const tile = scene.groundLayer.getTileAtWorldXY(1020, 457); + if (!tile) return { error: 'no tile at 1020,457' }; + u.moveToTile(tile); + const after = { x: u.x, y: u.y }; + return { before, after, tile: { x: tile.x, y: tile.y, index: tile.index } }; + } catch (e) { + return { error: e.message, stack: e.stack }; + } + }); + console.log(JSON.stringify(result, null, 2)); + expect(result.error).toBeUndefined(); + const dist = Math.sqrt((result.after.x - result.before.x) ** 2 + (result.after.y - result.before.y) ** 2); + console.log('moveToTile distance:', dist.toFixed(1)); + expect(dist).toBeGreaterThanOrEqual(0); // just verify it doesn't crash +}); + +test('direct nextPath smoke test', async ({ page }) => { + await setupRoutes(page); + await bootstrapGame(page); + + const result = await page.evaluate(() => { + try { + const scene = window.game.scene.getScene('Map_Player'); + const u = scene.goodGuys.list.find(x => x.active && x.body); + if (!u) return { error: 'no unit' }; + + const before = { x: u.x, y: u.y }; + // Give it a small manual path of 3 tiles horizontally + const tile = scene.groundLayer.getTileAtWorldXY(1020, 457); + if (!tile) return { error: 'no tile' }; + const path = [ + { x: tile.x, y: tile.y }, + { x: tile.x + 1, y: tile.y }, + { x: tile.x + 2, y: tile.y }, + ]; + u.setData('path', path); + u.ACTIONS.MOVE(); + // simulate 3 ticks manually + for (let i = 0; i < 3; i++) { + u.movement._frameTime = 9999; // force shouldUpdate true + u.state.updateFunction(u, 0, 1000); + } + const after = { x: u.x, y: u.y }; + return { before, after, pathAfter: u.getData('path'), state: u.state?.key }; + } catch (e) { + return { error: e.message, stack: e.stack }; + } + }); + console.log(JSON.stringify(result, null, 2)); + expect(result.error).toBeUndefined(); + expect(result.state).toBe('MOVING'); + const dist = Math.sqrt((result.after.x - result.before.x) ** 2 + (result.after.y - result.before.y) ** 2); + console.log('nextPath distance:', dist.toFixed(1)); + expect(dist).toBeGreaterThan(0); +}); diff --git a/tests/e2e/debug-rightclick-chain.spec.js b/tests/e2e/debug-rightclick-chain.spec.js new file mode 100644 index 0000000..108c784 --- /dev/null +++ b/tests/e2e/debug-rightclick-chain.spec.js @@ -0,0 +1,190 @@ +const { test, expect } = require('@playwright/test'); +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +const LOCAL_DIST = path.join(__dirname, '..', '..', 'dist'); + +function mapPathToFile(requestPath) { + const parsed = url.parse(requestPath); + let p = parsed.pathname; + if (p === '/' || p === '/index.html') return path.join(LOCAL_DIST, 'index.html'); + if (p.startsWith('/')) p = p.substring(1); + const candidate = path.join(LOCAL_DIST, p); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; + return path.join(LOCAL_DIST, 'index.html'); +} + +function contentTypeFor(p) { + const ext = path.extname(p).toLowerCase(); + const map = { + '.js': 'application/javascript', '.css': 'text/css', '.html': 'text/html', + '.png': 'image/png', '.jpg': 'image/jpeg', '.json': 'application/json', + '.tmj': 'application/json', + }; + return map[ext] || 'application/octet-stream'; +} + +async function setupRoutes(page) { + await page.route(/https:\/\/restitution\.damascusfront\.net(\/.*)?/, async (route) => { + const reqUrl = route.request().url(); + const parsed = url.parse(reqUrl); + const pathname = parsed.pathname; + if (pathname.startsWith('/api/') || pathname.startsWith('/matchmake/')) { + await route.continue(); + return; + } + const filePath = mapPathToFile(pathname); + try { + const body = fs.readFileSync(filePath); + await route.fulfill({ status: 200, contentType: contentTypeFor(filePath), body }); + } catch (e) { + await route.fulfill({ status: 404, body: 'Not found' }); + } + }); +} + +async function bootstrapGame(page) { + await page.goto('https://restitution.damascusfront.net', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.click('button:has-text("CREATE GAME")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); +} + +test('debug right-click chain', async ({ page }) => { + page.on('console', msg => console.log(`[PAGE ${msg.type()}]`, msg.text())); + page.on('pageerror', err => console.log('[PAGEERROR]', err.message)); + + await setupRoutes(page); + + await page.addInitScript(() => { + let _phaser = undefined; + Object.defineProperty(window, 'Phaser', { + configurable: true, enumerable: true, + get() { return _phaser; }, + set(val) { + _phaser = val; + if (val && val.Game) { + const OrigGame = val.Game; + val.Game = function PhaserGameProxy(...args) { + const instance = Reflect.construct(OrigGame, args, new.target || OrigGame); + window.game = instance; + return instance; + }; + Object.keys(OrigGame).forEach(k => { val.Game[k] = OrigGame[k]; }); + val.Game.prototype = OrigGame.prototype; + } + }, + }); + }); + + await bootstrapGame(page); + + // Spawn unit + await page.keyboard.press('KeyF'); + await page.waitForTimeout(800); + + const unitInfo = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + if (!scene.goodGuys) return null; + const cam = scene.cameras.main; + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active && u.body) { + return { wx: u.x, wy: u.y, sx: u.x - cam.scrollX, sy: u.y - cam.scrollY, name: u.name }; + } + } + return null; + }); + console.log('UNIT INFO:', JSON.stringify(unitInfo)); + expect(unitInfo).not.toBeNull(); + + // Select unit + await page.mouse.click(unitInfo.sx, unitInfo.sy); + await page.waitForTimeout(400); + + const selBefore = await page.evaluate(() => { + const sel = window.game.scene.getScene('Map_Player').orchestrator?.systems?.selection; + return { count: sel ? sel.count : -1, names: sel ? Array.from(sel.selected).map(e => e.name) : [] }; + }); + console.log('SELECTION BEFORE RIGHT-CLICK:', JSON.stringify(selBefore)); + + // Right-click target + const targetSX = unitInfo.sx + 96; + const targetSY = unitInfo.sy + 96; + console.log(`RIGHT-CLICK at screen ${targetSX},${targetSY}`); + + // Patch handleRightClick temporarily to log args + await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const sel = scene.orchestrator.systems.selection; + const orig = sel.handleRightClick.bind(sel); + sel.handleRightClick = function(pointer, currentlyOver) { + window._rhcDebug = { + called: true, + pointerRightDown: pointer.rightButtonDown ? pointer.rightButtonDown() : 'n/a', + pointerButton: pointer.button, + currentlyOverLen: currentlyOver ? currentlyOver.length : -1, + selectedSizeBefore: sel.selected.size, + }; + return orig(pointer, currentlyOver); + }; + }); + + await page.mouse.click(targetSX, targetSY, { button: 'right' }); + await page.waitForTimeout(500); + + const rhcDebug = await page.evaluate(() => window._rhcDebug || { called: false }); + console.log('RHC DEBUG:', JSON.stringify(rhcDebug)); + + // Check command queue + const cmdQueue = await page.evaluate(() => { + const sel = window.game.scene.getScene('Map_Player').orchestrator?.systems?.selection; + return sel ? sel.commandQueue.map(c => ({ type: c.type, target: JSON.stringify(c.target) })) : []; + }); + console.log('COMMAND QUEUE:', JSON.stringify(cmdQueue)); + + // Directly ask pathfinding for a path + const pathResult = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const pf = scene.orchestrator?.systems?.pathfinding; + if (!pf) return { error: 'no pathfinding' }; + const startTile = pf.worldToTile(1020, 456); + const endTile = pf.worldToTile(1020 + 96, 456 + 96); + return new Promise(resolve => { + pf.findPath(startTile.x, startTile.y, endTile.x, endTile.y).then(path => { + resolve({ startTile, endTile, pathLen: path ? path.length : null, path }); + }); + }); + }); + console.log('PATH RESULT:', JSON.stringify(pathResult)); + + // Wait for movement + await page.waitForTimeout(3000); + + const after = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active && u.body) { + return { + x: u.x, y: u.y, + pathLen: u.data?.get('path')?.length || 0, + stateKey: u.state?.key || 'no-state', + }; + } + } + return null; + }); + console.log('AFTER:', JSON.stringify(after)); + + const dist = Math.sqrt((after.x - unitInfo.wx)**2 + (after.y - unitInfo.wy)**2); + console.log('DIST MOVED:', dist); + + // Verify something moved if path existed + if (pathResult.pathLen > 0) { + expect(dist).toBeGreaterThan(30); + } +}); diff --git a/tests/e2e/debug-rightclick.js b/tests/e2e/debug-rightclick.js new file mode 100644 index 0000000..c638335 --- /dev/null +++ b/tests/e2e/debug-rightclick.js @@ -0,0 +1,165 @@ +const { chromium } = require('playwright'); +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +const SCREENSHOT_DIR = path.join(__dirname, 'screenshots'); +const LOCAL_DIST = path.join(__dirname, '..', 'dist'); + +function mapPathToFile(requestPath) { + const parsed = url.parse(requestPath); + let p = parsed.pathname; + if (p === '/' || p === '/index.html') return path.join(LOCAL_DIST, 'index.html'); + if (p.startsWith('/')) p = p.substring(1); + const candidate = path.join(LOCAL_DIST, p); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; + return path.join(LOCAL_DIST, 'index.html'); +} +function contentTypeFor(p) { + const ext = path.extname(p).toLowerCase(); + const map = { + '.js': 'application/javascript', '.css': 'text/css', '.html': 'text/html', + '.png': 'image/png', '.jpg': 'image/jpeg', '.json': 'application/json', + '.tmj': 'application/json', + }; + return map[ext] || 'application/octet-stream'; +} + +(async () => { + const browser = await chromium.launch({ executablePath: '/usr/bin/chromium', headless: true }); + const page = await browser.newPage({ viewport: { width: 1280, height: 720 } }); + + page.on('console', msg => console.log('CONSOLE', msg.type(), msg.text())); + page.on('pageerror', err => console.log('PAGEERROR', err.message)); + + await page.route('https://restitution.damascusfront.net/**', async (route) => { + const reqUrl = route.request().url(); + const parsed = url.parse(reqUrl); + const pathname = parsed.pathname; + if (pathname.startsWith('/api/') || pathname.startsWith('/matchmake/')) { + await route.continue(); + return; + } + const filePath = mapPathToFile(pathname); + try { + const body = fs.readFileSync(filePath); + await route.fulfill({ status: 200, contentType: contentTypeFor(filePath), body }); + } catch (e) { + await route.fulfill({ status: 404, body: 'Not found' }); + } + }); + + await page.addInitScript(() => { + let _phaser = undefined; + Object.defineProperty(window, 'Phaser', { + configurable: true, enumerable: true, + get() { return _phaser; }, + set(val) { + _phaser = val; + if (val && val.Game) { + const OrigGame = val.Game; + val.Game = function PhaserGameProxy(...args) { + const instance = Reflect.construct(OrigGame, args, new.target || OrigGame); + window.game = instance; + return instance; + }; + Object.keys(OrigGame).forEach(k => { val.Game[k] = OrigGame[k]; }); + val.Game.prototype = OrigGame.prototype; + } + }, + }); + }); + + await page.goto('https://restitution.damascusfront.net', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.click('button:has-text("CREATE GAME")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); + + // Press F to spawn unit + await page.keyboard.press('KeyF'); + await page.waitForTimeout(1000); + + // Get unit info + const unitInfo = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + if (!scene.goodGuys) return null; + const cam = scene.cameras.main; + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active && u.body) { + return { + wx: u.x, wy: u.y, + sx: u.x - cam.scrollX, sy: u.y - cam.scrollY, + name: u.name, + }; + } + } + return null; + }); + console.log('UNIT INFO:', JSON.stringify(unitInfo)); + + // Select unit + await page.mouse.click(unitInfo.sx, unitInfo.sy); + await page.waitForTimeout(400); + + const selAfterClick = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const sel = scene.orchestrator?.systems?.selection; + return { + count: sel ? sel.count : -1, + selectedNames: sel ? Array.from(sel.selected).map(e => e.name) : [], + }; + }); + console.log('SELECTION AFTER CLICK:', JSON.stringify(selAfterClick)); + + // Right-click move + const targetX = unitInfo.sx + 96; + const targetY = unitInfo.sy + 96; + console.log(`Right-clicking at screen ${targetX},${targetY}`); + + // Listen for pointer events + await page.evaluateOnNewDocument(() => { + window._rightClickDebug = []; + }); + await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + scene.input.on('pointerdown', (pointer, currentlyOver) => { + window._rightClickDebug.push({ + type: 'pointerdown', + button: pointer.button, + rightButtonDown: pointer.rightButtonDown(), + worldX: pointer.worldX, + worldY: pointer.worldY, + x: pointer.x, + y: pointer.y, + currentlyOverLength: currentlyOver ? currentlyOver.length : 0, + tile: scene.interface ? scene.interface.getTileAtPointerXY(pointer) : null, + }); + }); + }); + + await page.mouse.click(targetX, targetY, { button: 'right' }); + await page.waitForTimeout(3000); + + const debugEvents = await page.evaluate(() => window._rightClickDebug); + console.log('POINTER EVENTS:', JSON.stringify(debugEvents, null, 2)); + + // Check position after move + const newPos = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active && u.body) return { x: u.x, y: u.y, name: u.name }; + } + return null; + }); + console.log('NEW POS:', JSON.stringify(newPos)); + + const dist = Math.sqrt((newPos.x - unitInfo.wx) ** 2 + (newPos.y - unitInfo.wy) ** 2); + console.log('DISTANCE MOVED:', dist); + + await page.screenshot({ path: '/tmp/debug-move.png', fullPage: false }); + await browser.close(); +})(); diff --git a/tests/e2e/debug-run.js b/tests/e2e/debug-run.js new file mode 100644 index 0000000..754a92e --- /dev/null +++ b/tests/e2e/debug-run.js @@ -0,0 +1,119 @@ +const { chromium } = require('playwright'); + +(async () => { + const browser = await chromium.launch({ executablePath: '/usr/bin/chromium', headless: true }); + const page = await browser.newPage({ viewport: { width: 1280, height: 720 } }); + + // Route interception: serve local dist + const path = require('path'); + const fs = require('fs'); + const url = require('url'); + const LOCAL_DIST = path.join(__dirname, '..', 'dist'); + function mapPathToFile(requestPath) { + const parsed = url.parse(requestPath); + let p = parsed.pathname; + if (p === '/' || p === '/index.html') return path.join(LOCAL_DIST, 'index.html'); + if (p.startsWith('/')) p = p.substring(1); + const candidate = path.join(LOCAL_DIST, p); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; + return path.join(LOCAL_DIST, 'index.html'); + } + function contentTypeFor(p) { + const ext = path.extname(p).toLowerCase(); + const map = { + '.js': 'application/javascript', '.css': 'text/css', '.html': 'text/html', + '.png': 'image/png', '.jpg': 'image/jpeg', '.json': 'application/json', + '.tmj': 'application/json', + }; + return map[ext] || 'application/octet-stream'; + } + await page.route('https://restitution.damascusfront.net/**', async (route) => { + const reqUrl = route.request().url(); + const parsed = url.parse(reqUrl); + const pathname = parsed.pathname; + if (pathname.startsWith('/api/') || pathname.startsWith('/matchmake/')) { + await route.continue(); + return; + } + const filePath = mapPathToFile(pathname); + try { + const body = fs.readFileSync(filePath); + await route.fulfill({ status: 200, contentType: contentTypeFor(filePath), body }); + } catch (e) { + await route.fulfill({ status: 404, body: 'Not found' }); + } + }); + + // Expose window.game via Phaser proxy + await page.addInitScript(() => { + let _phaser = undefined; + Object.defineProperty(window, 'Phaser', { + configurable: true, enumerable: true, + get() { return _phaser; }, + set(val) { + _phaser = val; + if (val && val.Game) { + const OrigGame = val.Game; + val.Game = function PhaserGameProxy(...args) { + const instance = Reflect.construct(OrigGame, args, new.target || OrigGame); + window.game = instance; + return instance; + }; + Object.keys(OrigGame).forEach(k => { val.Game[k] = OrigGame[k]; }); + val.Game.prototype = OrigGame.prototype; + } + }, + }); + }); + + await page.goto('https://restitution.damascusfront.net', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.waitForTimeout(2000); + await page.screenshot({ path: '/tmp/debug-landing.png', fullPage: true }); + const html = await page.content(); + fs.writeFileSync('/tmp/debug-landing.html', html); + console.log('HTML length:', html.length); + // Look for the button by text or by any button + const buttons = await page.locator('button').allInnerTexts(); + console.log('Buttons found:', buttons); + await page.click('button:has-text("Create Game")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); + + // Debug: inspect page state + const info = await page.evaluate(() => { + const g = window.game; + if (!g) return { error: 'no window.game' }; + const scene = g.scene.getScene('Map_Player'); + if (!scene) return { error: 'no Map_Player', scenes: g.scene.scenes.map(s => s.sys?.config?.key || 'unknown') }; + const goodGuys = scene.goodGuys; + const list = goodGuys?.list || []; + return { + hasGoodGuys: !!goodGuys, + goodGuysLength: list.length, + goodGuysListKeys: list.map(u => u.name || u.type || 'unnamed'), + hasOrchestrator: !!scene.orchestrator, + selectionCount: scene.orchestrator?.systems?.selection?.count || 0, + cam: scene.cameras.main ? { scrollX: scene.cameras.main.scrollX, scrollY: scene.cameras.main.scrollY } : null, + }; + }); + console.log('PAGE INFO:', JSON.stringify(info, null, 2)); + + // Try pressing F + await page.keyboard.press('KeyF'); + await page.waitForTimeout(1000); + + const afterF = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const list = scene.goodGuys?.list || []; + return { + goodGuysLength: list.length, + units: list.map(u => ({ name: u.name, active: u.active, x: u.x, y: u.y })), + }; + }); + console.log('AFTER F:', JSON.stringify(afterF, null, 2)); + + await page.screenshot({ path: '/tmp/debug-screenshot.png', fullPage: false }); + await browser.close(); +})(); diff --git a/tests/e2e/debug-tile-coords.spec.js b/tests/e2e/debug-tile-coords.spec.js new file mode 100644 index 0000000..6f84dbf --- /dev/null +++ b/tests/e2e/debug-tile-coords.spec.js @@ -0,0 +1,144 @@ +const { test, expect } = require('@playwright/test'); +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +const SCREENSHOT_DIR = path.join(__dirname, 'screenshots'); +const LOCAL_DIST = path.join(__dirname, '..', '..', 'dist'); + +function mapPathToFile(requestPath) { + const parsed = url.parse(requestPath); + let p = parsed.pathname; + if (p === '/' || p === '/index.html') return path.join(LOCAL_DIST, 'index.html'); + if (p.startsWith('/')) p = p.substring(1); + const candidate = path.join(LOCAL_DIST, p); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; + return path.join(LOCAL_DIST, 'index.html'); +} + +function contentTypeFor(p) { + const ext = path.extname(p).toLowerCase(); + const map = { + '.js': 'application/javascript', + '.css': 'text/css', + '.html': 'text/html', + '.png': 'image/png', + '.json': 'application/json', + '.tmj': 'application/json', + }; + return map[ext] || 'application/octet-stream'; +} + +async function setupRoutes(page) { + await page.route(/https:\/\/restitution\.damascusfront\.net(\/.*)?/, async (route) => { + const reqUrl = route.request().url(); + const parsed = url.parse(reqUrl); + const pathname = parsed.pathname; + if (pathname.startsWith('/api/') || pathname.startsWith('/matchmake/')) { + await route.continue(); + return; + } + const filePath = mapPathToFile(pathname); + try { + const body = fs.readFileSync(filePath); + await route.fulfill({ status: 200, contentType: contentTypeFor(filePath), body }); + } catch (e) { + await route.fulfill({ status: 404, body: 'Not found' }); + } + }); +} + +async function bootstrapGame(page) { + await page.goto('https://restitution.damascusfront.net', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.click('button:has-text("CREATE GAME")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); +} + +test('tile coordinate debug', async ({ page }) => { + await setupRoutes(page); + await bootstrapGame(page); + + const result = await page.evaluate(() => { + try { + const scene = window.game.scene.getScene('Map_Player'); + const u = scene.goodGuys.list.find(x => x.active && x.body); + if (!u) return { error: 'no unit' }; + + const tileAtUnit = scene.groundLayer.getTileAtWorldXY(u.x, u.y) || scene.groundLayer.getTileAtWorldXY(u.x, u.y, true); + const startTile = { x: 31, y: 28 }; + const endTile = { x: 34, y: 34 }; + const worldStart = scene.map.tileToWorldXY(startTile.x, startTile.y, null, scene.cameras.main, scene.groundLayer); + const worldEnd = scene.map.tileToWorldXY(endTile.x, endTile.y, null, scene.cameras.main, scene.groundLayer); + const layerGetStart = scene.groundLayer.getTileAt(startTile.x, startTile.y); + const layerGetEnd = scene.groundLayer.getTileAt(endTile.x, endTile.y); + const ifStart = scene.interface.generateWorldXY(startTile); + const ifEnd = scene.interface.generateWorldXY(endTile); + return { + unit: { x: u.x, y: u.y, name: u.name }, + tileAtUnit: tileAtUnit ? { x: tileAtUnit.x, y: tileAtUnit.y, index: tileAtUnit.index } : null, + worldStart: worldStart ? { x: worldStart.x, y: worldStart.y } : null, + worldEnd: worldEnd ? { x: worldEnd.x, y: worldEnd.y } : null, + layerGetStart: layerGetStart ? { index: layerGetStart.index } : null, + layerGetEnd: layerGetEnd ? { index: layerGetEnd.index } : null, + interfaceStart: ifStart ? { x: ifStart.x, y: ifStart.y } : null, + interfaceEnd: ifEnd ? { x: ifEnd.x, y: ifEnd.y } : null, + }; + } catch (e) { + return { error: e.message, stack: e.stack }; + } + }); + console.log(JSON.stringify(result, null, 2)); + expect(result.error).toBeUndefined(); +}); + +test('direct moveToTile path smoke', async ({ page }) => { + await setupRoutes(page); + await bootstrapGame(page); + + const result = await page.evaluate(() => { + try { + const scene = window.game.scene.getScene('Map_Player'); + const u = scene.goodGuys.list.find(x => x.active && x.body); + if (!u) return { error: 'no unit' }; + + const before = { x: u.x, y: u.y }; + const path = [ + { x: 31, y: 28 }, + { x: 32, y: 29 }, + { x: 33, y: 30 }, + { x: 34, y: 31 }, + { x: 34, y: 32 }, + { x: 34, y: 33 }, + { x: 34, y: 34 }, + ]; + u.setData('path', path); + u.ACTIONS.MOVE(); + + // override shouldUpdate to fire immediately + let ticks = 0; + while (ticks < 20 && u.state?.key !== 'IDLING') { + u.movement._frameTime = 9999; + u.preUpdate(0, 100); + ticks++; + } + const after = { x: u.x, y: u.y }; + return { + before, + after, + ticks, + state: u.state?.key, + pathLen: u.getData('path')?.length ?? null, + }; + } catch (e) { + return { error: e.message, stack: e.stack }; + } + }); + console.log(JSON.stringify(result, null, 2)); + expect(result.error).toBeUndefined(); + const dist = Math.sqrt((result.after.x - result.before.x) ** 2 + (result.after.y - result.before.y) ** 2); + console.log('Direct path distance:', dist.toFixed(1)); + expect(dist).toBeGreaterThan(30); +}); diff --git a/tests/e2e/debug-updatelist.spec.js b/tests/e2e/debug-updatelist.spec.js new file mode 100644 index 0000000..8306a86 --- /dev/null +++ b/tests/e2e/debug-updatelist.spec.js @@ -0,0 +1,82 @@ +const { test, expect } = require('@playwright/test'); +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +const LOCAL_DIST = path.join(__dirname, '..', '..', 'dist'); +function mapPathToFile(requestPath) { + const parsed = url.parse(requestPath); + let p = parsed.pathname; + if (p === '/' || p === '/index.html') return path.join(LOCAL_DIST, 'index.html'); + if (p.startsWith('/')) p = p.substring(1); + const candidate = path.join(LOCAL_DIST, p); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; + return path.join(LOCAL_DIST, 'index.html'); +} +function contentTypeFor(p) { + const ext = path.extname(p).toLowerCase(); + const map = { '.js': 'application/javascript', '.html': 'text/html', '.json': 'application/json', '.tmj': 'application/json' }; + return map[ext] || 'application/octet-stream'; +} +async function setupRoutes(page) { + await page.route(/https:\/\/restitution\.damascusfront\.net(\/.*)?/, async (route) => { + const reqUrl = route.request().url(); + const pathname = url.parse(reqUrl).pathname; + if (pathname.startsWith('/api/') || pathname.startsWith('/matchmake/')) { await route.continue(); return; } + const filePath = mapPathToFile(pathname); + try { await route.fulfill({ status: 200, contentType: contentTypeFor(filePath), body: fs.readFileSync(filePath) }); } + catch (e) { await route.fulfill({ status: 404, body: 'Not found' }); } + }); +} +async function bootstrapGame(page) { + await page.goto('https://restitution.damascusfront.net', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.click('button:has-text("CREATE GAME")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); +} + +test('is unit in scene update list?', async ({ page }) => { + await setupRoutes(page); + await bootstrapGame(page); + + const result = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const u = scene.goodGuys.list.find(x => x.active && x.body); + const inUpdateList = scene.updateList?.list?.includes(u) || false; + const updateListLen = scene.updateList?.list?.length ?? 0; + return { + unitName: u?.name, + inUpdateList, + updateListLen, + parentName: u?.parentContainer?.name, + }; + }); + console.log(JSON.stringify(result, null, 2)); + expect(result.inUpdateList).toBe(true); +}); + +test('does preUpdate fire naturally?', async ({ page }) => { + await setupRoutes(page); + await bootstrapGame(page); + + const result = await page.evaluate(async () => { + return new Promise((resolve) => { + const scene = window.game.scene.getScene('Map_Player'); + const u = scene.goodGuys.list.find(x => x.active && x.body); + if (!u) return resolve({ error: 'no unit' }); + + let count = 0; + const orig = u.preUpdate; + u.preUpdate = function(t, d) { + count++; + return orig.call(this, t, d); + }; + + setTimeout(() => resolve({ count, unitName: u.name }), 500); + }); + }); + console.log(JSON.stringify(result, null, 2)); + expect(result.count).toBeGreaterThan(0); +}); diff --git a/tests/e2e/diag-rightclick.spec.js b/tests/e2e/diag-rightclick.spec.js new file mode 100644 index 0000000..924aa66 --- /dev/null +++ b/tests/e2e/diag-rightclick.spec.js @@ -0,0 +1,213 @@ +const { test, expect } = require('@playwright/test'); +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +const SCREENSHOT_DIR = path.join(__dirname, 'screenshots'); +const LOCAL_DIST = path.join(__dirname, '..', '..', 'dist'); + +function mapPathToFile(requestPath) { + const parsed = url.parse(requestPath); + let p = parsed.pathname; + if (p === '/' || p === '/index.html') return path.join(LOCAL_DIST, 'index.html'); + if (p.startsWith('/')) p = p.substring(1); + const candidate = path.join(LOCAL_DIST, p); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; + return path.join(LOCAL_DIST, 'index.html'); +} +function contentTypeFor(p) { + const ext = path.extname(p).toLowerCase(); + const map = { '.js': 'application/javascript', '.css': 'text/css', '.html': 'text/html', '.png': 'image/png', '.jpg': 'image/jpeg', '.json': 'application/json', '.tmj': 'application/json' }; + return map[ext] || 'application/octet-stream'; +} +async function setupRoutes(page) { + await page.route(/https:\/\/restitution\.damascusfront\.net(\/.*)?/, async (route) => { + const reqUrl = route.request().url(); + const parsed = url.parse(reqUrl); + const pathname = parsed.pathname; + if (pathname.startsWith('/api/') || pathname.startsWith('/matchmake/')) { + return route.continue(); + } + const filePath = mapPathToFile(pathname); + try { + const body = fs.readFileSync(filePath); + await route.fulfill({ status: 200, contentType: contentTypeFor(filePath), body }); + } catch (e) { + await route.fulfill({ status: 404, body: 'Not found' }); + } + }); +} +async function bootstrapGame(page) { + await page.goto('https://restitution.damascusfront.net', { waitUntil: 'domcontentloaded', timeout: 15000 }); + await page.click('button:has-text("CREATE GAME")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); +} + +test('diagnose right-click move v2', async ({ page }) => { + page.on('console', msg => console.log(`[console ${msg.type()}] ${msg.text()}`)); + page.on('pageerror', err => console.log(`[pageerror] ${err.message}`)); + + await setupRoutes(page); + await page.addInitScript(() => { + let _phaser = undefined; + Object.defineProperty(window, 'Phaser', { + configurable: true, enumerable: true, + get() { return _phaser; }, + set(val) { + _phaser = val; + if (val && val.Game) { + const OrigGame = val.Game; + val.Game = function PhaserGameProxy(...args) { + const instance = Reflect.construct(OrigGame, args, new.target || OrigGame); + window.game = instance; + return instance; + }; + Object.keys(OrigGame).forEach(k => { val.Game[k] = OrigGame[k]; }); + val.Game.prototype = OrigGame.prototype; + } + }, + }); + }); + + await bootstrapGame(page); + + await page.keyboard.press('KeyF'); + await page.waitForTimeout(1000); + + // Inject a robust spy via monkeypatch on SelectionSystem.prototype *after* page load + await page.evaluate(() => { + // We need to patch methods on the already-constructed SelectionSystem instance. + // Easier: patch after construction but we need to find the instance. + // Instead, proxy the scene input events so we can intercept right-clicks. + + const scene = window.game.scene.getScene('Map_Player'); + if (!scene) return; + + // Monkeypatch SelectionSystem methods on the existing instance + const sel = scene.orchestrator?.systems?.selection; + if (!sel) return; + + const origHandleRightClick = sel.handleRightClick; + sel.handleRightClick = function(pointer, currentlyOver) { + console.log('[DIAG] handleRightClick called. selected.size=' + this.selected.size); + console.log('[DIAG] pointer.worldX=' + (pointer?.worldX) + ' worldY=' + (pointer?.worldY)); + console.log('[DIAG] currentlyOver length=' + (currentlyOver ? currentlyOver.length : 'null')); + if (this.scene && this.scene.interface) { + const tile = this.scene.interface.getTileAtPointerXY(pointer); + console.log('[DIAG] tile=' + (tile ? 'x=' + tile.x + ' y=' + tile.y : 'null')); + } + return origHandleRightClick.call(this, pointer, currentlyOver); + }; + + const origIssueCommand = sel.issueCommand; + sel.issueCommand = function(type, target) { + console.log('[DIAG] issueCommand called type=' + type); + console.log('[DIAG] target=' + JSON.stringify(target)); + return origIssueCommand.call(this, type, target); + }; + + const origDispatchMove = sel['#dispatchMove'] ? sel['#dispatchMove'] : (() => {}); + // Can't directly access private method, but we can patch what's called inside it + + // Patch pathfinding.findPath on the runtime instance + const pf = scene.orchestrator?.systems?.pathfinding; + if (pf) { + const origFindPath = pf.findPath; + pf.findPath = function(...args) { + console.log('[DIAG] findPath called args=' + JSON.stringify(args)); + return origFindPath.apply(this, args).then(path => { + console.log('[DIAG] findPath resolved path=' + (path ? 'length=' + path.length : 'null')); + return path; + }); + }; + } + + // Patch every unit's moveToPath at prototype level — but classes already loaded. + // Instead patch existing goodGuys list members. + const units = scene.goodGuys?.list || []; + for (const u of units) { + if (typeof u.moveToPath === 'function') { + const orig = u.moveToPath.bind(u); + u.moveToPath = function(path, shiftDown) { + console.log('[DIAG] unit.moveToPath called name=' + this.name + ' path.length=' + (path ? path.length : 'null')); + return orig(path, shiftDown); + }; + } + if (typeof u.nextPath === 'function') { + const origNext = u.nextPath.bind(u); + u.nextPath = function() { + const path = this.getData('path'); + if (path && path.length > 0) { + console.log('[DIAG] nextPath peek point=' + JSON.stringify(path[0]) + ' len=' + path.length); + } else { + console.log('[DIAG] nextPath empty path'); + } + const result = origNext(); + console.log('[DIAG] nextPath result=' + result + ' pos=' + this.x + ',' + this.y); + return result; + }; + } + if (typeof u.moveToTile === 'function') { + const origMove = u.moveToTile.bind(u); + u.moveToTile = function(tile) { + const pv = this.scene.interface?.generateWorldXY?.(tile) || {x: (tile?.x ?? -1)*64, y: (tile?.y ?? -1)*64}; + console.log('[DIAG] moveToTile tile=' + JSON.stringify(tile ? {x: tile.x, y: tile.y} : null) + ' pv=' + JSON.stringify(pv)); + const result = origMove(tile); + console.log('[DIAG] moveToTile after pos=' + this.x + ',' + this.y + ' ret=' + result); + return result; + }; + } + } + }); + + const unitInfo = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const cam = scene.cameras.main; + const units = scene.goodGuys?.list || []; + for (const u of units) { + if (u.active && u.body) { + return { sx: u.x - cam.scrollX, sy: u.y - cam.scrollY, wx: u.x, wy: u.y, name: u.name }; + } + } + return null; + }); + console.log('UNIT INFO:', JSON.stringify(unitInfo)); + expect(unitInfo).not.toBeNull(); + + await page.mouse.click(unitInfo.sx, unitInfo.sy); + await page.waitForTimeout(300); + + const selCountBefore = await page.evaluate(() => { + return window.game.scene.getScene('Map_Player').orchestrator?.systems?.selection?.count ?? -1; + }); + console.log('SELECTED BEFORE RIGHT-CLICK:', selCountBefore); + + const rx = unitInfo.sx + 96; + const ry = unitInfo.sy + 96; + await page.mouse.click(rx, ry, { button: 'right' }); + await page.waitForTimeout(200); + + await page.waitForTimeout(3000); + + const after = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const sel = scene.orchestrator?.systems?.selection; + const units = scene.goodGuys?.list || []; + const u = units.find(x => x.active && x.body); + return { + selectedCount: sel ? sel.count : -1, + pathLength: u ? (u.getData('path')?.length ?? -1) : -1, + unitX: u ? u.x : null, + unitY: u ? u.y : null, + hasMoveToPath: u ? typeof u.moveToPath : 'no-unit', + stateKey: u ? (u.state?.key || 'no-state') : 'no-unit', + }; + }); + console.log('AFTER 3s:', JSON.stringify(after, null, 2)); + + // Check browser console logs were emitted + expect(true).toBe(true); +}); diff --git a/tests/e2e/milestone-1-rts-loop.spec.js b/tests/e2e/milestone-1-rts-loop.spec.js new file mode 100644 index 0000000..16c09ff --- /dev/null +++ b/tests/e2e/milestone-1-rts-loop.spec.js @@ -0,0 +1,444 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +/** + * E2E: RTS Control Loop — select, move, animate + * + * Milestone 1: verify the full RTS control loop end-to-end. + * + * Route-interception strategy: serve ALL static assets from the locally-built + * dist/ directory. API/WebSocket requests pass through to the live backend. + * This tests the current HEAD (including M1.1 + M1.2 + window.game patch) + * against the live Colyseus server without needing a Docker redeploy. + * + * Also injects window.game trapping so tests can inspect Phaser internals. + * + * Run: npx playwright test --config=tests/e2e/playwright.config.js + */ + +const SCREENSHOT_DIR = path.join(__dirname, 'screenshots'); +const LOCAL_DIST = path.join(__dirname, '..', '..', 'dist'); + +/** Map URL paths to local dist files */ +function mapPathToFile(requestPath) { + const parsed = url.parse(requestPath); + let p = parsed.pathname; + if (p === '/' || p === '/index.html') return path.join(LOCAL_DIST, 'index.html'); + if (p.startsWith('/')) p = p.substring(1); + const candidate = path.join(LOCAL_DIST, p); + if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate; + // SPA fallback: serve index.html for non-file paths + return path.join(LOCAL_DIST, 'index.html'); +} + +function contentTypeFor(p) { + const ext = path.extname(p).toLowerCase(); + const map = { + '.js': 'application/javascript', + '.css': 'text/css', + '.html': 'text/html', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.svg': 'image/svg+xml', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.tmj': 'application/json', + '.json': 'application/json', + '.ico': 'image/x-icon', + '.webp': 'image/webp', + '.mp3': 'audio/mpeg', + '.ogg': 'audio/ogg', + '.wav': 'audio/wav', + }; + return map[ext] || 'application/octet-stream'; +} + +/** Setup route interception: serve static from dist/, pass API/WS through */ +async function setupRoutes(page) { + await page.route(/https:\/\/restitution\.damascusfront\.net(\/.*)?/, async (route) => { + const reqUrl = route.request().url(); + const parsed = url.parse(reqUrl); + const pathname = parsed.pathname; + + // Pass API and WebSocket through to live backend + if (pathname.startsWith('/api/') || pathname.startsWith('/matchmake/')) { + await route.continue(); + return; + } + + const filePath = mapPathToFile(pathname); + try { + const body = fs.readFileSync(filePath); + await route.fulfill({ + status: 200, + contentType: contentTypeFor(filePath), + body: body, + }); + } catch (e) { + console.error(`[Hermes E2E] 404 serving ${pathname} → ${filePath}: ${e.message}`); + await route.fulfill({ status: 404, body: 'Not found' }); + } + }); +} + +/** Bootstrap: CREATE GAME → START GAME → wait for canvas + Phaser boot */ +async function bootstrapGame(page) { + await page.goto('https://restitution.damascusfront.net', { + waitUntil: 'domcontentloaded', + timeout: 15000, + }); + + await page.click('button:has-text("CREATE GAME")'); + await page.waitForSelector('text=START GAME', { timeout: 8000 }); + await page.click('button:has-text("START GAME")'); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(5000); +} + +test.describe('RTS Control Loop (M1)', () => { + test.beforeEach(async ({ page }) => { + // Serve local dist/ build instead of deployed static files + await setupRoutes(page); + + // Intercept Phaser.Game construction to capture instance as window.game + await page.addInitScript(() => { + let _phaser = undefined; + Object.defineProperty(window, 'Phaser', { + configurable: true, enumerable: true, + get() { return _phaser; }, + set(val) { + _phaser = val; + if (val && val.Game) { + const OrigGame = val.Game; + val.Game = function PhaserGameProxy(...args) { + const instance = Reflect.construct(OrigGame, args, new.target || OrigGame); + window.game = instance; + return instance; + }; + Object.keys(OrigGame).forEach(k => { val.Game[k] = OrigGame[k]; }); + val.Game.prototype = OrigGame.prototype; + } + }, + }); + }); + }); + + test('1. Game boots — canvas present, M1.1 + M1.2 code active', async ({ page }) => { + await bootstrapGame(page); + + const canvasCount = await page.evaluate(() => + document.querySelectorAll('canvas').length + ); + expect(canvasCount).toBeGreaterThan(0); + + // Verify M1.1 + M1.2 + window.game patch are active + const sceneInfo = await page.evaluate(() => { + const g = window.game; + if (!g) return { error: 'no game instance' }; + const scene = g.scene.getScene('Map_Player'); + if (!scene) return { error: 'no Map_Player scene' }; + return { + hasGoodGuys: 'goodGuys' in scene, + hasOrchestrator: 'orchestrator' in scene, + hasSpawnTestUnit: typeof scene.spawnTestUnit === 'function', + hasUnitFactory: 'unitFactory' in scene, + }; + }); + + expect(sceneInfo.hasGoodGuys, 'goodGuys should exist (M1.2)').toBe(true); + expect(sceneInfo.hasOrchestrator, 'orchestrator should exist (M1.2)').toBe(true); + expect(sceneInfo.hasSpawnTestUnit, 'spawnTestUnit should exist (M1.2)').toBe(true); + + await page.screenshot({ + path: path.join(SCREENSHOT_DIR, '01-game-booted.png'), + fullPage: false, + }); + }); + + test('2. Spawn test unit via F key', async ({ page }) => { + await bootstrapGame(page); + + await page.keyboard.press('KeyF'); + await page.waitForTimeout(800); + + const unitCount = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + if (!scene.goodGuys) return -1; + return scene.goodGuys.list ? scene.goodGuys.list.length : scene.goodGuys.length || 0; + }); + expect(unitCount, 'Should have spawned at least 1 unit').toBeGreaterThan(0); + + await page.screenshot({ + path: path.join(SCREENSHOT_DIR, '02-unit-spawned.png'), + fullPage: false, + }); + }); + + test('3. Select unit by clicking on it', async ({ page }) => { + await bootstrapGame(page); + + await page.keyboard.press('KeyF'); + await page.waitForTimeout(800); + + const clickTarget = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + if (!scene.goodGuys) return null; + const cam = scene.cameras.main; + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active && u.body) { + return { x: u.x - cam.scrollX, y: u.y - cam.scrollY }; + } + } + return null; + }); + + // Click unit or fallback to center + if (clickTarget) { + await page.mouse.click(clickTarget.x, clickTarget.y); + } else { + const vp = page.viewportSize(); + await page.mouse.click(vp ? vp.width / 2 : 400, vp ? vp.height / 2 : 300); + } + await page.waitForTimeout(400); + + // Check selection count + const selResult = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const sel = scene.orchestrator?.systems?.selection; + return sel ? sel.count : -1; + }); + + // If click didn't register, try Shift+click + if (selResult <= 0 && clickTarget) { + await page.keyboard.down('Shift'); + await page.mouse.click(clickTarget.x + 10, clickTarget.y + 10); + await page.keyboard.up('Shift'); + await page.waitForTimeout(400); + + const retryCount = await page.evaluate(() => { + const sel = window.game.scene.getScene('Map_Player').orchestrator?.systems?.selection; + return sel ? sel.count : -1; + }); + expect(retryCount, 'Shift+click retry should select a unit').toBeGreaterThan(0); + } else { + expect(selResult, 'Selection count should be > 0 after clicking').toBeGreaterThan(0); + } + + await page.screenshot({ + path: path.join(SCREENSHOT_DIR, '03-unit-selected.png'), + fullPage: false, + }); + }); + + test('4. Right-click move — pathfinding + unit moves', async ({ page }) => { + await bootstrapGame(page); + + await page.keyboard.press('KeyF'); + await page.waitForTimeout(800); + + const unitInfo = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + if (!scene.goodGuys) return null; + const cam = scene.cameras.main; + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active && u.body && u.name === 'test-unit') { + return { + wx: u.x, wy: u.y, + sx: u.x - cam.scrollX, sy: u.y - cam.scrollY, + }; + } + } + return null; + }); + expect(unitInfo, 'No active test-unit found').not.toBeNull(); + + const originalPos = { x: unitInfo.wx, y: unitInfo.wy }; + + // Select unit + await page.mouse.click(unitInfo.sx, unitInfo.sy); + await page.waitForTimeout(200); + + // Right-click target 3 tiles away (96px) + await page.mouse.click(unitInfo.sx + 96, unitInfo.sy + 96, { button: 'right' }); + await page.waitForTimeout(3000); + + // Verify movement + const newPos = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active && u.body && u.name === 'test-unit') return { x: u.x, y: u.y }; + } + return null; + }); + + if (newPos) { + const dist = Math.sqrt( + (newPos.x - originalPos.x) ** 2 + (newPos.y - originalPos.y) ** 2 + ); + expect(dist, `Unit moved ${dist.toFixed(1)}px`).toBeGreaterThan(30); + } + + await page.screenshot({ + path: path.join(SCREENSHOT_DIR, '04-post-move.png'), + fullPage: false, + }); + }); + + test('5. Animation state — unit state machine active', async ({ page }) => { + await bootstrapGame(page); + + await page.keyboard.press('KeyF'); + await page.waitForTimeout(800); + + const unitInfo = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const cam = scene.cameras.main; + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active && u.body) { + return { + sx: u.x - cam.scrollX, sy: u.y - cam.scrollY, + }; + } + } + return null; + }); + expect(unitInfo).not.toBeNull(); + + await page.mouse.click(unitInfo.sx, unitInfo.sy); + await page.waitForTimeout(200); + + // Issue move + await page.mouse.click(unitInfo.sx + 128, unitInfo.sy + 128, { button: 'right' }); + await page.waitForTimeout(800); + + // Screenshot during animation + await page.screenshot({ + path: path.join(SCREENSHOT_DIR, '05-animation-frame.png'), + fullPage: false, + }); + + // Check unit state + const animState = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active) { + return { + stateKey: u.state?.key || null, + animKey: u.anims?.currentAnim?.key || null, + }; + } + } + return {}; + }); + + const combined = (animState.stateKey || animState.animKey || ''); + // The unit should be in a real state — either animating or idling + expect(typeof animState.stateKey !== 'undefined' || typeof animState.animKey !== 'undefined', + 'Unit should have state or animation tracking').toBe(true); + + if (combined) { + expect(combined).toMatch(/MOVING|IDLING|walk|idle|move|moving/i); + } + + // Wait for completion + await page.waitForTimeout(2500); + + const finalState = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active) { + return u.state?.key || u.anims?.currentAnim?.key || 'alive'; + } + } + return 'no-unit'; + }); + expect(finalState).toMatch(/IDLING|idle|MOVING|walk|move|alive/i); + }); + + test('6. Multi-select — spawn 3, select 2+, move all', async ({ page }) => { + await bootstrapGame(page); + + // Spawn 3 units + for (let i = 0; i < 3; i++) { + await page.keyboard.press('KeyF'); + await page.waitForTimeout(500); + } + await page.waitForTimeout(500); + + // Get unit positions + const positions = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + if (!scene.goodGuys) return []; + const cam = scene.cameras.main; + const result = []; + const units = scene.goodGuys.list || []; + for (const u of units) { + if (u.active && u.body) { + result.push({ + x: u.x - cam.scrollX, y: u.y - cam.scrollY, + wx: u.x, wy: u.y, + }); + } + } + return result; + }); + + expect(positions.length, 'Need at least 2 units').toBeGreaterThanOrEqual(2); + + // Select first + await page.mouse.click(positions[0].x, positions[0].y); + await page.waitForTimeout(200); + + // Shift+click second + await page.keyboard.down('Shift'); + await page.mouse.click(positions[1].x, positions[1].y); + await page.keyboard.up('Shift'); + await page.waitForTimeout(300); + + // Verify multi-select + const selCount = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const sel = scene.orchestrator?.systems?.selection; + return sel ? sel.count : -1; + }); + expect(selCount, 'Multi-select should select >= 2 units').toBeGreaterThanOrEqual(2); + + // Record pre-move + const preMove = positions.map(p => ({ x: p.wx, y: p.wy })); + + // Right-click move all + await page.mouse.click(positions[0].x + 160, positions[0].y + 160, { button: 'right' }); + await page.waitForTimeout(3500); + + // Verify some units moved + const postMove = await page.evaluate(() => { + const scene = window.game.scene.getScene('Map_Player'); + const units = scene.goodGuys.list || []; + return units.filter(u => u.active && u.body).map(u => ({ x: u.x, y: u.y })); + }); + + let movedCount = 0; + for (let i = 0; i < Math.min(preMove.length, postMove.length); i++) { + const d = Math.sqrt( + (postMove[i].x - preMove[i].x) ** 2 + (postMove[i].y - preMove[i].y) ** 2 + ); + if (d > 20) movedCount++; + } + expect(movedCount, 'At least 2 units should have moved').toBeGreaterThanOrEqual(2); + + await page.screenshot({ + path: path.join(SCREENSHOT_DIR, '06-multi-select-move.png'), + fullPage: false, + }); + }); +}); diff --git a/tests/e2e/playwright.config.js b/tests/e2e/playwright.config.js new file mode 100644 index 0000000..00d7a65 --- /dev/null +++ b/tests/e2e/playwright.config.js @@ -0,0 +1,31 @@ +// @ts-check +const { defineConfig, devices } = require('@playwright/test'); + +module.exports = defineConfig({ + testDir: '.', + testMatch: '**/*.spec.js', + timeout: 60000, + expect: { timeout: 10000 }, + fullyParallel: false, + retries: 0, + reporter: 'list', + use: { + baseURL: 'https://restitution.damascusfront.net', + headless: true, + screenshot: 'off', + video: 'off', + trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + // Use system Chromium since Playwright browser download is unsupported on this OS + launchOptions: { + executablePath: '/usr/bin/chromium', + }, + }, + }, + ], +}); diff --git a/tests/e2e/screenshots/01-game-booted.png b/tests/e2e/screenshots/01-game-booted.png new file mode 100644 index 0000000000000000000000000000000000000000..394207ca2244aed3ff208531b1ee893c18b221a4 GIT binary patch literal 62264 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�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 literal 0 HcmV?d00001 diff --git a/tests/e2e/screenshots/02-unit-spawned.png b/tests/e2e/screenshots/02-unit-spawned.png new file mode 100644 index 0000000000000000000000000000000000000000..47ce49c40fe28a0ce789e430b0c9b334d46ae968 GIT binary patch literal 63719 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 YRv75sfjhdu5W4$!AKu0IHQ@LE0X{vSCjbBd literal 0 HcmV?d00001 diff --git a/tests/e2e/screenshots/05-animation-frame.png b/tests/e2e/screenshots/05-animation-frame.png new file mode 100644 index 0000000000000000000000000000000000000000..86a6698d5b39dd46575986041cc33636a3d08f26 GIT binary patch literal 63227 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 literal 0 HcmV?d00001 diff --git a/tests/e2e/screenshots/06-multi-select-move.png b/tests/e2e/screenshots/06-multi-select-move.png new file mode 100644 index 0000000000000000000000000000000000000000..2f931a937ee4d3a2ddf90a372f5a68b9ae31697a GIT binary patch literal 62990 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

T&95eeTxrJSasPh=blH)AoZ*Xz%7gIt-vH^Tk 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 ({ + Scene: class MockScene { + constructor(config) { + this.key = config?.key || ''; + this.sys = { events: new (require('events').EventEmitter)() }; + } + }, Physics: { Arcade: { DYNAMIC_BODY: 0, @@ -27,6 +33,8 @@ jest.mock('phaser', () => ({ this.clearTint = jest.fn(); // Stateful setData/getData so tests can read back what they wrote this._data = {}; + this.displayWidth = 32; + this.displayHeight = 32; this.setData = jest.fn((key, value) => { this._data[key] = value; }); this.getData = jest.fn((key) => this._data[key] ?? null); this.pulse = null; @@ -38,20 +46,6 @@ jest.mock('phaser', () => ({ } } }, - Math: { - Angle: { - BetweenPoints: (a, b) => Math.atan2(b.y - a.y, b.x - a.x) - }, - RadToDeg: rad => { - let deg = rad * (180 / Math.PI); - while (deg < 0) deg += 360; - return deg % 360; - }, - Distance: { - BetweenPoints: (a, b) => Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2)) - }, - Clamp: (v, min, max) => Math.max(min, Math.min(max, v)) - }, Display: { Color: { GetColor32: (r, g, b, a) => (r << 24) | (g << 16) | (b << 8) | a @@ -102,8 +96,75 @@ jest.mock('phaser', () => ({ this.body = { setCircle: jest.fn(), checkCollision: { none: false } }; } destroy() {} + }, + Graphics: class MockGraphics { + constructor() { + this.clear = jest.fn(); + this.fillStyle = jest.fn().mockReturnThis(); + this.fillRect = jest.fn().mockReturnThis(); + this.lineStyle = jest.fn().mockReturnThis(); + this.strokeRect = jest.fn().mockReturnThis(); + this.setDepth = jest.fn().mockReturnThis(); + this.setPosition = jest.fn().mockReturnThis(); + this.setVisible = jest.fn().mockReturnThis(); + this.setAlpha = jest.fn().mockReturnThis(); + this.active = true; + } + destroy() {} } - } + }, + Input: { + Keyboard: { + KeyCodes: { + A: 65, D: 68, W: 87, S: 83, SHIFT: 16, F: 70, CTRL: 17, + }, + }, + Events: { + POINTER_DOWN: 'pointerdown', + POINTER_MOVE: 'pointermove', + POINTER_UP: 'pointerup', + POINTER_WHEEL: 'wheel', + }, + }, + Cameras: { + Controls: { + SmoothedKeyControl: class MockSmoothedKeyControl { + constructor(config) { + this.config = config; + } + update() {} + } + } + }, + Geom: { + Rectangle: class MockRectangle { + constructor(x, y, w, h) { + this.x = x; this.y = y; this.width = w; this.height = h; + } + }, + Circle: class MockCircle { + constructor(x, y, r) { + this.x = x; this.y = y; this.radius = r; + } + } + }, + Math: { + Vector2: class MockVector2 { + constructor(x, y) { this.x = x; this.y = y; } + }, + Angle: { + BetweenPoints: (a, b) => Math.atan2(b.y - a.y, b.x - a.x) + }, + RadToDeg: rad => { + let deg = rad * (180 / Math.PI); + while (deg < 0) deg += 360; + return deg % 360; + }, + Distance: { + BetweenPoints: (a, b) => Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2)) + }, + Clamp: (v, min, max) => Math.max(min, Math.min(max, v)) + }, })); // Mock XState @@ -120,16 +181,25 @@ jest.mock('xstate', () => ({ })); // Mock EasyStar -jest.mock('easystarjs', () => { - return jest.fn().mockImplementation(() => ({ - setGrid: jest.fn(), - setIterationsPerCalculation: jest.fn(), - findPath: jest.fn((x, y, toX, toY, callback) => { - setTimeout(() => callback([{ x, y }, { x: toX, y: toY }]), 0); +jest.mock('easystarjs', () => ({ + __esModule: true, + default: { + js: jest.fn().mockImplementation(function () { + this.setGrid = jest.fn(); + this.setIterationsPerCalculation = jest.fn(); + this.findPath = jest.fn((x, y, toX, toY, callback) => { + setImmediate(() => callback([{ x, y }, { x: toX, y: toY }])); + }); + this.setTileAtXY = jest.fn(); + this.enableDiagonals = jest.fn(); + this.enableCornerCutting = jest.fn(); + this.setAcceptableTiles = jest.fn(); + this.setTileCost = jest.fn(); + this.setAdditionalPointCost = jest.fn(); + this.calculate = jest.fn(); }), - setTileAtXY: jest.fn() - })); -}); + }, +})); // Suppress console errors during tests console.error = jest.fn(); diff --git a/tests/unit/BuildMenu.test.js b/tests/unit/BuildMenu.test.js new file mode 100644 index 0000000..368e3e8 --- /dev/null +++ b/tests/unit/BuildMenu.test.js @@ -0,0 +1,168 @@ +/** + * BuildMenu.test.js — S5.1: Build menu HUD + * + * Tests: + * 1. Constructor creates a container with 4 building buttons fixed to camera + * 2. Each button shows building label and cost text + * 3. Clicking a button calls onSelect callback with building type + * 4. updateAffordability disables buttons when player can't afford + * 5. updateAffordability re-enables buttons when player can afford again + * 6. destroy cleans up all Phaser objects + */ + +import BuildMenu from 'Systems/BuildMenu'; + +function buildMockScene() { + const objects = []; + const inputOnHandlers = []; + + return { + add: { + container: jest.fn((x, y) => { + const c = { + x, y, + list: [], + add: jest.fn(function (obj) { this.list.push(obj); return this; }), + setPosition: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + setScrollFactor: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + }; + objects.push(c); + return c; + }), + rectangle: jest.fn((x, y, w, h) => { + const r = { + x, y, width: w, height: h, + setOrigin: jest.fn().mockReturnThis(), + setStrokeStyle: jest.fn().mockReturnThis(), + setInteractive: jest.fn().mockReturnThis(), + setFillStyle: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + on: jest.fn().mockReturnThis(), + off: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + }; + objects.push(r); + return r; + }), + text: jest.fn((x, y, text, style) => { + const t = { + x, y, text, + style, + setOrigin: jest.fn().mockReturnThis(), + setScrollFactor: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setText: jest.fn(function (v) { this.text = v; return this; }), + setColor: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + }; + objects.push(t); + return t; + }), + }, + cameras: { + main: { width: 1280, height: 720 }, + }, + input: { + on: jest.fn((evt, cb) => inputOnHandlers.push({ evt, cb })), + }, + _objects: objects, + _inputHandlers: inputOnHandlers, + }; +} + +describe('BuildMenu', () => { + let scene; + let menu; + let onSelect; + + beforeEach(() => { + scene = buildMockScene(); + onSelect = jest.fn(); + menu = new BuildMenu(scene, { onSelect, playerId: 'Player' }); + }); + + afterEach(() => { + menu?.destroy?.(); + }); + + // -- 1. Constructor ------------------------------------------------- + test('constructor creates a container fixed to camera', () => { + expect(scene.add.container).toHaveBeenCalled(); + expect(menu.container).toBeDefined(); + expect(menu.container.setScrollFactor).toHaveBeenCalledWith(0, 0); + }); + + test('container has 4 building buttons', () => { + expect(menu.buttons).toBeDefined(); + expect(menu.buttons.length).toBe(4); + }); + + // -- 2. Button labels + cost -------------------------------------- + test('each button has a text label', () => { + const labels = menu.buttons.map((b) => b.label?.text); + expect(labels).toContain('Barracks'); + expect(labels).toContain('Vehicle Depot'); + expect(labels).toContain('Logistics Center'); + expect(labels).toContain('Ammunition Factory'); + }); + + test('each button shows its resource cost', () => { + const costs = menu.buttons.map((b) => b.costText?.text); + expect(costs.some((c) => c && c.includes('Ammo'))).toBe(true); + expect(costs.some((c) => c && c.includes('Fuel'))).toBe(true); + }); + + // -- 3. Clicking a button ----------------------------------------- + test('clicking a button calls onSelect with building type', () => { + const barracksBtn = menu.buttons.find((b) => b.type === 'BARRACKS'); + expect(barracksBtn).toBeDefined(); + + // Simulate the rectangle.on('pointerdown') callback + const pointerdownCall = barracksBtn.bg.on.mock.calls.find((c) => c[0] === 'pointerdown'); + expect(pointerdownCall).toBeDefined(); + + pointerdownCall[1](); + expect(onSelect).toHaveBeenCalledWith('BARRACKS'); + }); + + // -- 4. updateAffordability (disabled) ----------------------------- + test('updateAffordability disables buttons player cannot afford', () => { + const economy = { + getResources: jest.fn(() => ({ fuel: 0, ammo: 0 })), + canAfford: jest.fn(() => false), + }; + menu.updateAffordability(economy, 'Player'); + + menu.buttons.forEach((btn) => { + expect(btn.bg.setAlpha).toHaveBeenCalledWith(0.4); + }); + }); + + // -- 5. updateAffordability (re-enabled) -------------------------- + test('updateAffordability enables affordable buttons', () => { + const economy = { + getResources: jest.fn(() => ({ fuel: 200, ammo: 200 })), + canAfford: jest.fn(() => true), + }; + menu.updateAffordability(economy, 'Player'); + + menu.buttons.forEach((btn) => { + expect(btn.bg.setAlpha).toHaveBeenCalledWith(1); + }); + }); + + // -- 6. destroy ---------------------------------------------------- + test('destroy cleans up container and buttons', () => { + const containerDestroy = menu.container.destroy; + menu.destroy(); + expect(containerDestroy).toHaveBeenCalled(); + expect(menu.buttons.length).toBe(0); + }); +}); diff --git a/tests/unit/BuildingIncome.test.js b/tests/unit/BuildingIncome.test.js new file mode 100644 index 0000000..7cbca67 --- /dev/null +++ b/tests/unit/BuildingIncome.test.js @@ -0,0 +1,175 @@ +/** + * BuildingIncome.test.js -- S5.4: Passive income tick wiring + * + * Tests: + * 1. BuildingStateMachine.tick returns null when no income config + * 2. BuildingStateMachine.tick returns income object when ACTIVE and 1000ms elapsed + * 3. BuildingStateMachine.tick returns null when state is CONSTRUCTING + * 4. Income is rate-limited -- no return within the same second + * 5. startActive flag bypasses CONSTRUCTING and begins in ACTIVE + * 6. SystemOrchestrator.update calls economy.addIncome with returned income + playerId + */ + +import BuildingStateMachine from 'Systems/BuildingStateMachine.js'; + +// Mock Phaser Events.EventEmitter for EconomySystem +jest.mock('phaser', () => { + class EventEmitter { + constructor() { this._listeners = {}; } + on(event, fn) { + if (!this._listeners[event]) this._listeners[event] = []; + this._listeners[event].push(fn); + } + emit(event, ...args) { + (this._listeners[event] || []).forEach((fn) => fn(...args)); + } + destroy() { this._listeners = {}; } + } + return { + Events: { EventEmitter }, + GameObjects: { Zone: class {} }, + Scene: class {}, + }; +}); + +import EconomySystem from 'Systems/EconomySystem.js'; + +describe('BuildingStateMachine income tick', () => { + test('tick returns null when no income configured', () => { + const bsm = new BuildingStateMachine({}, { type: 'BARRACKS', startActive: true }); + expect(bsm.tick(0, 16)).toBeNull(); + }); + + test('tick returns income when ACTIVE and first tick', () => { + const bsm = new BuildingStateMachine({}, { + type: 'LOGISTICS', + startActive: true, + income: { fuel: 5 }, + }); + const result = bsm.tick(0, 16); + expect(result).toEqual({ fuel: 5 }); + }); + + test('tick returns null when CONSTRUCTING even with income config', () => { + const bsm = new BuildingStateMachine({}, { + type: 'LOGISTICS', + income: { fuel: 5 }, + // default _currentState is CONSTRUCTING + }); + expect(bsm.tick(0, 16)).toBeNull(); + expect(bsm.tick(2000, 16)).toBeNull(); + }); + + test('income is rate-limited to once per 1000ms', () => { + const bsm = new BuildingStateMachine({}, { + type: 'LOGISTICS', + startActive: true, + income: { fuel: 5 }, + }); + expect(bsm.tick(0, 16)).toEqual({ fuel: 5 }); + expect(bsm.tick(500, 16)).toBeNull(); + expect(bsm.tick(1000, 16)).toEqual({ fuel: 5 }); + expect(bsm.tick(1500, 16)).toBeNull(); + expect(bsm.tick(2000, 16)).toEqual({ fuel: 5 }); + }); + + test('startActive flag sets state to ACTIVE immediately', () => { + const bsm = new BuildingStateMachine({}, { + type: 'LOGISTICS', + startActive: true, + income: { fuel: 5 }, + }); + expect(bsm._currentState).toBe('ACTIVE'); + }); + + test('playerId is stored and accessible', () => { + const bsm = new BuildingStateMachine({}, { + type: 'LOGISTICS', + startActive: true, + playerId: 'Player', + income: { fuel: 5 }, + }); + expect(bsm.playerId).toBe('Player'); + }); + + test('income with both fuel and ammo', () => { + const bsm = new BuildingStateMachine({}, { + type: 'COMMAND_CENTER', + startActive: true, + income: { fuel: 1, ammo: 1 }, + }); + const result = bsm.tick(0, 16); + expect(result).toEqual({ fuel: 1, ammo: 1 }); + }); +}); + +describe('SystemOrchestrator building income wiring', () => { + function buildMockOrchestrator() { + const economy = new EconomySystem({ events: { on: jest.fn(), emit: jest.fn() } }); + economy.initPlayer('Player', { fuel: 100, ammo: 100 }); + + const bsmActive = new BuildingStateMachine({}, { + type: 'LOGISTICS', + startActive: true, + playerId: 'Player', + income: { fuel: 5 }, + }); + + const bsmConstructing = new BuildingStateMachine({}, { + type: 'LOGISTICS', + playerId: 'Player', + income: { fuel: 5 }, + }); + + const bsmNoIncome = new BuildingStateMachine({}, { + type: 'BARRACKS', + startActive: true, + playerId: 'Player', + }); + + return { + economy, + buildingStateMachines: [bsmActive, bsmConstructing, bsmNoIncome], + }; + } + + test('simulated update loop adds income for ACTIVE buildings only', () => { + const { economy, buildingStateMachines } = buildMockOrchestrator(); + + // Simulate what SystemOrchestrator.update() does for 'buildings' case + const time = 0; + for (const bsm of buildingStateMachines) { + if (bsm.tick) { + const income = bsm.tick(time, 16); + if (income && bsm.playerId) { + economy.addIncome(bsm.playerId, income); + } + } + } + + const res = economy.getResources('Player'); + expect(res.fuel).toBe(105); // 100 + 5 from LOGISTICS + expect(res.ammo).toBe(100); // unchanged + }); + + test('simulated update loop skips CONSTRUCTING and no-income buildings', () => { + const { economy, buildingStateMachines } = buildMockOrchestrator(); + + // Two ticks, 1000ms apart + [0, 1000].forEach((time) => { + for (const bsm of buildingStateMachines) { + if (bsm.tick) { + const income = bsm.tick(time, 16); + if (income && bsm.playerId) { + economy.addIncome(bsm.playerId, income); + } + } + } + }); + + const res = economy.getResources('Player'); + // Only the ACTIVE LOGISTICS building fires twice (0ms and 1000ms) + expect(res.fuel).toBe(110); // 100 + 5 + 5 + expect(res.ammo).toBe(100); + }); +}); diff --git a/tests/unit/BuildingPlacer.test.js b/tests/unit/BuildingPlacer.test.js new file mode 100644 index 0000000..99118e3 --- /dev/null +++ b/tests/unit/BuildingPlacer.test.js @@ -0,0 +1,277 @@ +/** + * BuildingPlacer.test.js — S5.1: Building placement system + * + * Tests: + * 1. Constructor creates hidden ghost sprite, wires pointer events + * 2. startPlacement shows ghost and stores building type + * 3. updateGhost snaps to tile grid (tileToWorldXY) + * 4. isValidPlacement rejects collision tiles, water, overlapping buildings + * 5. Ghost tint green when valid, red when invalid + * 6. tryPlace on valid spot: deducts resources, calls orchestrator.registerBuilding(), emits building:placed + * 7. tryPlace returns false / no-op when player can't afford + * 8. cancel hides ghost and resets state + * 9. destroy cleans up ghost and input listeners + */ + +import BuildingPlacer from 'Systems/BuildingPlacer'; + +// ── Helpers ─────────────────────────────────────────────────────── + +function buildMockScene() { + const objects = []; + const eventListeners = {}; + const inputOnHandlers = {}; + const sprites = []; + + const mockScene = { + add: { + sprite: jest.fn((x, y, key) => { + const s = { + x, y, texture: key, + active: true, + visible: false, + alpha: 0, + tint: 0xffffff, + setPosition: jest.fn(function (px, py) { this.x = px; this.y = py; return this; }), + setVisible: jest.fn(function (v) { this.visible = v; return this; }), + setAlpha: jest.fn(function (a) { this.alpha = a; return this; }), + setTint: jest.fn(function (c) { this.tint = c; return this; }), + setDisplaySize: jest.fn().mockReturnThis(), + setOrigin: jest.fn().mockReturnThis(), + setScrollFactor: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + destroy: jest.fn(), + }; + sprites.push(s); + return s; + }), + rectangle: jest.fn((x, y, w, h, color) => { + const r = { + x, y, width: w, height: h, + fillColor: color, + setOrigin: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setPosition: jest.fn(function (px, py) { this.x = px; this.y = py; return this; }), + destroy: jest.fn(), + active: true, + }; + objects.push(r); + return r; + }), + container: jest.fn(() => ({ + list: [], + add: jest.fn(), + remove: jest.fn(), + getAll: jest.fn(() => []), + })), + }, + input: { + on: jest.fn((event, cb) => { + inputOnHandlers[event] = cb; + }), + off: jest.fn(), + _handlers: inputOnHandlers, + _fire: (event, ...args) => { + if (inputOnHandlers[event]) inputOnHandlers[event](...args); + }, + }, + events: { + emit: jest.fn(), + on: jest.fn((event, cb) => { + if (!eventListeners[event]) eventListeners[event] = []; + eventListeners[event].push(cb); + }), + _listeners: eventListeners, + _fire: (event, ...args) => { + (eventListeners[event] || []).forEach(fn => fn(...args)); + }, + }, + map: { + tileToWorldXY: jest.fn((tx, ty) => ({ x: tx * 32, y: ty * 32 })), + worldToTileXY: jest.fn((wx, wy) => ({ x: Math.floor(wx / 32), y: Math.floor(wy / 32) })), + }, + groundLayer: { + getTileAt: jest.fn((tx, ty) => ({ x: tx, y: ty, index: 1 })), + }, + rockLayer: { + getTileAt: jest.fn((tx, ty) => null), // no collision by default + }, + buildings: { + list: [], + add: jest.fn(), + getAll: jest.fn(() => []), + }, + orchestrator: { + systems: { + economy: { + canAfford: jest.fn(() => true), + deduct: jest.fn(() => true), + }, + }, + registerBuilding: jest.fn((building, config) => ({ building, config })), + }, + _sprites: sprites, + }; + + return mockScene; +} + +function buildPointer(x, y) { + return { x, y, worldX: x, worldY: y }; +} + +// ── Tests ───────────────────────────────────────────────────────── + +describe('BuildingPlacer', () => { + let scene; + let placer; + + beforeEach(() => { + scene = buildMockScene(); + placer = new BuildingPlacer(scene); + }); + + afterEach(() => { + placer?.destroy?.(); + }); + + // -- 1. Constructor ------------------------------------------------- + test('constructor creates a hidden ghost sprite', () => { + expect(scene.add.sprite).toHaveBeenCalled(); + expect(placer.ghost).toBeDefined(); + expect(placer.ghost.visible).toBe(false); + expect(placer.ghost.alpha).toBe(0); + }); + + test('constructor wires pointermove and pointerdown', () => { + expect(scene.input.on).toHaveBeenCalledWith('pointermove', expect.any(Function)); + expect(scene.input.on).toHaveBeenCalledWith('pointerdown', expect.any(Function)); + }); + + // -- 2. startPlacement ---------------------------------------------- + test('startPlacement shows ghost and remembers building type', () => { + placer.startPlacement('BARRACKS'); + expect(placer.ghost.setVisible).toHaveBeenCalledWith(true); + expect(placer.ghost.setAlpha).toHaveBeenCalledWith(0.6); + expect(placer._buildingType).toBe('BARRACKS'); + expect(placer._placing).toBe(true); + }); + + // -- 3. updateGhost + grid snap ----------------------------------- + test('updateGhost snaps ghost to tile grid', () => { + placer.startPlacement('BARRACKS'); + const pointer = buildPointer(100, 100); // tile (3,3) + scene.input._fire('pointermove', pointer); + + expect(scene.map.worldToTileXY).toHaveBeenCalled(); + expect(scene.map.tileToWorldXY).toHaveBeenCalled(); + expect(placer.ghost.setPosition).toHaveBeenCalled(); + }); + + // -- 4. isValidPlacement -------------------------------------------- + test('isValidPlacement returns true for open ground', () => { + const valid = placer.isValidPlacement(5, 5); + expect(valid).toBe(true); + }); + + test('isValidPlacement returns false for collision tiles (rock)', () => { + scene.rockLayer.getTileAt = jest.fn((tx, ty) => ({ x: tx, y: ty, properties: { collides: true } })); + const valid = placer.isValidPlacement(5, 5); + expect(valid).toBe(false); + }); + + test('isValidPlacement returns false for water tiles', () => { + scene.groundLayer.getTileAt = jest.fn((tx, ty) => ({ x: tx, y: ty, index: 2, properties: { water: true } })); + const valid = placer.isValidPlacement(5, 5); + expect(valid).toBe(false); + }); + + test('isValidPlacement returns false when overlapping existing buildings', () => { + scene.buildings.list = [{ x: 5 * 32, y: 5 * 32, width: 32, height: 32 }]; + scene.buildings.getAll = jest.fn(() => scene.buildings.list); + const valid = placer.isValidPlacement(5, 5); + expect(valid).toBe(false); + }); + + // -- 5. Ghost tint -------------------------------------------------- + test('ghost tint is green when placement is valid', () => { + placer.startPlacement('BARRACKS'); + const pointer = buildPointer(5 * 32, 5 * 32); + scene.input._fire('pointermove', pointer); + expect(placer.ghost.setTint).toHaveBeenCalledWith(0x00ff00); + }); + + test('ghost tint is red when placement is invalid', () => { + scene.rockLayer.getTileAt = jest.fn(() => ({ properties: { collides: true } })); + placer.startPlacement('BARRACKS'); + const pointer = buildPointer(5 * 32, 5 * 32); + scene.input._fire('pointermove', pointer); + expect(placer.ghost.setTint).toHaveBeenCalledWith(0xff0000); + }); + + // -- 6. tryPlace (valid, affordable) -------------------------------- + test('tryPlace deducts resources via economy', () => { + placer.startPlacement('BARRACKS'); + const pointer = buildPointer(5 * 32, 5 * 32); + scene.input._fire('pointerdown', pointer); + + expect(scene.orchestrator.systems.economy.deduct).toHaveBeenCalled(); + }); + + test('tryPlace calls orchestrator.registerBuilding', () => { + placer.startPlacement('BARRACKS'); + const pointer = buildPointer(5 * 32, 5 * 32); + scene.input._fire('pointerdown', pointer); + + expect(scene.orchestrator.registerBuilding).toHaveBeenCalled(); + }); + + test('tryPlace emits building:placed event', () => { + placer.startPlacement('BARRACKS'); + const pointer = buildPointer(5 * 32, 5 * 32); + scene.input._fire('pointerdown', pointer); + + expect(scene.events.emit).toHaveBeenCalledWith( + 'building:placed', + expect.objectContaining({ type: 'BARRACKS' }), + ); + }); + + test('tryPlace hides ghost after placement', () => { + placer.startPlacement('BARRACKS'); + const pointer = buildPointer(5 * 32, 5 * 32); + scene.input._fire('pointerdown', pointer); + + expect(placer.ghost.setVisible).toHaveBeenCalledWith(false); + expect(placer._placing).toBe(false); + }); + + // -- 7. tryPlace (can't afford) ------------------------------------- + test('tryPlace returns false when player cannot afford', () => { + scene.orchestrator.systems.economy.canAfford = jest.fn(() => false); + placer.startPlacement('VEHICLE_DEPOT'); + const pointer = buildPointer(5 * 32, 5 * 32); + scene.input._fire('pointerdown', pointer); + + expect(scene.orchestrator.systems.economy.deduct).not.toHaveBeenCalled(); + expect(scene.orchestrator.registerBuilding).not.toHaveBeenCalled(); + expect(placer._placing).toBe(true); // stays in placement mode + }); + + // -- 8. cancel ------------------------------------------------------ + test('cancel hides ghost and resets state', () => { + placer.startPlacement('BARRACKS'); + placer.cancel(); + expect(placer.ghost.setVisible).toHaveBeenCalledWith(false); + expect(placer._placing).toBe(false); + expect(placer._buildingType).toBeNull(); + }); + + // -- 9. destroy ----------------------------------------------------- + test('destroy removes ghost and unregisters listeners', () => { + const ghostDestroy = placer.ghost.destroy; + placer.destroy(); + expect(ghostDestroy).toHaveBeenCalled(); + expect(placer._placing).toBe(false); + }); +}); diff --git a/tests/unit/BuildingRenderer.test.js b/tests/unit/BuildingRenderer.test.js new file mode 100644 index 0000000..5785f23 --- /dev/null +++ b/tests/unit/BuildingRenderer.test.js @@ -0,0 +1,270 @@ +/** + * BuildingRenderer.test.js — S5.2: Building rendering + * + * Tests: + * 1. Constructor creates buildings container at correct depth + * 2. render() creates rectangle with type-specific color + * 3. render() registers with orchestrator.registerBuilding() + * 4. render() wires pointerdown for selection + * 5. update() sets CONSTRUCTING alpha to 0.4 + * 6. update() sets ACTIVE alpha to 1.0 + * 7. update() pulses PRODUCING alpha between 0.7 and 1.0 + * 8. select() shows selection highlight around building + * 9. deselect() hides selection highlight + * 10. destroyBuilding() unregisters from orchestrator and destroys graphics + * 11. destroy() cleans up all buildings and container + */ + +import BuildingRenderer from 'Systems/BuildingRenderer'; + +// ── Helpers ─────────────────────────────────────────────────────── + +function buildMockScene() { + const rectangles = []; + const containers = []; + const eventListeners = {}; + + const mockScene = { + add: { + rectangle: jest.fn((x, y, w, h, color) => { + const r = { + x, y, width: w, height: h, + fillColor: color, + alpha: 1, + visible: true, + active: true, + depth: 0, + setOrigin: jest.fn().mockReturnThis(), + setDepth: jest.fn(function (d) { this.depth = d; return this; }), + setPosition: jest.fn(function (px, py) { this.x = px; this.y = py; return this; }), + setAlpha: jest.fn(function (a) { this.alpha = a; return this; }), + setVisible: jest.fn(function (v) { this.visible = v; return this; }), + setFillStyle: jest.fn(function (c) { this.fillColor = c; return this; }), + setStrokeStyle: jest.fn().mockReturnThis(), + setInteractive: jest.fn().mockReturnThis(), + on: jest.fn((event, cb) => { + if (!eventListeners[event]) eventListeners[event] = []; + eventListeners[event].push({ target: r, cb }); + }), + off: jest.fn(), + destroy: jest.fn(), + input: {}, + }; + rectangles.push(r); + return r; + }), + container: jest.fn(() => { + const c = { + list: [], + add: jest.fn(function (obj) { this.list.push(obj); return this; }), + remove: jest.fn(), + removeAll: jest.fn(function () { this.list = []; return this; }), + destroy: jest.fn(), + setDepth: jest.fn().mockReturnThis(), + setName: jest.fn().mockReturnThis(), + active: true, + }; + containers.push(c); + return c; + }), + }, + events: { + emit: jest.fn(), + on: jest.fn(), + off: jest.fn(), + }, + orchestrator: { + registerBuilding: jest.fn((building, config) => ({ + building, + config, + getState: jest.fn(() => 'ACTIVE'), + destroy: jest.fn(), + })), + unregisterBuilding: jest.fn(), + systems: {}, + }, + map: { + tileToWorldXY: jest.fn((tx, ty) => ({ x: tx * 32, y: ty * 32 })), + }, + groundLayer: { + getTileAtWorldXY: jest.fn(() => ({ x: 0, y: 0 })), + }, + _rectangles: rectangles, + _containers: containers, + _inputListeners: eventListeners, + }; + + return mockScene; +} + +// ── Tests ───────────────────────────────────────────────────────── + +describe('BuildingRenderer', () => { + let scene; + let renderer; + + beforeEach(() => { + scene = buildMockScene(); + renderer = new BuildingRenderer(scene); + }); + + afterEach(() => { + renderer?.destroy?.(); + }); + + // -- 1. Constructor ------------------------------------------------- + test('constructor creates buildings container with correct depth', () => { + expect(scene.add.container).toHaveBeenCalled(); + expect(renderer.container).toBeDefined(); + expect(renderer.container.setDepth).toHaveBeenCalledWith(5); + expect(renderer.container.setName).toHaveBeenCalledWith('Buildings'); + }); + + // -- 2. render() type-specific color -------------------------------- + test('render() creates rectangle with Barracks color #4a90d9', () => { + const worldPos = { x: 100, y: 100 }; + renderer.render(worldPos, 'BARRACKS', { playerId: 'Player' }); + + const rect = scene._rectangles[scene._rectangles.length - 1]; + expect(rect.fillColor).toBe(0x4a90d9); + }); + + test('render() creates rectangle with VehicleDepot color #8b4513', () => { + const worldPos = { x: 100, y: 100 }; + renderer.render(worldPos, 'VEHICLE_DEPOT', { playerId: 'Player' }); + + const rect = scene._rectangles[scene._rectangles.length - 1]; + expect(rect.fillColor).toBe(0x8b4513); + }); + + test('render() creates rectangle with Logistics color #d4a017', () => { + const worldPos = { x: 100, y: 100 }; + renderer.render(worldPos, 'LOGISTICS', { playerId: 'Player' }); + + const rect = scene._rectangles[scene._rectangles.length - 1]; + expect(rect.fillColor).toBe(0xd4a017); + }); + + test('render() creates rectangle with AmmoFactory color #d94a4a', () => { + const worldPos = { x: 100, y: 100 }; + renderer.render(worldPos, 'AMMO_FACTORY', { playerId: 'Player' }); + + const rect = scene._rectangles[scene._rectangles.length - 1]; + expect(rect.fillColor).toBe(0xd94a4a); + }); + + test('render() creates rectangle with CommandCenter color #ffd700', () => { + const worldPos = { x: 100, y: 100 }; + renderer.render(worldPos, 'COMMAND_CENTER', { playerId: 'Player' }); + + const rect = scene._rectangles[scene._rectangles.length - 1]; + expect(rect.fillColor).toBe(0xffd700); + }); + + // -- 3. render() registers with orchestrator ---------------------- + test('render() calls orchestrator.registerBuilding()', () => { + const worldPos = { x: 100, y: 100 }; + renderer.render(worldPos, 'BARRACKS', { playerId: 'Player', buildTime: 5000 }); + + expect(scene.orchestrator.registerBuilding).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ type: 'BARRACKS', playerId: 'Player', buildTime: 5000 }), + ); + }); + + // -- 4. render() wires click for selection ------------------------- + test('render() wires pointerdown for selection', () => { + const worldPos = { x: 100, y: 100 }; + renderer.render(worldPos, 'BARRACKS', { playerId: 'Player' }); + + const rect = scene._rectangles[scene._rectangles.length - 1]; + expect(rect.setInteractive).toHaveBeenCalled(); + expect(rect.on).toHaveBeenCalledWith('pointerdown', expect.any(Function)); + }); + + // -- 5. update() CONSTRUCTING alpha -------------------------------- + test('update() sets CONSTRUCTING alpha to 0.4', () => { + const worldPos = { x: 100, y: 100 }; + const entry = renderer.render(worldPos, 'BARRACKS', { playerId: 'Player' }); + entry.bsm.getState = jest.fn(() => 'CONSTRUCTING'); + + renderer.update(0, 16); + expect(entry.graphics.setAlpha).toHaveBeenCalledWith(0.4); + }); + + // -- 6. update() ACTIVE alpha -------------------------------------- + test('update() sets ACTIVE alpha to 1.0', () => { + const worldPos = { x: 100, y: 100 }; + const entry = renderer.render(worldPos, 'BARRACKS', { playerId: 'Player' }); + entry.bsm.getState = jest.fn(() => 'ACTIVE'); + + renderer.update(0, 16); + expect(entry.graphics.setAlpha).toHaveBeenCalledWith(1.0); + }); + + // -- 7. update() PRODUCING pulsing alpha ---------------------------- + test('update() pulses PRODUCING alpha between 0.7 and 1.0', () => { + const worldPos = { x: 100, y: 100 }; + const entry = renderer.render(worldPos, 'BARRACKS', { playerId: 'Player' }); + entry.bsm.getState = jest.fn(() => 'PRODUCING'); + + renderer.update(0, 16); + const alphaAt0 = entry.graphics.setAlpha.mock.calls[entry.graphics.setAlpha.mock.calls.length - 1][0]; + expect(alphaAt0).toBeGreaterThanOrEqual(0.7); + expect(alphaAt0).toBeLessThanOrEqual(1.0); + + renderer.update(314, 16); // ~pi/2 phase + const alphaAt314 = entry.graphics.setAlpha.mock.calls[entry.graphics.setAlpha.mock.calls.length - 1][0]; + expect(alphaAt314).toBeGreaterThanOrEqual(0.7); + expect(alphaAt314).toBeLessThanOrEqual(1.0); + }); + + // -- 8. select() shows selection highlight ------------------------- + test('select() shows selection highlight around building', () => { + const worldPos = { x: 100, y: 100 }; + const entry = renderer.render(worldPos, 'BARRACKS', { playerId: 'Player' }); + + renderer.select(entry.graphics); + + expect(renderer.selectionHighlight).toBeDefined(); + expect(renderer.selectionHighlight.visible).toBe(true); + expect(renderer.selectionHighlight.setPosition).toHaveBeenCalledWith(entry.graphics.x, entry.graphics.y); + }); + + // -- 9. deselect() hides selection highlight ----------------------- + test('deselect() hides selection highlight', () => { + const worldPos = { x: 100, y: 100 }; + const entry = renderer.render(worldPos, 'BARRACKS', { playerId: 'Player' }); + + renderer.select(entry.graphics); + renderer.deselect(); + + expect(renderer.selectionHighlight.visible).toBe(false); + }); + + // -- 10. destroyBuilding() ----------------------------------------- + test('destroyBuilding() unregisters from orchestrator and destroys graphics', () => { + const worldPos = { x: 100, y: 100 }; + const entry = renderer.render(worldPos, 'BARRACKS', { playerId: 'Player' }); + const bsmDestroy = entry.bsm.destroy; + const graphicsDestroy = entry.graphics.destroy; + + renderer.destroyBuilding(entry.graphics); + + expect(scene.orchestrator.unregisterBuilding).toHaveBeenCalledWith(entry.bsm); + expect(bsmDestroy).toHaveBeenCalled(); + expect(graphicsDestroy).toHaveBeenCalled(); + }); + + // -- 11. destroy() -------------------------------------------------- + test('destroy() cleans up all buildings and container', () => { + renderer.render({ x: 100, y: 100 }, 'BARRACKS', { playerId: 'Player' }); + renderer.render({ x: 200, y: 200 }, 'LOGISTICS', { playerId: 'Player' }); + + const containerDestroy = renderer.container.destroy; + renderer.destroy(); + + expect(containerDestroy).toHaveBeenCalled(); + expect(renderer.buildings).toHaveLength(0); + }); +}); diff --git a/tests/unit/CaptureProgressUI.test.js b/tests/unit/CaptureProgressUI.test.js new file mode 100644 index 0000000..f0288cf --- /dev/null +++ b/tests/unit/CaptureProgressUI.test.js @@ -0,0 +1,136 @@ +/** + * CaptureProgressUI.test.js -- S4.2: Capture progress world-space HUD + * + * Tests: + * 1. drawBar -- lazily creates Graphics for a CP on first update + * 2. update -- refreshes fill based on getCaptureProgress() + * 3. Color by state: neutral grey, contested yellow, captured green/red + * 4. destroy(cp) cleans up graphics + * 5. shutdown clears all + */ + +import CaptureProgressUI from 'Systems/CaptureProgressUI'; + +function buildMockScene() { + const added = []; + return { + add: { + graphics: jest.fn(() => { + const g = { + clear: jest.fn(), + fillStyle: jest.fn().mockReturnThis(), + fillRect: jest.fn().mockReturnThis(), + fillCircle: jest.fn().mockReturnThis(), + strokeCircle: jest.fn().mockReturnThis(), + lineStyle: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setPosition: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + x: 0, y: 0, + }; + added.push(g); + return g; + }), + }, + _graphicsAdded: added, + }; +} + +function buildMockCP(state = 'NEUTRAL', progress = 0, owner = null, overrides = {}) { + return { + id: 'cp_test_01', + x: 400, + y: 300, + radiusPx: 80, + active: true, + getCaptureProgress: jest.fn(() => progress), + getState: jest.fn(() => state), + getOwner: jest.fn(() => owner), + zone: { active: true }, + ...overrides, + }; +} + +describe('CaptureProgressUI', () => { + let scene; + let ui; + + beforeEach(() => { + scene = buildMockScene(); + ui = new CaptureProgressUI(scene); + }); + + afterEach(() => { + ui?.shutdown?.(); + }); + + // -- 1. drawBar lazily creates Graphics -------------------------------- + test('update lazily draws a bar for a new CP', () => { + const cp = buildMockCP('NEUTRAL', 0, null); + ui.update(cp); + expect(scene.add.graphics).toHaveBeenCalledTimes(1); + const g = ui._bars.get(cp); + expect(g).toBeDefined(); + expect(g.setDepth).toHaveBeenCalled(); + }); + + // -- 2. update refreshes fill based on progress -------------------------- + test('bar fill reflects capture progress at 50%', () => { + const cp = buildMockCP('CONTESTED', 50, null); + ui.update(cp); + const g = ui._bars.get(cp); + expect(g.fillStyle).toHaveBeenCalled(); + expect(g.fillRect).toHaveBeenCalled(); + }); + + test('bar fill width scales with progress fraction', () => { + const cp = buildMockCP('CONTESTED', 75, null, { radiusPx: 100 }); + ui.update(cp); + const g = ui._bars.get(cp); + // Should call fillRect for background + foreground + possibly border + expect(g.fillRect.mock.calls.length).toBeGreaterThanOrEqual(1); + }); + + // -- 3. Colors by state ------------------------------------------------ + test('getColor returns grey for NEUTRAL', () => { + expect(ui.getColor('NEUTRAL')).toBe(0xaaaaaa); + }); + + test('getColor returns yellow for CONTESTED', () => { + expect(ui.getColor('CONTESTED')).toBe(0xffcc00); + }); + + test('getColor returns green when captured by player', () => { + expect(ui.getColor('CAPTURED', 'player')).toBe(0x00ff00); + }); + + test('getColor returns red when captured by enemy', () => { + expect(ui.getColor('CAPTURED', 'enemy')).toBe(0xff3333); + }); + + // -- 4. destroy(cp) cleans up graphics --------------------------------- + test('destroy(cp) removes bar from Map and destroys graphics', () => { + const cp = buildMockCP('CAPTURED', 100, 'player'); + ui.update(cp); + const g = ui._bars.get(cp); + const destroySpy = g.destroy; + ui.destroy(cp); + expect(destroySpy).toHaveBeenCalled(); + expect(ui._bars.has(cp)).toBe(false); + }); + + // -- 5. shutdown clears all -------------------------------------------- + test('shutdown destroys all bars and clears Map', () => { + const cp1 = buildMockCP('NEUTRAL', 0); + const cp2 = buildMockCP('CAPTURED', 100, 'player'); + ui.update(cp1); + ui.update(cp2); + expect(ui._bars.size).toBe(2); + ui.shutdown(); + expect(ui._bars.size).toBe(0); + expect(scene._graphicsAdded.every(g => g.destroy.mock.calls.length > 0)).toBe(true); + }); +}); diff --git a/tests/unit/CombatSystem.test.js b/tests/unit/CombatSystem.test.js index ede3df7..a212249 100644 --- a/tests/unit/CombatSystem.test.js +++ b/tests/unit/CombatSystem.test.js @@ -37,6 +37,7 @@ jest.mock('phaser', () => ({ allowGravity: false, setSize: jest.fn(), setOffset: jest.fn(), + setVelocity: jest.fn(), }; this._data = {}; this.setData = jest.fn((k, v) => { this._data[k] = v; }); @@ -147,6 +148,7 @@ function mockEntity(x, y, overrides = {}) { describe('CombatSystem', () => { let combat; let mockScene; + let mockTeamManager; beforeEach(() => { jest.clearAllMocks(); @@ -176,7 +178,14 @@ describe('CombatSystem', () => { tweens: { addCounter: jest.fn(() => ({ stop: jest.fn() })) }, }; - combat = new CombatSystem(mockScene); + mockTeamManager = { + getEntityTeam: jest.fn(() => 'team-a'), + getAllUnitsGrouped: jest.fn(() => new Map()), + isEnemy: jest.fn(() => false), + getTeams: jest.fn(() => []) + }; + + combat = new CombatSystem(mockScene, mockTeamManager); }); // ── constructor ───────────────────────────────────────────────── @@ -190,9 +199,8 @@ describe('CombatSystem', () => { expect(combat.damageModifiers.tank_cannon).toBeDefined(); }); - test('_goodGuys and _enemies start null', () => { - expect(combat._goodGuys).toBeNull(); - expect(combat._enemies).toBeNull(); + test('teamManager is stored', () => { + expect(combat.teamManager).toBe(mockTeamManager); }); }); @@ -231,6 +239,13 @@ describe('CombatSystem', () => { const originalLos = combat.hasLineOfSight; combat.hasLineOfSight = jest.fn(() => true); + mockTeamManager.getEntityTeam.mockImplementation((e) => e.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'); + mockTeamManager.isEnemy.mockImplementation((a, b) => { + const ta = a.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + const tb = b.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + return ta !== tb; + }); + // entity with custom range 150: should acquire close target at distance ~40, // but NOT far target at distance ~200 const entity = mockEntity(100, 100, { @@ -241,6 +256,8 @@ describe('CombatSystem', () => { }), }); + mockTeamManager.getAllUnitsGrouped.mockReturnValue(new Map([['bad', new Set([close, far])], ['good', new Set([entity])]])); + const result = combat.acquireTarget(entity); expect(result).toBe(close); @@ -252,6 +269,7 @@ describe('CombatSystem', () => { getAll: () => [close, far], }), }); + mockTeamManager.getAllUnitsGrouped.mockReturnValue(new Map([['bad', new Set([close, far])], ['good', new Set([entityShort])]])); expect(combat.acquireTarget(entityShort)).toBeNull(); combat.hasLineOfSight = originalLos; @@ -263,6 +281,13 @@ describe('CombatSystem', () => { const originalLos = combat.hasLineOfSight; combat.hasLineOfSight = jest.fn(() => true); + mockTeamManager.getEntityTeam.mockImplementation((e) => e.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'); + mockTeamManager.isEnemy.mockImplementation((a, b) => { + const ta = a.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + const tb = b.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + return ta !== tb; + }); + const entity = mockEntity(100, 100, { components: { combat: { range: 200 } }, getEnemyContainer: () => ({ @@ -271,6 +296,8 @@ describe('CombatSystem', () => { }), }); + mockTeamManager.getAllUnitsGrouped.mockReturnValue(new Map([['bad', new Set([target])], ['good', new Set([entity])]])); + const result = combat.acquireTarget(entity); expect(result).toBe(target); @@ -285,6 +312,13 @@ describe('CombatSystem', () => { const originalLos = combat.hasLineOfSight; combat.hasLineOfSight = jest.fn(() => true); + mockTeamManager.getEntityTeam.mockImplementation((e) => e.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'); + mockTeamManager.isEnemy.mockImplementation((a, b) => { + const ta = a.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + const tb = b.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + return ta !== tb; + }); + const entity = mockEntity(100, 100, { getEnemyContainer: () => ({ list: [target1, target2], @@ -292,6 +326,8 @@ describe('CombatSystem', () => { }), }); + mockTeamManager.getAllUnitsGrouped.mockReturnValue(new Map([['bad', new Set([target1, target2])], ['good', new Set([entity])]])); + const result = combat.acquireTarget(entity); expect(result).toBe(target1); // closest @@ -313,6 +349,14 @@ describe('CombatSystem', () => { const originalLos = combat.hasLineOfSight; combat.hasLineOfSight = jest.fn(() => true); + mockTeamManager.getEntityTeam.mockImplementation((e) => e.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'); + mockTeamManager.isEnemy.mockImplementation((a, b) => { + const ta = a.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + const tb = b.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + return ta !== tb; + }); + mockTeamManager.getAllUnitsGrouped.mockReturnValue(new Map([['bad', new Set([target])], ['good', new Set([entity])]])); + const result = combat.acquireTarget(entity, { fov: 90 }); expect(result).toBe(target); @@ -336,6 +380,14 @@ describe('CombatSystem', () => { const originalLos = combat.hasLineOfSight; combat.hasLineOfSight = jest.fn(() => true); + mockTeamManager.getEntityTeam.mockImplementation((e) => e.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'); + mockTeamManager.isEnemy.mockImplementation((a, b) => { + const ta = a.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + const tb = b.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + return ta !== tb; + }); + mockTeamManager.getAllUnitsGrouped.mockReturnValue(new Map([['bad', new Set([strong, weak])], ['good', new Set([entity])]])); + const result = combat.acquireTarget(entity, { priority: 'weakest' }); expect(result).toBe(weak); @@ -366,6 +418,9 @@ describe('CombatSystem', () => { containerName: 'Bad Guys', dead: true, }); + // attacker and target are on different containers, but mockTeamManager.isEnemy returns false + // so canHit returns friendly_fire. Override for this test. + mockTeamManager.isEnemy.mockReturnValue(true); expect(combat.canHit(attacker, target)).toEqual({ canHit: false, reason: 'target_dead' }); }); @@ -373,6 +428,7 @@ describe('CombatSystem', () => { const attacker = mockEntity(0, 0, { containerName: 'Good Guys' }); const target = mockEntity(2000, 2000, { containerName: 'Bad Guys' }); // distance ~2828, default range 150 + mockTeamManager.isEnemy.mockReturnValue(true); const originalLos = combat.hasLineOfSight; combat.hasLineOfSight = jest.fn(() => false); @@ -468,19 +524,6 @@ describe('CombatSystem', () => { }); }); - // ── registerUnitContainers ────────────────────────────────────── - describe('registerUnitContainers', () => { - test('stores goodGuys and enemies references', () => { - const goodGuys = { name: 'Good Guys' }; - const enemies = { name: 'Bad Guys' }; - - combat.registerUnitContainers(goodGuys, enemies); - - expect(combat._goodGuys).toBe(goodGuys); - expect(combat._enemies).toBe(enemies); - }); - }); - // ── update loop ───────────────────────────────────────────────── describe('update', () => { test('handles empty projectile group', () => { @@ -546,10 +589,13 @@ describe('CombatSystem', () => { const originalLos = combat.hasLineOfSight; combat.hasLineOfSight = jest.fn(() => true); - combat._goodGuys = { - list: [attacker], - getAll: () => [attacker], - }; + mockTeamManager.getEntityTeam.mockImplementation((e) => e.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'); + mockTeamManager.isEnemy.mockImplementation((a, b) => { + const ta = a.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + const tb = b.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + return ta !== tb; + }); + mockTeamManager.getAllUnitsGrouped.mockReturnValue(new Map([['bad', new Set([enemy])], ['good', new Set([attacker])]])); // Mock fireProjectile so we can spy on it const fireSpy = jest.fn(); @@ -568,8 +614,7 @@ describe('CombatSystem', () => { }); test('auto-engage no-op when container is empty', () => { - combat._goodGuys = { list: [], getAll: () => [] }; - combat._enemies = { list: [], getAll: () => [] }; + mockTeamManager.getAllUnitsGrouped.mockReturnValue(new Map()); const fireSpy = jest.fn(); combat.fireProjectile = fireSpy; @@ -599,10 +644,13 @@ describe('CombatSystem', () => { const originalLos = combat.hasLineOfSight; combat.hasLineOfSight = jest.fn(() => true); - combat._goodGuys = { - list: [attacker], - getAll: () => [attacker], - }; + mockTeamManager.getEntityTeam.mockImplementation((e) => e.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'); + mockTeamManager.isEnemy.mockImplementation((a, b) => { + const ta = a.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + const tb = b.parentContainer?.name === 'Bad Guys' ? 'bad' : 'good'; + return ta !== tb; + }); + mockTeamManager.getAllUnitsGrouped.mockReturnValue(new Map([['bad', new Set([farEnemy])], ['good', new Set([attacker])]])); const fireSpy = jest.fn(); combat.fireProjectile = fireSpy; diff --git a/tests/unit/ControlPointManager.test.js b/tests/unit/ControlPointManager.test.js new file mode 100644 index 0000000..6436897 --- /dev/null +++ b/tests/unit/ControlPointManager.test.js @@ -0,0 +1,187 @@ +/** + * ControlPointManager.test.js — Tests for placing CPs and wiring to EconomySystem. + */ + +// Mock xstate before CPStateMachine imports it +jest.mock('xstate', () => ({ + createMachine: jest.fn((config) => ({ config, id: config.id })), + interpret: jest.fn((machine) => ({ + machine, + start: jest.fn(), + send: jest.fn(), + stop: jest.fn(), + state: { + value: 'NEUTRAL', + context: { owner: null, captureProgress: 0, unitsInRadius: {} }, + }, + })), + assign: jest.fn((fn) => fn), +})); + +import ControlPointManager from 'Systems/ControlPointManager.js'; + +// ── Helpers ─────────────────────────────────────────────────────── + +function mockTilemap() { + return { + tileToWorldXY: jest.fn((tx, ty) => ({ x: tx * 32, y: ty * 32 })), + tileWidth: 32, + tileHeight: 32, + }; +} + +function mockScene() { + return { + add: { + zone: jest.fn((x, y) => ({ + x: x ?? 0, + y: y ?? 0, + setName: jest.fn().mockReturnThis(), + destroy: jest.fn(), + body: { setCircle: jest.fn(), setOffset: jest.fn() }, + })), + }, + physics: { + world: { enableBody: jest.fn() }, + }, + events: { on: jest.fn(), emit: jest.fn(), off: jest.fn() }, + }; +} + +function mockEconomy() { + return { + addIncome: jest.fn(), + getResources: jest.fn(), + initPlayer: jest.fn(), + }; +} + +// ── Tests ───────────────────────────────────────────────────────── + +describe('ControlPointManager', () => { + let manager; + let scene; + let tilemap; + let economy; + + beforeEach(() => { + scene = mockScene(); + tilemap = mockTilemap(); + economy = mockEconomy(); + manager = new ControlPointManager(scene, tilemap, economy); + }); + + afterEach(() => { + if (manager) manager.destroy(); + }); + + // ── constructor ───────────────────────────────────────────────── + describe('constructor', () => { + test('creates 4 control points', () => { + expect(manager.controlPoints.length).toBe(4); + }); + + test('accepts optional teamManager parameter', () => { + const tm = { getAllUnitsGrouped: jest.fn() }; + const mgr = new ControlPointManager(scene, tilemap, economy, tm); + expect(mgr.teamManager).toBe(tm); + }); + + test('falls back to scene.teamManager when no teamManager passed', () => { + scene.teamManager = { getAllUnitsGrouped: jest.fn() }; + const mgr = new ControlPointManager(scene, tilemap, economy); + expect(mgr.teamManager).toBe(scene.teamManager); + }); + + test('CPs are at clearing centers converted to world coords', () => { + const expectedTiles = [ + { tx: 32, ty: 32 }, + { tx: 96, ty: 32 }, + { tx: 32, ty: 96 }, + { tx: 96, ty: 96 }, + ]; + + for (let i = 0; i < 4; i++) { + const cp = manager.controlPoints[i]; + expect(cp.zone.x).toBe(expectedTiles[i].tx * 32); + expect(cp.zone.y).toBe(expectedTiles[i].ty * 32); + } + }); + + test('tileToWorldXY is called for each CP', () => { + expect(tilemap.tileToWorldXY).toHaveBeenCalledTimes(4); + expect(tilemap.tileToWorldXY).toHaveBeenCalledWith(32, 32); + expect(tilemap.tileToWorldXY).toHaveBeenCalledWith(96, 32); + expect(tilemap.tileToWorldXY).toHaveBeenCalledWith(32, 96); + expect(tilemap.tileToWorldXY).toHaveBeenCalledWith(96, 96); + }); + + test('each CP has type=controlPoint, captureTime=60000, radius=5 tiles', () => { + for (const cp of manager.controlPoints) { + expect(cp.type).toBe('controlPoint'); + expect(cp.captureTime).toBe(60000); + expect(cp.radiusTiles).toBe(5); + } + }); + }); + + // ── update ────────────────────────────────────────────────────── + describe('update', () => { + test('ticks every CP', () => { + const tickSpies = manager.controlPoints.map((cp) => + jest.spyOn(cp, 'tick').mockImplementation(() => {}), + ); + + manager.update(1000, 16); + + // tick should be called with time and delta, not necessarily scene + for (const spy of tickSpies) { + expect(spy).toHaveBeenCalledWith(1000, 16, scene); + } + }); + }); + + // ── CP income (tick-driven CAPTURED check) ───────────────────── + describe('CP income', () => { + test('each CP is wired with the economy system on construction', () => { + for (const cp of manager.controlPoints) { + expect(cp.economySystem).toBe(economy); + } + }); + + test('does NOT call addIncome when state is not CAPTURED', () => { + const cp = manager.controlPoints[0]; + cp.getState = jest.fn(() => 'NEUTRAL'); + + manager.update(1000, 1000); + + expect(economy.addIncome).not.toHaveBeenCalled(); + }); + + test('does NOT call addIncome when owner is null', () => { + const cp = manager.controlPoints[0]; + cp.getState = jest.fn(() => 'CAPTURED'); + cp.getOwner = jest.fn(() => null); + + manager.update(1000, 1000); + + expect(economy.addIncome).not.toHaveBeenCalled(); + }); + }); + + // ── destroy → stop generating ─────────────────────────────────── + describe('destroy', () => { + test('destroys all CPs and clears the array', () => { + const destroySpies = manager.controlPoints.map((cp) => + jest.spyOn(cp, 'destroy').mockImplementation(() => {}), + ); + + manager.destroy(); + + for (const spy of destroySpies) { + expect(spy).toHaveBeenCalled(); + } + expect(manager.controlPoints.length).toBe(0); + }); + }); +}); diff --git a/tests/unit/DeathHandling.test.js b/tests/unit/DeathHandling.test.js new file mode 100644 index 0000000..77566ce --- /dev/null +++ b/tests/unit/DeathHandling.test.js @@ -0,0 +1,575 @@ +/** + * DeathHandling.test.js — Tests for DYING state, opacity tween, corpse spawn, + * kill events, and container cleanup. + */ + +// Mock Phaser classes so Infantry/Tank imports don't explode +jest.mock('phaser', () => ({ + Scene: class MockScene { + constructor(config) { + this.key = config?.key || ''; + this.sys = { events: new (require('events').EventEmitter)() }; + } + }, + Physics: { + Arcade: { + DYNAMIC_BODY: 0, + Sprite: class MockSprite { + constructor(scene, x, y, texture) { + this.scene = scene; + this.x = x; + this.y = y; + this.texture = { key: texture }; + this.body = { + allowGravity: false, + setSize: jest.fn(), + setOffset: jest.fn(), + center: { x, y }, + velocity: { x: 0, y: 0 }, + enable: jest.fn(), + }; + this.setScale = jest.fn(); + this.setInteractive = jest.fn(); + this.on = jest.fn(); + this.setPosition = jest.fn(() => true); + this.setFlipX = jest.fn(); + this.setTint = jest.fn(); + this.clearTint = jest.fn(); + this._data = {}; + this.displayWidth = 32; + this.displayHeight = 32; + this.setData = jest.fn((key, value) => { this._data[key] = value; }); + this.getData = jest.fn((key) => this._data[key] ?? null); + this.setAlpha = jest.fn(); + this.alpha = 1; + this.pulse = null; + this.active = true; + this.visible = true; + this.parentContainer = null; + } + destroy() {} + static enable(scene, object) { + object.body = { allowGravity: false, setSize: jest.fn(), setOffset: jest.fn(), center: { x: object.x, y: object.y } }; + } + } + } + }, + Display: { + Color: { + GetColor32: (r, g, b, a) => (r << 24) | (g << 16) | (b << 8) | a + } + }, + Tweens: { + Tween: class MockTween { + constructor(config) { this.config = config; } + getValue() { return 200; } + stop() {} + }, + addCounter: config => { + const tween = { getValue: () => 200, stop: () => {} }; + if (config.onUpdate) config.onUpdate(tween); + return tween; + } + }, + Events: { + EventEmitter: class MockEventEmitter { + constructor() { this.listeners = {}; } + on(event, fn) { + if (!this.listeners[event]) this.listeners[event] = []; + this.listeners[event].push(fn); + } + emit(event, ...args) { + if (this.listeners[event]) { + this.listeners[event].forEach(fn => fn(...args)); + } + } + off(event, fn) { + if (!this.listeners[event]) return; + this.listeners[event] = this.listeners[event].filter(f => f !== fn); + } + } + }, + GameObjects: { + Graphics: class MockGraphics { + constructor() { + this.clear = jest.fn(); + this.fillStyle = jest.fn().mockReturnThis(); + this.fillCircle = jest.fn().mockReturnThis(); + this.fillRect = jest.fn().mockReturnThis(); + this.lineStyle = jest.fn().mockReturnThis(); + this.strokeRect = jest.fn().mockReturnThis(); + this.setDepth = jest.fn().mockReturnThis(); + this.setPosition = jest.fn().mockReturnThis(); + this.setVisible = jest.fn().mockReturnThis(); + this.setAlpha = jest.fn().mockReturnThis(); + this.setScale = jest.fn().mockReturnThis(); + this.active = true; + this.destroy = jest.fn(); + } + }, + Container: class MockContainer { + constructor() { + this.list = []; + this.add = jest.fn((obj) => { this.list.push(obj); obj.parentContainer = this; }); + this.remove = jest.fn((obj) => { + const idx = this.list.indexOf(obj); + if (idx !== -1) this.list.splice(idx, 1); + if (obj) obj.parentContainer = null; + }); + this.getAll = jest.fn(() => this.list); + this.destroy = jest.fn(); + } + }, + Zone: class MockZone { + constructor(scene, x, y, width, height) { + this.scene = scene; + this.x = x; this.y = y; + this.width = width; this.height = height; + this.body = { setCircle: jest.fn(), checkCollision: { none: false } }; + } + destroy() {} + } + }, + Geom: { + Circle: class MockCircle { + constructor(x, y, r) { this.x = x; this.y = y; this.radius = r; } + }, + Rectangle: class MockRectangle { + constructor(x, y, w, h) { this.x = x; this.y = y; this.width = w; this.height = h; } + } + }, + Math: { + Vector2: class MockVector2 { constructor(x, y) { this.x = x; this.y = y; } }, + Angle: { + BetweenPoints: (a, b) => Math.atan2(b.y - a.y, b.x - a.x), + Between: (x1, y1, x2, y2) => Math.atan2(y2 - y1, x2 - x1), + Wrap: (angle) => angle, + }, + Distance: { + BetweenPoints: (a, b) => Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2)), + Between: (x1, y1, x2, y2) => Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), + }, + RadToDeg: rad => { + let deg = rad * (180 / Math.PI); + while (deg < 0) deg += 360; + return deg % 360; + }, + DegToRad: deg => deg * Math.PI / 180, + Clamp: (v, min, max) => Math.max(min, Math.min(max, v)), + }, + Input: { + Keyboard: { + KeyCodes: { A: 65, D: 68, W: 87, S: 83, SHIFT: 16, F: 70, CTRL: 17 }, + }, + Events: { + POINTER_DOWN: 'pointerdown', + POINTER_MOVE: 'pointermove', + POINTER_UP: 'pointerup', + POINTER_WHEEL: 'wheel', + } + }, + Cameras: { + Controls: { + SmoothedKeyControl: class MockSmoothedKeyControl { + constructor(config) { this.config = config; } + update() {} + } + } + } +})); + +jest.mock('xstate', () => ({ + createMachine: jest.fn(config => ({ config })), + interpret: jest.fn(machine => ({ + machine, start: jest.fn(), send: jest.fn(), stop: jest.fn(), + state: { value: 'IDLING' } + })), + assign: jest.fn(fn => fn) +})); + +jest.mock('easystarjs', () => ({ + __esModule: true, + default: { + js: jest.fn().mockImplementation(function () { + this.setGrid = jest.fn(); + this.setIterationsPerCalculation = jest.fn(); + this.findPath = jest.fn((x, y, toX, toY, callback) => { + setImmediate(() => callback([{ x, y }, { x: toX, y: toY }])); + }); + this.setTileAtXY = jest.fn(); + this.enableDiagonals = jest.fn(); + this.enableCornerCutting = jest.fn(); + this.setAcceptableTiles = jest.fn(); + this.setTileCost = jest.fn(); + this.setAdditionalPointCost = jest.fn(); + this.calculate = jest.fn(); + }), + }, +})); + +// ── Imports ───────────────────────────────────────────────────── +import Infantry_State_Config from 'Entities/base-units/state-configs/infantry-states.js'; +import Tank_State_Config from 'Entities/base-units/state-configs/tank-states.js'; + +// ── Helpers ───────────────────────────────────────────────────── + +function createMockScene() { + const emitted = []; + const tweens = []; + + const scene = { + events: { + emit: jest.fn((event, data) => { emitted.push({ event, data }); }), + on: jest.fn(), + off: jest.fn(), + listeners: {}, + }, + tweens: { + add: jest.fn((config) => { + const tween = { + config, + stop: jest.fn(), + isPlaying: jest.fn(() => false), + }; + tweens.push(tween); + // Fire onComplete immediately for synchronous test flow + if (config.onComplete) config.onComplete(tween); + return tween; + }), + addCounter: jest.fn((config) => { + const tween = { getValue: () => 200, stop: jest.fn() }; + if (config.onUpdate) config.onUpdate(tween); + return tween; + }), + }, + add: { + graphics: jest.fn(() => { + const g = new (require('phaser').GameObjects.Graphics)(); + return g; + }), + }, + physics: { + world: { enableBody: jest.fn() }, + add: { existing: jest.fn(), group: jest.fn(() => ({ getChildren: () => [] })) }, + overlap: jest.fn(() => false), + closest: jest.fn(() => null), + }, + groundLayer: { + getTileAt: jest.fn(() => ({ x: 0, y: 0 })), + tileToWorldXY: jest.fn((x, y) => ({ x: x * 32, y: y * 32 })), + }, + anims: { exists: jest.fn(() => false), create: jest.fn() }, + _emitted: emitted, + _tweens: tweens, + }; + + return scene; +} + +function createMockInfantry(scene) { + // Minimal Infantry-like mock with the methods the DYING state needs + const unit = { + scene, + x: 100, + y: 100, + dead: false, + body: { + center: { x: 100, y: 100 }, + velocity: { x: 10, y: 10 }, + allowGravity: false, + setSize: jest.fn(), + setOffset: jest.fn(), + }, + _data: {}, + texture: { key: 'infantry-ukraine' }, + setData: jest.fn((key, value) => { unit._data[key] = value; }), + getData: jest.fn((key) => unit._data[key] ?? null), + setAlpha: jest.fn((a) => { unit.alpha = a; }), + alpha: 1, + setScale: jest.fn(), + setVelocity: jest.fn(), + disableBody: jest.fn(() => { + unit.body.velocity.x = 0; + unit.body.velocity.y = 0; + unit.body.enable = false; + }), + destroy: jest.fn(() => { unit.active = false; }), + active: true, + parentContainer: null, + movement: { shouldUpdate: jest.fn(() => true) }, + stateMachine: { send: jest.fn(), destroy: jest.fn(), tick: jest.fn() }, + pulse: null, + _playAnimation: jest.fn(), + clearTarget: jest.fn(), + engageNearbyEnemies: jest.fn(), + nextPath: jest.fn(() => false), + orientToTarget: jest.fn(), + newOrientation: jest.fn(), + handleDeath: jest.fn(() => { + unit.dead = true; + // Trigger the DYING state's onEnter manually in tests + Infantry_State_Config.DYING.onEnter(unit); + }), + isDead: jest.fn(() => unit.dead), + canHitBody: jest.fn(() => false), + ACTIONS: { + goIDLE: jest.fn(), + MOVE: jest.fn(), + goSHOOT: jest.fn(), + DIE: jest.fn(() => { + unit.dead = true; + Infantry_State_Config.DYING.onEnter(unit); + }), + shootTARGET: jest.fn(), + }, + }; + return unit; +} + +function createMockTank(scene) { + const unit = { + scene, + x: 200, + y: 200, + dead: false, + body: { + center: { x: 200, y: 200 }, + velocity: { x: 5, y: 5 }, + allowGravity: false, + setSize: jest.fn(), + setOffset: jest.fn(), + }, + _data: {}, + texture: { key: 'tank-ukrainian' }, + setData: jest.fn((key, value) => { unit._data[key] = value; }), + getData: jest.fn((key) => unit._data[key] ?? null), + setAlpha: jest.fn((a) => { unit.alpha = a; }), + alpha: 1, + setScale: jest.fn(), + setVelocity: jest.fn(), + disableBody: jest.fn(() => { + unit.body.velocity.x = 0; + unit.body.velocity.y = 0; + unit.body.enable = false; + }), + destroy: jest.fn(() => { unit.active = false; }), + active: true, + parentContainer: null, + movement: { shouldUpdate: jest.fn(() => true) }, + stateMachine: { send: jest.fn(), destroy: jest.fn(), tick: jest.fn() }, + pulse: null, + _playAnimation: jest.fn(), + clearTarget: jest.fn(), + engageNearbyEnemies: jest.fn(), + nextPath: jest.fn(() => false), + orientToTarget: jest.fn(), + newOrientation: jest.fn(), + handleDeath: jest.fn(() => { + unit.dead = true; + Tank_State_Config.DYING.onEnter(unit); + }), + isDead: jest.fn(() => unit.dead), + canHitBody: jest.fn(() => false), + ACTIONS: { + goIDLE: jest.fn(), + MOVE: jest.fn(), + goSHOOT: jest.fn(), + DIE: jest.fn(() => { + unit.dead = true; + Tank_State_Config.DYING.onEnter(unit); + }), + shootTARGET: jest.fn(), + }, + }; + return unit; +} + +// ── Test Suite ────────────────────────────────────────────────── + +describe('Death Handling', () => { + let scene; + + beforeEach(() => { + jest.useFakeTimers(); + scene = createMockScene(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + // ── 1. DYING state triggers opacity tween ───────────────────── + describe('DYING state — opacity tween', () => { + test('infantry DYING onEnter adds opacity tween via scene.tweens.add', () => { + const unit = createMockInfantry(scene); + unit.ACTIONS.DIE(); + + expect(scene.tweens.add).toHaveBeenCalled(); + // Two tweens: puff (300ms) and unit alpha (500ms) + const unitTweenCall = scene.tweens.add.mock.calls.find( + c => c[0].duration === 500 + ); + expect(unitTweenCall).toBeDefined(); + expect(unitTweenCall[0].targets).toBe(unit); + expect(unitTweenCall[0].alpha).toBeDefined(); + }); + + test('infantry DYING onEnter sets dead flag and stops movement', () => { + const unit = createMockInfantry(scene); + unit.ACTIONS.DIE(); + + expect(unit.dead).toBe(true); + expect(unit.disableBody).toHaveBeenCalled(); + expect(unit.body.velocity.x).toBe(0); + expect(unit.body.velocity.y).toBe(0); + }); + + test('tank DYING onEnter adds opacity tween with same parameters', () => { + const unit = createMockTank(scene); + unit.ACTIONS.DIE(); + + expect(scene.tweens.add).toHaveBeenCalled(); + const unitTweenCall = scene.tweens.add.mock.calls.find( + c => c[0].duration === 500 + ); + expect(unitTweenCall).toBeDefined(); + expect(unitTweenCall[0].targets).toBe(unit); + }); + }); + + // ── 2. Corpse spawns on death ─────────────────────────────────── + describe('Corpse / smoke-puff effect', () => { + test('infantry death spawns a Graphics smoke puff at unit position', () => { + const unit = createMockInfantry(scene); + unit.ACTIONS.DIE(); + + expect(scene.add.graphics).toHaveBeenCalled(); + const puff = scene.add.graphics.mock.results[0].value; + expect(puff.fillStyle).toHaveBeenCalled(); + expect(puff.fillCircle).toHaveBeenCalled(); + }); + + test('tank death spawns a Graphics smoke puff at tank position', () => { + const unit = createMockTank(scene); + unit.ACTIONS.DIE(); + + expect(scene.add.graphics).toHaveBeenCalled(); + }); + + test('smoke puff tweens scale and alpha over 300ms', () => { + const unit = createMockInfantry(scene); + unit.ACTIONS.DIE(); + + // We should have at least two tweens: one for unit alpha, one for puff + const puffTween = scene._tweens.find(t => + t.config && t.config.duration === 300 + ); + expect(puffTween).toBeDefined(); + }); + + test('smoke puff is destroyed after its tween completes', () => { + const unit = createMockInfantry(scene); + unit.ACTIONS.DIE(); + + const puff = scene.add.graphics.mock.results[0].value; + // The puff tween's onComplete should destroy the puff + const puffTween = scene._tweens.find(t => + t.config && t.config.duration === 300 + ); + expect(puffTween).toBeDefined(); + expect(puffTween.config.onComplete).toBeDefined(); + // Simulate completion + puffTween.config.onComplete(puffTween); + expect(puff.destroy).toHaveBeenCalled(); + }); + }); + + // ── 3. Kill event fires ─────────────────────────────────────── + describe('Kill event', () => { + test('infantry DYING emits unit:killed after 500ms', () => { + const unit = createMockInfantry(scene); + unit.ACTIONS.DIE(); + + // The tween's onComplete should fire the kill event + const tween = scene._tweens.find(t => t.config && t.config.duration === 500); + expect(tween).toBeDefined(); + expect(tween.config.onComplete).toBeDefined(); + + // Fire the onComplete callback + tween.config.onComplete(tween); + + expect(scene.events.emit).toHaveBeenCalledWith( + 'unit:killed', + expect.objectContaining({ entity: unit }) + ); + }); + + test('tank DYING emits unit:killed after 500ms', () => { + const unit = createMockTank(scene); + unit.ACTIONS.DIE(); + + const tween = scene._tweens.find(t => t.config && t.config.duration === 500); + expect(tween).toBeDefined(); + tween.config.onComplete(tween); + + expect(scene.events.emit).toHaveBeenCalledWith( + 'unit:killed', + expect.objectContaining({ entity: unit }) + ); + }); + + test('unit:killed payload includes entity reference', () => { + const unit = createMockInfantry(scene); + unit.ACTIONS.DIE(); + + const tween = scene._tweens.find(t => t.config && t.config.duration === 500); + tween.config.onComplete(tween); + + const killCall = scene.events.emit.mock.calls.find( + ([event]) => event === 'unit:killed' + ); + expect(killCall).toBeDefined(); + expect(killCall[1].entity).toBe(unit); + }); + }); + + // ── 4. Cleanup removes from container ─────────────────────────── + describe('Cleanup', () => { + test('DYING onEnter removes unit from its parent container', () => { + const container = { list: [], remove: jest.fn(), destroy: jest.fn() }; + const unit = createMockInfantry(scene); + unit.parentContainer = container; + unit.ACTIONS.DIE(); + + expect(container.remove).toHaveBeenCalledWith(unit); + }); + + test('unit is destroyed after kill event (via tween onComplete)', () => { + const unit = createMockInfantry(scene); + unit.ACTIONS.DIE(); + + const tween = scene._tweens.find(t => t.config && t.config.duration === 500); + tween.config.onComplete(tween); + + expect(unit.destroy).toHaveBeenCalled(); + }); + + test('scene shutdown destroys all tracked effects', () => { + // Simulate effects tracking array on scene + scene._deathEffects = []; + + const unit = createMockInfantry(scene); + unit.ACTIONS.DIE(); + + const puff = scene.add.graphics.mock.results[0]?.value; + if (puff) scene._deathEffects.push(puff); + + // Simulate shutdown + for (const effect of (scene._deathEffects || [])) { + effect.destroy(); + } + + if (puff) expect(puff.destroy).toHaveBeenCalled(); + }); + }); +}); diff --git a/tests/unit/HealthBarSystem.test.js b/tests/unit/HealthBarSystem.test.js new file mode 100644 index 0000000..1bfa0ef --- /dev/null +++ b/tests/unit/HealthBarSystem.test.js @@ -0,0 +1,160 @@ +/** + * HealthBarSystem.test.js — M2.2: Health bar overlay + * + * Tests: + * 1. drawBar — bar draws with correct width proportional to sprite displayWidth + * 2. Color transitions at thresholds — green (100%), yellow (50%), red (<25%) + * 3. Auto-hide at full HP, show on damage + * 4. destroy(unit) cleans up graphics objects + */ + +import HealthBarSystem from 'Systems/HealthBarSystem'; + +// ── Helpers ─────────────────────────────────────────────────────── + +function buildMockScene() { + const added = []; + return { + add: { + graphics: jest.fn(() => { + const g = { + clear: jest.fn(), + fillStyle: jest.fn().mockReturnThis(), + fillRect: jest.fn().mockReturnThis(), + lineStyle: jest.fn().mockReturnThis(), + strokeRect: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setPosition: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + x: 0, + y: 0, + }; + added.push(g); + return g; + }), + }, + _graphicsAdded: added, + }; +} + +function buildMockUnit(overrides = {}) { + const hp = overrides.hp ?? 100; + const maxHp = overrides.maxHp ?? 100; + + return { + x: 100, + y: 200, + displayWidth: 48, + displayHeight: 48, + active: true, + _data: { health: hp, maxHp }, + getData: jest.fn((key) => { + if (key === 'health') return hp; + if (key === 'maxHp') return maxHp; + return undefined; + }), + setData: jest.fn(), + ...overrides, + }; +} + +// ── Tests ───────────────────────────────────────────────────────── + +describe('HealthBarSystem', () => { + let scene; + let system; + + beforeEach(() => { + scene = buildMockScene(); + system = new HealthBarSystem(scene); + }); + + afterEach(() => { + system?.destroy?.(); + }); + + // ── 1. drawBar — correct width proportional to displayWidth ───── + test('drawBar creates a bar whose width matches unit displayWidth', () => { + const unit = buildMockUnit({ hp: 75, maxHp: 100 }); + system.drawBar(unit); + + expect(scene.add.graphics).toHaveBeenCalled(); + const graphics = system._bars.get(unit); + expect(graphics).toBeDefined(); + expect(graphics.active).toBe(true); + + // drawBar should call fillRect twice: background + fill bar + expect(graphics.fillRect).toHaveBeenCalled(); + + // The second fillRect call (foreground bar) should have width based on health fraction + const calls = graphics.fillRect.mock.calls; + expect(calls.length).toBeGreaterThanOrEqual(1); + }); + + // ── 2. Color transitions at thresholds ──────────────────────── + test('getColor returns green at 100%, yellow at 50%, red below 25%', () => { + expect(system.getColor(1.0)).toBe(0x00ff00); + expect(system.getColor(0.75)).toBe(0x00ff00); + expect(system.getColor(0.5)).toBe(0xffff00); + expect(system.getColor(0.25)).toBe(0xffff00); + expect(system.getColor(0.24)).toBe(0xff0000); + expect(system.getColor(0.0)).toBe(0xff0000); + }); + + // ── 3. Auto-hide at full HP, show on damage ─────────────────── + test('bar is hidden at full HP and shown when damaged', () => { + const unit = buildMockUnit({ hp: 100, maxHp: 100 }); + system.drawBar(unit); + system.update(unit); + + const graphics = system._bars.get(unit); + expect(graphics).toBeDefined(); + + // At full HP, bar should be hidden + expect(graphics.setVisible).toHaveBeenCalledWith(false); + + // Simulate damage + unit.getData = jest.fn((key) => { + if (key === 'health') return 80; + if (key === 'maxHp') return 100; + return undefined; + }); + + // Reset mock + graphics.setVisible.mockClear(); + system.update(unit); + + // After damage, bar should be visible + expect(graphics.setVisible).toHaveBeenCalledWith(true); + }); + + // ── 4. destroy(unit) cleans up graphics ────────────────────── + test('destroy(unit) destroys the health bar graphics', () => { + const unit = buildMockUnit({ hp: 60, maxHp: 100 }); + system.drawBar(unit); + const graphics = system._bars.get(unit); + + expect(graphics).toBeDefined(); + const destroySpy = graphics.destroy; + + system.destroy(unit); + + expect(destroySpy).toHaveBeenCalled(); + expect(system._bars.has(unit)).toBe(false); + }); + + // ── flash ───────────────────────────────────────────────────── + test('flash sets tint color briefly', () => { + const unit = buildMockUnit({ hp: 60, maxHp: 100 }); + system.drawBar(unit); + const graphics = system._bars.get(unit); + + system.flash(unit, 0xff0000); + + // Flash should temporarily change fill style + expect(graphics.fillStyle).toHaveBeenCalled(); + }); +}); diff --git a/tests/unit/ProductionPanel.test.js b/tests/unit/ProductionPanel.test.js new file mode 100644 index 0000000..f03c49b --- /dev/null +++ b/tests/unit/ProductionPanel.test.js @@ -0,0 +1,309 @@ +/** + * ProductionPanel.test.js — S5.3: Building production queue UI + * + * Tests: + * 1. Constructor creates container fixed to camera at bottom-right + * 2. show() renders building name + state for a production building + * 3. show() renders Add Unit buttons per building type productions array + * 4. show() with non-production building hides Add Unit buttons + * 5. Clicking Add Unit checks canAfford → deduct → addToQueue + * 6. Clicking Add Unit when unaffordable does nothing (visual disabled) + * 7. Queue full disables further Add Unit clicks + * 8. hide() clears panel and nulls selected building + * 9. destroy() cleans up all Phaser objects + * 10. update() refreshes progress bar width for first queue item + */ + +import ProductionPanel from 'Systems/ProductionPanel'; + +// ── Helpers ───────────────────────────────────────────────────────── + +function buildMockScene() { + const objects = []; + const inputOnHandlers = []; + + return { + add: { + container: jest.fn((x, y) => { + const c = { + x, y, + list: [], + add: jest.fn(function (obj) { this.list.push(obj); return this; }), + setPosition: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + setScrollFactor: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + }; + objects.push(c); + return c; + }), + rectangle: jest.fn((x, y, w, h, color) => { + const r = { + x, y, width: w, height: h, fillColor: color, + setOrigin: jest.fn().mockReturnThis(), + setStrokeStyle: jest.fn().mockReturnThis(), + setInteractive: jest.fn().mockReturnThis(), + setFillStyle: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + on: jest.fn().mockReturnThis(), + off: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + }; + objects.push(r); + return r; + }), + text: jest.fn((x, y, text, style) => { + const t = { + x, y, text, style, + setOrigin: jest.fn().mockReturnThis(), + setScrollFactor: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setText: jest.fn(function (v) { this.text = v; return this; }), + setColor: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + }; + objects.push(t); + return t; + }), + }, + cameras: { + main: { width: 1280, height: 720 }, + }, + input: { + on: jest.fn((evt, cb) => inputOnHandlers.push({ evt, cb })), + off: jest.fn(), + _handlers: inputOnHandlers, + _fire: (evt, ...args) => { + inputOnHandlers.filter(h => h.evt === evt).forEach(h => h.cb(...args)); + }, + }, + events: { + emit: jest.fn(), + on: jest.fn(), + off: jest.fn(), + }, + _objects: objects, + }; +} + +function buildMockBSM(overrides = {}) { + const queue = overrides.productionQueue ?? []; + return { + building: overrides.building ?? { x: 100, y: 100 }, + type: overrides.type ?? 'BARRACKS', + playerId: overrides.playerId ?? 'Player', + getState: jest.fn(() => overrides.state ?? 'ACTIVE'), + productionQueue: queue, + addToQueue: jest.fn((unitType, count = 1) => { + for (let i = 0; i < count; i++) { + queue.push({ unitType, startTime: 0 }); + } + }), + cancelQueue: jest.fn(() => { queue.length = 0; }), + productionTime: overrides.productionTime ?? 8000, + }; +} + +function buildMockEconomy(canAfford = true) { + return { + canAfford: jest.fn(() => canAfford), + deduct: jest.fn(() => canAfford), + getResources: jest.fn(() => ({ fuel: 100, ammo: 100 })), + }; +} + +// ── Tests ─────────────────────────────────────────────────────────── + +describe('ProductionPanel', () => { + let scene; + let panel; + let economy; + + beforeEach(() => { + scene = buildMockScene(); + economy = buildMockEconomy(true); + panel = new ProductionPanel(scene, { + playerId: 'Player', + getEconomy: () => economy, + }); + }); + + afterEach(() => { + panel?.destroy?.(); + }); + + // -- 1. Constructor ------------------------------------------------- + test('constructor creates container fixed to camera at bottom-right', () => { + expect(scene.add.container).toHaveBeenCalled(); + expect(panel.container).toBeDefined(); + expect(panel.container.setScrollFactor).toHaveBeenCalledWith(0, 0); + expect(panel.container.setDepth).toHaveBeenCalledWith(120); + expect(panel.container.x).toBeGreaterThan(600); // near right edge + expect(panel.container.y).toBeGreaterThan(500); // near bottom + }); + + test('constructor starts hidden', () => { + expect(panel.container.setVisible).toHaveBeenCalledWith(false); + expect(panel.container.setAlpha).toHaveBeenCalledWith(0); + }); + + // -- 2. show() renders building info ------------------------------ + test('show() renders building name and state', () => { + const bsm = buildMockBSM({ type: 'BARRACKS', state: 'ACTIVE' }); + panel.show(bsm); + + expect(panel.nameText.text).toContain('Barracks'); + expect(panel.stateText.text).toContain('ACTIVE'); + expect(panel.container.setVisible).toHaveBeenCalledWith(true); + expect(panel.container.setAlpha).toHaveBeenCalledWith(1); + }); + + // -- 3. show() renders Add Unit buttons ---------------------------- + test('show() renders Add Unit buttons for production building', () => { + const bsm = buildMockBSM({ type: 'BARRACKS' }); + panel.show(bsm); + + expect(panel.unitButtons).toBeDefined(); + expect(panel.unitButtons.length).toBe(1); // Barracks has 1 production + expect(panel.unitButtons[0].label.text).toContain('Infantry'); + }); + + // -- 4. show() with non-production building hides buttons ---------- + test('show() with COMMAND_CENTER has no unit buttons', () => { + const bsm = buildMockBSM({ type: 'COMMAND_CENTER', productions: [] }); + panel.show(bsm); + + expect(panel.unitButtons.length).toBe(0); + }); + + // -- 5. Clicking Add Unit (affordable) ----------------------------- + test('clicking Add Unit deducts cost and adds to queue', () => { + const bsm = buildMockBSM({ type: 'BARRACKS' }); + panel.show(bsm); + + const btn = panel.unitButtons[0]; + expect(btn).toBeDefined(); + + // Simulate pointerdown on button bg + const pointerdownCall = btn.bg.on.mock.calls.find(c => c[0] === 'pointerdown'); + expect(pointerdownCall).toBeDefined(); + pointerdownCall[1](); + + expect(economy.canAfford).toHaveBeenCalledWith('Player', { ammo: 20 }); + expect(economy.deduct).toHaveBeenCalledWith('Player', { ammo: 20 }); + expect(bsm.addToQueue).toHaveBeenCalledWith('infantry', 1); + }); + + // -- 6. Clicking Add Unit (unaffordable) -------------------------- + test('clicking Add Unit when unaffordable does not deduct or queue', () => { + economy = buildMockEconomy(false); + panel = new ProductionPanel(scene, { + playerId: 'Player', + getEconomy: () => economy, + }); + + const bsm = buildMockBSM({ type: 'BARRACKS' }); + panel.show(bsm); + + const btn = panel.unitButtons[0]; + const pointerdownCall = btn.bg.on.mock.calls.find(c => c[0] === 'pointerdown'); + pointerdownCall[1](); + + expect(economy.canAfford).toHaveBeenCalledWith('Player', { ammo: 20 }); + expect(economy.deduct).not.toHaveBeenCalled(); + expect(bsm.addToQueue).not.toHaveBeenCalled(); + }); + + // -- 7. Queue full disables button -------------------------------- + test('Add Unit disabled when queue is full', () => { + const bsm = buildMockBSM({ + type: 'BARRACKS', + productionQueue: [ + { unitType: 'infantry', startTime: 0 }, + { unitType: 'infantry', startTime: 0 }, + { unitType: 'infantry', startTime: 0 }, + { unitType: 'infantry', startTime: 0 }, + { unitType: 'infantry', startTime: 0 }, + ], + }); + panel.show(bsm); + + const btn = panel.unitButtons[0]; + expect(btn.bg.setAlpha).toHaveBeenCalledWith(0.4); + + const pointerdownCall = btn.bg.on.mock.calls.find(c => c[0] === 'pointerdown'); + pointerdownCall[1](); + + expect(bsm.addToQueue).not.toHaveBeenCalled(); + }); + + // -- 8. hide() ----------------------------------------------------- + test('hide() hides container and clears selection', () => { + const bsm = buildMockBSM({ type: 'BARRACKS' }); + panel.show(bsm); + panel.hide(); + + expect(panel.container.setVisible).toHaveBeenCalledWith(false); + expect(panel.container.setAlpha).toHaveBeenCalledWith(0); + expect(panel.selectedBSM).toBeNull(); + }); + + // -- 9. destroy() -------------------------------------------------- + test('destroy() cleans up container and buttons', () => { + const containerDestroy = panel.container.destroy; + panel.destroy(); + + expect(containerDestroy).toHaveBeenCalled(); + expect(panel.container).toBeNull(); + expect(panel.unitButtons.length).toBe(0); + }); + + // -- 10. update() progress bar ----------------------------------- + test('update() sets progress bar width based on production time', () => { + const bsm = buildMockBSM({ + type: 'BARRACKS', + productionQueue: [{ unitType: 'infantry', startTime: 2000 }], + productionTime: 8000, + }); + panel.show(bsm); + + // At time=6000, 4000ms elapsed out of 8000ms = 50% + panel.update(6000); + + expect(panel.progressBar.setFillStyle).toHaveBeenCalled(); + // width should be about half of max (120 * 0.5 = 60) + const fillCall = panel.progressBar.setFillStyle.mock.calls.find(() => true); + expect(fillCall).toBeDefined(); + }); + + // -- 11. show() with different building clears old buttons ------- + test('show() with new building clears previous unit buttons', () => { + const bsm1 = buildMockBSM({ type: 'BARRACKS' }); + panel.show(bsm1); + expect(panel.unitButtons.length).toBe(1); + + const bsm2 = buildMockBSM({ type: 'VEHICLE_DEPOT' }); + panel.show(bsm2); + expect(panel.unitButtons.length).toBe(1); + expect(panel.unitButtons[0].label.text).toContain('Tank'); + }); + + // -- 12. auto-close on click away -------------------------------- + test('scene pointerdown outside panel hides it', () => { + const bsm = buildMockBSM({ type: 'BARRACKS' }); + panel.show(bsm); + + // Simulate pointerdown on scene (not on panel) + scene.input._fire('pointerdown', { x: 10, y: 10 }); + + expect(panel.container.setVisible).toHaveBeenCalledWith(false); + }); +}); diff --git a/tests/unit/ProjectileSprite.test.js b/tests/unit/ProjectileSprite.test.js new file mode 100644 index 0000000..c455981 --- /dev/null +++ b/tests/unit/ProjectileSprite.test.js @@ -0,0 +1,125 @@ +/** + * ProjectileSprite Unit Tests + */ + +jest.mock('phaser', () => ({ + Physics: { + Arcade: { + DYNAMIC_BODY: 0, + Sprite: class MockArcadeSprite { + constructor(scene, x, y, texture) { + this.scene = scene; + this.x = x; + this.y = y; + this.texture = texture; + this.active = true; + this.visible = true; + this.body = { + velocity: { x: 0, y: 0 }, + allowGravity: false, + setSize: jest.fn(), + setOffset: jest.fn(), + }; + this._data = {}; + this.setData = jest.fn((k, v) => { this._data[k] = v; }); + this.getData = jest.fn((k) => this._data[k] ?? null); + this.setTint = jest.fn(); + this.clearTint = jest.fn(); + this.setRotation = jest.fn(); + this.setDepth = jest.fn(); + this.destroy = jest.fn(); + this.emit = jest.fn(); + } + preUpdate() {} + }, + }, + }, + Math: { + RadToDeg: jest.fn((rad) => { + let deg = rad * (180 / Math.PI); + while (deg < 0) deg += 360; + return deg % 360; + }), + }, +})); + +import ProjectileSprite from '../../src/systems/ProjectileSprite'; + +const makeScene = () => ({ + add: { existing: jest.fn() }, + physics: { + world: { enableBody: jest.fn() }, + velocityFromAngle: jest.fn((angle, speed, vec) => { + const rad = angle * Math.PI / 180; + vec.x = Math.cos(rad) * speed; + vec.y = Math.sin(rad) * speed; + }), + overlap: jest.fn(() => false), + }, + cameras: { + main: { worldView: { x: 0, y: 0, width: 800, height: 600 } }, + }, + events: { emit: jest.fn() }, +}); + +const makeSource = (teamName) => ({ + parentContainer: { name: teamName }, + x: 100, + y: 100, + getData: jest.fn(() => 100), + setData: jest.fn(), +}); + +describe('ProjectileSprite', () => { + it('sets velocity toward target angle', () => { + const scene = makeScene(); + const angle = Math.PI / 4; + const speed = 200; + const p = new ProjectileSprite(scene, 0, 0, 'bullet', angle, speed, 10, makeSource('Good Guys')); + expect(scene.physics.velocityFromAngle).toHaveBeenCalledWith( + expect.closeTo(45, 1), + speed, + p.body.velocity, + ); + expect(p.body.velocity.x).toBeCloseTo(Math.cos(Math.PI / 4) * speed, 1); + expect(p.body.velocity.y).toBeCloseTo(Math.sin(Math.PI / 4) * speed, 1); + }); + + it('destroys itself when off-screen', () => { + const scene = makeScene(); + const p = new ProjectileSprite(scene, -200, 0, 'bullet', 0, 100, 10, makeSource('Good Guys')); + expect(p.destroy).not.toHaveBeenCalled(); + p.preUpdate(0, 16); + expect(p.destroy).toHaveBeenCalled(); + }); + + it('tints blue for player faction and red for enemy faction', () => { + const scene = makeScene(); + const playerP = new ProjectileSprite(scene, 0, 0, 'bullet', 0, 100, 10, makeSource('Good Guys')); + expect(playerP.setTint).toHaveBeenCalledWith(0x0000ff); + const enemyP = new ProjectileSprite(scene, 0, 0, 'bullet', 0, 100, 10, makeSource('Bad Guys')); + expect(enemyP.setTint).toHaveBeenCalledWith(0xff0000); + }); + + it('applies damage and destroys on hit', () => { + const scene = makeScene(); + const source = makeSource('Good Guys'); + const target = { + active: true, + dead: false, + body: {}, + getData: jest.fn((k) => (k === 'health' ? 100 : undefined)), + setData: jest.fn(), + emit: jest.fn(), + handleTakeDamage: jest.fn(), + }; + const p = new ProjectileSprite(scene, 0, 0, 'bullet', 0, 100, 25, source); + p.onHit(target); + expect(target.handleTakeDamage).toHaveBeenCalledWith(25); + expect(p.destroy).toHaveBeenCalled(); + expect(scene.events.emit).toHaveBeenCalledWith( + 'combat:projectileHit', + expect.objectContaining({ attacker: source, target, damage: 25 }), + ); + }); +}); diff --git a/tests/unit/ResourceBar.test.js b/tests/unit/ResourceBar.test.js new file mode 100644 index 0000000..1ac4e06 --- /dev/null +++ b/tests/unit/ResourceBar.test.js @@ -0,0 +1,148 @@ +/** + * ResourceBar.test.js -- S4.2: Resource bar HUD (fuel / ammo / CP) + * + * Tests: + * 1. Constructor sets up a Phaser Text object with HUD layout + * 2. updateFromResources refreshes text with current values + * 3. Listens to economy:updated and auto-refreshes + * 4. Color icons: fuel = orange, ammo = yellow, CP = green + * 5. Position fixed to camera (scrollFactor 0) + */ + +import ResourceBar from 'Systems/ResourceBar'; + +function buildMockScene() { + const texts = []; + return { + add: { + text: jest.fn((x, y, content, style) => { + const t = { + x, y, + text: content, + style, + setText: jest.fn(function (v) { this.text = v; return this; }), + setOrigin: jest.fn().mockReturnThis(), + setScrollFactor: jest.fn().mockReturnThis(), + setDepth: jest.fn().mockReturnThis(), + setVisible: jest.fn().mockReturnThis(), + setAlpha: jest.fn().mockReturnThis(), + destroy: jest.fn(), + active: true, + }; + texts.push(t); + return t; + }), + }, + _textsAdded: texts, + cameras: { main: { width: 1280, height: 720 } }, + events: { + listeners: {}, + on(event, fn) { if (!this.listeners[event]) this.listeners[event] = []; this.listeners[event].push(fn); }, + emit(event, ...args) { + (this.listeners[event] || []).forEach(fn => fn(...args)); + }, + }, + }; +} + +describe('ResourceBar', () => { + let scene; + let bar; + + beforeEach(() => { + scene = buildMockScene(); + bar = new ResourceBar(scene, { playerId: 'Player' }); + }); + + afterEach(() => { + bar?.destroy?.(); + }); + + // -- 1. Constructor creates Text object ------------------------------- + test('constructor creates a Phaser Text HUD element', () => { + expect(scene.add.text).toHaveBeenCalled(); + expect(bar._text).toBeDefined(); + expect(bar._text.active).toBe(true); + }); + + test('HUD text is fixed to camera (scrollFactor 0)', () => { + expect(bar._text.setScrollFactor).toHaveBeenCalledWith(0, 0); + }); + + test('HUD text is at top-left by default', () => { + // Default padding 16, y offset ~20 from top + const x = bar._text.x; + const y = bar._text.y; + expect(x).toBeGreaterThanOrEqual(0); + expect(y).toBeGreaterThanOrEqual(0); + expect(x).toBeLessThan(200); + expect(y).toBeLessThan(100); + }); + + // -- 2. updateFromResources formats text correctly -------------------- + test('updateFromResources shows fuel / ammo / CP', () => { + bar.updateFromResources({ fuel: 80, ammo: 60, capturePoints: 3 }); + expect(bar._text.setText).toHaveBeenCalled(); + const lastText = bar._text.text; + expect(lastText).toContain('80'); + expect(lastText).toContain('60'); + expect(lastText).toContain('3'); + }); + + test('format includes emoji/color icons', () => { + bar.updateFromResources({ fuel: 100, ammo: 100, capturePoints: 0 }); + const text = bar._text.text; + expect(text).toContain('Fuel'); + expect(text).toContain('Ammo'); + expect(text).toContain('CP'); + }); + + // -- 3. Listens to economy:updated ---------------------------------- + test('setEconomySystem wires economy:updated listener', () => { + const economy = { events: new (require('events').EventEmitter)() }; + const spy = jest.spyOn(economy.events, 'on'); + bar.setEconomySystem(economy, 'Player'); + expect(spy).toHaveBeenCalledWith('economy:updated', expect.any(Function)); + }); + + test('economy:updated auto-updates the bar', () => { + const economy = { events: new (require('events').EventEmitter)() }; + bar.setEconomySystem(economy, 'Player'); + const spy = jest.fn(); + bar.updateFromResources = spy; + economy.events.emit('economy:updated', { + playerId: 'Player', + resources: { fuel: 55, ammo: 44, capturePoints: 2 }, + }); + expect(spy).toHaveBeenCalledWith({ fuel: 55, ammo: 44, capturePoints: 2 }); + }); + + test('ignores economy:updated for other players', () => { + const economy = { events: new (require('events').EventEmitter)() }; + bar.setEconomySystem(economy, 'Player'); + const spy = jest.fn(); + bar.updateFromResources = spy; + economy.events.emit('economy:updated', { + playerId: 'Enemy', + resources: { fuel: 999, ammo: 999, capturePoints: 99 }, + }); + expect(spy).not.toHaveBeenCalled(); + }); + + // -- 4. Default starting resources displayed ------------------------ + test('bar text shows default starting resources on creation', () => { + // Default economy values: 100 fuel, 100 ammo, 0 CP + expect(bar._text.setText).toHaveBeenCalled(); + const text = bar._text.text; + expect(text).toMatch(/100/); + expect(text).toMatch(/0/); + }); + + // -- 5. destroy cleans up text -------------------------------------- + test('destroy removes the Phaser Text object', () => { + const destroySpy = bar._text.destroy; + bar.destroy(); + expect(destroySpy).toHaveBeenCalled(); + expect(bar._text).toBeNull(); + }); +}); diff --git a/tests/unit/UnitFactory.test.js b/tests/unit/UnitFactory.test.js.old similarity index 100% rename from tests/unit/UnitFactory.test.js rename to tests/unit/UnitFactory.test.js.old