# Card System Architecture Design

## Quick Start: How to Add a New Card

**For most cards, you only need to do this:**

1. Open the appropriate file:
   - Weapons → `src/game/cards/weapons.js`
   - Gear → `src/game/cards/gear.js`
   - Consumables → `src/game/cards/consumables.js`

2. Add your card definition:
```javascript
laserRifle: {
  id: 'laserRifle',
  name: 'Laser Rifle',
  type: 'weapon',
  targetMode: 'single',  // 'single', 'all', 'self', 'none'
  chargeCost: 2,
  charges: 0,
  maxCharges: 4,
  damage: 3,
  description: 'Powerful laser weapon. Deals 3 damage.',
  
  effect: (G, targetIndex) => {
    dealDamage(G, targetIndex, 3);
  },
}
```

3. **That's it!** Your card will automatically:
   - Show up in the game
   - Have a working UI
   - Handle all validation and targeting
   - Integrate with the combat system

4. Add it to the starting equipment in `CombatGame.js` setup if needed:
```javascript
equipment: {
  charger: createEquipment('charger'),
  blaster: createEquipment('blaster'),
  laserRifle: createEquipment('laserRifle'),  // Add here
}
```

**Need something custom?** See the detailed sections below for advanced options.

---

## Overview

This document describes the architecture for the card system in the combat game. The design prioritizes:
- **Ease of adding new cards** - 95% of cards should require minimal code
- **Flexibility for unique mechanics** - Special cards can have custom logic
- **Clear organization** - Related cards grouped together for balancing
- **Separation of concerns** - Game logic separate from UI rendering

## Design Philosophy: Hybrid Approach

We use a **mostly function-based approach with strategic data fields**:

### ⚠️ Critical Constraint: JSON Serialization

**boardgame.io requires ALL game state to be JSON-serializable.**

✅ **Allowed in G (game state):**
- Objects: `{}`
- Arrays: `[]`
- Primitives: `string`, `number`, `boolean`, `null`

❌ **NOT allowed in G:**
- `Set`, `Map` - Use arrays/objects instead
- `Date` - Use timestamps (numbers)
- Functions - Keep in card definitions, never in G
- `undefined` - Use `null` instead

**Example:**
```javascript
// ❌ WRONG - Set is not serializable
G.usedThisFight = new Set();
G.usedThisFight.add('speedBooster');

// ✅ CORRECT - Array is serializable
G.usedThisFight = [];
G.usedThisFight.push('speedBooster');
```

### Data Fields
Use data fields for common patterns that are tedious to code repeatedly:
- `targetMode` - How the card selects targets ('single', 'all', 'self', 'none')
- `chargeCost` - How much charge is consumed on use (default: 1)
- `type` - Card category ('weapon', 'gear', 'consumable')
- `subtype` - Additional classification as needed

### Functions
Use functions for actual behavior (maximum flexibility):
- `canUse(G, equipment)` - Optional custom validation logic
- `effect(G, ...targets)` - What the card does when used
- `onRecharge(equipment)` - Optional custom recharge behavior (e.g., Blaster fully charges)

### Why Hybrid?
- **Simple cards** need almost no code - data fields + one-line effect function
- **Complex cards** have full JavaScript power available
- **No over-engineering** - Only abstract patterns we've actually encountered
- **Room to grow** - Easy to add more data fields or helpers as patterns emerge

---

## File Organization

### Directory Structure
```
src/game/
  cards/
    index.js          # Re-exports everything, main entry point
    weapons.js        # ~15 weapon card definitions
    gear.js           # ~8 gear card definitions  
    consumables.js    # ~10 consumable card definitions
    robots.js         # ~12 robot card definitions
  helpers/
    combat.js         # dealDamage, dealAoEDamage, rechargeEquipment, etc.
    validation.js     # canUseEquipment, validateTarget, etc.
  CombatGame.js       # Game logic, moves, phases
  constants.js        # Game constants
```

### Why Group by Type?

**Benefits:**
- **Balancing** - See all weapons side-by-side when tuning damage values
- **Pattern recognition** - Similar cards near each other for easy copy-paste-modify
- **Import simplicity** - `import { WEAPONS, GEAR } from './cards'` instead of 50 imports
- **Manageable size** - 15 weapons in one file (~300-400 lines) is readable

**File size guidelines:**
- Each card definition: ~15-30 lines
- Aim for 10-20 cards per file
- If a file exceeds 600 lines, consider splitting by sub-category

### Example: weapons.js
```javascript
import { dealDamage, dealAoEDamage } from '../helpers/combat';

export const WEAPONS = {
  blaster: {
    id: 'blaster',
    name: 'Blaster',
    type: 'weapon',
    subtype: 'primary',
    targetMode: 'single',
    chargeCost: 1,
    charges: 2,
    maxCharges: 2,
    damage: 1,
    description: 'Deals 1 dmg. Always fully charges.',
    
    effect: (G, targetIndex) => {
      dealDamage(G, targetIndex, G.player.equipment.blaster.damage);
    },
    
    onRecharge: (equipment) => {
      equipment.charges = equipment.maxCharges; // Always fully charges
    },
  },
  
  wideBeam: {
    id: 'wideBeam',
    name: 'Wide Beam',
    type: 'weapon',
    targetMode: 'all',
    chargeCost: 3,
    charges: 0,
    maxCharges: 3,
    damage: 1,
    description: 'Hits all robots for 1 dmg. Costs 3 charge.',
    
    effect: (G) => {
      G.robots.forEach((robot, idx) => {
        dealDamage(G, idx, 1);
      });
    },
  },
  
  // ... more weapons
};
```

### Example: index.js (cards aggregator)
```javascript
import { WEAPONS } from './weapons';
import { GEAR } from './gear';
import { CONSUMABLES } from './consumables';
import { ROBOTS } from './robots';

// Combine all equipment types
export const EQUIPMENT_CARDS = {
  ...WEAPONS,
  ...GEAR,
  ...CONSUMABLES,
};

export const ROBOT_CARDS = ROBOTS;

// Helper functions for creating instances
export function createEquipment(equipmentId) {
  const template = EQUIPMENT_CARDS[equipmentId];
  if (!template) return null;
  
  // Only copy serializable properties (no functions!)
  // boardgame.io requires G to be JSON-serializable
  return {
    id: equipmentId,
    name: template.name,
    type: template.type,
    subtype: template.subtype,
    charges: template.charges,
    maxCharges: template.maxCharges,
    damage: template.damage,
    description: template.description,
    upgradeLevel: 0,
    // Don't copy functions or complex objects
  };
}

export function getEquipmentDefinition(equipmentId) {
  return EQUIPMENT_CARDS[equipmentId];
}

export function createRobot(robotId) {
  const template = ROBOT_CARDS[robotId];
  if (!template) return null;
  
  return {
    ...template,
    id: `${robotId}_${Date.now()}_${Math.random()}`, // Unique instance ID
  };
}
```

---

## Card Definition Schema

### Common Fields (All Cards)

```javascript
{
  id: 'blaster',              // Unique identifier
  name: 'Blaster',            // Display name
  type: 'weapon' | 'gear' | 'consumable',
  description: 'Text shown to player',
}
```

### Weapon-Specific Fields

```javascript
{
  subtype: 'primary' | 'secondary',  // Optional classification
  targetMode: 'single' | 'all' | 'self' | 'none',
  chargeCost: 1,              // Charge consumed on use (default: 1)
  charges: 2,                 // Starting charges
  maxCharges: 2,              // Maximum charges
  damage: 1,                  // Base damage value
  
  // Optional: Custom validation
  canUse: (G, equipment) => {
    return equipment.charges >= equipment.chargeCost;
  },
  
  // Required: What happens when used
  effect: (G, targetIndex) => {
    // Card behavior goes here
  },
  
  // Optional: Custom recharge behavior
  onRecharge: (equipment) => {
    equipment.charges = Math.min(equipment.charges + 1, equipment.maxCharges);
  },
}
```

### Gear-Specific Fields

```javascript
{
  subtype: 'passive' | 'active',
  
  // For active gear (like Charger)
  effect: (G) => {
    // What happens when activated
  },
  
  // For passive gear
  // Behavior is often implemented via hooks in game logic
  // or by checking G.player.equipment in move functions
}
```

### Consumable-Specific Fields

```javascript
{
  targetMode: 'self' | 'single' | 'all',
  limitPerFight: 1,           // Optional usage limit
  
  canUse: (G, equipment) => {
    // Check if already used this fight
    return !G.usedThisFight?.has(equipment.id);
  },
  
  effect: (G) => {
    // One-time powerful effect
    // Mark as used if limited
    G.usedThisFight = G.usedThisFight || new Set();
    G.usedThisFight.add(equipment.id);
  },
}
```

---

## Helper Functions

Helper functions encapsulate common operations, making card effects cleaner and more consistent.

### combat.js - Damage and Combat Helpers

```javascript
// src/game/helpers/combat.js

/**
 * Deal damage to a single robot
 * Later can add: shields, armor, damage type checks
 */
export function dealDamage(G, robotIndex, damage, damageType = 'physical') {
  if (robotIndex < 0 || robotIndex >= G.robots.length) return;
  
  // Future: Check for shields, armor, etc.
  G.robots[robotIndex].hp -= damage;
}

/**
 * Deal damage to all robots
 */
export function dealAoEDamage(G, damage, damageType = 'physical') {
  G.robots.forEach((robot, idx) => {
    dealDamage(G, idx, damage, damageType);
  });
}

/**
 * Recharge equipment by specified amount
 */
export function rechargeEquipment(equipment, amount = 1) {
  if (equipment.charges !== undefined && equipment.maxCharges !== undefined) {
    equipment.charges = Math.min(equipment.charges + amount, equipment.maxCharges);
  }
}

/**
 * Remove dead robots from combat
 */
export function removeDeadRobots(G) {
  G.robots = G.robots.filter(robot => robot.hp > 0);
}
```

### validation.js - Usage Validation Helpers

```javascript
// src/game/helpers/validation.js
import { getEquipmentDefinition } from '../cards';

/**
 * Check if equipment can be used
 */
export function canUseEquipment(G, equipment) {
  const def = getEquipmentDefinition(equipment.id);
  
  // If card has custom validation, use it
  if (def?.canUse) {
    return def.canUse(G, equipment);
  }
  
  // Default validation: check charges
  const chargeCost = def?.chargeCost || 1;
  return equipment.charges >= chargeCost;
}

/**
 * Validate target based on targetMode
 */
export function validateTarget(G, def, targetIndex) {
  if (def.targetMode === 'single') {
    if (targetIndex === undefined) return { valid: false, message: 'Select a target!' };
    if (targetIndex < 0 || targetIndex >= G.robots.length) {
      return { valid: false, message: 'Invalid target!' };
    }
  }
  
  if (def.targetMode === 'all' || def.targetMode === 'self') {
    if (G.robots.length === 0) {
      return { valid: false, message: 'No targets available!' };
    }
  }
  
  return { valid: true };
}
```

---

## UI Component Architecture

### Component Structure

```
src/components/
  CombatBoard.jsx              # Main board, orchestration
  equipment/
    EquipmentCard.jsx          # Router: picks right component or custom render
    WeaponCard.jsx             # Default weapon display
    GearCard.jsx               # Default gear display  
    ConsumableCard.jsx         # Default consumable display
  robots/
    RobotCard.jsx              # Robot display (already exists)
```

### Design Pattern: Generic with Opt-In Customization

**95% of cards**: Use type-based default components (WeaponCard, GearCard, etc.)

**5% of cards**: Provide custom render function in card definition

### Card Definition - UI Customization (Optional)

**IMPORTANT:** Keep game layer pure - no JSX in card definitions!

```javascript
// Most cards don't need this - they use defaults
blaster: {
  // ... game logic fields only
}

// Cards needing custom UI: hint at the component name
speedBooster: {
  id: 'speedBooster',
  name: 'Speed Booster',
  type: 'consumable',
  targetMode: 'self',
  description: 'For this combat, you take an extra action on every turn.',
  
  canUse: (G, equipment) => {
    return !G.usedThisFight?.has('speedBooster');
  },
  
  effect: (G) => {
    G.actionsPerTurn += 1;
    G.actionsRemaining += 1;
  },
  
  // String hint to UI layer - keeps game layer pure JS
  customUI: 'SpeedBoosterCard',
}
```

Then create the actual component in UI layer:
```javascript
// src/components/equipment/SpeedBoosterCard.jsx
export default function SpeedBoosterCard({ equipment, onUse, disabled, G }) {
  const isActive = G.usedThisFight?.has('speedBooster');
  
  return (
    <div className="equipment-card consumable speed-booster">
      <div className="equipment-name">{equipment.name}</div>
      <div className="equipment-desc">{equipment.description}</div>
      {isActive && (
        <div className="active-indicator">✓ ACTIVE THIS COMBAT</div>
      )}
      <button onClick={onUse} disabled={disabled || isActive}>
        {isActive ? 'Used' : 'Use'}
      </button>
    </div>
  );
}
```

Update router in `EquipmentCard.jsx` to check for custom components:
```javascript
if (def?.customUI === 'SpeedBoosterCard') {
  return <SpeedBoosterCard equipment={equipment} {...handlers} G={G} />;
}
```

### EquipmentCard.jsx - Router Component

```javascript
// src/components/equipment/EquipmentCard.jsx
import WeaponCard from './WeaponCard';
import GearCard from './GearCard';
import ConsumableCard from './ConsumableCard';
import { getEquipmentDefinition } from '../../game/cards';

/**
 * Routes to appropriate component based on type
 * or uses custom render if provided
 */
export default function EquipmentCard({ 
  equipmentId, 
  equipment, 
  G,
  handlers, // { onSelect, onUse, disabled }
  isSelected 
}) {
  const def = getEquipmentDefinition(equipmentId);
  
  // If card has custom render function, use it
  if (def?.ui?.render) {
    return def.ui.render(equipment, handlers, G);
  }
  
  // Otherwise, use type-based default components
  switch (equipment.type) {
    case 'weapon':
      return <WeaponCard equipment={equipment} {...handlers} isSelected={isSelected} />;
    case 'gear':
      return <GearCard equipment={equipment} {...handlers} />;
    case 'consumable':
      return <ConsumableCard equipment={equipment} {...handlers} />;
    default:
      return <div>Unknown equipment type</div>;
  }
}
```

### WeaponCard.jsx - Default Weapon Display

```javascript
// src/components/equipment/WeaponCard.jsx

export default function WeaponCard({ equipment, onSelect, disabled, isSelected }) {
  const canUse = equipment.charges > 0 && !disabled;
  
  return (
    <div 
      className={`equipment-card weapon ${isSelected ? 'selected' : ''} ${!canUse ? 'depleted' : ''}`}
      onClick={() => canUse && onSelect()}
    >
      <div className="equipment-name">{equipment.name}</div>
      <div className="equipment-charges">
        [{equipment.charges}/{equipment.maxCharges}]
      </div>
      <div className="equipment-desc">{equipment.description}</div>
      {canUse && equipment.damage && (
        <div className="equipment-stats">DMG: {equipment.damage}</div>
      )}
    </div>
  );
}
```

### Usage in CombatBoard.jsx

```javascript
// BEFORE: Hard-coded rendering for specific cards
{G.player.equipment.blaster && (
  <div className={`equipment-card weapon ...`}>
    <div className="equipment-name">{G.player.equipment.blaster.name}</div>
    // ... 20 lines of JSX
  </div>
)}

// AFTER: Generic rendering - works for all cards
<div className="equipment-list">
  {Object.entries(G.player.equipment).map(([id, equipment]) => (
    <EquipmentCard
      key={id}
      equipmentId={id}
      equipment={equipment}
      G={G}
      handlers={{
        onSelect: () => setSelectedWeapon(id),
        onUse: () => moves.useEquipment(id),
        disabled: !canAct
      }}
      isSelected={selectedWeapon === id}
    />
  ))}
</div>
```

---

## Game Logic Integration

### Refactored useEquipment Move

```javascript
// In CombatGame.js
useEquipment: ({ G, effects, events }, equipmentId, targetIndex) => {
  const equipment = G.player.equipment[equipmentId];
  const def = getEquipmentDefinition(equipmentId);
  
  if (!equipment || !def) {
    effects.showMessage('Equipment not found!');
    return;
  }
  
  // Validate can use (custom or default logic)
  if (!canUseEquipment(G, equipment)) {
    effects.showMessage(`Can't use ${equipment.name}!`);
    return;
  }
  
  // Validate target based on targetMode
  const targetValidation = validateTarget(G, def, targetIndex);
  if (!targetValidation.valid) {
    effects.showMessage(targetValidation.message);
    return;
  }
  
  // Consume charges
  const chargeCost = def.chargeCost || 1;
  equipment.charges -= chargeCost;
  
  // Store robot info for effects
  let robotId, hpBefore, hpAfter, damageDealt;
  if (def.targetMode === 'single' && targetIndex !== undefined) {
    const targetRobot = G.robots[targetIndex];
    robotId = targetRobot.id;
    hpBefore = targetRobot.hp;
  }
  
  // Execute card effect
  def.effect(G, targetIndex);
  
  // Emit visual effects if attacking
  if (def.targetMode === 'single' && targetIndex !== undefined) {
    hpAfter = G.robots[targetIndex]?.hp || 0;
    damageDealt = hpBefore - hpAfter;
    const isDead = hpAfter <= 0;
    effects.robotHit({ robotId, damage: damageDealt, isDead });
  }
  
  // Remove dead robots
  removeDeadRobots(G);
  
  // Check victory
  if (G.robots.length === 0) {
    G.lastMessage = 'Victory! All robots defeated!';
    events.setPhase('loot');
    return;
  }
  
  // Consume action and continue combat flow
  consumeAction(G, events, effects);
}
```

---

## Card Examples

### Simple Weapon (Minimal Code)

```javascript
laserPistol: {
  id: 'laserPistol',
  name: 'Laser Pistol',
  type: 'weapon',
  targetMode: 'single',
  chargeCost: 1,
  charges: 2,
  maxCharges: 2,
  damage: 2,
  description: 'Deals 2 damage.',
  
  effect: (G, targetIndex) => {
    dealDamage(G, targetIndex, 2);
  },
}
// That's it! Uses all defaults, auto-recharges normally
```

### AoE Weapon

```javascript
wideBeam: {
  id: 'wideBeam',
  name: 'Wide Beam',
  type: 'weapon',
  targetMode: 'all',      // UI knows not to ask for target
  chargeCost: 3,
  charges: 0,
  maxCharges: 3,
  damage: 1,
  description: 'Hits all robots for 1 damage. Costs 3 charge.',
  
  effect: (G) => {
    dealAoEDamage(G, 1);
  },
}
```

### Complex Consumable

```javascript
speedBooster: {
  id: 'speedBooster',
  name: 'Speed Booster',
  type: 'consumable',
  targetMode: 'self',
  description: 'For this combat, you take an extra action on every turn.',
  
  canUse: (G, equipment) => {
    return !G.usedThisFight?.has('speedBooster');
  },
  
  effect: (G) => {
    // Modify actions per turn
    G.actionsPerTurn += 1;
    G.actionsRemaining += 1;
    
    // Mark as used this fight
    G.usedThisFight = G.usedThisFight || new Set();
    G.usedThisFight.add('speedBooster');
  },
  
  // Custom UI to show active state
  ui: {
    render: (equipment, handlers, G) => (
      <div className="equipment-card consumable speed-booster">
        <div className="equipment-name">{equipment.name}</div>
        <div className="equipment-desc">{equipment.description}</div>
        {G.usedThisFight?.has('speedBooster') && (
          <div className="active-indicator">ACTIVE</div>
        )}
        <button onClick={handlers.onUse} disabled={handlers.disabled}>
          Use
        </button>
      </div>
    )
  }
}
```

### Active Gear

```javascript
charger: {
  id: 'charger',
  name: 'Charger',
  type: 'gear',
  subtype: 'active',
  targetMode: 'self',
  description: 'Recharge all equipment by +1.',
  cannotScrap: true,
  
  effect: (G) => {
    Object.keys(G.player.equipment).forEach(key => {
      const item = G.player.equipment[key];
      rechargeEquipment(item, 1);
      
      // Check for custom recharge behavior
      const itemDef = getEquipmentDefinition(key);
      if (itemDef?.onRecharge) {
        itemDef.onRecharge(item);
      }
    });
  },
}
```

---

## Development Workflow

### Adding a New Simple Card

1. Open the appropriate file (`weapons.js`, `gear.js`, or `consumables.js`)
2. Add card definition with required fields
3. Write one-line `effect` function (usually calling a helper)
4. **Done!** - Card works in-game with no UI code needed

### Adding a Complex Card

1. Define card in appropriate file
2. Write custom `canUse` function if needed
3. Write custom `effect` function with full flexibility
4. If needed, add custom `ui.render` function
5. Test in-game

### Discovering New Helpers

1. Implement 3-5 new cards
2. Notice repeated code patterns
3. Extract to helper function
4. Refactor existing cards to use helper

---

## Benefits of This Architecture

### For Simple Cards (80% case)
- Minimal code: ~10 lines per card
- No UI code needed
- Fast to implement
- Easy to balance (see all similar cards together)

### For Complex Cards (20% case)
- Full JavaScript power available
- Can add custom validation, effects, UI
- Not constrained by framework

### For Maintenance
- Cards grouped logically for balancing
- Helpers reduce duplication
- Clear patterns emerge naturally
- Easy to find and modify cards

### For Scaling
- Adding card #5 is as easy as adding card #50
- No cognitive overhead
- UI automatically adapts
- Framework doesn't get in the way

---

## Implementation Checklist

- [ ] Create helpers/combat.js with dealDamage, dealAoEDamage, rechargeEquipment
- [ ] Create helpers/validation.js with canUseEquipment, validateTarget
- [ ] Split cards.js into cards/weapons.js, cards/gear.js, cards/consumables.js, cards/robots.js
- [ ] Create cards/index.js to aggregate and re-export
- [ ] Refactor useWeapon to useEquipment with generic logic
- [ ] Create components/equipment/WeaponCard.jsx
- [ ] Create components/equipment/GearCard.jsx
- [ ] Create components/equipment/ConsumableCard.jsx
- [ ] Create components/equipment/EquipmentCard.jsx router
- [ ] Refactor CombatBoard.jsx to use EquipmentCard component
- [ ] Update existing card definitions with targetMode, chargeCost
- [ ] Test with existing cards
- [ ] Add new cards to validate system works

---

## Future Enhancements (Not Part of Initial Implementation)

These can be added later as patterns emerge:

- Damage type system (physical, fire, energy)
- Status effects (stun, burn, etc.)
- Shields and damage reduction
- Multi-stage effects (effects that persist across turns)
- Equipment synergies
- Upgrade system integration with card definitions
