Pipe Runner
A grid-based pipe routing puzzle game where players route building systems (plumbing, HVAC, electrical, fire suppression) through 2D building cross-sections. Teaches VDC (Virtual Design and Construction) coordination concepts using real MEP (Mechanical, Electrical, Plumbing) terminology. Features crossing penalties, material budgets, routing priority order, and a hint system to create genuine optimization puzzles. 32-level progression across four difficulty tiers.
Game Flow
Menu → Level Selection → Level Intro → Countdown → Playing ⇄ Paused → Results
- Menu: Hero screen with how-to-play instructions and a Play button
- Level Selection: 32 levels grouped by difficulty (Easy/Medium/Hard/Expert). Shows stars, best score, best time
- Level Intro: Shows level title, description, scenario card (building context), grid size, system count, par time. Hard/Expert levels show a warning that all systems are unlocked but out-of-order routing incurs penalties
- Countdown: 3-2-1-GO via shared
Countdowncomponent - Playing: Grid displays with system endpoints. Player selects a system from the palette, then taps/drags to route a path through the grid connecting the two endpoints. Eye toggles control per-system layer visibility. Hint button reveals the optimal path for the active system (once per level, with penalty)
- Results: Full score breakdown with crossings, budget, rework, out-of-order, and hint penalties; efficiency bonus; grade and star rating
Unlock Logic
- Level 1 always unlocked
- Completing any level unlocks the next (linear progression)
Core Mechanics
Crossing Penalties
Pipes can cross other system paths at different elevations (like real VDC coordination), but each crossing incurs a -40 point penalty. This creates optimization puzzles where the challenge is finding routes that minimize or eliminate crossings.
At crossing points, the "under" pipe renders shorter with reduced opacity, and a dark separator dot marks the elevation change visually.
Material Budget
Each system can have a budget (max cells). Going over budget doesn't prevent completion but applies a -15 pts/cell penalty. This represents material cost and teaches students about efficient routing. Easy levels have no budgets; Medium+ levels have increasingly tight budgets.
Routing Priority Order
Systems can have a priority number (1 = first, 2 = second, etc.). Priority behavior varies by difficulty:
- Easy/Medium: Priority is enforced — players must complete higher-priority systems before lower-priority ones unlock. The SystemPalette shows priority badges and lock icons for locked systems.
- Hard/Expert: Priority is recommended — all systems are unlocked from the start, but completing a system before its predecessors incurs a -30 pts/violation out-of-order penalty. Priority badges display in amber with "recommended order" tooltips instead of locks.
This mirrors real VDC coordination: gravity-dependent waste lines route first, then large ducts, then smaller pipes. On harder levels, students experience deadlocks and learn why order matters organically.
Hint System
Players can use a hint once per level. The hint button (lightbulb icon) uses crossing-aware pathfinding (Dijkstra) on the current live grid state to find and auto-place the best path for the active system. This costs -100 points.
- Available once per level per attempt (button disables after use)
- Uses
findSmartPath— a budget-aware Dijkstra algorithm that minimizes crossings, not just path length (see Pathfinding below) - If no valid path exists (other pipes completely block the route), the hint shows a "Path blocked — clear a system first" message and is not consumed
- If the hint completes the last remaining system, the timer freezes immediately and a 1.5-second delay shows the solution before transitioning to results
Pathfinding
Two pathfinding algorithms serve different purposes:
| Algorithm | Function | Used For |
|---|---|---|
| Plain BFS | findOptimalPath | Level validation, efficiency bonus check, budget slack calculation — finds the geometric shortest path on the static grid |
| Dijkstra | findSmartPath | Hint system — finds the score-optimal path considering existing pipes on the live board |
Crossing-aware cost model (findSmartPath):
Each cell traversed costs 1. Each cell occupied by another system's pipe costs 1 + effectiveCrossingCost. The effective cost is budget-aware:
effectiveCrossingCost = min(budgetSlack + floor(crossingPenalty / overBudgetPerCell), 10)
= min(budgetSlack + floor(40 / 15), 10)
= min(budgetSlack + 2, 10)
This models: "I have S cells of budget slack (free detour) plus floor(40/15) = 2 more cells that are still cheaper than a crossing (-30 pts < -40 pts)."
| Budget Slack | Effective Cost | Max Detour to Avoid 1 Crossing |
|---|---|---|
| No budget (Easy) | 10 | 10 cells |
| 6 (generous) | 8 | 8 cells |
| 3 (tight) | 5 | 5 cells |
| 1 (very tight) | 3 | 3 cells |
Building Scenarios
Every level includes a scenario field that describes the building context (e.g., "Hospital wing mechanical room" or "Data center build-out"). Scenarios are displayed on the Level Intro screen and progressively introduce trade terminology:
- CW (Cold Water) — Level 1
- DWV (Drain-Waste-Vent) — Level 2
- SA (Supply Air) — Level 3
- EMT (Electrical Metallic Tubing) — Level 4
- FP (Fire Protection) — Level 5
- RA (Return Air) — Level 7
- MEP (Mechanical, Electrical, Plumbing) — Level 14
- VDC (Virtual Design and Construction) — Level 28
- BIM (Building Information Modeling) — Level 32
System Types
| Type | Display Name | Color | Symbol | Description |
|---|---|---|---|---|
| plumbing-supply | Copper Supply (CW) | Blue | CW | Cold water supply piping |
| plumbing-waste | PVC Waste (DWV) | Green | DWV | Drain/waste/vent piping |
| hvac-supply | Supply Duct (SA) | Red | SA | Supply air ductwork |
| hvac-return | Return Duct (RA) | Cyan | RA | Return air ductwork |
| electrical | EMT Conduit | Yellow | E | Electrical metallic tubing |
| fire-suppression | Sprinkler Main (FP) | Crimson | FP | Fire protection piping |
Grid Mechanics
Cell Types
- Empty: Available for routing
- Wall: Impassable solid obstacle (hatched pattern)
- Structural: Labeled obstacle (W-Flange, Bar Joist, CMU Wall, Column)
- Endpoint: System connection point (start or end of a system route)
Interaction
Supports both tap and drag:
- Tap: Click/tap a system endpoint to start routing, then tap adjacent cells to extend the path
- Drag: Press and drag across cells to paint a path in one motion
- Retract: Tap the last cell in a path to remove it; drag backward to undo
- Clear: Per-system clear button in the SystemPalette (counts as a reroute)
Path Validation
Each cell placement is validated:
- Must be adjacent (cardinal) to the previous path cell
- Cannot be a wall or structural element
- Endpoints only accept their own system
- A pipe cannot cross itself (self-overlap prevented)
- Crossing another system's path is allowed but penalized in scoring
Layer Visibility
Show/hide individual system layers using eye toggle buttons integrated into the SystemPalette. Helps plan routes by isolating visual layers — mirrors real BIM coordination workflows.
Difficulty Scaling
| Setting | Easy | Medium | Hard | Expert |
|---|---|---|---|---|
| Grid size | 6×6 | 8×8 | 10×8 | 12×10 |
| Systems | 1-2 | 2-3 | 3-4 | 4-5 |
| Par time (sec) | 60 | 120 | 180 | 300 |
| Priority | No | Enforced (locked) | Recommended (penalty) | Recommended (penalty) |
| Material budget | No | Generous (+6 slack) | Tight (+4 slack) | Very tight (+1-3 slack) |
| Out-of-order penalty | N/A | N/A | -30 pts/violation | -30 pts/violation |
| Features | Open space | Corridors, priority | Pre-placed obstacles | All system types |
Scoring
Config (single source of truth)
All values are centralized in SCORING_CONFIG in config.ts:
SCORING_CONFIG = {
maxScore: 1000,
timePenaltyAtPar: 120,
crossingPenalty: 40,
reworkPenalty: 20,
overBudgetPenaltyPerCell: 15,
excessPipePenaltyPerCell: 10,
outOfOrderPenalty: 30,
hintPenalty: 200,
efficiencyBonus: 50,
}
Formula
baseScore = max(0, 1000 - (elapsedSeconds / parTime) × 120)
crossingPenalty = crossingCount × 40
reworkPenalty = rerouteCount × 20
budgetPenalty = totalCellsOverBudget × 15 (systems with budgets)
excessPipePenalty = totalCellsBeyondOptimal × 10 (systems without budgets)
outOfOrderPenalty = outOfOrderCount × 30 (Hard/Expert only)
hintPenalty = hintUsed ? 200 : 0 (once per level)
efficiencyBonus = allPathsOptimal ? 50 : 0
finalScore = max(0, baseScore - crossingPenalty - reworkPenalty - budgetPenalty
- excessPipePenalty - outOfOrderPenalty - hintPenalty + efficiencyBonus)
Time penalty scales with par time — at par the cost is 120pts regardless of difficulty. Excess pipe counts cells beyond optimal+2 for systems that have no material budget. A path is "optimal" if its length is within 2 cells of the BFS shortest path.
Stars (difficulty-scaled)
| Stars | Easy | Medium | Hard | Expert |
|---|---|---|---|---|
| 3 | ≤par, 0 reroutes, 0 crossings, optimal | ≤par, 0 reroutes, ≤1 crossing, optimal | ≤par, 0 reroutes, ≤2 crossings, optimal | ≤par, ≤1 reroute, ≤3 crossings, optimal |
| 2 | ≤1.5×par, ≤1 crossing | ≤1.5×par, ≤2 crossings | ≤1.5×par, ≤4 crossings | ≤1.5×par, ≤5 crossings |
| 1 | Completed | Completed | Completed | Completed |
Accuracy (difficulty-scaled)
accuracy = max(0, 100 - rerouteCount × reworkWeight - crossingCount × crossingWeight)
| Difficulty | Rework weight | Crossing weight |
|---|---|---|
| Easy | 15 | 7 |
| Medium | 13 | 5 |
| Hard | 11 | 5 |
| Expert | 10 | 3 |
Grade Thresholds (difficulty-scaled)
Both minimum score AND minimum accuracy must be met:
| Grade | Easy | Medium | Hard | Expert |
|---|---|---|---|---|
| S | 900 pts / 95% acc | 875 pts / 90% acc | 825 pts / 85% acc | 750 pts / 75% acc |
| A | 750 pts / 80% acc | 725 pts / 80% acc | 675 pts / 70% acc | 600 pts / 60% acc |
| B | 550 pts / 70% acc | 550 pts / 60% acc | 500 pts / 50% acc | 425 pts / 40% acc |
| C | 350 pts / 50% acc | 325 pts / 40% acc | 300 pts / 30% acc | 250 pts / 20% acc |
| D | 0 pts / 0% acc | 0 pts / 0% acc | 0 pts / 0% acc | 0 pts / 0% acc |
In-Game HUD
During gameplay the HUD displays:
- Live score: Updates every tick based on current time + all penalties
- Crossing count: Color-coded badge with point deduction shown (0=green, 1-2=yellow, 3+=red)
- Budget status: Cells used vs total budget with over-budget deduction shown (green/yellow/red)
- Rework count: Badge with point deduction shown (appears when > 0)
- Out-of-order count: Amber badge with point deduction shown (Hard/Expert, appears when > 0)
- Hint used: Amber badge with -100 deduction shown (appears after use)
- Hint button: Lightbulb icon, amber colored, disabled after use or when no active system
- Scoring legend: Expandable "?" panel showing all penalty and bonus values, including out-of-order and hint costs
- System palette: System buttons with eye toggles, per-system clear, priority badges (locked on Easy/Medium, recommended on Hard/Expert), budget tracker
Platform Score
Uses the cumulative score model. After each level, the sum of best scores across all completed levels is posted. TNW_COMPLETE fires once on the player's first-ever level completion.
Level Design Guidelines
When adding or editing levels:
- Easy levels (1-10): No priority, no budget. 1-2 systems max. Levels 1-5 introduce each system type solo (CW, DWV, SA, E, FP). Levels 6-7 introduce multi-system coordination with zero crossings (including RA intro at level 7). Levels 8-10 introduce crossings with ascending density (2→3→4).
- Medium levels (11-20): Add priority (waste/largest first). Set budget = optimal + ~5. Priority is enforced (locked). 2-3 systems. L11 is 2-system bridge from Easy; L12-20 are 3-system with crossings ascending 4→5→6→6→7→7→7→8→8.
- Hard levels (21-27): Priority is recommended (not locked). Budget = optimal + ~3. All systems unlocked — out-of-order routing penalized. L21-22 are 3-system transition; L23-27 are 4-system. Scenario references real building contexts.
- Expert levels (28-32): Full priority chain (4-5 systems). Budget = optimal + 1-3. All unlocked with out-of-order penalties. Crossings ascend 16→19→20→22→28. BIM Coordination Final is level 32 (true capstone).
Always verify (tests in tests/games/pipe-runner/):
levels.test.ts: Level solvability, budget slack, priority deadlocks, scoring mathgrid.test.ts:findOptimalPath(BFS),findSmartPath(Dijkstra crossing avoidance),isCellAvailable,isPathComplete
Solver Analysis (auto-generated via solveLevel)
Easy (6×6, no budget/priority)
| Lvl | Title | Systems | Opt. Len | Crossings | Phase |
|---|---|---|---|---|---|
| 1 | First Pipe Run | 1 (CW) | 11 | 0 | Solo intro |
| 2 | Laundry Drain Line | 1 (DWV) | 9 | 0 | Solo intro |
| 3 | Classroom Ductwork | 1 (SA) | 8 | 0 | Solo intro |
| 4 | Garage Wiring Run | 1 (E) | 11 | 0 | Solo intro |
| 5 | Sprinkler Loop | 1 (FP) | 8 | 0 | Solo intro |
| 6 | Dual Line Rough-In | 2 (CW+DWV) | 14 | 0 | Multi, no cross |
| 7 | Server Room Returns | 2 (RA+CW) | 14 | 0 | Multi, no cross (RA intro) |
| 8 | Utility Closet Connect | 2 (CW+SA) | 20 | 2 | Crossings |
| 9 | Break Room HVAC | 2 (RA+E) | 22 | 3 | Crossings |
| 10 | Small Office Fit-Out | 2 (SA+E) | 22 | 4 | Crossings |
Medium (8×8, enforced priority, generous budget)
| Lvl | Title | Sys | Crossings | Min Slack | Opt. Len |
|---|---|---|---|---|---|
| 11 | Bathroom Stack Coordination | 2 | 4 | 6 | 27 |
| 12 | Apartment Riser | 3 | 4 | 4 | 40 |
| 13 | Kitchen Mechanical Chase | 3 | 5 | 6 | 38 |
| 14 | First Floor MEP Rough-In | 3 | 6 | 6 | 38 |
| 15 | Hotel Guest Room | 3 | 6 | 6 | 33 |
| 16 | Retail Sprinkler Install | 3 | 7 | 5 | 38 |
| 17 | Medical Clinic | 3 | 7 | 4 | 38 |
| 18 | Warehouse Mezzanine | 3 | 7 | 4 | 41 |
| 19 | Restaurant Exhaust | 3 | 8 | 5 | 38 |
| 20 | Library Reading Room | 3 | 8 | 3 | 37 |
Hard (10×8, recommended priority, tight budget)
| Lvl | Title | Sys | Crossings | Min Slack | Opt. Len |
|---|---|---|---|---|---|
| 21 | Ceiling Plenum Stack-Up | 3 | 8 | 4 | 44 |
| 22 | Corridor Crossover | 3 | 8 | 2 | 45 |
| 23 | Mechanical Room Coordination | 4 | 11 | 4 | 52 |
| 24 | School Gymnasium | 4 | 10 | 4 | 53 |
| 25 | Hospital Corridor | 4 | 14 | 4 | 56 |
| 26 | Clean Room Prep | 4 | 14 | 3 | 55 |
| 27 | Parking Garage MEP | 4 | 12 | 2 | 59 |
Expert (12×10, recommended priority, very tight budget)
| Lvl | Title | Sys | Crossings | Min Slack | Opt. Len |
|---|---|---|---|---|---|
| 28 | Full Floor Coordination | 5 | 16 | 1 | 76 |
| 29 | Riser Shaft Coordination | 5 | 19 | 3 | 83 |
| 30 | Operating Suite | 5 | 20 | 2 | 79 |
| 31 | Industrial Process Plant | 5 | 22 | 3 | 86 |
| 32 | BIM Coordination Final | 5 | 28 | 1 | 86 |
Technical Architecture
Key Files
| Area | File | Purpose |
|---|---|---|
| Types | games/pipe-runner/types.ts | GamePhase, CellType, SystemType, GridCell, SystemDef (with budget and priority), LevelConfig (with scenario), LevelProgress |
| Config | games/pipe-runner/config.ts | SCORING_CONFIG, calculateFinalScore(), calculateStars(), calculateAccuracy(), calculateOverBudget(), isPriorityEnforced(), per-difficulty grade thresholds/star limits/accuracy weights |
| Store | games/pipe-runner/store.ts | Zustand store: grid state, routing paths, system selection, layer visibility, timer, crossing/rework/outOfOrder counts, hintUsed, priority enforcement (difficulty-aware), hint action with crossing-aware pathfinding |
| Grid | games/pipe-runner/grid.ts | Pure grid utility functions: createGrid, isCellAvailable, isAdjacent, getPathConnections, isPathComplete, calculateOptimalLength, findOptimalPath (BFS), findSmartPath (Dijkstra), solveLevel |
| Systems | games/pipe-runner/content/systems.ts | System type definitions with colors, symbols, display names |
| Levels | games/pipe-runner/content/levels.ts | 32 level definitions with grids, walls, structural elements, system endpoints, budgets, priorities, scenario text |
| Hook | games/pipe-runner/hooks/use-game-actions.ts | updateLevelProgress(), getProgress(), getCumulativeScore() |
| Audio | games/pipe-runner/hooks/use-game-audio.ts | Phase-aware SFX management (synthwave BGM) |
| Barrel | games/pipe-runner/index.ts | Public exports for all config, store selectors, and types |
Components
| Component | Purpose |
|---|---|
game-grid.tsx | Main grid container with pointer event handling (tap + drag routing), multi-layer pipe rendering |
grid-cell.tsx | Individual cell rendering: wall, structural, endpoint (with active pulse), pipe segments, crossover visuals |
system-palette.tsx | System selection bar with eye toggles, priority badges (locked or recommended), per-system clear, completion status, budget tracker |
hud.tsx | Live score, timer, hint button, crossing/rework/out-of-order/hint badges with deductions, budget indicator, scoring legend, system palette, completion delay for hint |
level-list.tsx | 32-level grid with difficulty grouping, progress stats, cumulative score |
how-to-play.tsx | Instructions for the menu screen (references SCORING_CONFIG values) |
Uses shared components: LevelCard, LevelIntro, Countdown, PauseMenu, ResultsScreen, GameError, LoadingScreen, GameShell, GameHeader.
Routing
/pipe-runner/ → Menu (hero + Play button)
/pipe-runner/levels/ → Level selection (32 levels)
/pipe-runner/play/[level]/ → Gameplay (level = 1-32)
Persistence
Storage key: pipe-runner-state
Save data:
{
levelProgress: {
level_1: { bestTimeMs, stars, unlocked, completedAt, attempts, bestScore, bestAccuracy },
level_2: { ... },
...
},
lastPlayedLevel
}